Algo Ssstablecoinsss

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

liquidate reverts on full or over-cover due to the strict HealthFactorNotImproved assert and unbounded debt_to_cover underflow

liquidate reverts on full liquidation because HealthFactorNotImproved uses strict greater-than against max_value

Description

After burning the user's debt, liquidate requires the ending health factor to be strictly greater than the starting one.

# dsc_engine.vy:130-134
self._burn_dsc(debt_to_cover, user, msg.sender)
ending_user_health_factor: uint256 = self._health_factor(user)
assert (
ending_user_health_factor > starting_user_health_factor # @> strict >, fails when both == max
), "DSCEngine__HealthFactorNotImproved"

When a liquidator covers the user's entire debt, user_to_dsc_minted becomes 0, so _calculate_health_factor returns max_value(uint256) (dsc_engine.vy:324-325). If the starting health factor was already max_value for any rounding reason it cannot increase, and more importantly the > comparison plus the engine's lack of a debt_to_cover <= user_debt bound means an over-cover underflows user_to_dsc_minted (dsc_engine.vy:263) and reverts.

Risk

Likelihood:
Medium. Full liquidation is the normal way to clear a small underwater position; a liquidator passing debt_to_cover equal to or above the user's debt is the expected path.

Impact:
Medium. Liquidations of fully-underwater or fully-covered positions revert, so positions that should be cleared are stuck and the protocol accrues bad debt. It also makes liquidation bots fail unpredictably, reducing the system's ability to stay solvent.

Proof of Concept

Cover slightly more than the user's outstanding debt.

# user owes 500 DSC
engine.liquidate(weth, user, 600 * 10**18)
# _burn_dsc: 500 - 600 underflows -> revert, position never liquidated

Recommended Mitigation

Bound debt_to_cover to the user's debt and compare health factors with >= where appropriate.

@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
+ user_debt: uint256 = self.user_to_dsc_minted[user]
+ debt: uint256 = min(debt_to_cover, user_debt)
starting_user_health_factor: uint256 = self._health_factor(user)
Updates

Lead Judging Commences

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