Summary
The hardcoded TIMEOUT
value in the OracleLib.sol
contract may result in the system timing out constantly between heartbeats for price feeds with longer heartbeat durations. This approach is considered bad practice.
Can find the Heartbeat duration of Chainlink Data Feeds Here
Vulnerability Details
The getTimeOut
function in the OracleLib.sol
contract uses a hardcoded TIMEOUT
value. This can be problematic, especially for price feeds with longer heartbeat durations, as the system may timeout consistently between heartbeats. Using the same TIMEOUT
value for all price feeds, regardless of their individual heartbeat durations, is not optimal or safe.
function getTimeout(AggregatorV3Interface ) public pure returns (uint256) {
return TIMEOUT;
}
Impact
The impact of this issue could lead to constant system timeouts between heartbeats for price feeds that have longer heartbeat durations. This may result in data fetching failures or unnecessary system delays.
Tools Used
The issue was identified through manual code review.
Recommendations
Consider refactoring the getSepoliaEthConfig
and getOrCreateAnvilEthConfig
functions to include the heartbeat duration for each price feed:
function getSepoliaEthConfig() public view returns (NetworkConfig memory) {
return NetworkConfig({
wethUsdPriceFeed: 0x694AA1769357215DE4FAC081bf1f309aDC325306,
wbtcUsdPriceFeed: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43,
weth: 0xdd13E55209Fd76AfE204dBda4007C227904f0a81,
wbtc: 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063,
deployerKey: vm.envUint("PRIVATE_KEY"),
heartbeatWethUsd: ,
heartbeatBtcUsd:
});
}
function getOrCreateAnvilEthConfig() public view returns (NetworkConfig memory) {
return NetworkConfig({
wethUsdPriceFeed: address(ethUsdPriceFeed),
wbtcUsdPriceFeed: address(btcUsdPriceFeed),
weth: address(wethMock),
wbtc: address(wbtcMock),
deployerKey: DEFAULT_ANVIL_KEY,
heartbeatWethUsd: ,
heartbeatBtcUsd:
});
}
Refactor the constructor to accept an array of heartbeat values, and store them in a new mapping:
constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, uint256[] memory heartbeats, address dscAddress) {
if (tokenAddresses.length != priceFeedAddresses.length || tokenAddresses.length != heartbeats.length) {
revert DSCEngine__TokenAddressesAndPriceFeedAddressesMustBeSameLength();
}
for (uint256 i = 0; i < tokenAddresses.length; i++) {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_heartbeats[tokenAddresses[i]] = heartbeats[i];
s_collateralTokens.push(tokenAddresses[i]);
}
i_dsc = DecentralizedStableCoin(dscAddress);
}
Finally, in OracleLib.sol
, import NetworkConfig
and use the specific timeout for each price feed in the staleCheckLatestRoundData
and getTimeout
functions:
function staleCheckLatestRoundData(AggregatorV3Interface priceFeed)
public
view
returns (uint80, int256, uint256, uint256, uint80)
{
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
priceFeed.latestRoundData();
uint256 secondsSince = block.timestamp - updatedAt;
if (secondsSince > getTimeout) revert OracleLib__StalePrice();
return (roundId, answer, startedAt, updatedAt, answeredInRound);
}
function getTimeout(AggregatorV3Interface priceFeed) public view returns (uint256) {
return NetworkConfig(priceFeed).heartbeat;
}
This refactoring allows the system to use the appropriate heartbeat duration for each price feed, preventing constant timeouts and improving system efficiency.