Skip to main content
Sigvex

Front-Running Remediation

How to reduce front-running exposure through commit-reveal schemes, slippage protection, and transaction batching.

Front-Running Remediation

Overview

Front-running occurs when a transaction’s outcome is predictable from the mempool and an observer can submit a competing transaction with higher gas to exploit it. Complete elimination is impossible on a public mempool blockchain, but the attack surface can be dramatically reduced by making transactions unpredictable (commit-reveal), bounded in value extracted (slippage limits), or resistant to ordering manipulation (batch auctions).

Related Detector: Front-Running

Before (Vulnerable)

// Vulnerable: swap with no slippage protection — trivially sandwich-able
function swap(address tokenIn, address tokenOut, uint256 amount) external {
    address[] memory path = new address[](2);
    path[0] = tokenIn;
    path[1] = tokenOut;
    router.swapExactTokensForTokens(amount, 0, path, msg.sender, block.timestamp);
}

After (Fixed)

// Fixed: caller-specified minimum output and deadline
function swap(
    address tokenIn,
    address tokenOut,
    uint256 amount,
    uint256 minOut,
    uint256 deadline
) external {
    require(deadline > block.timestamp, "Deadline passed");
    address[] memory path = new address[](2);
    path[0] = tokenIn;
    path[1] = tokenOut;
    router.swapExactTokensForTokens(amount, minOut, path, msg.sender, deadline);
}

Alternative Mitigations

Commit-reveal for sensitive operations:

// Phase 1: commit a hash of the secret action
mapping(address => bytes32) public commitments;

function commit(bytes32 hash) external {
    commitments[msg.sender] = hash;
}

// Phase 2: reveal and execute — front-runners cannot act without knowing the secret
function reveal(bytes32 secret, uint256 amount) external {
    bytes32 expected = keccak256(abi.encodePacked(secret, amount, msg.sender));
    require(commitments[msg.sender] == expected, "Invalid reveal");
    delete commitments[msg.sender];
    _execute(msg.sender, amount);
}

Incremental allowance pattern (prevents ERC-20 approve front-running):

// Use increaseAllowance / decreaseAllowance instead of approve
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
    _approve(msg.sender, spender, allowance(msg.sender, spender) + addedValue);
    return true;
}

Private mempool submission (Flashbots, MEV Blocker) — submit transactions to builders who guarantee inclusion without public mempool exposure. This is off-chain infrastructure and cannot be enforced by contract code.

Common Mistakes

Setting deadline = block.timestamp — this provides no meaningful deadline window because validators include the block immediately.

Using on-chain price as minOut — if minOut is derived from a manipulable oracle in the same transaction, slippage protection is bypassed by manipulating the oracle before deriving minOut.

Commit-reveal without a minimum reveal delay — if there is no block delay between commit and reveal, front-runners can still act between the two transactions.

References