L2OutputOracle.sol 11.6 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
 * @custom:proxied
10
 * @title L2OutputOracle
11 12 13
 * @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.
14
 */
15
contract L2OutputOracle is Initializable, Semver {
16
    /**
17 18
     * @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.
19
     */
20 21
    uint256 public immutable SUBMISSION_INTERVAL;

22
    /**
23
     * @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
24
     */
25
    uint256 public immutable L2_BLOCK_TIME;
26

27 28 29 30 31 32 33 34 35 36
    /**
     * @notice The address of the challenger. Can be updated via upgrade.
     */
    address public immutable CHALLENGER;

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

37
    /**
38
     * @notice The number of the first L2 block recorded in this contract.
39
     */
40
    uint256 public startingBlockNumber;
41

42
    /**
43
     * @notice The timestamp of the first L2 block recorded in this contract.
44
     */
45
    uint256 public startingTimestamp;
46

47
    /**
48
     * @notice Array of L2 output proposals.
49
     */
50
    Types.OutputProposal[] internal l2Outputs;
51

52 53 54 55
    /**
     * @notice Emitted when an output is proposed.
     *
     * @param outputRoot    The output root.
56
     * @param l2OutputIndex The index of the output in the l2Outputs array.
57
     * @param l2BlockNumber The L2 block number of the output root.
58
     * @param l1Timestamp   The L1 timestamp when proposed.
59 60 61
     */
    event OutputProposed(
        bytes32 indexed outputRoot,
62 63 64
        uint256 indexed l2OutputIndex,
        uint256 indexed l2BlockNumber,
        uint256 l1Timestamp
65 66 67
    );

    /**
68
     * @notice Emitted when outputs are deleted.
69
     *
70 71
     * @param prevNextOutputIndex Next L2 output index before the deletion.
     * @param newNextOutputIndex  Next L2 output index after the deletion.
72
     */
73
    event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
74

75
    /**
76
     * @custom:semver 1.0.0
77
     *
78 79 80 81 82 83
     * @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.
84 85 86
     */
    constructor(
        uint256 _submissionInterval,
87
        uint256 _l2BlockTime,
88 89
        uint256 _startingBlockNumber,
        uint256 _startingTimestamp,
90
        address _proposer,
91
        address _challenger
92
    ) Semver(1, 0, 0) {
93
        SUBMISSION_INTERVAL = _submissionInterval;
94
        L2_BLOCK_TIME = _l2BlockTime;
95 96
        PROPOSER = _proposer;
        CHALLENGER = _challenger;
97

98
        initialize(_startingBlockNumber, _startingTimestamp);
99 100 101 102 103
    }

    /**
     * @notice Initializer.
     *
104 105
     * @param _startingBlockNumber Block number for the first recoded L2 block.
     * @param _startingTimestamp   Timestamp for the first recoded L2 block.
106
     */
107 108 109 110
    function initialize(uint256 _startingBlockNumber, uint256 _startingTimestamp)
        public
        initializer
    {
111 112 113 114 115
        require(
            _startingTimestamp <= block.timestamp,
            "L2OutputOracle: starting L2 timestamp must be less than current time"
        );

116
        startingTimestamp = _startingTimestamp;
117
        startingBlockNumber = _startingBlockNumber;
118 119 120
    }

    /**
121
     * @notice Deletes all output proposals after and including the proposal that corresponds to
122
     *         the given output index. Only the challenger address can delete outputs.
123
     *
124 125
     * @param _l2OutputIndex Index of the first L2 output to be deleted. All outputs after this
     *                       output will also be deleted.
126
     */
127
    // solhint-disable-next-line ordering
128
    function deleteL2Outputs(uint256 _l2OutputIndex) external {
129 130 131 132 133
        require(
            msg.sender == CHALLENGER,
            "L2OutputOracle: only the challenger address can delete outputs"
        );

134
        // Make sure we're not *increasing* the length of the array.
135
        require(
136 137
            _l2OutputIndex < l2Outputs.length,
            "L2OutputOracle: cannot delete outputs after the latest output index"
138 139
        );

140
        uint256 prevNextL2OutputIndex = nextOutputIndex();
141

142 143 144 145
        // Use assembly to delete the array elements because Solidity doesn't allow it.
        assembly {
            sstore(l2Outputs.slot, _l2OutputIndex)
        }
146

147
        emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
148 149 150
    }

    /**
151 152 153
     * @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.
154
     *
155 156
     * @param _outputRoot    The L2 output of the checkpoint block.
     * @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
157
     * @param _l1BlockHash   A block hash which must be included in the current chain.
158
     * @param _l1BlockNumber The block number with the specified block hash.
159
     */
160 161
    function proposeL2Output(
        bytes32 _outputRoot,
162
        uint256 _l2BlockNumber,
163
        bytes32 _l1BlockHash,
164
        uint256 _l1BlockNumber
165 166 167 168 169 170
    ) external payable {
        require(
            msg.sender == PROPOSER,
            "L2OutputOracle: only the proposer address can propose new outputs"
        );

171 172
        require(
            _l2BlockNumber == nextBlockNumber(),
173
            "L2OutputOracle: block number must be equal to next expected block number"
174
        );
175

176 177
        require(
            computeL2Timestamp(_l2BlockNumber) < block.timestamp,
178 179 180 181 182 183
            "L2OutputOracle: cannot propose L2 output in the future"
        );

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

186
        if (_l1BlockHash != bytes32(0)) {
187
            // This check allows the proposer to propose an output based on a given L1 block,
188 189 190
            // 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
191 192
            // 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
193 194 195
            // blockhash value, and delay submission until it is confident that the L1 block is
            // finalized.
            require(
196 197
                blockhash(_l1BlockNumber) == _l1BlockHash,
                "L2OutputOracle: block hash does not match the hash at the expected height"
198 199 200
            );
        }

201
        emit OutputProposed(_outputRoot, nextOutputIndex(), block.timestamp, _l2BlockNumber);
202

203 204 205 206 207 208 209
        l2Outputs.push(
            Types.OutputProposal({
                outputRoot: _outputRoot,
                timestamp: uint128(block.timestamp),
                l2BlockNumber: uint128(_l2BlockNumber)
            })
        );
210 211 212
    }

    /**
213 214
     * @notice Returns an output by index. Exists because Solidity's array access will return a
     *         tuple instead of a struct.
215
     *
216 217 218
     * @param _l2OutputIndex Index of the output to return.
     *
     * @return The output at the given index.
219
     */
220
    function getL2Output(uint256 _l2OutputIndex)
Mark Tyneway's avatar
Mark Tyneway committed
221 222 223 224
        external
        view
        returns (Types.OutputProposal memory)
    {
225 226 227 228 229 230 231 232 233 234 235 236 237
        return l2Outputs[_l2OutputIndex];
    }

    /**
     * @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.
     */
    function getL2OutputIndexAfter(uint256 _l2BlockNumber) public view returns (uint256) {
        // Make sure an output for this block number has actually been proposed.
238
        require(
239 240
            _l2BlockNumber <= latestBlockNumber(),
            "L2OutputOracle: cannot get output for a block that has not been proposed"
241 242
        );

243
        // Make sure there's at least one output proposed.
244
        require(
245 246
            l2Outputs.length > 0,
            "L2OutputOracle: cannot get output as no outputs have been proposed yet"
247
        );
248

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
        // 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;
    }

    /**
     * @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.
     */
    function getL2OutputAfter(uint256 _l2BlockNumber)
        external
        view
        returns (Types.OutputProposal memory)
    {
        return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
    }

    /**
     * @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.
     */
    function latestOutputIndex() external view returns (uint256) {
        return l2Outputs.length - 1;
    }

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

    /**
     * @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.
     */
    function latestBlockNumber() public view returns (uint256) {
        return
            l2Outputs.length == 0
                ? startingBlockNumber
                : l2Outputs[l2Outputs.length - 1].l2BlockNumber;
310 311
    }

312 313
    /**
     * @notice Computes the block number of the next L2 block that needs to be checkpointed.
314 315
     *
     * @return Next L2 block number.
316 317
     */
    function nextBlockNumber() public view returns (uint256) {
318
        return latestBlockNumber() + SUBMISSION_INTERVAL;
319 320 321 322 323 324
    }

    /**
     * @notice Returns the L2 timestamp corresponding to a given L2 block number.
     *
     * @param _l2BlockNumber The L2 block number of the target block.
325 326
     *
     * @return L2 timestamp of the given block.
327 328
     */
    function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
329
        return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * L2_BLOCK_TIME);
330
    }
331
}