Core Contracts

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

Missing Stale Price Checks in Oracle and Lending Pool Enable Undercollateralized Borrowing and Protocol Insolvency Risk

Summary

The protocol's core collateral valuation system fails to validate price data freshness, allowing critical financial operations to execute using obsolete NFT valuations. This affects both the price oracle (RAACHousePrices) and lending pool (LendingPool), enabling undercollateralized borrowing, invalid liquidations, and premature NFT withdrawals. The absence of timestamp checks creates systemic risk where delayed price updates could be exploited to manipulate loan positions and protocol solvency, representing a high-severity vulnerability in the protocol's risk management framework.

Vulnerability Details

The protocol lacks critical staleness checks for oracle price updates in two key locations:

  1. Oracle Contract Price Retrieval
    The RAACHousePrices.getLatestPrice function (RAACHousePrices.sol#L34-L38) returns price data without validating the freshness of the information. This allows the protocol to operate using arbitrarily outdated price values.

function getLatestPrice(
uint256 _tokenId
) external view returns (uint256, uint256) {
@> return (tokenToHousePrice[_tokenId], lastUpdateTimestamp);
}
  1. Lending Pool Price Validation
    The LendingPool.getNFTPrice function (LendingPool.sol#L591-L595) consumes oracle data without performing secondary staleness verification, despite being responsible for critical collateral valuation in borrowing/liquidation operations.

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

This dual-layer failure enables several dangerous scenarios:

  • Loans issued against collateral valued using obsolete prices

  • Liquidations triggered/avoided based on inaccurate valuations

  • Withdrawals permitted using deprecated NFT valuations

The absence of timestamp validation in both the data source (oracle) and data consumer (lending pool) violates fundamental oracle security practices, leaving the protocol vulnerable to price manipulation through delayed updates.

Impact

This vulnerability creates systemic risk across the protocol's core financial mechanisms, with severe consequences:

  1. Collateral Valuation Failures
    Outdated NFT prices enable undercollateralized borrowing, exposing lenders to bad debt accumulation when collateral value declines unreported.

contract LendingPool is ILendingPool, Ownable, ReentrancyGuard, ERC721Holder, Pausable {
// ...
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;
}
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// ...
@> uint256 collateralValue = getUserCollateralValue(msg.sender);
@> if (collateralValue == 0) revert NoCollateral();
// ...
// 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();
}
// ...
}
}
  1. Withdrawal Exploitation
    The LendingPool.withdrawNFT function uses stale prices to evaluate collateralization ratios, allowing withdrawals of NFTs while their real-world value has dropped below safe thresholds.

contract LendingPool is ILendingPool, Ownable, ReentrancyGuard, ERC721Holder, Pausable {
// ...
function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
// ...
// Check if withdrawal would leave user undercollateralized
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
@> uint256 collateralValue = getUserCollateralValue(msg.sender);
@> uint256 nftValue = getNFTPrice(tokenId);
@> if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
// ...
}
}
  1. Liquidation Mechanism Breakdown
    Liquidations may:

    • Fail to trigger when collateral values drop below safe thresholds

    • Wrongly liquidate positions still above water due to stale high prices

contract LendingPool is ILendingPool, Ownable, ReentrancyGuard, ERC721Holder, Pausable {
// ...
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;
}
function initiateLiquidation(address userAddress) external nonReentrant whenNotPaused {
if (isUnderLiquidation[userAddress]) revert UserAlreadyUnderLiquidation();
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
UserData storage user = userData[userAddress];
@> uint256 healthFactor = calculateHealthFactor(userAddress);
@> if (healthFactor >= healthFactorLiquidationThreshold) revert HealthFactorTooLow();
isUnderLiquidation[userAddress] = true;
liquidationStartTime[userAddress] = block.timestamp;
emit LiquidationInitiated(msg.sender, userAddress);
}
}
  1. Price Manipulation Vulnerability
    Malicious actors could exploit delayed price updates to:

    • Take oversized loans against temporarily overvalued NFTs

    • Avoid legitimate liquidations during price declines

    • Artificially inflate protocol TVL metrics

  2. Protocol Insolvency Risk
    Accumulation of improperly collateralized loans could lead to cascading defaults, threatening the entire system's solvency during market volatility.

This vulnerability represents a high severity risk as it enables direct value extraction through delayed price updates while undermining core protocol solvency safeguards.

Tools Used

Manual Review

Recommendations

Implement a two-layer staleness verification system:

  1. Oracle-Level Enforcement
    Add time validation to RAACHousePrices.getLatestPrice:

    require(block.timestamp - lastUpdateTimestamp <= MAX_PRICE_AGE, "Stale price");

    Implement token-specific timestamp tracking rather than global lastUpdateTimestamp.

  2. Consumer-Level Validation
    Enhance LendingPool.getNFTPrice with:

    if (block.timestamp - lastUpdateTimestamp > ALLOWED_STALE_TIME) {
    revert PriceDataExpired();
    }
Updates

Lead Judging Commences

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