The Standard

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

Lack of Chainlink price freshness check could lead to using stale price

Summary

Chainlink docs recommends that the updatedAt timestamp returned from the latestRoundData() function call should be validated to ensure the price is not stale.

Vulnerability Details

In The LiquidationPool.sol#distributeAssets(...) function, the chainlink's latestRoundData() is used to get the price of token but the timestamp at which these prices were last updated where not validated as recommended by Chainlink docs:

"Your application should track the latestTimestamp variable or use the updatedAt value from the latestRoundData() function to make sure that the latest answer is recent enough for your application to use it. If your application detects that the reported answer is not updated within the heartbeat or within time limits that you determine are acceptable for your application, pause operation or switch to an alternate operation mode while identifying the cause of the delay. Chainlink Price Feeds do not provide streaming data. Rather, the aggregator updates its latestAnswer when the value deviates beyond a specified threshold or when the heartbeat idle time has passed. You can find the heartbeat and deviation values for each data feed at data.chain.link or in the Contract Addresses lists."

source: https://docs.chain.link/data-feeds/price-feeds/addresses/?network=arbitrum&page=1
Each pricefeed also have their own heartbeat.

FIle: LiquidationPool.sol
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
consolidatePendingStakes();
(,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
uint256 stakeTotal = getStakeTotal();
uint256 burnEuros;
uint256 nativePurchased;
for (uint256 j = 0; j < holders.length; j++) {
Position memory _position = positions[holders[j]];
uint256 _positionStake = stake(_position);
if (_positionStake > 0) {
for (uint256 i = 0; i < _assets.length; i++) {
ILiquidationPoolManager.Asset memory asset = _assets[i];
if (asset.amount > 0) {
(,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;

Impact

Potential loss of assets due to using an old price.

Tools Used

Manual Review

Recommendations

use the returned updatedAt timestamp to validate that the price returned is not an old one.
Ensure to use the specific heartbeat for each price feed as recommended by chainlink because some price feed has 24hr heartbeat while some have just 1 hour heartbeat.

(,int256 priceEurUsd,,uint256 updatedAt,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
require(block.timestamp - updatedAt < tokenFeed[heartbeat] , "Stale price");//each pricefeed have the time difference at which price becomes stale.
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.