Core Contracts

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

[H] Incorrect Debt Calculation in `mint` Function of `DebtToken` Contract

Summary

The mint function in the DebtToken contract incorrectly calculates the amount of debt tokens to mint by including the interest accrued from the previous debt. This can lead to over-minting of debt tokens, resulting in an inaccurate representation of the user's total debt.

Vulnerability Details

The current mint function in the DebtToken contract is:

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());
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
if (
_userState[onBehalfOf].index != 0 &&
_userState[onBehalfOf].index < index
) {
balanceIncrease =
scaledBalance.rayMul(index) -
scaledBalance.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 (scaledBalance == 0, amountToMint, totalSupply());
}

Explanation

The mint function calculates the amountToMint as the sum of the new debt (amount) and the interest accrued from the previous debt (balanceIncrease). However, this approach is incorrect because the total debt of a user should be calculated as D(t) = A * R(t), where A is the normalized (scaled) principal amount and R(t) is the usage index.

To correctly calculate the total debt, we need to consider the following:

  1. Current Debt Calculation: The total debt of a user(including interest accrued) is represented by the debt tokens, which should reflect the normalized debt (balanceOf Scales the user balance by current UsageIndex).

  2. New Debt Calculation: When a user takes on new debt, the total debt should be updated to include the new debt, scaled by the current usage index.

The correct approach is to calculate the total debt as follows:

  • Old Debt: oldDebt = scaledBalance * currentUsageIndex

  • New Debt: newDebt = amount

  • Total Debt: totalDebt = oldDebt + newDebt

Since we store the normalized debt (scaled value), we need to scale the total debt by the current usage index:

  • Normalized Total Debt: normalizedTotalDebt = (oldDebt + newDebt) / currentUsageIndex

Given that the user already has oldDebt as debtToken Balance, we only need to mint the additional debt tokens for the new debt:

  • Amount to Mint: amountToMint = newDebt(scaling is done at the _update function)

_update function in the DebtToken is

function _update(
address from,
address to,
uint256 amount
) internal virtual override {
if (from != address(0) && to != address(0)) {
revert TransfersNotAllowed(); // Only allow minting and burning
}
uint256 scaledAmount = amount.rayDiv(
ILendingPool(_reservePool).getNormalizedDebt()
);
super._update(from, to, scaledAmount);
emit Transfer(from, to, amount);
}

Example Scenario

Consider the following scenario (Referenced from the MakerDAO Rates Module [https://docs.makerdao.com/smart-contract-modules/rates-module]):

  • At time t0, a user has an existing debt of 20 DAI with a usage index of 1.

  • After 12 years, the usage index increases to 1.5, and the user's debt grows to 30 DAI due to interest accrual.

  • At this point, the user takes on an additional 10 DAI of debt.

The total debt should be calculated as:

  • Old Debt: oldDebt = 20 * 1.5 = 30 DAI

  • New Debt: newDebt = 10 DAI

  • Total Debt: totalDebt = 30 + 10 = 40 DAI

  • Normalized Total Debt: normalizedTotalDebt = 40 / 1.5 ≈ 26.67 DAI

The amount to mint should be:

  • Amount to Mint: amountToMint = 10 / 1.5 ≈ 6.67 DAI

However, the current implementation would incorrectly add the interest accrued from the previous debt (balanceIncrease) to the new debt amount, resulting in over-minting.

Links:

  1. https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L160

Impact

This issue can lead to over-minting of debt tokens, resulting in an inaccurate representation of the user's total debt. This can cause discrepancies in the protocol's accounting and potentially lead to financial losses.

Tools Used

Manual code review.

Recommendations

Update the mint function to correctly calculate the amount of debt tokens to mint by considering the normalized debt. The corrected function should be:

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());
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
...
uint256 amountToMint = amountScaled;
_mint(onBehalfOf, amountToMint.toUint128());
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0, amountToMint, totalSupply());
}

This ensures that the amount of debt tokens minted accurately reflects the user's total debt, preventing over-minting and maintaining the integrity of the protocol's accounting.

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

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.