DEV Community

metadevdigital
metadevdigital

Posted on

The $14M Slippage Lesson: How AMM Price Impact Eats Your Lunch

The $14M Slippage Lesson: How AMM Price Impact Eats Your Lunch

cover

On November 8, 2022, a trader on dYdX executed what should've been a routine Uniswap V3 swap using flash loans—and lost $14M in a single transaction. The problem was dead simple: they didn't set amountOutMinimum, or set it wrong. This still happens constantly because developers conflate two separate problems and implement neither correctly.

Slippage and price impact are not the same thing, and misunderstanding this gap is how you lose serious money.

Price Impact vs. Slippage (They're Different)

Price impact is the cost of moving the market yourself. Hit a Uniswap V2 pool with a big order and you're sliding down the curve—the bigger your order relative to liquidity, the worse your execution price. This is deterministic. Given reserves and your input amount, you can calculate it exactly off-chain. You can't eliminate it. It's the spread the market charges for instant execution.

Slippage is what happens between signing your transaction and landing on-chain. Network lag sits your tx in the mempool. Sandwich attackers front-run you to move prices, then back-run to profit. MEV extraction happens. This is the actual leakage vector you can defend against using parameters like amountOutMinimum.

Most developers only think about one of these, and often the wrong one.

The Math

Uniswap V2's constant product formula is:

(x + Δx) × (y - Δy) = x × y
Enter fullscreen mode Exit fullscreen mode

Where x and y are reserves, Δx is input, Δy is output:

Δy = y × Δx / (x + Δx)
Enter fullscreen mode Exit fullscreen mode

Take an ETH/USDC pool: 1000 ETH, 2M USDC. You swap 10 ETH:

Δy = 2,000,000 × 10 / (1000 + 10) ≈ 19,801.98 USDC
Enter fullscreen mode Exit fullscreen mode

Naive rate would be 2000 USDC/ETH × 10 = 20,000 USDC. You eat ~200 USDC in price impact, or 1%. Scale to 100 ETH:

Δy = 2,000,000 × 100 / (1000 + 100) ≈ 181,818 USDC
Enter fullscreen mode Exit fullscreen mode

Expected 200,000. You get 181,818. That's ~$18k gone to price impact—9%. The relationship is non-linear. Big orders get demolished.

The Vulnerable Code (Don't Ship This)

// ❌ VULNERABLE: No slippage protection
function swapWithoutProtection(
    address tokenIn,
    address tokenOut,
    uint256 amountIn
) external {
    ISwapRouter.ExactInputSingleParams memory params =
        ISwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: tokenOut,
            fee: 3000,
            recipient: msg.sender,
            deadline: block.timestamp + 60,
            amountIn: amountIn,
            amountOutMinimum: 0, // 🚨 DEATH WISH
            sqrtPriceLimitX96: 0
        });

    router.exactInputSingle(params);
}
Enter fullscreen mode Exit fullscreen mode

Zero slippage protection. A sandwich attacker watches this pending tx, moves the price, and you get drained. This pattern killed people in 2020–2021. Pancakebunny. Harvest Finance. Over and over.

The Correct Pattern

// ✅ CORRECT: Calculate minimum output off-chain, validate on-chain
function swapWithProtection(
    address tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 minAmountOut // ← Calculated off-chain
) external {
    require(minAmountOut > 0, "Slippage protection required");

    ISwapRouter.ExactInputSingleParams memory params =
        ISwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: tokenOut,
            fee: 3000,
            recipient: msg.sender,
            deadline: block.timestamp + 60,
            amountIn: amountIn,
            amountOutMinimum: minAmountOut,
            sqrtPriceLimitX96: 0
        });

    uint256 amountOut = router.exactInputSingle(params);
    require(amountOut >= minAmountOut, "Slippage exceeded");
}
Enter fullscreen mode Exit fullscreen mode

Off-chain calculation with ethers.js:

// Calculate minimum output with user-configurable slippage tolerance
async function calculateMinAmountOut(
    tokenIn, 
    tokenOut, 
    amountIn, 
    userSlippageBps = 50 // 0.5%, reasonable default
) {
    // Get current reserves from contract
    const reserves = await pool.getReserves();

    // Calculate expected output (price impact included)
    const expectedOutput = calculateAmountOut(
        amountIn, 
        reserves[tokenIndex0],
        reserves[tokenIndex1]
    );

    // Apply user slippage tolerance
    const minAmountOut = expectedOutput
        .mul(10000 - userSlippageBps)
        .div(10000);

    // Safety: reject if user requested >5% slippage (obvious sandwich)
    if (userSlippageBps > 500) {
        throw new Error("Slippage tolerance too high");
    }

    return minAmountOut;
}
Enter fullscreen mode Exit fullscreen mode

The key: calculate minimum output off-chain using current reserves, then bake it into the transaction as an immutable parameter. The contract enforces it. User can't hack it mid-flight.

The Gotchas

V3 concentrated liquidity makes this worse. The same 10 ETH swap that costs 1% in V2 might hit 3–5% in V3 if liquidity is thin at your execution price.

MEV sandwiching is harder to solve contractually. You can use sqrtPriceLimitX96 as a circuit breaker, but determined attackers with enough capital will still push past it. Real defense requires encrypted mempools or threshold encryption—Shutter Network is working on this.

Flash loan–powered price manipulation is a different beast. If someone borrows liquidity to tank the pool price, your amountOutMinimum might still look "reasonable" but represent a 50% loss. Harvest Finance got hit for $34M in 2020. You can't defend against that with slippage parameters alone. You need oracle-backed pricing or other safeguards.

Just Do This

Price impact is honest economics. Slippage is a leak you can plug. Always calculate minimum acceptable output off-chain using live data, pass it as an immutable tx parameter, and enforce it on-chain. Your users' million-dollar moments depend on it. Also: cap user-provided slippage percentages. I've seen frontends let users set 50% slippage "for advanced traders." That's not advanced, that's a foot gun.



Top comments (0)