Bug Description
As it can be deployed to L2 like Optimism, Arbitrum, it should check sequencer uptime feed to check if L2 sequencer is down or restarted in less than grace period.
In that cases, price feed will stop being updated and become stale, thus it might trigger massive liquidation events.
https://docs.chain.link/data-feeds/l2-sequencer-feeds
To prevent such scenario, Aave v3 for example pause liquidation and borrowing when sequencer is down or restarted in less than grace period.
https://docs.aave.com/developers/core-contracts/priceoraclesentinel
https://github.com/aave/aave-v3-core/blob/27a6d5c83560694210849d4abf09a09dec8da388/contracts/protocol/configuration/PriceOracleSentinel.sol#L57C1-L81C1
function isBorrowAllowed() public view override returns (bool) {
return _isUpAndGracePeriodPassed();
}
function isLiquidationAllowed() public view override returns (bool) {
return _isUpAndGracePeriodPassed();
}
* @notice Checks the sequencer oracle is healthy: is up and grace period passed.
* @return True if the SequencerOracle is up and the grace period passed, false otherwise
*/
function _isUpAndGracePeriodPassed() internal view returns (bool) {
(, int256 answer, , uint256 lastUpdateTimestamp, ) = _sequencerOracle.latestRoundData();
return answer == 0 && block.timestamp - lastUpdateTimestamp > _gracePeriod;
}
function setSequencerOracle(address newSequencerOracle) public onlyPoolAdmin {
_sequencerOracle = ISequencerOracle(newSequencerOracle);
emit SequencerOracleUpdated(newSequencerOracle);
}
Impact
An innocent user might be a target of unfair massive liquidation by MEV searchers in case of L2 sequencer down event.
Recommendation
address private s_sequencerOracle;
+ function _isUpAndGracePeriodPassed() internal view returns (bool) {
+ if(s_sequencerOracle == address(0)) return true;
+ (, int256 answer, , uint256 lastUpdateTimestamp, ) = (AggregatorV3Interface(s_sequencerOracle).latestRoundData();
+ return answer == 0 && block.timestamp - lastUpdateTimestamp > _gracePeriod;
+ }
function liquidate(address collateral, address user, uint256 debtToCover)
external
moreThanZero(debtToCover)
nonReentrant
{
+ require(_isUpAndGracePeriodPassed(), DSCEngine_SequencerDownEvent);
...
}