Core Contracts

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

A user can borrow more than their collateral can back an attacker can use this to steal funds/ take loans to never be repaid

Summary

Presently a user can borrow more funds than their collateral valuation can support, this allows an attacker to drain the contract balance by depositing collaterals and taking 20% or more of the valuation of their collateral.

Vulnerability Details

A user can borrow by calling the borrow function

/**
* @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
@audit>> _ensureLiquidity(amount);
@audit>> // Fetch user's total debt after borrowing
@audit>> uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
@audit>> // Ensure the user has enough collateral to cover the new debt // like you just look at this i can offer less collaterl and get full amount like 20% worth more
@audit>> 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); // home free
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);
}
  1. Collateral Value 1000 USD

  2. Debt to open 1250 USD

  3. Health check for this will fail normally 800/1250 = 0.64 e18 less than 1e18

  4. This position should never open

  5. But The check

  6. 1000 USD < 1250 * 80 /100

  7. 1000 USD < 1000 USD

  8. This check passes with an health factor of 0.64e18. This position is immediately open to liquidation

  9. For a deliberate attack an attacker has made an extra 25% profit from his collateral

  10. This can be used to drain the contract creating multiple bad debt position and selling an NFT over 25% of it actual value.

LOOK at how Aave checks solvency =>

require(vars.userCollateralInBaseCurrency != 0, Errors.COLLATERAL_BALANCE_IS_ZERO);
require(vars.currentLtv != 0, Errors.LTV_VALIDATION_FAILED);
require(
vars.healthFactor > HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
vars.amountInBaseCurrency =
IPriceOracleGetter(params.oracle).getAssetPrice(
vars.eModePriceSource != address(0) ? vars.eModePriceSource : params.asset
) *
params.amount;
unchecked {
vars.amountInBaseCurrency /= vars.assetUnit;
}
//add the current already borrowed amount to the amount requested to calculate the total collateral needed.
@audit>> we use div not mul >>> vars.collateralNeededInBaseCurrency = (vars.userDebtInBaseCurrency + vars.amountInBaseCurrency)
.percentDiv(vars.currentLtv); //LTV is calculated in percentage
@audit>> require(
vars.collateralNeededInBaseCurrency <= vars.userCollateralInBaseCurrency,
Errors.COLLATERAL_CANNOT_COVER_NEW_BORROW
);

Impact

An attacker can drain the asset in the contract by selling their NFT immediately by borrowing for a worth above 25% of the actual market value this causes a big loss for liquidity providers.

Tools Used

Manual Review

Recommendations

Correct the solvency check

// Ensure the user has enough collateral to cover the new debt // like you just look at this i can offer less collaterl and get full amount like 20% worth more
++ if (collateralValue < userTotalDebt.percentDiv(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
Updates

Lead Judging Commences

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