Algo Ssstablecoinsss

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

Liquidation Insufficient Collateral Check

Root + Impact

Description

  • The liquidate() function is designed to seize collateral from unhealthy positions, calculating the total amount needed (debt + 10% bonus) and calling _redeem_collateral() to transfer it to the liquidator.

  • The function never checks if the user actually has enough collateral to cover the liquidation amount. When liquidating deeply underwater positions (collateral value < debt), the redemption attempts to subtract more collateral than exists, causing an underflow that reverts the entire transaction.

@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
# Calculate collateral to seize
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 check: Does user have this much collateral?
self._redeem_collateral(
collateral,
token_amount_from_debt_covered + bonus_collateral, # @> May exceed user's balance
user,
msg.sender,
)
@internal
def _redeem_collateral(...):
self.user_to_token_address_to_amount_deposited[_from][
token_collateral_address
] -= amount_collateral # @> UNDERFLOW when amount > balance

Risk

Likelihood:

  • Market crashes cause collateral values to drop rapidly below debt values, creating underwater positions where debt exceeds available collateral.

  • The 200% collateralization requirement provides only a 50% price drop buffer before positions become unliquidatable, occurring frequently in volatile crypto markets.

Impact:

  • Unliquidatable Bad Debt: Underwater positions cannot be liquidated, accumulating as protocol bad debt that exceeds collateral backing.

  • Protocol Insolvency: Failed liquidations during market crashes prevent bad debt cleanup, leaving the protocol undercollateralized with more DSC in circulation than collateral value.

Proof of Concept

import boa
from eth_utils import to_wei
def test_exploit_liquidation_underflow_underwater_position(
dsce, dsc, weth, eth_usd, some_user, liquidator
):
COLLATERAL_AMOUNT = to_wei(10, "ether")
AMOUNT_TO_MINT = to_wei(9900, "ether")
with boa.env.prank(some_user):
weth.approve(dsce, COLLATERAL_AMOUNT)
dsce.deposit_collateral_and_mint_dsc(weth, COLLATERAL_AMOUNT, AMOUNT_TO_MINT)
eth_usd.updateAnswer(800_00000000)
with boa.env.prank(liquidator):
dsc.approve(dsce, AMOUNT_TO_MINT)
try:
dsce.liquidate(weth, some_user, AMOUNT_TO_MINT)
assert False, "Should have reverted"
except Exception as e:
pass
# Result: $9,900 bad debt cannot be liquidated
# Protocol insolvent by $1,900 ($9,900 debt - $8,000 collateral)

Attack Steps:

  1. User deposits 10 ETH at $2,000 = $20,000 collateral

  2. User mints 9,900 DSC (health factor 1.01, barely safe)

  3. ETH crashes 60% to $800

  4. User position now: $8,000 collateral, $9,900 debt (underwater)

  5. Liquidator attempts liquidation of full $9,900 debt

  6. System calculates: needs 13.6125 ETH (12.375 + 10% bonus)

  7. User only has 10 ETH

  8. _redeem_collateral tries: 10 ETH - 13.6125 ETHUNDERFLOW

  9. Transaction reverts, position unliquidatable

Recommended Mitigation

@external
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
starting_user_health_factor: uint256 = self._health_factor(user)
assert (
starting_user_health_factor < MIN_HEALTH_FACTOR
), "DSCEngine__HealthFactorOk"
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
+ total_collateral_to_redeem: uint256 = token_amount_from_debt_covered + bonus_collateral
+
+ # Check user's available collateral
+ user_collateral_balance: uint256 = self.user_to_token_address_to_amount_deposited[user][collateral]
+
+ # Cap redemption at available collateral
+ if total_collateral_to_redeem > user_collateral_balance:
+ total_collateral_to_redeem = user_collateral_balance
+ # Recalculate proportional debt covered
+ debt_to_cover = (
+ user_collateral_balance * LIQUIDATION_PRECISION * debt_to_cover
+ ) // ((token_amount_from_debt_covered + bonus_collateral) * LIQUIDATION_PRECISION)
- self._redeem_collateral(
- collateral,
- token_amount_from_debt_covered + bonus_collateral,
- user,
- msg.sender,
- )
+ self._redeem_collateral(collateral, total_collateral_to_redeem, 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"
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!