Algo Ssstablecoinsss

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

Insolvent Positions Revert on Liquidation Due to Uncapped Liquidation Bonus Subtraction, Permanently Freezing Bad Debt

Summary

  • DSCEngine.liquidate() calculates seized collateral as the debt covered plus a 10% liquidation bonus.

  • When a position becomes deeply insolvent (collateral value < debt covered + 10% bonus), subtracting this collateral amount from the user's balance underflows.

  • The subtraction reverts under Vyper 0.4.0 arithmetic checks, preventing liquidators from clearing underwater accounts and permanently locking bad debt in the protocol.

Vulnerability Details

Description

In dsc_engine.vy, liquidate() calculates the collateral payout including a 10% bonus:

@external
def liquidate(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
) // 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)

In _redeem_collateral(), the calculated collateral amount is subtracted from the user's recorded balance:

@internal
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
extcall IERC20(token_collateral_address).transfer(_to, amount_collateral)

If market prices drop rapidly such that:

The subtraction balance -= amount_collateral underflows. Under Vyper 0.4.0, unsigned integer underflows revert unconditionally.

Risk

Likelihood: High

  • Occurs during sharp market downturns, oracle lag, or flash crashes where collateral value drops below 110% of debt.

Impact: High

  • Positions cannot be liquidated once insolvent, leaving unbacked DSC in circulation and freezing collateral permanently.

Severity: High

Proof of Concept

def test_insolvent_position_cannot_be_liquidated(
some_user, liquidator, weth, eth_usd, wbtc, btc_usd, dsc
):
token_addresses = [weth, wbtc]
feed_addresses = [eth_usd, btc_usd]
dsce = dsc_engine.deploy(token_addresses, feed_addresses, dsc)
dsc.set_minter(dsce.address, True)
dsc.transfer_ownership(dsce)
# User deposits 1 ETH ($2,000) and mints 1,000 DSC
user_collateral = to_wei(1, "ether")
user_mint = to_wei(1000, "ether")
with boa.env.prank(some_user):
weth.mint_amount(user_collateral)
weth.approve(dsce, user_collateral)
dsce.deposit_collateral_and_mint_dsc(weth, user_collateral, user_mint)
# Price drops to $1,000/ETH (Collateral = $1,000, Debt = 1,000 DSC)
# Covering 1,000 DSC requires 1.0 ETH + 0.1 ETH bonus = 1.1 ETH.
# User only has 1.0 ETH deposited.
eth_usd.updateAnswer(1000 * 10**8)
# Liquidator attempts liquidation
with boa.env.prank(liquidator):
weth.mint_amount(to_wei(10, "ether"))
weth.approve(dsce, to_wei(10, "ether"))
dsce.deposit_collateral_and_mint_dsc(weth, to_wei(10, "ether"), user_mint)
dsc.approve(dsce, user_mint)
# Underflows and reverts:
with boa.reverts():
dsce.liquidate(weth, some_user, user_mint)

Explanation: When collateral is insufficient to cover both the debt and the full 10% bonus, liquidate() underflows and reverts, preventing liquidation of insolvent positions.

Recommended Mitigation

Cap the redeemed collateral to the user's available deposited collateral balance:

@external
def liquidate(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
) // LIQUIDATION_PRECISION
+ user_balance: uint256 = self.user_to_token_address_to_amount_deposited[user][collateral]
+ total_collateral_to_redeem: uint256 = token_amount_from_debt_covered + bonus_collateral
+ if total_collateral_to_redeem > user_balance:
+ total_collateral_to_redeem = user_balance
self._redeem_collateral(
collateral,
- token_amount_from_debt_covered + bonus_collateral,
+ total_collateral_to_redeem,
user,
msg.sender,
)

Explanation: Capping redemption ensures liquidators can claim up to 100% of the debtor's remaining collateral without triggering an underflow revert.

Updates

Lead Judging Commences

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