In the burn function of the DebtToken contract, a user's effective balance is determined via the overridden balanceOf function, which scales the stored balance by the current normalized debt. When a user attempts to burn tokens, the contract ensures that the burn amount does not exceed this effective balance. However, the internal _update function re-scales the burn amount using rayDiv with the current normalized debt. If the normalized debt is below the base value (1e27), this scaling increases the required burn amount. Consequently, even if the user’s raw burn amount equals their effective balance, the scaled burn amount becomes larger than the stored balance—causing the burn operation to revert due to insufficient tokens.
Effective Balance Calculation:
The overridden balanceOf function computes a user’s effective debt balance as follows:
Example:
If the stored (scaled) balance is such that when multiplied by a normalized debt of 1e27, the effective balance is 100 tokens, then balanceOf returns 100 tokens.
Burn Function Balance Check:
In the burn function, the contract first retrieves the effective balance:
This ensures that the requested burn amount does not exceed the user’s effective balance.
Scaling in the _update Function:
The _update function, called during a burn, re-scales the raw burn amount:
The scaling is computed as:
scaledAmount = (amount * RAY) / getNormalizedDebt()
where RAY is defined as 1e27.
Mismatch Due to Decreasing Normalized Debt:
Scenario:
Over time, due to accrued interest or other protocol mechanics, the normalized debt (returned by getNormalizedDebt()) may fall below 1e27.
Example Calculation:
Assume a user’s effective balance (as returned by balanceOf) is 100 tokens.
The user attempts to burn 100 tokens.
If getNormalizedDebt() returns 0.8e27, the _update function computes the scaled amount as follows:
It calculates (100 * 1e27) / (0.8e27), which equals 125 tokens.
Outcome:
Even though the user’s effective balance is 100 tokens, the update logic demands burning 125 scaled tokens. This discrepancy causes the burn operation to revert because the stored balance is insufficient to cover 125 tokens, even though it appears sufficient when viewed via balanceOf.
Direct Burn Reversion:
Users attempting to burn their full effective balance will experience reversion due to the internal scaling increasing the required token amount beyond their stored balance.
Inaccurate Debt Accounting:
The mismatch between the effective balance calculation and the scaled amount deducted during the burn leads to inconsistencies in debt accounting. This prevents users from correctly settling their debt positions.
Manual review
Assume:
A user’s stored scaled balance, when multiplied by the normalized debt at a baseline of 1e27, yields an effective balance of 100 tokens.
The user calls the burn function with an amount of 100 tokens and the usage index is such that getNormalizedDebt() returns 0.8e27.
Steps:
The balanceOf function returns 100 tokens because it calculates 100 * (1e27 / 1e27), which equals 100 tokens.
The burn function sets the burn amount to 100 tokens, as it does not exceed the user’s effective balance.
The _update function then calculates the scaled amount as (100 * 1e27) / (0.8e27), resulting in 125 tokens.
The update process attempts to subtract 125 scaled tokens from the user's balance. However, the user's stored scaled balance corresponds to an effective balance of only 100 tokens.
Result:
The burn operation reverts due to insufficient tokens, preventing the user from redeeming their debt tokens.
Harmonize Scaling Logic:
Ensure that both the effective balance calculation in balanceOf and the scaling in _update use a consistent method. One approach is to use the same normalized debt value across both functions, or to adjust the burn logic so that the raw amount is correctly converted to the stored scaled balance.
2.*Adjust Burn Amount Verification:
Revise the burn function to account for the potential increase in the scaled amount when getNormalizedDebt() is below 1e27. This may involve recalculating the effective burn amount after scaling and ensuring that the user’s stored balance is sufficient.
The amount and userBalance are both in actual debt units, making the comparison valid. No reverts occur - the burn function works as intended by burning the correct scaled amount from storage.
The amount and userBalance are both in actual debt units, making the comparison valid. No reverts occur - the burn function works as intended by burning the correct scaled amount from storage.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.