The Standard

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

Missing checks for round completeness

Summary

No check for round completeness in could lead to stale prices and wrong price return value, or outdated price. The functions rely on accurate price feed might not work as expected, sometimes can lead to fund loss.

Vulnerability Details

PriceCalculator::tokenToEurAvg(), PriceCalculator::tokenToEur() and PriceCalculator::eurToToken() call out to an oracle with latestRoundData() to get the price of EUR / USD feed. There is no check for round completeness.

According to Chainlink's documentation, this function does not error if no answer has been reached but returns 0 or outdated round data. Therefore, the Chainlink oracle, which provides index price information to the system, introduces risk inherent to any dependency on third-party data sources.
For example, the oracle could fall behind or otherwise fail to be maintained, resulting in outdated data being fed to the index price calculations. Oracle reliance has historically resulted in crippled on-chain systems, and complications that lead to these outcomes can arise from things as simple as network congestion.

Impact

If there is a problem with chainlink starting a new round and finding consensus on the new value for the oracle (e.g. chainlink nodes abandon the oracle, chain congestion, vulnerability/attacks on the chainlink system) consumers of this contract may continue using outdated stale data (if oracles are unable to submit no new round is started).
This could lead to stale prices and wrong price return value, or outdated price.

As a result, the functions rely on accurate price feed might not work as expected, sometimes can lead to fund loss. The impacts vary and depends on the specific situation, ranging from incorrect liquidations to bad swaps.

Tools Used

Manual review

Recommendations

Validate data feed for round completeness.

File: contracts/utils/PriceCalculator.sol
function avgPrice(uint8 _hours, Chainlink.AggregatorV3Interface _priceFeed) private view returns (uint256) {
uint256 startPeriod = block.timestamp - _hours * 1 hours;
uint256 roundTS;
uint80 roundId;
int256 answer;
(roundId, answer,, roundTS, answeredInRound) = _priceFeed.latestRoundData();
+ if (answeredInRound < roundId) revert("round not complete");
uint256 accummulatedRoundPrices = uint256(answer);
uint256 roundCount = 1;
while (roundTS > startPeriod && roundId > 1) {
roundId--;
try _priceFeed.getRoundData(roundId) {
(, answer,, roundTS,) = _priceFeed.getRoundData(roundId);
accummulatedRoundPrices += uint256(answer);
roundCount++;
} catch {
continue;
}
}
return accummulatedRoundPrices / roundCount;
}
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();
+ (uint80 roundId, int256 eurUsdPrice,,, uint80 answeredInRound) = clEurUsd.latestRoundData();
+ if (answeredInRound < roundId) revert("round not complete");
return collateralUsd / uint256(eurUsdPrice);
}
function tokenToEur(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);
- (,int256 _tokenUsdPrice,,,) = tokenUsdClFeed.latestRoundData();
+ (uint80 roundIDTU,int256 _tokenUsdPrice,,, uint80 answeredInRoundTU) = tokenUsdClFeed.latestRoundData();
+ if (answeredInRoundTU < roundIdTU) revert("round not complete");
uint256 collateralUsd = scaledCollateral * uint256(_tokenUsdPrice);
- (, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
+ (uint80 roundIdEU, int256 eurUsdPrice,,, uint80 answeredInRoundEU) = clEurUsd.latestRoundData();
+ if (answeredInRoundEU < roundIdEU) revert("round not complete");
return collateralUsd / uint256(eurUsdPrice);
}
function eurToToken(ITokenManager.Token memory _token, uint256 _eurValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
- (, int256 tokenUsdPrice,,,) = tokenUsdClFeed.latestRoundData();
+ (uint80 roundIdTU, int256 tokenUsdPrice,,, uint80 answeredInRoundTU) = tokenUsdClFeed.latestRoundData();
+ if (answeredInRoundTU < roundIdTU) revert("round not complete");
- (, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
+ (uint80 roundIdEU, int256 eurUsdPrice,,, uint80 answeredInRoundEU) = clEurUsd.latestRoundData();
+ if (answeredInRoundEU < roundIdEU) revert("round not complete");
return _eurValue * uint256(eurUsdPrice) / uint256(tokenUsdPrice) / 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
}
Updates

Lead Judging Commences

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

informational/invalid

Support

FAQs

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