DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Valid

Inadequate checks to confirm the correct status of the sequencerUptimeFeed in `ChainlinkUtil.getPrice()`

Summary

The ChainlinkUtil contract has sequencerUptimeFeed checks in place to assert if the sequencer on the L2 is running but these checks are not implemented correctly. According to the chainlink docs the sequencerUptimeFeed can return a 0 value for startedAt if it is called during an "invalid round".
chainlink sequencer

Vulnerability Details

Consider a scenario where a round begins with `startedAt` recorded as 0 and the answer is initially set to 0. According to the documentation, an `answer` of 0 indicates the sequencer is up, while an `answer` of 1 means the sequencer is down. However, in this situation, both `answer` and `startedAt` can initially be `0` until all data is received from oracles and the update is confirmed. After this, the values are adjusted to reflect the accurate status of the sequencer.

in such a scenario the checks in `ChainlinkUtil.getPrice()` will not sufficiently check against an invalid sequencer as `answer` will be zero and so will `startedAt` passing the second check as `block.timestamp` minus o will still be block.timestamp which is greater than `SEQUENCER_GRACE_PERIOD_TIME` (3600).

Code Snippet Link

```solidity

function getPrice(

IAggregatorV3 priceFeed,

uint32 priceFeedHeartbeatSeconds,

IAggregatorV3 sequencerUptimeFeed

)

internal

view

returns (UD60x18 price)

{

uint8 priceDecimals = priceFeed.decimals();

// should revert if priceDecimals > 18

if (priceDecimals > Constants.SYSTEM_DECIMALS) {

revert Errors.InvalidOracleReturn();

}

if (address(sequencerUptimeFeed) != address(0)) {

try sequencerUptimeFeed.latestRoundData() returns (

uint80, int256 answer, uint256 startedAt, uint256, uint80

) {

@> bool isSequencerUp = answer == 0;

if (!isSequencerUp) {

revert Errors.OracleSequencerUptimeFeedIsDown(address(sequencerUptimeFeed));

}

@> uint256 timeSinceUp = block.timestamp - startedAt;

@> if (timeSinceUp <= Constants.SEQUENCER_GRACE_PERIOD_TIME) {

revert Errors.GracePeriodNotOver();

}

} catch {

revert Errors.InvalidSequencerUptimeFeedReturn();

}

}

//More Code...

}

```

It has been established that `startedAt` is another important value that must be verified to ensure an active sequencer, with the current implementation of `getPrice()` an invalid can still be passed active thus defeating the purpose of a sequencerFeed check

Impact

This will lead to `getPrice()` not reverting in an invalid round, which goes against the intention of the developers.

Tools Used

Manual Review

Recommendations

A check should be added that reverts if `startedAt` is returned as 0.

```solidity

function getPrice(

IAggregatorV3 priceFeed,

uint32 priceFeedHeartbeatSeconds,

IAggregatorV3 sequencerUptimeFeed

)

internal

view

returns (UD60x18 price)

{

uint8 priceDecimals = priceFeed.decimals();

// should revert if priceDecimals > 18

if (priceDecimals > Constants.SYSTEM_DECIMALS) {

revert Errors.InvalidOracleReturn();

}

if (address(sequencerUptimeFeed) != address(0)) {

try sequencerUptimeFeed.latestRoundData() returns (

uint80, int256 answer, uint256 startedAt, uint256, uint80

) {

bool isSequencerUp = answer == 0;

if (!isSequencerUp) {

revert Errors.OracleSequencerUptimeFeedIsDown(address(sequencerUptimeFeed));

}

@> if (startedAt = 0) {

@> revert Errors.OracleSequencerUptimeFeedIsDown(address(sequencerUptimeFeed));

}

uint256 timeSinceUp = block.timestamp - startedAt;

if (timeSinceUp <= Constants.SEQUENCER_GRACE_PERIOD_TIME) {

revert Errors.GracePeriodNotOver();

}

} catch {

revert Errors.InvalidSequencerUptimeFeedReturn();

}

}

//More Code...

}

```

Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Inadequate implementation of sequencer check

Support

FAQs

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