Strategy valuation uses manipulable AMM spot price
Quillon Vaults · src/strategies/AmmLpStrategy.sol:L97–L131
estimatedTotalAssets() values the LP position by reading the pool’s current reserves and computing the position’s share of each side at the spot ratio. The vault includes this estimate in totalAssets(), which drives both minting and redemption.
Reserves can be moved arbitrarily within one transaction using borrowed capital. Because the valuation reads reserves at the moment of the call, an attacker can skew the ratio, interact with the vault at the distorted price, and restore the pool before the transaction ends.
Exploit sequence
- Attacker flash-borrows a large amount of token B.
- Attacker swaps B for A in the pool, skewing reserves so that the strategy’s LP position is valued above its fair value.
- Attacker redeems shares from the vault at the inflated totalAssets(), receiving more underlying than their shares are worth.
- Attacker swaps back to restore the pool and repays the flash loan.
- The loss is borne by remaining depositors through a lower share price.
Recommendation
Value positions with a manipulation-resistant reference price: a time-weighted average price over a window long enough that sustaining a distortion is uneconomic, or an external oracle with deviation checks. Additionally compare spot to the reference and revert deposits and withdrawals when the deviation exceeds a bound.
function estimatedTotalAssets() public view returns (uint256) {- (uint112 r0, uint112 r1, ) = pool.getReserves();- return _lpValue(uint256(r1) * 1e18 / r0) + idle();+ uint256 twap = oracle.consult(address(asset), TWAP_WINDOW); // 30 minutes+ uint256 spot = _spotPrice();+ if (_deviationBps(spot, twap) > MAX_DEVIATION_BPS) revert PriceDeviation(spot, twap);+ return _lpValue(twap) + idle(); }

