Calculated from recorded token losses using historical USD prices at the incident time.
0xffcf45b540e6c9f094ae656d2e34ad11cdfdb187Ethereum0x009211344ee05ff3f69d9aadf0d3a0ab099c5363EthereumUniswap V1 token-specific exchanges for ERC777-backed assets exposed a reentrancy window on token-to-ETH trades. In the representative IMBTC exploit transaction 0x9437dde6c06a20f6d56f69b07f43d5fb918e6c57c97e1fc25a4162c693f578aa at block 9893295, attacker EOA 0x60f3fdb85b2f7faaa888ca7afc382c57f6415a81 used helper contract 0xbd2250d713bf98b7e00c26e2907370ad30f0891a to buy IMBTC from the Uniswap V1 IMBTC exchange 0xffcf45b540e6c9f094ae656d2e34ad11cdfdb187, then sell the position back while reentering through the ERC777 sender hook. The exchange paid out ETH before its token reserve was updated, so the nested sale priced against stale reserves and overpaid the attacker. The same helper pattern was later reused against the SideToken exchange 0x009211344ee05ff3f69d9aadf0d3a0ab099c5363.
Uniswap V1 deploys one exchange contract per token, but each exchange shares the same Vyper implementation. For token-to-ETH swaps, the exchange computes output from current reserves, pays ETH to the seller, and only then pulls tokens with transferFrom. That ordering is safe only if token transfers are callback-free. IMBTC and SideToken expose ERC777 semantics through ERC1820-discovered sender hooks, so a selling contract can run code before transferFrom finishes moving balances. Once the exchange has already sent ETH but has not yet received the sold tokens, a reentrant call can observe a reduced ETH reserve and an unchanged token reserve, which breaks the AMM settlement invariant.
The root cause is ERC777 sender-hook reentrancy in Uniswap V1 token-to-ETH settlement. The invariant is that a token-to-ETH trade must not leave the exchange in a state where ETH reserves are reduced while the corresponding token reserve increase is still unsettled. The verified Uniswap V1 code violates that invariant in both and by calling before . IMBTC can trigger on the selling helper before balances move, because the helper registered itself in ERC1820 as an implementer. The exploit transaction shows exactly one IMBTC buy followed by two half-size sells, which is the reentrant pattern enabled by that ordering bug. The SideToken transaction repeats the same pattern against a second exchange instance, confirming the issue is implementation-wide rather than pool-specific. The exploit requires three concrete conditions that are all met in the collected artifacts: the exchange token must implement ERC777-style hooks, the attacker must control a contract registered as an ERC777 sender hook, and the attacker must enter the token-to-ETH path where Uniswap sends ETH before completes. Those conditions map directly to the violated security principles in the incident: checks-effects-interactions ordering, avoiding callback-free assumptions for token transfers, and settling AMM reserve changes before yielding external control.
tokenToEthInputtokenToEthOutputsend(recipient, ...)self.token.transferFrom(buyer, self, tokens_sold)tokensToSendERC777TokensSendertransferFromThe vulnerable exchange logic is explicit in the verified source:
def tokenToEthInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
token_reserve: uint256 = self.token.balanceOf(self)
eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
assert wei_bought >= min_eth
send(recipient, wei_bought)
assert self.token.transferFrom(buyer, self, tokens_sold)
The same ordering appears in tokenToEthOutput, where send(recipient, eth_bought) also executes before self.token.transferFrom(...). That is the code-level breakpoint that makes the exploit possible.
IMBTC's verified source implements ERC777 interfaces and uses ERC1820 lookups to invoke sender and recipient hooks during token movement. The attacker's helper deployment transaction 0xfd92e015cf8d68361c23ba8fe0c359c6541c7af73f51def90613f10ef39f3ce7 contains constructor logic that registers the helper as both ERC777TokensSender and ERC777TokensRecipient in the global ERC1820 registry 0x1820a4b7618bde71dce8cdc73aab6c95905fad24.
The IMBTC exploit receipt shows the economic shape of the attack:
IMBTC purchase to helper: 175846212
IMBTC sale from helper to exchange: 87923106
IMBTC sale from helper to exchange: 87923106
Those values appear in the collected receipt and IMBTC token-flow artifacts for tx 0x9437dde6...578aa, proving a single purchase followed by two half-size sells in one transaction. The on-chain trace shows the control-flow step that matters: during IMBTC::transferFrom(helper, exchange, 87923106), IMBTC calls the helper's tokensToSend(...), and that callback reenters tokenToEthSwapInput(87923106, ...) before the first transfer updates the exchange's token reserve.
The balance diff confirms the exploit predicate was realized:
{
"attacker_delta_wei": "1452829133229421504",
"exchange_delta_wei": "-1457804579229421504",
"miner_fee_wei": "4975446000000000"
}
This is not a benign hook side effect. It is a direct reserve-accounting failure: the exchange prices the nested sale after already reducing ETH reserves with send, but before increasing token reserves with transferFrom, so the nested trade receives more ETH than a non-reentrant path would allow.
The attacker lifecycle is fully observable on-chain. First, EOA 0x60f3fdb85b2f7faaa888ca7afc382c57f6415a81 deployed helper contract 0xbd2250d713bf98b7e00c26e2907370ad30f0891a in tx 0xfd92e015cf8d68361c23ba8fe0c359c6541c7af73f51def90613f10ef39f3ce7. That deployment registered ERC1820 hook implementers so the helper could intercept ERC777 token movements.
Second, in tx 0x9437dde6c06a20f6d56f69b07f43d5fb918e6c57c97e1fc25a4162c693f578aa, the helper bought 175846212 IMBTC from the IMBTC exchange, approved the exchange, and invoked the token-to-ETH sell path. When Uniswap attempted to pull the first 87923106 IMBTC chunk, IMBTC executed the helper's sender hook before balance settlement. The helper used that callback to invoke a second tokenToEthSwapInput for the same 87923106 amount, causing two ETH payouts against stale token reserves. The exchange ended the transaction poorer in ETH, while the attacker EOA ended richer in ETH after gas.
Third, the same helper was later used against the SideToken exchange in tx 0x470d7e4f741e87022054ffa945aa63eb8e3e5a4271de9415dfde13ddd5b4984a. The SideToken balance diff shows the same one-unit token dust pattern on the helper and an 18.407710922812403392 ETH gain to the attacker EOA, confirming the exploit generalizes to other ERC777-backed V1 pools that share the same exchange implementation.
The collected surrounding transactions also identify downstream recipient EOAs 0xa4c55431acd5540ef11d15c1b87b1021c7626365 and 0xb701b2438a2b234ff1e2767e1adf93b9525ae6e2, which received post-exploit withdrawals from the attacker EOA. Additional relevant attacker-linked transactions captured in the session are 0xcb755debd5c0ee03accdf84701511e7c715f35a739a225f4fa7a71458e87311d, 0x3b2f9c4db8b3f2856da24087df9e387060bd61467bb5d908fb7088994bddd514, 0xac5c0ec932c70bc08478d0045377f71bd8fde27bf7b6f3b58423030f0db1d8a1, 0x9ee6dc021ca762e447bd9334e45d835f22785b5232df08bc6935aea1d7ac66c0, and 0xfae7310352b9e3c252959019a45163b8b909053fb8db44a18078ca662ce96093.
The representative IMBTC exploit transaction drained 1.457804579229421504 ETH from the IMBTC exchange, while the attacker realized 1.452829133229421504 ETH net of gas in the same transaction. The analysis also proves impact beyond a single pool: the SideToken exchange suffered the identical bug class and lost 18.411429707812403392 ETH in the sampled related exploit transaction. The affected parties were ERC777-backed Uniswap V1 exchange pools whose reserve accounting assumed ERC20-style transfers without reentrant sender callbacks.
0xfd92e015cf8d68361c23ba8fe0c359c6541c7af73f51def90613f10ef39f3ce70x9437dde6c06a20f6d56f69b07f43d5fb918e6c57c97e1fc25a4162c693f578aa0x470d7e4f741e87022054ffa945aa63eb8e3e5a4271de9415dfde13ddd5b4984a0xcb755debd5c0ee03accdf84701511e7c715f35a739a225f4fa7a71458e87311d, 0x3b2f9c4db8b3f2856da24087df9e387060bd61467bb5d908fb7088994bddd514, 0xac5c0ec932c70bc08478d0045377f71bd8fde27bf7b6f3b58423030f0db1d8a1, 0x9ee6dc021ca762e447bd9334e45d835f22785b5232df08bc6935aea1d7ac66c0, 0xfae7310352b9e3c252959019a45163b8b909053fb8db44a18078ca662ce960930xffcf45b540e6c9f094ae656d2e34ad11cdfdb1870x009211344ee05ff3f69d9aadf0d3a0ab099c53630x3212b29e33587a00fb1c83346f5dbfa69a4589230x09a8f2041be23e8ec3c72790c9a92089bc70fbca0x1820a4b7618bde71dce8cdc73aab6c95905fad240xa4c55431acd5540ef11d15c1b87b1021c7626365, 0xb701b2438a2b234ff1e2767e1adf93b9525ae6e2