Assessment reports>WOOFi Swap>Medium findings>Chainlink data staleness
Category: Business Logic

Chainlink data staleness

Medium Severity
Medium Impact
Low Likelihood

Description

The Wooracle contract relies on Chainlink as one of its third-party pricing sources. The latestRoundData method is used to retrieve data, including token prices. However, the contract does not sufficiently check for stale data, which can result in inaccurate pricing information.

function _cloPriceInQuote(address _fromToken, address _toToken)
    internal
    view
    returns (uint256 refPrice, uint256 refTimestamp)
{
    address baseOracle = clOracles[_fromToken].oracle;

    // NOTE: Only for chains where chainlink oracle is unavailable
    // if (baseOracle == address(0)) {
    //     return (0, 0);
    // }
    require(baseOracle != address(0), "WooracleV2_2: !oracle");

    address quoteOracle = clOracles[_toToken].oracle;
    uint8 quoteDecimal = clOracles[_toToken].decimal;


    (, int256 rawBaseRefPrice, , uint256 baseUpdatedAt, ) = AggregatorV3Interface(baseOracle).latestRoundData();
    (, int256 rawQuoteRefPrice, , uint256 quoteUpdatedAt, ) = AggregatorV3Interface(quoteOracle).latestRoundData();
    // ...
}

Impact

Should the data returned by Chainlink be stale, the health-factor calculation would be incorrect, which could in turn affect the entire protocol.

Recommendations

We recommend adding checks for each of the returned values to ensure that the data is not stale.

function _cloPriceInQuote(address _fromToken, address _toToken)
    internal
    view
    returns (uint256 refPrice, uint256 refTimestamp)
{
    address baseOracle = clOracles[_fromToken].oracle;

    // NOTE: Only for chains where chainlink oracle is unavailable
    // if (baseOracle == address(0)) {
    //     return (0, 0);
    // }
    require(baseOracle != address(0), "WooracleV2_2: !oracle");

    address quoteOracle = clOracles[_toToken].oracle;
    uint8 quoteDecimal = clOracles[_toToken].decimal;


    (
+       uint80 baseRoundID,
        int256 rawBaseRefPrice, 
+       uint baseStartedAt,
        uint256 baseUpdatedAt, 
+       uint80 baseAnsweredInRound
    ) = AggregatorV3Interface(baseOracle).latestRoundData();
    (
+       uint80 quoteRoundID,
        int256 rawQuoteRefPrice, 
+       uint quoteStartedAt,
        uint256 quoteUpdatedAt
+       uint80 quoteAnsweredInRound
    ) = AggregatorV3Interface(quoteOracle).latestRoundData();

+    require(
+       baseUpdatedAt != 0,
+       "base round is not complete"
+    );
+
+    require(
+        baseAnsweredInRound >= baseRoundID,
+        "base stale data"
+    );

+    require(
+       quoteUpdatedAt != 0,
+       "quote round is not complete"
+    );

+    require(
+        quoteAnsweredInRound >= quoteRoundID,
+        "quote stale data"
+    );
    // ...
}

Remediation

Additionally, the WOOFI team has stated that:

Checking the staleness onchain costs more gas than necessary (regarding the TTL is typically 24 hours). However, we add an offchain- script monitor for chainlink staleness (as well as L2 sequencer status), if any oracle got expired, we pause the WooPP immediately. So essentially, we're having the staleness monitor now, but in offchain.

Zellic © 2024Back to top ↑