Core Contracts

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

Stale Price Usage in Lending Pool's Collateral Valuation System

Summary

The LendingPool contract retrieves NFT house prices without validating their staleness, potentially allowing borrowing and liquidation decisions based on outdated price data, which could lead to significant protocol risks.

Vulnerability Details

The LendingPool contract uses NFT house prices for critical operations including borrowing, liquidations, and health factor calculations. The getNFTPrice() function retrieves prices from the oracle but fails to validate whether these prices are fresh enough to be used, despite having access to the lastUpdateTimestamp:

/**
* @notice Gets the current price of an NFT from the oracle
* @param tokenId The token ID of the NFT
* @return The price of the NFT
*
@> * Checks if the price is stale
*/
// @audit it does not actually check if the price is stale
function getNFTPrice(uint256 tokenId) public view returns (uint256) {
(uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
return price;
}

This stale price issue becomes particularly dangerous because the getNFTPrice() function is used in several critical functions:

Through getUserCollateralValue():

function getUserCollateralValue(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
uint256 totalValue = 0;
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
uint256 price = getNFTPrice(tokenId); // potentially stale price
totalValue += price;
}
return totalValue;
}

Which is then used in critical functions like:

  • borrow() - for determining borrowing capacity

  • calculateHealthFactor() - for liquidation decisions

  • withdrawNFT() - for checking if withdrawal would leave user undercollateralized

The staleness vulnerability can be exploited in several scenarios:

  • When house prices (imagine a house has caught fire example California wild fires burn) drop sharply but oracle hasn't updated

  • During oracle downtime or technical issues or in cases of network congestion preventing timely updates

PoC

  1. Oracle sets NFT price to 100,000 USDC

  2. Market conditions cause actual value to drop to 60,000 USDC

  3. Oracle fails to update due to technical issues

  4. User borrows 80,000 USDC against the NFT (based on stale price)

  5. Protocol is now undercollateralized as the actual value is only 60,000 USDC

Impact

  • Protocol could become undercollateralized due to loans based on stale prices

  • Liquidations might be delayed or prevented due to incorrect health factor calculations

  • Users could withdraw NFTs when they should be locked for collateral

  • Overall protocol solvency at risk

Tools Used

Manual Review

Recommendations

Add staleness check in getNFTPrice():

function getNFTPrice(uint256 tokenId) public view returns (uint256) {
(uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
if (block.timestamp - lastUpdateTimestamp > PRICE_FRESHNESS_THRESHOLD)
revert StalePrice();
return price;
}
Updates

Lead Judging Commences

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

LendingPool::getNFTPrice or getPrimeRate doesn't validate timestamp staleness despite claiming to, allowing users to exploit outdated collateral values during price drops

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

LendingPool::getNFTPrice or getPrimeRate doesn't validate timestamp staleness despite claiming to, allowing users to exploit outdated collateral values during price drops

Support

FAQs

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