QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Invalid

ChainlinkOracle.sol Accepts Stale Price Data Leading to Critical Financial Risks

Summary

The ChainlinkOracle contract lacks timestamp validation for price data, accepting stale prices that could be hours old. This creates significant risks for protocols relying on accurate, real-time price data.

Vulnerability Details

The `getData()` function in ChainlinkOracle.sol (lines 27-34) retrieves price data without checking timestamp freshness:

function _getData() internal view override returns (int216, uint40) {
(, /*uint80 roundID*/ int data, , /*uint startedAt*/ uint timestamp, ) = /*uint80 answeredInRound*/
priceFeed.latestRoundData();
require(data > 0, "INVLDDATA");
data = data * int(10 ** normalizationFactor);
return (int216(data), uint40(timestamp)); // Overflow of data is extremely improbable and uint40 is large enough for timestamps for a very long time
}
}

The function only validates that the price is positive but fails to ensure the data is recent.

Proof of Concept

function testStaleData() public {
vm.warp(block.timestamp + 1 days);
mockFeed.setStaleData(2 hours);
(int216 price, uint40 timestamp) = oracle.getData();
console.log("Current timestamp:", block.timestamp);
console.log("Oracle timestamp:", timestamp);
console.log("Time difference:", block.timestamp - timestamp);
console.log("Price returned:", uint256(int256(price)));
assertTrue(block.timestamp - timestamp >= 2 hours, "Data should be stale");
}

Results:

[PASS] testStaleData() (gas: 34540)
Logs:
Current timestamp: 86401
Oracle timestamp: 79201
Time difference: 7200
Price returned: 1000000000000000000000
Traces:
[34540] ChainlinkOracleTest::testStaleData()
├─ [0] VM::warp(86401 [8.64e4])
│ └─ ← [Return]
├─ [5390] MockChainlinkFeed::setStaleData(7200)
│ └─ ← [Stop]
├─ [10156] ChainlinkOracle::getData() [staticcall]
│ ├─ [8855] MockChainlinkFeed::latestRoundData() [staticcall]
│ │ └─ ← [Return] 1, 100000000000 [1e11], 0, 79201 [7.92e4], 1
│ └─ ← [Return] 1000000000000000000000 [1e21], 79201 [7.92e4]
├─ [0] console::log("Current timestamp:", 86401 [8.64e4]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Oracle timestamp:", 79201 [7.92e4]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Time difference:", 7200) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Price returned:", 1000000000000000000000 [1e21]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::assertTrue(true, "Data should be stale") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]

Impact

This vulnerability allows stale prices to be used for critical operations:

  • Liquidations could occur at outdated prices

  • Users could open/close positions at incorrect valuations

  • Protocol could become insolvent due to mispriced positions

In volatile markets, even slightly outdated prices can lead to significant losses.

Tools Used

Foundry forge test

Manual code review

Recommendations

Add a maximum staleness threshold check:

function getData() public view returns (int216, uint40) {
(,int data, , uint timestamp,) = priceFeed.latestRoundData();
require(data > 0, "INVLDDATA");
require(block.timestamp - timestamp <= 1 hours, "STALE");
data = data * int(10 ** normalizationFactor);
return (int216(data), uint40(timestamp));
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

invalid_chainlink_staled_data_updateAt_roundId_known_issue

LightChaser: ## [Medium-4] Insufficient oracle validation

Appeal created

nomadic_bear Submitter
7 months ago
n0kto Lead Judge
6 months ago
n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

invalid_chainlink_staled_data_updateAt_roundId_known_issue

LightChaser: ## [Medium-4] Insufficient oracle validation

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.