Skip to content
Last updated: March 11, 2025

SCWE-037: Insufficient Protection Against Front-Running

Stable Version v0.0.1

This content is in the version-(v0.0.1) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Relationships

Description

Insufficient protection against front-running refers to vulnerabilities that allow malicious actors to exploit the order of transactions for profit. This can lead to: - Unauthorized actions by malicious actors. - Loss of funds or data. - Exploitation of the contract’s logic.

Remediation

  • Use commit-reveal schemes: Implement commit-reveal mechanisms to hide transaction details until they are finalized.
  • Add delays: Introduce time delays for critical operations to reduce the risk of front-running.
  • Test thoroughly: Conduct extensive testing to ensure front-running protection is effective.

Examples

  • Vulnerable to Front-Running
    pragma solidity ^0.8.0;
    
    interface IERC20 {
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
        function balanceOf(address account) external view returns (uint256);
    }
    
    contract FrontRunningVulnerable {
        IERC20 public token;
        uint256 public price = 1 ether; // 1 token = 1 ETH
    
        constructor(address _token) {
            token = IERC20(_token);
        }
    
        function buyTokens(uint256 amount) public payable {
            require(msg.value >= amount * price, "Insufficient ETH");
            token.transferFrom(address(this), msg.sender, amount);
        }
    }
    
    Why is this vulnerable?
  • Attackers see the transaction in the mempool and execute a transaction to buy first, raising the price.
  • Victim's transaction executes at a higher price or fails due to slippage.
  • Attacker sells at a profit, exploiting sandwich attacks.

  • Protected Against Front-Running- Commit-Reveal to Hide Trade Intent

    pragma solidity ^0.8.0;
    
    contract SecureTrade {
        mapping(address => bytes32) public commitments;
        uint256 public price = 1 ether; // 1 token = 1 ETH
    
        function commitTrade(bytes32 hash) public {
            commitments[msg.sender] = hash;
        }
    
        function executeTrade(uint256 amount, bytes32 secret) public payable {
            require(commitments[msg.sender] == keccak256(abi.encodePacked(amount, secret)), "Invalid commitment");
            require(msg.value >= amount * price, "Insufficient ETH");
    
            commitments[msg.sender] = 0; // Prevent reusing commitment
            // Execute trade after revealing the commitment
        }
    }
    
    Why is this better?

  • Traders commit to the trade off-chain before revealing the amount, preventing mempool sniping.
  • Transactions cannot be front-run because attackers don’t know the amount until revealed.