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

Chainlink Oracle may return incorrect price (zero, outside min/max range)

Summary

The OracleLib contract, which fetches the latest round data from a Chainlink Oracle, does not validate if the returned price falls within an acceptable range (min/max). As a result, in the event of significant price volatility, the Chainlink Oracle's built-in circuit breaker might return a price at the extreme of the predetermined band, rather than the asset's actual market price. This scenario was observed during the LUNA market crash, where borrowers were allowed to continue operations based on this incorrect price, leading to the accumulation of bad debt in the protocol.

Vulnerability Details

The OracleLib contract retrieves the latest round data from a Chainlink Oracle using the latestRoundData() function. Although the contract checks if the data is older than a predefined TIMEOUT, it does not verify whether the fetched price falls within a set acceptable range (min/max). In cases of significant market volatility, the Chainlink Oracle's circuit breaker can return a price at the limit of the predetermined price band, rather than the actual market price of the asset.

This situation occurred during the LUNA market crash, where despite a significant drop in LUNA's price, the Chainlink Oracle continued to return the minimum price, allowing borrowers to continue operations based on an incorrect asset valuation.

function staleCheckLatestRoundData(AggregatorV3Interface priceFeed)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
priceFeed.latestRoundData();
uint256 secondsSince = block.timestamp - updatedAt;
if (secondsSince > TIMEOUT) revert OracleLib__StalePrice();
return (roundId, answer, startedAt, updatedAt, answeredInRound);
}

In this function, priceFeed is a reference to a Chainlink Oracle. The function retrieves the latest price and timestamp and checks if the data is stale. However, it does not verify whether the fetched price falls within an acceptable price range.

Impact

In the event of a massive drop in an asset's price, such as during the LUNA market crash, the Chainlink Oracle's built-in circuit breaker would return the minimum price rather than the asset's actual market price. This could lead to borrowers continuing operations based on an incorrect price, resulting in the accumulation of bad debt in the protocol and other potential adverse effects.

Tools Used

Manual review

Recommendations

Implement a price range check. When calling latestRoundData(), verify that the fetched price falls within an acceptable range (min/max). If it doesn't, handle this situation appropriately to prevent the use of incorrect price data.

function staleCheckLatestRoundData(AggregatorV3Interface priceFeed, int256 minPrice, int256 maxPrice)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
(uint80 roundId, int256 price,, uint256 updatedAt,) =
priceFeed.latestRoundData();
require(price >= minPrice && price <= maxPrice, "Price out of bounds");
uint256 secondsSince = block.timestamp - updatedAt;
if (secondsSince > TIMEOUT) revert OracleLib__StalePrice();
return (roundId, price, startedAt, updatedAt, answeredInRound);
}

In this function, we add two parameters, minPrice and maxPrice, which represent the minimum and maximum acceptable prices, respectively.

Support

FAQs

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