Algo Ssstablecoinsss

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

Flawed Health Factor Monotonicity Assertion Blocks Liquidation of Underwater Accounts ($HF \le 0.55$), Causing Bad Debt Insolvency

[H-02] Flawed Health Factor Monotonicity Assertion Blocks Liquidation of Underwater Accounts (), Causing Bad Debt Insolvency

Summary

The liquidate() function in dsc_engine.vy requires that the borrower's ending health factor must strictly exceed their starting health factor. Because liquidators receive a 10% liquidation bonus, liquidations of positions with an initial Health Factor mathematically reduce the borrower's health factor. This causes the assertion ending_user_health_factor > starting_user_health_factor to revert, permanently freezing liquidations on underwater positions and accumulating bad debt.

Vulnerability Details

In src/dsc_engine.vy, liquidate() executes:

# Lines 182-188
ending_user_health_factor: uint256 = self._health_factor(user)
assert (
ending_user_health_factor > starting_user_health_factor
), "DSCEngine__HealthFactorNotImproved"

During liquidation, a liquidator pays debt and receives $1.1 \Delta D$ worth of collateral in USD.
Let:

  • : Initial Collateral USD value

  • : Initial Debt in DSC

  • : Liquidation threshold (50%)

  • : Liquidation bonus (10%)

The health factor equations are:


For :

Substituting :
$2 \cdot HF_{start} > 1.10 \iff HF_{start} > 0.55$

Mathematical Proof of Failure

When a user's health factor falls to (e.g. during a market crash or rapid price decline), any liquidation mathematically results in . As a result, assert ending_user_health_factor > starting_user_health_factor ALWAYS reverts with "DSCEngine__HealthFactorNotImproved".

Impact

  • Catastrophic Protocol Insolvency: Any borrower position whose Health Factor falls to can NEVER be liquidated.

  • Permanent Bad Debt Accumulation: Liquidators are blocked from liquidating the protocol's most severely underwater accounts, leaving the stablecoin backing severely compromised.

Proof of Concept

Automated tests are provided in:

  • tests/unit/test_poc_audit.py::test_poc_liquidation_fails_when_hf_below_55

  • tests/fuzz/test_invariant_audit.py::test_invariant_liquidation_cannot_clear_bad_debt_when_hf_drops

In the test, ETH price drops from $2,000 to $10, causing the borrower's HF to drop to 0.50 (where collateral $100 debt $100). The liquidator attempts to cover debt, but every liquidation call reverts with "DSCEngine__HealthFactorNotImproved".

Tools Used

  • Moccasin v0.4.4

  • Titanoboa v0.2.8

  • Hypothesis v6.168.0

Recommended Mitigation

Remove the strict monotonicity requirement when accounts are deeply underwater, or allow 100% full liquidations that clear debt completely:

ending_user_health_factor: uint256 = self._health_factor(user)
- assert (
- ending_user_health_factor > starting_user_health_factor
- ), "DSCEngine__HealthFactorNotImproved"
+ if self.s_dsc_minted[user] > 0:
+ assert (
+ ending_user_health_factor > starting_user_health_factor or starting_user_health_factor <= 55 * 10**16
+ ), "DSCEngine__HealthFactorNotImproved"

Alternatively, follow industry standard designs (e.g. MakerDAO/Aave) where liquidators can liquidate up to a close factor (e.g. 50% or 100%) without requiring health factor improvement.

Updates

Lead Judging Commences

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