Calculated from recorded token losses using historical USD prices at the incident time.
0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c780xe77ec1bf3a5c95bfe3be7bdbacfe3ac1c7e454cdEthereum0x732276168b421d4792e743711e1a48172ea574a2EthereumOn Ethereum mainnet, transaction 0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c78 exploited the MINER ERC_X token at 0xe77ec1bf3a5c95bfe3be7bdbacfe3ac1c7e454cd. The adversary sent the transaction from 0xea75aec151f968b8de3789ca201a2a3a7faeefba to an attacker-controlled helper contract at 0xbff51c9c3d50d6168dfef72133f5dbda453ebf29.
The root cause was an ERC20 self-transfer accounting bug. ERC_X loaded the sender and recipient balances before updating either one, then wrote the debit and credit separately. When from == to, both writes addressed the same _balances slot and the second write overwrote the debit with originalBalance + value, inflating the caller's MINER balance.
The attacker used this during a Uniswap V3 callback for the WETH/MINER pool 0x732276168b421d4792e743711e1a48172ea574a2. The pool released 28210613620184568130 WETH raw units to the attacker contract, and the attacker satisfied the callback with MINER units synthesized by repeated self-transfers.
ERC_X is a hybrid ERC20/ERC1155-style token. Its ERC20 transfer(address,uint256) entrypoint calls _transfer(owner, to, value, true), which then calls _update(from, to, value, mint). The mint flag controls additional NFT-style side effects when balances cross boundaries, but the core fungible accounting happens in .
tokensPerNFT_updateAt the incident block, the WETH/MINER Uniswap V3 pool at 0x732276168b421d4792e743711e1a48172ea574a2 was a live venue accepting MINER as token1. ERC_X also had launch restrictions such as transferDelay and whitelist checks, but the auditor evidence records that transferDelay was disabled at the relevant fork point and the pool path passed the necessary transfer checks.
The transaction was included in block 19226599. The ACT pre-state is Ethereum mainnet at the end of block 19226598, before the exploit transaction. The strategy did not require private keys, privileged protocol access, or attacker-side secrets: a fresh adversary could deploy its own callback receiver, interact with the public pool, and call the public MINER transfer function.
The vulnerability class is broken token accounting on aliased balance slots. ERC20 self-transfers should be balance no-ops: after transfer(address(this), amount), the sender's balance should be unchanged except for gas spent by the transaction sender. ERC_X violated that invariant because _update cached both balances before writing either balance slot. For a normal transfer, the debit and credit affect different mappings and conserve total user balances. For a self-transfer, the same mapping key is debited and then credited using the old cached recipient balance, so the final value becomes oldBalance + amount.
The exploit used this bug as a settlement primitive inside a Uniswap V3 swap callback. The attacker requested WETH output from the pool and owed MINER token1 back to the pool. During the callback, the attacker repeatedly called MINER.transfer(attackerContract, 499999999999999999) to inflate its own MINER balance, then transferred MINER to the pool. The pool observed that its MINER balance had increased enough to satisfy the callback delta and finalized the swap.
The relevant ERC_X source path is:
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = msg.sender;
_transfer(owner, to, value, true);
return true;
}
function _transfer(address from, address to, uint256 value, bool mint) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value, mint);
}
function _update(address from, address to, uint256 value, bool mint) internal virtual {
uint256 fromBalance = _balances[from];
uint256 toBalance = _balances[to];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
_balances[from] = fromBalance - value;
_balances[to] = toBalance + value;
}
emit Transfer(from, to, value);
...
}
For from != to, this implements a conventional debit and credit. For from == to, fromBalance and toBalance are the same original value. The first write stores originalBalance - value; the second write stores originalBalance + value to the same storage slot. The safety invariant is therefore violated at the second _balances[to] assignment.
The trace confirms that this was not theoretical. Inside the Uniswap V3 callback, the attacker contract repeatedly called MINER with the same contract as sender and recipient:
ERC_X::transfer(0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29, 499999999999999999)
emit Transfer(
from: 0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29,
to: 0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29,
value: 499999999999999999
)
storage changes:
attacker balance slot: ...30927f74c9de0000 -> ...3782dace9d8fffff
The trace then shows the attacker transferring MINER to the pool:
ERC_X::transfer(0x732276168b421D4792E743711E1A48172EA574a2, 499999999999999999)
emit Transfer(
from: 0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29,
to: 0x732276168b421D4792E743711E1A48172EA574a2,
value: 499999999999999999
)
The seed balance diff records the aggregate effect. The pool's MINER balance increased from 20760445641193926256214 to 21760445641193926254214, a delta of 999999999999999998000. This matches the token1 callback obligation recorded in the swap trace.
The top-level transaction called the attacker helper with selector 0x95805dad and argument 2000, decoded by the trace as start(2000). The helper initiated a Uniswap V3 swap against the WETH/MINER pool with zeroForOne=false and amountSpecified=999999999999999998000, requesting WETH out and accepting a MINER token1 payment obligation.
The pool transferred WETH to the attacker contract, then invoked the Uniswap V3 callback with amount0Delta = -28210613620184568130 and amount1Delta = 999999999999999998000. A negative token0 delta represents WETH output to the attacker; a positive token1 delta represents MINER owed to the pool.
During the callback, the attacker used the public MINER transfer function in an alternating loop. First, it self-transferred 499999999999999999 MINER raw units to inflate its own balance. Then it transferred MINER to the pool. Repeating this sequence allowed the pool's MINER balance to rise by the required amount without the attacker holding legitimate pre-existing MINER for the full payment.
The pool then emitted the final swap event:
Swap(
param0: 0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29,
param1: 0xBfF51C9C3D50d6168DFEF72133F5DBda453EbF29,
param2: -28210613620184568130,
param3: 999999999999999998000,
...
)
The adversary-related accounts are:
EOA sender and gas payer:
0xea75aec151f968b8de3789ca201a2a3a7faeefba
Attacker-controlled helper and WETH recipient:
0xbff51c9c3d50d6168dfef72133f5dbda453ebf29
The victim components are:
MINER ERC_X token:
0xe77ec1bf3a5c95bfe3be7bdbacfe3ac1c7e454cd
WETH/MINER Uniswap V3 pool:
0x732276168b421d4792e743711e1a48172ea574a2
The measurable pool output was 28210613620184568130 WETH raw units, equivalent to 28.210613620184568130 WETH with 18 decimals. The transaction sender paid 640119611819849142 wei in native ETH gas cost according to the seed balance diff, and the attacker helper received the WETH output from the pool.
The MINER side of the swap was not economically valid input from the attacker. The pool received 999999999999999998000 MINER raw units that were produced through ERC_X self-transfer inflation during the callback. The impact was therefore a WETH extraction from the WETH/MINER pool in exchange for inflated MINER accounting units.
Primary exploit transaction: 0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c78 on Ethereum mainnet, block 19226599.
Seed metadata: /workspace/session/artifacts/collector/seed/1/0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c78/metadata.json.
Verbose transaction trace: /workspace/session/artifacts/collector/seed/1/0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c78/trace.cast.log.
Balance diff: /workspace/session/artifacts/collector/seed/1/0x75e3aeb00df69882a1b15d424e5e642650326ca3b923d7fd1922d57c51bc2c78/balance_diff.json.
Verified ERC_X source: /workspace/session/artifacts/collector/seed/1/0xe77ec1bf3a5c95bfe3be7bdbacfe3ac1c7e454cd/src/Contract.sol.
Validator challenge result: /workspace/session/artifacts/validator/root_cause_challenge_result.json.