Algo Ssstablecoinsss

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

Oracle Returns Negative or Zero Price Leading to Catastrophic Collateral Miscalculation

Root + Impact

Root Cause: The _stale_check_latest_round_data function in oracle_lib.vy returns price data without validating that the price is positive. The dsc_engine.vy then converts this int256 price directly to uint256 without any checks.

Impact: If Chainlink returns zero, division by zero occurs in _get_token_amount_from_usd() causing all liquidations to revert. If Chainlink returns a negative price, the conversion to uint256 produces an astronomically large number, allowing attackers to mint unlimited DSC with minimal collateral, leading to complete protocol insolvency.

Description

Normal Behavior: The oracle library fetches price data from Chainlink and returns it to the DSC Engine for collateral valuation. The price should always be a positive value representing the USD price of the collateral token.

Issue: The _stale_check_latest_round_data function in oracle_lib.vy returns the price as int256 without validating that it is positive. The dsc_engine.vy then converts this directly to uint256 without validation. Chainlink oracles can return zero or negative prices during failure modes, leading to division by zero or integer underflow.
# oracle_lib.vy
@internal
@view
def _stale_check_latest_round_data(
price_price_address: address,
) -> (uint80, int256, uint256, uint256, uint80):
# ...
(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"
seconds_since: uint256 = block.timestamp - updated_at
assert seconds_since <= TIMEOUT, "DSCEngine_StalePrice"
# @> No validation that price > 0
return (round_id, price, started_at, updated_at, answered_in_round)

Risk

Likelihood:HIGH

  • Reason 1: Chainlink oracles have historically returned zero prices during network congestion or oracle failures (e.g., YFI/ETH on November 19, 2020)

  • Reason 2: During extreme market volatility or flash crashes, price feeds may temporarily report invalid data

Impact:HIGH

  • Impact 1:If price = 0: Division by zero in _get_token_amount_from_usd() causes all liquidations to revert, freezing the protocol

  • Impact 2:If price < 0: Conversion to uint256 results in an astronomically large number, allowing attackers to mint unlimited DSC with minimal collateral

Proof of Concept

When Chainlink returns price = 0, the _get_token_amount_from_usd() function attempts to divide by zero, causing all liquidation transactions to revert. When Chainlink returns price = -1, converting to uint256 yields max_value(uint256), making 1 wei of collateral appear infinitely valuable, allowing an attacker to mint billions of DSC with dust collateral.

# Scenario: Chainlink returns price = 0
# In _get_token_amount_from_usd():
def _get_token_amount_from_usd(token: address, usd_amount_in_wei: uint256) -> uint256:
# price = 0 from oracle
price: int256 = 0
# This calculation becomes:
# (usd_amount_in_wei * PRECISION) // (convert(0, uint256) * ADDITIONAL_FEED_PRECISION)
# = (usd_amount_in_wei * PRECISION) // 0
# = DIVISION BY ZERO -> REVERT
# All liquidations fail, protocol freezes with bad debt
# Scenario: Chainlink returns price = -1
# In _get_usd_value():
def _get_usd_value(token: address, amount: uint256) -> uint256:
# price = -1 from oracle
price: int256 = -1
# convert(-1, uint256) = 115792089237316195423570985008687907853269984665640564039457584007913129639935
# (max uint256)
# Collateral appears infinitely valuable
# User deposits 1 wei of ETH
# System calculates collateral value as essentially infinite
# User mints unlimited DSC

Recommended Mitigation

Add a validation check to ensure the oracle price is positive before returning the data. This single assertion prevents both zero and negative price scenarios from propagating through the system.

# oracle_lib.vy
@internal
@view
def _stale_check_latest_round_data(
price_price_address: address,
) -> (uint80, int256, uint256, uint256, uint80):
# ... existing code ...
seconds_since: uint256 = block.timestamp - updated_at
assert seconds_since <= TIMEOUT, "DSCEngine_StalePrice"
+ # Validate price is positive
+ assert price > 0, "OracleLib__InvalidPrice"
return (round_id, price, started_at, updated_at, answered_in_round)
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!