The RAACHousePrices contract maintains a single global timestamp (lastUpdateTimestamp
) for price updates instead of storing per-NFT update timestamps. When an oracle updates the price of one NFT, all NFTs falsely appear as recently updated, potentially allowing attackers to use outdated or manipulated prices for loans, liquidations, or collateral valuations.
This issue undermines the protocol's ability to verify price freshness and could result in severe financial loss if attackers exploit outdated high valuations to overborrow or avoid liquidations.
(Note: This bug is hereby submitted assuming the staleness check will be implemented when querying getLatestPrice()
as I have separately submitted a report for it.)
The function setHousePrice()
updates lastUpdateTimestamp
globally instead of tracking timestamps per NFT:
The function getLatestPrice()
does not verify if the specific NFT's price is stale, making it appear that all NFTs were updated at the same time:
How an Attacker Could Exploit This
Step1. Obtain an Overvalued NFT:
Suppose an NFT was priced at $10,000
three months ago but has since lost value to $2,000.
The protocol expects a fresh update before allowing loans based on this NFT’s value.
Step2. Trigger an Irrelevant NFT Price Update:
Price update of an unrelated NFT in the system transpires moments beforehand.
The global timestamp is updated, falsely making it appear that all NFTs, including the overpriced one, were recently updated.
Step3. Use the Outdated NFT Price for Loans or Avoid Liquidation:
The attacker uses the outdated $10,000 price to borrow more funds than they should be allowed.
Alternatively, they could avoid liquidation, since the protocol believes the outdated NFT still holds value.
Regardless, it doesn't matter when the position is liquidated later as the attacker has already maximized the arbitrage as restrained by the liquidationThreshold check
beforehand.
Note: The reverse is also true where NFT prices have appreciated and a position should have avoided liquidation via a revert from the staleness check, e.g. block.timestamp - lastUpdateTimestamp > MAX_PRICE_AGE
, had per-token timestamp
been properly assigned.
This issue can be actively exploited to mislead the system into believing prices are fresh, leading to incorrect lending (deceptive over-collateralization) or faulty liquidations in the opposite scenario. Since NFT prices can fluctuate significantly, failing to track per-token timestamps can result in millions in bad debt or unjust liquidations from the opposite end.
Manual
Consider making the following refactoring:
And, don't forget to modify getNFTPrice()
in LendingPool.sol to reject stale prices via something like:
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.