Core Contracts

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

Inconsistent unit handling in mint interest accrual

Summary

In the mint function of the DebtToken.sol contract, the calculation of the total amount to mint is performed as follows:

uint256 amountToMint = amount + balanceIncrease;

Here, amount represents the new debt in underlying asset units, while balanceIncrease is computed as:

balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);

The issue is that these two values are derived from different unit bases—mixing underlying and scaled units—which leads to an inconsistent calculation of the debt tokens to be minted.

Vulnerability Details

  • Mismatched Units:

    • The variable amount is provided in underlying asset units and is later converted to a scaled amount using rayDiv(index) (although that result is only used for a zero-check).

    • The balanceIncrease is calculated based on scaledBalance, which is obtained by calling the overridden balanceOf function. This function returns the underlying debt by multiplying the stored scaled balance by the current normalized debt.

    • As a result, adding amount (underlying units) to balanceIncrease (effectively derived from an already scaled balance) leads to an inconsistent total that does not properly reflect either the true scaled value or the underlying amount.

  • Interest Accrual Inaccuracy:
    The intention is to account for accrued interest on the user's previous debt by increasing their minted amount. However, because of the unit mismatch, the accrued interest is either over- or under-estimated, which can result in:

    • Undercharging Interest: Users may not be charged the correct interest on their outstanding debt.

    • Overcharging Interest: Alternatively, the protocol might mint too many tokens, disrupting the debt accounting mechanism.

  • Potential Downstream Effects:
    This inconsistency can lead to errors in overall debt accounting, inaccurate total supply calculations, and potential vulnerabilities where users might exploit the discrepancy to reduce their effective repayment obligations.

Impact

  • Debt Accounting Errors:
    Inaccurate minting due to mixed unit calculations can lead to significant discrepancies in users’ debt positions and the protocol’s overall debt supply.

  • Economic Exploitation:
    If interest accrual is miscalculated, borrowers might repay less than their true debt, potentially undermining the revenue model of the lending protocol.

Tools Used

Manual review

Recommendations

  1. Consistent Unit Usage:
    Ensure that all calculations in the mint function are performed using the same unit basis. For instance, if the stored balances are in scaled units, then both the new mint amount and the accrued interest should be calculated in scaled units:

    // Retrieve the stored scaled balance directly (not the underlying balance)
    uint256 storedScaledBalance = super.balanceOf(onBehalfOf); // Raw scaled balance
    // Calculate accrued interest in scaled units
    uint256 scaledBalanceIncrease = storedScaledBalance.rayMul(index) - storedScaledBalance.rayMul(_userState[onBehalfOf].index);
    // Convert the new amount to be minted into scaled units
    uint256 amountScaled = amount.rayDiv(index);
    // Total scaled amount to mint
    uint256 totalScaledToMint = amountScaled + scaledBalanceIncrease;
  2. Correct Minting Process:
    Use the scaled amounts for the _mint call so that the ERC20 internal bookkeeping remains consistent:

    _mint(onBehalfOf, totalScaledToMint.toUint128());

    This ensures that the underlying debt (when later computed via the overridden balanceOf) correctly reflects both the new debt and the accrued interest.

Below is a detailed proof‐of‐concept that shows—with concrete numbers—how the calculation

uint256 amountToMint = amount + balanceIncrease;

leads to an over‐minting of debt tokens due to a unit mismatch between “underlying” and “scaled” values.


Proof of Concept (POC)

Assumptions & Setup

  • Scaling Factor (“RAY”):

  • Usage/Interest Index:

    • Old index:

    • New index:
      (This represents a doubling of the interest index.)

  • Normalized Debt:
    We assume that the LendingPool’s normalized debt equals the current usage index.

    • When index is , normalized debt = .

    • When index is , normalized debt = .

  • User’s Initial Borrow:
    The user initially mints a debt of 100 underlying units at the old index.

    • The conversion in a correct implementation would be:
      $$
      \text{scaled amount} = \text{amount}.\texttt{rayDiv(oldIndex)} \quad \Rightarrow \quad 100.\texttt{rayDiv}(1e27) \approx 100
      $$

    • The contract stores an internal “scaled” balance of 100.

    • When queried via the overridden balanceOf, the underlying debt is computed as:
      $$
      \text{underlying} = \text{scaled balance}.\texttt{rayMul(normalizedDebt)} \quad \Rightarrow \quad 100.\texttt{rayMul}(1e27) = 100
      $$

