Front-Running
Detects transactions where the outcome depends on call ordering, enabling miners or other users to manipulate execution order for profit.
Front-Running
Overview
Remediation Guide: How to Fix Front-Running
The front-running detector identifies smart contract patterns where the outcome of a transaction is predictable from the mempool and can be exploited by an observer who submits a competing transaction with a higher gas price. This includes: DEX arbitrage without slippage protection, commit-reveal schemes that reveal secrets prematurely, ERC-20 approve() followed by transferFrom() without incremental allowance patterns, and any function that relies on being the first caller to succeed.
Sigvex detects potential front-running by analyzing function signatures and data-flow patterns that historically correlate with ordering sensitivity: first-mover rewards, dutch auctions without time locks, token purchases with no price limits, and exposed commitment values in function parameters.
Why This Is an Issue
Front-running is endemic in Ethereum’s mempool. MEV (Maximal Extractable Value) bots continuously monitor pending transactions and submit competing transactions to capture arbitrage. While not all front-running causes direct harm, it can lead to: failed arbitrage for legitimate users, stolen whitelist spots in NFT mints, pre-empted liquidations, sandwich attacks on DEX trades, and stolen protocol bounties.
The approve-race detector covers the specific ERC-20 approve/transferFrom race condition. This detector covers broader ordering-sensitive patterns.
How to Resolve
// Before: Vulnerable DEX swap — no slippage or deadline
function swapTokens(uint256 amountIn, address tokenIn, address tokenOut) external {
// No minAmountOut or deadline — sandwich attack possible
uint256 amountOut = calculateOutput(amountIn, tokenIn, tokenOut);
_executeSwap(tokenIn, tokenOut, amountIn, amountOut);
}
// After: Slippage protection and deadline
function swapTokens(
uint256 amountIn,
uint256 minAmountOut, // Minimum acceptable output
address tokenIn,
address tokenOut,
uint256 deadline // Transaction must be included by this time
) external {
require(block.timestamp <= deadline, "Swap expired");
uint256 amountOut = calculateOutput(amountIn, tokenIn, tokenOut);
require(amountOut >= minAmountOut, "Insufficient output amount");
_executeSwap(tokenIn, tokenOut, amountIn, amountOut);
}
For commit-reveal to prevent front-running of revealed secrets:
contract CommitReveal {
mapping(address => bytes32) public commitments;
uint256 public revealStart;
// Phase 1: commit (hash is kept secret until reveal phase)
function commit(bytes32 hashedValue) external {
commitments[msg.sender] = hashedValue;
}
// Phase 2: reveal only after commit phase ends
function reveal(uint256 secret) external {
require(block.timestamp >= revealStart, "Not yet reveal phase");
bytes32 expected = keccak256(abi.encodePacked(secret, msg.sender));
require(commitments[msg.sender] == expected, "Invalid reveal");
// Process revealed value
}
}
Examples
Vulnerable Code
// ERC-20 approve then transferFrom — front-running attack possible
contract VulnerableToken is IERC20 {
function approve(address spender, uint256 amount) external returns (bool) {
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
// Attack: owner sets allowance from 100 to 50
// Spender front-runs and spends 100 before the tx confirms
// Then spender also gets to spend 50 — total 150 extracted
}
Fixed Code
contract SafeToken {
// Use increaseAllowance / decreaseAllowance instead of absolute approve
function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {
uint256 currentAllowance = _allowances[msg.sender][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(msg.sender, spender, currentAllowance - subtractedValue);
}
return true;
}
}
Sample Sigvex Output
{
"detector_id": "front-running",
"severity": "medium",
"confidence": 0.65,
"description": "Function swapTokens() performs a DEX swap without a minimum output amount or deadline parameter. Searchers can sandwich this transaction to extract value.",
"location": { "function": "swapTokens(uint256,address,address)", "offset": 92 }
}
Detection Methodology
- Pattern matching: Identifies function signatures commonly associated with front-running vulnerability (swap, approve, bid, mint, claim).
- Slippage parameter analysis: Checks whether swap-like functions include a
minOutorminAmountOutparameter that is validated. - Deadline parameter analysis: Verifies that time-sensitive operations include a
deadlinecompared againstblock.timestamp. - Commitment exposure: Looks for patterns where a function accepts a plaintext value that will be used in a first-mover context.
Limitations
False positives:
- Internal/private functions that are only reachable through a protected external function may be flagged.
- Protocols that implement their own sandwich protection through relays (e.g., Flashbots Protect) are not recognized.
False negatives:
- Multi-step MEV strategies involving chained transactions are not fully modeled.
- Governance front-running (placing votes before a critical proposal) is a separate concern.
Related Detectors
- Slippage Validation — specifically detects missing slippage checks in DEX operations
- Timestamp Dependence — detects reliance on block.timestamp for time-sensitive logic