Algo Ssstablecoinsss

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

Smart Contract Security Audit Report: [HIGH-02]

Executive Summary

A targeted security audit was conducted on the Algo Ssstablecoinsss smart contracts on ZKsync Era, focusing on finding HIGH-02: deeply insolvent positions reverting on liquidation due to uncapped 10% bonus subtraction.

Finding Summary

Severity Count Resolved Acknowledged
Critical 0 0 0
High 1 0 0
Medium 0 0 0
Low 0 0 0
Informational 0 0 0
Total 1 0 0

Audit Scope

File Path Logic Overview SWC / Risk Focus
src/dsc_engine.vy Liquidation and collateral redemption math SWC-101 (Arithmetic Underflow) / Bad Debt

Detailed Findings

[HIGH-02] Deeply Insolvent Positions Revert on Liquidation Due to Uncapped 10% Bonus Subtraction, Freezing Bad Debt

  • Severity: High

  • Status: Confirmed / PoC Verified

  • Vulnerability Class: SWC-101 (Arithmetic Underflow / Bad Debt Freeze)

  • Affected File(s): src/dsc_engine.vy:L112-L135, src/dsc_engine.vy:L249-L252

Description

The liquidation mechanism is designed to incentivize third-party liquidators to repay delinquent debt in exchange for seized collateral plus a 10% liquidation bonus (LIQUIDATION_BONUS = 10%).

In dsc_engine.vy, liquidate() computes the collateral to seize based on debt covered plus 10%:

@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)
...

Inside _redeem_collateral():

@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
...

If market prices drop rapidly such that the borrower's total deposited collateral is less than token_amount_from_debt_covered + bonus_collateral:

The unsigned integer subtraction underflows. In Vyper 0.4.0, unsigned integer underflow reverts automatically.

Proof of Concept

The following test executed via uv run mox test tests/unit/test_pocs.py -s proves that an insolvent position reverts on liquidation:

def test_poc_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 crashes to $1,000 per ETH (Total collateral = $1,000. Debt = 1,000 DSC).
# Liquidating $1,000 debt requires 1.0 ETH + 10% bonus (0.1 ETH) = 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)
# Reverts on unsigned integer underflow:
with boa.reverts():
dsce.liquidate(weth, some_user, user_mint)

PoC Verification Log Output:

[PoC 3] Verified: Insolvent position cannot be liquidated, locking bad debt permanently!
PASSED in 3.55s

Impact

  • Permanent Protocol Bad Debt: Deeply insolvent positions cannot be liquidated. Any liquidator attempting to liquidate the position receives a revert.

  • Unbacked Stablecoins: The insolvent debt remains active on the protocol ledger, while the remaining collateral sits permanently locked and cannot be liquidated or recovered.

Recommendation

In liquidate(), cap the redeemed collateral at the user's available collateral balance. If available collateral is insufficient to cover the full 10% bonus, transfer all remaining collateral to the liquidator rather than reverting:

@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
+ total_user_collateral: uint256 = self.user_to_token_address_to_amount_deposited[user][collateral]
+ collateral_to_redeem: uint256 = token_amount_from_debt_covered + bonus_collateral
+ if collateral_to_redeem > total_user_collateral:
+ collateral_to_redeem = total_user_collateral
self._redeem_collateral(
collateral,
- token_amount_from_debt_covered + bonus_collateral,
+ collateral_to_redeem,
user,
msg.sender,
)

Automated Analysis Log Summary

  • Moccasin / Titanoboa Unit Test: test_poc_insolvent_position_cannot_be_liquidated passed in 3.55s.


Disclaimer

This security review does not guarantee 100% security against zero-day vulnerabilities. Continuous monitoring, bug bounties, and formal verification are recommended prior to mainnet deployment.

Updates

Lead Judging Commences

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