Uniswap V1 imBTC ERC-777 Reentrant Microtrading Drain (2020-04-18)
- Name: Uniswap V1 imBTC ERC-777 reentrant microtrading drain
- Date: 2020-04-18
- Protocol: Uniswap V1
- Chain: Ethereum
- Category: AMM integration failure / ERC-777 sender-hook reentrancy / reserve-desynchronization pricing exploit
- Loss: about
1,278 ETHfrom the ETH-imBTC pool, per PeckShield’s incident accounting - Summary: Uniswap V1’s
tokenToEthSwapInput()path for token sales calculated the ETH output, sent ETH to the seller, and only then pulled tokens in withtransferFrom(). That ordering was survivable for inert ERC-20s, but imBTC exposed ERC-777 sender-hook behavior through ERC1820. When the attacker sold imBTC through a malicious contract, imBTC called the attacker’stokensToSend()hook in the middle of the swap. At that point the Uniswap exchange had already paid out ETH and reduced its ETH reserve, but had not yet received the sold imBTC or increased its token reserve. The attacker used that callback window to re-entertokenToEthSwapInput()again and again, so each nested sale was priced against too little ETH and not yet enough imBTC. The exploit did not fake balances inside Uniswap; it repeatedly forced Uniswap V1 to quote with one side of the pool updated and the other still stale, draining ETH from the imBTC pool through what OpenZeppelin later calledreentrant microtrading. - Trigger / entry point:
- The Uniswap V1 imBTC exchange sold ETH before finishing token intake in
tokenToEthInput(). - imBTC supported ERC-777-style sender hooks discoverable through the ERC1820 registry.
- The attacker registered a malicious sender implementer and sold imBTC from a contract, so
tokensToSend()executed before the exchange’stransferFrom()completed.
- The Uniswap V1 imBTC exchange sold ETH before finishing token intake in
- Root cause:
- The core mistake was AMM pricing against mid-transaction reserve state: ETH reserve down first, token reserve up later.
- Uniswap V1 treated
transferFrom()like a passive asset movement instead of a full control-flow handoff that could re-enter pricing logic. - Because pricing in constant-product AMMs depends on both reserves, temporarily updating only the ETH side made each nested token sale too favorable to the attacker.
- Exploit path:
- The attacker prepared a contract able to sell imBTC and receive the ERC-777 sender-hook callback during
transferFrom(). - The attacker called the Uniswap V1 imBTC exchange’s
tokenToEthSwapInput(). - Inside
tokenToEthInput(), Uniswap computed the ETH output using the current pool reserves and immediately sent ETH to the attacker contract. - Uniswap then attempted to pull in the sold imBTC with
IMBTC.transferFrom(). - imBTC invoked the attacker’s
tokensToSend()hook before finalizing the transfer. - During that callback, the attacker re-entered
tokenToEthSwapInput()and sold another batch of imBTC while the exchange’s ETH reserve was already lower but the imBTC reserve had not yet risen. - Each nested sale therefore received a better ETH price than honest sequential trading would have allowed, because the denominator side of the pricing function was artificially frozen while the numerator side had already moved.
- The attacker repeated the callback-driven loop until enough ETH had been extracted, then let the outer calls finish and finally transferred the imBTC in.
- The attacker prepared a contract able to sell imBTC and receive the ERC-777 sender-hook callback during
- What the attacker needed to be true:
- The listed token had to expose a hook-capable transfer path that could yield execution before the exchange finished reserve updates.
- The token-sale function had to send ETH before the token reserve was actually updated.
- The attacker needed enough imBTC inventory and gas budget to make repeated nested sales worthwhile.
- The pool needed meaningful ETH liquidity so the mispricing could be monetized before slippage and fees made further recursion unattractive.
- Technical anatomy worth remembering:
- This was not a classic
withdraw twice before balance updateexploit. The profit came from forcing an AMM to price sequential sales as if the token reserve had not yet increased. - The decisive callback was the ERC-777 sender hook, not a recipient hook on the Uniswap exchange.
- OpenZeppelin’s earlier 2019 disclosure had already described the same primitive as
reentrant microtrading: many small nested token sales become more profitable than honest sequential sales because reserve updates are observed asymmetrically. - The exploit is a useful AMM counterpart to the next day’s Lendf.Me incident. In Uniswap, the callback corrupted swap pricing. In Lendf.Me, the same token standard corrupted collateral accounting.
- The deeper lesson is not merely
ERC-777 is scary; it is that reserve-based pricing code is only safe if both sides of the trade become true before control can leave the function.
- This was not a classic
Three reserve-and-swap-authority promotions this incident collapsed
- A callback-capable token-sale edge became live at all
- Uniswap’s
tokenToEthInput()path had already crossed the meaningful boundary from local math into external execution: it sent ETH and then entered a token-transfer path that could trigger ERC-777 sender hooks through ERC1820. - At this stage the strongest safe statement was only
the swap is mid-settlement and attacker-controlled code can still run, notthe pool has already reached one settled reserve truth for this sale.
- Uniswap’s
- That half-updated reserve state was promoted into canonical quote truth for the next sale
- This was the decisive failure. Each nested
tokenToEthSwapInput()call reused a pool view where ETH reserves had already gone down but imBTC reserves had not yet gone up. - Uniswap therefore treated one-sided reserve movement as if it were a money-good state from which the next token sale could be quoted, even though the missing token-side reserve update had only been delayed by the still-open hook frame.
- This was the decisive failure. Each nested
- That accepted quote truth still deserved immediate ETH-spend authority on every nested re-entry
- Once each re-entrant quote was computed, the exchange still honored it with real ETH payouts before the corresponding imBTC had become authoritative pool inventory.
- The exploit therefore did not require one giant invariant bypass; it only required that each attacker-authored intermediate quote retained ordinary ETH-release authority while settlement was still incomplete.
- Representative transactions and artifacts:
- Main exploit transaction analyzed by PeckShield:
0x9cb1d93d6859883361e8c2f9941f13d6156a1e8daa0ebe801b5d0b5a612723c1 - OpenZeppelin proof-of-concept and writeup describing the same Uniswap V1 ERC-777 attack primitive:
openzeppelin/exploit-uniswap - Tokenlon / imBTC incident notice documenting the April 18 suspension timeline: “About Recent Uniswap and Lendf.Me Reentrancy Attacks”
- Main exploit transaction analyzed by PeckShield:
- Response / mitigation:
- Tokenlon identified the Uniswap incident as a P0 security issue and temporarily suspended imBTC transfers while downstream integrations were evaluated.
- Tokenlon later resumed imBTC transfers after partner review, then suspended again during the subsequent Lendf.Me exploit response.
- The broader ecosystem response was architectural rather than administrative: newer AMM designs and adapter guidance treated token transfers as potentially callback-capable control-flow surrender, and Uniswap V2 changed the swap sequencing model so output settlement no longer relied on the same vulnerable
send ETH, then pull tokenpath.
Why this incident matters
- It preserves one of the cleanest early examples of an AMM being exploitable not because its invariant formula was wrong, but because reserve updates became temporarily one-sided during a hook callback.
- It shows why
open listingandsupports ERC-20 compatibilityare not enough as integration assumptions. Token semantics can change the timing model of core exchange logic. - Together with Lendf.Me, it is a canonical reminder that callback-capable token standards can break very different DeFi surfaces — swap pricing, collateral accounting, and later reward or borrow paths — through the same hidden control-flow edge.
Pattern tags
- Sender-side callback-capable swap asset: the exploitable control transfer happened in the ERC-777
tokensToSend()hook triggered duringtransferFrom(), not in a plain ETH send or an obvious recipient-only callback surface. - Reserve-asymmetry pricing window: the exploit did not need to falsify reserves permanently; it only needed one side of the AMM reserve pair to become economically real before the other side did.
- Reentrant microtrade sequencing abuse: the profit came from splitting one larger token sale into many nested in-transaction sales that all reused the temporarily frozen token-reserve denominator.
- ERC1820-mediated semantic drift: the deeper integration failure was assuming an asset that looked ERC-20-compatible still preserved ERC-20-style inert transfer semantics during swap settlement.
Why the exploit worked
- Uniswap V1’s token-sale path paid out ETH before actually receiving the sold tokens, so the exchange exposed price-sensitive mid-swap state to an external callback boundary.
- imBTC preserved ERC-777 sender-hook behavior through ERC1820, which meant
transferFrom()was not a passive asset movement but an attacker-controlled execution window. - Constant-product pricing made that callback economically meaningful: each nested sale saw lower ETH reserves but the same token reserves, so the exchange kept paying slightly too much ETH for each additional batch of imBTC.
- The exploit therefore compounded through sequencing, not through a single giant invariant break. Reentrancy let the attacker manufacture an artificially favorable series of quotes inside one transaction before the token side of the trade caught up.
- Operationally, the incident also showed that
open listingwithout semantic admission discipline lets exotic token behavior import new control-flow assumptions directly into core AMM pricing logic.
Detection and audit pivots
- In every AMM sell path, trace whether either reserve becomes economically live before the counter-reserve has actually been updated, especially around token transfers, ETH sends, and adapter callbacks.
- Inventory listed assets for ERC-777 hooks, ERC1820 lookups, fee-on-transfer semantics, rebasing behavior, or any transfer path that can change the control-flow or accounting assumptions embedded in swap code.
- Fuzz
sell -> hook callback -> reenter sell againsequences with hook-capable mock tokens and compare payout curves against honest sequential trading; if nested in-transaction sales beat the legitimate curve, the pricing boundary is wrong. - Ask whether reserve reads used by pricing functions remain coherent at every callback edge rather than only at full function exit.
- Treat asset-listing review as execution-model review, not only standards-surface review. The key question is not
does the token transfer?butwhat code can run, and what reserve state is visible, before both sides of the swap become true? - Look for profitability thresholds, not only logical possibility. A callback-capable market may sit dormant until enough ETH liquidity accumulates for microtrade sequencing to pay for gas and slippage.
Failure-prevention checklist
- Make swap settlement observe actual balances after external token movement rather than trusting a pre-transfer reserve story. A price-sensitive path should not pay out against reserves that only become true on one side first.
- Finalize the accounting meaning of both sides of the trade before any callback-capable edge can re-enter pricing logic. If asset delivery can run external code, either both reserve changes are already authoritative or the function should fail closed.
- Treat token listing as execution-model admission. If an asset can dispatch ERC777 / ERC1820 hooks or otherwise change control flow during
transferFrom(), prove the swap path stays price-correct at that edge or isolate the asset behind a safer adapter or wrapper. - Regression-test
sell -> hook callback -> reenter sell againand compare the cumulative output to honest sequential trading over the same total size. Nested in-transaction selling should never beat the legitimate curve just because reserve updates land asymmetrically. - Avoid sequencing where the pool pays out one side before verifying the other side has been received or made authoritative.
Output first, reserve truth lateris not a harmless implementation detail in price-sensitive code. - Keep listing / pause runbooks ready for token-semantics surprises. Tokenlon’s own timeline showed that once imBTC’s sender-hook behavior broke one venue, every venue using the same transfer assumptions needed immediate re-evaluation.
- Prefer AMM architectures where invariant enforcement is checked against post-transfer balances instead of optimistic mid-function reserve variables that can be observed during callbacks.
Audit prompts
- At every swap edge, what exact reserve state is economically real before control can leave the function, and is any pricing logic still willing to quote against a half-updated reserve pair?
- Can
transferFrom()or any adapter call during token intake execute user-controlled code, and if so, what invariant proves re-entry cannot get a better quote than honest sequential trading? - Are reserves measured from canonical post-transfer balances, or from optimistic local variables that become stale the moment an external call occurs?
- If one listed asset preserves ERC777 / ERC1820 semantics, what exact containment layer keeps those semantics from leaking directly into swap pricing?
- Do tests prove only that the invariant is restored at final function exit, or also the stronger property that no callback edge exposes an exploitable intermediate price?
- If a neighboring protocol discovers that a listed asset can callback during transfer, what operational rule says this AMM market must be paused, wrapped, or re-reviewed before trading resumes?
- What gas, liquidity, or fee regime makes a logically possible reentrant microtrade actually profitable here, and are those thresholds monitored?
- Which exact design choice prevents
same token, same hook, different venuefrom reproducing the same exploit family across multiple integrations?
Lessons that should already be standard
- Reserve math can be right and swap sequencing can still be exploitable. Uniswap V1’s formula was not the core failure; the failure was letting only one side of the reserve pair become economically real before callbacks could re-enter pricing.
- Sender-side hooks matter. Teams that look only for receiver callbacks will miss the exact edge that broke both imBTC incidents.
Open listingis security-sensitive execution-model admission. Supporting a token whose transfer can hand control away is not merely an asset-expansion decision.- Reentrancy in AMMs is not always
withdraw twice; sometimes it isquote the same trade family against a reserve denominator that has not caught up yet. - Profitability thresholds hide real bugs. A callback-capable pool can sit apparently fine until ETH depth, gas conditions, and attacker inventory make microtrade sequencing worth executing.
- Cross-protocol warning signals count. Once imBTC’s sender-hook behavior broke Uniswap, other venues relying on inert-transfer assumptions should have treated that as an immediate solvency warning, not just someone else’s incident.
- The clean design question is simple: before any callback can run, are both sides of the swap already true enough that re-entry cannot obtain a better price than honest trading?
Internal linkages
- Core recurring pattern: reentrancy.
- Asset-integration lens: interface-conformance-semantic-compatibility-failure — the deeper mistake was treating an ERC-777-hooked asset as if token transfer into the AMM were a passive ERC-20 event.
- Positive-design companion: actual-receipt-accounting-and-token-semantics-admission-discipline — the healthier answer was to treat
what exact transfer semantics are we admitting into reserve truth and swap settlement?as a first-class design boundary instead of an implicit listing assumption. - Closest companion incident: lendf-me-imbtc-erc777-reentrancy-2020-04-19 — same token standard, different payoff surface.
- Adjacent arithmetic-pricing cousin: balancer-sta-deflationary-token-gulp-pool-drain-2020-06-28 — different token semantics, but the same general lesson that AMM reserve logic silently encodes assumptions about token behavior.
Control surface
-
The leverage sat in token-listing openness, ERC1820 hook registration, swap-function ordering, and the assumption that a token transfer could not re-enter price-sensitive logic mid-swap.
-
The exploit was not
imBTC broke Uniswap. It was Uniswap V1 letting a hook-capable token decide when its reserves became economically real. -
Sources:
- https://peckshield.medium.com/uniswap-lendf-me-hacks-root-cause-and-loss-analysis-50f3263dcc09
- https://www.openzeppelin.com/news/exploiting-uniswap-from-reentrancy-to-actual-profit
- https://github.com/OpenZeppelin/exploit-uniswap
- https://support.tokenlon.im/hc/en-us/articles/360042063572-About-Recent-Uniswap-and-Lendf-Me-Reentrancy-Attacks
- https://eips.ethereum.org/EIPS/eip-777
- https://eips.ethereum.org/EIPS/eip-1820
-
Last reviewed: 2026-08-02 UTC