Algo Ssstablecoinsss

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

[M-04] Redundant health factor check in `burn_dsc()` blocks debt repayment during oracle outages

Description

burn_dsc calls _revert_if_health_factor_is_broken after burning DSC. Burning debt can only improve health factor, so this check can never fail. But it calls the oracle via _health_factor -> _get_usd_value -> _stale_check_latest_round_data. If the oracle is stale or down, users cannot reduce their own debt.

Vulnerability Details

# dsc_engine.vy:148-151
@external
def burn_dsc(amount_dsc_to_burn: uint256):
self._burn_dsc(amount_dsc_to_burn, msg.sender, msg.sender)
self._revert_if_health_factor_is_broken(msg.sender) # @> redundant, but calls oracle

_revert_if_health_factor_is_broken checks user_health_factor >= MIN_HEALTH_FACTOR. After burning DSC, the user's debt is lower, so their HF can only be equal or higher. The check passes in 100% of cases.

But _health_factor -> _get_account_information -> _get_account_collateral_value -> _get_usd_value -> oracle_lib._stale_check_latest_round_data, which asserts seconds_since <= TIMEOUT. If the oracle is stale (or returns price <= 0 per M-01), this reverts.

Debt repayment is the one operation that should always be safe regardless of oracle state. Blocking it during a crisis forces users to watch their positions deteriorate while holding the DSC they need to save themselves.

Risk

Likelihood:

  • Oracle outages happen. The oracle_lib docstring explicitly acknowledges this: "So if the Chainlink network explodes and you have a lot of money locked in the protocol... too bad."

  • ZKsync Era L2 sequencer downtime compounds this risk.

Impact:

  • Users cannot reduce their debt during oracle outages, the exact time they'd most want to.

  • Positions that could have been saved by burning DSC instead get pushed into liquidation territory (or the H-01 dead zone).

Proof of Concept

1. User has 10 ETH collateral, 8,000 DSC debt, HF = 1.25e18 (healthy)
2. Chainlink oracle goes stale (updated_at > 72h ago) due to network issues
3. User sees risk, wants to burn 4,000 DSC to improve their position
4. burn_dsc(4000e18) calls _burn_dsc (succeeds), then _revert_if_health_factor_is_broken
5. _health_factor -> _get_usd_value -> oracle_lib._stale_check_latest_round_data
6. assert seconds_since <= TIMEOUT fails -> entire transaction reverts
7. User's 4,000 DSC is NOT burned. Position unchanged. User stuck.

Recommendations

Remove the redundant health factor check. Burning DSC is provably safe.

@external
def burn_dsc(amount_dsc_to_burn: uint256):
self._burn_dsc(amount_dsc_to_burn, msg.sender, msg.sender)
- self._revert_if_health_factor_is_broken(msg.sender)
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!