15,000 USDC
View results
Submission Details
Severity: medium
Valid

Incorrect assumption about price feed heartbeat can freeze the system even if the price is not stale

Summary

OracleLib library is designed to revert if the price feed answer has not been updated in the last TIMEOUT seconds. The TIMEOUT value is hardcoded to 3 hours. However, many ChainLink price feeds have heartbeats bigger than 3 hours. If any of those feeds is utilized, in the times of low volatillity the system will freeze even though the oracle is functionning correctly.

Vulnerability Details

In every place in the DSCEngine contract where the current USD price is needed for calculations, staleCheckLatestRoundData method from OracleLib is utlized to obtain it. This method wraps the AggregatorV3's latestRoundData method with the safety check for stale prices:

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 > TIMEOUT) revert OracleLib__StalePrice();
return (roundId, answer, startedAt, updatedAt, answeredInRound);
}

This functionality is designed to freeze the system if the price goes stale. The TIMEOUT constant is currently hardcoded to 3 hours:

uint256 private constant TIMEOUT = 3 hours; // 3 * 60 * 60 = 10800 seconds

While this value is appropriate for token basket used in the deploy script (WETH and WBTC, both with price feed heartbeat of 1 hour), the documentation states:

The system is meant to be such that someone could fork this codebase, swap out WETH & WBTC for any basket of assets they like, and the code would work the same.

This assumption is not true for all the price feeds. As per ChainLink documentation, there are multiple price feeds with much longer heartbeats, such as 24 hours. Examples of those are: ADA/USD, ATOM/USD, USDT/USD and many more.

Impact

Using any of tokens with bigger price feed heartbeats in the basket will result is a severely limited availability of the system. Please consider the following scenario:

  1. The Foundry DeFi Stablecoin codebase is forked and deployed to Ethereum mainnet with the WADA token included in the assets basket. The ADA/USD price feed's heartbeat is 86400 seconds.

  2. In times of low volatility oracle will update the answer only after the heartbeat has passed - once every 24 hours. The following operations will only work for 3 hours a day, reverting with OracleLib__StalePrice() error in the remaining 21 hours, even if the price has not really gone stale and the price feed is behaving as expected.

    • Redeeming collateral for the user who has ADA in the collateral basket

    • Minting DSC for the user who has ADA in the collateral basket

    • Burning DSC by the user who has ADA in the collateral basket

    • Liquidating the user who has ADA in their collateral basket

    • Getting collateral value and account information of the user who has ADA in their collateral basket

Tools Used

Manual Review

Recommendations

Changing the TIMEOUT to a bigger hardcoded value to cover all of the feeds' heartbeats is not recommended, as it may result in the system catching the stale prices too late. Please consider utilizing different heartbeats per each price feed instead of relying on one-fits-all hardcoded value.

Support

FAQs

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