Algo Ssstablecoinsss

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

[M-01] Missing oracle price validation for zero or negative values freezes the entire protocol

Description

oracle_lib._stale_check_latest_round_data validates updated_at, answered_in_round, and staleness but never checks that price > 0. A zero or negative price from Chainlink freezes the protocol completely.

Vulnerability Details

# oracle_lib.vy:28-50
(
round_id, price, started_at, updated_at, answered_in_round
) = staticcall price_price.latestRoundData()
assert updated_at != 0, "DSCEngine_StalePrice"
assert answered_in_round >= round_id, "DSCEngine_StalePrice"
# @> MISSING: assert price > 0

The returned price flows into two functions in dsc_engine.vy:

  1. _get_usd_value (line 302-316): convert(price, uint256) * ADDITIONAL_FEED_PRECISION * amount // PRECISION. If price = 0, all collateral is valued at 0. If price < 0, Vyper 0.4.0's convert(price, uint256) reverts on negative int256.

  2. _get_token_amount_from_usd (line 346-364): divides by convert(price, uint256) * ADDITIONAL_FEED_PRECISION. If price = 0, this is a division by zero and reverts.

With price = 0: _get_usd_value returns 0 (all collateral worthless), making every position undercollateralized. But _get_token_amount_from_usd divides by zero and reverts, so liquidation is impossible. Protocol frozen.

With price < 0: convert(price, uint256) reverts for negative int256 in Vyper 0.4.0. Every function touching prices reverts. Complete protocol freeze.

Risk

Likelihood:

  • Chainlink has returned zero/negative prices during edge cases (e.g., Luna crash in May 2022, where LUNA/USD briefly reported 0).

  • Chainlink's latestRoundData returns int256 precisely because prices can go negative in some feeds.

Impact:

  • Complete protocol freeze. No deposits, no withdrawals, no liquidations, no minting, no burning.

  • Users cannot rescue their own collateral until the oracle returns a positive price.

Proof of Concept

No PoC needed. The division-by-zero path in _get_token_amount_from_usd is self-evident from the source: dividing by convert(0, uint256) * ADDITIONAL_FEED_PRECISION = dividing by 0.

Recommendations

(
round_id, price, started_at, updated_at, answered_in_round
) = staticcall price_price.latestRoundData()
+ assert price > 0, "DSCEngine__InvalidPrice"
assert updated_at != 0, "DSCEngine_StalePrice"
assert answered_in_round >= round_id, "DSCEngine_StalePrice"
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours 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!