Skip to main content
Sigvex

ERC-20 Approve Race

Detects ERC-20 approve functions vulnerable to the front-running race condition where a spender can spend both old and new allowances.

ERC-20 Approve Race

Overview

The ERC-20 approve race detector identifies token contracts where the approve() function sets a new allowance without requiring the current allowance to be zero first. This creates a front-running window: when a token holder changes an allowance from N to M, the spender can observe the pending transaction, quickly spend N tokens, and then spend M more tokens after the new allowance takes effect — spending N+M instead of the intended M.

Why This Is an Issue

The approve-then-transferFrom pattern is fundamental to DeFi interactions. Users regularly adjust allowances for routers, vaults, and aggregators. If the spender (or a MEV searcher acting on the spender’s behalf) can front-run the allowance change, they extract the full old allowance plus the new one. While the ERC-20 standard acknowledges this issue, many implementations do not mitigate it.

How to Resolve

// Before: Vulnerable — direct allowance overwrite
function approve(address spender, uint256 amount) external returns (bool) {
    _allowances[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, amount);
    return true;
}

// After: Fixed — require zero-first or use increaseAllowance
function approve(address spender, uint256 amount) external returns (bool) {
    require(
        amount == 0 || _allowances[msg.sender][spender] == 0,
        "Approve: reset first"
    );
    _allowances[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, amount);
    return true;
}

function increaseAllowance(address spender, uint256 added) external returns (bool) {
    _allowances[msg.sender][spender] += added;
    emit Approval(msg.sender, spender, _allowances[msg.sender][spender]);
    return true;
}

Detection Methodology

  1. Approve function identification: Locates functions matching the ERC-20 approve(address,uint256) selector (0x095ea7b3).
  2. Allowance write pattern: Checks if the function performs a direct SSTORE to the allowance mapping without reading the current value first.
  3. Zero-check absence: Flags implementations that do not require the current allowance to be zero before setting a non-zero value.
  4. SafeApprove detection: Checks for increaseAllowance/decreaseAllowance patterns that mitigate the issue.

Limitations

False positives: Some protocols intentionally allow direct allowance overwrites because their threat model does not include front-running (e.g., the spender is a trusted protocol contract). False negatives: Custom allowance mechanisms that do not use standard storage patterns may not be detected.

References