DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: low
Valid

Unhandled Chainlink revert can deny access to oracle prices

Summary

The LibOracle::getOraclePrice() lacks a preventive approach to handling the case of the Chainlink's latestRoundData() reverts, resulting in a permanent denial of service to the Ditto protocol.

Vulnerability Details

The getOraclePrice() makes use of Chainlink's latestRoundData() in L32 and L55 to get the latest prices for the USD asset (ETH/USD feed) and an asset other than the USD, respectively. The calls to the latestRoundData() can be reverted for several reasons, such as Chainlink's multisigs block access to price feeds, etc.

However, there is no preventive approach to handling the case of the latestRoundData() reverts, resulting in a permanent denial of service to the Ditto protocol.

function getOraclePrice(address asset) internal view returns (uint256) {
AppStorage storage s = appStorage();
AggregatorV3Interface baseOracle = AggregatorV3Interface(s.baseOracle);
uint256 protocolPrice = getPrice(asset);
// prettier-ignore
(
uint80 baseRoundID,
int256 basePrice,
/*uint256 baseStartedAt*/
,
uint256 baseTimeStamp,
/*uint80 baseAnsweredInRound*/
@> ) = baseOracle.latestRoundData();
AggregatorV3Interface oracle = AggregatorV3Interface(s.asset[asset].oracle);
if (address(oracle) == address(0)) revert Errors.InvalidAsset();
if (oracle == baseOracle) {
//@dev multiply base oracle by 10**10 to give it 18 decimals of precision
uint256 basePriceInEth = basePrice > 0
? uint256(basePrice * Constants.BASE_ORACLE_DECIMALS).inv()
: 0;
basePriceInEth = baseOracleCircuitBreaker(
protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth
);
return basePriceInEth;
} else {
// prettier-ignore
(
uint80 roundID,
int256 price,
/*uint256 startedAt*/
,
uint256 timeStamp,
/*uint80 answeredInRound*/
@> ) = oracle.latestRoundData();
uint256 priceInEth = uint256(price).div(uint256(basePrice));
oracleCircuitBreaker(
roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp
);
return priceInEth;
}
}
  • The use of Chainlink's latestRoundData() for USD asset (ETH/USD feed): https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibOracle.sol#L32

  • The use of Chainlink's latestRoundData() for an asset other than the USD: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibOracle.sol#L55

Impact

Chainlink's latestRoundData() reverts can permanently brick the Ditto protocol.

Tools Used

Manual Review

Recommendations

Wrap both calls (in L32 and L55) to the latestRoundData() in try/catch blocks and handle any errors appropriately (e.g., fallback to Uniswap's TWAP oracle).

//@audit -- an example of try/catch block for the Chainlink's latestRoundData()
try baseOracle.latestRoundData() returns (
uint80 baseRoundID,
int256 basePrice,
/*uint256 baseStartedAt*/
,
uint256 baseTimeStamp,
/*uint80 baseAnsweredInRound*/
) {
// Handle the successful execution here
...
} catch Error(string memory) {
// Handle errors here: (e.g., fallback to Uniswap's TWAP oracle)
...
}
Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-57

Support

FAQs

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