USDC drain via unchecked Uniswap V3-style callback
Exploit Transactions
0x6be0c4b5414883a933639c136971026977df4737b061f864a4a04e4bd7f07106Victim Addresses
0x8d2ef0d39a438c3601112ae21701819e13c41288BaseLoss Breakdown
Similar Incidents
Base USDC drain from malicious transferFrom spender approvals
37%Base DUCKVADER infinite mint + Uniswap drain
37%Treasury allowance-router abuse drains USDC/USDT/WBTC cross-chain
33%Unauthorized WETH drain via unprotected Uniswap V3 callback
32%Odos router signature-validation bug enables arbitrary token drains
31%TSURUWrapper onERC1155Received bug mints unbacked tokens and drains WETH
31%Root Cause Analysis
USDC drain via unchecked Uniswap V3-style callback
1. Incident Overview TL;DR
On Base (chainid 8453), adversary EOA 0x4efd5f0749b1b91afdcd2ecf464210db733150e0 called helper contract 0x2a59ac31c58327efcbf83cc5a52fae1b24a81440, which triggered a Uniswap V3-style swap callback in victim contract 0x8d2ef0d39a438c3601112ae21701819e13c41288. During this callback, the victim instructed USDC FiatTokenV2_2 at 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 to transfer 40,000 USDC from the victim-held balance to the adversary EOA in a single transaction. The incident is an ACT-style, single-transaction drain that relies only on public chain state, standard EIP-1559 transaction rules, and unprivileged contracts.
The root cause is that the victim contract implements a Uniswap V3-like callback that transfers USDC to msg.sender without checking that msg.sender is an authentic Uniswap V3 pool derived from the canonical factory and token pair. Any contract that can reach this callback while the victim holds USDC can therefore pull funds directly.
2. Key Background
The victim is an unverified contract on Base at 0x8d2ef0d39a438c3601112ae21701819e13c41288 that holds USDC (FiatTokenV2_2 at 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913) and participates in Uniswap-style liquidity operations. Heimdall decompilation of the victim shows multiple Uniswap-related constants and calls into router and factory-like addresses, as well as a callback-style function that sends tokens to msg.sender:
// Decompiled snippet from victim 0x8d2e... (Uniswap-related paths)
uint256 public constant UNISWAP_V3_FACTORY = 291572013138592638916390241167448300373264498173;
uint256 public constant UNISWAP_V3_ROUTER = 217797988974002275012249995888057192686247076993;
...
(bool success, bytes memory ret0) = address(sourceToken / 0x01).balanceOf(var_i); // staticcall
...
require(address(sourceToken / 0x01) < (address(defaultTickSpacing / 0x01)));
...
var_o = 0x28af8d0b...;
(bool success, bytes memory ret0) = address(0x5e7bb104d84c7cb9b682aac2f3d509f5f406809a).getPool(var_k, var_l, var_n); // staticcall
The helper contract at 0x2a59ac31c58327efcbf83cc5a52fae1b24a81440 is also unverified and decompiled. It contains a fallback that can move ERC20 tokens by calling transferFrom on a hard-coded token address, indicating it is designed to orchestrate token flows for the origin EOA:
// Decompiled helper 0x2a59... (excerpt)
fallback() external payable {
...
var_g = 0x23b872dd...; // transferFrom selector
address var_h = msg.sender;
address var_i = address(this);
var_j = 0x0de0b6b3a7640000;
(bool success, bytes memory ret0) =
address(0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c).Unresolved_23b872dd(var_h); // call
...
}
USDC itself is the standard Circle FiatTokenV2_2 implementation. The incident artifacts include its verified source mapped to the Base deployment, and the ERC20 balance diffs confirm that USDC enforces correct debits and credits; the incorrect transfer originates entirely from the victim’s callback logic.
3. Vulnerability Analysis & Root Cause Summary
This incident is categorized as an ATTACK. The vulnerability is a missing authentication check on a Uniswap V3-style swap callback. The victim contract assumes that only canonical Uniswap pools will ever call back into it, but the code does not enforce this assumption.
The intended invariant is that victim-held USDC should only leave the victim contract in response to valid Uniswap V3 swaps routed through authentic pools derived from a known factory and token pair. The concrete breakpoint is the victim’s callback-equivalent logic that transfers USDC to msg.sender without verifying that msg.sender is a legitimate pool address.
Because msg.sender is completely unguarded, an adversary-controlled helper contract can invoke the callback path directly while the victim holds USDC, causing the victim to instruct USDC to transfer tokens to the adversary EOA. This turns a settlement-only callback into a direct withdrawal primitive.
4. Detailed Root Cause Analysis
The attack is realized in a single Base transaction:
{
"chainid": 8453,
"txhash": "0x6be0c4b5414883a933639c136971026977df4737b061f864a4a04e4bd7f07106"
}
The trace for this transaction shows the following high-level call chain:
Seed transaction trace for 0x6be0c4b5... (cast run -vvvvv, excerpt)
0x4efd5f0749b1b91afdcd2ecf464210db733150e0
-> 0x2a59ac31c58327efcbf83cc5a52fae1b24a81440 (selector 0x8cceaf1f)
-> 0x8d2ef0d39a438c3601112ae21701819e13c41288 (victim callback path)
-> 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 (FiatTokenV2_2 USDC).transfer(...)
The corresponding ERC20 balance changes in balance_diff.json are:
{
"erc20_balance_deltas": [
{
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"holder": "0x8d2ef0d39a438c3601112ae21701819e13c41288",
"before": "40000000000",
"after": "0",
"delta": "-40000000000",
"contract_name": "FiatTokenV2_2"
},
{
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"holder": "0x4efd5f0749b1b91afdcd2ecf464210db733150e0",
"before": "0",
"after": "40000000000",
"delta": "40000000000",
"contract_name": "FiatTokenV2_2"
}
]
}
With 6 decimals, 40,000,000,000 token units correspond to exactly 40,000 USDC, debited from the victim and credited to the adversary EOA.
The victim’s decompiled contract shows Uniswap V3-style behavior: it queries pools from a factory-like address, emits events when pools are discovered, and routes through router-like components. However, in its callback path it uses msg.sender directly when sending tokens, with no restriction that msg.sender must be the canonical pool. In effect, any contract that can drive the victim into this path while the victim holds USDC can cause a transfer from the victim to itself or a designated recipient.
The helper contract 0x2a59... is used by the adversary to construct such a call. The seed trace shows that selector 0x8cceaf1f is sent from the adversary EOA to 0x2a59..., which then calls the victim contract and steers execution into the vulnerable callback logic. Because the victim does not authenticate msg.sender in this callback, it issues a USDC transfer that moves 40,000 USDC from the victim’s balance to the adversary’s EOA.
USDC’s own implementation is correct: the balance_diff.json layout metadata ties the token to a verified FiatTokenV2_2 deployment, and the deltas are consistent with a single ERC20 transfer. There is no indication of reentrancy, mis-accounting, or non-standard token behavior. The entire exploit hinges on the victim’s missing msg.sender validation in the Uniswap V3-style callback.
5. Adversary Flow Analysis
The adversary’s strategy is a single-tx, ACT-style drain that uses only public on-chain information and unprivileged contracts.
- Adversary cluster. The analysis identifies:
- EOA
0x4efd5f0749b1b91afdcd2ecf464210db733150e0on Base as the adversary profit-taker. This EOA sends the exploit transaction and receives the full 40,000 USDC net increase. - Contract
0x2a59ac31c58327efcbf83cc5a52fae1b24a81440on Base as an adversary-controlled helper. It is called by the adversary EOA and in turn triggers the victim’s callback.
- EOA
- Victim candidate. Contract
0x8d2ef0d39a438c3601112ae21701819e13c41288on Base is the USDC-holding Uniswap V3 callback contract that loses 40,000 USDC in the exploit transaction.
The adversary lifecycle in this incident has a single critical stage:
- Adversary exploit transaction (block 34,459,414).
- Transaction:
0x6be0c4b5414883a933639c136971026977df4737b061f864a4a04e4bd7f07106on Base. - From: adversary EOA
0x4efd5f0749b1b91afdcd2ecf464210db733150e0. - To: helper contract
0x2a59ac31c58327efcbf83cc5a52fae1b24a81440with selector0x8cceaf1f. - Effect: the helper calls victim
0x8d2e..., which executes its Uniswap V3-style callback and instructs USDC to transfer 40,000,000,000 units (40,000 USDC) from the victim’s balance to the adversary EOA. - Evidence:
trace.cast.logshows the call chain0x4efd5f... -> 0x2a59ac31... -> 0x8d2ef0... -> 0x833589fc...::transfer, andbalance_diff.jsonrecords the exact USDC debits and credits.
- Transaction:
This flow satisfies the ACT criteria: the adversary uses only standard EIP-1559 transaction submission from an unprivileged EOA, interacts with publicly deployed contracts, and exploits a deterministic vulnerability in the victim’s code without any privileged off-chain coordination.
6. Impact & Losses
The measurable impact is a direct loss of 40,000 USDC from the victim contract’s balance on Base and a corresponding gain for the adversary EOA.
- Token: USDC (FiatTokenV2_2) at
0x833589fcd6edb6e08f4c7c32d4f71b54bda02913. - Victim address:
0x8d2ef0d39a438c3601112ae21701819e13c41288. - Adversary EOA:
0x4efd5f0749b1b91afdcd2ecf464210db733150e0. - Loss amount: 40,000 USDC (40,000,000,000 token units).
The Base-native gas fees are paid by the adversary EOA in ETH and are not counted in the USDC-denominated success predicate. In USDC terms, the adversary’s portfolio moves from 0 to 40,000 USDC in this transaction, for a net profit of 40,000 USDC and a corresponding loss for the victim.
7. References
- Seed transaction metadata for
0x6be0c4b5...(Base 8453): incident input and RPC-derived state used to reconstruct the pre- and post-state. balance_diff.jsonfor0x6be0c4b5...: ERC20 and native balance changes showing −40,000 USDC for0x8d2e...and +40,000 USDC for0x4efd5f....trace.cast.logfor0x6be0c4b5...: full EVM trace showing the call stack from the adversary EOA through helper0x2a59...to victim0x8d2e...and USDCtransfer.- Decompiled victim contract
0x8d2ef0d39a438c3601112ae21701819e13c41288(Heimdall output): used to identify the Uniswap V3-style callback and missingmsg.senderauthentication. - Decompiled helper contract
0x2a59ac31c58327efcbf83cc5a52fae1b24a81440: used to confirm adversary control and its role in reaching the victim’s callback. - Circle FiatTokenV2_2 source tree for the Base USDC deployment: confirms that USDC transfer semantics are standard and that the erroneous state change originates from the victim’s callback logic.