The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Call to Chainlink oracle doesn't check for stale prices / round completeness / reporting 0

Summary

The calls to the Chainlink oracles which fetch the EUR/USD and the Asset USD price, utilized in the LiquidationPool.sol don't check for stale prices.

Vulnerability Details

Oracle price feeds can become stale due to a variety of reasons. Using a stale price will result in incorrect calculations in the distributeAssets() function leading to incorrect calculations.

(, int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
(, int256 assetPriceUsd,,,) =
Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();

As we can see in both of the implementations above, no checks for stale data are performed. Here's more information as part of Chainlink's documentation: https://docs.chain.link/data-feeds/historical-data

Impact

If stale prices are fetched this can affect the calculations for the distribution of assets during liquidations, causing inaccurate calculation of the costInEuros variable leading to a wrong amount of EUROs burned + a wrong amount calculated for the postilion in EUROs, which will break the accounting logic.

This can also lead to unfair liquidations as the undercollateralized() function depends on price checks through the PriceCalculator.sol as well, which also miss the stale pricing checks:

function tokenToEurAvg(ITokenManager.Token memory _token, uint256 _tokenValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
uint256 scaledCollateral = _tokenValue * 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
uint256 collateralUsd = scaledCollateral * avgPrice(4, tokenUsdClFeed);
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return collateralUsd / uint256(eurUsdPrice);
}

Other affected functions include swap (due to the price feed dependency in the calculateMinimumAmountOut() function as well.

Tools Used

  • Manual Review

Recommendations

Consider adding checks for stale data.
Example:

(uint80 roundID, int256 priceEurUsd, uint256 timestamp, uint256 updatedAt, ) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
//Checks
require(updatedAt >= roundID, "Stale price");
require(timestamp != 0,"Round not complete");
require(priceEurUsd > 0,"Chainlink answer reporting 0");
//You can set a MAX_DELAY_TIME constant which will be the max tolerance for update staleness, e.g. 1 hour.
if (updatedAt < block.timestamp - MAX_DELAY_TIME)
revert PRICE_OUTDATED(_token);
Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Chainlink-price

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Chainlink-price

Support

FAQs

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