Algo Ssstablecoinsss

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

Fixed Liquidation Bonus Reverts When Victim Position is Severely Undercollateralized — Insolvent Positions Cannot Be Cleared

Fixed Liquidation Bonus Reverts When Victim Position is Severely Undercollateralized — Insolvent Positions Cannot Be Cleared

Scope

  • src/dsc_engine.vy

Description

The liquidate() function is expected to allow any liquidator to clear an unhealthy position (health factor < 1) by paying off the user's DSC debt and receiving their collateral plus a 10% bonus as incentive. This mechanism is the primary defense against the protocol accumulating bad debt.

When a position becomes severely undercollateralized — where the collateral's USD value is less than the outstanding DSC debt — the 10% liquidation bonus calculation produces a total collateral demand that exceeds the user's actual deposited balance. Vyper 0.4.0's built-in arithmetic overflow protection causes the subtraction in _redeem_collateral() to revert, making it impossible to liquidate the most dangerous positions in the system. Bad debt accumulates without any mechanism for resolution.

# dsc_engine.vy
def liquidate(collateral: address, user: address, debt_to_cover: uint256):
...
token_amount: uint256 = self._get_token_amount_from_usd(collateral, debt_to_cover)
@> bonus_collateral: uint256 = token_amount * LIQUIDATION_BONUS // LIQUIDATION_PRECISION # 10%
@> total_collateral_to_redeem: uint256 = token_amount + bonus_collateral
# If user has 1 ETH but token_amount + bonus = 1.2375 ETH → REVERTS below
self._redeem_collateral(collateral, total_collateral_to_redeem, user, msg.sender)
def _redeem_collateral(token_collateral_address: address, amount_collateral: uint256, from_: address, to: address):
@> self.user_to_token_address_to_amount_deposited[from_][token_collateral_address] -= amount_collateral
# Vyper arithmetic check: if amount > deposited → REVERT (no underflow)

Risk

Likelihood: Medium

  • Occurs whenever ETH or BTC price drops more than approximately 9% below the liquidation threshold without the position being partially cleared first.

  • Fast market crashes (flash crashes, black swan events) routinely produce such conditions for overleveraged CDP positions.

  • The 10% bonus threshold is breached whenever collateral_value / debt < 1.1, which is the zone of maximum insolvency risk.

Impact: High

  • The protocol accumulates irrecoverable bad debt — DSC is backed by less collateral than its total supply.

  • The DSC peg is threatened as the backing ratio falls below 100%.

  • Liquidators cannot act even when incentivized to do so, allowing positions to deteriorate further.

Severity: High

Proof of Concept

A user deposits 1 ETH when ETH = $2,000. They mint 900 DSC (within the 50% threshold: $1,000 effective collateral). ETH price crashes 60% to $800. The user is now liquidatable (health factor = 0.44) but the liquidation call reverts because the bonus demand exceeds available collateral.

# titanoboa PoC demonstrating liquidation revert
import boa
from eth_utils import to_wei
# Setup: user deposits 1 ETH ($2000) and mints 900 DSC
with boa.env.prank(victim):
weth.approve(dsce, to_wei(1, "ether"))
dsce.deposit_collateral_and_mint_dsc(weth, to_wei(1, "ether"), to_wei(900, "ether"))
# ETH crashes from $2000 to $800
eth_usd.updateAnswer(800 * 10**8)
# Health factor = (800 * 0.5) / 900 = 0.44 → liquidatable
assert dsce.health_factor(victim) < to_wei(1, "ether")
# Liquidator tries to clear the debt
dsc.mint(liquidator, to_wei(900, "ether"))
with boa.env.prank(liquidator):
dsc.approve(dsce, to_wei(900, "ether"))
# token_amount = 900/800 = 1.125 ETH; bonus = 0.1125 ETH; total = 1.2375 ETH
# User only has 1 ETH → Vyper arithmetic underflow → REVERTS
with boa.reverts():
dsce.liquidate(weth, victim, to_wei(900, "ether"))
# Bad debt: 900 DSC unbacked, cannot be cleared

Recommended Mitigation

Cap the total collateral to redeem at the victim's available deposited balance. When the position is deeply underwater, the liquidator still receives all available collateral (just without the full 10% bonus), which is sufficient incentive given the liquidator is still recovering more value than they pay in DSC. This is a common pattern in mature CDP protocols.

token_amount: uint256 = self._get_token_amount_from_usd(collateral, debt_to_cover)
bonus_collateral: uint256 = token_amount * LIQUIDATION_BONUS // LIQUIDATION_PRECISION
- total_collateral_to_redeem: uint256 = token_amount + bonus_collateral
+ available: uint256 = self.user_to_token_address_to_amount_deposited[user][collateral]
+ total_collateral_to_redeem: uint256 = min(token_amount + bonus_collateral, available)
self._redeem_collateral(collateral, total_collateral_to_redeem, user, msg.sender)
Updates

Lead Judging Commences

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