Skip to content

Latest commit

 

History

History
138 lines (100 loc) · 6.13 KB

File metadata and controls

138 lines (100 loc) · 6.13 KB

Little Malachite Perch

Medium

[MEDIUM] OracleReader.sol Missed Checking of int256 answer (price) in function getOraclePrice()

Summary

The OracleReader contract provides functionality to fetch and process price data from Chainlink oracles via the OracleFeeds contract. While the implementation offers flexibility and extensibility, there are potential vulnerabilities and design considerations that could expose the system to exploits, especially in volatile market conditions or during oracle manipulation.

This report details identified vulnerabilities, their potential impact, and recommendations to mitigate them. >> OracleReader.sol::Line 78

Root Cause

1. Unused Error NoPriceFound

  • Description: The error NoPriceFound() is declared in the contract but never used. This introduces potential ambiguity in error handling and omits an opportunity for better reporting of missing price data.
  • Impact: The lack of explicit error reporting when no valid price is retrieved may result in undefined behavior or missed debugging opportunities.
  • Recommendation: Use NoPriceFound() in the getOraclePrice function when the oracle returns an invalid or zero price. Example:
if (answer == 0) {
    revert NoPriceFound();
}

2. Risk of Incorrect Price During Flash Crashes

  • Description: Chainlink oracles enforce a predefined price range through minAnswer and maxAnswer. During flash crashes, bridge exploits, or depegging events, if the actual price falls below minAnswer or exceeds maxAnswer, the oracle may return an incorrect or stale price.
  • Impact: An attacker could exploit incorrect pricing to borrow more assets than allowed by depositing underpriced tokens. This could lead to bad debt in lending/borrowing platforms or other financial protocols relying on the contract.
  • Recommendation: Implement checks to ensure that the retrieved price is within the acceptable bounds:
(, int256 answer, , , ) = AggregatorV3Interface(feed).latestRoundData();
int256 minAnswer = OracleFeeds(oracleFeeds).getMinAnswer(feed);
int256 maxAnswer = OracleFeeds(oracleFeeds).getMaxAnswer(feed);
if (answer < minAnswer || answer > maxAnswer) {
    revert NoPriceFound();
}
  • Introduce off-chain monitoring to validate Chainlink prices against external sources.

3. Stale Price Handling

  • Description: The getOraclePrice function correctly checks if the price is stale by comparing the updatedTimestamp to the current block.timestamp. However, this relies entirely on the feedHeartbeats values provided by OracleFeeds. Impact: If the feedHeartbeats value is incorrect or misconfigured, stale prices may go undetected, leading to mispriced assets.
  • Recommendation: Validate feedHeartbeats during deployment or initialization. Cross-check updatedTimestamp with a configurable maximum allowed delay in addition to feedHeartbeats.

4. Lack of Cross-Oracle Validation

  • Description: The contract relies solely on Chainlink oracles for pricing. In case of oracle manipulation or outages, there is no fallback mechanism or cross-checking with other oracle providers.
  • Impact: Oracle manipulation could result in significant financial losses for protocols using this contract.
  • Recommendation: Integrate a fallback oracle mechanism (e.g., Band Protocol or Uniswap TWAP). Use a medianizer to aggregate prices from multiple sources.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

1. Critical Impact:

  • Exploiting incorrect prices during flash crashes could lead to significant financial losses for users and protocols integrating this contract.
  • Stale prices could be exploited to borrow funds at outdated rates.

2. Moderate Impact:

  • Misrepresentation of constants (e.g., USD and ETH) may lead to integration errors.
  • Lack of cross-oracle validation increases dependency on a single source.

PoC

No response

Mitigation

  1. Use NoPriceFound() to handle invalid prices.
  2. Validate retrieved prices against minAnswer and maxAnswer bounds.
  3. Include cross-oracle validation and fallback mechanisms for improved reliability.
  4. Enhance documentation to prevent misinterpretation of constants.
  5. Validate the configuration of feedHeartbeats to avoid stale prices.

Example Fix for Flash Crash Protection:

function getOraclePrice(
    address quote,
    address base
) public view returns (uint256) {
    bool isInverted = false;
    address feed = OracleFeeds(oracleFeeds).priceFeeds(quote, base);

    if (feed == address(0)) {
        feed = OracleFeeds(oracleFeeds).priceFeeds(base, quote);
        if (feed == address(0)) {
            revert NoFeedFound();
        }
        isInverted = true;
    }

    (, int256 answer, , uint256 updatedTimestamp, ) = AggregatorV3Interface(
        feed
    ).latestRoundData();

    int256 minAnswer = OracleFeeds(oracleFeeds).getMinAnswer(feed);
    int256 maxAnswer = OracleFeeds(oracleFeeds).getMaxAnswer(feed);
    if (answer < minAnswer || answer > maxAnswer) {
        revert NoPriceFound();
    }

    if (
        updatedTimestamp + OracleFeeds(oracleFeeds).feedHeartbeats(feed) <
        block.timestamp
    ) {
        revert StalePrice();
    }

    uint256 decimals = uint256(AggregatorV3Interface(feed).decimals());
    return
        isInverted
            ? (10 ** decimals * 10 ** decimals) / uint256(answer)
            : uint256(answer);
}

Notes to Judge:

Hi dear Sherlock judge,

This is my first time participating in a competitive audit, and I hope you find my report well-prepared and in accordance with the rules and requirements outlined in the Sherlock audit guidelines.

If there are any areas where I’ve fallen short, I would greatly appreciate detailed feedback to ensure I improve in future reports. Of course, I completely understand if your schedule doesn’t allow for extensive feedback given the number of reports you have to review.

Wishing you a great day! Sending virtual cookies your way 🍪😊