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.