MEV — Sandwich Attack
An auto-swap contract calls a Uniswap V2 router with amountOutMin hardcoded to zero. Any pending swap in the public mempool can be sandwiched: a bot buys the output token first, lets the victim's swap execute at the worsened price, then sells to pocket the spread.
~7 min read
MEV (Maximal Extractable Value) is value extracted by reordering, inserting, or censoring transactions in a block. The sandwich attack is the most common form: a bot exploits a victim's publicly visible swap to extract value by front-running and back-running. The root cause is always a missing or inadequate slippage guard.
1. The public mempool and transaction ordering
Ethereum transactions are broadcast to a public mempool before they are included in a block. Any node — including MEV bots — can see pending transactions, their calldata, and the approximate price impact they will have. Block proposers (validators) can reorder transactions within a block to maximize their own revenue.
A bot watching the mempool sees a swap call: 'user wants to swap 100 WETH for USDC at the current market price'. The bot can compute exactly how much that swap will move the AMM price. It can then insert two transactions: one before the victim (buy the output token to move the price up) and one after (sell the output token at the now-higher price), sandwiching the victim.
The victim receives fewer tokens than expected. The difference goes to the bot as profit. The amount the bot extracts depends on how much the swap moves the price (price impact) and how wide the victim's acceptable slippage window is. With amountOutMin = 0, the victim's window is infinite — the bot can extract almost everything.
2. AMM price mechanics
A constant-product AMM (Uniswap V2) maintains the invariant x * y = k, where x and y are the reserves of two tokens. A swap of dx into the pool gives dy = y - k/(x + dx). Larger swaps relative to pool size cause larger price movements — this is price impact.
Slippage tolerance is the acceptable deviation from the expected output. If you expect 100 USDC but pass amountOutMinimum = 99 (1% tolerance), the transaction reverts if you get less than 99. If you pass amountOutMinimum = 0, the transaction accepts any output — even 0 USDC.
3. The attack — zero amountOutMin
The VulnerableAutoSwap contract calls swapExactTokensForTokens with amountOutMin = 0. This means the router will accept any amount of tokenOut, no matter how bad the rate. The attack pseudocode:
// Bot sees victim's swap(100 WETH) in mempool
// Step 1: bot front-runs — buys 50 WETH worth of USDC, raises price
// Step 2: victim's swap executes at worse price — gets 900 USDC instead of 1000
// Step 3: bot back-runs — sells the USDC back, profits ~90 USDC
// Victim: amountOutMin = 0, so swap did not revert despite receiving far lessThe combination of amountOutMin = 0 and the public mempool is the vulnerability. Either one without the other limits the attack: a private RPC hides the transaction, and a valid amountOutMin causes the sandwiched swap to revert and the bot's front-run to be unprofitable.
Attack timeline
- 1
- 2
- 3
- 4
- 5
4. The fix — compute amountOutMinimum
Pass a valid amountOutMinimum based on the expected output minus an acceptable slippage (e.g., 0.5%). This prevents the swap from executing if the price moves more than that tolerance:
The minAmountOut parameter must be computed off-chain using current pool reserves or a TWAP oracle, not hardcoded. A TWAP (time-weighted average price) is manipulation-resistant because it averages price over multiple blocks, making it impractical to skew with a single block's sandwich.
For contracts that auto-execute swaps on behalf of users (like this one), consider routing through Flashbots Protect or another private RPC to keep the transaction out of the public mempool. This removes the bot's ability to see and target the swap. Both defenses together — slippage + private RPC — are significantly stronger than either alone.
5. What to look for as an auditor
- Grep for amountOutMin = 0 or amountOutMinimum: 0 in any router call. This is an immediate finding regardless of context.
- Check deadline parameter: if deadline = 0 or deadline = block.timestamp, the transaction can be held in the mempool and executed stale. Deadline should be a user-supplied future timestamp.
- Any contract that executes swaps on behalf of users (keeper bots, zapper contracts, auto-compounders) is a high-value sandwich target. Audit slippage handling carefully.
- TWAP vs spot price: if the contract uses an on-chain spot price to compute slippage, that price itself can be manipulated. A TWAP is more robust. Chainlink price feeds are another option.
- Multi-hop swaps: if a swap routes through multiple pools (A → B → C), slippage accumulates at each hop. The amountOutMinimum must account for the worst-case across all hops.
- Note the pattern: amountOutMin = 0 is not just a sandwich risk — it also exposes users to large organic price impact from liquidity depth changes. Even without an active attacker, the swap can execute at a terrible price during a volatile market.
With the sandwich mechanic clear, open the Hunt tab. Find the exact parameter that enables the exploit, then describe how a bot would extract value from a pending swap call.