sherlock-audit/2023-02-gmx-judging

Avci - latestRoundData is not checking if returns stale result

Closed this issue · 0 comments

Avci

medium

latestRoundData is not checking if returns stale result

Summary

On Oracle.sol, the contract uses the latestRoundData, but there is no check for return value round completeness and it will impact the project logic.

Vulnerability Detail

The _setPricesFromPriceFeeds function in the contract Oracle.sol fetches the asset price from a Chainlink aggregator using the latestRoundData function. However, there are no checks on roundID or timeStamp, resulting in stale prices/not completeness rounds. The oracle wrapper calls out to a chainlink oracle receiving the latestRoundData(). It then checks freshness by verifying that the answer is indeed for the last known round. The returned updatedAt timestamp is not checked.

Impact

may oracle return stale price.

Code Snippet

https://github.com/sherlock-audit/2023-02-gmx-0xdanial/blob/817cf894ded79defe62e598e56aa8e9597e75dac/gmx-synthetics/contracts/oracle/Oracle.sol#L571-L577

solidity function _setPricesFromPriceFeeds(DataStore dataStore, EventEmitter eventEmitter, address[] memory priceFeedTokens) internal {
        for (uint256 i = 0; i < priceFeedTokens.length; i++) {
            address token = priceFeedTokens[i];

            if (!primaryPrices[token].isEmpty()) {
                revert PriceAlreadySet(token, primaryPrices[token].min, primaryPrices[token].max);
            }

            IPriceFeed priceFeed = getPriceFeed(dataStore, token);

            (
                /* uint80 roundID */,
                int256 _price,
                /* uint256 startedAt */,
                /* uint256 timestamp */,
                /* uint80 answeredInRound */
            ) = priceFeed.latestRoundData();

            uint256 price = SafeCast.toUint256(_price);
            uint256 precision = getPriceFeedMultiplier(dataStore, token);

            price = price * precision / Precision.FLOAT_PRECISION;

            if (price == 0) {
                revert EmptyFeedPrice(token);
            }

            uint256 stablePrice = getStablePrice(dataStore, token);

            Price.Props memory priceProps;

            if (stablePrice > 0) {
                priceProps = Price.Props(
                    price < stablePrice ? price : stablePrice,
                    price < stablePrice ? stablePrice : price
                );
            } else {
                priceProps = Price.Props(
                    price,
                    price
                );
            }

            primaryPrices[token] = priceProps;

            tokensWithPrices.add(token);

            emitOraclePriceUpdated(eventEmitter, token, priceProps.min, priceProps.max, true, true);
        }

Tool used

Manual Review

Recommendation

Validating Feed Correctly

solidity function _setPricesFromPriceFeeds(DataStore dataStore, EventEmitter eventEmitter, address[] memory priceFeedTokens) internal {
        for (uint256 i = 0; i < priceFeedTokens.length; i++) {
            address token = priceFeedTokens[i];

            if (!primaryPrices[token].isEmpty()) {
                revert PriceAlreadySet(token, primaryPrices[token].min, primaryPrices[token].max);
            }

            IPriceFeed priceFeed = getPriceFeed(dataStore, token);

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

            uint256 price = SafeCast.toUint256(_price);
            uint256 precision = getPriceFeedMultiplier(dataStore, token);

            price = price * precision / Precision.FLOAT_PRECISION;

            require(answeredInRound >= roundID, "...");

            require(timeStamp != 0, "...");

            if (price == 0) {
                revert EmptyFeedPrice(token);
            }

            uint256 stablePrice = getStablePrice(dataStore, token);

            Price.Props memory priceProps;

            if (stablePrice > 0) {
                priceProps = Price.Props(
                    price < stablePrice ? price : stablePrice,
                    price < stablePrice ? stablePrice : price
                );
            } else {
                priceProps = Price.Props(
                    price,
                    price
                );
            }

            primaryPrices[token] = priceProps;

            tokensWithPrices.add(token);

            emitOraclePriceUpdated(eventEmitter, token, priceProps.min, priceProps.max, true, true);
        }