Aave AMM LP Oracle Manipulation Through Delegated Recursive LP Looping
Exploit Transactions
0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985bVictim Addresses
0x5F360c6b7B25DfBfA4F10039ea0F7ecfB9B02E60Ethereum0x8A4236F5eF6158546C34Bd7BC2908B8106Ab1Ea1Ethereum0x004375Dff511095CC5A197A54140a24eFEF3A416EthereumLoss Breakdown
Similar Incidents
Ploutos Market Oracle Feed Misconfiguration Enabled Undercollateralized WETH Borrow
35%bZx/Fulcrum iETH oracle manipulation enables undercollateralized WETH borrowing
35%bZx/Fulcrum WBTC market manipulation drains ETH liquidity
33%ParaSpace cAPE Donation Repricing
33%SilicaPools decimal-manipulation bug drains WBTC flashloan collateral
31%Euler DAI Reserve Donation
31%Root Cause Analysis
Aave AMM LP Oracle Manipulation Through Delegated Recursive LP Looping
1. Incident Overview TL;DR
On Ethereum mainnet block 16384470, tx 0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985b used a single adversary-crafted call from EOA 0x67a909f2953fb1138bea4b60894b51291d2d0795 into helper contract 0x3a5b7db0be9f74324370fbd65b75850a5c82d176. The helper took a zero-fee Balancer flash loan of 5,673,090.338021 USDC, deposited that USDC into the Aave AMM market on behalf of the borrower EOA, recursively borrowed the listed WBTC/USDC Uniswap V2 LP token against delegated debt, and redeposited the LP onto itself as collateral. After building that collateral stack, the helper burned one LP tranche, donated USDC back into the pair, and synced the pair before calling the final Aave USDC borrow. The validated all_relevant_txs set for this incident contains only this adversary-crafted transaction.
That sequence changed the live pair state that Aave's LP oracle read during borrow validation. The LP price seen by Aave rose from 3495450576387056244740 before the exploit to 4320806762049972060102 at the final borrow check, which let the helper borrow the full Aave USDC reserve of 5673090338021 base units. The attacker-controlled helper finished the block with 229462904 WBTC satoshis and 39982134101 USDC base units, while the attacker EOA paid 532165187372027622 wei in gas. Using the same block's Chainlink BTC/USD and ETH/USD answers, the net profit was 79246.202439 USD.
The root cause is an oracle design failure in the Aave AMM market. Aave treated a thin Uniswap V2 LP token as both collateral and a borrowable reserve while valuing it from synchronous pair reserves and totalSupply() inside the same transaction. Because the borrow path had no TWAP, cooldown, or anti-self-referential guard, the attacker could create temporary borrowing power by rewriting the LP pair state immediately before the final borrow validation.
2. Key Background
Aave V2 borrow validation values every active collateral and debt position through the configured price oracle. In the Aave V2 GenericLogic and ValidationLogic libraries, the protocol converts balances into ETH-equivalent values with getAssetPrice(asset) and then checks whether the requested borrow is still covered by the user's collateral:
vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress);
...
vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv(
vars.currentLtv
);
require(
vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
Errors.VL_COLLATERAL_CANNOT_COVER_NEW_BORROW
);
The exploit also relies on Aave credit delegation. DebtTokenBase.approveDelegation() allows one account to delegate borrow capacity to another account without transferring collateral ownership:
function approveDelegation(address delegatee, uint256 amount) external override {
_borrowAllowances[_msgSender()][delegatee] = amount;
emit BorrowAllowanceDelegated(_msgSender(), delegatee, _getUnderlyingAssetAddress(), amount);
}
That means one attacker-controlled address can accumulate the LP debt while another attacker-controlled address accumulates the LP aToken collateral.
Finally, Uniswap V2 LP value per share is a direct function of pair balances and LP totalSupply(). The pair contract's burn() and sync() functions update reserves from live token balances:
function burn(address to) external lock returns (uint amount0, uint amount1) {
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
amount0 = liquidity.mul(balance0) / _totalSupply;
amount1 = liquidity.mul(balance1) / _totalSupply;
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
_update(balance0, balance1, _reserve0, _reserve1);
}
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
Any oracle that reads pair reserves and totalSupply() synchronously is therefore manipulable if the attacker can burn LP, change one-sided balances, and force a reserve update before the borrow check.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ATTACK case, not a passive market event. The vulnerable condition was Aave AMM listing the WBTC/USDC Uniswap V2 LP token 0x004375Dff511095CC5A197A54140a24eFEF3A416 as collateral and debt while pricing it through AaveOracle 0x8A4236F5eF6158546C34Bd7BC2908B8106Ab1Ea1, which forwarded the LP lookup to source 0x849AF4b128be3317a694bFD262dEFF636AB84c1b. The seed trace shows that this source read UniswapV2Pair::getReserves() and UniswapV2Pair::totalSupply() during the same transaction in which the attacker modified those values.
The attacker first used delegated borrowing to separate debt from collateral: the borrower EOA carried LP variable debt while the helper contract built a large LP aToken balance. Once the helper had enough LP collateral, it burned one additional LP tranche, withdrew both underlying assets, donated USDC back into the pair, and synced the pool to write the manipulated balances into reserve storage. Aave then recalculated collateral using the inflated LP price during the final USDC borrow validation and accepted borrowing power that existed only because of the attacker's intra-transaction state changes. No privileged role, compromised key, private orderflow, or attacker-side artifact was required; every primitive used was permissionless.
The missing controls were exactly the ones a safe collateral oracle for thin LP tokens needs: a manipulation-resistant valuation source, temporal separation between collateral state changes and borrow authorization, or a circuit breaker that prevented the same LP from being both thinly borrowable and immediately mark-to-spot collateral. Because none of those controls existed, the attacker could self-create borrowing capacity and drain the seeded USDC liquidity.
4. Detailed Root Cause Analysis
4.1 ACT Opportunity Definition
The ACT opportunity existed in the Ethereum pre-state immediately before the exploit transaction in block 16384470. Relevant public conditions were already present:
- Aave AMM LendingPool
0x5F360c6b7B25DfBfA4F10039ea0F7ecfB9B02E60listed the WBTC/USDC LP reserve. - The LP price path was already wired through AaveOracle
0x8A4236...to source0x849AF4.... - The LP reserve was funded and borrowable.
- Balancer's flash loan interface and Aave credit delegation were open to any unprivileged caller.
The root safety invariant is:
Collateral used to authorize borrowing must be priced with a manipulation-resistant valuation, so a borrower cannot increase the borrowing capacity of already-deposited collateral inside the same transaction by changing a thin LP's reserves or supply.
The concrete code-level breakpoint is Aave V2's collateral valuation path in GenericLogic.calculateUserAccountData(), which writes vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress), and the downstream ValidationLogic.validateBorrow() check that compares the requested borrow against vars.userCollateralBalanceETH. For this reserve, that price read reached 0x849AF4...::latestAnswer(), which the trace shows reading the manipulated pair state directly.
4.2 Bootstrap and Delegated Recursive LP Loop
The attacker used credit delegation exactly as intended by Aave's debt token design, but in a composition that Aave's LP oracle model could not safely tolerate. The exploit tx starts with the helper taking a zero-fee Balancer flash loan and depositing the flash-loaned USDC into Aave on behalf of the borrower EOA. The helper then repeatedly borrows the LP token on behalf of that borrower and redeposits the LP onto itself.
By the end of the transaction:
- the borrower EOA held
145820303250LP variable debt units, - the helper held
143308665997LP aToken units as collateral, - the helper later opened
5673090338021USDC variable debt, - the Aave LP reserve itself had been fully emptied of immediately borrowable LP.
Those balances are not inferred; they are the post-state deltas recorded in the collector artifacts. The loop was permissionless because the only special action was approveDelegation, which the borrower EOA can call directly.
4.3 LP Burn, Single-Sided Donation, and Oracle Manipulation
The exploitable LP oracle behavior appears when the helper burns one borrowed LP tranche and then rewrites the pair reserve state before the final USDC borrow. The Uniswap pair source code above explains why this works: burn() pays out underlying based on live balances and sync() snapshots live balances back into reserves. The seed trace shows that exact state transition:
UniswapV2Pair::burn(0x3a5b7DB0BE9F74324370FBD65b75850A5c82D176)
emit Sync(reserve0: 282538140, reserve1: 49288009792)
emit Burn(sender: 0x3a5b7DB0BE9F74324370FBD65b75850A5c82D176, amount0: 296141905, amount1: 51661149896, to: 0x3a5b7DB0BE9F74324370FBD65b75850A5c82D176)
UniswapV2Pair::sync()
emit Sync(reserve0: 282538140, reserve1: 75312078962)
LendingPool::borrow(FiatTokenProxy, 5673090338021, 2, 0, 0x3a5b7DB0BE9F74324370FBD65b75850A5c82D176)
0x849AF4b128be3317a694bFD262dEFF636AB84c1b::latestAnswer()
UniswapV2Pair::getReserves() -> (282538140, 75312078962, ...)
UniswapV2Pair::totalSupply() -> 2818151713
-> 4320806762049972060102
That trace matters for two reasons.
First, it proves the attacker's pair-state rewrite happened before the final Aave borrow validation, not as a later accounting artifact. Second, it proves the LP oracle was consuming the manipulated reserves and reduced LP supply synchronously. The LP oracle value seen by Aave during the final borrow rose from 3495450576387056244740 before the attack to 4320806762049972060102 at the validation point, and then to 4322044803711754187326 after the block. That is exactly the behavior a manipulation-resistant collateral oracle must prevent.
4.4 Why the Final Borrow Passed
Once the helper's LP collateral was repriced upward, ValidationLogic.validateBorrow() treated that inflated LP balance as real collateral and approved the full USDC reserve borrow. The attack did not need to forge state or bypass access control. It simply ensured that when Aave asked, "what is this user's LP collateral worth right now?", the answer came from the attacker's own temporary, same-tx rewrite of the pair's reserves and supply.
This also explains why delegated borrowing was only an execution primitive, not the root cause. Credit delegation let the attacker separate who carried the LP debt from who carried the LP collateral, but the loss came from Aave trusting a same-tx spot price for that LP collateral. Without the oracle flaw, the delegated loop would not have created extractable borrowing power.
The exploit conditions were therefore:
- the LP token had to be listed in the Aave AMM market,
- Aave had to price it from the live LP reserve/supply path,
- public flash liquidity and delegation had to be available,
- the LP pair had to be thin enough that burning one tranche and donating one-sided reserves materially changed the price.
The violated security principles were equally direct:
- collateral oracles must be manipulation-resistant at the borrow decision point,
- a protocol must not allow users to self-create borrowing power from their own intra-tx state changes,
- thin LP tokens should not be both borrowable and collateralizable against a synchronous spot oracle.
5. Adversary Flow Analysis
The adversary strategy was a single-transaction, multi-stage attack that combined public flash liquidity, delegated recursive LP borrowing, LP oracle manipulation, and a final reserve drain.
-
Bootstrap and funding The tx sender
0x67a909...called helper0x3a5b7d..., which pulled a zero-fee Balancer flash loan of5673090338021USDC. -
Deposit and delegation setup The helper deposited the flash-loaned USDC into Aave on behalf of the borrower EOA. The attacker had already granted delegation on the LP variable debt token, so the helper could borrow LP against the borrower's credit line.
-
Recursive LP loop The helper repeatedly called
LendingPool::borrow(LP_PAIR, ..., onBehalfOf = borrower)and thenLendingPool::deposit(LP_PAIR, ..., onBehalfOf = helper). This stacked LP aToken collateral on the helper while leaving the borrower with the matching LP variable debt. -
LP manipulation The helper borrowed one more LP tranche, transferred it into the Uniswap pair, called
burn(), and received296141905WBTC satoshis plus51661149896USDC base units. It then returned USDC to the pair and calledsync(), updating the pair reserves to a state that produced a higher LP oracle value. -
Final Aave borrow Immediately after the sync, the helper borrowed the full Aave USDC liquidity of
5673090338021base units. The borrow passed because Aave recalculated LP collateral using the manipulated price. -
Profit realization The helper repaid the Balancer flash loan principal and kept the residual assets. The post-state balance diff shows the helper retaining
229462904WBTC satoshis and39982134101USDC base units.
This execution satisfies the ACT model. The sequence uses only public contracts, public state, public code, and a permissionless adversary-controlled EOA plus freshly deployed helper contract. No privileged consensus power, private key compromise, or non-public dependency is required.
6. Impact & Losses
The measurable impact from the exploit transaction is:
2.29462904WBTC (229462904satoshis) captured by the attacker-controlled helper.39982.134101USDC (39982134101base units) captured by the attacker-controlled helper.709.158007USD of gas paid by the attacker EOA, against79955.360446USD gross profit.79246.202439USD net profit after gas.
Measured in the report's reference asset, the adversary moved from 0 USD before the transaction to 79955.360446 USD after the transaction, with the full delta explained by the post-state WBTC and USDC balances minus gas. The protocol impact is broader than the immediate token balances. Aave AMM's borrow-capacity calculation accepted a temporary, attacker-manufactured increase in LP collateral value as if it were legitimate collateral appreciation. The final USDC borrow also drained the seeded Aave USDC liquidity, leaving the USDC aToken with zero immediately available USDC after the exploit.
7. References
- Seed exploit transaction:
0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985b - Collector metadata:
/workspace/session/artifacts/collector/seed/1/0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985b/metadata.json - Collector trace:
/workspace/session/artifacts/collector/seed/1/0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985b/trace.cast.log - Collector balance diff:
/workspace/session/artifacts/collector/seed/1/0x927b784148b60d5233e57287671cdf67d38e3e69e5b6d0ecacc7c1aeaa98985b/balance_diff.json - Historical oracle and profit observations:
/workspace/session/artifacts/auditor/iter_0/rpc_observations.json - Aave V2
GenericLogic.sol:https://raw.githubusercontent.com/aave/protocol-v2/master/contracts/protocol/libraries/logic/GenericLogic.sol - Aave V2
ValidationLogic.sol:https://raw.githubusercontent.com/aave/protocol-v2/master/contracts/protocol/libraries/logic/ValidationLogic.sol - Aave
DebtTokenBase.solsource artifact:/workspace/session/artifacts/collector/seed/1/0xb19dd5dad35af36cf2d80d1a9060f1949b11fcb0/src/DebtTokenBase.sol - Uniswap V2 pair source artifact:
/workspace/session/artifacts/collector/seed/1/0x004375dff511095cc5a197a54140a24efef3a416/src/Contract.sol