15,000 USDC
View results
Submission Details
Severity: medium
Valid

`latestRoundData()` does not check round completeness

Summary

The OracleLib contract fetches the latest round data from a Chainlink oracle. However, the contract doesn't check for the completeness of the latest round. This could potentially result in the use of incomplete or stale prices, which might disrupt functions relying on accurate price data and can lead to incorrect calculations or operations, and in certain scenarios, loss of funds.

Vulnerability Details

The OracleLib contract retrieves the latest round data from a Chainlink oracle using the latestRoundData() function. Although the contract checks if the data is older than a predefined TIMEOUT, it doesn't check for the completeness of the latest round. According to Chainlink's documentation, latestRoundData() doesn't error out if no consensus has been reached for the latest round; instead, it returns 0 or data from the previous round.

function staleCheckLatestRoundData(AggregatorV3Interface priceFeed)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
priceFeed.latestRoundData();
uint256 secondsSince = block.timestamp - updatedAt;
if (secondsSince > TIMEOUT) revert OracleLib__StalePrice();
return (roundId, answer, startedAt, updatedAt, answeredInRound);
}

In this function, priceFeed is a reference to a Chainlink oracle. The function retrieves the latest price and timestamp and checks if the data is stale. However, it doesn't check if the latest round of data is complete.

Impact

Functions that rely on accurate price data might not work as expected if the data is incomplete or stale.

Tools Used

Manual review

Recommendations

Check for round completeness: when calling latestRoundData(), check whether the latest round of data is complete. If it isn't, handle this situation appropriately to prevent the use of incomplete or stale price data.

Here is a code example:

function staleCheckLatestRoundData(AggregatorV3Interface priceFeed)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
(uint80 roundId, int256 price,, uint256 updatedAt, uint80 answeredInRound) =
priceFeed.latestRoundData();
// Check if the latest round of data is complete
require(answeredInRound >= roundId, "Round not complete");
uint256 secondsSince = block.timestamp - updatedAt;
if (secondsSince > TIMEOUT) revert OracleLib__StalePrice();
return (roundId, price, startedAt, updatedAt, answeredInRound);
}

Support

FAQs

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