Core Contracts

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

Global timestamp update in RAACHousePrices leads to use of stale prices in `LendingPool` contract

Summary

The RAACHousePrices contract uses a global lastUpdateTimestamp that gets overwritten each time any token's price is updated. This causes all previously updated token prices to incorrectly report their last update time as the most recent update time, potentially masking stale prices in the lending protocol's risk calculations.

Vulnerability Details

In RAACHousePrices::setHousePrice, the global lastUpdateTimestamp is updated every time a token's price is set:

function setHousePrice(uint256 _tokenId, uint256 _amount) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
@> lastUpdateTimestamp = block.timestamp; // @audit overwrites timestamp for all tokens
emit PriceUpdated(_tokenId, _amount);
}

This means when getLatestPrice is called, it returns the most recent update time of any token, not the specific time when the queried token's price was last updated.

Proof of Concept

  1. Oracle updates token 1's price:

    setHousePrice(1, 100); // at t=1000
    getLatestPrice(1); // returns (100, 1000) ✓ correct
  2. Oracle updates token 2's price:

    setHousePrice(2, 200); // at t=2000
    getLatestPrice(1); // returns (100, 2000) ✗ wrong timestamp
    getLatestPrice(2); // returns (200, 2000) ✓ correct
  3. LendingPool uses stale price without knowing:

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

Impact

Stale prices may be used in critical functions:

  • In LendingPool::getUserCollateralValue, collateral could be overvalued

  • In LendingPool::withdrawNFT, may allow withdrawals based on stale valuations

  • In LendingPool::calculateHealthFactor, could delay liquidations using outdated prices

Tools Used

Manual review

Recommendations

Track update timestamps per token using a dedicated mapping:

contract RAACHousePrices is Ownable {
mapping(uint256 => uint256) public tokenToHousePrice;
+ mapping(uint256 => uint256) public tokenToLastUpdate;
- uint256 public lastUpdateTimestamp;
function setHousePrice(uint256 _tokenId, uint256 _amount) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
- lastUpdateTimestamp = block.timestamp;
+ tokenToLastUpdate[_tokenId] = block.timestamp;
emit PriceUpdated(_tokenId, _amount);
}
function getLatestPrice(uint256 _tokenId) external view returns (uint256, uint256) {
- return (tokenToHousePrice[_tokenId], lastUpdateTimestamp);
+ return (tokenToHousePrice[_tokenId], tokenToLastUpdate[_tokenId]);
}
}
Updates

Lead Judging Commences

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

RAACHousePrices uses a single global lastUpdateTimestamp for all NFTs instead of per-token tracking, causing misleading price freshness data

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

RAACHousePrices uses a single global lastUpdateTimestamp for all NFTs instead of per-token tracking, causing misleading price freshness data

Support

FAQs

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