What Is MevBot's Front Running Attack on Users? How to Avoid It? πŸ€”

Written by codingjourneyfromunemployment | Published 2023/12/01
Tech Story Tags: cybersecurity | solidity | ethereum | mevbot | front-running-attack | sandwich-attack-explained | prevent-front-running-attacks | ethereum-attacks

TLDRThis reminds us that when writing smart contracts, we need to consider vulnerabilities that MevBots might exploit and attack and take appropriate measures to avoid them. In this article, I will demonstrate a simple example of a front-running attack and how to prevent such attacks. πŸ›‘οΈvia the TL;DR App

Many users often complain about being victims of sandwich attacks or front-running attacks. In reality, these attacks are often initiated by Mev Bots. In this short article, I won’t delve into the details of MEV (you can refer to the Ethereum official documentation for that).

MevBots are still actively operating on the Ethereum network today, with some continuing to target users. 🌐

This reminds us that when writing smart contracts, we need to consider vulnerabilities that MevBots might exploit and attack and take appropriate measures to avoid them. In this article, I will demonstrate a simple example of a front-running attack and how to prevent such attacks. πŸ›‘οΈ

First, Let's Describe the Process of a Front Running Attack

Alice deployed a simple game contract and deposited 10 ether. The rule of the game is to guess the string (a word) corresponding to a specific hash, and the player who guesses it wins a reward of 10 ether. The code is as follows:

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

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 player, Bob, guesses the word corresponding to the hash as "Ethereum" and calls the solve function with this word as the parameter. His wallet's default transaction gas is set at 10 gwei.

The attacker Eve's bot detects Bob's transaction in the memory pool and automatically initiates another transaction with the same content but sets the gas to 1000 gwei.

Transactions in the memory pool are sorted based on gas, and since Eve's transaction is set with much higher gas than Bob's, it gets included in the block before Bob's and is mined first.

Thus, Eve beats Bob to win the 10 ether in the game. Bob's call to the solve function only leads to a revert, with the message β€œFailed to send Ether.” πŸ’Έ

How to Prevent Such Front-Running Attacks

This attack is quite straightforward, but it showcases an important design line of MEV, monitoring and attacking in the dark forest of the memory pool. Many MevBots work on this principle, including the sandwich attack bots mentioned earlier.

Based on this design strategy of MevBots, when we write contracts, we can consider the following methods to reduce such risks: πŸ”

Using Commit-Reveal Schemes Under Existing Conditions for Transaction Design:

Let's use the same game as an example to explain how the Commit-Reveal transaction design avoids such monitoring and attacks targeting the memory pool. The code is as follows:

