15,000 USDC
View results
Submission Details
Severity: medium
Valid

Missing Chainlink Price Feed Safety Checks

Summary

The vulnerability is related to missing safety checks for validating data returned from Chainlink's latestRoundData function, which fetches price feeds. The missing checks include ensuring the validity and completeness of the requested round, verifying that the price is greater than zero, and checking the freshness of the price data.

The impact of these missing safety checks can lead to various issues, such as using data from an invalid round for computations, encountering zero prices, using stale price data for calculations, and basing computations on incorrect prices.

To mitigate the vulnerability, I recommend defining specific errors for different scenarios and two variables representing the minimum and maximum price ranges. Additionally, the recommended mitigation steps include adding the necessary safety checks to the staleCheckLatestRoundData() function to ensure the validity and reliability of the price data.

Vulnerability Details

Certain safety checks that should be used to validate data returned from latestRoundData are missing:

  • require(updatedAt > 0): This checks whether the requested round is valid and complete. A description of the parameter validation can be found in Chainlink's documentaion

  • require(answer > 0): While the price is expected to be greater than zero, the code may consume zero prices. This check should be added before returning the price.

  • require(answerInRound >= roundId): The answeredInRound must be greater than or equal to roundId. If answeredInRound is less than roundId, the answer is carried over. If answeredInRound is equal to roundId, then the answer is fresh.

  • require(answer > minThreshold && answer < maxThreshold): It is important to be aware of minAnswer and maxAnswer of the Chainlink oracle. These values are not allowed to be reached or surpassed. You have to consider having off-chain monitoring to identify when the market price moves out of the [minAnswer, maxAnswer] range. Also, you can consider minAnswer and maxAnswer, and if the result of the oracle is equal to minAnswer or maxAnswer, you can revert the execution.

Impact

Consider the following cases:

  • When updatedAt == 0 means the requested round is not valid an complete. Then, the computations will be based on data from an invalid round.

  • When answer == 0 very detrimental cases can happen. For example, this can cause the liquidation of all of the users that use the collateral whose price is deemed to be zero.

  • When answerInRound < roundId means that the given price is not fresh and the computations will be using stale price.

  • The returned price, answer, would not be out of the [minAnswer, maxAnswer] range. When the actual price is less than minAnswer or more than maxAnswer, minAnswer or maxAnswer will be returned. In these cases, the following computations would be based on the wrong price.

Tools Used

Manual Review

Recommendations

In the first step, define the following errors:

error OracleLib__NotValidRound();
error OracleLin__ZeroPrice();
error OracleLib__NotFreshPrice();
error OracleLib__PriceMoreThanMax();
error OracleLib__PriceLessThanMin();

Also, define two variables that show the minimum and maximum range of price that the price can deviate from:

uint256 minPrice;
uint256 maxPrice;

Finally, add the following checks to the staleCheckLatestRoundData() function:

if (updatedAt == 0) revert OracleLib__NotValidRound(); // This is not an issue in the code; however, I added it for the sake of clarity and completeness.
if (answer == 0) revert OracleLib__ZeroPrice();
if (answerInRound < roundId) revert OracleLib__NotFreshPrice();
if (answer > maxPrice) revert OracleLib__PriceMoreThanMax();
if (answer < minPrice) revert OracleLin__PriceLessThanMin();

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.