Algo Ssstablecoinsss

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

No Validation That Oracle Price Is Positive Causes DoS and Incorrect Valuations

No Validation That Oracle Price Is Positive Causes DoS and Incorrect Valuations

Description

The _stale_check_latest_round_data function in oracle_lib.vy validates staleness but does not check that the returned price is positive:

@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"
return (round_id, price, started_at, updated_at, answered_in_round)

Missing: assert price > 0, "DSCEngine_InvalidPrice"

This affects two downstream functions:

1. _get_usd_value (line 315): If price = 0, convert(0, uint256) * ADDITIONAL_FEED_PRECISION * amount = 0. All collateral is valued at $0, making every user's health factor 0.

2. _get_token_amount_from_usd (line 361): If price = 0, usd_amount * PRECISION // (0 * ADDITIONAL_FEED_PRECISION) causes division by zero, reverting the transaction. Since liquidate() calls this function, liquidation becomes impossible.

3. Negative price: If Chainlink returns a negative price (technically possible as int256), convert(negative_int256, uint256) in Vyper 0.4.0 will revert due to overflow protection, freezing the entire protocol.

Risk

Likelihood: Low -- Chainlink is designed to never return 0 or negative prices. However, oracle malfunctions, misconfiguration, or contract upgrades could theoretically trigger this.

Impact: High -- If triggered:

  • All positions appear unhealthy (collateral worth $0), but liquidation reverts (division by zero)

  • Users cannot redeem collateral (health factor check fails)

  • All funds locked permanently until the oracle recovers

Real-World Precedent: Chainlink documentation explicitly recommends require(price > 0) after every latestRoundData() call.

Proof of Concept

How the attack works:

  1. Chainlink price feed malfunctions and returns price = 0

  2. _get_usd_value returns 0 for all collateral -- every user's health factor drops to 0

  3. Anyone calling liquidate() triggers _get_token_amount_from_usd, which divides by zero and reverts

  4. Users calling redeem_collateral have their health factor checked -- with collateral worth $0, the check fails

  5. Protocol is in a deadlock: positions are underwater, liquidation is impossible, collateral is trapped

Expected outcome: Complete protocol freeze. All collateral locked until oracle returns a valid price.

def test_zero_price_freezes_protocol(self, dsce, weth, eth_usd, dsc, some_user, liquidator):
# User creates healthy position
with boa.env.prank(some_user):
weth.approve(dsce.address, COLLATERAL_AMOUNT)
dsce.deposit_collateral_and_mint_dsc(weth.address, COLLATERAL_AMOUNT, AMOUNT_TO_MINT)
# Oracle malfunction: price = 0
eth_usd.updateAnswer(0)
assert dsce.health_factor(some_user) == 0 # Collateral worth $0
# Setup liquidator (before price crash)
eth_usd.updateAnswer(2000 * 10**8)
weth.mint(liquidator, to_wei(20, "ether"))
with boa.env.prank(liquidator):
weth.approve(dsce.address, to_wei(20, "ether"))
dsce.deposit_collateral_and_mint_dsc(weth.address, to_wei(20, "ether"), AMOUNT_TO_MINT)
eth_usd.updateAnswer(0) # Crash again
# Liquidation REVERTS (division by zero)
with boa.env.prank(liquidator):
dsc.approve(dsce.address, AMOUNT_TO_MINT)
with boa.reverts():
dsce.liquidate(weth, some_user, AMOUNT_TO_MINT)
# Redemption also REVERTS
with boa.env.prank(some_user):
with boa.reverts():
dsce.redeem_collateral(weth, COLLATERAL_AMOUNT)
# All funds LOCKED
assert dsce.get_collateral_balance_of_user(some_user, weth) == COLLATERAL_AMOUNT

Recommended Mitigation

Add a price positivity check in the oracle library:

assert price > 0, "DSCEngine__InvalidPrice" # Add before returning oracle data
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 5 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!