Core Contracts

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

Miscalculation in LendingPool can make raacNFT owners become insolvent in borrow function.

Summary

Wrong calculation in LendingPool::borrow function leads to users being able to borrow 20% more value in reserve assets than their nft collateral value. This makes the user insolvent, giving the protocol bad debt while the user goes away with his 20% profit.

Vulnerability Details

In LendingPool::initiateLiquidation function we check to se if a users healthFactor is below liquidationThreshold. We do this by calling the calculateHealthFactor function of the user. This function gets the users debt and 80% of their collateral value to be collateralThreshold used in Health factor calculations:

function calculateHealthFactor(address userAddress) public view returns (uint256) {
uint256 collateralValue = getUserCollateralValue(userAddress);
uint256 userDebt = getUserDebt(userAddress);
if (userDebt < 1) return type(uint256).max;
uint256 collateralThreshold = collateralValue.percentMul(liquidationThreshold);
return (collateralThreshold * 1e18) / userDebt;
}

If userDebt in this function is more than 80% of their collateral, this function will return less than 1e18 meaning their position is liquidatable in the LendingPool::initializeLiquidation function:

uint256 healthFactor = calculateHealthFactor(userAddress);
@> if (healthFactor >= healthFactorLiquidationThreshold) revert HealthFactorTooLow(); // treshold is 1e18

This is all good, however in the LendingPool::borrow function users can borrow themself in debt beyond liquidatable all the way to insolvent! This is possible due to a miscalculation in the borrow function. What we should be doing in the borrow function is the same as in the initiateLiquidation function: scale down collateralValue to 80% and make sure debt is lower than that. Instead we do the opposite. We make sure 100% of the collateralValue is more than a 80% scaled down user debt:

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);
user.scaledDebtBalance += scaledAmount;
// reserve.totalUsage += amount;
reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
// Rebalance liquidity after borrowing
_rebalanceLiquidity();
emit Borrow(msg.sender, amount);
}

Due to this miscalculation in the if logic. Users can borrow a maximum of 20% above their collateralValue. Meaning they have gone past liquidatable, all the way to insolvent. Giving the protocol bad debt. The function then proceeds to mint the user the debt tokens and transfer the borrowed assets to the user.

Impact

raacNFT owners can borrow 20% more value than their collateral value giving the protocol bad debt.

POC

Lets say the user raacNFT is worth 100 USD.

  1. The user calls the LendingPool::depositNFT

  2. Then the user call LendingPool::borrow with amount == 119.

  3. The function then gives him 119 reserve assets.

  4. He or someone else starts LendingPool::initiateLiquidation

  5. He gets liquidated, loosing his 100 USD worth of raacNFT

  6. he is left with 119 reserve asset. Which leaves him in a profit of 19 USD.

Reccomened mitigation

change this line in LendingPool::borrow to make sure users cant borrow themself into liquidation

+ if (collateralValue.percentMul(liquidationThreshold) < userTotalDebt) {
- if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
Updates

Lead Judging Commences

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

LendingPool::borrow as well as withdrawNFT() reverses collateralization check, comparing collateral < debt*0.8 instead of collateral*0.8 > debt, allowing 125% borrowing vs intended 80%

Support

FAQs

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