DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Unhandled Sequencer Downtime (L2s)

Summary

During sequencer downtime, price feeds (e.g., Chainlink oracles) may not update. After the sequencer resumes, stale price data from before the downtime could be used, leading to incorrect price validations (e.g., undercollateralized positions, inaccurate swaps).

https://github.com/CodeHawks-Contests/2025-02-gamma/blob/e5b98627a4c965e203dbb616a5f43ec194e7631a/contracts/KeeperProxy.sol#L155

https://github.com/CodeHawks-Contests/2025-02-gamma/blob/e5b98627a4c965e203dbb616a5f43ec194e7631a/contracts/KeeperProxy.sol#L188

Vulnerability Details

Flaws:

// KeeperProxy.sol
function _validatePrice(...) internal view {
// Sequencer check
(..., int256 answer, uint256 startedAt, ...) = sequencerUptimeFeed.latestRoundData();
require(answer == 0, "sequencer is down");
require(block.timestamp - startedAt > GRACE_PERIOD_TIME, "Grace period not over");
// Price freshness check (does not account for sequencer downtime)
_check(marketData.indexToken, prices.indexTokenPrice.min);
_check(marketData.longToken, prices.indexTokenPrice.min);
_check(marketData.shortToken, prices.shortTokenPrice.min);
}
function _check(...) internal view {
(, int256 chainlinkPrice, , uint256 updatedAt, ) = AggregatorV2V3Interface(...).latestRoundData();
require(updatedAt > block.timestamp - maxTimeWindow[token], "stale price feed");
// ...
}

Missing Post-Downtime Feed Check: The _check function validates feed freshness against maxTimeWindow but does not ensure updates occurred after sequencer restart.

Stale Data Risk: Feeds updated before/during downtime might be used post-recovery, even if maxTimeWindow is large.

Example Scenario:

  • Sequencer Downtime: The L2 sequencer is offline for 2 hours.

  • Price Feed Staleness: Chainlink oracles cannot update during downtime.

  • Sequencer Resumes: After 1 hour (grace period), the protocol resumes operations.

  • Stale Data Used: Price feeds still reflect pre-downtime values, causing faulty liquidations or trades.

Impact

Trades execute with stale prices during downtime.

Tools Used

Manual Review

Recommendations

Validate Price Feed Updates Post-Sequencer Restart: Add checks to ensure price feeds were updated after the sequencer resumed operations.

// KeeperProxy.sol
function _validatePrice(...) internal view {
// Sequencer status check
(..., int256 answer, uint256 sequencerRestartTime, ...) = sequencerUptimeFeed.latestRoundData();
require(answer == 0, "sequencer is down");
require(block.timestamp - sequencerRestartTime > GRACE_PERIOD_TIME, "Grace period not over");
// Ensure price feeds updated AFTER sequencer restarted
MarketProps memory marketData = IVaultReader(vaultReader).getMarket(market);
_checkFeedUpdatedAfter(marketData.indexToken, sequencerRestartTime);
_checkFeedUpdatedAfter(marketData.longToken, sequencerRestartTime);
_checkFeedUpdatedAfter(marketData.shortToken, sequencerRestartTime);
// Existing price validation
_check(marketData.indexToken, prices.indexTokenPrice.min);
_check(marketData.longToken, prices.indexTokenPrice.min);
_check(marketData.shortToken, prices.shortTokenPrice.min);
}
function _checkFeedUpdatedAfter(address token, uint256 sequencerRestartTime) internal view {
(, , , uint256 feedUpdatedAt, ) = AggregatorV2V3Interface(dataFeed[token]).latestRoundData();
require(feedUpdatedAt > sequencerRestartTime, "Price feed not updated post-sequencer restart");
}

After Fixes:

  1. Post-Restart Feed Check:

    _checkFeedUpdatedAfter ensures the price feed’s updatedAt timestamp is after the sequencer’s sequencerRestartTime.

    Rejects stale data from before/during downtime.

  2. Grace Period Enforcement:

    Maintains the existing grace period to avoid using data immediately after sequencer restart.

  3. Comprehensive Coverage:

    Applies to all relevant tokens (index, long, short) in the market.

Verification

Test Case 1 (Sequencer Restart + Feed Updated):

Sequencer restarts at t=1000.

Price feed updates at t=1100.

Result: Validation passes ✅.

Test Case 2 (Feed Not Updated Post-Restart):

Sequencer restarts at t=1000.

Feed last updated at t=900 (pre-downtime).

Result: Reverts with "Price feed not updated post-sequencer restart" ❌.

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.