Algo Ssstablecoinsss

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

No partial liquidation cap

Root + Impact

The liquidate() function has no cap on debt_to_cover, allowing full liquidation for minor health violations, causing harsh 10% penalties on entire positions.

Description

  • The protocol liquidates positions when health factor drops below 1.0, giving liquidators a 10% collateral bonus.

  • The debt_to_cover parameter is uncapped, allowing 100% liquidation even for minimal violations (HF = 0.99 instead of 1.0).

# dsc_engine.vy lines 112-135
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
starting_user_health_factor: uint256 = self._health_factor(user)
token_amount_from_debt_covered: uint256 = self._get_token_amount_from_usd(collateral, debt_to_cover)
bonus_collateral: uint256 = (token_amount_from_debt_covered * LIQUIDATION_BONUS) // LIQUIDATION_PRECISION
# @> No cap - liquidator can specify any debt amount up to 100%
self._redeem_collateral(collateral, token_amount_from_debt_covered + bonus_collateral, user, msg.sender)

Risk

Likelihood:

  • Users maximize capital efficiency by staying near liquidation threshold, triggering liquidations from small price movements.

  • MEV bots race to liquidate entire positions immediately when health drops below 1.0.

Impact:

  • Users lose 10% penalty on full position for tiny violations ($9,800 loss when $4,000 would fix health).

  • Creates harsh liquidation environment discouraging protocol usage.

Proof of Concept

# User: 100 ETH @ $2000 = $200K collateral, 98K DSC debt, HF = 1.02
# Price drops 2%: ETH → $1960, HF = 0.999 ❌
# Liquidator covers full 98K DSC debt
# Seizes: 50 ETH + 5 ETH bonus = $107,800
# User penalty: $9,800 (needed only $4K to restore health)
dsce.liquidate(weth, user, 98000e18) # No cap prevents full liquidation

Recommended Mitigation

# dsc_engine.vy
+MAX_LIQUIDATION_PERCENTAGE: constant(uint256) = 50
@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
assert debt_to_cover > 0, "DSCEngine__NeedsMoreThanZero"
starting_user_health_factor: uint256 = self._health_factor(user)
assert (starting_user_health_factor < MIN_HEALTH_FACTOR), "DSCEngine__HealthFactorOk"
+ total_debt: uint256 = self.user_to_dsc_minted[user]
+ max_liquidatable: uint256 = (total_debt * MAX_LIQUIDATION_PERCENTAGE) // 100
+ if debt_to_cover > max_liquidatable:
+ debt_to_cover = max_liquidatable
token_amount_from_debt_covered: uint256 = self._get_token_amount_from_usd(collateral, debt_to_cover)
bonus_collateral: uint256 = (token_amount_from_debt_covered * LIQUIDATION_BONUS) // LIQUIDATION_PRECISION
self._redeem_collateral(collateral, token_amount_from_debt_covered + bonus_collateral, user, msg.sender)
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
- ), "DSCEngine__HealthFactorNotImproved"
- self._revert_if_health_factor_is_broken(msg.sender)
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!