sherlock-audit / 2023-05-perennial-judging

12 stars 9 forks source link

SolidityATL - No check for active Arbitrum Sequencer in Chainlink feeds #167

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

SolidityATL

medium

No check for active Arbitrum Sequencer in Chainlink feeds

Summary

When using chainlink oracles on optimistic rollups, there must to be a validations that the L2 sequencer is up and active when consuming price feeds. Chainlink recommends that all Optimistic L2 oracles consult the Sequencer Uptime Feed to ensure that the sequencer is live before trusting the data returned by the oracle: https://docs.chain.link/data-feeds/l2-sequencer-feeds#overview

Vulnerability Detail

When the Arbitrum sequencer is down, data returned from the oracle should not be trusted.

Impact

The oracle feed is a key mechanism when settling positions, trusted bad oracle data can cause unexpected behavior in the protocol or be abused by malicious actors.

Code Snippet

https://github.com/equilibria-xyz/perennial-mono/blob/b06d5145db62a312dd88dfcafef0f8e2166c5e32/packages/perennial-oracle/contracts/types/ChainlinkAggregator.sol#L32-#L36

https://github.com/equilibria-xyz/perennial-mono/blob/b06d5145db62a312dd88dfcafef0f8e2166c5e32/packages/perennial-oracle/contracts/types/ChainlinkRegistry.sol#L34-#L38

Tool used

Manual Review

Recommendation

When fetching the latestRoundData on an Arbitrum feed in ChainlinkAggregator.sol and ChainlinkRegistry.sol first verify that the Sequencer is up

function getLatestRound(ChainlinkAggregator self) internal view returns (ChainlinkRound memory) {
    // Check that sequencer is up
    if (!isSequencerActive()) revert Errors.L2SequencerUnavailable();

    (uint80 roundId, int256 answer, , uint256 updatedAt, ) =
        AggregatorProxyInterface(ChainlinkAggregator.unwrap(self)).latestRoundData();
    return ChainlinkRound({roundId: roundId, timestamp: updatedAt, answer: answer});
}

// https://docs.chain.link/data-feeds/l2-sequencer-feeds
function isSequencerUp() internal view returns (bool) {
    address sequencerAddress = ...; // https://docs.chain.link/data-feeds/l2-sequencer-feeds#available-networks
    AggregatorV2V3Interface memory sequencerUptimeFeed = AggregatorV2V3Interface(sequencerAddress);

    (, int256 answer, uint256 startedAt, ,) = sequencerUptimeFeed.latestRoundData();

    // Answer == 0: Sequencer is up
    // Answer == 1: Sequencer is down
    return answer == 0
}

Duplicate of #13