contract SecuredFindThisHash {
    // Struct is used to store the commit details
    struct Commit {
        bytes32 solutionHash;
        uint commitTime;
        bool revealed;
    }

    // The hash that is needed to be solved
    bytes32 public hash =
        0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;

    // Address of the winner
    address public winner;

    // Price to be rewarded
    uint public reward;

    // Status of game
    bool public ended;

    // Mapping to store the commit details with address
    mapping(address => Commit) commits;

    // Modifier to check if the game is active
    modifier gameActive() {
        require(!ended, "Already ended");
        _;
    }

    constructor() payable {
        reward = msg.value;
    }

    /* 
       Commit function to store the hash calculated using keccak256(address in lowercase + solution + secret). 
       Users can only commit once and if the game is active.
    */
    function commitSolution(bytes32 _solutionHash) public gameActive {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime == 0, "Already committed");
        commit.solutionHash = _solutionHash;
        commit.commitTime = block.timestamp;
        commit.revealed = false;
    }

    /* 
        Function to get the commit details. It returns a tuple of (solutionHash, commitTime, revealStatus);  
        Users can get solution only if the game is active and they have committed a solutionHash
    */
    function getMySolution() public view gameActive returns (bytes32, uint, bool) {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime != 0, "Not committed yet");
        return (commit.solutionHash, commit.commitTime, commit.revealed);
    }

    /* 
        Function to reveal the commit and get the reward. 
        Users can reveal the solution only if the game is active and they have committed a solutionHash before this block and not revealed yet.
        It generates a keccak256(msg.sender + solution + secret) and checks it with the previously committed hash.  
        Front runners will not be able to pass this check since the msg.sender is different.
        Then the actual solution is checked using keccak256(solution), if the solution matches, the winner is declared, 
        the game is ended and the reward amount is sent to the winner.
    */
    function revealSolution(
        string memory _solution,
        string memory _secret
    ) public gameActive {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime != 0, "Not committed yet");
        require(commit.commitTime < block.timestamp, "Cannot reveal in the same block");
        require(!commit.revealed, "Already committed and revealed");

        bytes32 solutionHash = keccak256(
            abi.encodePacked(Strings.toHexString(msg.sender), _solution, _secret)
        );
        require(solutionHash == commit.solutionHash, "Hash doesn't match");

        require(keccak256(abi.encodePacked(_solution)) == hash, "Incorrect answer");

        winner = msg.sender;
        ended = true;

        (bool sent, ) = payable(msg.sender).call{value: reward}("");
        if (!sent) {
            winner = address(0);
            ended = false;
            revert("Failed to send ether.");
        }
    }
}
  1. First, Alice still deploys a game contract to guess the word corresponding to the hash to win 10 ether.

  2. Bob finds the corresponding word "Ethereum." He uses keccak256(abi.encodePacked(Strings.toHexString(msg.sender), _solution, _secret) on the frontend to pack his own address, the answer, and a secret into a hash value (let's call this correctAnswerHash) and calls the commitSolution function, passing in this correctAnswerHash value. A commit is then saved in the contract's commits mapping.

  3. Attacker Eve's MevBot detects this transaction in the memory pool and submits another transaction with the same parameter as Bob's, also correctAnswerHash, at a higher gas. Her commit is also saved in the contract's commits mapping. But at this point, no one has won the reward yet because there is a second step to the game.

  4. Then Bob calls the revealSolution function, this time passing in two parameters on the frontend: the word he guessed "Ethereum" and the secret he set earlier. The contract again packs and calculates the hash with these two inputs and the caller's address: keccak256(abi.encodePacked(Strings.toHexString(msg.sender), _solution, _secret). We'll call this hash correctAnswerHash2.

  5. The contract compares the correctAnswerHash and correctAnswerHash2 for the same caller. Bob's will be the same as the parameters used in both calculations are identical. However, Eve's will be different, as her first submitted correctAnswerHash was calculated using Bob's address, while the second calculated correctAnswerHash2 uses her own address.

  6. In this way, even if Eve continuously monitors the memory pool and submits transactions with higher gas ahead of Bob, she ultimately cannot pass the contract's check, and Bob wins the game. πŸŽ‰

Adopting Privacy-Enhancing Technologies or Services Like Flashbots:

Since the design strategy of these MevBot attacks is based on monitoring and attacking the memory pool, as long as our transactions are not publicly exposed in the memory pool, we can avoid these attacks.

One effective way is to use Zero-Knowledge Proofs (ZK-Proofs). ZK-Proofs are an encryption technology that allows one party (the prover) to prove to another party (the verifier) that a statement is true without revealing any other information about the statement.

For example, in this game, users can generate a proof that they know an input that produces the correct hash output without publicly exposing this input on the blockchain. They can only claim the reward when their proof is verified as true.

Some proof methods, like zk-SNARKs, can provide fast verification times. Thus, we currently see many Ethereum network L2s applying zero-knowledge proofs for upgrades.

Another widely used method nowadays is to employ services like Flashbots. πŸš€ These services enable the bundling of transactions into a package that are submitted directly to miners (validators), bypassing the mempool. 🌐

This approach effectively shields your transactions from being exposed in the mempool, thereby eliminating the possibility of being detected and exploited by the aforementioned MevBots. πŸ›‘οΈ

That wraps up our discussion on Front Running attacks and related security strategies. πŸ“ If you've made it this far, please take a moment to give this a like! πŸ‘πŸ™‚


Written by codingjourneyfromunemployment | Middle-aged and determined to reinvent myself in the world of programming
Published by HackerNoon on 2023/12/01