Algo Ssstablecoinsss

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

Both Collateral Oracles Called Regardless of User Balance — Single Oracle Failure Freezes All Protocol Operations

Both Collateral Oracles Called Regardless of User Balance — Single Oracle Failure Freezes All Protocol Operations

Scope

  • src/dsc_engine.vy

Description

The _get_account_collateral_value() function is expected to return the total USD value of a user's collateral by summing across all supported collateral tokens. It should only need to query the price oracle for tokens the user actually holds.

The function iterates over all entries in COLLATERAL_TOKENS (both WETH and WBTC) and calls _get_usd_value(token, amount) for each — regardless of whether amount is zero. This means every health factor check triggers oracle calls for both price feeds. If either oracle becomes stale (reverts at assert updated_at != 0 or assert seconds_since <= TIMEOUT), ALL operations gated on the health factor — minting, redeeming, burning DSC, and liquidating — revert for ALL users. A user with 100% WETH and 0% WBTC is frozen by a WBTC oracle failure they have zero exposure to.

@> for token: address in COLLATERAL_TOKENS: # WETH and WBTC always iterated
@> amount: uint256 = self.user_to_token_address_to_amount_deposited[user][token]
@> total_collateral_value_in_usd += self._get_usd_value(token, amount)
# _get_usd_value fires the oracle staticcall even if amount == 0
# oracle_lib._stale_check_latest_round_data() REVERTS if oracle is stale
# → full protocol freeze if WBTC feed stale, even for WETH-only users

Risk

Likelihood: Medium

  • Chainlink ZKsync Era feeds operate at 1-hour heartbeat; any infrastructure interruption to either WETH or WBTC feed causes 72h+ stale window (until D01 is fixed) or 1h+ window (even after D01 fix).

  • Only ONE of the two price feeds needs to fail to freeze the entire protocol.

  • Two independent oracle failure surfaces double the likelihood of a freeze event.

Impact: High

  • Zero DSC can be minted or burned while either oracle is stale.

  • No collateral can be deposited or redeemed by any user.

  • No liquidations can proceed — bad debt accumulates.

  • DSC holders cannot redeem underlying collateral — peg breaks.

Severity: High

Proof of Concept

A user holds only WETH collateral. The WBTC/USD oracle on ZKsync Era goes stale (1 hour without an update). The user tries to mint DSC against their WETH — the call reverts because the WBTC oracle call inside _get_account_collateral_value() fires and reverts.

import boa
from eth_utils import to_wei
# Setup: WETH oracle = live. WBTC oracle = stale (no update for 3601s)
eth_usd.updateAnswer(2000 * 10**8) # WETH oracle: fresh
# Do NOT update btc_usd — let it go stale
boa.env.time_travel(seconds=3601) # After D01 fix: 1h grace period exceeded for WBTC
with boa.env.prank(victim):
weth.approve(dsce, to_wei(1, "ether"))
dsce.deposit_collateral(weth, to_wei(1, "ether"))
# User has 0 WBTC. But WBTC oracle is stale.
# _get_account_collateral_value loops both tokens:
# _get_usd_value(weth, 1e18) → ok
# _get_usd_value(wbtc, 0) → fires oracle! → REVERT "DSCEngine_StalePrice"
with boa.reverts("DSCEngine_StalePrice"):
dsce.mint_dsc(to_wei(500, "ether")) # Reverts even though user has sufficient WETH collateral

Recommended Mitigation

Skip the oracle call entirely when a user's balance of that collateral token is zero. The result of _get_usd_value(token, 0) is always zero regardless of price — there is no reason to call the oracle.

for token: address in COLLATERAL_TOKENS:
amount: uint256 = self.user_to_token_address_to_amount_deposited[user][token]
+ if amount == 0:
+ continue
total_collateral_value_in_usd += self._get_usd_value(token, amount)
Updates

Lead Judging Commences

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