Algo Ssstablecoinsss

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

[M-03] Liquidator's Health Factor Not Checked Before Debt Transfer

Root + Impact

Description

  • In the liquidate() function, the liquidator's health factor is only checked at the very end after they've already taken on the user's debt.

  • When the liquidator doesn't have sufficient collateral to absorb this new debt, the transaction reverts at the end, wasting gas. More critically, there's no check that the liquidator has enough DSC tokens approved to burn, which could lead to unexpected reverts mid-transaction.

// Root cause in the codebase with @> marks to highlight the relevant section
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
# ... liquidation logic ...
self._burn_dsc(debt_to_cover, user, msg.sender) # Burns from liquidator
# ...
@> self._revert_if_health_factor_is_broken(msg.sender) # Checked AFTER all operations

Risk

Likelihood: Medium

  • Reason 1 // Liquidators may miscalculate their capacity

  • Reason 2 // MEV bots might attempt partial liquidations

Impact: Medium

  • Impact 1 // Gas wasted on failed liquidation attempts

  • Impact 2 // Legitimate liquidations may fail unexpectedly

  • Impact 3 // Reduced liquidation efficiency during crises

Proof of Concept

The following shows how a liquidator can waste gas on a liquidation that was always doomed to fail. The health factor check at the end reverts after all the expensive operations have already been performed.

def test_liquidator_health_check_too_late():
# Liquidator has minimal collateral
with boa.env.prank(liquidator):
weth.approve(dsce, SMALL_AMOUNT)
dsce.deposit_collateral(weth, SMALL_AMOUNT)
# Liquidator attempts to cover large debt
# All operations execute (gas consumed)
# Then health factor check fails at the end
dsc.approve(dsce, LARGE_DEBT)
with boa.reverts("DSCEngine__BreaksHealthFactor"):
dsce.liquidate(weth, underwater_user, LARGE_DEBT)
# Gas wasted on operations that were always going to fail

Recommended Mitigation

Add an early check to ensure the liquidator has sufficient collateral capacity before performing the liquidation. This saves gas on failed attempts and provides clearer error messages.

@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
+ # Check liquidator can handle additional debt
+ liquidator_dsc: uint256 = self.user_to_dsc_minted[msg.sender]
+ liquidator_collateral: uint256 = self._get_account_collateral_value(msg.sender)
+ projected_health: uint256 = self._calculate_health_factor(
+ liquidator_dsc + debt_to_cover, liquidator_collateral
+ )
+ assert projected_health >= MIN_HEALTH_FACTOR, "DSCEngine__LiquidatorWouldBeUnhealthy"
starting_user_health_factor: uint256 = self._health_factor(user)
# ... rest of function
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!