L2OutputOracle.sol 12.1 KB
Newer Older
1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.15;
3

4
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
5
import { Semver } from "../universal/Semver.sol";
6
import { Types } from "../libraries/Types.sol";
7

8 9 10 11 12
/// @custom:proxied
/// @title L2OutputOracle
/// @notice The L2OutputOracle contains an array of L2 state outputs, where each output is a
///         commitment to the state of the L2 chain. Other contracts like the OptimismPortal use
///         these outputs to verify information about the state of L2.
13
contract L2OutputOracle is Initializable, Semver {
14 15 16
    /// @notice The interval in L2 blocks at which checkpoints must be submitted.
    ///         Although this is immutable, it can safely be modified by upgrading the
    ///         implementation contract.
17 18
    uint256 public immutable SUBMISSION_INTERVAL;

19
    /// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
20
    uint256 public immutable L2_BLOCK_TIME;
21

22
    /// @notice The address of the challenger. Can be updated via upgrade.
23 24
    address public immutable CHALLENGER;

25
    /// @notice The address of the proposer. Can be updated via upgrade.
26 27
    address public immutable PROPOSER;

28
    /// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized.
29 30
    uint256 public immutable FINALIZATION_PERIOD_SECONDS;

31
    /// @notice The number of the first L2 block recorded in this contract.
32
    uint256 public startingBlockNumber;
33

34
    /// @notice The timestamp of the first L2 block recorded in this contract.
35
    uint256 public startingTimestamp;
36

37
    /// @notice An array of L2 output proposals.
38
    Types.OutputProposal[] internal l2Outputs;
39

40 41 42 43 44
    /// @notice Emitted when an output is proposed.
    /// @param outputRoot    The output root.
    /// @param l2OutputIndex The index of the output in the l2Outputs array.
    /// @param l2BlockNumber The L2 block number of the output root.
    /// @param l1Timestamp   The L1 timestamp when proposed.
45 46
    event OutputProposed(
        bytes32 indexed outputRoot,
47 48 49
        uint256 indexed l2OutputIndex,
        uint256 indexed l2BlockNumber,
        uint256 l1Timestamp
50 51
    );

52 53 54
    /// @notice Emitted when outputs are deleted.
    /// @param prevNextOutputIndex Next L2 output index before the deletion.
    /// @param newNextOutputIndex  Next L2 output index after the deletion.
55
    event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
56

57 58 59 60 61 62 63 64
    /// @custom:semver 1.3.1
    /// @notice Constructs the L2OutputOracle contract.
    /// @param _submissionInterval  Interval in blocks at which checkpoints must be submitted.
    /// @param _l2BlockTime         The time per L2 block, in seconds.
    /// @param _startingBlockNumber The number of the first L2 block.
    /// @param _startingTimestamp   The timestamp of the first L2 block.
    /// @param _proposer            The address of the proposer.
    /// @param _challenger          The address of the challenger.
65 66
    constructor(
        uint256 _submissionInterval,
67
        uint256 _l2BlockTime,
68 69
        uint256 _startingBlockNumber,
        uint256 _startingTimestamp,
70
        address _proposer,
71 72
        address _challenger,
        uint256 _finalizationPeriodSeconds
73
    ) Semver(1, 3, 1) {
74
        require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
75 76 77 78
        require(
            _submissionInterval > 0,
            "L2OutputOracle: submission interval must be greater than 0"
        );
79

80
        SUBMISSION_INTERVAL = _submissionInterval;
81
        L2_BLOCK_TIME = _l2BlockTime;
82 83
        PROPOSER = _proposer;
        CHALLENGER = _challenger;
84
        FINALIZATION_PERIOD_SECONDS = _finalizationPeriodSeconds;
85

86
        initialize(_startingBlockNumber, _startingTimestamp);
87 88
    }

89 90 91
    /// @notice Initializer.
    /// @param _startingBlockNumber Block number for the first recoded L2 block.
    /// @param _startingTimestamp   Timestamp for the first recoded L2 block.
92 93 94 95
    function initialize(uint256 _startingBlockNumber, uint256 _startingTimestamp)
        public
        initializer
    {
96 97 98 99 100
        require(
            _startingTimestamp <= block.timestamp,
            "L2OutputOracle: starting L2 timestamp must be less than current time"
        );

101
        startingTimestamp = _startingTimestamp;
102
        startingBlockNumber = _startingBlockNumber;
103 104
    }

105 106 107 108
    /// @notice Deletes all output proposals after and including the proposal that corresponds to
    ///         the given output index. Only the challenger address can delete outputs.
    /// @param _l2OutputIndex Index of the first L2 output to be deleted.
    ///                       All outputs after this output will also be deleted.
109
    // solhint-disable-next-line ordering
110
    function deleteL2Outputs(uint256 _l2OutputIndex) external {
111 112 113 114 115
        require(
            msg.sender == CHALLENGER,
            "L2OutputOracle: only the challenger address can delete outputs"
        );

116
        // Make sure we're not *increasing* the length of the array.
117
        require(
118 119
            _l2OutputIndex < l2Outputs.length,
            "L2OutputOracle: cannot delete outputs after the latest output index"
120 121
        );

122 123
        // Do not allow deleting any outputs that have already been finalized.
        require(
124
            block.timestamp - l2Outputs[_l2OutputIndex].timestamp < FINALIZATION_PERIOD_SECONDS,
125 126 127
            "L2OutputOracle: cannot delete outputs that have already been finalized"
        );

128
        uint256 prevNextL2OutputIndex = nextOutputIndex();
129

130 131 132 133
        // Use assembly to delete the array elements because Solidity doesn't allow it.
        assembly {
            sstore(l2Outputs.slot, _l2OutputIndex)
        }
134

135
        emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
136 137
    }

138 139 140 141 142 143 144
    /// @notice Accepts an outputRoot and the timestamp of the corresponding L2 block.
    ///         The timestamp must be equal to the current value returned by `nextTimestamp()` in
    ///         order to be accepted. This function may only be called by the Proposer.
    /// @param _outputRoot    The L2 output of the checkpoint block.
    /// @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
    /// @param _l1BlockHash   A block hash which must be included in the current chain.
    /// @param _l1BlockNumber The block number with the specified block hash.
145 146
    function proposeL2Output(
        bytes32 _outputRoot,
147
        uint256 _l2BlockNumber,
148
        bytes32 _l1BlockHash,
149
        uint256 _l1BlockNumber
150 151 152 153 154 155
    ) external payable {
        require(
            msg.sender == PROPOSER,
            "L2OutputOracle: only the proposer address can propose new outputs"
        );

156 157
        require(
            _l2BlockNumber == nextBlockNumber(),
158
            "L2OutputOracle: block number must be equal to next expected block number"
159
        );
160

161 162
        require(
            computeL2Timestamp(_l2BlockNumber) < block.timestamp,
163 164 165 166 167 168
            "L2OutputOracle: cannot propose L2 output in the future"
        );

        require(
            _outputRoot != bytes32(0),
            "L2OutputOracle: L2 output proposal cannot be the zero hash"
169
        );
170

171
        if (_l1BlockHash != bytes32(0)) {
172
            // This check allows the proposer to propose an output based on a given L1 block,
173 174 175
            // without fear that it will be reorged out.
            // It will also revert if the blockheight provided is more than 256 blocks behind the
            // chain tip (as the hash will return as zero). This does open the door to a griefing
176 177
            // attack in which the proposer's submission is censored until the block is no longer
            // retrievable, if the proposer is experiencing this attack it can simply leave out the
178 179 180
            // blockhash value, and delay submission until it is confident that the L1 block is
            // finalized.
            require(
181 182
                blockhash(_l1BlockNumber) == _l1BlockHash,
                "L2OutputOracle: block hash does not match the hash at the expected height"
183 184 185
            );
        }

186
        emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp);
187

188 189 190 191 192 193 194
        l2Outputs.push(
            Types.OutputProposal({
                outputRoot: _outputRoot,
                timestamp: uint128(block.timestamp),
                l2BlockNumber: uint128(_l2BlockNumber)
            })
        );
195 196
    }

