Public Curve Treasury Drain
Exploit Transactions
0xbc08860cd0a08289c41033bdc84b2bb2b0c54a51ceae59620ed9904384287a38Victim Addresses
0x05f016765c6c601fd05a10dba1abe21a04f924a5EthereumLoss Breakdown
Similar Incidents
Audius Governance Reinitialization and Treasury AUDIO Drain
37%Curve Vyper Lock Reentrancy
37%OMPxContract bonding-curve loop exploit drains ETH reserves
37%ASResearch Public Rebalance Drain
36%NOON Pool Drain via Public transfer
34%VINU Reserve Drain
34%Root Cause Analysis
Public Curve Treasury Drain
1. Incident Overview TL;DR
On Ethereum mainnet block 18523344, transaction 0xbc08860cd0a08289c41033bdc84b2bb2b0c54a51ceae59620ed9904384287a38 used a freshly deployed attacker contract at 0xeadf72fd4733665854c76926f4473389ff1b78b1 to flash-borrow 27,255 WETH from Aave, abuse a public swap routine on victim contract 0x05f016765c6c601fd05a10dba1abe21a04f924a5, distort Curve Tricrypto pricing, repay the flash loan, and leave 1,047.161413417997701244 WETH with attacker EOA 0x46d9b3dfbc163465ca9e306487cba60bc438f5a2. The root cause is straightforward: selector 0xf6ebebbb on the victim contract spends address(this) balances and wraps address(this).balance into WETH without any caller authorization, even though the same runtime contains an owner guard for privileged routines.
2. Key Background
The victim is an unverified Ethereum contract that already held treasury balances in USDC, USDT, WBTC, WETH, and native ETH before the exploit block. Aave flashLoanSimple is permissionless, so any unprivileged EOA can borrow WETH as long as the callback repays principal plus premium in the same transaction.
The exploit path depends on Curve liquidity rather than private keys or privileged contracts. The victim routine only allows a small set of token and pool combinations, but those allowlisted paths include Curve 3pool at 0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7 and Curve Tricrypto at 0xd51a44d3fae010294c616388b506acda1bfaae46, which is enough to convert the victim treasury into the exact reserve distortion the attacker needs.
The victim was created earlier in block 18370467, well before the attacker contract. At block 18523343, the victim owner was 0xECc8aC967d7e62b4Fdd2aAaEC00B8FF888Eb2531, not the attacker.
3. Vulnerability Analysis & Root Cause Summary
This is an access-control failure on a treasury-spending function, not a generic MEV opportunity. The victim runtime has an owner-guarded control plane, but selector 0xf6ebebbb is wired around that guard and lands in a routine that directly consumes balances held by the victim itself. Once entered, the routine reads token balances from address(this), optionally wraps address(this).balance into WETH, and then executes Curve exchange calls with the victim as the token source. The only meaningful restrictions are a hardcoded pool allowlist and a small route allowlist; there is no caller authorization at the function entrypoint. That lets any attacker choose the routing order and interleave their own flash-loan trade between the victim-funded Curve swaps. The result is deterministic treasury depletion on the victim side and deterministic profit on the attacker side.
4. Detailed Root Cause Analysis
The key invariant is: only the victim owner or an explicitly authorized operator should be able to spend the victim's treasury assets or convert the victim's ETH into WETH for downstream swaps. The runtime bytecode violates that invariant at the dispatcher.
Disassembly of the on-chain runtime shows the public selector being routed into the treasury routine without touching the owner guard:
00000061: PUSH4 0xf6ebebbb
00000067: PUSH2 0x0985
...
00000981: PUSH2 0x1eb2
00000984: JUMP
00000985: JUMPDEST
...
000009a1: PUSH2 0x1ed2
000009a4: JUMP
...
00001eb2: JUMPDEST
00001ebf: CALLER
00001ec0: EQ
...
00001ed2: JUMPDEST
That structure matters: owner-protected paths jump through 0x1eb2, but the 0xf6ebebbb selector instead reaches 0x1ed2 directly. Independent disassembly also shows that this body checks balances, allows only the two Curve pools above, and contains the WETH deposit() path that wraps native ETH owned by the victim.
The execution trace of the exploit transaction confirms that the attacker calls the victim four times and that the victim, not the attacker, becomes the caller into Curve:
0xeaDF72Fd4733665854C76926F4473389FF1B78B1::executeOperation(..., 27255000000000000000000, ...)
0x05f016765c6C601fd05a10dBa1AbE21a04F924A5::f6ebebbb(... USDC -> USDT via 3pool ...)
0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7::exchange(1, 2, 610000001612, 0)
0x05f016765c6C601fd05a10dBa1AbE21a04F924A5::f6ebebbb(... USDT -> WETH via Tricrypto ...)
0x05f016765c6C601fd05a10dBa1AbE21a04F924A5::f6ebebbb(... WBTC -> WETH via Tricrypto ...)
...
0x05f016765c6C601fd05a10dBa1AbE21a04F924A5::f6ebebbb(... WETH -> WBTC via Tricrypto ...)
WETH9::deposit{value: 250669186725186258484}()
The exploit sequence is:
- The attacker deploys
0xeadf72fd4733665854c76926f4473389ff1b78b1in tx0x9afa682fc313aa2d77b139ca7b9c80561f3c7728a1d2005232f7da91a997f9cc. - In the seed tx, that contract borrows
27,255WETH from Aave. - The attacker forces the victim to swap all victim-held USDC into USDT on Curve 3pool.
- The attacker forces the victim to swap all victim-held USDT and WBTC into WETH on Tricrypto.
- The attacker then uses its flash-loaned WETH to buy WBTC from the same Tricrypto pool, moving reserves in the opposite direction.
- The attacker forces one final victim call that wraps all remaining victim ETH into WETH and swaps the combined victim WETH balance into WBTC.
- With reserves now skewed by both the victim-funded swaps and the attacker's inserted trade, the attacker unwinds its WBTC back into WETH, repays Aave, and transfers the residual WETH to the attacker EOA.
The loss and profit accounting is fully determined. The attacker cluster held only 0.9233112357941 ETH-equivalent before the exploit and 1047.904952453791801244 ETH-equivalent after it, for a net gain of 1046.981641217997701244 ETH-equivalent after 0.1797722 ETH of gas. The seed balance diff and token balance snapshots also show that the attacker contract ended with zero tracked balances because the residual WETH was transferred out to the attacker EOA before returning.
5. Adversary Flow Analysis
The attacker flow is a single-transaction ACT exploit. No privileged address, private key leak, or attacker-owned victim helper is involved.
First, EOA 0x46d9b3dfbc163465ca9e306487cba60bc438f5a2 deploys a dedicated flash-loan receiver. Next, that contract calls Aave and receives 27,255 WETH inside executeOperation. Inside the callback, it queries victim balances and invokes the victim's public selector with attacker-chosen (amount, tokenIn, tokenOut, pool) parameters.
The attacker uses the victim as the price-manipulation leg: three victim-funded swaps move the Curve reserves, then the attacker spends borrowed WETH into Tricrypto, then a fourth victim-funded swap consumes the victim's remaining WETH plus wrapped ETH. The trace ends with the attacker contract transferring 1047161413417997701244 wei of WETH to the attacker EOA:
WETH9::transfer(
0x46d9B3dFbc163465ca9E306487CbA60bC438F5a2,
1047161413417997701244
)
emit Transfer(
from: 0xeaDF72Fd4733665854C76926F4473389FF1B78B1,
to: 0x46d9B3dFbc163465ca9E306487CbA60bC438F5a2,
value: 1047161413417997701244
)
Because the vulnerable entrypoint is public and the rest of the route uses permissionless Aave and Curve primitives, the same execution model is available to any unprivileged searcher who observes the same public state.
6. Impact & Losses
The victim contract lost all of its pre-attack USDC, all native ETH, and all WETH, plus a material amount of WBTC and USDT. The collector's balance diff shows at least the following victim-side losses in smallest units:
[
{"token_symbol":"USDC","amount":"610000001612","decimal":6},
{"token_symbol":"USDT","amount":"585000009866","decimal":6},
{"token_symbol":"WBTC","amount":"305752960","decimal":8},
{"token_symbol":"WETH","amount":"308657730541712706358","decimal":18},
{"token_symbol":"ETH","amount":"250669186725186258484","decimal":18}
]
The attacker EOA paid 179772200000000000 wei in gas, but still finished with 1047.161413417997701244 WETH and 0.7435390357941 ETH. The net realized gain is therefore 1046.981641217997701244 ETH-equivalent.
7. References
- Seed exploit transaction:
0xbc08860cd0a08289c41033bdc84b2bb2b0c54a51ceae59620ed9904384287a38 - Attacker contract creation tx:
0x9afa682fc313aa2d77b139ca7b9c80561f3c7728a1d2005232f7da91a997f9cc - Victim contract creation tx:
0x079ed97a3211864b4e4fe512ff5c1ff19cdeeeae706ae313e6e51e20384b9d48 - Victim contract:
0x05f016765c6c601fd05a10dba1abe21a04f924a5 - Attacker EOA:
0x46d9b3dfbc163465ca9e306487cba60bc438f5a2 - Attacker contract:
0xeadf72fd4733665854c76926f4473389ff1b78b1 - Aave pool:
0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 - Curve Tricrypto:
0xD51a44d3FaE010294C616388b506AcdA1bfAAE46 - Curve 3pool:
0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7 - Validator evidence sources: on-chain runtime disassembly of the victim, Foundry trace of the seed transaction, collector balance diff for the seed transaction, and RPC balance snapshots for the attacker cluster at blocks
18523343and18523344