Core Contracts

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

Stale Oracle Price Data Used Without Validation in NFT Price Feed

Description

The lending protocol's getNFTPrice function fails to validate the staleness of price data retrieved from the oracle. While the oracle returns both a price and timestamp, the function only checks if the price is non-zero, ignoring the timestamp completely. This oversight is particularly critical as this function is used in core protocol functions that determine collateral valuations and withdrawal permissions, potentially leading to significant financial risks.

Affected code

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L588-L600

function getNFTPrice(uint256 tokenId) public view returns (uint256) {
(uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
return price;
}

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L560-L576

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);
totalValue += price;
}
return totalValue;
}

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L285-L320

function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
// ... other checks ...
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId);
if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
// ... withdrawal logic ...
}

Vulnerability details

The vulnerability becomes especially dangerous in two critical protocol functions that rely on getNFTPrice:

  1. Collateral Valuation (getUserCollateralValue)

    // Current implementation without staleness check
    for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
    uint256 price = getNFTPrice(tokenId); // Could use stale prices
    totalValue += price;
    }

    Impact:

    • Multiple stale prices could compound into severely misreported total collateral value

    • If a user has 5 NFTs and each price is stale by -20%, their $100k position could actually be worth $80k

    • This affects all protocol functions that rely on collateral valuations

  2. NFT Withdrawals (withdrawNFT)

    collateralValue = getUserCollateralValue(msg.sender); // Potentially stale
    nftValue = getNFTPrice(tokenId); // Also potentially stale
    if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold))

    Real-world scenario:

    Actual market conditions:
    - User has 2 NFTs: NFT A (worth 70 ETH) and NFT B (worth 30 ETH)
    - Total collateral = 100 ETH
    - User debt = 60 ETH
    - Liquidation threshold = 80%
    - Minimum required collateral = 75 ETH
    With stale prices (20% higher):
    - System sees: NFT A (84 ETH) and NFT B (36 ETH)
    - System calculates total collateral = 120 ETH
    - System allows withdrawal of NFT A thinking 36 ETH collateral remains
    - Actually only 30 ETH collateral remains, position should be liquidated

Tools Used

Manual Review

Recommended Mitigation Steps

  1. Add staleness validation to getNFTPrice:

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

Lead Judging Commences

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

Give us feedback!