197 198 199
    /// @notice Returns an output by index. Needed to return a struct instead of a tuple.
    /// @param _l2OutputIndex Index of the output to return.
    /// @return The output at the given index.
200
    function getL2Output(uint256 _l2OutputIndex)
Mark Tyneway's avatar
Mark Tyneway committed
201 202 203 204
        external
        view
        returns (Types.OutputProposal memory)
    {
205 206 207
        return l2Outputs[_l2OutputIndex];
    }

208 209 210 211 212
    /// @notice Returns the index of the L2 output that checkpoints a given L2 block number.
    ///         Uses a binary search to find the first output greater than or equal to the given
    ///         block.
    /// @param _l2BlockNumber L2 block number to find a checkpoint for.
    /// @return Index of the first checkpoint that commits to the given L2 block number.
213 214
    function getL2OutputIndexAfter(uint256 _l2BlockNumber) public view returns (uint256) {
        // Make sure an output for this block number has actually been proposed.
215
        require(
216 217
            _l2BlockNumber <= latestBlockNumber(),
            "L2OutputOracle: cannot get output for a block that has not been proposed"
218 219
        );

220
        // Make sure there's at least one output proposed.
221
        require(
222 223
            l2Outputs.length > 0,
            "L2OutputOracle: cannot get output as no outputs have been proposed yet"
224
        );
225

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
        // Find the output via binary search, guaranteed to exist.
        uint256 lo = 0;
        uint256 hi = l2Outputs.length;
        while (lo < hi) {
            uint256 mid = (lo + hi) / 2;
            if (l2Outputs[mid].l2BlockNumber < _l2BlockNumber) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }

        return lo;
    }

