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

Protocol users will be unable to liquidate / call any function if any one of the Chainlink Aggregator Feeds reverts

Summary

If Chainlink Aggregator Feed reverts due to stale time, the whole protocol will be unusable since most important functions like addCollateral, liquidate, removeCollateral, mint and burn calls _revertIfHealthFactorIsBroken().

Vulnerability Details

  1. Most important functions in DSCEngine.sol calls _revertIfHealthFactorIsBroken(). One such example is the minting of the DSC stablecoin

function mintDsc(uint256 amountDscToMint) public moreThanZero(amountDscToMint) nonReentrant {
s_DSCMinted[msg.sender] += amountDscToMint;
// if they minted too much ($150 DSC, $100 ETH)
@> _revertIfHealthFactorIsBroken(msg.sender);
bool minted = i_dsc.mint(msg.sender, amountDscToMint);
if (!minted) {
revert DSCEngine__MintFailed();
}
}
  1. _revertIfHealthFactorIsBroken() calls _healthFactor() -> _getAccountInformation() -> getAccountCollateralValue()

function getAccountCollateralValue(address user) public view returns (uint256 totalCollateralValueInUsd) {
// loop through each collateral token, get the amount they have deposited, and map it to
// the price, to get the USD value
for (uint256 i = 0; i < s_collateralTokens.length; i++) {
address token = s_collateralTokens[i];
uint256 amount = s_collateralDeposited[user][token];
totalCollateralValueInUsd += getUsdValue(token, amount);
}
return totalCollateralValueInUsd;
}
  1. getAccountCollateralValue() calls getUsdValue() which calls the Chainlink Aggregator Feed.

function getUsdValue(address token, uint256 amount) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
// 1 ETH = $1000
// The returned value from CL will be 1000 * 1e8
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
}
  1. The Chainlink Aggregator Feed is capable of reverting if the returned price is more than 3 hours stale.

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);
}

It is also good to note that a user can use more than one type of collateral, depending on the protocol. The protocol simply aggregates all the USD value into one whole value for the user.

Let's say a user uses 10 WBTC, 10 WETH and a 1 ABC token for collateral. WBTC is worth $30,000 , WETH is worth $2,000 and ABC token is worth $1. The user decides to put in 1 ABC token for fun, so his collateral is $320,001 and can afford to take a maximum loan of $160,000.5. If the ABC token oracle on Chainlink fails, $320,000 worth of assets is locked in the protocol just because of 1 oracle issue. The faulty oracle should either be sidestepped or something should make for a temporary solution.

Also noted that protocol comments states that this functionality is by design, and that's why I set as low severity and provide a potential solution, but I still think that it's important to find a solution instead of simply freezing the whole protocol since a lot of money may be at stake.

* If a price is stale, the function will revert, and render the DSCEngine unusable - this is by design.
* We want the DSCEngine to freeze if prices become stale.
*
* So if the Chainlink network explodes and you have a lot of money locked in the protocol... too bad.

Impact

Protocol does not work if one oracle feed is down.

Tools Used

Manual Review

Recommendations

Recommend diverting to the latest price of the token, or having a backup oracle like Liquity oracles...

Support

FAQs

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