Algo Ssstablecoinsss

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

M02. No close factor allowing full liquidation

Root + Impact

Description

  • Normal behavior: Liquidations should typically be partial and limited by a close factor (e.g., 50%), allowing users time to restore health and reducing MEV and slippage.

  • Issue: The liquidate function allows a liquidator to cover 100% of a user’s debt in a single transaction, enabling full account wipeouts and creating attractive MEV opportunities for aggressive liquidators.

// dsc_engine.vy (conceptual)
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
# @> no validation that debt_to_cover <= close_factor * user_debt
self._liquidate_position(collateral, user, debt_to_cover)

Risk

Likelihood:

  • Reason 1 // Liquidation bots commonly take full advantage of any allowed range, so they will routinely use the maximum legal debt_to_cover.

  • Reason 2 // Market volatility often pushes multiple users toward liquidation simultaneously, attracting competitive MEV searchers.

Impact:

  • Impact 1 // Users can lose 100% of their position in one transaction, even when a smaller partial liquidation would have restored safety, harming user experience and perceived fairness.

  • Impact 2 // Large all‑at‑once liquidations can create sudden, sizeable collateral flows to liquidators, exacerbating slippage and destabilizing markets around the underlying assets.

Proof of Concept

A liquidator targets an at‑risk account and liquidates the entire debt at once:

  1. User is slightly undercollateralized.

  2. Liquidator computes full outstanding DSC debt and calls liquidate with debt_to_cover = total_debt.

  3. Engine processes full liquidation, transferring all collateral plus bonus to the liquidator.

  4. User has no opportunity to add collateral or repay debt between partial liquidations.

function testFullAccountLiquidationInSingleTx() public {
address user = makeAddr("user");
address liquidator = makeAddr("liq");
// User sets up at-risk position
// ...
uint256 userDebt = engine.get_user_debt(user);
vm.startPrank(liquidator);
dsc.mint(liquidator, userDebt);
dsc.approve(address(engine), userDebt);
// @> no close factor: entire debt can be covered
engine.liquidate(address(weth), user, userDebt);
vm.stopPrank();
// User's position fully wiped out despite moderate undercollateralization
}

Recommended Mitigation

Introduce a close factor that caps debt_to_cover as a fraction of the user’s total debt per liquidation.

# dsc_engine.vy
+CLOSE_FACTOR: constant(uint256) = 50 # percent
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
user_debt: uint256 = self._get_user_debt(user)
+ max_cover: uint256 = user_debt * CLOSE_FACTOR / 100
+ assert debt_to_cover <= max_cover, "DSCEngine__ExceedsCloseFactor"
self._liquidate_position(collateral, user, debt_to_cover)
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!