Cross-Chain Oracle Inconsistency Exploit Generator
Sigvex exploit generator that validates cross-chain oracle inconsistency vulnerabilities including L2 sequencer downtime, oracle price lag between chains, and missing Chainlink sequencer uptime feed checks.
Cross-Chain Oracle Inconsistency Exploit Generator
Overview
The cross-chain oracle inconsistency exploit generator validates findings from the cross_chain_oracle, oracle_lag, sequencer_uptime, and related detectors by executing the target contract under four oracle scenarios: synchronized mainnet and L2 oracles, L2 lagging by 5 minutes (5% price difference), L2 sequencer down with 1-hour stale data, and L2 oracle ahead of mainnet. Missing sequencer uptime validation is the most critical finding (confidence 0.95).
L2 chains (Arbitrum, Optimism, Base) use a sequencer that batches and orders transactions. When the sequencer goes down, Chainlink oracle prices stop updating — but the last price remains on-chain. Protocols that read the oracle without first checking the Chainlink sequencer uptime feed will use prices that are up to 1 hour old (or more). Venus Protocol suffered $100M+ in losses from oracle staleness. Multiple L2 protocols have experienced losses during sequencer downtime when contracts accepted stale prices for liquidation decisions.
Note: Exploit generation in Sigvex is for vulnerability validation purposes only.
Attack Scenario
Sequencer downtime exploitation:
- The L2 sequencer goes offline for 90 minutes (e.g., Arbitrum or Optimism maintenance).
- The Chainlink ETH/USD feed stops updating — stuck at its last reported price of $2000.
- The real market price moves to $1800 during the downtime.
- An attacker monitors the mempool, waiting for the sequencer to come back online.
- In the first block after restart, the attacker submits a liquidation transaction.
- The oracle still reports $2000 (hasn’t updated yet), but the attacker knows the real price is $1800.
- The attacker triggers invalid liquidations on positions that would be healthy at $1800.
Cross-chain oracle lag arbitrage:
- A protocol uses two Chainlink feeds: one on Ethereum mainnet, one on an L2.
- The L2 oracle lags by 5 minutes and reports $1900 while mainnet reports $2000.
- The attacker borrows on the L2 at the underpriced $1900 collateral valuation.
- The attacker bridges to mainnet and sells at $2000.
- The 5% price lag creates consistent arbitrage until the lag closes.
Exploit Mechanics
The generator sets up three simulated oracle addresses:
- Mainnet oracle — reporting the current market price
- L2 oracle — reporting a lagging or stale price
- Sequencer uptime feed — reporting the sequencer online/offline status
Base timestamp: 2024-01-01. Current market price: $2000 (in 8-decimal Chainlink units).
| Scenario | Chain ID | L2 price | L2 timestamp | Sequencer | Confirmed if |
|---|---|---|---|---|---|
| 1 — Synchronized | 1 (mainnet) | $2000 | current | online | Baseline |
| 2 — L2 lagging | 10 (Optimism) | $1900 (-5%) | -300s | online | Succeeds (0.85) |
| 3 — Sequencer down | 42161 (Arbitrum) | $1800 (-10%) | -3600s | offline (0) | Succeeds (0.95) |
| 4 — Reverse lag | 137 (Polygon) | $2000 (fresh) | current | online | Succeeds (0.70) |
Verdict:
- Synchronized succeeds and Sequencer-down succeeds → no L2 sequencer uptime validation (confidence 0.95): critical.
- Synchronized succeeds and L2 lagging succeeds → cross-chain oracle lag (confidence 0.85): high.
- Synchronized succeeds and Reverse lag succeeds → oracle freshness asymmetry (confidence 0.70): medium.
- Sequencer-down reverts → protected: sequencer uptime check implemented.
// VULNERABLE: No sequencer uptime check on L2
contract VulnerableL2Protocol {
AggregatorV3Interface public priceFeed;
function getPrice() public view returns (uint256) {
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
// CRITICAL: No sequencer uptime check!
// During downtime, updatedAt is 1+ hour ago
require(price > 0, "Invalid price");
return uint256(price);
}
function liquidate(address user) external {
uint256 price = getPrice(); // Could be 1+ hour old if sequencer down!
// Liquidation based on stale price...
}
}
// SECURE: L2 sequencer uptime check + grace period
contract SafeL2Protocol {
AggregatorV3Interface public priceFeed;
ISequencerUptimeFeed public sequencerFeed; // Chainlink L2 sequencer feed
uint256 constant GRACE_PERIOD = 3600; // 1 hour after sequencer restarts
function getPrice() public view returns (uint256) {
// Step 1: Check sequencer is online
(, int256 sequencerStatus, uint256 startedAt,,) = sequencerFeed.latestRoundData();
require(sequencerStatus == 1, "Sequencer offline");
// Step 2: Grace period after restart (prices may still be stale)
require(block.timestamp - startedAt > GRACE_PERIOD, "Sequencer recently restarted");
// Step 3: Standard price validation
(, int256 price,, uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
uint80 roundId;
(roundId,,,,) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
require(updatedAt >= block.timestamp - 3600, "Price too stale");
require(answeredInRound >= roundId, "Incomplete round");
return uint256(price);
}
}
// SECURE: Cross-chain oracle validation
contract SafeCrossChain {
AggregatorV3Interface public mainnetFeed;
AggregatorV3Interface public l2Feed;
uint256 constant MAX_TIME_DIFF = 120; // 2 min max lag
uint256 constant MAX_PRICE_DIFF = 200; // 2% max deviation (basis points)
function getCrossChainPrice() public view returns (uint256) {
(, int256 mainnetPrice,, uint256 mainnetTime,) = mainnetFeed.latestRoundData();
(, int256 l2Price,, uint256 l2Time,) = l2Feed.latestRoundData();
// Validate timestamp difference
uint256 timeDiff = mainnetTime > l2Time ? mainnetTime - l2Time : l2Time - mainnetTime;
require(timeDiff < MAX_TIME_DIFF, "Oracle lag too high");
// Validate price deviation
uint256 priceDiff = uint256(mainnetPrice > l2Price ? mainnetPrice - l2Price : l2Price - mainnetPrice);
uint256 maxDiff = uint256(mainnetPrice) * MAX_PRICE_DIFF / 10000;
require(priceDiff < maxDiff, "Price deviation too high");
return uint256((mainnetPrice + l2Price) / 2);
}
}
Remediation
- Detector: Cross-Chain Oracle Detector
- Remediation Guide: Cross-Chain Oracle Remediation
Implement a layered defense for L2 oracle use:
// Required for all L2 deployments using Chainlink
// Arbitrum: 0xFdB631F5EE196F0ed6FAa767959853A9F217697D
// Optimism: 0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389
ISequencerUptimeFeed sequencerFeed = ISequencerUptimeFeed(SEQUENCER_FEED_ADDRESS);
(, int256 answer, uint256 startedAt,,) = sequencerFeed.latestRoundData();
require(answer == 1, "Sequencer down");
require(block.timestamp - startedAt > GRACE_PERIOD, "Sequencer recently restarted");
Cross-chain oracle validation checklist:
- Sequencer uptime: Always check the Chainlink L2 sequencer uptime feed before reading any price on Arbitrum, Optimism, or Base.
- Grace period: After a sequencer restart, wait 1+ hour before trusting prices — stale data may still be in the system.
- Timestamp validation: Reject prices where
|mainnetTime - l2Time| > 2 minutes. - Price deviation: Reject cross-chain price averages if the two sources differ by more than 2%.
- Circuit breaker: Pause the protocol if any oracle validation fails.
- Minimum staleness: Use the strictest freshness requirement across all oracle sources.