All incidents

Fake Pool Callback Drain

Share
Apr 11, 2025 11:38 UTCAttackLoss: 22.51 WETH, 27,260 USDCPending manual check1 exploit txWindow: Atomic
Estimated Impact
22.51 WETH, 27,260 USDC
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Apr 11, 2025 11:38 UTC → Apr 11, 2025 11:38 UTC

Exploit Transactions

TX 1Base
0x1a6002d8aee205dff67cb2cdaf60569721655857d49ffe2ce81e10fde8c45946
Apr 11, 2025 11:38 UTCExplorer

Victim Addresses

0x607742a2adea4037020e11bb67cb98e289e3ec7dBase
0xddddf3d84a1e94036138cab7ff35d003c1207a77Base

Loss Breakdown

22.51WETH
27,260USDC

Similar Incidents

Root Cause Analysis

Fake Pool Callback Drain

1. Incident Overview TL;DR

On Base block 28791090, EOA 0xcfad03a6f9dc4007eb3716fee51f108b00d6736d sent transaction 0x1a6002d8aee205dff67cb2cdaf60569721655857d49ffe2ce81e10fde8c45946 to its deployed helper 0x780e5Cb8DE79846f35541b700637057c9dddEd68. That helper impersonated a Uniswap V3 pool and called 0x607742a2adea4037020e11bb67cb98e289e3ec7d::uniswapV3SwapCallback twice, causing approved payer 0xddddf3d84a1e94036138cab7ff35d003c1207a77 to lose 22.51 WETH and 27,260 USDC.

The root cause is a callback-authentication failure. The victim helper trusted arbitrary msg.sender, queried token0() or token1() from that caller, and then executed transferFrom(payer, msg.sender, amount) without verifying that the caller was a canonical Uniswap V3 pool.

2. Key Background

Uniswap V3 swap callbacks are safe only when the callee authenticates the callback sender as the deterministic pool for the intended token pair and fee tier. If a callback implementation derives token identity from msg.sender before authenticating the caller, a fake pool can return arbitrary token addresses and redirect settlement to itself.

That is the relevant context here. The victim helper at 0x607742a2adea4037020e11bb67cb98e289e3ec7d had approval to spend both WETH and USDC from payer 0xddddf3d84a1e94036138cab7ff35d003c1207a77 before the exploit block. Pre-state observations at block 28791089 show at least 22517012217948887448 WETH, 27260506734 USDC, and ample allowances to the helper for both assets.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ATTACK-category ACT exploit against an unauthenticated callback settlement helper. The vulnerable function is selector 0xfa461e33 on 0x607742a2adea4037020e11bb67cb98e289e3ec7d, corresponding to uniswapV3SwapCallback.

The bytecode shows two critical behaviors. First, the helper reads CALLER and issues a STATICCALL using selector 0x0dfe1681, which is token0(), and elsewhere the symmetric branch performs the equivalent caller-derived token lookup for the other side. Second, after resolving the token address, the helper builds selector 0x23b872dd and executes transferFrom(payer, CALLER, amount). The relevant disassembly fragments are:

0000017c: CALLER
...
0000019e: PUSH4 0x0dfe1681
...
000001c0: STATICCALL
...
0000020a: PUSH4 0x23b872dd
00000210: CALLER

and:

0000033f: PUSH4 0x23b872dd
00000345: CALLER

The broken invariant is: only the canonical Uniswap V3 pool for the expected swap path may trigger callback settlement, and settlement may only pull the owed token for that authenticated pool from the designated payer. The code-level breakpoint is the settlement branch inside uniswapV3SwapCallback where the helper queries token addresses from msg.sender and transfers funds to msg.sender before any factory validation.

4. Detailed Root Cause Analysis

The exploit pre-state is straightforward. At Base block 28791089, the victim helper was already deployed, the payer already held both target assets, and the payer had already approved the helper. That meant an attacker only needed a contract that implemented the expected callback surface and returned attacker-chosen token addresses.

The on-chain trace shows exactly that sequence. The fake pool 0x780e5Cb8DE79846f35541b700637057c9dddEd68 called the victim helper twice:

