Algo Ssstablecoinsss

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

Fee-on-transfer collateral is overcredited, enabling unbacked minting

Root + Impact

Description

  • when a user deposits collateral, the protocol should increase that user’s collateral balance by the amount of tokens the engine actually receives. This is important because the recorded collateral balance is later used to compute the user’s USD collateral value, health factor, and maximum mintable DSC.

  • _deposit_collateral() increments internal accounting using the user-supplied amount_collateral before verifying how many tokens were transferred in practice. The code assumes that transferFrom(msg.sender, self, amount_collateral) always results in the engine receiving exactly amount_collateral, which is not true for fee-on-transfer, taxed, deflationary, or burn-on-transfer tokens.

    If such a token is ever supported, the contract will over-credit the depositor and overestimate the amount of collateral backing their debt. Because the engine later uses this inflated balance in get_account_collateral_value(), health_factor(), and mint checks, the user can mint more DSC than the actual collateral can support.

    The result is immediate hidden insolvency: the protocol’s accounting says the position is safe, but the engine holds fewer real assets than required to redeem or liquidate that position safely.

def _deposit_collateral(
token_collateral_address: address, amount_collateral: uint256
):
assert amount_collateral > 0, "DSCEngine_NeedsMoreThanZero"
assert self.token_address_to_price_feed[token_collateral_address] != empty(
address
), "DSCEngine__TokenNotAllowed"
<self.user_to_token_address_to_amount_deposited[msg.sender][
token_collateral_address
] += amount_collateral>
log CollateralDeposited(msg.sender, amount_collateral)
success: bool = extcall IERC20(token_collateral_address).transferFrom(
msg.sender, self, amount_collateral
)
assert success, "DSCEngine_TransferFailed"

Risk

Likelihood:

  • This occurs whenever a supported collateral token charges transfer fees, burns on transfer, or otherwise transfers less than requested.

  • The current code does not verify the actual received balance.

Impact:

  • Borrowing power becomes inflated.

  • The system can become undercollateralized immediately and later redemptions can fail due to missing backing.

Proof of Concept

/*
Assume the protocol supports a collateral token that deducts a 10% fee on every transfer.
The token is otherwise priced normally by the oracle, so the engine treats it as valid collateral.
Step 1: attacker acquires 100 tokens.
Step 2: attacker approves the engine and deposits 100 tokens.
deposit_collateral(token, 100e18)
Step 3: internal accounting is updated first.
user_to_token_address_to_amount_deposited[attacker][token] += 100e18
Step 4: transferFrom executes, but because the token charges a 10% fee,
the engine only receives 90e18.
Internal protocol view:
- recorded collateral = 100e18
Actual engine balance:
- real collateral received = 90e18
Step 5: the engine values the position using 100e18, not 90e18.
Step 6: attacker mints DSC based on the inflated collateral value.
Result:
- the attacker has more DSC than should be allowed
- the position appears safer than it really is
- the missing 10e18 of backing becomes hidden protocol insolvency
*/
Expanded attack walkthrough:
/*
Example with round numbers:
Assume token price = $1 per token
Assume liquidation threshold = 50%
Attacker deposits 100 tokens
Engine records collateral = $100
Actual engine holdings = 90 tokens = $90
Borrowing power according to protocol accounting:
$100 * 50% = $50 DSC
Real safe borrowing power:
$90 * 50% = $45 DSC
Attacker mints $50 DSC
Protocol thinks:
health factor = healthy
Reality:
the position only has enough collateral for $45 DSC of debt at the configured threshold
There is now $5 of immediate hidden undercollateralization caused solely by wrong deposit accounting.
*/
Why this matters operationally:
/*
When the attacker later redeems or gets liquidated, the engine will continue using the inflated
stored balance as the source of truth.
That means:
- health factor is overstated
- liquidation may happen too late
- redemptions may fail because the engine does not actually possess the recorded amount
- insolvency is realized only when users try to withdraw or liquidators try to seize collateral
*/

Recommended Mitigation

- credit amount_collateral directly
- assume transferFrom always delivers the requested amount
+ uint256 balanceBefore = IERC20(token_collateral_address).balanceOf(self)
+ success: bool = extcall IERC20(token_collateral_address).transferFrom(
+ msg.sender, self, amount_collateral
+ )
+ assert success, "DSCEngine_TransferFailed"
+ uint256 balanceAfter = IERC20(token_collateral_address).balanceOf(self)
+ uint256 received = balanceAfter - balanceBefore
+ assert received > 0, "DSCEngine_TransferFailed"
+ self.user_to_token_address_to_amount_deposited[msg.sender][
+ token_collateral_address
+ ] += received
Updates

Lead Judging Commences

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