Algo Ssstablecoinsss

AI First Flight #2
Beginner FriendlyDeFi
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Missing L2 Sequencer Uptime Check Enables Stale Price Exploitation

Root + Impact

Root Cause: The protocol documentation states the code should work for "any basket of assets" after forking, implying L2 deployment. However, oracle_lib.vy contains no sequencer uptime validation for Layer 2 networks.

Impact: On L2s like Arbitrum or Optimism, when the sequencer comes back online after downtime, there's a window where transactions can execute but oracle prices haven't updated. Attackers can exploit stale prices during this recovery period to mint excess DSC or avoid liquidation.

Description

Normal Behavior: On Layer 2 networks (Arbitrum, Optimism, Base), the sequencer uptime feed should be checked before using price data. When the sequencer comes back online after downtime, there's a grace period before prices are reliable.

Issue: The protocol documentation states the code should work for "any basket of assets" after forking, implying L2 deployment. However, oracle_lib.vy has no sequencer uptime validation.
# oracle_lib.vy
# @> No sequencer uptime check anywhere in the code
@internal
@view
def _stale_check_latest_round_data(price_price_address: address) -> (...):
# Only checks price feed staleness
# @> Does not check if L2 sequencer is up
# @> Does not enforce grace period after sequencer restart

Risk

Likelihood:MEDIUM

  • Reason 1 : L2 sequencer downtime occurs periodically (Arbitrum has had multiple outages)

  • Reason 2 : Arbitrage bots actively monitor for these opportunities

Impact:

  • Impact 1 : Attackers exploit stale prices during sequencer recovery window

  • Impact 2 : Massive value extraction through minting at stale favorable prices

Proof of Concept

The Arbitrum sequencer goes down while ETH is at $2000. During the 2-hour downtime, ETH drops to $1500 on L1. When the sequencer restarts, transactions can be submitted immediately, but Chainlink oracles haven't pushed updated prices yet. An attacker deposits 100 ETH valued at the stale $2000 price, mints maximum DSC, and profits when prices update to reality.

def test_l2_sequencer_exploitation():
# Timeline on Arbitrum:
# T+0: Sequencer goes down, ETH = $2000
# T+1h: ETH actual price = $1500 (25% drop during downtime)
# T+2h: Sequencer comes back online
# T+2h: Oracle still shows $2000 (hasn't updated yet)
# Attack (within seconds of sequencer restart):
# 1. Attacker deposits 100 ETH
# 2. System values at $2000 * 100 = $200,000 (stale)
# 3. Attacker mints $90,000 DSC (200% collateralization at stale price)
# 4. Oracle updates to $1500
# 5. Collateral now worth $150,000
# 6. True collateralization: $150,000 / $90,000 = 166% (under threshold)
# Profit: Attacker has $90,000 DSC backed by only $150,000 collateral
# At scale: Millions extracted from protocol

Recommended Mitigation

Add a sequencer uptime feed check for L2 deployments. The check should verify the sequencer is online and enforce a grace period after restart before accepting any oracle prices. Set the sequencer feed address to zero for L1 deployments to skip the check.

# oracle_lib.vy
+ GRACE_PERIOD_TIME: constant(uint256) = 3600 # 1 hour grace period
+ sequencer_uptime_feed: public(address) # Set to zero address on L1
+ @internal
+ @view
+ def _check_sequencer_uptime():
+ """
+ @notice Checks if L2 sequencer is up and grace period has passed
+ @dev Only applicable on L2 deployments
+ """
+ if self.sequencer_uptime_feed == empty(address):
+ return # L1 deployment, skip check
+
+ sequencer: AggregatorV3Interface = AggregatorV3Interface(self.sequencer_uptime_feed)
+ (round_id, answer, started_at, updated_at, answered_in_round) = staticcall sequencer.latestRoundData()
+
+ # answer == 0: Sequencer is up
+ # answer == 1: Sequencer is down
+ assert answer == 0, "OracleLib__SequencerDown"
+
+ time_since_up: uint256 = block.timestamp - started_at
+ assert time_since_up > GRACE_PERIOD_TIME, "OracleLib__GracePeriodNotOver"
@internal
@view
def _stale_check_latest_round_data(price_price_address: address) -> (...):
+ self._check_sequencer_uptime()
# ... rest of function
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 3 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!