Algo Ssstablecoinsss

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

M03. Strict health-factor improvement check blocks valid liquidations

Root + Impact

Description

  • Normal behavior: The liquidation health factor check should accept any liquidation that does not worsen the borrower’s health, including cases where rounding leaves the health factor unchanged.

  • Issue: The implementation uses a strict > check (ending_user_health_factor > starting_user_health_factor), causing valid liquidations that only marginally improve or keep the health factor constant (due to rounding) to revert.

// dsc_engine.vy (conceptual)
@internal
def _finalize_liquidation(user: address, starting_hf: uint256):
ending_hf: uint256 = self._get_health_factor(user)
- assert ending_hf > starting_hf, "DSCEngine__HealthFactorNotImproved"
+ # @> strict greater-than causes edge-case reverts

Risk

Likelihood:

  • Reason 1 // Fixed‑point arithmetic and integer division frequently produce off‑by‑one style rounding effects.

  • Reason 2 // Partial liquidations near the threshold are common, especially from automated bots that try to optimize capital use.

Impact:

  • Impact 1 // Some otherwise valid liquidation attempts revert unexpectedly, leaving a subset of risky positions unliquidated.

  • Impact 2 // Unreliable liquidation behavior complicates bot strategies and can lead to pockets of unaddressed risk that accumulate over time.

Proof of Concept

A partial liquidation leaves the health factor at exactly the same value:

  1. Choose parameters such that a given debt_to_cover reduces the user’s debt just enough that the ratio remains the same after rounding.

  2. starting_user_health_factor and ending_user_health_factor both compute to the same integer.

  3. The assertion ending_hf > starting_hf fails, reverting the liquidation even though it did not worsen the position.

function testLiquidationFailsWhenHealthFactorUnchangedByRounding() public {
address user = makeAddr("user");
address liquidator = makeAddr("liq");
// Setup a position and oracle values where a precise math calculation
// would slightly improve HF, but integer rounding keeps it constant.
// ...
vm.startPrank(liquidator);
dsc.mint(liquidator, appropriateAmount());
dsc.approve(address(engine), type(uint256).max);
vm.expectRevert("DSCEngine__HealthFactorNotImproved");
engine.liquidate(address(weth), user, partialDebt());
vm.stopPrank();
}

Recommended Mitigation

Relax the check to >= or otherwise ensure that small rounding effects do not block beneficial or neutral liquidations.

@internal
def _finalize_liquidation(user: address, starting_hf: uint256):
ending_hf: uint256 = self._get_health_factor(user)
- assert ending_hf > starting_hf, "DSCEngine__HealthFactorNotImproved"
+ assert ending_hf >= starting_hf, "DSCEngine__HealthFactorNotImproved"
Updates

Lead Judging Commences

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