Skip to main content
Sigvex

Permit Frontrunning

Detects ERC-2612 permit implementations where nonce mismanagement enables frontrunning attacks on gasless approvals.

Permit Frontrunning

Overview

The permit frontrunning detector identifies contracts that use ERC-2612 permit() followed by transferFrom() in a way that is vulnerable to frontrunning. An attacker who observes a pending permit + transferFrom transaction can frontrun it by submitting just the permit call, causing the original transaction to revert (nonce already used) while the attacker’s subsequent transferFrom succeeds using the now-active approval.

Sigvex identifies sequences where permit() is called and its success is required before transferFrom(), without handling the case where the permit may have already been executed.

Why This Is an Issue

ERC-2612 permits enable gasless token approvals via off-chain signatures. The frontrunning attack works because permit() is idempotent at the signature level but not at the nonce level. If an attacker submits the permit before the intended caller, the nonce increments, and the caller’s transaction reverts. Contracts that wrap permit + transferFrom in a single function are particularly vulnerable.

How to Resolve

// Before: Vulnerable — reverts if permit was already used
function depositWithPermit(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
    token.permit(msg.sender, address(this), amount, deadline, v, r, s);  // Reverts if frontrun
    token.transferFrom(msg.sender, address(this), amount);
}

// After: Fixed — catch permit failure (approval may already exist)
function depositWithPermit(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
    try token.permit(msg.sender, address(this), amount, deadline, v, r, s) {} catch {}
    token.transferFrom(msg.sender, address(this), amount);  // Works if already approved
}

Detection Methodology

  1. Permit call identification: Locates calls matching the permit(address,address,uint256,uint256,uint8,bytes32,bytes32) selector.
  2. Return check analysis: Checks whether the permit call’s return value gates subsequent operations via require or revert.
  3. TransferFrom sequence: Identifies transferFrom calls following permit on the same token.
  4. Error handling check: Determines whether the permit call is wrapped in try/catch or otherwise handles revert gracefully.

Limitations

False positives: Contracts where the permit-then-transfer is only callable by a trusted relayer may be flagged. False negatives: Non-standard permit implementations with different selectors may be missed.

References