Cyfrin / 2023-07-foundry-defi-stablecoin

38 stars 33 forks source link

`latestRoundData()` does not check round completeness #1095

Open codehawks-bot opened 1 year ago

codehawks-bot commented 1 year ago

latestRoundData() does not check round completeness

Severity

Medium Risk

Relevant GitHub Links

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L21-#L33

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);
}