Core Contracts

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

Double scaling of debt balance in mint function causes excessive debt token minting

Summary

The DebtToken mint function incorrectly calculates a balance increase by applying the usage index scaling twice. The bug arises when the function retrieves the user's scaled balance using the overridden balanceOf, which already factors in the usage index, and then applies an additional scaling during the balance increase calculation. This results in minting more debt tokens than warranted, causing inflated debt balances for borrowers.

Vulnerability Details

How the Debt Scaling Works

  • Debt Scaling in balanceOf:
    The overridden balanceOf function returns a user’s debt balance by taking the raw stored balance (obtained via super.balanceOf(account)) and scaling it with the current usage index:

    uint256 scaledBalance = super.balanceOf(account);
    return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());

    This means that the value returned by balanceOf is already adjusted for accrued interest via the usage index.

  • Flawed Balance Increase Calculation in mint:
    In the mint function, the balance increase is computed as:

    if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
    balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
    }

    Here, scaledBalance is re-multiplied by the new and previous usage indexes even though it has already been scaled by the normalized debt. This double scaling inflates the computed balance increase.

The Mathematical Impact

  • Over-Accrual of Debt:
    Since the balance is scaled twice, the computed balanceIncrease is higher than it should be. For example, if the underlying debt amount is 1,000 tokens and the usage index has increased from a stored value to a new higher index, the additional interest computed by the above expression will be exaggerated.

  • Excess Debt Token Minting:
    The inflated balance increase is added to the actual mint amount:

    uint256 amountToMint = amount + balanceIncrease;
    _mint(onBehalfOf, amountToMint.toUint128());

    This results in minting an excess number of tokens, which overstates the borrower’s debt obligations in the system.

  • Collateral and Risk Implications:
    The inflated debt balance can lead to:

    • Misrepresentation of a borrower's debt relative to their collateral, potentially triggering premature liquidations.

    • Accounting discrepancies in total debt supply, which can undermine system stability and risk management.

Impact

  • Inflated Debt Balances:
    Borrowers’ debt is overstated, which can distort collateral ratios and potentially force unwarranted liquidations.

  • Economic Imbalance:
    The excess minting of debt tokens can lead to a misalignment in the protocol’s revenue model, where borrowers effectively pay more than their true accrued interest.

  • Accounting Inconsistencies:
    The discrepancy in debt token supply affects downstream calculations, including interest accrual, burn operations, and overall reserve accounting, thereby compromising system integrity.

  • Stakeholder Risk:
    Liquidity providers and risk managers relying on accurate debt data might face unforeseen risks due to inflated debt figures.

Proof-of-Concept (PoC)

Scenario Setup

  • Initial Conditions:

    • User’s stored raw debt balance: 1,000 tokens.

    • Current usage index (from reserve): 1e27 (RAY).

    • Previous stored usage index in _userState: 0.95e27.

    • New usage index at mint time: 1e27.

    • Underlying mint amount: 100 tokens.

Flawed Calculation Steps

  1. Retrieve Scaled Balance:
    The balanceOf returns:

    scaledBalance = 1,000 tokens * 1e27 / 1e27 = 1,000 tokens

    (Note: The scaling here is conceptual; actual values are in 27-decimal precision.)

  2. Calculate Balance Increase:
    The function calculates:

    balanceIncrease = scaledBalance.rayMul(1e27) - scaledBalance.rayMul(0.95e27)

    Since scaledBalance is already scaled, applying rayMul again causes the increase to be exaggerated. Instead of accurately capturing the accrued interest for the difference between 0.95e27 and 1e27, the arithmetic magnifies the balance increase.

  3. Mint Excess Tokens:
    The excess balanceIncrease is added to the mint amount (100 tokens), resulting in more tokens minted than the 100 underlying tokens justify.

Outcome

Due to the double scaling, the borrower’s new debt balance is inflated, and the total debt supply is over-reported, distorting the intended interest accrual mechanism.

Tools Used

Manual review

Recommendations

Revised Mint Function

To resolve the issue, the calculation should reference the underlying stored balance without the extra scaling. One possible mitigation is as follows:

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
// Calculate the amount in its scaled (raw) form without applying usage index.
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
// Retrieve the raw stored balance, not the normalized balance.
uint256 rawBalance = super.balanceOf(onBehalfOf);
bool isFirstMint = rawBalance == 0;
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
// Adjust balance increase using the raw balance and the difference in indices.
balanceIncrease = rawBalance.rayMul(index) - rawBalance.rayMul(_userState[onBehalfOf].index);
}
_userState[onBehalfOf].index = index.toUint128();
uint256 amountToMint = amount + balanceIncrease;
_mint(onBehalfOf, amountToMint.toUint128());
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
return (isFirstMint, amountToMint, totalSupply());
}

Explanation of Changes

  • Use Raw Balance:
    The revised implementation uses super.balanceOf(onBehalfOf) to get the raw balance, avoiding the extra scaling applied in the overridden balanceOf method.

  • Correct Balance Increase Calculation:
    The balance increase now reflects only the change in the usage index from the stored value to the new index, without compounding an already scaled balance.

Updates

Lead Judging Commences

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

DebtToken::mint miscalculates debt by applying interest twice, inflating borrow amounts and risking premature liquidations

Support

FAQs

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

Give us feedback!