Core Contracts

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

Debt miscalculation lets users take excess loans against NFT collateral

Summary

LendingPool::borrow() allows users to borrow reserve assets using their NFT collateral, however the function incorrectly calculates the total debt of a user before allowing borrowing. The amount parameter, representing the new borrow amount, is not properly scaled before being added to the user's total debt, leading to an inaccurate debt calculation.

Vulnerability Details

In the borrow function, the total user debt is calculated as follows:

[contracts/core/pools/LendingPool/LendingPool.sol]
325 function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
326 if (isUnderLiquidation[msg.sender]) revert CannotBorrowUnderLiquidation();
327
328 UserData storage user = userData[msg.sender];
329
330 uint256 collateralValue = getUserCollateralValue(msg.sender);
331
332 if (collateralValue == 0) revert NoCollateral();
333
334 // Update reserve state before borrowing
335 ReserveLibrary.updateReserveState(reserve, rateData);
336
337 // Ensure sufficient liquidity is available
338 _ensureLiquidity(amount);
339
340 // Fetch user's total debt after borrowing
341 -> uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount; // @audit - amount is not scaled
342
343 // Ensure the user has enough collateral to cover the new debt
344 if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
345 revert NotEnoughCollateralToBorrow();
346 }
347
348 // Update user's scaled debt balance
349 uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
350
351
352 // Mint DebtTokens to the user (scaled amount)
353 (bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
354
355 // Transfer borrowed amount to user
356 IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
357
358 user.scaledDebtBalance += scaledAmount;
359 // reserve.totalUsage += amount;
360 reserve.totalUsage = newTotalSupply;
361
362 // Update liquidity and interest rates
363 ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
364
365 // Rebalance liquidity after borrowing
366 _rebalanceLiquidity();
367
368 emit Borrow(msg.sender, amount);
369 }

However user.scaledDebtBalance is stored in a scaled format (i.e., divided by usageIndex).

Adding amount directly without scaling results in an underestimated total debt. This could allow users to borrow more than their collateral allows, bypassing lending constraints.

Impact

Users are able to borrow more than their collateral allows, increasing the risk of insolvency.

Recommendations

Modify the total debt calculation to properly scale amount before adding it.

- uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
+ uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
+ uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + scaledAmount;
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.