Skip to main content
Sigvex

L2 Rollup Vulnerabilities

Detects Layer 2-specific vulnerabilities including sequencer dependency, L1-L2 message replay, and gas price oracle manipulation.

L2 Rollup Vulnerabilities

Overview

The L2 rollup detector identifies vulnerabilities specific to contracts deployed on Layer 2 rollups (Optimism, Arbitrum, Base, zkSync, etc.). These include sequencer dependency, cross-domain message replay, L1/L2 gas price oracle misuse, and incorrect assumptions about block properties that differ between L1 and L2 environments.

Why This Is an Issue

L2 rollups have fundamentally different execution environments from L1 Ethereum:

  • Sequencer centralization: A single sequencer orders transactions, enabling censorship and MEV extraction.
  • Cross-domain messaging: Messages between L1 and L2 can be replayed or delayed, breaking assumptions about atomic execution.
  • Block properties: block.timestamp, block.number, and block.basefee behave differently on each L2.
  • Gas pricing: L1 data costs (calldata/blob) are separate from L2 execution costs.

The Optimism Wintermute incident ($20M, 2022) resulted from a Gnosis Safe deployment that assumed L1-equivalent CREATE2 addresses. Multiple bridge exploits have targeted cross-domain message validation failures.

How to Resolve

// Before: Assumes L1 block properties
function isExpired(uint256 deadline) public view returns (bool) {
    return block.number > deadline; // L2 block numbers increment differently
}

// After: Use timestamp-based expiry on L2
function isExpired(uint256 deadline) public view returns (bool) {
    return block.timestamp > deadline; // Timestamps are more consistent across L2s
}

For cross-domain messaging:

// Before: No replay protection on L1->L2 message
function receiveMessage(bytes calldata data) external {
    require(msg.sender == L1_BRIDGE, "Not bridge");
    _execute(data); // Can be replayed if L2 chain reorgs
}

// After: Nonce-based replay protection
function receiveMessage(bytes calldata data, uint256 nonce) external {
    require(msg.sender == L1_BRIDGE, "Not bridge");
    require(!processedNonces[nonce], "Already processed");
    processedNonces[nonce] = true;
    _execute(data);
}

Examples

Vulnerable Code

contract L2Vault {
    // Uses block.number as time proxy -- unreliable on L2
    uint256 public lastHarvestBlock;
    uint256 constant HARVEST_INTERVAL = 7200; // ~24h on L1, varies wildly on L2

    function harvest() external {
        require(block.number >= lastHarvestBlock + HARVEST_INTERVAL, "Too soon");
        lastHarvestBlock = block.number;
        _distributeRewards();
    }
}

Fixed Code

contract L2Vault {
    uint256 public lastHarvestTimestamp;
    uint256 constant HARVEST_INTERVAL = 24 hours;

    function harvest() external {
        require(block.timestamp >= lastHarvestTimestamp + HARVEST_INTERVAL, "Too soon");
        lastHarvestTimestamp = block.timestamp;
        _distributeRewards();
    }
}

Sample Sigvex Output

{
  "detector_id": "l2-rollup",
  "severity": "high",
  "confidence": 0.74,
  "description": "Function harvest() uses block.number for time-based logic. On L2 rollups, block production rates vary and block.number is not a reliable time proxy. Use block.timestamp instead.",
  "location": { "function": "harvest()", "offset": 42 }
}

Detection Methodology

  1. Block property analysis: Flags uses of block.number for time-based calculations, block.difficulty (always 0 on L2 post-merge), and L2-specific precompiles.
  2. Cross-domain message patterns: Identifies bridge message handlers without replay protection.
  3. Gas price assumptions: Detects hardcoded gas values or tx.gasprice comparisons that do not account for L2 pricing models.
  4. CREATE2 address assumptions: Flags cross-chain address predictions that assume identical deployment conditions.

Limitations

  • Cannot determine which L2 a contract will be deployed on; findings are generalized across rollup types.
  • Sequencer censorship risk is architectural, not detectable at the bytecode level.
  • L2-specific precompiles (Arbitrum’s ArbSys, Optimism’s L1Block) may not be fully recognized in all decompiled outputs.

References