sherlock-audit / 2023-05-USSD-judging

9 stars 7 forks source link

w42d3n - Chainlink's latestRoundData might return stale price #926

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

w42d3n

medium

Chainlink's latestRoundData might return stale price

Summary

Chainlink's latestRoundData() is used but there is no check if the return value indicates stale data. This could lead to stale prices according to the Chainlink documentation:

https://docs.chain.link/docs/historical-price-data/#historical-rounds

Vulnerability Detail

The functions

StableOracleDAI.getpriceUSD() StableOracleWBTC.getpriceUSD() StableOracleWETH.getpriceUSD()

uses Chainlink's latestRoundData() to get the latest price. However, there is no check if the return value indicates stale data.

Impact

These contracts could return stale price data for the underlying asset.

Code Snippet

https://github.com/sherlock-audit/2023-05-USSD/blob/main/ussd-contracts/contracts/oracles/StableOracleDAI.sol#L33-L53


function getPriceUSD() external view override returns (uint256) {
        address[] memory pools = new address[](1);
        pools[0] = 0x60594a405d53811d3BC4766596EFD80fd545A270;
        uint256 DAIWethPrice = DAIEthOracle.quoteSpecificPoolsWithTimePeriod(
            1000000000000000000, // 1 Eth
            0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH (base token)
            0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI (quote token)
            pools, // DAI/WETH pool uni v3
            600 // period
        );

        uint256 wethPriceUSD = ethOracle.getPriceUSD();

        // chainlink price data is 8 decimals for WETH/USD, so multiply by 10 decimals to get 18 decimal fractional
        //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeedDAIETH.latestRoundData();
        (, int256 price, , , ) = priceFeedDAIETH.latestRoundData();

        return
            (wethPriceUSD * 1e18) /
            ((DAIWethPrice + uint256(price) * 1e10) / 2);
    }

https://github.com/sherlock-audit/2023-05-USSD/blob/main/ussd-contracts/contracts/oracles/StableOracleWBTC.sol#LL21C1-L26C6

    function getPriceUSD() external view override returns (uint256) {
        //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();
        (, int256 price, , , ) = priceFeed.latestRoundData();
        // chainlink price data is 8 decimals for WETH/USD
        return uint256(price) * 1e10;
    }

https://github.com/sherlock-audit/2023-05-USSD/blob/main/ussd-contracts/contracts/oracles/StableOracleWETH.sol#LL21C1-L26C6

    function getPriceUSD() external view override returns (uint256) {
        //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();
        (, int256 price, , , ) = priceFeed.latestRoundData();
        // chainlink price data is 8 decimals for WETH/USD
        return uint256(price) * 1e10;
    }

Tool used

Manual Review

Recommendation

Consider adding checks for stale data. e.g

(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();

(+) require(answeredInRound >= roundId, "Price stale");

Duplicate of #31