Summary
The contract's documentation indicates token-specific timestamp tracking, but implementation uses a single global timestamp, leading to inaccurate price update history.
Vulnerability Details
The contract comments suggest individual token timestamps:
* Updates timestamp for each token individually
*/
function setHousePrice(uint256 _tokenId, uint256 _amount) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
lastUpdateTimestamp = block.timestamp;
emit PriceUpdated(_tokenId, _amount);
}
Impact
Severity: Low
Incorrect timestamp tracking for individual tokens
Inability to implement proper staleness checks
Misleading documentation vs. implementation
Proof of Concept
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("RAACHousePrices Timestamp Issue", function () {
let prices, owner, oracle;
beforeEach(async () => {
[owner, oracle] = await ethers.getSigners();
const RAACHousePrices = await ethers.getContractFactory("RAACHousePrices");
prices = await RAACHousePrices.deploy(owner.address);
await prices.connect(owner).setOracle(oracle.address);
});
it("demonstrates incorrect timestamp tracking", async () => {
await prices.connect(oracle).setHousePrice(1, ethers.utils.parseEther("100"));
const firstUpdateTime = (await ethers.provider.getBlock('latest')).timestamp;
await ethers.provider.send("evm_increaseTime", [3600]);
await ethers.provider.send("evm_mine");
await prices.connect(oracle).setHousePrice(2, ethers.utils.parseEther("200"));
const secondUpdateTime = (await ethers.provider.getBlock('latest')).timestamp;
const [, token1Time] = await prices.getLatestPrice(1);
const [, token2Time] = await prices.getLatestPrice(2);
expect(token1Time).to.equal(secondUpdateTime);
expect(token2Time).to.equal(secondUpdateTime);
expect(token1Time).to.not.equal(firstUpdateTime);
});
});
Recommended Fix
contract RAACHousePrices is Ownable {
mapping(uint256 => uint256) public tokenUpdateTimestamps;
event PriceUpdated(uint256 indexed tokenId, uint256 newPrice, uint256 timestamp);
function setHousePrice(uint256 _tokenId, uint256 _amount) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
tokenUpdateTimestamps[_tokenId] = block.timestamp;
emit PriceUpdated(_tokenId, _amount, block.timestamp);
}
function getLatestPrice(uint256 _tokenId) external view returns (uint256, uint256) {
return (tokenToHousePrice[_tokenId], tokenUpdateTimestamps[_tokenId]);
}
function isPriceStale(uint256 _tokenId, uint256 _maxAge) external view returns (bool) {
return block.timestamp - tokenUpdateTimestamps[_tokenId] > _maxAge;
}
}
Tools Used