Core Contracts

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

Wrong calculation of balance increase when a user adds/borrows more debt tokens

Summary

Whenever a user increments their borrowed funds the balance increase of the debt is minted to the users account as debt amount. But this is calculated wrongly and will lead to a wrong account of the debt amount interest.

Vulnerability Details

When a user borrows more

/**
* @notice Allows a user to borrow reserve assets using their NFT collateral
* @param amount The amount of reserve assets to borrow
*/
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (isUnderLiquidation[msg.sender]) revert CannotBorrowUnderLiquidation();
UserData storage user = userData[msg.sender];
uint256 collateralValue = getUserCollateralValue(msg.sender);
if (collateralValue == 0) revert NoCollateral();
// Update reserve state before borrowing
ReserveLibrary.updateReserveState(reserve, rateData);
// Ensure sufficient liquidity is available
_ensureLiquidity(amount);
// Fetch user's total debt after borrowing
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
// Ensure the user has enough collateral to cover the new debt
revert NotEnoughCollateralToBorrow();
}
// Update user's scaled debt balance
uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
// Mint DebtTokens to the user (scaled amount)
@audit>> (bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount); // home free
@audit>> user.scaledDebtBalance += scaledAmount; // increase debt
// reserve.totalUsage += amount;
reserve.totalUsage = newTotalSupply; // set new total supply
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount); // update rate amount taken away
// Rebalance liquidity after borrowing
_rebalanceLiquidity();
emit Borrow(msg.sender, amount);
}

During the minting process we call to obtain the scaled balance of the user to calculate his owed interest

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();
@audit>>> 1. uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
@audit>>> if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
@audit>>> 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());
}
/**

The actually debt is always calculated by multiplying scaled balance with index

see below

/**
* @notice Returns the scaled debt balance of the user
* @param account The address of the user
* @return The user's debt balance (scaled by the usage index)
*/
1. function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}

The balance of function returns the actual debt that we are owing and not the scaled balance/ debt.

When we try to obtain the balance increase we multiply the actual debt obtained by raymul index again which will now give us a completely wrong value

uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
@audit >>> using balance instead of scaled balance>> balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}

This will return a wrong amount to mint causing the system to mint the user more debt token than a user should hold, then they owe.

We are miniting a double interest

  1. user balance 1000 USD

  2. index 1

  3. rate now 1.1

  4. user calls to add 500 USD more debt tokens (borrow)

  5. balance of returns 1100 USD .

  6. but during the balance increase

  7. 1100(1.1) - 1100(1) = 210 USD instead of the 100 USD actual profit owed by the user.

Using Aave as a reference again , see implementation =>

/// @inheritdoc IStableDebtToken
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 rate
) external virtual override onlyPool returns (bool, uint256, uint256) {
MintLocalVars memory vars;
if (user != onBehalfOf) {
_decreaseBorrowAllowance(onBehalfOf, user, amount);
}
(, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(onBehalfOf);

All the calculations were done once with the scaled balance to obtain the balance change and the new principal

/**
* @notice Calculates the increase in balance since the last user interaction
* @param user The address of the user for which the interest is being accumulated
* @return The previous principal balance
* @return The new principal balance
* @return The balance increase
*/
function _calculateBalanceIncrease(
address user
) internal view returns (uint256, uint256, uint256) {
@audit>>>> uint256 previousPrincipalBalance = super.balanceOf(user);
if (previousPrincipalBalance == 0) {
return (0, 0, 0);
}
@audit>>>> uint256 newPrincipalBalance = balanceOf(user);
return (
previousPrincipalBalance,
@audit>>>> newPrincipalBalance,
@audit>>>> newPrincipalBalance - previousPrincipalBalance
);
}

The double mulray is wrong and will mint users more debt tokens than they owe

Impact

Wrong account and minting a user more debt tokens than they actually owe.

Tools Used

Manual review

Recommendations

Return the scaledbalance of and not the balanceof the user

-- uint256 scaledBalance = balanceOf(onBehalfOf);
++ uint256 scaledBalance = scaledBalanceOf(onBehalfOf);
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!