Algo Ssstablecoinsss

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

Missing Return Value Check on DSC.burn_from()

Root + Impact

Description

  • The _burn_dsc() function is designed to burn DSC tokens when users repay debt. It should burn tokens first, then update the user's debt record.

  • The function reduces the user's debt before calling DSC.burn_from() and does not check the return value. If burn fails, the debt is cleared but tokens remain in circulation, allowing users to withdraw collateral while keeping unbacked DSC.

@internal
def _burn_dsc(
amount_dsc_to_burn: uint256, on_behalf_of: address, dsc_from: address
):
self.user_to_dsc_minted[on_behalf_of] -= amount_dsc_to_burn # @> Debt cleared FIRST
# Note, we are not checking success here
extcall DSC.burn_from(dsc_from, amount_dsc_to_burn) # @> No return value check - burn could fail silently

Risk

Likelihood:

  • The burn_from() function can fail when insufficient allowance exists, the token holder has insufficient balance, or gas limits are reached during execution.

  • The incorrect order of operations (debt reduction before token burn) means a single failure creates permanent protocol insolvency.

Impact:

  • Protocol Insolvency: Users clear debt without burning DSC tokens, leaving unbacked stablecoins in circulation.

  • Collateral Theft: Users with cleared debt withdraw full collateral while retaining DSC tokens to spend or trade.

Proof of Concept

import boa
from eth_utils import to_wei
def test_exploit_unchecked_burn_accounting_mismatch(dsce, dsc, weth, some_user):
COLLATERAL_AMOUNT = to_wei(10, "ether")
AMOUNT_TO_MINT = to_wei(5000, "ether")
with boa.env.prank(some_user):
# User deposits collateral and mints DSC
weth.approve(dsce, COLLATERAL_AMOUNT)
dsce.deposit_collateral_and_mint_dsc(weth, COLLATERAL_AMOUNT, AMOUNT_TO_MINT)
# User attempts to burn DSC - if burn_from() fails:
# 1. user_to_dsc_minted[some_user] -= 5000 (debt cleared)
# 2. DSC.burn_from() returns False (no check!)
# 3. No revert occurs
dsc.approve(dsce, AMOUNT_TO_MINT)
dsce.burn_dsc(AMOUNT_TO_MINT)
# In exploit scenario:
# - Debt: 0 DSC (cleared)
# - Balance: 5000 DSC (not burned!)
# - Supply: 5000 DSC (inflated!)
# User withdraws all collateral (succeeds because debt = 0)
dsce.redeem_collateral(weth, COLLATERAL_AMOUNT)
# Result: User has 10 ETH + 5000 DSC ($35,000 from $30,000 deposit)

Attack Steps:

  1. Attacker deposits 10 ETH collateral (~$30,000)

  2. Attacker mints 5,000 DSC

  3. Attacker calls burn_dsc(5,000) with conditions causing burn_from to fail

  4. Debt cleared: user_to_dsc_minted[attacker] = 0

  5. DSC.burn_from() fails silently (no revert)

  6. Attacker withdraws all 10 ETH collateral

  7. Result: Attacker keeps 10 ETH + 5,000 DSC (~$35,000 total)

Recommended Mitigation

@internal
def _burn_dsc(
amount_dsc_to_burn: uint256, on_behalf_of: address, dsc_from: address
):
- self.user_to_dsc_minted[on_behalf_of] -= amount_dsc_to_burn
- # Note, we are not checking success here
- extcall DSC.burn_from(dsc_from, amount_dsc_to_burn)
+ # Burn tokens FIRST, then update state
+ success: bool = extcall DSC.burn_from(dsc_from, amount_dsc_to_burn)
+ assert success, "DSCEngine__BurnFailed"
+ self.user_to_dsc_minted[on_behalf_of] -= amount_dsc_to_burn
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!