Core Contracts

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

Incorrect Adjustment of Burn Amount Due to Ignored Balance Increase Calculation

Summary

The DebtToken contract is designed to account for accrued debt by computing a balanceIncrease during token burns. However, in the burn function, the calculated balanceIncrease is not applied to adjust the amount to be burned. Instead, the code uses a no-op assignment (amount = amount;), which results in inaccurate debt adjustments when a user's balance has increased due to accrued interest.

Vulnerability Details

In the burn function, when a user’s stored index is lower than the current index, the contract computes a balanceIncrease value intended to represent the accrued interest on the user’s debt, However, instead of adjusting the amount by incorporating this computed increase, the code incorrect set back amount = amount

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
uint256 userBalance = balanceOf(from);
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
@>> balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
@>> amount = amount;
}
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
_burn(from, amount.toUint128());
emit Burn(from, amountScaled, index);
return (amount, totalSupply(), amountScaled, balanceIncrease);
}

The failure to adjust amount with balanceIncrease means that the debt reduction is calculated on an outdated value. Users who have accrued additional debt due to interest will not see their burn operation reflect the true debt amount. This miscalculation can lead to persistent inaccuracies in debt accounting.

Example:

Scenario:

  1. Initial State:

    • User’s stored debt index: 1.0e27 (RAY)

    • Current debt index (from getNormalizedDebt()): 1.1e27 (10% interest)

    • User’s debt balance (balanceOf): 100 tokens (principal)

  2. Interest Accrual:

    • The user’s debt has accrued to 100 * 1.1 = 110 tokens due to interest.

    • balanceIncrease calculation:
      balanceIncrease = 100 * (1.1 - 1.0) = 10 tokens

  3. Burn Operation:

    • User calls burn(100) to repay the principal (100 tokens).

    • Expected Behavior:
      The contract should burn 110 tokens (principal + accrued interest) to fully clear the debt.

    • Actual Behavior:
      The code leaves amount = 100 (ignoring balanceIncrease), resulting in:

      • Scaled amount burned: 100 / 1.1 ≈ 90.90 (rounded down)

      • Remaining scaled debt: 100 - 90.90 ≈ 9.09

      • Remaining debt after applying index: 9.09 * 1.1 ≈ 10 tokens

Result:
The user’s debt is reduced to 10 tokens instead of 0, leaving residual debt due to unaccounted interest.

Impact

Inaccurate debt accounting leads to residual debt after burns, allowing users to underpay their obligations and potentially exploit the system to maintain lower effective debt than owed.

Tools Used

manual review

Recommendations

Adjust the amount to be burned by incorporating the balanceIncrease to ensure the full debt (principal + accrued interest) is accounted for.

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
uint256 userBalance = balanceOf(from);
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
- amount = amount;
+ amount = amount + balanceIncrease; // Adjust amount to include accrued interest
}
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
_burn(from, amount.toUint128());
emit Burn(from, amountScaled, index);
return (amount, totalSupply(), amountScaled, balanceIncrease);
}

This ensures the amount reflects the total debt (principal + interest) to be burned.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

Support

FAQs

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

Give us feedback!