We do not have a reliable USD price for the recorded assets yet.
0xeb2bd37d47c98d7739b4606eaeffc46f1e519babBaseAn 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.
Aerodrome V6 uses depositor state as an authorization primitive for reward harvesting.
depositors[tokenId] is a security-critical mapping._resolveNFPM(pool) resolves NFT manager as ICLPool(pool).nft().batchHarvest checks only depositors[tokenId] == msg.sender (or owner override), then calls gauge .0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d85068390xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac80x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f690571540x09fd2056f0712262bae72569180cfc0119a997bbddd0f8bdf8580b30ee526bf30xb401c2b5ee9ad4f874f83c8b8f0ba33cd001c3c85d107fdc65250fccd5ac99640x11b118096f6531790079c7c758c8be048f4b539dba33bc09c52c7e265f9e8c020x1f3bcd3705c31c6c2e102c233b28608b4e7de6e8a936966b6945cd3560aefff50xe7cb648e9032a671d13037eb03a2a4f005e9409c0b22de125e231c1063abf6210xd7eb812ab0c094b9963ca834fdd37281b988fa31665fe0b801106dfb3be647210x86d156040d50dcefab6226139f8232a32d3995c826f280b3f116f52e9e7495410xf12a0756c9021ac77af6119cf240e7a9e45c39eb6259b457217852a8e4ad5ef00xa7beff52308c2718d13f5c7c86c64553e30f6f74862a115b24f6093214871ce30x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c5060x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3getReward(tokenId)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;
}
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:
depositUnstaked(pool, tokenId) state update to depositors[tokenId].batchHarvest(...) authorization check against overwritten mapping.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
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
Pause transaction 0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638 emitted pause event on V6. Subsequent replay attempt 0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3 reverted on first depositUnstaked call with 0xd93c0665 (EnforcedPause()).
0x0000e83906facc4132c520403ae99ee0ab8feaa70x2ccdedebb20098141c91a83725a22b4eb72628b8, 0xb32de53dc70228880c9cedf107d2271accfe527b0x1e221ee4abcf419872e71ea403e9a7ca52b784f5Victim-side protocol/token components:
0xeb2bd37d47c98d7739b4606eaeffc46f1e519bab0x940181a94a35a4569e4529a3cdfb74e38fd986310xdd8039634e6fc44d1d770bbb9644b34d26908fd867aed0e3453dedc8ad575c7e (block 42956055)0xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac8 (block 42957298)0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d8506839 (block 42956775)0x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c506 (block 42964189)0xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638 (block 42964272)0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d3 (block 42964798)Seed/related plus adversary-crafted set:
0x6c581fb9d552075e07d4c9f544a971b81d7b14d5a50064e38839fb5a45cfa3380xdd8039634e6fc44d1d770bbb9644b34d26908fd867aed0e3453dedc8ad575c7e, 0x035937a401b7e7a760391e3559d03f8d9c011625fac3c63eef0545e4790df65e, 0x953abdb832952697d7b33ff6f8720d82fe2683979169eb5250c8a0d3d8506839, 0xdfc28bb357348a2f152f6564d8063b1e769782d00d84f4045c7eb1c77da5bac8, 0x63113ffa41566078382c2b6cc221340da5c2fbe08871756a00c66f3f69057154, 0x09fd2056f0712262bae72569180cfc0119a997bbddd0f8bdf8580b30ee526bf3, 0xb401c2b5ee9ad4f874f83c8b8f0ba33cd001c3c85d107fdc65250fccd5ac9964, 0x11b118096f6531790079c7c758c8be048f4b539dba33bc09c52c7e265f9e8c02, 0x1f3bcd3705c31c6c2e102c233b28608b4e7de6e8a936966b6945cd3560aefff5, 0xe7cb648e9032a671d13037eb03a2a4f005e9409c0b22de125e231c1063abf621, 0xd7eb812ab0c094b9963ca834fdd37281b988fa31665fe0b801106dfb3be64721, 0x86d156040d50dcefab6226139f8232a32d3995c826f280b3f116f52e9e749541, 0xf12a0756c9021ac77af6119cf240e7a9e45c39eb6259b457217852a8e4ad5ef0, 0xa7beff52308c2718d13f5c7c86c64553e30f6f74862a115b24f6093214871ce3, 0x5f9b35d9888ad28f48dd8500367742db1878654d9eaab9b5f4a1f42a7746c506, 0x6525084589a2668513ca9fb64791e51cbc0629b1b35a6c87ff06a3ca562286d30xeba22027756c7d090722dfd88b58dcc26722ed9c017d1331602785a901472638Measured on-chain loss (token flow):
AERO: 199931656475035207998 wei (18 decimals)Observed behavior:
0x1e221ee4abcf419872e71ea403e9a7ca52b784f5.+15412603354802695460 wei-AERO (tx 0x953abd...)+14790754003241337582 wei-AERO (tx 0x5f9b35...)Deterministic fee accounting (for context, not AERO-denominated predicate):
Sigma(gasUsed * effectiveGasPrice) = 15545487842576235 wei ETH (0.015545487842576236 ETH).[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