Steadefi

Steadefi
DeFiHardhatFoundryOracle
35,000 USDC
View results
Submission Details
Severity: low
Valid

[M-01] `ChainlinkARBOracle` contract will return wrong price for assets if underlying aggregator hits minAnswer

Summary

Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset. This would allow user to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA imploded.

Vulnerability Details

ChainlinkAggregators have minAnswer and maxAnswer circuit breakers built into them. This means that if the price of the asset drops below the minAnswer, the protocol will continue to value the token at minAnswer instead of it's actual value. This will allow users to take out huge amounts of bad debt and bankrupt the protocol.

the function _getChainlinkResponse() is used to get the price of the requested token:

function _getChainlinkResponse(address _feed) internal view returns (ChainlinkResponse memory) {
ChainlinkResponse memory _chainlinkResponse;
_chainlinkResponse.decimals = AggregatorV3Interface(_feed).decimals();
// Arbitrum sequencer uptime feed
(
/* uint80 _roundID*/,
int256 _answer,
uint256 _startedAt,
/* uint256 _updatedAt */,
/* uint80 _answeredInRound */
) = sequencerUptimeFeed.latestRoundData();
// Answer == 0: Sequencer is up
// Answer == 1: Sequencer is down
bool _isSequencerUp = _answer == 0;
if (!_isSequencerUp) revert Errors.SequencerDown();
// Make sure the grace period has passed after the
// sequencer is back up.
uint256 _timeSinceUp = block.timestamp - _startedAt;
if (_timeSinceUp <= SEQUENCER_GRACE_PERIOD_TIME) revert Errors.GracePeriodNotOver();
(
uint80 _latestRoundId,
int256 _latestAnswer,
/* uint256 _startedAt */,
uint256 _latestTimestamp,
/* uint80 _answeredInRound */
) = AggregatorV3Interface(_feed).latestRoundData();
_chainlinkResponse.roundId = _latestRoundId;
_chainlinkResponse.answer = _latestAnswer;
_chainlinkResponse.timestamp = _latestTimestamp;
_chainlinkResponse.success = true;
return _chainlinkResponse;
}

However, there are no checks in place if an asset price falls below minAnswer.The latestRoundData extracts the linked aggregator and requests round data from it. If an asset's price falls below the minAnswer, the protocol continues to value the token at the minAnswer rather than its real value. This discrepancy could have the protocol end up minting drastically larger amount of assets as well as returning a much bigger collateral factor.

For example, if the minAnswer for TokenA is set at 1 dollar and its actual price drops to 0.10 dollars, the aggregator persists in reporting a value of $1. This results in the associated function calls recognizing a value that's tenfold greater than TokenA's real worth.

It's important to note that while Chainlink oracles form part of the OracleAggregator system and the use of a combination of oracles could potentially prevent such a situation, there's still a risk. Secondary oracles, such as Band, could potentially be exploited by a malicious user who can DDOS relayers to prevent price updates. Once the price becomes stale, the Chainlink oracle's price would be the sole reference, posing a significant risk.

Impact

In the event of an asset crash (like LUNA), the protocol can be manipulated to handle calls at an inflated price.

Tools Used

  • Manual Review

Recommendations

The function _getChainlinkResponse should cross-check the returned answer agains the minAnswer/maxAnswer and revert if the answer is outside of these bounds:

(
uint80 _latestRoundId,
int256 _latestAnswer,
/* uint256 _startedAt */,
uint256 _latestTimestamp,
/* uint80 _answeredInRound */
) = AggregatorV3Interface(_feed).latestRoundData();
if(answer >= maxAnswer or price <=minAnswer) revert();
_chainlinkResponse.roundId = _latestRoundId;
_chainlinkResponse.answer = _latestAnswer;
_chainlinkResponse.timestamp = _latestTimestamp;
_chainlinkResponse.success = true;
return _chainlinkResponse;
Updates

Lead Judging Commences

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

Chainlink oracle minAnswer check

Impact: Medium Likelihood: Low

Support

FAQs

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