Core Contracts

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

Incorrect updation of user.scaledDebtBalance leads to incorrect debt calculation

Summary

The debt accrued by a user is supposed to be stored in both the debttoken.balanceof(user) and the user.scaledDebtBalance(both are supposed to be the same). But when borrowing the user.scaledDebtBalance is updated incorrectly. And further in the contract this incorrect user.scaledDebtBalance is used for debt calculation. Thus incorrect amount of debt will be paid by borrowers.

Vulnerability Details

In the borrow function, the debt token minted can be greater than the amountScaled which is added to the user.scaledDebtBalance. This happens when a borrower borrows again. During minting instead of the usual amount being minted, an additional balanceIncrease is also minted. The user.scaledDebtBalance fails to consider this. Thus during all further calculations the debt used is wrong. This can be understood when a user tries to repay his debt, even after repaying the full user.scaledDebtBalance the user will have remaining debt stored in his debtToken.balanceof(user).

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();
=> //Note: amount minted is incremented by balanceIncrease (which is not considered in the user.scaledDebtBalance)
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());
}
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
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
// Update user's scaled debt balance
uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
// Mint DebtTokens to the user (scaled amount)
(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);
=> // Note that the amount minted in the debt token is different from amount.rayDiv(usageIndex)
user.scaledDebtBalance += scaledAmount;
reserve.totalUsage = newTotalSupply;

Note: the assumption that the debtToken.balanceof(user) is the user's debt is further strengthend by the fact that the totalUsage is updated as the newTotalSupply. totalUsage is the total debt of all the borrowers combined.

This has many impacts wherever the user.scaledDebtBalance is used : 1 of them is that the users cant ever repay the full debt. Because the user.scaledDebtBalance will try to go negative (txn will revert if user tries).

Impact

The borrowers will have to pay less than what the protocol intended.

Tools Used

manual review

Recommendations

update the user.scaledDebtBalance with amountMinted instead of the scaledAmount.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::borrow tracks debt as user.scaledDebtBalance += scaledAmount while DebtToken mints amount+interest, leading to accounting mismatch and preventing full debt repayment

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!