Command Palette

Search for a command to run...

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

Available

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.

The sandwich exploit: bot buys tokenOut (raises price) → victim's swap executes at the inflated price (gets fewer tokenOut) → bot sells tokenOut back (lowers price, restores reserves, bot pockets the spread). The victim's transaction cost becomes the bot's profit.

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 less

The 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. 1

    Mempool observation

    MEV bot monitors the public mempool. It detects a pending call to VulnerableAutoSwap.swap(largeAmount) and computes the expected price impact on the Uniswap V2 pool.

  2. 2

    Front-run

    Bot submits a buy transaction with higher gas (priority fee) that arrives in the same block before the victim. Bot buys tokenOut, pushing up the price. Pool reserves shift.

  3. 3

    Victim executes

    Victim's swap executes at the now-worse price. Because amountOutMin = 0, the router accepts the lower output and does not revert. Victim receives substantially fewer tokens.

  4. 4

    Back-run

    Bot's second transaction (after victim) sells the tokenOut it bought in step 2. The price is now lower again (near original). Bot profits from the spread.

  5. 5

    Net result

    Bot's profit = victim's slippage loss minus bot's gas cost. For large swaps on illiquid pools, this can exceed 5% of the trade value. Victim has no recourse.

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:

function swap(uint256 amountIn, uint256 minAmountOut) external {
    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
    IERC20(tokenIn).approve(address(router), amountIn);
    address[] memory path = new address[](2);
    path[0] = tokenIn;
    path[1] = tokenOut;
    // amountOutMin must be computed off-chain using current reserves
    // e.g., expectedOut * 995 / 1000 for 0.5% slippage tolerance
    router.swapExactTokensForTokens(
        amountIn,
        minAmountOut, // NOT 0
        path,
        msg.sender,
        block.timestamp
    );
}

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.

← Back to skill tree