CoW Protocol: Malicious Solver Drains $50.4M aEthUSDT via Signed Order Exploit + MEV Backrun
On March 12, 2026 (block 24,643,151), a victim address (0x98b9d979) lost approximately $50.4 million worth of Aave-wrapped USDT (aEthUSDT) on Ethereum mainnet through a two-transaction attack. In the primary transaction, a registered CoW Protocol solver (0x3980daa7) submitted a settlement executing a victim-signed order that sold 50.4M aEthUSDT for only 327.24 aEthAAVE – an effective rate of 154,115 USDT/AAVE versus the market rate of ~$149.50/AAVE, representing a 1,034x worse-than-market price. The order’s swap route dumped 17,958 WETH into the Uniswap V2 AAVE/WETH pool, extracting nearly all 331.3 AAVE and catastrophically skewing the pool’s reserves. In a same-block backrun transaction, an MEV bot (0x5884b2fa) flash-loaned 14,176 WETH and arbitraged the distorted UniV2 pool, extracting ~17,912 ETH ($50.3M) of which $36.8M was paid as a coinbase bribe and $13.5M was kept as profit. The attack was not a smart-contract vulnerability but rather the exploitation of cryptographic signatures obtained from the victim through suspected key compromise or phishing.
Root Cause
Vulnerable Contract
No protocol smart contract contains a vulnerability. The “vulnerability” is the compromise of the victim’s signing credentials. The CoW Protocol settlement infrastructure (GPv2Settlement, 0x9008d19f58aabd9ed0d60971565aa8510560ab41, verified source, not a proxy) executed the order exactly as designed.
Vulnerable Function
The exploit leverages two standard protocol entry points that functioned correctly:
GPv2Settlement.settle(address[],uint256[],(uint256,uint256,address,uint256,uint256,uint32,bytes32,uint256,uint256,uint256,bytes)[],(address,uint256,bytes)[][3])– selector0x13d79a0b– called by the malicious solver to process the victim’s signed order.aEthUSDT.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)– selector0xd505accf– called in the pre-hook to authorize the vault relayer to spend the victim’s aEthUSDT via an EIP-2612 permit signature also possessed by the attacker.
Vulnerable Code
The GPv2Settlement.settle() function (verified source, Solidity 0.7.6):
// GPv2Settlement.sol (verified)
function settle(
IERC20[] calldata tokens,
uint256[] calldata clearingPrices,
GPv2Trade.Data[] calldata trades,
GPv2Interaction.Data[][3] calldata interactions
) external nonReentrant onlySolver {
executeInteractions(interactions[0]); // pre-hooks: runs permit()
(
GPv2Transfer.Data[] memory inTransfers,
GPv2Transfer.Data[] memory outTransfers
) = computeTradeExecutions(tokens, clearingPrices, trades);
vaultRelayer.transferFromAccounts(inTransfers); // <-- TRANSFERS VICTIM'S aEthUSDT
executeInteractions(interactions[1]); // interactions: adapter swaps
vault.transferToAccounts(outTransfers); // <-- SENDS buyAmount TO VICTIM
executeInteractions(interactions[2]);
}
The internal price check in computeTradeExecution (from GPv2Trade library):
// GPv2Trade.sol (verified)
require(
order.sellAmount.mul(sellPrice) >= order.buyAmount.mul(buyPrice),
"GPv2: limit price not respected" // <-- VULNERABILITY: checks clearing price
); // ratio, NOT whether the order is at
// fair market value
Why It’s Vulnerable
Expected behavior: A CoW Protocol order should represent the owner’s genuine intention to trade at or near fair market value. The protocol enforces only that the clearing price ratio (sellAmount * sellPrice >= buyAmount * buyPrice) is respected and that the signed buyAmount is delivered. It does not – and by design cannot – verify that the signed order terms are economically reasonable.
Actual behavior: The victim signed (or had their key compromised to sign) a GPv2Order with:
sellToken: aEthUSDT (Aave USDT aToken, 6 decimals)buyToken: aEthAAVE (Aave AAVE aToken, 18 decimals)sellAmount: 50,432,688,416,180 raw (= 50,432,688.42 aEthUSDT, the victim’s entire balance)buyAmount: 327,241,335,505,966,487,788 raw (= 327.24 aEthAAVE)
This implies an effective rate of 154,115 USDT per AAVE – 1,034x the market rate of ~$149.50/AAVE. The victim sold their entire balance for 0.097% of fair value.
The solver also possessed a valid EIP-2612 permit signature from the victim authorizing the vault relayer (0xc92e8bdf) to spend 50,432,688,416,180 aEthUSDT with a deadline of 1,773,340,224 – only 421 seconds (7 minutes) after the block timestamp of 1,773,339,803. This tight window is consistent with both signatures being generated together immediately before the settlement submission.
Why this matters: The CoW Protocol onlySolver modifier checks only that the caller is on the solver allow-list. A solver with access to the victim’s order and permit signatures can submit any conforming settlement. The protocol cannot distinguish malicious solver behavior from legitimate execution because both result in valid cryptographic signatures.
Normal flow vs Attack flow:
Normal: User signs order via CoW Swap UI at near-market prices. Solver competition finds best execution. Surplus is returned to the user.
Attack: Attacker obtains victim’s signing credentials (private key compromise or phishing). Attacker crafts a settlement selling 50.4M USDT-equivalent for $48,921 of aEthAAVE (0.097% of value). The massive USDT-to-WETH swap through UniV3 followed by WETH-to-AAVE swap through UniV2 catastrophically distorts the UniV2 AAVE/WETH pool. An MEV backrun in the same block arbitrages the distorted pool to capture the remaining $50.3M of value.
Attack Execution
High-Level Flow
Transaction 1 – Primary Exploit (0x9fa9feab..., tx index 1):
- Solver
0x3980daa7callsGPv2Settlement.settle()as a registered solver. - Pre-hook executes:
aEthUSDT.permit()grants vault relayer 50.4M aEthUSDT allowance using victim’s EIP-2612 signature. - Settlement recovers victim address
0x98b9d979from order signature via ecrecover, confirming a valid signature. - Vault relayer transfers 50,432,688.42 aEthUSDT from victim to settlement contract.
- Settlement approves CoWInteractionExecutor (
0xd524f98f) to spend 50.4M aEthUSDT. - Executor calls CoWSwapAdapter (
0x699c5bd4) which:- Pulls 50.4M aEthUSDT from settlement via transient allowance
- Withdraws underlying 50.4M USDT from Aave V3
- Swaps 50.4M USDT for 17,957.81 WETH on Uniswap V3 (WETH/USDT pool)
- Swaps 17,957.81 WETH for 331.31 AAVE on Uniswap V2 (AAVE/WETH pool), draining 99.9% of the pool’s AAVE liquidity
- Supplies 331.31 AAVE to Aave V3, minting 331.31 aEthAAVE to settlement
- Settlement transfers 327.24 aEthAAVE to victim (the signed
buyAmount); 4.06 aEthAAVE surplus remains in settlement.
Transaction 2 – MEV Backrun (0x45388b0f..., tx index 2):
- MEV bot (
0x5884b2fa) flash-loans 14,175.71 WETH from Morpho Blue Vault (0xbbbbbbbb). - Bot unwraps 17.72 WETH to ETH, trades via Bancor (ETH -> BNT -> AAVE), obtaining 128.57 AAVE.
- Bot sends 128.57 AAVE to the distorted UniV2 AAVE/WETH pool and swaps for 17,929.77 WETH.
- Bot repays 14,175.71 WETH flash loan.
- Bot unwraps remaining 17,912.05 WETH to ETH: pays 13,087.73 ETH coinbase bribe, keeps 4,824.32 ETH profit.
Detailed Call Trace
Transaction 1 (Primary Exploit):
EOA (0x3980daa7) -> GPv2Settlement.settle() [0x13d79a0b] (CALL)
|
+-- GPv2Settlement -> CoWAllowListAuth(0x2c4c28dd).isSolver(0x3980daa7) [0x02cc250d] (STATICCALL)
| +-- (DELEGATECALL) GPv2AllowListAuthentication -> returns true
|
+-- [Pre-hooks] GPv2Settlement -> HooksTrampoline(0x60bf7823).execute() [0x760f2a0b] (CALL)
| +-- HooksTrampoline -> aEthUSDT.permit(victim, vaultRelayer, 50432688416180, deadline, v, r, s)
| [0xd505accf] (CALL)
| +-- (DELEGATECALL) aEthUSDT_Impl.permit -> ecrecover -> victim confirmed
| -> emits Approval(victim, vaultRelayer, 50432688416180)
|
+-- [Signature recovery] GPv2Settlement -> ecrecover precompile (0x0001)
| -> returns victim 0x98b9d979
|
+-- [transferFrom] GPv2Settlement -> GPv2VaultRelayer(0xc92e8bdf).transferFromAccounts() [0x7d10d11f] (CALL)
| +-- VaultRelayer -> aEthUSDT.transferFrom(victim, settlement, 50432688416180) [0x23b872dd]
| -> emits Transfer(victim -> settlement, 50,432,688.42 aEthUSDT)
|
+-- GPv2Settlement -> aEthUSDT.approve(CoWInteractionExecutor, 50432688416180) [0x095ea7b3]
|
+-- [Interactions] GPv2Settlement -> CoWInteractionExecutor(0xd524f98f).func_0x494b3137() [0x494b3137] (CALL)
| +-- CoWInteractionExecutor -> CoWSwapAdapter(0x699c5bd4).func_0x9fcbfeeb() [0x9fcbfeeb] (CALL)
| +-- aEthAAVE.balanceOf(settlement) [0x70a08231] (STATICCALL) -> pre-trade balance
| +-- CoWSwapAdapter -> CoWInteractionExecutor.func_0x381fef89() [0x381fef89] (CALL)
| | +-- aEthUSDT.transferFrom(settlement -> CoWSwapAdapter, 50432688416180) [0x23b872dd]
| +-- aEthUSDT.allowance(adapter, AavePool) [0xdd62ed3e] (STATICCALL)
| +-- AaveV3Pool.withdraw(USDT, 50432688416180, adapter) [0x69328dec] (CALL)
| | -> burns 50.4M aEthUSDT, sends 50.4M USDT to adapter
| +-- USDT.balanceOf(adapter) [0x70a08231] (STATICCALL)
| +-- UniswapV3_WETH_USDT.swap(recipient=UniV2, zeroForOne=false, amountSpecified=50432688416180, ...) [0x128acb08] (CALL)
| | +-- UniV3 -> WETH.transfer(UniV2_AAVE_WETH, 17957810805702142342238) [0xa9059cbb]
| | +-- UniV3 -> CoWSwapAdapter.uniswapV3SwapCallback() [0xfa461e33] (CALL)
| | +-- adapter -> USDT.transfer(UniV3, 50432688416180) [0xa9059cbb]
| +-- WETH.balanceOf(adapter) [0x70a08231] (STATICCALL) -> 0
| +-- UniswapV2_AAVE_WETH.getReserves() [0x0902f1ac] (STATICCALL)
| | -> reserve0=331.63 AAVE, reserve1=17.65 WETH
| +-- WETH.balanceOf(UniV2) [0x70a08231] (STATICCALL) -> 17975.46 WETH (after V3 transfer)
| +-- UniswapV2_AAVE_WETH.swap(331305315608938235428 AAVE, 0 WETH, adapter, ...) [0x022c0d9f] (CALL)
| | +-- UniV2 -> AAVE.transfer(adapter, 331305315608938235428) [0xa9059cbb]
| +-- AAVE.balanceOf(adapter) [0x70a08231] (STATICCALL)
| +-- AAVE.allowance(adapter, AavePool) [0xdd62ed3e] (STATICCALL)
| +-- AaveV3Pool.supply(AAVE, 331305315608938235428, settlement, 0) [0x617ba037] (CALL)
| +-- AAVE.transferFrom(adapter -> aEthAAVE, 331.31 AAVE) [0x23b872dd]
| +-- aEthAAVE.mint(adapter, settlement, 331.31, ...) [0xb3f1c93d]
| -> emits Transfer(0x0 -> settlement, 331.31 aEthAAVE)
|
+-- [Out-transfers] GPv2Settlement -> aEthAAVE.transfer(victim, 327241335505966487788) [0xa9059cbb]
-> emits Transfer(settlement -> victim, 327.24 aEthAAVE)
Transaction 2 (MEV Backrun):
EOA (0x5884b2fa) -> MEV Bot (0x06cff708) (CALL)
|
+-- Bot -> Morpho Blue Vault (0xbbbbbbbb).flashLoan() [0xe0232b42] (CALL)
+-- Morpho -> WETH.transfer(bot, 14175707124029000000000) [0xa9059cbb]
+-- Morpho -> Bot.callback() [0x31f57072] (CALL)
+-- Bot -> Bot (self-call, internal routing)
+-- WETH.withdraw(17.72 ETH) [0x2e1a7d4d]
+-- Bot -> Bancor CarbonController(0xeef417e1) (17.72 ETH) -> gets 122,365 BNT
+-- Bot -> Bancor Network(0x2f9ec37d).trade(BNT -> AAVE via 0x5ba4) [0xb77d239b]
| +-- BNT.transferFrom(bot -> pool, 122,365 BNT)
| +-- Pool(0x5ba42fac) -> AAVE.transfer(UniV2_AAVE_WETH, 128.57 AAVE) [0xa9059cbb]
+-- UniV2_AAVE_WETH.getReserves() [0x0902f1ac]
| -> reserve0=0.327 AAVE, reserve1=17,975.46 WETH (distorted by Tx1)
+-- UniV2_AAVE_WETH.swap(0 AAVE, 17929.77 WETH, bot, ...) [0x022c0d9f]
| +-- WETH.transfer(bot, 17929770158685932657052) [0xa9059cbb]
+-- WETH.withdraw(17912.05 ETH) [0x2e1a7d4d]
+-- Bot -> Coinbase(0x4838b106) {13,087.73 ETH} (validator bribe)
+-- Bot -> EOA(0x5884b2fa) {4,824.32 ETH} (profit)
+-- Morpho -> WETH.transferFrom(bot -> Morpho, 14175707124029000000000) [0x23b872dd]
-> flash loan repaid
Key selector verifications:
settle(...)->0x13d79a0bexecute((address,bytes,uint256)[])->0x760f2a0bpermit(address,address,uint256,uint256,uint8,bytes32,bytes32)->0xd505accftransferFromAccounts((address,address,uint256,bytes32)[])->0x7d10d11fwithdraw(address,uint256,address)->0x69328decswap(address,bool,int256,uint160,bytes)->0x128acb08swap(uint256,uint256,address,bytes)->0x022c0d9fsupply(address,uint256,address,uint16)->0x617ba037
Financial Impact
Transaction 1 – Primary Exploit
From funds_flow.json (primary evidence source):
| Address | Role | Token | Net Change |
|---|---|---|---|
0x98b9d979 | Victim | aEthUSDT | -50,432,679.42 (sold ~entire balance) |
0x98b9d979 | Victim | aEthAAVE | +327.24 |
0x9008d19f | GPv2Settlement | aEthAAVE | +4.064 (solver surplus) |
0x4e68ccd3 | UniV3 WETH/USDT | USDT | +50,432,688.42 |
0x4e68ccd3 | UniV3 WETH/USDT | WETH | -17,957.81 |
0xd75ea151 | UniV2 AAVE/WETH | WETH | +17,957.81 |
0xd75ea151 | UniV2 AAVE/WETH | AAVE | -331.31 |
Victim loss: The victim sold 50,432,688.42 aEthUSDT (market value ~$50,432,688) and received 327.24 aEthAAVE worth ~$48,921 at the block-time AAVE price of $149.50. Net victim loss: **$50,383,767 USD**.
Solver gain from Tx1: 4.064 aEthAAVE surplus retained in the settlement contract (worth ~$607).
UniV2 pool distortion: The AAVE/WETH pool went from balanced reserves (331.63 AAVE / 17.65 WETH) to extreme imbalance (0.327 AAVE / 17,975.46 WETH). The pool held approximately $50.4M of WETH against almost no AAVE, creating a massive arbitrage opportunity.
Transaction 2 – MEV Backrun
From 45388b0f_funds_flow.json (primary evidence source):
| Address | Role | Token | Net Change |
|---|---|---|---|
0xbbbbbbbb | Morpho Flash Loan | WETH | 0 (repaid) |
0x06cff708 | MEV Bot Contract | WETH | +17,929.77 |
0xd75ea151 | UniV2 AAVE/WETH | WETH | -17,929.77 |
0xd75ea151 | UniV2 AAVE/WETH | AAVE | +128.57 |
0x5ba42fac | Bancor Pool | AAVE | -128.57 |
0x5884b2fa | Bot EOA | ETH | +4,824.32 |
0x4838b106 | Coinbase (block proposer) | ETH | +13,087.73 |
Bot profit: 4,824.32 ETH (~ $13,548,606) sent to bot EOA.
Coinbase bribe: 13,087.73 ETH (~ $36,755,568) sent to block proposer.
Total value extracted from UniV2: 17,912.05 ETH (~$50,304,174), representing the vast majority of the WETH that was dumped into the pool by the primary exploit.
Combined Impact Summary
| Metric | Amount |
|---|---|
| Victim total loss | ~$50,383,767 USD |
| Value received by victim | ~$48,921 (327.24 aEthAAVE) |
| Solver surplus (Tx1) | ~$607 (4.064 aEthAAVE) |
| MEV bot profit (Tx2) | ~$13,548,606 (4,824.32 ETH) |
| Coinbase bribe (Tx2) | ~$36,755,568 (13,087.73 ETH) |
| UniV3 WETH/USDT pool | No loss (normal AMM execution) |
| Aave V3 | No loss (normal supply/withdraw) |
The victim’s ~$50.4M aEthUSDT was converted through the following chain: aEthUSDT -> USDT -> WETH (dumped into UniV2) -> minimal AAVE (to victim) + massive WETH arbitrage opportunity (captured by backrun). The economic value was ultimately extracted by the block proposer ($36.8M bribe) and the MEV bot ($13.5M profit).
No protocol (CoW Protocol, Aave V3, Uniswap V2/V3) was exploited or left insolvent. The loss is entirely borne by the victim whose signing credentials were compromised.
Evidence
Transaction 1 status: receipt.status = 0x1 (success). Transaction index 1 in block 24,643,151.
Transaction 2 status: receipt.status = 0x1 (success). Transaction index 2 in block 24,643,151.
Order Trade event (GPv2Settlement log index 0x9, topic 0xa07a543a):
owner = 0x98b9d979c33dd7284c854909bcc09b51fbf97ac8(victim)sellToken = 0x23878914...(aEthUSDT)buyToken = 0xa700b4eb...(aEthAAVE)sellAmount = 0x2dde467a65b4= 50,432,688,416,180 raw (50,432,688.42 USDT at 6 decimals)buyAmount = 0x11bd62c4d8018e58ec= 327,241,335,505,966,487,788 raw (327.24 aEthAAVE at 18 decimals)feeAmount = 0
Signature recovery (trace depth 1, ecrecover precompile at address 0x0001):
- Input digest begins with
0xe5dd1404...(this is the EIP-712 order hash, not a function selector) - Output:
0x00000000000000000000000098b9d979c33dd7284c854909bcc09b51fbf97ac8 - Confirms the order was signed by the victim’s private key.
Permit event (aEthUSDT log index 0x7, Approval):
owner = 0x98b9d979,spender = 0xc92e8bdf(VaultRelayer)amount = 50,432,688,416,180- Deadline
0x69b30640= 1,773,340,224 > block.timestamp 1,773,339,803 (valid, 421-second window)
Solver allow-list check (trace index 1-2):
isSolver(0x3980daa7)via GPv2AllowListAuthentication -> returnedtrue- Confirms the solver was legitimately registered on the allow-list.
UniV2 AAVE/WETH pre-swap reserves (trace index 70, getReserves()):
reserve0 (AAVE) = 331.63 AAVEreserve1 (WETH) = 17.65 WETH- Pool implied AAVE/USD price: ~$149.50 (consistent with market)
UniV2 post-exploit reserves (backrun trace, getReserves()):
reserve0 (AAVE) = 0.327 AAVEreserve1 (WETH) = 17,975.46 WETH- Pool was 99.9% drained of AAVE, creating an extreme arbitrage opportunity.
Backrun flash loan (Tx2 trace):
- Morpho Blue Vault (
0xbbbbbbbb) lent 14,175.71 WETH, fully repaid in same transaction.
Backrun profit distribution (Tx2 trace):
0x4838b106(coinbase): received 13,087.73 ETH0x5884b2fa(bot EOA): received 4,824.32 ETH
Related URLs
- Transaction 1 (Primary): https://etherscan.io/tx/0x9fa9feab3c1989a33424728c23e6de07a40a26a98ff7ff5139f3492ce430801f
- Transaction 2 (Backrun): https://etherscan.io/tx/0x45388b0f9ff46ffe98a3124c22ab1db2b1764ecb3b61234e29e5c9732b7fd4ab
- Block: https://etherscan.io/block/24643151
- Victim: https://etherscan.io/address/0x98b9d979c33dd7284c854909bcc09b51fbf97ac8
- Solver/Attacker: https://etherscan.io/address/0x3980daa7eaad0b7e0c53cfc5c2760037270da54d
- MEV Bot EOA: https://etherscan.io/address/0x5884b2faa9ad6f38010831e2290e515af17a7d47
- MEV Bot Contract: https://etherscan.io/address/0x06cff7088619c7178f5e14f0b119458d08d2f5ef
- CoW Protocol Settlement: https://etherscan.io/address/0x9008d19f58aabd9ed0d60971565aa8510560ab41