github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L1/L2OutputOracle.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
     5  import { ISemver } from "src/universal/ISemver.sol";
     6  import { Types } from "src/libraries/Types.sol";
     7  import { Constants } from "src/libraries/Constants.sol";
     8  
     9  /// @custom:proxied
    10  /// @title L2OutputOracle
    11  /// @notice The L2OutputOracle contains an array of L2 state outputs, where each output is a
    12  ///         commitment to the state of the L2 chain. Other contracts like the OptimismPortal use
    13  ///         these outputs to verify information about the state of L2.
    14  contract L2OutputOracle is Initializable, ISemver {
    15      /// @notice The number of the first L2 block recorded in this contract.
    16      uint256 public startingBlockNumber;
    17  
    18      /// @notice The timestamp of the first L2 block recorded in this contract.
    19      uint256 public startingTimestamp;
    20  
    21      /// @notice An array of L2 output proposals.
    22      Types.OutputProposal[] internal l2Outputs;
    23  
    24      /// @notice The interval in L2 blocks at which checkpoints must be submitted.
    25      /// @custom:network-specific
    26      uint256 public submissionInterval;
    27  
    28      /// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
    29      /// @custom:network-specific
    30      uint256 public l2BlockTime;
    31  
    32      /// @notice The address of the challenger. Can be updated via upgrade.
    33      /// @custom:network-specific
    34      address public challenger;
    35  
    36      /// @notice The address of the proposer. Can be updated via upgrade.
    37      /// @custom:network-specific
    38      address public proposer;
    39  
    40      /// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized.
    41      /// @custom:network-specific
    42      uint256 public finalizationPeriodSeconds;
    43  
    44      /// @notice Emitted when an output is proposed.
    45      /// @param outputRoot    The output root.
    46      /// @param l2OutputIndex The index of the output in the l2Outputs array.
    47      /// @param l2BlockNumber The L2 block number of the output root.
    48      /// @param l1Timestamp   The L1 timestamp when proposed.
    49      event OutputProposed(
    50          bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp
    51      );
    52  
    53      /// @notice Emitted when outputs are deleted.
    54      /// @param prevNextOutputIndex Next L2 output index before the deletion.
    55      /// @param newNextOutputIndex  Next L2 output index after the deletion.
    56      event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
    57  
    58      /// @notice Semantic version.
    59      /// @custom:semver 1.8.0
    60      string public constant version = "1.8.0";
    61  
    62      /// @notice Constructs the L2OutputOracle contract. Initializes variables to the same values as
    63      ///         in the getting-started config.
    64      constructor() {
    65          initialize({
    66              _submissionInterval: 1,
    67              _l2BlockTime: 1,
    68              _startingBlockNumber: 0,
    69              _startingTimestamp: 0,
    70              _proposer: address(0),
    71              _challenger: address(0),
    72              _finalizationPeriodSeconds: 0
    73          });
    74      }
    75  
    76      /// @notice Initializer.
    77      /// @param _submissionInterval  Interval in blocks at which checkpoints must be submitted.
    78      /// @param _l2BlockTime         The time per L2 block, in seconds.
    79      /// @param _startingBlockNumber The number of the first L2 block.
    80      /// @param _startingTimestamp   The timestamp of the first L2 block.
    81      /// @param _proposer            The address of the proposer.
    82      /// @param _challenger          The address of the challenger.
    83      /// @param _finalizationPeriodSeconds The minimum time (in seconds) that must elapse before a withdrawal
    84      ///                                   can be finalized.
    85      function initialize(
    86          uint256 _submissionInterval,
    87          uint256 _l2BlockTime,
    88          uint256 _startingBlockNumber,
    89          uint256 _startingTimestamp,
    90          address _proposer,
    91          address _challenger,
    92          uint256 _finalizationPeriodSeconds
    93      )
    94          public
    95          initializer
    96      {
    97          require(_submissionInterval > 0, "L2OutputOracle: submission interval must be greater than 0");
    98          require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
    99          require(
   100              _startingTimestamp <= block.timestamp,
   101              "L2OutputOracle: starting L2 timestamp must be less than current time"
   102          );
   103  
   104          submissionInterval = _submissionInterval;
   105          l2BlockTime = _l2BlockTime;
   106          startingBlockNumber = _startingBlockNumber;
   107          startingTimestamp = _startingTimestamp;
   108          proposer = _proposer;
   109          challenger = _challenger;
   110          finalizationPeriodSeconds = _finalizationPeriodSeconds;
   111      }
   112  
   113      /// @notice Getter for the submissionInterval.
   114      ///         Public getter is legacy and will be removed in the future. Use `submissionInterval` instead.
   115      /// @return Submission interval.
   116      /// @custom:legacy
   117      function SUBMISSION_INTERVAL() external view returns (uint256) {
   118          return submissionInterval;
   119      }
   120  
   121      /// @notice Getter for the l2BlockTime.
   122      ///         Public getter is legacy and will be removed in the future. Use `l2BlockTime` instead.
   123      /// @return L2 block time.
   124      /// @custom:legacy
   125      function L2_BLOCK_TIME() external view returns (uint256) {
   126          return l2BlockTime;
   127      }
   128  
   129      /// @notice Getter for the challenger address.
   130      ///         Public getter is legacy and will be removed in the future. Use `challenger` instead.
   131      /// @return Address of the challenger.
   132      /// @custom:legacy
   133      function CHALLENGER() external view returns (address) {
   134          return challenger;
   135      }
   136  
   137      /// @notice Getter for the proposer address.
   138      ///         Public getter is legacy and will be removed in the future. Use `proposer` instead.
   139      /// @return Address of the proposer.
   140      /// @custom:legacy
   141      function PROPOSER() external view returns (address) {
   142          return proposer;
   143      }
   144  
   145      /// @notice Getter for the finalizationPeriodSeconds.
   146      ///         Public getter is legacy and will be removed in the future. Use `finalizationPeriodSeconds` instead.
   147      /// @return Finalization period in seconds.
   148      /// @custom:legacy
   149      function FINALIZATION_PERIOD_SECONDS() external view returns (uint256) {
   150          return finalizationPeriodSeconds;
   151      }
   152  
   153      /// @notice Deletes all output proposals after and including the proposal that corresponds to
   154      ///         the given output index. Only the challenger address can delete outputs.
   155      /// @param _l2OutputIndex Index of the first L2 output to be deleted.
   156      ///                       All outputs after this output will also be deleted.
   157      function deleteL2Outputs(uint256 _l2OutputIndex) external {
   158          require(msg.sender == challenger, "L2OutputOracle: only the challenger address can delete outputs");
   159  
   160          // Make sure we're not *increasing* the length of the array.
   161          require(
   162              _l2OutputIndex < l2Outputs.length, "L2OutputOracle: cannot delete outputs after the latest output index"
   163          );
   164  
   165          // Do not allow deleting any outputs that have already been finalized.
   166          require(
   167              block.timestamp - l2Outputs[_l2OutputIndex].timestamp < finalizationPeriodSeconds,
   168              "L2OutputOracle: cannot delete outputs that have already been finalized"
   169          );
   170  
   171          uint256 prevNextL2OutputIndex = nextOutputIndex();
   172  
   173          // Use assembly to delete the array elements because Solidity doesn't allow it.
   174          assembly {
   175              sstore(l2Outputs.slot, _l2OutputIndex)
   176          }
   177  
   178          emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
   179      }
   180  
   181      /// @notice Accepts an outputRoot and the timestamp of the corresponding L2 block.
   182      ///         The timestamp must be equal to the current value returned by `nextTimestamp()` in
   183      ///         order to be accepted. This function may only be called by the Proposer.
   184      /// @param _outputRoot    The L2 output of the checkpoint block.
   185      /// @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
   186      /// @param _l1BlockHash   A block hash which must be included in the current chain.
   187      /// @param _l1BlockNumber The block number with the specified block hash.
   188      function proposeL2Output(
   189          bytes32 _outputRoot,
   190          uint256 _l2BlockNumber,
   191          bytes32 _l1BlockHash,
   192          uint256 _l1BlockNumber
   193      )
   194          external
   195          payable
   196      {
   197          require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs");
   198  
   199          require(
   200              _l2BlockNumber == nextBlockNumber(),
   201              "L2OutputOracle: block number must be equal to next expected block number"
   202          );
   203  
   204          require(
   205              computeL2Timestamp(_l2BlockNumber) < block.timestamp,
   206              "L2OutputOracle: cannot propose L2 output in the future"
   207          );
   208  
   209          require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash");
   210  
   211          if (_l1BlockHash != bytes32(0)) {
   212              // This check allows the proposer to propose an output based on a given L1 block,
   213              // without fear that it will be reorged out.
   214              // It will also revert if the blockheight provided is more than 256 blocks behind the
   215              // chain tip (as the hash will return as zero). This does open the door to a griefing
   216              // attack in which the proposer's submission is censored until the block is no longer
   217              // retrievable, if the proposer is experiencing this attack it can simply leave out the
   218              // blockhash value, and delay submission until it is confident that the L1 block is
   219              // finalized.
   220              require(
   221                  blockhash(_l1BlockNumber) == _l1BlockHash,
   222                  "L2OutputOracle: block hash does not match the hash at the expected height"
   223              );
   224          }
   225  
   226          emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp);
   227  
   228          l2Outputs.push(
   229              Types.OutputProposal({
   230                  outputRoot: _outputRoot,
   231                  timestamp: uint128(block.timestamp),
   232                  l2BlockNumber: uint128(_l2BlockNumber)
   233              })
   234          );
   235      }
   236  
   237      /// @notice Returns an output by index. Needed to return a struct instead of a tuple.
   238      /// @param _l2OutputIndex Index of the output to return.
   239      /// @return The output at the given index.
   240      function getL2Output(uint256 _l2OutputIndex) external view returns (Types.OutputProposal memory) {
   241          return l2Outputs[_l2OutputIndex];
   242      }
   243  
   244      /// @notice Returns the index of the L2 output that checkpoints a given L2 block number.
   245      ///         Uses a binary search to find the first output greater than or equal to the given
   246      ///         block.
   247      /// @param _l2BlockNumber L2 block number to find a checkpoint for.
   248      /// @return Index of the first checkpoint that commits to the given L2 block number.
   249      function getL2OutputIndexAfter(uint256 _l2BlockNumber) public view returns (uint256) {
   250          // Make sure an output for this block number has actually been proposed.
   251          require(
   252              _l2BlockNumber <= latestBlockNumber(),
   253              "L2OutputOracle: cannot get output for a block that has not been proposed"
   254          );
   255  
   256          // Make sure there's at least one output proposed.
   257          require(l2Outputs.length > 0, "L2OutputOracle: cannot get output as no outputs have been proposed yet");
   258  
   259          // Find the output via binary search, guaranteed to exist.
   260          uint256 lo = 0;
   261          uint256 hi = l2Outputs.length;
   262          while (lo < hi) {
   263              uint256 mid = (lo + hi) / 2;
   264              if (l2Outputs[mid].l2BlockNumber < _l2BlockNumber) {
   265                  lo = mid + 1;
   266              } else {
   267                  hi = mid;
   268              }
   269          }
   270  
   271          return lo;
   272      }
   273  
   274      /// @notice Returns the L2 output proposal that checkpoints a given L2 block number.
   275      ///         Uses a binary search to find the first output greater than or equal to the given
   276      ///         block.
   277      /// @param _l2BlockNumber L2 block number to find a checkpoint for.
   278      /// @return First checkpoint that commits to the given L2 block number.
   279      function getL2OutputAfter(uint256 _l2BlockNumber) external view returns (Types.OutputProposal memory) {
   280          return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
   281      }
   282  
   283      /// @notice Returns the number of outputs that have been proposed.
   284      ///         Will revert if no outputs have been proposed yet.
   285      /// @return The number of outputs that have been proposed.
   286      function latestOutputIndex() external view returns (uint256) {
   287          return l2Outputs.length - 1;
   288      }
   289  
   290      /// @notice Returns the index of the next output to be proposed.
   291      /// @return The index of the next output to be proposed.
   292      function nextOutputIndex() public view returns (uint256) {
   293          return l2Outputs.length;
   294      }
   295  
   296      /// @notice Returns the block number of the latest submitted L2 output proposal.
   297      ///         If no proposals been submitted yet then this function will return the starting
   298      ///         block number.
   299      /// @return Latest submitted L2 block number.
   300      function latestBlockNumber() public view returns (uint256) {
   301          return l2Outputs.length == 0 ? startingBlockNumber : l2Outputs[l2Outputs.length - 1].l2BlockNumber;
   302      }
   303  
   304      /// @notice Computes the block number of the next L2 block that needs to be checkpointed.
   305      /// @return Next L2 block number.
   306      function nextBlockNumber() public view returns (uint256) {
   307          return latestBlockNumber() + submissionInterval;
   308      }
   309  
   310      /// @notice Returns the L2 timestamp corresponding to a given L2 block number.
   311      /// @param _l2BlockNumber The L2 block number of the target block.
   312      /// @return L2 timestamp of the given block.
   313      function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
   314          return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * l2BlockTime);
   315      }
   316  }