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 about 1 month 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.