Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Incorrect amount burned in DebtToken Contract leads to token accounting discrepancy

Summary

In the DebtToken contract's burn() function, the wrong amount is being burned. The function burns the nominal amount instead of the scaled amount, which breaks the token's accounting system and can lead to incorrect debt tracking.

Vulnerability Details

The DebtToken contract follows a scaled balance model similar to Aave's variable debt tokens, where:

  1. The actual token balances are scaled relative to a usage index that increases over time to represent accumulated interest

  2. Internal accounting uses scaled amounts while external-facing values show the actual debt amounts

DebtToken.sol:L210

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
// ...
uint256 amountScaled = amount.rayDiv(index);
// @audit Burns amount instead of amountScaled
_burn(from, amount.toUint128());
// But returns amountScaled as the burned amount
return (amount, totalSupply(), amountScaled, balanceIncrease);
}

The issue is that the function burns the nominal amount instead of the amountScaled. This is incorrect because:

  1. The internal balance accounting of DebtToken works with scaled balances

  2. The _burn() function affects these internal scaled balances

  3. By burning the nominal amount instead of scaled amount, we're burning too many tokens

To understand why this is wrong, consider how the scaling works:

  • Actual Debt = Scaled Balance * Current Index

  • Scaled Balance = Actual Debt / Current Index

When burning debt tokens:

  1. We receive the actual debt amount to burn

  2. We must convert it to scaled amount (done correctly with amountScaled = amount.rayDiv(index))

  3. We should burn this scaled amount to maintain correct accounting

The current implementation burns the actual amount, which is much larger than the scaled amount (since index is generally > 1 RAY), leading to:

  • Incorrect debt accounting

  • Users having less debt than they should

  • Potential system-wide accounting mismatches

This is further complicated by the contract returning the correct amountScaled in the return values, while actually burning a different amount, which can confuse integrating systems.

PoC

  1. User A borrows 100 tokens (index = 2 * RAY)

  2. The scaled amount would be 50 (100 / 2)

  3. User A tries to repay 60 tokens

  4. The contract calculates scaled amount as 30 (60 / 2)

  5. But burns 60 tokens from the scaled balance instead of 30

  6. User A's debt is reduced more than it should be

Impact

  • Incorrect debt accounting across the lending protocol

  • Users can have their debt reduced more than intended

  • Protocol's total debt calculations become inaccurate

  • Potential economic loss for the protocol due to debt undervaluation

Tools Used

Manual review

Recommendations

Change the burn function to use the scaled amount:

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
// ... existing code ...
uint256 amountScaled = amount.rayDiv(index);
// Burn the scaled amount instead of nominal amount
_burn(from, amountScaled.toUint128());
// ... rest of the code ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!