Core Contracts

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

Incorrect NFT prices returned by `LendingPool::getNFTPrice` and `tokenToHousePrice` leads to DOS and NFT purchases at insignificant amounts.

Vulnerability Details

The LendingPool::getNFTPrice gets price from the HousePriceOracle and directly returns it.

function getNFTPrice(uint256 tokenId) public view returns (uint256) {
// @audit price not scaled?
@> (uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
@> return price;
}

The issue is, this price is not scaled for use in the protocol which uses WAD | RAY precision values throught the functions.
In the LendingPool.test.js, it's been stated the oracle price returned should be changed. The tests only work because
house prices have been hardcoded to a wad precision value. The real prices will not be in WAD or RAY precision and direct use of it
would lead to problems,

// FIXME: we are using price oracle and therefore the price should be changed from the oracle.
await raacHousePrices.setHousePrice(1, ethers.parseEther("100"));

Meaning, it should be scaled appropriately for use. This would cause problems in the following functions that make use
of the price returned,

  1. borrow():

uint256 collateralValue = getUserCollateralValue(msg.sender); // not a scaled value
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
@> if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

It first calculates userTotalDebt in ray precision. Then it compares it against the collateralValue which calls getNFTPrice for the price. Both collateralValue & userTotalDebt are expected to be RAY precision. The borrow feature would be DOS-ed since initially, userTotalDebt (RAY) would always be > collateralValue (6 decimal | 8 decimal USD value). Initially, the user.scaledDebtBalance would be zero, but amount (should be coverted to RAY before it is summed with user.scaledDebtBalance which is another issue) added to it would make userTotalDebt go way beyond the unscaled collateralValue value resulting in DOS. As stated before,

2.withdrawNFT(): Uses it like this,

uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
// @audit collateralValue not scaled to ray or wad
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId); // @audit Not scaled to ray or wad
// @ audit unscaled values being compared to a ray precision value
@> if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}

If price returned isn't in ray precision, any other precision value would result in revert, since
it would be less than the userDebt which is 27 decimal ray precision.

3.getHealthFactor():

/**
* @notice Calculates the user's health factor
* @param userAddress The address of the user
* @return The health factor (in RAY)
*/
function calculateHealthFactor(address userAddress) public view returns (uint256) {
@> uint256 collateralValue = getUserCollateralValue(userAddress); // @ unscaled (not wad or ray)
uint256 userDebt = getUserDebt(userAddress); // @ ray precision
if (userDebt < 1) return type(uint256).max;
// @ unscaled
uint256 collateralThreshold = collateralValue.percentMul(liquidationThreshold);
// @audit results in low hf
@> return (collateralThreshold * 1e18) / userDebt;
}
function initiateLiquidation(address userAddress) external nonReentrant whenNotPaused {
...
...
uint256 healthFactor = calculateHealthFactor(userAddress);
// @ healthFactorLiquidationThreshold = 1e18 ~ wad precision
@> if (healthFactor >= healthFactorLiquidationThreshold) revert HealthFactorTooLow();
...
...
}

Incorrect healthfactor returned resulting in a lower hf than necessary resulting in user liquidated easily and undeservedly.

Furthermore, in RAACNFT.sol, NFT purchase happens at the price returned by tokenToHousePrice,

uint256 price = raac_hp.tokenToHousePrice(_tokenId);

If the price returned isn't properly scaled to be compared with underlyingToken amount decimals (usually 18), this would lead to
users minting NFTs at negligible price, and borrow RToken from LendingPool for a profits since their collateral that gets liquidated won't be worth much.

Impact

A DOS in all the functions stated, and negligible NFT prices break the core functionality of the protocol.

Tools Used

Manual Review

Recommendations

Either scale it to return wad precision value or RAY precision value since it's compared against userDebt which is a RAY-precision value.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!