241 242 243 244 245
    /// @notice Returns the L2 output proposal that checkpoints a given L2 block number.
    ///         Uses a binary search to find the first output greater than or equal to the given
    ///         block.
    /// @param _l2BlockNumber L2 block number to find a checkpoint for.
    /// @return First checkpoint that commits to the given L2 block number.
246 247 248 249 250 251 252 253
    function getL2OutputAfter(uint256 _l2BlockNumber)
        external
        view
        returns (Types.OutputProposal memory)
    {
        return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
    }

254 255 256
    /// @notice Returns the number of outputs that have been proposed.
    ///         Will revert if no outputs have been proposed yet.
    /// @return The number of outputs that have been proposed.
257 258 259 260
    function latestOutputIndex() external view returns (uint256) {
        return l2Outputs.length - 1;
    }

261 262
    /// @notice Returns the index of the next output to be proposed.
    /// @return The index of the next output to be proposed.
263 264 265 266
    function nextOutputIndex() public view returns (uint256) {
        return l2Outputs.length;
    }

267 268 269 270
    /// @notice Returns the block number of the latest submitted L2 output proposal.
    ///         If no proposals been submitted yet then this function will return the starting
    ///         block number.
    /// @return Latest submitted L2 block number.
271 272 273 274 275
    function latestBlockNumber() public view returns (uint256) {
        return
            l2Outputs.length == 0
                ? startingBlockNumber
                : l2Outputs[l2Outputs.length - 1].l2BlockNumber;
276 277
    }

278 279
    /// @notice Computes the block number of the next L2 block that needs to be checkpointed.
    /// @return Next L2 block number.
280
    function nextBlockNumber() public view returns (uint256) {
281
        return latestBlockNumber() + SUBMISSION_INTERVAL;
282 283
    }

284 285 286
    /// @notice Returns the L2 timestamp corresponding to a given L2 block number.
    /// @param _l2BlockNumber The L2 block number of the target block.
    /// @return L2 timestamp of the given block.
287
    function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
288
        return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * L2_BLOCK_TIME);
289
    }
290
}