DeFiHardhat
35,000 USDC
View results
Submission Details
Severity: low
Invalid

Timeout used to identify valid chainlink price is very large (4 days)

Summary

The Protocol Fetch the Price for ETH/USD and STETH/ETH from chainLink Price Feed to Identify the Price of WSTETH/USD. To validate the price return from chainlink priceFeed is not steal the protocol checks it against a 4 day timeout. which in fact is very large time as compare with HeartBeat PriceFeed of WETETH/ETH oracle which is one day.

Vulnerability Details

The Price timeout used for validity of Price return from chainLink price feed is very large uint256 constant FOUR_DAY_TIMEOUT = 345600. when the Price return from ChainLink the protocol check wheatear its steal price or not using following function:

function checkForInvalidTimestampOrAnswer(
uint256 timestamp,
int256 answer,
uint256 currentTimestamp,
uint256 maxTimeout
) private pure returns (bool) {
// Check for an invalid timeStamp that is 0, or in the future
if (timestamp == 0 || timestamp > currentTimestamp) return true;
// Check if Chainlink's price feed has timed out
if (currentTimestamp.sub(timestamp) > maxTimeout) return true;
// Check for non-positive price
if (answer <= 0) return true;
}

inside the getPrice and getTwap function as follows:

function getPrice(
address priceAggregatorAddress,
uint256 maxTimeout
) internal view returns (uint256 price) {
...
// Secondly, try to get latest price data:
try priceAggregator.latestRoundData() returns (
uint80 roundId,
int256 answer,
uint256 /* startedAt */,
uint256 timestamp,
uint80 /* answeredInRound */
) {
// Check for an invalid roundId that is 0
if (roundId == 0) return 0;
@> if (checkForInvalidTimestampOrAnswer(timestamp, answer, block.timestamp, maxTimeout)) {
return 0;
}
...
}
function getTwap(
address priceAggregatorAddress,
uint256 maxTimeout,
uint256 lookback
) internal view returns (uint256 price) {
...
// Secondly, try to get latest price data:
try priceAggregator.latestRoundData() returns (
uint80 roundId,
int256 answer,
uint256 /* startedAt */,
uint256 timestamp,
uint80 /* answeredInRound */
) {
// Check for an invalid roundId that is 0
if (roundId == 0) return 0;
@> if (checkForInvalidTimestampOrAnswer(timestamp, answer, block.timestamp, maxTimeout)) {
return 0;
}
TwapVariables memory t;
t.endTimestamp = block.timestamp.sub(lookback);
// Check if last round was more than `lookback` ago.
if (timestamp <= t.endTimestamp) {
return uint256(answer).mul(PRECISION).div(10 ** decimals);
} else {
t.lastTimestamp = block.timestamp;
// Loop through previous rounds and compute cumulative sum until
// a round at least `lookback` seconds ago is reached.
while (timestamp > t.endTimestamp) {
t.cumulativePrice = t.cumulativePrice.add(
uint256(answer).mul(t.lastTimestamp.sub(timestamp))
);
roundId -= 1;
t.lastTimestamp = timestamp;
(answer, timestamp) = getRoundData(priceAggregator, roundId);
@> if (checkForInvalidTimestampOrAnswer(
timestamp,
answer,
t.lastTimestamp,
maxTimeout
)) {
return 0;
}
}
t.cumulativePrice = t.cumulativePrice.add(
uint256(answer).mul(t.lastTimestamp.sub(t.endTimestamp))
);
return t.cumulativePrice.mul(PRECISION).div(10 ** decimals).div(lookback);
}
} catch {
// If call to Chainlink aggregator reverts, return a price of 0 indicating failure
return 0;
}
}

Impact

The Price would validate as true against a steal Price.

Tools Used

Manual Review

Recommendations

Change the uint256 constant FOUR_DAY_TIMEOUT = 345600 to meet the HearBeat of chainLink Price feed which is 1 day :

--- a/protocol/contracts/libraries/Oracle/LibChainlinkOracle.sol
+++ b/protocol/contracts/libraries/Oracle/LibChainlinkOracle.sol
- uint256 constant FOUR_DAY_TIMEOUT = 345600;
+ uint256 constant FOUR_DAY_TIMEOUT = 86400;
Updates

Lead Judging Commences

giovannidisiena Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Chainlink timeout

Support

FAQs

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