DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Invalid

Chainlink oracle will return the wrong price if the aggregator hits `minAnswer`

Summary

Vulnerability Details

Chainlink aggregators have a built-in circuit breaker if the price of an asset goes outside of a predetermined price band.
The result is that if an asset experiences a huge drop in value(i.e.LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset.
This would allow users to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNAcrashed.

This vulnerability happens in this protocol because the functions baseOracleCircuitBreaker and oracleCircuitBreaker only check if the price is <= 0, which is not sufficient.

function baseOracleCircuitBreaker(
uint256 protocolPrice,
uint80 roundId,
int256 chainlinkPrice,
uint256 timeStamp,
uint256 chainlinkPriceInEth
) private view returns (uint256 _protocolPrice) {
bool invalidFetchData = roundId == 0 || timeStamp == 0
|| timeStamp > block.timestamp || chainlinkPrice <= 0
|| block.timestamp > 2 hours + timeStamp;
uint256 chainlinkDiff = chainlinkPriceInEth > protocolPrice
? chainlinkPriceInEth - protocolPrice
: protocolPrice - chainlinkPriceInEth;
bool priceDeviation =
protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;
//@dev if there is issue with chainlink, get twap price. Compare twap and chainlink
if (invalidFetchData || priceDeviation) {
uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(
Constants.UNISWAP_WETH_BASE_AMT, 30 minutes
);
uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;
uint256 twapPriceInv = twapPriceInEther.inv();
if (twapPriceInEther == 0) {
revert Errors.InvalidTwapPrice();
}
if (invalidFetchData) {
return twapPriceInv;
} else {
uint256 twapDiff = twapPriceInv > protocolPrice
? twapPriceInv - protocolPrice
: protocolPrice - twapPriceInv;
//@dev save the price that is closest to saved oracle price
if (chainlinkDiff <= twapDiff) {
return chainlinkPriceInEth;
}
//@dev In case USDC_WETH suddenly has no liquidity
IERC20 weth = IERC20(Constants.WETH);
uint256 wethBal = weth.balanceOf(Constants.USDC_WETH);
if (wethBal < 100 ether) revert Errors.InsufficientEthInLiquidityPool();
return twapPriceInv;
}
} else {
return chainlinkPriceInEth;
}
}
function oracleCircuitBreaker(
uint80 roundId,
uint80 baseRoundId,
int256 chainlinkPrice,
int256 baseChainlinkPrice,
uint256 timeStamp,
uint256 baseTimeStamp
) private view {
bool invalidFetchData = roundId == 0 || timeStamp == 0
|| timeStamp > block.timestamp || chainlinkPrice <= 0 || baseRoundId == 0
|| baseTimeStamp == 0 || baseTimeStamp > block.timestamp
|| baseChainlinkPrice <= 0;
if (invalidFetchData) revert Errors.InvalidPrice();
}

Impact

Financial losses

Tools Used

Manual

Recommendations

You should check if the price of the token has reached a certain minValue/maxValue you define and then revert accordingly.

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issues

Support

FAQs

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