Summary
_badChainlinkResponse()
function in both the ChainlinkARBOracle
and ChainlinkOracle
contracts are missing the stale data check.
Vulnerability Details
None of the oracle calls check for stale prices, for example
(
uint80 _latestRoundId,
int256 _latestAnswer,
,
uint256 _latestTimestamp,
@>
) = AggregatorV3Interface(_feed).latestRoundData();
function _badChainlinkResponse(ChainlinkResponse memory response) internal view returns (bool) {
if (!response.success) { return true; }
if (response.roundId == 0) { return true; }
if (response.timestamp == 0 || response.timestamp > block.timestamp) { return true; }
if (response.answer == 0) { return true; }
return false;
}
Impact
Oracle data feeds can return stale pricing data for a variety of reasons. If the returned pricing data is stale, the getChainlinkResponse
function will execute with prices that don’t reflect the current pricing resulting in a potential loss of funds due to incorrect calculations.
Tools Used
Manual
Recommendations
Read the _answeredInRound parameter from the calls to latestRoundData() and consider implementing the following changes in both ChainlinkARBOracle
and ChainlinkOracle
contracts :
function _getChainlinkResponse
:
(
uint80 _latestRoundId,
int256 _latestAnswer,
/* uint256 _startedAt */,
uint256 _latestTimestamp,
- /* uint80 _answeredInRound */
) = AggregatorV3Interface(_feed).latestRoundData();
(
uint80 _latestRoundId,
int256 _latestAnswer,
/* uint256 _startedAt */,
uint256 _latestTimestamp,
+ /* uint80 _answeredInRound */
) = AggregatorV3Interface(_feed).latestRoundData();
function _badChainlinkResponse
:
function _badChainlinkResponse(ChainlinkResponse memory response) internal view returns (bool) {
// Check for response call reverted
if (!response.success) { return true; }
// Check for an invalid roundId that is 0
if (response.roundId == 0) { return true; }
// Check for an invalid timeStamp that is 0, or in the future
if (response.timestamp == 0 || response.timestamp > block.timestamp) { return true; }
// Check for non-positive price
if (response.answer == 0) { return true; }
// Check for stale price
+ if (response.answeredInRound >= response.roundId) { return true; }
return false;
}