Core Contracts

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

Double counting of interest in DebtToken

Summary

Upon mint being called, interest is unnecessarily double counted.

Vulnerability Details

In my opinion the best way to look at this vulnerability is to initially look at how userTotalDebt is calculated. In LendingPool::borrow userTotalDebt is calculated through:
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;

Therefore, it is clear that when user debt is calculated, the scaled balance is taken, and then it multiplied by the usage index to accout for interest accrual.
However, the logic in DebtToken::mint contradicts this logic.
https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/tokens/DebtToken.sol#L154-L160

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;

This is from DebtToken::mint, this function calculates the balanceIncrease and adds it to amountToMint. The additional minting gives extra debt to the user. This increase should not be added onto amountToMint, this is managed correctly in RToken::mint https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/tokens/RToken.sol#L102-L141 but here it is not managed correctly.

Also, this is clearly wrong because this code is inspired by aave v3, and aave is a highly audited protocol that doesn't add the balanceIncrease onto amountToMint. balanceIncrease is only used for events purposes.
Here's aave's code:

/**
* @notice Implements the basic logic to mint a scaled balance token.
* @param caller The address performing the mint
* @param onBehalfOf The address of the user that will receive the scaled tokens
* @param amount The amount of tokens getting minted
* @param index The next liquidity index of the reserve
* @return `true` if the the previous balance of the user was 0
*/
function _mintScaled(
address caller,
address onBehalfOf,
uint256 amount,
uint256 index
) internal returns (bool) {
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
uint256 scaledBalance = super.balanceOf(onBehalfOf);
uint256 balanceIncrease = scaledBalance.rayMul(index) -
scaledBalance.rayMul(_userState[onBehalfOf].additionalData);
_userState[onBehalfOf].additionalData = index.toUint128();
_mint(onBehalfOf, amountScaled.toUint128());
uint256 amountToMint = amount + balanceIncrease;
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0);
}

Also now just to further make this issue clear, here is an example.
(in the context of calling mint) let:
(parameter) amount = 100e18
scaledBalance = 500e18
index = 1.02e27
_userState[onBehalfOf].index = 1.01e27

balanceIncrease = 500e18.rayMul(1.02e27) - 500e18.rayMul(1.01e27) = 5e18
amountToMint = 100e18 + 5e18 = 105e18
_mint(msg.sender, 105e18)
now the msg.sender has a balance of 605e18

Then during this calculation in LendingPool::borrow:
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
Ignoring the additional amount variable the 500e18 scaledBalance value is scaled in this function too with the most recent reserve.usageIndex value, therefore, the debt is double counted due to:

  1. the scaling here with the usage index

  2. the balanceIncrease being added through the mint function

As a result, userTotalDebt is higher than it should be, preventing users from borrowing as much as they should be entitled to, or even resulting in early unfair liquidations and a loss of user funds.

Impact

Depositors have less ability to borrow against their nft due to their increased debt balance. Here is where the borrows would be prevented (LendingPool::borrow).

if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

Tools Used

Manual review

Recommendations

Don't add the balanceIncrease to amountToMint, alike what is done in RToken::mint.

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

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!