RAACHousePrice uses a single lastUpdateTimestamp for all token IDs, misleading the LendingPool about how up-to-date each NFT’s price is. Attackers can let other tokens keep “updating” the global timestamp while never refreshing their own NFT’s stale or inflated price. Switching to per-token timestamps ensures each NFT’s update time is tracked individually, preventing stale or never-updated collateral data from being treated as fresh.
RAACHousePrice stores a single:
The contract lumps all token IDs under one lastUpdateTimestamp, even if _tokenId is just one specific NFT being updated.
LendingPool calls priceOracle.getLatestPrice(tokenId), receiving (price, lastTimestamp). Because lastUpdateTimestamp is used for all tokens, the “timestamp” is the time of the most recent update for any NFT, not necessarily the NFT in question. LendingPool or other consumers might treat that “lastTimestamp” as fresh for every token ID.
Result: A token ID that never had its price set or had it last set weeks ago can appear “fresh” because lastUpdateTimestamp was updated by some other token’s setHousePrice(...) call. This can:
Let an attacker borrow more than they should if their NFT’s actual price is stale or artificially inflated from a long time ago.
Allow silent subversion of the LendingPool’s stale data checks, because the global timestamp is advanced whenever any token is updated, not necessarily the one being used for collateral.
Token #123 has an old price from 2 months ago, last set at T0.
Token #456 is updated now at T1 (recent block), so RAACHousePrice.setHousePrice(456, newAmount) sets lastUpdateTimestamp = T1.
User uses Token #123 as collateral in the LendingPool:
The code sees lastUpdated = T1 (the global lastUpdateTimestamp), incorrectly believing the price for NFT #123 is fresh.
The user can overborrow or rely on an out-of-date price if the actual #123 price is inflated or never updated.
Stale or Never Updated Price: The LendingPool (and other consumers) incorrectly deems the data “recent,” ignoring that the specific token’s tokenToHousePrice[_tokenId] might be from months ago. This can lead to severe mispricing of collateral and over-lending.
Exploitable Over Borrowing: Attackers deliberately keep a high old price for their NFT #123, never calling setHousePrice for that ID. Meanwhile, someone else updates a different NFT. The global lastUpdateTimestamp is advanced, letting #123 appear “fresh” even though it’s actually stale and possibly inflated.
Undermines Collateral Security: The entire system presupposes that lastUpdateTimestamp reveals how recently each NFT’s price was updated. Instead, it lumps all token IDs under a single timestamp.
Per-Token Timestamps
In RAACHousePrice, store lastUpdatedTimestamp[_tokenId] = block.timestamp for each token. So each NFT’s price data has a unique update time.
Then getLatestPrice(_tokenId) can return (tokenToHousePrice[_tokenId], lastUpdatedTimestamp[_tokenId]).
Remove or Repurpose lastUpdateTimestamp
If the contract wants a global “any price updated” timestamp for some reason, rename it to something like globalLastUpdate. But do not feed it to the LendingPool as if it’s the NFT’s personal timestamp.
Instead, supply the per-token timestamp to confirm the actual data’s freshness.
Enhance LendingPool
The LendingPool can check whether block.timestamp - tokenSpecificLastUpdated[_tokenId] < someStaleThreshold to ensure each NFT’s data is truly fresh. If it’s older than a threshold, revert or require an updated price.
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.