Core Contracts

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

`RAACHousePrices` provides incorrect data for house price freshness

Summary

The RAACHousePrices contract maintains and returns a global lastUpdateTimestamp for all houses, instead of individual ones. This results in incorrect price freshness data when querying prices for individual houses, as the queried price might be stale.

Vulnerability Details

The RAACHousePrices contract makes use of the RAACHousePriceOracle to maintain up to date house price data. House prices are updated by sending a request to the oracle. When the request returns with a response, it ultimately calls RAACHousePriceOracle#_processResponse, then sets the latest house price in RAACHousePrices#setHousePrice.

There's what setHousePrice looks like:

function setHousePrice(
uint256 _tokenId,
uint256 _amount
) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
lastUpdateTimestamp = block.timestamp;
emit PriceUpdated(_tokenId, _amount);
}

Notice how it sets a global lastUpdateTimestamp. Whenever setHousePrice is called, regardless of _tokenId (in other words, regardless of what house we're dealing with), it always updates the lastUpdateTimestamp.

This is problematic, because later, the protocol will make use of RAACHousePrices#getLatestPrice which returns the house price for a given _tokenId including that global lastUpdateTimestamp as indicator when the price was last updated.

function getLatestPrice(
uint256 _tokenId
) external view returns (uint256, uint256) {
return (tokenToHousePrice[_tokenId], lastUpdateTimestamp);
}

However, given that the lastUpdateTimestamp is not tracked for every house (_tokenId) individually, it will provide incorrect freshness data for houses prices that haven't been updated in a longer time.

Impact

The LendingPool makes use of this in its getNFTPrice function, which is critical to the protocol to figure out whether or not a debt position is undercollateralized. The function might return a price that's very outdated, possibly higher than what it really is, resulting in risky debt positions.

The calculated health factor could be incorrect, which also ultimately prevents accounts from being liquidated, leading to more overall risk in the protocol for both, lenders and other borrowers.

Tools Used

Manual review.

Recommendations

Ensure data for house price freshness is tracked for every _tokenId (house) individually, such that freshness checks can be performed on individual properties.

Here's what this could look like:

contract RAACHousePrices is Ownable {
+ struct PriceData {
+ uint256 price;
+ uint256 lastUpdateTimestamp;
+ }
- mapping(uint256 => uint256) public tokenToHousePrice;
+. mapping(uint256 => PriceData) public tokenToHousePrice;
address public oracle;
- uint256 public lastUpdateTimestamp;
function getLatestPrice(
uint256 _tokenId
) external view returns (uint256, uint256) {
- return (tokenToHousePrice[_tokenId], lastUpdateTimestamp);
+ PriceData memory data = tokenPriceData[_tokenId];
+ return (data.price, data.lastUpdateTimestamp);
}
function setHousePrice(
uint256 _tokenId,
uint256 _amount
) external onlyOracle {
- tokenToHousePrice[_tokenId] = _amount;
- lastUpdateTimestamp = block.timestamp;
+ tokenPriceData[_tokenId] = PriceData({
+ price: _amount,
+ lastUpdateTimestamp: block.timestamp
+ });
emit PriceUpdated(_tokenId, _amount);
}
...
}

Relevant links

The following functions are either relevant or directly affected by this bug:

Updates

Lead Judging Commences

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