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 }