Algo Ssstablecoinsss

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

H2. Deeply underwater positions become unliquidatable

Root + Impact

Description

  • Normal behavior: When a user is undercollateralized, liquidators should be able to repay part or all of the user’s DSC debt and receive collateral plus a bonus, eventually fully unwinding insolvent positions.

  • Issue: For deeply underwater positions, the computed collateral_to_redeem + bonus can exceed the user’s total collateral balance, causing underflow or failed transfers and effectively making some positions unliquidatable, leaving permanent bad debt.

// dsc_engine.vy (conceptual)
@internal
def _liquidate_position(collateral: address, user: address, debt_to_cover: uint256):
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 // 10%
total_collateral_to_redeem: uint256 = token_amount_from_debt_covered + bonus_collateral
# @> assumes total_collateral_to_redeem <= user collateral
self.user_to_token_address_to_amount_deposited[user][collateral] -= total_collateral_to_redeem

Risk

Likelihood:

  • Reason 1 // Sharp price crashes or oracle delays routinely produce positions far below the liquidation threshold in volatile assets like ETH and BTC.

  • Reason 2 // As protocol TVL grows, the probability that at least one position will become deeply underwater increases, especially during systemic events.

Impact:

  • Impact 1 // Certain undercollateralized positions become impossible to liquidate, leaving unbacked DSC in circulation and directly violating the protocol’s overcollateralization invariant.

  • Impact 2 // Accumulating bad debt erodes trust in the stablecoin’s solvency and can trigger a broader loss of peg and user confidence.

Proof of Concept

A price crash makes a position so underwater that even trying to liquidate 100% of the debt plus bonus exceeds available collateral:

  1. User deposits 1 WETH at $2000 and mints 1000 DSC.

  2. Price of WETH drops to $500.

  3. A liquidator attempts to cover 1000 DSC; required collateral plus 10% bonus exceeds 1 WETH.

  4. The subtraction from user_to_token_address_to_amount_deposited[user][collateral] underflows or fails, reverting the transaction.

  5. Repeated attempts with smaller debt_to_cover continue to fail until the chosen amount happens to not exceed remaining collateral, leaving residual bad debt.

function testDeeplyUnderwaterPositionBecomesUnliquidatable() public {
address user = makeAddr("user");
address liquidator = makeAddr("liq");
// User opens leveraged position
vm.startPrank(user);
weth.approve(address(engine), 1 ether);
engine.deposit_collateral(address(weth), 1 ether);
engine.mint_dsc(1000e18);
vm.stopPrank();
// Price crashes
setPrice(address(weth), 500e18);
// Liquidator tries to cover full debt
vm.startPrank(liquidator);
dsc.mint(liquidator, 1000e18);
dsc.approve(address(engine), type(uint256).max);
vm.expectRevert(); // @> revert due to exceeding user collateral
engine.liquidate(address(weth), user, 1000e18);
vm.stopPrank();
}

Recommended Mitigation

Reduce or cap the liquidation bonus in deep insolvency so that collateral_from_debt + bonus_collateral always fits within the user’s remaining collateral, and explicitly account for any remaining bad debt.

@internal
def _liquidate_position(collateral: address, user: address, debt_to_cover: uint256):
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 // 10%
total_collateral_to_redeem: uint256 = token_amount_from_debt_covered + bonus_collateral
user_balance: uint256 = self.user_to_token_address_to_amount_deposited[user][collateral]
if total_collateral_to_redeem > user_balance:
# first, reduce the bonus so total fits in remaining collateral
if token_amount_from_debt_covered >= user_balance:
# no room for bonus; clamp redemption to user balance
total_collateral_to_redeem = user_balance
bonus_collateral = 0
else:
bonus_collateral = user_balance - token_amount_from_debt_covered
total_collateral_to_redeem = token_amount_from_debt_covered + bonus_collateral
# record remaining uncovered debt as protocol bad debt
self.total_bad_debt += debt_to_cover - self._get_usd_value(collateral, total_collateral_to_redeem - bonus_collateral)
self.user_to_token_address_to_amount_deposited[user][collateral] -= total_collateral_to_redeem
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!