Interest Accrual

Time passes and interest accrues so that the new usage index becomes .

  • The user’s existing scaled balance remains at 100.

  • However, when recalculated using the new normalized debt, the underlying debt becomes:
    $$
    100.\texttt{rayMul}(2e27) = \frac{100 \times 2e27}{1e27} = 200 \quad \text{underlying units.}
    $$

This means that due solely to interest accrual, the user’s outstanding debt should have grown from 100 to 200 underlying units.

New Mint Operation (Adding New Debt)

Now, the user borrows an additional 100 underlying units at the new index (\left(2 \times 10^{27}\right)).

Correct Calculation (Expected Behavior):

  1. Convert the new amount to scaled units:
    $$
    \text{newScaled} = 100.\texttt{rayDiv}(2e27) \approx 50.
    $$

  2. Update the stored scaled balance:
    $$
    \text{New internal scaled balance} = \text{Old scaled balance} + \text{newScaled} = 100 + 50 = 150.
    $$

  3. Compute the new underlying debt:

    Interpretation:

    • The original 100 (minted at the old index) grows to 200 under the new index,

    • Plus the newly borrowed 100 gives a total of 300 underlying units.

    This is the expected correct behavior.

Buggy Calculation (Using the Existing Code):

Inside the buggy mint function, the following steps occur:

  1. Compute amountScaled:
    (Though calculated, it is not used for minting.)
    $$
    \text{amountScaled} = 100.\texttt{rayDiv}(2e27) \approx 50.
    $$

  2. Retrieve the current underlying debt:
    The function calls balanceOf(onBehalfOf), which returns:
    $$
    \text{scaledBalance (underlying)} = 100.\texttt{rayMul}(2e27) = 200.
    $$

  3. Calculate the accrued interest (balanceIncrease):
    Using:

    balanceIncrease = scaledBalance.rayMul(2e27) - scaledBalance.rayMul(1e27);

    Compute each term:


    • Therefore:
      $$
      \text{balanceIncrease} = 400 - 200 = 200.
      $$

  4. Add the new amount and accrued interest:
    $$
    \text{amountToMint} = \text{amount} + \text{balanceIncrease} = 100 + 200 = 300.
    $$

  5. Call _mint:
    The contract mints 300 tokens (in its internal “scaled” units) for the user.

  6. Update the user’s internal scaled balance:
    The new stored scaled balance becomes:
    $$
    \text{Old scaled balance} + 300 = 100 + 300 = 400.
    $$

  7. Recalculate the underlying debt:
    When queried via balanceOf, it becomes:
    $$
    400.\texttt{rayMul}(2e27) = \frac{400 \times 2e27}{1e27} = 800 \quad \text{underlying units.}
    $$

Summary of the Mismatch:

  • Expected:

    • New internal scaled balance: 150

    • Underlying debt: (150 \times 2 = 300)

  • Buggy (Actual):

    • New internal scaled balance: 400

    • Underlying debt: (400 \times 2 = 800)

This means the user ends up with an outstanding debt of 800 underlying units instead of the correct 300, effectively overcharging the user by a factor of more than 2.5×.


Conclusion

The bug arises because the function mixes underlying and scaled units:

  • amount is provided in underlying units.

  • balanceIncrease is derived from the user’s underlying debt (which already factors in interest via the normalized debt).

  • Adding these together results in a value that is then treated as if it were in scaled units when calling _mint.

This unit mismatch causes the protocol to mint far more tokens than intended—dramatically inflating the borrower’s debt. An attacker or even an honest user interacting with the protocol would see their debt balloon incorrectly, leading to both inaccurate accounting and potential economic exploitation.


By following the above POC with concrete numbers, it’s clear that the calculation of

uint256 amountToMint = amount + balanceIncrease;

is incorrect and must be revised to consistently operate in one unit (preferably scaled units) to prevent over‐minting.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.