-
-
Save ajsutton/d52d1dd89af037f5c89c7a27d6f18c03 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 8,10d7 | |
| < import { Types } from "src/libraries/Types.sol"; | |
| < import { Hashing } from "src/libraries/Hashing.sol"; | |
| < import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; | |
| 24a22 | |
| > import { Types } from "src/libraries/Types.sol"; | |
| 42d39 | |
| < L2BlockNumberChallenged, | |
| 49,50d45 | |
| < BlockNumberMatches, | |
| < InvalidHeaderRLP, | |
| 54d48 | |
| < InvalidOutputRootProof, | |
| 62a57,58 | |
| > import { Hashing } from "src/libraries/Hashing.sol"; | |
| > import { Encoding } from "src/libraries/Encoding.sol"; | |
| 71,73c67,74 | |
| < /// @title FaultDisputeGame | |
| < /// @notice An implementation of the `IFaultDisputeGame` interface. | |
| < contract FaultDisputeGame is Clone, ISemver { | |
| --- | |
| > /// @title SuperFaultDisputeGame | |
| > /// @notice An implementation of the `IFaultDisputeGame` interface for interop. | |
| > contract SuperFaultDisputeGame is Clone, ISemver { | |
| > /// @dev Error to prevent initializing a dispute game with the reserved invalid root claim. | |
| > error SuperFaultDisputeGameInvalidRootClaim(); | |
| > /// @dev Error to prevent passing a chainId to this dispute game | |
| > error NoChainIdNeeded(); | |
| > | |
| 97c98 | |
| < /// @notice Parameters for creating a new FaultDisputeGame. We place this into a struct to | |
| --- | |
| > /// @notice Parameters for creating a new SuperFaultDisputeGame. We place this into a struct to | |
| 126a128,130 | |
| > /// @notice The constant for invalid root claims. | |
| > bytes32 internal constant INVALID_ROOT_CLAIM = keccak256("invalid"); | |
| > | |
| 144,147c148,154 | |
| < /// @notice The index of the block number in the RLP-encoded block header. | |
| < /// @dev Consensus encoding reference: | |
| < /// https://github.com/paradigmxyz/reth/blob/5f82993c23164ce8ccdc7bf3ae5085205383a5c8/crates/primitives/src/header.rs#L368 | |
| < uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8; | |
| --- | |
| > /// @notice The byte count of the data before the extra data in the CWIA payload. | |
| > /// Expected length: 88 bytes | |
| > /// - 20 bytes: creator address | |
| > /// - 32 bytes: root claim | |
| > /// - 32 bytes: l1 head | |
| > /// - 4 bytes: game type | |
| > uint256 internal constant PRE_EXTRA_DATA_BYTE_COUNT = 88; | |
| 149,150c156,166 | |
| < /// @notice Semantic version. | |
| < /// @custom:semver 2.4.2 | |
| --- | |
| > /// @notice The byte count of the game implementation args for this contract. | |
| > /// Expected length: 124 bytes | |
| > /// - 32 bytes: absolutePrestate | |
| > /// - 20 bytes: vm address | |
| > /// - 20 bytes: anchorStateRegistry address | |
| > /// - 20 bytes: weth address | |
| > /// - 32 bytes: l2ChainId (unused) | |
| > uint256 internal constant GAME_IMPL_ARGS_BYTE_COUNT = 124; | |
| > | |
| > /// @notice Semantic version. | |
| > /// @custom:semver 0.8.0 | |
| 152c168 | |
| < return "2.4.2"; | |
| --- | |
| > return "0.8.0"; | |
| 167,173d182 | |
| < /// @notice Flag for whether or not the L2 block number claim has been invalidated via `challengeRootL2Block`. | |
| < bool public l2BlockNumberChallenged; | |
| < | |
| < /// @notice The challenger of the L2 block number claim. Should always be `address(0)` if `l2BlockNumberChallenged` | |
| < /// is `false`. Should be the address of the challenger if `l2BlockNumberChallenged` is `true`. | |
| < address public l2BlockNumberChallenger; | |
| < | |
| 193c202 | |
| < Proposal public startingOutputRoot; | |
| --- | |
| > Proposal public startingProposal; | |
| 207c216 | |
| < /// @param _params Parameters for creating a new FaultDisputeGame. | |
| --- | |
| > /// @param _params Parameters for creating a new SuperFaultDisputeGame. | |
| 252c261 | |
| < // - An output root cannot be proposed at or before the starting block number. | |
| --- | |
| > // - A super root cannot be proposed at or before the starting l2SequenceNumber. | |
| 261,262c270,271 | |
| < // output proposal to be created. | |
| < if (msg.data.length != expectedInitCallDataLength()) revert BadExtraData(); | |
| --- | |
| > // super proposal to be created. | |
| > if (!_verifyInitCallDataLength()) revert BadExtraData(); | |
| 263a273,280 | |
| > // Sanity check to prevent initializing right away with an invalid claim state that is used as convention | |
| > // Should be impossible to find a valid Super preimage of INVALID_ROOT_CLAIM | |
| > if (rootClaim().raw() == INVALID_ROOT_CLAIM) revert SuperFaultDisputeGameInvalidRootClaim(); | |
| > | |
| > // Revert if the super root proof in extraData does not match the root claim. | |
| > Types.SuperRootProof memory superRootProof = Encoding.decodeSuperRootProof(extraData()); | |
| > if (Hashing.hashSuperRootProof(superRootProof) != rootClaim().raw()) revert BadExtraData(); | |
| > | |
| 265c282 | |
| < (Hash root, uint256 rootBlockNumber) = anchorStateRegistry().getAnchorRoot(); | |
| --- | |
| > (Hash root, uint256 rootL2SequenceNumber) = anchorStateRegistry().getAnchorRoot(); | |
| 270,276c287,288 | |
| < // Set the starting proposal. | |
| < startingOutputRoot = Proposal({ l2SequenceNumber: rootBlockNumber, root: root }); | |
| < | |
| < // Do not allow the game to be initialized if the root claim corresponds to a block at or before the | |
| < // configured starting block number. | |
| < if (l2BlockNumber() <= rootBlockNumber) revert UnexpectedRootClaim(rootClaim()); | |
| < if (l2BlockNumber() > type(uint64).max) revert UnexpectedRootClaim(rootClaim()); | |
| --- | |
| > // Set the starting Proposal. | |
| > startingProposal = Proposal({ l2SequenceNumber: rootL2SequenceNumber, root: root }); | |
| 278d289 | |
| < // Validate parameters that require access to the VM. | |
| 279a291,293 | |
| > // Runtime check was added instead of changing the ABI since the contract is already | |
| > // deployed in production. We perform the same check within the PreimageOracle for the | |
| > // benefit of developers but also perform this check here defensively. | |
| 285c299 | |
| < uint256 maxGameDepthExtension = uint256(CLOCK_EXTENSION.raw()) + uint64(vm().oracle().challengePeriod()); | |
| --- | |
| > uint256 maxGameDepthExtension = uint256(CLOCK_EXTENSION.raw()) + uint256(vm().oracle().challengePeriod()); | |
| 293a308,314 | |
| > // The l2ChainId must still exist, yet zero-valued, in the game args for legacy reasons. | |
| > if (_l2ChainId() != 0) revert NoChainIdNeeded(); | |
| > | |
| > // Do not allow the game to be initialized if the root claim corresponds to a l2 sequence number (timestamp) at | |
| > // or before the configured starting sequence number. | |
| > if (l2SequenceNumber() <= rootL2SequenceNumber) revert UnexpectedRootClaim(rootClaim()); | |
| > | |
| 322,328c343,369 | |
| < /// @notice Returns the expected calldata length for the initialize method | |
| < function expectedInitCallDataLength() internal pure returns (uint256) { | |
| < // Expected length: 6 bytes + immutable args byte count | |
| < // - 4 bytes: selector | |
| < // - 2 bytes: CWIA length prefix | |
| < // - n bytes: Immutable args data | |
| < return 6 + immutableArgsByteCount(); | |
| --- | |
| > /// @notice Validates the expected length of msg.data for the initialize() call. | |
| > /// @dev This function must only be called by initialize(). | |
| > /// | |
| > /// Expected msg.data structure: | |
| > /// ┌────────────────────────────────────────────────────────────────────┐ | |
| > /// │ 4 bytes │ Function selector (initialize()) │ | |
| > /// │ 2 bytes │ CWIA length prefix │ | |
| > /// │===================│ ============ pre extra data ================== │ | |
| > /// │ 20 bytes │ creator address │ | |
| > /// │ 32 bytes │ root claim │ | |
| > /// │ 32 bytes │ l1 head │ | |
| > /// │ 4 bytes │ game type │ | |
| > /// │===================│ ============ extra data ====================== │ | |
| > /// │ 1 byte │ super version │ | |
| > /// │ 8 bytes │ super timestamp (seqnr) │ | |
| > /// │ n * (32+32) bytes │ (chainId, outputRoot) tuples │ | |
| > /// │===================│ ============ end extra data ================== │ | |
| > /// │ 124 bytes │ game impl args │ | |
| > /// └────────────────────────────────────────────────────────────────────┘ | |
| > function _verifyInitCallDataLength() internal pure returns (bool) { | |
| > uint256 preExtraDataLen = 4 + 2 + PRE_EXTRA_DATA_BYTE_COUNT; | |
| > uint256 minLen = preExtraDataLen + 9 + GAME_IMPL_ARGS_BYTE_COUNT; | |
| > if (msg.data.length < minLen) return false; | |
| > | |
| > uint256 superLen = msg.data.length - preExtraDataLen - GAME_IMPL_ARGS_BYTE_COUNT; | |
| > uint256 rem = superLen - 9; | |
| > return rem != 0 && rem % 64 == 0; | |
| 331,344c372,378 | |
| < /// @notice Returns the byte count of the immutable args for this contract. | |
| < function immutableArgsByteCount() internal pure virtual returns (uint256) { | |
| < // Expected length: 244 bytes | |
| < // - 20 bytes: creator address | |
| < // - 32 bytes: root claim | |
| < // - 32 bytes: l1 head | |
| < // - 4 bytes: game type | |
| < // - 32 bytes: extraData | |
| < // - 32 bytes: absolutePrestate | |
| < // - 20 bytes: vm address | |
| < // - 20 bytes: anchorStateRegistry address | |
| < // - 20 bytes: weth address | |
| < // - 32 bytes: l2ChainId | |
| < return 244; | |
| --- | |
| > /// @notice Returns the length of the super extra data in the initialize() call. | |
| > /// @dev Precondition: msg.data has a valid length. | |
| > function _extraDataByteCount() internal pure returns (uint256) { | |
| > // The CWIA runtime appends the immutable args and a 2-byte length suffix to every call; | |
| > // strip the original calldata and suffix so offsets stay correct for functions with params. | |
| > uint256 immutableArgsLength = msg.data.length - _getImmutableArgsOffset() - 2; | |
| > return immutableArgsLength - PRE_EXTRA_DATA_BYTE_COUNT - GAME_IMPL_ARGS_BYTE_COUNT; | |
| 471,474d504 | |
| < // INVARIANT: No moves against the root claim can be made after it has been challenged with | |
| < // `challengeRootL2Block`.` | |
| < if (l2BlockNumberChallenged && _challengeIndex == 0) revert L2BlockNumberChallenged(); | |
| < | |
| 600,613c630 | |
| < // Load the disputed proposal's L2 block number as a big-endian uint64 in the | |
| < // high order 8 bytes of the word. | |
| < | |
| < // We add the index at depth + 1 to the starting block number to get the disputed L2 | |
| < // block number. | |
| < uint256 l2Number = startingOutputRoot.l2SequenceNumber + disputedPos.traceIndex(SPLIT_DEPTH) + 1; | |
| < | |
| < // Choose the minimum between the `l2BlockNumber` claim and the bisected-to L2 block number. | |
| < l2Number = l2Number < l2BlockNumber() ? l2Number : l2BlockNumber(); | |
| < | |
| < oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2Number << 0xC0), 8, _partOffset); | |
| < } else if (_ident == LocalPreimageKey.CHAIN_ID) { | |
| < // Load the chain ID as a big-endian uint64 in the high order 8 bytes of the word. | |
| < oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2ChainId() << 0xC0), 8, _partOffset); | |
| --- | |
| > oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2SequenceNumber() << 0xC0), 8, _partOffset); | |
| 631,636c648 | |
| < /// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`. | |
| < function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { | |
| < l2BlockNumber_ = _getArgUint256(88); | |
| < } | |
| < | |
| < /// @notice The l2SequenceNumber of the disputed output root in the `L2OutputOracle` (in this case - block number). | |
| --- | |
| > /// @notice The l2SequenceNumber (timestamp) of the disputed super root in game root claim. | |
| 638c650 | |
| < l2SequenceNumber_ = l2BlockNumber(); | |
| --- | |
| > l2SequenceNumber_ = _getArgUint64(PRE_EXTRA_DATA_BYTE_COUNT + 1); | |
| 641,643c653,655 | |
| < /// @notice Only the starting block number of the game. | |
| < function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { | |
| < startingBlockNumber_ = startingOutputRoot.l2SequenceNumber; | |
| --- | |
| > /// @notice Only the starting sequence number (timestamp) of the game. | |
| > function startingSequenceNumber() external view returns (uint256 startingSequenceNumber_) { | |
| > startingSequenceNumber_ = startingProposal.l2SequenceNumber; | |
| 646c658 | |
| < /// @notice Starting output root and block number of the game. | |
| --- | |
| > /// @notice Starting super root of the game. | |
| 648c660 | |
| < startingRootHash_ = startingOutputRoot.root; | |
| --- | |
| > startingRootHash_ = startingProposal.root; | |
| 651,698d662 | |
| < /// @notice Challenges the root L2 block number by providing the preimage of the output root and the L2 block header | |
| < /// and showing that the committed L2 block number is incorrect relative to the claimed L2 block number. | |
| < /// @param _outputRootProof The output root proof. | |
| < /// @param _headerRLP The RLP-encoded L2 block header. | |
| < function challengeRootL2Block( | |
| < Types.OutputRootProof calldata _outputRootProof, | |
| < bytes calldata _headerRLP | |
| < ) | |
| < external | |
| < { | |
| < // INVARIANT: Moves cannot be made unless the game is currently in progress. | |
| < if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); | |
| < | |
| < // The root L2 block claim can only be challenged once. | |
| < if (l2BlockNumberChallenged) revert L2BlockNumberChallenged(); | |
| < | |
| < // Verify the output root preimage. | |
| < if (Hashing.hashOutputRootProof(_outputRootProof) != rootClaim().raw()) revert InvalidOutputRootProof(); | |
| < | |
| < // Verify the block hash preimage. | |
| < if (keccak256(_headerRLP) != _outputRootProof.latestBlockhash) revert InvalidHeaderRLP(); | |
| < | |
| < // Decode the header RLP to find the number of the block. In the consensus encoding, the timestamp | |
| < // is the 9th element in the list that represents the block header. | |
| < RLPReader.RLPItem[] memory headerContents = RLPReader.readList(RLPReader.toRLPItem(_headerRLP)); | |
| < bytes memory rawBlockNumber = RLPReader.readBytes(headerContents[HEADER_BLOCK_NUMBER_INDEX]); | |
| < | |
| < // Sanity check the block number string length. | |
| < if (rawBlockNumber.length > 32) revert InvalidHeaderRLP(); | |
| < | |
| < // Convert the raw, left-aligned block number to a uint256 by aligning it as a big-endian | |
| < // number in the low-order bytes of a 32-byte word. | |
| < // | |
| < // SAFETY: The length of `rawBlockNumber` is checked above to ensure it is at most 32 bytes. | |
| < uint256 blockNumber; | |
| < assembly { | |
| < blockNumber := shr(shl(0x03, sub(0x20, mload(rawBlockNumber))), mload(add(rawBlockNumber, 0x20))) | |
| < } | |
| < | |
| < // Ensure the block number does not match the block number claimed in the dispute game. | |
| < if (blockNumber == l2BlockNumber()) revert BlockNumberMatches(); | |
| < | |
| < // Issue a special counter to the root claim. This counter will always win the root claim subgame, and receive | |
| < // the bond from the root claimant. | |
| < l2BlockNumberChallenger = msg.sender; | |
| < l2BlockNumberChallenged = true; | |
| < } | |
| < | |
| 818,827c782,784 | |
| < if (_claimIndex == 0 && l2BlockNumberChallenged) { | |
| < // Special case: If the root claim has been challenged with the `challengeRootL2Block` function, | |
| < // the bond is always paid out to the issuer of that challenge. | |
| < address challenger = l2BlockNumberChallenger; | |
| < _distributeBond(challenger, subgameRootClaim); | |
| < subgameRootClaim.counteredBy = challenger; | |
| < } else { | |
| < // If the parent was not successfully countered, pay out the parent's bond to the claimant. | |
| < // If the parent was successfully countered, pay out the parent's bond to the challenger. | |
| < _distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim); | |
| --- | |
| > // If the parent was not successfully countered, pay out the parent's bond to the claimant. | |
| > // If the parent was successfully countered, pay out the parent's bond to the challenger. | |
| > _distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim); | |
| 829,832c786,788 | |
| < // Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to | |
| < // resolveClaim will not need to traverse this subgame. | |
| < subgameRootClaim.counteredBy = countered; | |
| < } | |
| --- | |
| > // Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to | |
| > // resolveClaim will not need to traverse this subgame. | |
| > subgameRootClaim.counteredBy = countered; | |
| 850,855c806,818 | |
| < /// @notice Getter for the root claim for a given L2 chain ID. | |
| < /// @param _chainId The L2 chain ID to get the root claim for. | |
| < /// @return rootClaim_ The root claim of the DisputeGame. | |
| < function rootClaimByChainId(uint256 _chainId) public pure returns (Claim rootClaim_) { | |
| < if (_chainId != l2ChainId()) revert UnknownChainId(); | |
| < rootClaim_ = rootClaim(); | |
| --- | |
| > /// @notice Returns the output root in the root claim for the specified L2 chain ID. | |
| > /// @param _chainId The L2 chain ID to get the output root claim for. | |
| > /// @return outputRootClaim_ The output root claim for the specified L2 chain ID. | |
| > function rootClaimByChainId(uint256 _chainId) public pure returns (Claim outputRootClaim_) { | |
| > Types.SuperRootProof memory superRootProof = Encoding.decodeSuperRootProof(extraData()); | |
| > Types.OutputRootWithChainId[] memory outputRoots = superRootProof.outputRoots; | |
| > | |
| > for (uint256 i = 0; i < outputRoots.length; i++) { | |
| > if (outputRoots[i].chainId == _chainId) { | |
| > return Claim.wrap(outputRoots[i].root); | |
| > } | |
| > } | |
| > revert UnknownChainId(); | |
| 873c836 | |
| < /// @dev `clones-with-immutable-args` argument #5 | |
| --- | |
| > /// @dev `clones-with-immutable-args` argument #4 | |
| 876,878c839 | |
| < // The extra data starts at the second word within the cwia calldata and | |
| < // is 32 bytes long. | |
| < extraData_ = _getArgBytes(88, 32); | |
| --- | |
| > extraData_ = _getArgBytes(PRE_EXTRA_DATA_BYTE_COUNT, _extraDataByteCount()); | |
| 885c846 | |
| < absolutePrestate_ = Claim.wrap(_getArgBytes32(120)); | |
| --- | |
| > absolutePrestate_ = Claim.wrap(_getArgBytes32(PRE_EXTRA_DATA_BYTE_COUNT + _extraDataByteCount())); | |
| 892c853 | |
| < vm_ = IBigStepper(_getArgAddress(152)); | |
| --- | |
| > vm_ = IBigStepper(_getArgAddress(PRE_EXTRA_DATA_BYTE_COUNT + _extraDataByteCount() + 32)); | |
| 899c860 | |
| < registry_ = IAnchorStateRegistry(_getArgAddress(172)); | |
| --- | |
| > registry_ = IAnchorStateRegistry(_getArgAddress(PRE_EXTRA_DATA_BYTE_COUNT + _extraDataByteCount() + 52)); | |
| 906c867 | |
| < weth_ = IDelayedWETH(payable(_getArgAddress(192))); | |
| --- | |
| > weth_ = IDelayedWETH(payable(_getArgAddress(PRE_EXTRA_DATA_BYTE_COUNT + _extraDataByteCount() + 72))); | |
| 912,913c873,874 | |
| < function l2ChainId() public pure returns (uint256 l2ChainId_) { | |
| < l2ChainId_ = _getArgUint256(212); | |
| --- | |
| > function _l2ChainId() internal pure returns (uint256 l2ChainId_) { | |
| > l2ChainId_ = _getArgUint256(PRE_EXTRA_DATA_BYTE_COUNT + _extraDataByteCount() + 92); | |
| 1026a988,996 | |
| > // We won't close the game if the system is currently paused. Paused games are temporarily | |
| > // invalid which would cause the game to go into refund mode and potentially cause some | |
| > // confusion for honest challengers. By blocking the game from being closed while the | |
| > // system is paused, the game will only go into refund mode if it ends up being explicitly | |
| > // invalidated in the AnchorStateRegistry. | |
| > if (anchorStateRegistry().paused()) { | |
| > revert GamePaused(); | |
| > } | |
| > | |
| 1035,1044d1004 | |
| < } | |
| < | |
| < // We won't close the game if the system is currently paused. Paused games are temporarily | |
| < // invalid which would cause the game to go into refund mode and potentially cause some | |
| < // confusion for honest challengers. By blocking the game from being closed while the | |
| < // system is paused, the game will only go into refund mode if it ends up being explicitly | |
| < // invalidated in the AnchorStateRegistry. If the game has already been closed and a refund | |
| < // mode has been selected, we'll already have returned and we won't hit this revert. | |
| < if (anchorStateRegistry().paused()) { | |
| < revert GamePaused(); | |
| 1169c1129 | |
| < // 1. Signal that the VM panicked or resulted in an invalid transition if the disputed output root | |
| --- | |
| > // 1. Signal that the VM panicked or resulted in an invalid transition if the disputed super root | |
| 1271,1272c1231,1232 | |
| < // If this is an attack on the first output root (the block directly after the starting | |
| < // block number), the starting claim nor position exists in the tree. We leave these as | |
| --- | |
| > // If this is an attack on the first super root (the block directly after the starting | |
| > // timestamp), the starting claim nor position exists in the tree. We leave these as | |
| 1278c1238 | |
| < startingClaim_ = Claim.wrap(startingOutputRoot.root.raw()); | |
| --- | |
| > startingClaim_ = Claim.wrap(startingProposal.root.raw()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment