The getNFTPrice() function claims to check for stale prices in its documentation but fails to implement any staleness checks.
Additionally, the oracle design uses a global lastUpdateTimestamp instead of per-token timestamps, which could lead to price manipulation and incorrect pricing for NFTs.
Two critical issues exist:
1. Missing Staleness Check:
```solidity
function getNFTPrice(uint256 tokenId) public view returns (uint256) {
(uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
return price; // @audit: lastUpdateTimestamp is never checked
}
```
2. Global Update Timestamp Design Flaw:
```solidity
contract RAACHousePrices is Ownable {
mapping(uint256 => uint256) public tokenToHousePrice;
uint256 public lastUpdateTimestamp; // @audit: global timestamp for all tokens
// ...
}
```
## Proof of Concept
1. Token A's price is updated at timestamp 100
2. Token B hasn't been updated since timestamp 50
3. When querying Token B's price, the system will return timestamp 100 (the global timestamp)
4. This makes Token B's stale price appear fresh, potentially leading to incorrect valuations // TODO: correct in context of valuations
High severity.
The vulnerability could lead to:
- Usage of stale or outdated price data for NFT transactions
- Potential price manipulation through outdated data
- Financial losses for users relying on incorrect pricing data
- One token update could make other tokens appear fresh when they're stale
VScode
1. Implement proper staleness checks:
```solidity
function getNFTPrice(uint256 tokenId) public view returns (uint256) {
(uint256 price, uint256 lastUpdateTimestamp) = priceOracle.getLatestPrice(tokenId);
if (price == 0) revert InvalidNFTPrice();
// Add staleness check
uint256 stalePriceThreshold = 24 hours;
if (block.timestamp - lastUpdateTimestamp > stalePriceThreshold) {
revert StalePriceData();
}
return price;
}
```
2. Modify the oracle to track per-token timestamps:
```solidity
contract RAACHousePrices is Ownable {
mapping(uint256 => uint256) public tokenToHousePrice;
mapping(uint256 => uint256) public tokenToLastUpdateTimestamp; // Per-token timestamp
function updatePrice(uint256 tokenId, uint256 newPrice) external {
tokenToHousePrice[tokenId] = newPrice;
tokenToLastUpdateTimestamp[tokenId] = block.timestamp; // Update token-specific timestamp
emit PriceUpdated(tokenId, newPrice);
}
}
```
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.