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
Recommended Fix
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.