Aerodrome V6 Unauthorized Reward Harvest via Forged Depositor Assignment
Exploit Transactions
0xdd8039634e6fc44d1d770bbb9644b34d26908fd867aed0e3453dedc8ad575c7e0x035937a401b7e7a760391e3559d03f8d9c011625fac3c63eef0545e4790df65e0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d85068390xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac80x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f690571540x09fd2056f0712262bae72569180cfc0119a997bbddd0f8bdf8580b30ee526bf30xb401c2b5ee9ad4f874f83c8b8f0ba33cd001c3c85d107fdc65250fccd5ac99640x11b118096f6531790079c7c758c8be048f4b539dba33bc09c52c7e265f9e8c020x1f3bcd3705c31c6c2e102c233b28608b4e7de6e8a936966b6945cd3560aefff50xe7cb648e9032a671d13037eb03a2a4f005e9409c0b22de125e231c1063abf6210xd7eb812ab0c094b9963ca834fdd37281b988fa31665fe0b801106dfb3be647210x86d156040d50dcefab6226139f8232a32d3995c826f280b3f116f52e9e7495410xf12a0756c9021ac77af6119cf240e7a9e45c39eb6259b457217852a8e4ad5ef00xa7beff52308c2718d13f5c7c86c64553e30f6f74862a115b24f6093214871ce30x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c5060x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3Victim Addresses
0xeb2bd37d47c98d7739b4606eaeffc46f1e519babBaseLoss Breakdown
Similar Incidents
Veil01ETH forged-proof drain on Base
33%CAROL Reward Inflation Drain
29%MPRO Staking Proxy unwrapWETH Flash-Loan Exploit (Base)
28%Base DUCKVADER infinite mint + Uniswap drain
27%ACP Router/PaymentManager Double-Claim Drain on Base
26%Base USDC drain from malicious transferFrom spender approvals
26%Root Cause Analysis
Aerodrome V6 Unauthorized Reward Harvest via Forged Depositor Assignment
1. Incident Overview TL;DR
An unprivileged actor on Base exploited Aerodrome CL Position Manager V6 (0xeb2bd37d47c98d7739b4606eaeffc46f1e519bab) by forging depositor ownership for existing position tokenIds, then harvesting gauge rewards to an attacker-controlled recipient.
The critical flaw is authorization coupling: batchHarvest trusts depositors[tokenId], while depositUnstaked allows writing depositors[tokenId] through an attacker-controlled pool -> nft() resolution path. A spoofed NFPM can satisfy safeTransferFrom without proving canonical NFT custody.
This was repeatedly realized across adversary-crafted transactions until the protocol owner paused V6. Observed total extracted value is 199931656475035207998 wei-AERO, credited to 0x1e221ee4abcf419872e71ea403e9a7ca52b784f5.
2. Key Background
Aerodrome V6 uses depositor state as an authorization primitive for reward harvesting.
depositors[tokenId]is a security-critical mapping._resolveNFPM(pool)resolves NFT manager asICLPool(pool).nft().batchHarvestchecks onlydepositors[tokenId] == msg.sender(or owner override), then calls gaugegetReward(tokenId).- Rewards (AERO) are forwarded to caller after harvesting.
Relevant victim-side code:
function depositUnstaked(address pool, uint256 tokenId) external whenNotPaused nonReentrant {
if (pool == address(0)) revert InvalidPool();
address nfpm = _resolveNFPM(pool);
INonfungiblePositionManager(nfpm).safeTransferFrom(msg.sender, address(this), tokenId);
depositors[tokenId] = msg.sender;
isStaked[tokenId] = false;
positionPools[tokenId] = pool;
emit DepositedUnstaked(pool, msg.sender, tokenId, nfpm);
}
function batchHarvest(address[] calldata pools, uint256[] calldata tokenIds) external whenNotPaused nonReentrant {
for (uint256 i = 0; i < pools.length; i++) {
if (depositors[tokenIds[i]] != msg.sender && owner() != msg.sender) revert Unauthorized();
ICLGauge(_resolveGauge(pools[i])).getReward(tokenIds[i]);
}
...
}
function _resolveNFPM(address pool) internal view returns (address) {
address nfpm = ICLPool(pool).nft();
if (nfpm == address(0)) revert InvalidPool();
return nfpm;
}
3. Vulnerability Analysis & Root Cause Summary
Root cause category: ATTACK.
The protocol invariant should be: only canonical NFPM custody transfer can establish depositor authorization for a position. In V6, this invariant is broken because depositor assignment is reachable via a user-supplied pool whose nft() result is trusted.
An attacker deploys helper contracts that impersonate pool/NFPM behavior and returns attacker-controlled addresses through nft(). V6 then invokes safeTransferFrom on that attacker-controlled contract. Because there is no canonical binding check between supplied pool/NFPM and the real position owner, state writes proceed: depositors[tokenId] = attacker and positionPools[tokenId] = suppliedPool.
After overwrite, batchHarvest authorization is satisfied by attacker-controlled mapping state, allowing harvest over real target pools/tokenIds and transfer of AERO to attacker-selected recipient.
Code-level breakpoint:
- Write primitive:
depositUnstaked(pool, tokenId)state update todepositors[tokenId]. - Abuse point:
batchHarvest(...)authorization check against overwritten mapping.
4. Detailed Root Cause Analysis
4.1 Deterministic exploit mechanism
Representative exploit transaction 0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d8506839 shows forged depositor assignment followed by harvest:
AerodromeCLPositionManagerV6::depositUnstaked(0x2ccDEDEbb20098141c91A83725A22b4EB72628b8, 55023230)
0x2ccDE...::nft() -> 0x2ccDE...
0x2ccDE...::safeTransferFrom(...)
emit DepositedUnstaked(pool: 0x2ccDE..., depositor: 0x2ccDE..., tokenId: 55023230, nfpm: 0x2ccDE...)
AerodromeCLPositionManagerV6::batchHarvest([...pools...], [...tokenIds...])
... getReward(...) repeated across target gauges ...
The helper decompile for 0xb32de53dc70228880c9cedf107d2271accfe527b also shows direct invocation of V6 selectors:
0xa8a59a64(depositUnstaked(address,uint256))0xa5eaea2e(batchHarvest(address[],uint256[]))
(bool success, bytes memory ret0) = address(0xeb2bd...19bab).Unresolved_a8a59a64(var_n); // call
...
(bool success, bytes memory ret0) = address(0xeb2bd...19bab).Unresolved_a5eaea2e(var_i, var_s, var_n, var_o); // call
4.2 State-diff confirmation of authorization overwrite
State diff for 0x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f69057154 shows V6 depositor-related slots rewritten from prior values (often 0x2ccded...) to new attacker helper (0xb32de53...), proving attacker-controlled reassignment across multiple tokenIds:
slot 0x0cffbb18...: 0x...2ccdedebb20098141c91a83725a22b4eb72628b8 -> 0x...b32de53dc70228880c9cedf107d2271accfe527b
slot 0x16724184...: 0x...2ccdedebb20098141c91a83725a22b4eb72628b8 -> 0x...b32de53dc70228880c9cedf107d2271accfe527b
slot 0x20236063...: 0x...2ccdedebb20098141c91a83725a22b4eb72628b8 -> 0x...b32de53dc70228880c9cedf107d2271accfe527b
slot 0x4387a588...: 0x...2ccdedebb20098141c91a83725a22b4eb72628b8 -> 0x...b32de53dc70228880c9cedf107d2271accfe527b
4.3 Mitigation and failed replay
Pause transaction 0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638 emitted pause event on V6. Subsequent replay attempt 0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3 reverted on first depositUnstaked call with 0xd93c0665 (EnforcedPause()).
5. Adversary Flow Analysis
5.1 Adversary-related accounts
- EOA executor:
0x0000e83906facc4132c520403ae99ee0ab8feaa7 - Helper contracts:
0x2ccdedebb20098141c91a83725a22b4eb72628b8,0xb32de53dc70228880c9cedf107d2271accfe527b - Profit recipient:
0x1e221ee4abcf419872e71ea403e9a7ca52b784f5
Victim-side protocol/token components:
- Aerodrome V6 manager:
0xeb2bd37d47c98d7739b4606eaeffc46f1e519bab - AERO token:
0x940181a94a35a4569e4529a3cdfb74e38fd98631
5.2 Lifecycle stages with concrete transactions
- Helper deployment/priming:
0xdd8039634e6fc44d1d770bbb9644b34d26908fd867aed0e3453dedc8ad575c7e(block 42956055)0xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac8(block 42957298)
- Depositor overwrite and reward extraction (representative):
0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d8506839(block 42956775)0x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c506(block 42964189)
- Mitigation and failed replay:
0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638(block 42964272)0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3(block 42964798)
5.3 Full relevant transaction set
Seed/related plus adversary-crafted set:
- seed:
0x6c581fb9d552075e07d4c9f544a971b81d7b14d5a50064e38839fb5a45cfa338 - adversary-crafted:
0xdd8039634e6fc44d1d770bbb9644b34d26908fd867aed0e3453dedc8ad575c7e,0x035937a401b7e7a760391e3559d03f8d9c011625fac3c63eef0545e4790df65e,0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d8506839,0xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac8,0x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f69057154,0x09fd2056f0712262bae72569180cfc0119a997bbddd0f8bdf8580b30ee526bf3,0xb401c2b5ee9ad4f874f83c8b8f0ba33cd001c3c85d107fdc65250fccd5ac9964,0x11b118096f6531790079c7c758c8be048f4b539dba33bc09c52c7e265f9e8c02,0x1f3bcd3705c31c6c2e102c233b28608b4e7de6e8a936966b6945cd3560aefff5,0xe7cb648e9032a671d13037eb03a2a4f005e9409c0b22de125e231c1063abf621,0xd7eb812ab0c094b9963ca834fdd37281b988fa31665fe0b801106dfb3be64721,0x86d156040d50dcefab6226139f8232a32d3995c826f280b3f116f52e9e749541,0xf12a0756c9021ac77af6119cf240e7a9e45c39eb6259b457217852a8e4ad5ef0,0xa7beff52308c2718d13f5c7c86c64553e30f6f74862a115b24f6093214871ce3,0x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c506,0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3 - related mitigation:
0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638
6. Impact & Losses
Measured on-chain loss (token flow):
AERO:199931656475035207998wei (18 decimals)
Observed behavior:
- Rewards were redirected from at least 17 holder addresses to attacker recipient
0x1e221ee4abcf419872e71ea403e9a7ca52b784f5. - Representative credited deltas to attacker recipient include:
+15412603354802695460wei-AERO (tx0x953abd...)+14790754003241337582wei-AERO (tx0x5f9b35...)
Deterministic fee accounting (for context, not AERO-denominated predicate):
- Sum over adversary-crafted tx receipts:
Sigma(gasUsed * effectiveGasPrice) = 15545487842576235wei ETH (0.015545487842576236ETH).
7. References
[1] Aerodrome V6 source (depositUnstaked, batchHarvest, _resolveNFPM):
/workspace/session/artifacts/collector/iter_2/contract/8453/0xeb2bd37d47c98d7739b4606eaeffc46f1e519bab/source/forge_clone/src/AerodromeCLPositionManagerV6.sol
[2] Representative exploit trace:
/workspace/session/artifacts/collector/iter_2/tx/8453/0x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c506/trace.calltracer.json
[3] Storage overwrite state diff:
/workspace/session/artifacts/collector/iter_2/tx/8453/0x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f69057154/state_diff.prestate_tracer_diff.json
[4] V6 storage layout:
/workspace/session/artifacts/collector/iter_2/contract/8453/0xeb2bd37d47c98d7739b4606eaeffc46f1e519bab/source/forge_clone/out/AerodromeCLPositionManagerV6.sol/AerodromeCLPositionManagerV6.json
[5] Helper decompile (0xb32de53...):
/workspace/session/artifacts/collector/iter_2/contract/8453/0xb32de53dc70228880c9cedf107d2271accfe527b/decompile/0xb32de53dc70228880c9cedf107d2271accfe527b-decompiled.sol
[6] Helper decompile (0x2ccded...):
/workspace/session/artifacts/collector/iter_2/contract/8453/0x2ccdedebb20098141c91a83725a22b4eb72628b8/decompile/0x2ccdedebb20098141c91a83725a22b4eb72628b8-decompiled.sol
[7] Exploit balance diffs:
/workspace/session/artifacts/collector/iter_2/tx/8453
[8] Pause receipt:
/workspace/session/artifacts/collector/iter_2/tx/8453/0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638/receipt.json
[9] Collector summary (coverage and artifact quality):
/workspace/session/artifacts/collector/data_collection_summary.json