0x607742A2Adea4037020e11Bb67CB98E289E3eC7D::uniswapV3SwapCallback(
  -125859570852398,
  22510000000000000000,
  0x...ddddf3d84a1e94036138cab7ff35d003c1207a77...
)
  0x780e5Cb8DE79846f35541b700637057c9dddEd68::token1() -> 0x4200000000000000000000000000000000000006
  WETH9::transferFrom(
    0xddddF3D84a1E94036138Cab7ff35d003c1207A77,
    0x780e5Cb8DE79846f35541b700637057c9dddEd68,
    22510000000000000000
  )

The second callback repeated the same pattern for USDC:

0x607742A2Adea4037020e11Bb67CB98E289E3eC7D::uniswapV3SwapCallback(
  -125859570852398,
  27260000000,
  0x...ddddf3d84a1e94036138cab7ff35d003c1207a77...
)
  0x780e5Cb8DE79846f35541b700637057c9dddEd68::token1() -> 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
  FiatTokenV2_2::transferFrom(
    0xddddF3D84a1E94036138Cab7ff35d003c1207A77,
    0x780e5Cb8DE79846f35541b700637057c9dddEd68,
    27260000000
  )

This is sufficient to establish the mechanism. The attacker controlled msg.sender, controlled the return value of token1(), and therefore controlled which token contract the victim helper used for settlement. Because the helper never authenticated the callback sender against a Uniswap V3 factory, the attacker could redirect any token for which the payer had granted allowance and still held balance.

The balance diff confirms the exact asset movement. The payer lost 22510000000000000000 WETH and 27260000000 USDC, while the fake pool gained the same amounts. The exploit therefore realized the stated ACT predicate directly through unauthorized allowance use rather than through price manipulation, privileged access, or hidden attacker-side artifacts.

5. Adversary Flow Analysis

The adversary cluster consists of EOA 0xcfad03a6f9dc4007eb3716fee51f108b00d6736d and its nonce-0 deployed fake pool 0x780e5Cb8DE79846f35541b700637057c9dddEd68. The EOA submitted the exploit transaction, and the fake pool was the direct recipient of the stolen assets.

The end-to-end flow was:

  1. The attacker deployed a fake pool contract that exposed the expected callback-facing surface and could return arbitrary token addresses.
  2. The fake pool called the victim helper's uniswapV3SwapCallback with attacker-chosen deltas and calldata encoding the payer.
  3. During the first callback, the fake pool returned WETH from token1(), and the victim helper transferred 22.51 WETH from the payer into the fake pool.
  4. During the second callback, the fake pool returned USDC from token1(), and the victim helper transferred 27,260 USDC from the payer into the fake pool.
  5. The attacker-controlled fake pool retained custody of both assets and the EOA only paid gas and L1 fee overhead.

Because all required ingredients were public and permissionless, any unprivileged adversary could reproduce the exploit by deploying an equivalent fake pool contract against a payer that had granted approval to the vulnerable helper.

6. Impact & Losses

The exploit drained two ERC20 assets from the approved payer in one transaction:

  • WETH: raw loss "22510000000000000000" with decimal 18
  • USDC: raw loss "27260000000" with decimal 6

The payer address 0xddddf3d84a1e94036138cab7ff35d003c1207a77 was reduced to 7012217948887448 WETH and 506734 USDC in the traced transaction. The attacker EOA paid 386974209882 wei in fees, while the attacker-controlled contract received the stolen ERC20 balances.

7. References

  • Seed transaction metadata: 0x1a6002d8aee205dff67cb2cdaf60569721655857d49ffe2ce81e10fde8c45946
  • Seed trace showing the two callback-triggered transferFrom executions into the fake pool
  • Seed balance diff confirming payer losses and attacker-pool gains for WETH and USDC
  • Victim helper disassembly for selector 0xfa461e33 and the CALLER-derived transferFrom branches
  • Attacker helper disassembly showing the attacker-controlled callback surface and token-selection logic
  • Pre-state observations at block 28791089 confirming balances, allowances, and fake-pool deployment address derivation