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

4 5 6
import {
    OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7
import { Semver } from "../universal/Semver.sol";
8
import { Types } from "../libraries/Types.sol";
9 10

/**
11
 * @custom:proxied
12 13
 * @title L2OutputOracle
 * @notice The L2 state is committed to in this contract
14
 *         The payable keyword is used on proposeL2Output to save gas on the msg.value check.
15
 *         This contract should be deployed behind an upgradable proxy
16 17
 */
// slither-disable-next-line locked-ether
18
contract L2OutputOracle is OwnableUpgradeable, Semver {
19 20 21
    /**
     * @notice The interval in L2 blocks at which checkpoints must be submitted.
     */
22
    // solhint-disable-next-line var-name-mixedcase
23 24
    uint256 public immutable SUBMISSION_INTERVAL;

25 26 27
    /**
     * @notice The number of blocks in the chain before the first block in this contract.
     */
28
    // solhint-disable-next-line var-name-mixedcase
29 30
    uint256 public immutable HISTORICAL_TOTAL_BLOCKS;

31 32 33
    /**
     * @notice The number of the first L2 block recorded in this contract.
     */
34
    // solhint-disable-next-line var-name-mixedcase
35
    uint256 public immutable STARTING_BLOCK_NUMBER;
36

37 38 39
    /**
     * @notice The timestamp of the first L2 block recorded in this contract.
     */
40
    // solhint-disable-next-line var-name-mixedcase
41
    uint256 public immutable STARTING_TIMESTAMP;
42

43 44 45
    /**
     * @notice The time between L2 blocks in seconds.
     */
46
    // solhint-disable-next-line var-name-mixedcase
47
    uint256 public immutable L2_BLOCK_TIME;
48

49
    /**
50
     * @notice The address of the proposer;
51
     */
52
    address public proposer;
53

54 55 56 57
    /**
     * @notice The number of the most recent L2 block recorded in this contract.
     */
    uint256 public latestBlockNumber;
58

59 60 61
    /**
     * @notice A mapping from L2 block numbers to the respective output root. Note that these
     *         outputs should not be considered finalized until the finalization period (as defined
62
     *         in the Optimism Portal) has passed.
63
     */
64
    mapping(uint256 => Types.OutputProposal) internal l2Outputs;
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    /**
     * @notice Emitted when an output is proposed.
     *
     * @param outputRoot    The output root.
     * @param l1Timestamp   The L1 timestamp when proposed.
     * @param l2BlockNumber The L2 block number of the output root.
     */
    event OutputProposed(
        bytes32 indexed outputRoot,
        uint256 indexed l1Timestamp,
        uint256 indexed l2BlockNumber
    );

    /**
     * @notice Emitted when an output is deleted.
     *
     * @param outputRoot    The output root.
     * @param l1Timestamp   The L1 timestamp when proposed.
     * @param l2BlockNumber The L2 block number of the output root.
     */
    event OutputDeleted(
        bytes32 indexed outputRoot,
        uint256 indexed l1Timestamp,
        uint256 indexed l2BlockNumber
    );

    /**
     * @notice Emitted when the proposer address is changed.
     *
     * @param previousProposer The previous proposer address.
     * @param newProposer      The new proposer address.
     */
    event ProposerChanged(address indexed previousProposer, address indexed newProposer);

100
    /**
101
     * @notice Reverts if called by any account other than the proposer.
102
     */
103
    modifier onlyProposer() {
104
        require(proposer == msg.sender, "L2OutputOracle: function can only be called by proposer");
105 106 107
        _;
    }

108
    /**
109 110
     * @custom:semver 0.0.1
     *
111
     * @param _submissionInterval    Interval in blocks at which checkpoints must be submitted.
112
     * @param _genesisL2Output       The initial L2 output of the L2 chain.
113
     * @param _historicalTotalBlocks Number of blocks preceding this L2 chain.
114 115
     * @param _startingBlockNumber   The number of the first L2 block.
     * @param _startingTimestamp     The timestamp of the first L2 block.
116
     * @param _l2BlockTime           The time per L2 block, in seconds.
117
     * @param _proposer              The address of the proposer.
118
     * @param _owner                 The address of the owner.
119 120 121 122 123
     */
    constructor(
        uint256 _submissionInterval,
        bytes32 _genesisL2Output,
        uint256 _historicalTotalBlocks,
124 125 126
        uint256 _startingBlockNumber,
        uint256 _startingTimestamp,
        uint256 _l2BlockTime,
127
        address _proposer,
128
        address _owner
129
    ) Semver(0, 0, 1) {
130
        require(
131
            _l2BlockTime < block.timestamp,
132
            "L2OutputOracle: initial L2 block time must be less than current time"
133
        );
134

135 136
        SUBMISSION_INTERVAL = _submissionInterval;
        HISTORICAL_TOTAL_BLOCKS = _historicalTotalBlocks;
137 138 139 140
        STARTING_BLOCK_NUMBER = _startingBlockNumber;
        STARTING_TIMESTAMP = _startingTimestamp;
        L2_BLOCK_TIME = _l2BlockTime;

141
        initialize(_genesisL2Output, _startingBlockNumber, _proposer, _owner);
142 143 144
    }

    /**
145 146 147 148 149
     * @notice Deletes the most recent output. This is used to remove the most recent output in the
     *         event that an erreneous output is submitted. It can only be called by the contract's
     *         owner, not the proposer. Longer term, this should be replaced with a more robust
     *         mechanism which will allow deletion of proposals shown to be invalid by a fault
     *         proof.
150
     *
151
     * @param _proposal Represents the output proposal to delete
152
     */
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
    function deleteL2Output(Types.OutputProposal memory _proposal) external onlyOwner {
        Types.OutputProposal memory outputToDelete = l2Outputs[latestBlockNumber];

        require(
            _proposal.outputRoot == outputToDelete.outputRoot,
            "L2OutputOracle: output root to delete does not match the latest output proposal"
        );

        require(
            _proposal.timestamp == outputToDelete.timestamp,
            "L2OutputOracle: timestamp to delete does not match the latest output proposal"
        );

        emit OutputDeleted(outputToDelete.outputRoot, outputToDelete.timestamp, latestBlockNumber);

        delete l2Outputs[latestBlockNumber];
        latestBlockNumber = latestBlockNumber - SUBMISSION_INTERVAL;
170 171 172
    }

    /**
173
     * @notice Accepts an outputRoot and the timestamp of the corresponding L2 block. The
174
     *         timestamp must be equal to the current value returned by `nextTimestamp()` in order
175
     *         to be accepted. This function may only be called by the Proposer.
176
     *
177 178
     * @param _outputRoot    The L2 output of the checkpoint block.
     * @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
179 180
     * @param _l1Blockhash   A block hash which must be included in the current chain.
     * @param _l1BlockNumber The block number with the specified block hash.
181
     */
182 183
    function proposeL2Output(
        bytes32 _outputRoot,
184
        uint256 _l2BlockNumber,
185
        bytes32 _l1Blockhash,
186
        uint256 _l1BlockNumber
187
    ) external payable onlyProposer {
188 189
        require(
            _l2BlockNumber == nextBlockNumber(),
190
            "L2OutputOracle: block number must be equal to next expected block number"
191
        );
192

193 194
        require(
            computeL2Timestamp(_l2BlockNumber) < block.timestamp,
195 196 197 198 199 200
            "L2OutputOracle: cannot propose L2 output in the future"
        );

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

        if (_l1Blockhash != bytes32(0)) {
204
            // This check allows the proposer to propose an output based on a given L1 block,
205 206 207
            // 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
208 209
            // 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
210 211 212
            // blockhash value, and delay submission until it is confident that the L1 block is
            // finalized.
            require(
213
                blockhash(_l1BlockNumber) == _l1Blockhash,
214
                "L2OutputOracle: blockhash does not match the hash at the expected height"
215 216 217
            );
        }

218
        l2Outputs[_l2BlockNumber] = Types.OutputProposal(_outputRoot, block.timestamp);
219
        latestBlockNumber = _l2BlockNumber;
220

221
        emit OutputProposed(_outputRoot, block.timestamp, _l2BlockNumber);
222 223 224
    }

    /**
225 226 227 228 229
     * @notice Returns the L2 output proposal associated with a target L2 block number. If the
     *         L2 block number provided is between checkpoints, this function will rerutn the next
     *         proposal for the next checkpoint.
     *         Reverts if the output proposal is either not found, or predates
     *         the STARTING_BLOCK_NUMBER.
230 231
     *
     * @param _l2BlockNumber The L2 block number of the target block.
232
     */
Mark Tyneway's avatar
Mark Tyneway committed
233 234 235 236 237
    function getL2Output(uint256 _l2BlockNumber)
        external
        view
        returns (Types.OutputProposal memory)
    {
238 239 240 241 242 243 244 245 246 247 248 249 250 251
        require(
            _l2BlockNumber >= STARTING_BLOCK_NUMBER,
            "L2OutputOracle: block number cannot be less than the starting block number."
        );

        // Find the distance between _l2BlockNumber, and the checkpoint block before it.
        uint256 offset = (_l2BlockNumber - STARTING_BLOCK_NUMBER) % SUBMISSION_INTERVAL;

        // If the offset is zero, then the _l2BlockNumber should be checkpointed.
        // Otherwise, we'll look up the next block that will be checkpointed.
        uint256 lookupBlockNumber = offset == 0
            ? _l2BlockNumber
            : _l2BlockNumber + (SUBMISSION_INTERVAL - offset);

252
        Types.OutputProposal memory output = l2Outputs[lookupBlockNumber];
253 254 255 256 257
        require(
            output.outputRoot != bytes32(0),
            "L2OutputOracle: No output found for that block number."
        );
        return output;
258 259 260
    }

    /**
261
     * @notice Initializer.
262
     *
263 264 265 266
     * @param _genesisL2Output     The initial L2 output of the L2 chain.
     * @param _startingBlockNumber The timestamp to start L2 block at.
     * @param _proposer            The address of the proposer.
     * @param _owner               The address of the owner.
267
     */
268 269 270 271 272 273 274 275 276 277 278
    function initialize(
        bytes32 _genesisL2Output,
        uint256 _startingBlockNumber,
        address _proposer,
        address _owner
    ) public initializer {
        l2Outputs[_startingBlockNumber] = Types.OutputProposal(_genesisL2Output, block.timestamp);
        latestBlockNumber = _startingBlockNumber;
        __Ownable_init();
        changeProposer(_proposer);
        _transferOwnership(_owner);
279
    }
280 281

    /**
282
     * @notice Transfers the proposer role to a new account (`newProposer`).
283 284
     *         Can only be called by the current owner.
     */
285
    function changeProposer(address _newProposer) public onlyOwner {
286 287 288 289 290 291 292 293 294 295
        require(
            _newProposer != address(0),
            "L2OutputOracle: new proposer cannot be the zero address"
        );

        require(
            _newProposer != owner(),
            "L2OutputOracle: proposer cannot be the same as the owner"
        );

296 297
        emit ProposerChanged(proposer, _newProposer);
        proposer = _newProposer;
298
    }
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

    /**
     * @notice Computes the block number of the next L2 block that needs to be checkpointed.
     */
    function nextBlockNumber() public view returns (uint256) {
        return latestBlockNumber + SUBMISSION_INTERVAL;
    }

    /**
     * @notice Returns the L2 timestamp corresponding to a given L2 block number.
     *         Returns a null output proposal if none is found.
     *
     * @param _l2BlockNumber The L2 block number of the target block.
     */
    function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
        require(
            _l2BlockNumber >= STARTING_BLOCK_NUMBER,
            "L2OutputOracle: block number must be greater than or equal to starting block number"
        );

        return STARTING_TIMESTAMP + ((_l2BlockNumber - STARTING_BLOCK_NUMBER) * L2_BLOCK_TIME);
    }
321
}