Calculated from recorded token losses using historical USD prices at the incident time.
0x9008d19f58aabd9ed0d60971565aa8510560ab41Ethereum0xd75ea151a61d06868e31f8988d28dfe5e9df57b4Ethereum0xd524f98f554bd34f4185678f64a85bb98971d314EthereumOn Ethereum mainnet block 24643151, transaction 0x9fa9feab3c1989a33424728c23e6de07a40a26a98ff7ff5139f3492ce430801f settled a CoW order that converted aEthUSDT into aEthAAVE. The solver route withdrew 50432688416180 aEthUSDT, swapped through WETH, bought AAVE from the thin Sushi AAVE/WETH pool at 0xd75ea151a61d06868e31f8988d28dfe5e9df57b4, then supplied AAVE back into Aave and transferred 327241335505966487788 aEthAAVE to the order owner 0x98b9d979c33dd7284c854909bcc09b51fbf97ac8.
The next transaction in the same block, 0x45388b0f9ff46ffe98a3124c22ab1db2b1764ecb3b61234e29e5c9732b7fd4ab, backran that public route. An unprivileged attacker used Morpho flash liquidity plus Bancor sourcing to acquire AAVE and sell it back into the now-distorted Sushi pool. The sender EOA 0x5884b2faa9ad6f38010831e2290e515af17a7d47 finished with a net native-balance increase of 4824317516734375086288 wei after gas, while the attacker-controlled contract also paid 13087732595504669669052 wei to block coinbase as a builder payment.
This is an ACT MEV opportunity, not a protocol-invariant break in Aave, Bancor, Morpho, or Sushi. The root cause is the public CoW solver route itself: GPv2Settlement allows an authorized solver to execute arbitrary interaction bundles, and the chosen interaction path pushed a large terminal leg through a thin public AAVE/WETH pool without route-level protection against deterministic same-block backrunning.
The policy boundary for this incident is CoW's verified settlement contract 0x9008d19f58aabd9ed0d60971565aa8510560ab41. Its settle function authenticates only the caller as a solver, then executes three arrays of solver-supplied interactions around the token-transfer phases. The contract forbids calls only to the vault relayer, not to arbitrary external venues or helper contracts.
Source: verified GPv2Settlement.sol
function settle(
IERC20[] calldata tokens,
uint256[] calldata clearingPrices,
GPv2Trade.Data[] calldata trades,
GPv2Interaction.Data[][3] calldata interactions
) external nonReentrant onlySolver {
executeInteractions(interactions[0]);
(GPv2Transfer.Data[] memory inTransfers, GPv2Transfer.Data[] memory outTransfers) =
computeTradeExecutions(tokens, clearingPrices, trades);
vaultRelayer.transferFromAccounts(inTransfers);
executeInteractions(interactions[1]);
vault.transferToAccounts(outTransfers);
executeInteractions(interactions[2]);
}
function executeInteractions(GPv2Interaction.Data[] calldata interactions) internal {
for (uint256 i; i < interactions.length; i++) {
GPv2Interaction.Data calldata interaction = interactions[i];
require(interaction.target != address(vaultRelayer), "GPv2: forbidden interaction");
GPv2Interaction.execute(interaction);
}
}
The first helper in the observed route is the verified HooksTrampoline at 0x60Bf78233f48eC42eE3F101b9a05eC7878728006. It only enforces that the caller is the settlement contract, then forwards each hook with the caller-provided gas limit and explicitly tolerates hook reverts. That means it constrains execution context, but it does not constrain routing economics.
Source: verified HooksTrampoline.sol
function execute(Hook[] calldata hooks) external onlySettlement {
unchecked {
for (uint256 i; i < hooks.length; ++i) {
Hook calldata hook = hooks[i];
uint256 forwardedGas = gasleft() * 63 / 64;
if (forwardedGas < hook.gasLimit) revertByWastingGas();
(bool success,) = hook.target.call{gas: hook.gasLimit}(hook.callData);
success;
}
}
}
The terminal price-setting venue is the Sushi AAVE/WETH pair 0xd75ea151a61d06868e31f8988d28dfe5e9df57b4. Its verified UniswapV2Pair::swap implementation is standard constant-product logic: it transfers outputs optimistically, infers input from resulting balances, and reverts unless the fee-adjusted product invariant holds.
Source: verified UniswapV2Pair.sol
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT");
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
require(amount0Out < _reserve0 && amount1Out < _reserve1, "UniswapV2: INSUFFICIENT_LIQUIDITY");
...
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT");
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), "UniswapV2: K");
}
On the backrun side, the liquidity sources are public by code. Morpho's flashLoan transfers the token to the caller, calls onMorphoFlashLoan, and then pulls the same amount back with safeTransferFrom. Bancor V3 tradeBySourceAmount and legacy convertByPath both let any caller choose source token, target token, amount, path, and beneficiary. Nothing in those contracts gives the attacker privileged access; they simply provide permissionless capital and inventory sourcing.
The vulnerability class is MEV arising from a public solver route that externalized its terminal execution cost into thin AMM liquidity. GPv2Settlement and HooksTrampoline behaved according to design: they allowed an approved solver to execute a custom interaction bundle that still satisfied the order. The problem is that the chosen interaction bundle routed a large aEthUSDT -> aEthAAVE fill through the Sushi AAVE/WETH pool as its final price-setting leg. That route created a deterministic same-block post-state in which AAVE was overpriced on Sushi relative to permissionless alternative sourcing venues. The next searcher in the block therefore had a clean backrun: borrow WETH from Morpho, source AAVE through Bancor, sell AAVE into Sushi, repay the flash loan, and keep the residual ETH/WETH spread. No invariant in Aave, Bancor, Morpho, or Sushi is broken; the exploitable condition is the solver's public route choice under CoW's interaction model.
The victim-observed transaction is tx 0x9fa9feab3c1989a33424728c23e6de07a40a26a98ff7ff5139f3492ce430801f, sent by approved solver 0x3980daa7eaad0b7e0c53cfc5c2760037270da54d to GPv2Settlement. The collector trace shows the settlement authenticating the solver, invoking HooksTrampoline.execute, pulling aEthUSDT from the order owner, approving the route executor, and then calling 0xD524...::494b3137.
Source: collected trace for tx 0x9fa9...
0x9008D19f58AAbD9eD0D60971565AA8510560ab41::13d79a0b(...)
0x60Bf78233f48eC42eE3F101b9a05eC7878728006::execute(...)
...
emit Transfer(from: 0x98B9..., to: 0x9008..., value: 50432688416180)
...
0xD524f98F554Bd34f4185678F64a85bB98971d314::494b3137(...)
...
0xD75EA151a61d06868E31F8988D28DFE5E9df57B4::swap(331305315608938235428, 0, 0x699C..., 0x)
...
emit Transfer(from: 0x9008..., to: 0x98B9..., value: 327241335505966487788)
The verified helper contracts explain why this path is possible, and the route-executor disassembly explains what the unverified 0xD524... does. In the disassembly, selector 0x381fef89 builds and issues an ERC20 transferFrom call with selector 23b872dd, and selector 0x494b3137 copies dynamic calldata and executes external CALLs. The route therefore functions as a generic settlement-side orchestrator, not as a venue-specific primitive with built-in slippage protection.
Source: disassembly of 0xD524...
00001a PUSH4 381fef89
...
000098 PUSH32 23b872dd00000000000000000000000000000000000000000000000000000000
...
0000cb GAS
0000cc CALL
...
000133 PUSH4 494b3137
...
0001cd SWAP11
0001d2 CALLDATACOPY
...
0001de GAS
0001df CALL
From the trace, the concrete route is deterministic. The settlement path:
50432688416180 aEthUSDT.17957810805702142342238 WETH.331305315608938235428 AAVE.327241335505966487788 aEthAAVE to the order owner.This route satisfies the user order, but it leaves the Sushi pair at a distorted price. Immediately after the victim transaction, Sushi reserves have moved in the direction expected from a large AAVE buy: the pair holds materially less AAVE and materially more WETH than before the trade. Because the settlement was public, every searcher could observe the route and place a backrun against that exact post-state.
The adversary transaction 0x45388b0f9ff46ffe98a3124c22ab1db2b1764ecb3b61234e29e5c9732b7fd4ab realizes that opportunity. The trace shows:
Source: collected trace for tx 0x4538...
Morpho::flashLoan(WETH, 14175707124029000000000, ...)
WETH9::withdraw(17719999999999988000)
BancorNetwork::tradeBySourceAmount(ETH, BNT, 17719999999999988000, 1, ..., 0x06CF...)
BancorNetworkLegacy::convertByPath([BNT, 0x6c84..., AAVE], 122364950283527011601734, 1, 0x06CF..., 0x0, 0)
0xD75EA151...::swap(0, 17929770158685932657052, 0x06CF..., 0x)
WETH9::withdraw(17912050158685932669052)
0x4838B106...::fallback{value: 13087732595504669669052}()
0x5884B2fa...::fallback{value: 4824317563181263000000}()
The supporting code explains why each step is permissionless. Morpho lends to the caller and requires only repayment. Bancor V3 accepts arbitrary sourceToken, targetToken, sourceAmount, minReturnAmount, deadline, and beneficiary. Legacy convertByPath checks path shape and beneficiary handling but does not restrict who may route through it. Sushi then executes a standard invariant-preserving swap against the already-distorted pool. The attacker sequence therefore exploits only publicly available routing surfaces and public state transitions.
The exploit predicate is confirmed by chain balance changes. The attacker sender EOA moved from 6160688597521780704 wei to 4830478205331896866992 wei, for a net increase of 4824317516734375086288 wei after 46446887913712 wei of gas. Separately, the attacker-controlled contract paid 13087732595504669669052 wei to block coinbase after extracting 17912050158685932669052 wei of gross WETH profit. That is the economic loss created by the public settlement route.
The attacker cluster consists of EOA 0x5884b2faa9ad6f38010831e2290e515af17a7d47 and contract 0x06cff7088619c7178f5e14f0b119458d08d2f5ef. Their execution flow is straightforward and entirely permissionless.
0x9fa9... in block 24643151.14175707124029000000000 WETH from Morpho in the very next transaction.17719999999999988000 WETH into BNT through Bancor V3, then convert 122364950283527011601734 BNT into 128566045690201882053 AAVE through Bancor legacy routing.17929770158685932657052 WETH.The critical decision point is the attacker's choice to source AAVE outside Sushi and to sell it back into the distorted Sushi pool. That is the exact opposite side of the victim route's terminal leg, and it is what converts the public settlement's price impact into deterministic ETH profit.
The measurable extracted value is 17912050158685932669052 wei of ETH-equivalent profit generated inside the backrun contract before gas. Of that amount:
13087732595504669669052 wei is paid directly to block coinbase as a builder payment.4824317516734375086288 wei remains as the sender EOA's net gain after gas.46446887913712 wei.The affected public components are CoW's settlement route, the solver helper path that executed the route, and the thin Sushi AAVE/WETH pool that served as the terminal price-setting venue. The loss is economic rather than accounting-based: the route overpaid for AAVE in a way that left immediate same-block value for any unprivileged searcher to capture.
0x9fa9feab3c1989a33424728c23e6de07a40a26a98ff7ff5139f3492ce430801f.0x45388b0f9ff46ffe98a3124c22ab1db2b1764ecb3b61234e29e5c9732b7fd4ab.GPv2Settlement source collected under the auditor artifacts.HooksTrampoline source collected under the auditor artifacts.Morpho, BancorNetworkV3, PoolCollection, BancorNetworkLegacy, and UniswapV2Pair sources collected under the auditor artifacts.0xD524f98F554Bd34f4185678F64a85bB98971d314./workspace/session/artifacts/collector/seed/.