diff --git a/deploy-config/local.json b/deploy-config/local.json index 1a97a853a..f1d05276f 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -4,10 +4,7 @@ "baseFeeVaultWithdrawalNetwork": 0, "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "disputeGameFinalityDelaySeconds": 302400, - "faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000", - "faultGameGenesisBlock": 0, - "faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", - "faultGameWithdrawalDelay": 302400, + "delayedWETHWithdrawalDelay": 302400, "finalSystemOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "fundDevAccounts": true, "gasPriceOracleBaseFeeScalar": 1368, @@ -18,8 +15,6 @@ "l1FeeVaultWithdrawalNetwork": 0, "l2ChainId": 901, "l2GenesisBlockGasLimit": "0x17D7840", - "l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - "l2OutputOracleChallenger": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A", "l2OutputOracleStartingBlockNumber": 1, "l2OutputOracleStartingTimestamp": 1, "multiproofBlockInterval": 100, @@ -33,11 +28,9 @@ "operatorFeeVaultRecipient": "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec", "operatorFeeVaultWithdrawalNetwork": 0, "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", - "preimageOracleChallengePeriod": 86400, - "preimageOracleMinProposalSize": 126000, "proofMaturityDelaySeconds": 604800, "proxyAdminOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", - "respectedGameType": 0, + "respectedGameType": 621, "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", "sequencerFeeVaultWithdrawalNetwork": 0, diff --git a/deploy-config/mainnet.json b/deploy-config/mainnet.json index 644e8f760..ecdde63b3 100644 --- a/deploy-config/mainnet.json +++ b/deploy-config/mainnet.json @@ -4,10 +4,7 @@ "baseFeeVaultWithdrawalNetwork": 0, "batchSenderAddress": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985", "disputeGameFinalityDelaySeconds": 302400, - "faultGameAbsolutePrestate": "0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c", - "faultGameGenesisBlock": 120059863, - "faultGameGenesisOutputRoot": "0x2694ac14dcf54b7a77363e3f60e6462dc78da0d43d1e2f058dbb6a1488814977", - "faultGameWithdrawalDelay": 604800, + "delayedWETHWithdrawalDelay": 604800, "finalSystemOwner": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A", "gasPriceOracleBaseFeeScalar": 1368, "gasPriceOracleBlobBaseFeeScalar": 810949, @@ -17,26 +14,22 @@ "l1FeeVaultWithdrawalNetwork": 0, "l2ChainId": 10, "l2GenesisBlockGasLimit": "0x1c9c380", - "l2OutputOracleProposer": "0x473300df21D047806A082244b417f96b32f13A33", - "l2OutputOracleChallenger": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A", "l2OutputOracleStartingBlockNumber": 105235063, "l2OutputOracleStartingTimestamp": 1686068903, "multiproofBlockInterval": 600, "multiproofConfigHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "multiproofGameType": 621, "multiproofGenesisBlockNumber": 0, - "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", "multiproofIntermediateBlockInterval": 30, "nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000", "operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "operatorFeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", "operatorFeeVaultWithdrawalNetwork": 0, "p2pSequencerAddress": "0xAAAA45d9549EDA09E70937013520214382Ffc4A2", - "preimageOracleChallengePeriod": 86400, - "preimageOracleMinProposalSize": 126000, "proofMaturityDelaySeconds": 604800, "proxyAdminOwner": "0x7871d1187A97cbbE40710aC119AA3d412944e4Fe", - "respectedGameType": 0, + "respectedGameType": 621, "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", "sequencerFeeVaultWithdrawalNetwork": 0, diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index 675c4429e..cfcea5d2d 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -4,10 +4,7 @@ "baseFeeVaultWithdrawalNetwork": 0, "batchSenderAddress": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", "disputeGameFinalityDelaySeconds": 302400, - "faultGameAbsolutePrestate": "0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b", - "faultGameGenesisBlock": 9496192, - "faultGameGenesisOutputRoot": "0x63b1cda487c072b020a57c1203f7c2921754005cadbd54bed7f558111b8278d8", - "faultGameWithdrawalDelay": 604800, + "delayedWETHWithdrawalDelay": 604800, "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", "gasPriceOracleBaseFeeScalar": 1368, "gasPriceOracleBlobBaseFeeScalar": 810949, @@ -17,8 +14,6 @@ "l1FeeVaultWithdrawalNetwork": 0, "l2ChainId": 11155420, "l2GenesisBlockGasLimit": "0x1c9c380", - "l2OutputOracleProposer": "0x49277EE36A024120Ee218127354c4a3591dc90A9", - "l2OutputOracleChallenger": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", "l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleStartingTimestamp": 1690493568, "multiproofBlockInterval": 600, @@ -32,11 +27,9 @@ "operatorFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", "operatorFeeVaultWithdrawalNetwork": 0, "p2pSequencerAddress": "0x57CACBB0d30b01eb2462e5dC940c161aff3230D3", - "preimageOracleChallengePeriod": 86400, - "preimageOracleMinProposalSize": 126000, "proofMaturityDelaySeconds": 604800, "proxyAdminOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", - "respectedGameType": 0, + "respectedGameType": 621, "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", "sequencerFeeVaultWithdrawalNetwork": 0, diff --git a/foundry.toml b/foundry.toml index b3d023b34..f863db201 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,8 +22,6 @@ additional_compiler_profiles = [ ] compilation_restrictions = [ { paths = "src/L1/proofs/DisputeGameFactory.sol", optimizer_runs = 5000 }, - { paths = "src/L1/proofs/v2/FaultDisputeGameV2.sol", optimizer_runs = 5000 }, - { paths = "src/L1/proofs/v2/PermissionedDisputeGameV2.sol", optimizer_runs = 5000 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, { paths = "src/L1/SystemConfig.sol", optimizer_runs = 5000 }, { paths = "src/universal/OptimismMintableERC20Factory.sol", optimizer_runs = 5000 }, @@ -140,8 +138,6 @@ additional_compiler_profiles = [ ] compilation_restrictions = [ { paths = "src/L1/proofs/DisputeGameFactory.sol", optimizer_runs = 0 }, - { paths = "src/L1/proofs/v2/FaultDisputeGameV2.sol", optimizer_runs = 0 }, - { paths = "src/L1/proofs/v2/PermissionedDisputeGameV2.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, { paths = "src/L1/SystemConfig.sol", optimizer_runs = 0 }, { paths = "src/universal/OptimismMintableERC20Factory.sol", optimizer_runs = 0 }, diff --git a/interfaces/L1/IHasSuperchainConfig.sol b/interfaces/L1/IHasSuperchainConfig.sol deleted file mode 100644 index 69779131f..000000000 --- a/interfaces/L1/IHasSuperchainConfig.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; - -/// @notice Generic interface for contracts that have a superchain config. -interface IHasSuperchainConfig { - /// @notice Retrieves the superchain config for a given contract. - function superchainConfig() external view returns (ISuperchainConfig); -} diff --git a/interfaces/L1/IL1CrossDomainMessengerV160.sol b/interfaces/L1/IL1CrossDomainMessengerV160.sol deleted file mode 100644 index 09d4e604f..000000000 --- a/interfaces/L1/IL1CrossDomainMessengerV160.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; - -/// @notice This interface corresponds to the op-contracts/v1.6.0 release of the L1CrossDomainMessenger -/// contract, which has a semver of 2.3.0 as specified in -/// https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -interface IL1CrossDomainMessengerV160 is ICrossDomainMessenger { - function PORTAL() external view returns (address); - function initialize(ISuperchainConfig _superchainConfig, IOptimismPortal _portal) external; - function portal() external view returns (address); - function superchainConfig() external view returns (address); - function systemConfig() external view returns (address); - function version() external view returns (string memory); - - function __constructor__() external; -} diff --git a/interfaces/L1/IL1StandardBridgeV160.sol b/interfaces/L1/IL1StandardBridgeV160.sol deleted file mode 100644 index 8d212de24..000000000 --- a/interfaces/L1/IL1StandardBridgeV160.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; -import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; - -/// @notice This interface corresponds to the op-contracts/v1.6.0 release of the L1StandardBridge -/// contract, which has a semver of 2.1.0 as specified in -/// https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -interface IL1StandardBridgeV160 is IStandardBridge { - event ERC20DepositInitiated( - address indexed l1Token, - address indexed l2Token, - address indexed from, - address to, - uint256 amount, - bytes extraData - ); - event ERC20WithdrawalFinalized( - address indexed l1Token, - address indexed l2Token, - address indexed from, - address to, - uint256 amount, - bytes extraData - ); - event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - - function depositERC20( - address _l1Token, - address _l2Token, - uint256 _amount, - uint32 _minGasLimit, - bytes memory _extraData - ) - external; - function depositERC20To( - address _l1Token, - address _l2Token, - address _to, - uint256 _amount, - uint32 _minGasLimit, - bytes memory _extraData - ) - external; - function depositETH(uint32 _minGasLimit, bytes memory _extraData) external payable; - function depositETHTo(address _to, uint32 _minGasLimit, bytes memory _extraData) external payable; - function finalizeERC20Withdrawal( - address _l1Token, - address _l2Token, - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - external; - function finalizeETHWithdrawal( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - external - payable; - function initialize(ICrossDomainMessenger _messenger, ISuperchainConfig _superchainConfig) external; - function l2TokenBridge() external view returns (address); - function superchainConfig() external view returns (ISuperchainConfig); - function systemConfig() external view returns (ISystemConfig); - function version() external view returns (string memory); - - function __constructor__() external; -} diff --git a/interfaces/L1/proofs/IAggregateVerifier.sol b/interfaces/L1/proofs/IAggregateVerifier.sol new file mode 100644 index 000000000..dd588e4ec --- /dev/null +++ b/interfaces/L1/proofs/IAggregateVerifier.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IDisputeGame } from "./IDisputeGame.sol"; +import { IDisputeGameFactory } from "./IDisputeGameFactory.sol"; +import { IDelayedWETH } from "./IDelayedWETH.sol"; +import { IVerifier } from "./IVerifier.sol"; +import { Proposal, Hash } from "src/libraries/bridge/Types.sol"; +import { Timestamp } from "src/libraries/bridge/LibUDT.sol"; + +interface IAggregateVerifier is IDisputeGame { + function SLOW_FINALIZATION_DELAY() external view returns (uint64); + function FAST_FINALIZATION_DELAY() external view returns (uint64); + function EIP2935_CONTRACT() external view returns (address); + function BLOCKHASH_WINDOW() external view returns (uint256); + function EIP2935_WINDOW() external view returns (uint256); + function PROOF_THRESHOLD() external view returns (uint256); + function DISPUTE_GAME_FACTORY() external view returns (IDisputeGameFactory); + function DELAYED_WETH() external view returns (IDelayedWETH); + function TEE_VERIFIER() external view returns (IVerifier); + function TEE_IMAGE_HASH() external view returns (bytes32); + function ZK_VERIFIER() external view returns (IVerifier); + function ZK_RANGE_HASH() external view returns (bytes32); + function ZK_AGGREGATE_HASH() external view returns (bytes32); + function CONFIG_HASH() external view returns (bytes32); + function L2_CHAIN_ID() external view returns (uint256); + function BLOCK_INTERVAL() external view returns (uint256); + function INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); + + function startingOutputRoot() external view returns (Proposal memory); + function bondRecipient() external view returns (address); + function bondUnlocked() external view returns (bool); + function bondClaimed() external view returns (bool); + function bondAmount() external view returns (uint256); + function counteredByIntermediateRootIndexPlusOne() external view returns (uint256); + function expectedResolution() external view returns (Timestamp); + function proofCount() external view returns (uint8); + + function initializeWithInitData(bytes calldata proof) external payable; + function verifyProposalProof(bytes calldata proofBytes) external; + function challenge( + bytes calldata proofBytes, + uint256 intermediateRootIndex, + bytes32 intermediateRootToProve + ) + external; + function nullify( + bytes calldata proofBytes, + uint256 intermediateRootIndex, + bytes32 intermediateRootToProve + ) + external; + function claimCredit() external; + function closeGame() external; + + function startingBlockNumber() external view returns (uint256); + function startingRootHash() external view returns (Hash); + function teeProver() external view returns (address); + function zkProver() external view returns (address); + function gameOver() external view returns (bool); + function intermediateOutputRootsCount() external view returns (uint256); + function intermediateOutputRoots() external view returns (bytes memory); + function intermediateOutputRoot(uint256 index) external view returns (bytes32); + function parentAddress() external pure returns (address); +} diff --git a/interfaces/L1/proofs/IAnchorStateRegistry.sol b/interfaces/L1/proofs/IAnchorStateRegistry.sol index 7bda0379c..7823ac899 100644 --- a/interfaces/L1/proofs/IAnchorStateRegistry.sol +++ b/interfaces/L1/proofs/IAnchorStateRegistry.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; @@ -14,14 +13,14 @@ interface IAnchorStateRegistry is IProxyAdminOwnedBase { error AnchorStateRegistry_Unauthorized(); error ReinitializableBase_ZeroInitVersion(); - event AnchorUpdated(IFaultDisputeGameV2 indexed game); + event AnchorUpdated(IDisputeGame indexed game); event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); event RespectedGameTypeSet(GameType gameType); event RetirementTimestampSet(uint256 timestamp); function initVersion() external view returns (uint8); - function anchorGame() external view returns (IFaultDisputeGameV2); + function anchorGame() external view returns (IDisputeGame); function anchors(GameType) external view returns (Hash, uint256); function blacklistDisputeGame(IDisputeGame _disputeGame) external; function disputeGameBlacklist(IDisputeGame) external view returns (bool); diff --git a/interfaces/L1/proofs/IBigStepper.sol b/interfaces/L1/proofs/IBigStepper.sol deleted file mode 100644 index e5a8bd3ec..000000000 --- a/interfaces/L1/proofs/IBigStepper.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; - -/// @title IBigStepper -/// @notice Describes a state machine that can perform a single instruction step, provided a prestate and an optional -/// proof. -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠶⢅⠒⢄⢔⣶⡦⣤⡤⠄⣀⠀⠀⠀⠀⠀⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠨⡏⠀⠀⠈⠢⣙⢯⣄⠀⢨⠯⡺⡘⢄⠀⠀⠀⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣶⡆⠀⠀⠀⠀⠈⠓⠬⡒⠡⣀⢙⡜⡀⠓⠄⠀⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡷⠿⣧⣀⡀⠀⠀⠀⠀⠀⠀⠉⠣⣞⠩⠥⠀⠼⢄⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠉⢹⣶⠒⠒⠂⠈⠉⠁⠘⡆⠀⣿⣿⠫⡄⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣤⣀⡀⠀⠀⢸⡿⠀⠀⠀⠀⠀⢀⠞⠀⠀⢡⢨⢀⡄⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡒⣿⢿⡤⠝⡣⠉⠁⠚⠛⠀⠤⠤⣄⡰⠁⠀⠀⠀⠉⠙⢸⠀⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⢯⡌⡿⡇⠘⡷⠀⠁⠀⠀⢀⣰⠢⠲⠛⣈⣸⠦⠤⠶⠴⢬⣐⣊⡂⠀ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡪⡗⢫⠞⠀⠆⣀⠻⠤⠴⠐⠚⣉⢀⠦⠂⠋⠁⠀⠁⠀⠀⠀⠀⢋⠉⠇⠀ -/// ⠀⠀⠀⠀⣀⡤⠐⠒⠘⡹⠉⢸⠇⠸⠀⠀⠀⠀⣀⣤⠴⠚⠉⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠀⣾⠀ -/// ⠀⠀⠀⡰⠀⠉⠉⠀⠁⠀⠀⠈⢇⠈⠒⠒⠘⠈⢀⢡⡂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⢸⡄ -/// ⠀⠀⠸⣿⣆⠤⢀⡀⠀⠀⠀⠀⢘⡌⠀⠀⣀⣀⣀⡈⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢸⡇ -/// ⠀⠀⢸⣀⠀⠉⠒⠐⠛⠋⠭⠭⠍⠉⠛⠒⠒⠒⠀⠒⠚⠛⠛⠛⠩⠭⠭⠭⠭⠤⠤⠤⠤⠤⠭⠭⠉⠓⡆ -/// ⠀⠀⠘⠿⣷⣶⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ -/// ⠀⠀⠀⠀⠀⠉⠙⠛⠛⠻⠿⢿⣿⣿⣷⣶⣶⣶⣤⣤⣀⣁⣛⣃⣒⠿⠿⠿⠤⠠⠄⠤⠤⢤⣛⣓⣂⣻⡇ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠙⠛⠻⠿⠿⠿⢿⣿⣿⣿⣷⣶⣶⣾⣿⣿⣿⣿⠿⠟⠁ -/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀ -interface IBigStepper { - /// @notice Performs the state transition from a given prestate and returns the hash of the post state witness. - /// @param _stateData The raw opaque prestate data. - /// @param _proof Opaque proof data, can be used to prove things about the prestate in relation to the state of the - /// interface's implementation. - /// @param _localContext The local key context for the preimage oracle. Optional, can be set as a constant if the - /// implementation only requires one set of local keys. - /// @return postState_ The hash of the post state witness after the state transition. - function step( - bytes calldata _stateData, - bytes calldata _proof, - bytes32 _localContext - ) - external - returns (bytes32 postState_); - - /// @notice Returns the preimage oracle used by the state machine. - function oracle() external view returns (IPreimageOracle oracle_); -} diff --git a/interfaces/L1/proofs/IDisputeGame.sol b/interfaces/L1/proofs/IDisputeGame.sol index b6c0e7d73..be8c0980b 100644 --- a/interfaces/L1/proofs/IDisputeGame.sol +++ b/interfaces/L1/proofs/IDisputeGame.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.0; import { IInitializable } from "interfaces/L1/proofs/IInitializable.sol"; -import { Timestamp, GameStatus, GameType, Claim, Hash } from "src/libraries/bridge/Types.sol"; +import { IAnchorStateRegistry } from "./IAnchorStateRegistry.sol"; +import { GameStatus, GameType, Hash } from "src/libraries/bridge/Types.sol"; +import { Timestamp, Claim } from "src/libraries/bridge/LibUDT.sol"; interface IDisputeGame is IInitializable { event Resolved(GameStatus indexed status); @@ -19,4 +21,5 @@ interface IDisputeGame is IInitializable { function resolve() external returns (GameStatus status_); function gameData() external view returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_); function wasRespectedGameTypeWhenCreated() external view returns (bool); + function anchorStateRegistry() external view returns (IAnchorStateRegistry); } diff --git a/interfaces/L1/proofs/IDisputeGameFactory.sol b/interfaces/L1/proofs/IDisputeGameFactory.sol index ca42d0bf4..aa845c0cd 100644 --- a/interfaces/L1/proofs/IDisputeGameFactory.sol +++ b/interfaces/L1/proofs/IDisputeGameFactory.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.0; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { GameId, Timestamp, Claim, Hash, GameType } from "src/libraries/bridge/Types.sol"; +import { Hash, GameType } from "src/libraries/bridge/Types.sol"; +import { Timestamp, Claim, GameId } from "src/libraries/bridge/LibUDT.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IReinitializableBase } from "interfaces/universal/IReinitializableBase.sol"; diff --git a/interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol b/interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol deleted file mode 100644 index 339e47b05..000000000 --- a/interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IBigStepper } from "interfaces/L1/proofs/IBigStepper.sol"; -import { Types } from "src/libraries/Types.sol"; -import { Claim, Position, Clock, Hash, Duration, BondDistributionMode } from "src/libraries/bridge/Types.sol"; - -interface IFaultDisputeGameV2 is IDisputeGame { - struct ClaimData { - uint32 parentIndex; - address counteredBy; - address claimant; - uint128 bond; - Claim claim; - Position position; - Clock clock; - } - - struct ResolutionCheckpoint { - bool initialCheckpointComplete; - uint32 subgameIndex; - Position leftmostPosition; - address counteredBy; - } - - struct GameConstructorParams { - uint256 maxGameDepth; - uint256 splitDepth; - Duration clockExtension; - Duration maxClockDuration; - } - - error AlreadyInitialized(); - error AnchorRootNotFound(); - error BadExtraData(); - error BlockNumberMatches(); - error BondTransferFailed(); - error CannotDefendRootClaim(); - error ClaimAboveSplit(); - error ClaimAlreadyExists(); - error ClaimAlreadyResolved(); - error ClockNotExpired(); - error ClockTimeExceeded(); - error ContentLengthMismatch(); - error DuplicateStep(); - error EmptyItem(); - error GameDepthExceeded(); - error GameNotInProgress(); - error IncorrectBondAmount(); - error InvalidChallengePeriod(); - error InvalidClockExtension(); - error InvalidDataRemainder(); - error InvalidDisputedClaimIndex(); - error InvalidHeader(); - error InvalidHeaderRLP(); - error InvalidLocalIdent(); - error InvalidOutputRootProof(); - error InvalidParent(); - error InvalidPrestate(); - error InvalidSplitDepth(); - error L2BlockNumberChallenged(); - error MaxDepthTooLarge(); - error NoCreditToClaim(); - error OutOfOrderResolution(); - error UnexpectedList(); - error UnexpectedRootClaim(Claim rootClaim); - error UnexpectedString(); - error ValidStep(); - error InvalidBondDistributionMode(); - error GameNotFinalized(); - error GameNotResolved(); - error GamePaused(); - event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant); - event GameClosed(BondDistributionMode bondDistributionMode); - - function absolutePrestate() external view returns (Claim absolutePrestate_); - function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external; - function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_); - function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable; - function bondDistributionMode() external view returns (BondDistributionMode); - function challengeRootL2Block(Types.OutputRootProof memory _outputRootProof, bytes memory _headerRLP) external; - function claimCredit(address _recipient) external; - function claimData(uint256) - external - view // nosemgrep - returns ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ); - function claimDataLen() external view returns (uint256 len_); - function claims(Hash) external view returns (bool); - function clockExtension() external view returns (Duration clockExtension_); - function closeGame() external; - function credit(address _recipient) external view returns (uint256 credit_); - function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable; - function getChallengerDuration(uint256 _claimIndex) external view returns (Duration duration_); - function getNumToResolve(uint256 _claimIndex) external view returns (uint256 numRemainingChildren_); - function getRequiredBond(Position _position) external view returns (uint256 requiredBond_); - function hasUnlockedCredit(address) external view returns (bool); - function l2BlockNumber() external pure returns (uint256 l2BlockNumber_); - function l2BlockNumberChallenged() external view returns (bool); - function l2BlockNumberChallenger() external view returns (address); - function l2ChainId() external view returns (uint256 l2ChainId_); - function maxClockDuration() external view returns (Duration maxClockDuration_); - function maxGameDepth() external view returns (uint256 maxGameDepth_); - function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) external payable; - function normalModeCredit(address) external view returns (uint256); - function refundModeCredit(address) external view returns (uint256); - function resolutionCheckpoints(uint256) - external - view - returns (bool initialCheckpointComplete, uint32 subgameIndex, Position leftmostPosition, address counteredBy); // nosemgrep - function resolveClaim(uint256 _claimIndex, uint256 _numToResolve) external; - function resolvedSubgames(uint256) external view returns (bool); - function splitDepth() external view returns (uint256 splitDepth_); - function startingBlockNumber() external view returns (uint256 startingBlockNumber_); - function startingOutputRoot() external view returns (Hash root, uint256 l2SequenceNumber); // nosemgrep - function startingRootHash() external view returns (Hash startingRootHash_); - function step(uint256 _claimIndex, bool _isAttack, bytes memory _stateData, bytes memory _proof) external; - function subgames(uint256, uint256) external view returns (uint256); - function version() external pure returns (string memory); - function vm() external view returns (IBigStepper vm_); - function wasRespectedGameTypeWhenCreated() external view returns (bool); - function weth() external view returns (IDelayedWETH weth_); - - function __constructor__(GameConstructorParams memory _params) external; -} diff --git a/interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol b/interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol deleted file mode 100644 index bc011b54d..000000000 --- a/interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { Types } from "src/libraries/Types.sol"; -import { Claim, Position, Clock, Hash, Duration, BondDistributionMode } from "src/libraries/bridge/Types.sol"; - -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; -import { IBigStepper } from "interfaces/L1/proofs/IBigStepper.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; - -interface IPermissionedDisputeGameV2 is IDisputeGame { - struct ClaimData { - uint32 parentIndex; - address counteredBy; - address claimant; - uint128 bond; - Claim claim; - Position position; - Clock clock; - } - - struct ResolutionCheckpoint { - bool initialCheckpointComplete; - uint32 subgameIndex; - Position leftmostPosition; - address counteredBy; - } - - error AlreadyInitialized(); - error AnchorRootNotFound(); - error BadExtraData(); - error BlockNumberMatches(); - error BondTransferFailed(); - error CannotDefendRootClaim(); - error ClaimAboveSplit(); - error ClaimAlreadyExists(); - error ClaimAlreadyResolved(); - error ClockNotExpired(); - error ClockTimeExceeded(); - error ContentLengthMismatch(); - error DuplicateStep(); - error EmptyItem(); - error GameDepthExceeded(); - error GameNotInProgress(); - error IncorrectBondAmount(); - error InvalidChallengePeriod(); - error InvalidClockExtension(); - error InvalidDataRemainder(); - error InvalidDisputedClaimIndex(); - error InvalidHeader(); - error InvalidHeaderRLP(); - error InvalidLocalIdent(); - error InvalidOutputRootProof(); - error InvalidParent(); - error InvalidPrestate(); - error InvalidSplitDepth(); - error L2BlockNumberChallenged(); - error MaxDepthTooLarge(); - error NoCreditToClaim(); - error OutOfOrderResolution(); - error UnexpectedList(); - error UnexpectedRootClaim(Claim rootClaim); - error UnexpectedString(); - error ValidStep(); - error InvalidBondDistributionMode(); - error GameNotFinalized(); - error GameNotResolved(); - error GamePaused(); - event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant); - event GameClosed(BondDistributionMode bondDistributionMode); - - function absolutePrestate() external view returns (Claim absolutePrestate_); - function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external; - function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_); - function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable; - function bondDistributionMode() external view returns (BondDistributionMode); - function challengeRootL2Block(Types.OutputRootProof memory _outputRootProof, bytes memory _headerRLP) external; - function claimCredit(address _recipient) external; - function claimData(uint256) - external - view // nosemgrep - returns ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ); - function claimDataLen() external view returns (uint256 len_); - function claims(Hash) external view returns (bool); - function clockExtension() external view returns (Duration clockExtension_); - function closeGame() external; - function credit(address _recipient) external view returns (uint256 credit_); - function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable; - function getChallengerDuration(uint256 _claimIndex) external view returns (Duration duration_); - function getNumToResolve(uint256 _claimIndex) external view returns (uint256 numRemainingChildren_); - function getRequiredBond(Position _position) external view returns (uint256 requiredBond_); - function hasUnlockedCredit(address) external view returns (bool); - function initialize() external payable; - function l2BlockNumber() external pure returns (uint256 l2BlockNumber_); - function l2BlockNumberChallenged() external view returns (bool); - function l2BlockNumberChallenger() external view returns (address); - function l2ChainId() external view returns (uint256 l2ChainId_); - function maxClockDuration() external view returns (Duration maxClockDuration_); - function maxGameDepth() external view returns (uint256 maxGameDepth_); - function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) external payable; - function normalModeCredit(address) external view returns (uint256); - function refundModeCredit(address) external view returns (uint256); - function resolutionCheckpoints(uint256) - external - view - returns (bool initialCheckpointComplete, uint32 subgameIndex, Position leftmostPosition, address counteredBy); // nosemgrep - function resolveClaim(uint256 _claimIndex, uint256 _numToResolve) external; - function resolvedSubgames(uint256) external view returns (bool); - function splitDepth() external view returns (uint256 splitDepth_); - function startingBlockNumber() external view returns (uint256 startingBlockNumber_); - function startingOutputRoot() external view returns (Hash root, uint256 l2SequenceNumber); // nosemgrep - function startingRootHash() external view returns (Hash startingRootHash_); - function step(uint256 _claimIndex, bool _isAttack, bytes memory _stateData, bytes memory _proof) external; - function subgames(uint256, uint256) external view returns (uint256); - function version() external pure returns (string memory); - function vm() external view returns (IBigStepper vm_); - function wasRespectedGameTypeWhenCreated() external view returns (bool); - function weth() external view returns (IDelayedWETH weth_); - - error BadAuth(); - - function proposer() external pure returns (address proposer_); - function challenger() external pure returns (address challenger_); - - function __constructor__( - IFaultDisputeGameV2.GameConstructorParams memory _params - ) - external; -} diff --git a/interfaces/L2/IERC7802.sol b/interfaces/L2/IERC7802.sol deleted file mode 100644 index 8381be3d8..000000000 --- a/interfaces/L2/IERC7802.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IERC165 } from "lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol"; - -/// @title IERC7802 -/// @notice Defines the interface for crosschain ERC20 transfers. -interface IERC7802 is IERC165 { - /// @notice Emitted when a crosschain transfer mints tokens. - /// @param to Address of the account tokens are being minted for. - /// @param amount Amount of tokens minted. - /// @param sender Address of the account that finalized the crosschain transfer. - event CrosschainMint(address indexed to, uint256 amount, address indexed sender); - - /// @notice Emitted when a crosschain transfer burns tokens. - /// @param from Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - /// @param sender Address of the account that initiated the crosschain transfer. - event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); - - /// @notice Mint tokens through a crosschain transfer. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function crosschainMint(address _to, uint256 _amount) external; - - /// @notice Burn tokens through a crosschain transfer. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. - function crosschainBurn(address _from, uint256 _amount) external; -} diff --git a/interfaces/L2/IMintableAndBurnableERC20.sol b/interfaces/L2/IMintableAndBurnableERC20.sol deleted file mode 100644 index 92cc42fde..000000000 --- a/interfaces/L2/IMintableAndBurnableERC20.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; - -/// @title IMintableAndBurnableERC20 -/// @notice Interface for mintable and burnable ERC20 tokens. -interface IMintableAndBurnableERC20 is IERC20 { - /// @notice Mints `_amount` of tokens to `_to`. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external; - - /// @notice Burns `_amount` of tokens from `_from`. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external; -} diff --git a/interfaces/L2/ISuperchainERC20.sol b/interfaces/L2/ISuperchainERC20.sol deleted file mode 100644 index 243f6f7ba..000000000 --- a/interfaces/L2/ISuperchainERC20.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Interfaces -import { IERC7802 } from "interfaces/L2/IERC7802.sol"; -import { IERC20Solady as IERC20 } from "interfaces/vendor/IERC20Solady.sol"; -import { ISemver } from "interfaces/universal/ISemver.sol"; - -/// @title ISuperchainERC20 -/// @notice This interface is available on the SuperchainERC20 contract. -/// @dev This interface is needed for the abstract SuperchainERC20 implementation but is not part of the standard -interface ISuperchainERC20 is IERC7802, IERC20, ISemver { - error Unauthorized(); - - function supportsInterface(bytes4 _interfaceId) external view returns (bool); - - function __constructor__() external; -} diff --git a/interfaces/cannon/IMIPS64.sol b/interfaces/cannon/IMIPS64.sol deleted file mode 100644 index 488becc4d..000000000 --- a/interfaces/cannon/IMIPS64.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ISemver } from "interfaces/universal/ISemver.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; - -/// @title IMIPS64 -/// @notice Interface for the MIPS64 contract. -interface IMIPS64 is ISemver { - struct State { - bytes32 memRoot; - bytes32 preimageKey; - uint32 preimageOffset; - uint32 pc; - uint32 nextPC; - uint32 lo; - uint32 hi; - uint32 heap; - uint8 exitCode; - bool exited; - uint64 step; - uint32[32] registers; - } - - error InvalidExitedValue(); - error InvalidPC(); - error InvalidSecondMemoryProof(); - error UnsupportedStateVersion(); - error InvalidMemoryProof(); - error InvalidRMWInstruction(); - - function version() external view returns (string memory); - function oracle() external view returns (IPreimageOracle oracle_); - function stateVersion() external view returns (uint256 stateVersion_); - function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32 postState_); - - function __constructor__(IPreimageOracle _oracle, uint256 _stateVersion) external; -} diff --git a/interfaces/cannon/IPreimageOracle.sol b/interfaces/cannon/IPreimageOracle.sol deleted file mode 100644 index 3b5ff82e5..000000000 --- a/interfaces/cannon/IPreimageOracle.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { LibKeccak } from "lib/lib-keccak/contracts/lib/LibKeccak.sol"; -import { LPPMetaData } from "src/cannon/libraries/CannonTypes.sol"; - -interface IPreimageOracle { - struct Leaf { - bytes input; - uint256 index; - bytes32 stateCommitment; - } - - error ActiveProposal(); - error AlreadyFinalized(); - error AlreadyInitialized(); - error BadProposal(); - error BondTransferFailed(); - error InsufficientBond(); - error InvalidInputSize(); - error InvalidPreimage(); - error InvalidProof(); - error NotEOA(); - error NotInitialized(); - error PartOffsetOOB(); - error PostStateMatches(); - error StatesNotContiguous(); - error TreeSizeOverflow(); - error WrongStartingBlock(); - - function KECCAK_TREE_DEPTH() external view returns (uint256); - function MAX_LEAF_COUNT() external view returns (uint256); - function MIN_BOND_SIZE() external view returns (uint256); - function PRECOMPILE_CALL_RESERVED_GAS() external view returns (uint256); - function addLeavesLPP( - uint256 _uuid, - uint256 _inputStartBlock, - bytes memory _input, - bytes32[] memory _stateCommitments, - bool _finalize - ) - external; - function challengeFirstLPP( - address _claimant, - uint256 _uuid, - Leaf memory _postState, - bytes32[] memory _postStateProof - ) - external; - function challengeLPP( - address _claimant, - uint256 _uuid, - LibKeccak.StateMatrix memory _stateMatrix, - Leaf memory _preState, - bytes32[] memory _preStateProof, - Leaf memory _postState, - bytes32[] memory _postStateProof - ) - external; - function challengePeriod() external view returns (uint256 challengePeriod_); - function getTreeRootLPP(address _owner, uint256 _uuid) external view returns (bytes32 treeRoot_); - function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable; - function loadBlobPreimagePart( - uint256 _z, - uint256 _y, - bytes memory _commitment, - bytes memory _proof, - uint256 _partOffset - ) - external; - function loadKeccak256PreimagePart(uint256 _partOffset, bytes memory _preimage) external; - function loadLocalData( - uint256 _ident, - bytes32 _localContext, - bytes32 _word, - uint256 _size, - uint256 _partOffset - ) - external - returns (bytes32 key_); - function loadPrecompilePreimagePart( - uint256 _partOffset, - address _precompile, - uint64 _requiredGas, - bytes memory _input - ) - external; - function loadSha256PreimagePart(uint256 _partOffset, bytes memory _preimage) external; - function minProposalSize() external view returns (uint256 minProposalSize_); - function preimageLengths(bytes32) external view returns (uint256); - function preimagePartOk(bytes32, uint256) external view returns (bool); - function preimageParts(bytes32, uint256) external view returns (bytes32); - function proposalBlocks(address, uint256, uint256) external view returns (uint64); - function proposalBlocksLen(address _claimant, uint256 _uuid) external view returns (uint256 len_); - function proposalBonds(address, uint256) external view returns (uint256); - function proposalBranches(address, uint256, uint256) external view returns (bytes32); - function proposalCount() external view returns (uint256 count_); - function proposalMetadata(address, uint256) external view returns (LPPMetaData); - function proposalParts(address, uint256) external view returns (bytes32); - function proposals(uint256) external view returns (address claimant, uint256 uuid); // nosemgrep: - // sol-style-return-arg-fmt - function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_); - function squeezeLPP( - address _claimant, - uint256 _uuid, - LibKeccak.StateMatrix memory _stateMatrix, - Leaf memory _preState, - bytes32[] memory _preStateProof, - Leaf memory _postState, - bytes32[] memory _postStateProof - ) - external; - function version() external view returns (string memory); - function zeroHashes(uint256) external view returns (bytes32); - - function __constructor__(uint256 _minProposalSize, uint256 _challengePeriod) external; -} diff --git a/interfaces/vendor/IERC20Solady.sol b/interfaces/vendor/IERC20Solady.sol deleted file mode 100644 index b05b906ee..000000000 --- a/interfaces/vendor/IERC20Solady.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IERC20Solady { - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - /// @dev The allowance of Permit2 is fixed at infinity. - error Permit2AllowanceIsFixedAtInfinity(); - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev Returns the name of the token. - function name() external view returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() external view returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() external view returns (uint8); - - /// @dev Returns the amount of tokens in existence. - function totalSupply() external view returns (uint256 result); - - /// @dev Returns the amount of tokens owned by `owner` - function balanceOf(address owner) external view returns (uint256 result); - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) external view returns (uint256 result); - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) external returns (bool); - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) external returns (bool); - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) external returns (bool); - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) external view returns (uint256 result); - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) - external; - - /// @dev Returns the EIP-712 domain separator for the EIP-2612 permit. - function DOMAIN_SEPARATOR() external view returns (bytes32 result); -} diff --git a/justfile b/justfile index dbfb035ea..e525ebb9f 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,6 @@ install-foundry: deps: clean-lib forge install --no-git \ github.com/foundry-rs/forge-std@6853b9ec7df5dc0c213b05ae67785ad4f4baa0ea \ - github.com/ethereum-optimism/lib-keccak@3b1e7bbb4cc23e9228097cfebe42aedaf3b8f2b9 \ github.com/OpenZeppelin/openzeppelin-contracts@ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d \ github.com/OpenZeppelin/openzeppelin-contracts-upgradeable@0a2cb9a445c365870ed7a8ab461b12acf3e27d63 \ github.com/transmissions11/solmate@8f9b23f8838670afda0fd8983f2c41e8037ae6bc \ @@ -128,7 +127,7 @@ prepare-upgrade-env *ARGS : build-go-ffi export FORK_BACKOFF=1000 export FORK_TEST=true {{ARGS}} \ - --match-path "test/{L1,dispute,cannon}/**" + --match-path "test/L1/**" # Runs upgrade path variant of contract tests. test-upgrade *ARGS: @@ -151,7 +150,7 @@ test-upgrade-against-anvil *ARGS: build-go-ffi export FORK_RPC_URL=http://127.0.0.1:8545 export FORK_TEST=true forge test {{ARGS}} \ - --match-path "test/{L1,dispute,cannon}/**" + --match-path "test/L1/**" # Runs standard contract tests with rerun flag. test-rerun: build-go-ffi diff --git a/scripts/checks/interfaces/main.go b/scripts/checks/interfaces/main.go index 66c0f12a0..9274ad843 100644 --- a/scripts/checks/interfaces/main.go +++ b/scripts/checks/interfaces/main.go @@ -22,16 +22,10 @@ func normalizeInternalType(internalType string) string { // excludeContracts is a list of contracts whose interfaces do not need to match perfectly. var excludeContracts = []string{ - "IERC20", "IERC721", "IERC5267", "IERC721Enumerable", "IERC721Upgradeable", "IERC721Metadata", - "IERC165", "IERC165Upgradeable", "ERC721TokenReceiver", "ERC1155TokenReceiver", - "ERC777TokensRecipient", "Guard", "IProxy", "Vm", "VmSafe", "IMulticall3", - "IERC721TokenReceiver", "IProxyCreationCallback", "IBeacon", "IEIP712", - "IHasSuperchainConfig", - "IEAS", "ISchemaResolver", "ISchemaRegistry", + "IProxy", "IEIP712", "IEAS", "ISchemaResolver", "ISchemaRegistry", // TODO: Interfaces that need to be fixed - "IInitializable", "IOptimismMintableERC20", - "KontrolCheatsBase", "IResolvedDelegateProxy", + "IInitializable", "IOptimismMintableERC20", "IResolvedDelegateProxy", } // excludeSourceContracts is a list of contracts that are allowed to not have interfaces @@ -39,7 +33,7 @@ var excludeSourceContracts = []string{ "CrossDomainMessengerLegacySpacer0", "CrossDomainMessengerLegacySpacer1", // FIXME - "WETH", "MIPS64", + "WETH", } type ContractDefinition struct { diff --git a/scripts/checks/test-validation/exclusions.toml b/scripts/checks/test-validation/exclusions.toml index 262884850..84d41075c 100644 --- a/scripts/checks/test-validation/exclusions.toml +++ b/scripts/checks/test-validation/exclusions.toml @@ -18,8 +18,6 @@ src_validation = [ "test/invariants/", # Invariant testing framework - no direct src counterpart "test/scripts/", # Script tests - test deployment/utility scripts, not contracts - "test/cannon/MIPS64Memory.t.sol", # Tests external MIPS implementation - "test/libraries/bridge/LibClock.t.sol", # Tests library utilities "test/libraries/bridge/LibGameId.t.sol", # Tests library utilities "test/universal/ExtendedPause.t.sol", # Tests extended functionality "test/vendor/Initializable.t.sol", # Tests external vendor code @@ -41,7 +39,6 @@ src_validation = [ # These naming inconsistencies may indicate the presence of specialized test # infrastructure beyond standard harnesses or different setup contracts patterns. contract_name_validation = [ - "test/L1/proofs/FaultDisputeGame.t.sol", # Contains contracts not matching FaultDisputeGame base name "test/L1/ResourceMetering.t.sol", # Contains contracts not matching ResourceMetering base name "test/L2/GasPriceOracle.t.sol", # Contains contracts not matching GasPriceOracle base name "test/universal/StandardBridge.t.sol", # Contains contracts not matching StandardBridge base name diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 87f72c988..7fe484580 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -14,8 +14,6 @@ contract DeployConfig is Script { address public batchSenderAddress; address public finalSystemOwner; address public l1FeeVaultRecipient; - address public l2OutputOracleChallenger; - address public l2OutputOracleProposer; address public nitroEnclaveVerifier; address public operatorFeeVaultRecipient; address public p2pSequencerAddress; @@ -30,7 +28,6 @@ contract DeployConfig is Script { bool public fundDevAccounts; bool public useUpgradedFork; - bytes32 public faultGameGenesisOutputRoot; bytes32 public multiproofConfigHash; bytes32 public multiproofGenesisOutputRoot; bytes32 public teeImageHash; @@ -43,13 +40,7 @@ contract DeployConfig is Script { uint256 public baseFeeVaultMinimumWithdrawalAmount; uint256 public baseFeeVaultWithdrawalNetwork; uint256 public disputeGameFinalityDelaySeconds; - uint256 public faultGameAbsolutePrestate; - uint256 public faultGameGenesisBlock; - uint256 public faultGameV2ClockExtension; - uint256 public faultGameV2MaxClockDuration; - uint256 public faultGameV2MaxGameDepth; - uint256 public faultGameV2SplitDepth; - uint256 public faultGameWithdrawalDelay; + uint256 public delayedWETHWithdrawalDelay; uint256 public l1ChainId; uint256 public l1FeeVaultMinimumWithdrawalAmount; uint256 public l1FeeVaultWithdrawalNetwork; @@ -63,8 +54,6 @@ contract DeployConfig is Script { uint256 public multiproofIntermediateBlockInterval; uint256 public operatorFeeVaultMinimumWithdrawalAmount; uint256 public operatorFeeVaultWithdrawalNetwork; - uint256 public preimageOracleChallengePeriod; - uint256 public preimageOracleMinProposalSize; uint256 public proofMaturityDelaySeconds; uint256 public respectedGameType; uint256 public sequencerFeeVaultMinimumWithdrawalAmount; @@ -78,8 +67,6 @@ contract DeployConfig is Script { batchSenderAddress = _json.readAddress("$.batchSenderAddress"); finalSystemOwner = _json.readAddress("$.finalSystemOwner"); l1FeeVaultRecipient = _json.readAddress("$.l1FeeVaultRecipient"); - l2OutputOracleChallenger = _json.readAddress("$.l2OutputOracleChallenger"); - l2OutputOracleProposer = _json.readAddress("$.l2OutputOracleProposer"); nitroEnclaveVerifier = _json.readAddress("$.nitroEnclaveVerifier"); operatorFeeVaultRecipient = _json.readAddress("$.operatorFeeVaultRecipient"); p2pSequencerAddress = _json.readAddress("$.p2pSequencerAddress"); @@ -93,7 +80,6 @@ contract DeployConfig is Script { fundDevAccounts = _json.readBoolOr("$.fundDevAccounts", false); - faultGameGenesisOutputRoot = _json.readBytes32("$.faultGameGenesisOutputRoot"); multiproofConfigHash = _json.readBytes32("$.multiproofConfigHash"); multiproofGenesisOutputRoot = _json.readBytes32("$.multiproofGenesisOutputRoot"); teeImageHash = _json.readBytes32("$.teeImageHash"); @@ -106,13 +92,7 @@ contract DeployConfig is Script { baseFeeVaultMinimumWithdrawalAmount = _json.readUint("$.baseFeeVaultMinimumWithdrawalAmount"); baseFeeVaultWithdrawalNetwork = _json.readUint("$.baseFeeVaultWithdrawalNetwork"); disputeGameFinalityDelaySeconds = _json.readUint("$.disputeGameFinalityDelaySeconds"); - faultGameAbsolutePrestate = _json.readUint("$.faultGameAbsolutePrestate"); - faultGameGenesisBlock = _json.readUint("$.faultGameGenesisBlock"); - faultGameV2ClockExtension = _json.readUintOr("$.faultGameV2ClockExtension", 10800); - faultGameV2MaxClockDuration = _json.readUintOr("$.faultGameV2MaxClockDuration", 302400); - faultGameV2MaxGameDepth = _json.readUintOr("$.faultGameV2MaxGameDepth", 73); - faultGameV2SplitDepth = _json.readUintOr("$.faultGameV2SplitDepth", 30); - faultGameWithdrawalDelay = _json.readUint("$.faultGameWithdrawalDelay"); + delayedWETHWithdrawalDelay = _json.readUint("$.delayedWETHWithdrawalDelay"); l1ChainId = _json.readUint("$.l1ChainId"); l1FeeVaultMinimumWithdrawalAmount = _json.readUint("$.l1FeeVaultMinimumWithdrawalAmount"); l1FeeVaultWithdrawalNetwork = _json.readUint("$.l1FeeVaultWithdrawalNetwork"); @@ -126,8 +106,6 @@ contract DeployConfig is Script { multiproofIntermediateBlockInterval = _json.readUintOr("$.multiproofIntermediateBlockInterval", 10); operatorFeeVaultMinimumWithdrawalAmount = _json.readUint("$.operatorFeeVaultMinimumWithdrawalAmount"); operatorFeeVaultWithdrawalNetwork = _json.readUint("$.operatorFeeVaultWithdrawalNetwork"); - preimageOracleChallengePeriod = _json.readUint("$.preimageOracleChallengePeriod"); - preimageOracleMinProposalSize = _json.readUint("$.preimageOracleMinProposalSize"); proofMaturityDelaySeconds = _json.readUintOr("$.proofMaturityDelaySeconds", 0); respectedGameType = _json.readUintOr("$.respectedGameType", 0); sequencerFeeVaultMinimumWithdrawalAmount = _json.readUint("$.sequencerFeeVaultMinimumWithdrawalAmount"); diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index aa3545b85..227d123d6 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -12,8 +12,6 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { StateDiff } from "scripts/libraries/StateDiff.sol"; import { Types } from "scripts/libraries/Types.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -28,8 +26,6 @@ import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry. import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { ITEEProverRegistry } from "interfaces/L1/proofs/tee/ITEEProverRegistry.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; @@ -45,8 +41,8 @@ import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; import { Constants } from "src/libraries/Constants.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; -import { Claim, Duration, GameType, GameTypes, Hash, Proposal } from "src/libraries/bridge/Types.sol"; -import { LibGameArgs } from "src/libraries/bridge/LibGameArgs.sol"; +import { GameType, GameTypes, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; /// @title SystemDeploy /// @notice Script-level API for deploying or upgrading a complete OP Stack L1 system. @@ -57,7 +53,6 @@ contract SystemDeploy is Script { Artifacts internal constant artifacts = Artifacts(address(uint160(uint256(keccak256(abi.encode("optimism.artifacts")))))); - uint256 internal constant STANDARD_MIPS_VERSION = 8; uint256 internal constant ETH_MAINNET_CHAIN_ID = 1; uint256 internal constant ETH_SEPOLIA_CHAIN_ID = 11155111; @@ -75,36 +70,24 @@ contract SystemDeploy is Script { struct ImplementationInput { uint256 withdrawalDelaySeconds; - uint256 minProposalSizeBytes; - uint256 challengePeriodSeconds; uint256 proofMaturityDelaySeconds; uint256 disputeGameFinalityDelaySeconds; - uint256 mipsVersion; - uint256 faultGameV2MaxGameDepth; - uint256 faultGameV2SplitDepth; - uint256 faultGameV2ClockExtension; - uint256 faultGameV2MaxClockDuration; bytes32 teeImageHash; bytes32 zkRangeHash; bytes32 zkAggregationHash; bytes32 multiproofConfigHash; uint256 multiproofGameType; address nitroEnclaveVerifier; - uint256 l2ChainID; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; ISP1Verifier sp1Verifier; address teeProposer; address teeChallenger; - ISuperchainConfig superchainConfigProxy; - IProxyAdmin superchainProxyAdmin; address guardian; address incidentResponder; } struct DeployInput { - bool deploySuperchain; - bool deployImplementations; bool saveArtifacts; SuperchainInput superchainInput; ISuperchainConfig superchainConfigProxy; @@ -113,15 +96,9 @@ contract SystemDeploy is Script { Types.DeployInput opChainInput; } - struct ImplementationOutput { - Types.Implementations implementations; - IPreimageOracle preimageOracleSingleton; - IMIPS64 mipsSingleton; - } - struct DeployOutput { SuperchainOutput superchain; - ImplementationOutput implementationOutput; + Types.Implementations impls; Types.DeployOutput opChain; } @@ -129,12 +106,12 @@ contract SystemDeploy is Script { bool saveArtifacts; ISuperchainConfig superchainConfigProxy; Types.Implementations implementations; - Types.OpChainConfig[] opChainConfigs; + ISystemConfig systemConfigProxy; } struct UpgradeOutput { bool superchainConfigUpgraded; - uint256 chainsUpgraded; + bool chainUpgraded; } struct AggregateVerifierInput { @@ -147,7 +124,7 @@ contract SystemDeploy is Script { bytes32 zkRangeHash; bytes32 zkAggregationHash; bytes32 multiproofConfigHash; - uint256 l2ChainID; + uint256 l2ChainId; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; } @@ -167,8 +144,7 @@ contract SystemDeploy is Script { error InvalidRoleAddress(string role); error InvalidStartingAnchorRoot(); error MissingImplementations(); - error PrestateNotSet(); - error SuperchainConfigNeedsUpgrade(uint256 index); + error SuperchainConfigNeedsUpgrade(); /// @notice Sets up the shared deployment config and artifact registry. function setUp() public virtual { @@ -218,13 +194,9 @@ contract SystemDeploy is Script { } /// @notice Deploys implementation contracts from the active deploy config and saves their artifact names. - function deployImplementations() public returns (ImplementationOutput memory output_) { - ISuperchainConfig superchainConfigProxy = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); - IProxyAdmin superchainProxyAdmin = superchainConfigProxy.proxyAdmin(); - - output_ = _deployImplementations(_configuredImplementationsInput(superchainConfigProxy, superchainProxyAdmin)); - _saveUpgradeArtifacts(output_.implementations); - _saveIfSet("PreimageOracle", address(output_.preimageOracleSingleton)); + function deployImplementations() public returns (Types.Implementations memory output_) { + output_ = _deployImplementations(_configuredImplementationsInput()); + _saveUpgradeArtifacts(output_); } /// @notice Deploys the shared Superchain proxy admin and SuperchainConfig proxy. @@ -246,9 +218,6 @@ contract SystemDeploy is Script { disputeGameFactoryImpl: artifacts.mustGetAddress("DisputeGameFactoryImpl"), anchorStateRegistryImpl: artifacts.mustGetAddress("AnchorStateRegistryImpl"), delayedWETHImpl: artifacts.mustGetAddress("DelayedWETHImpl"), - mipsImpl: artifacts.mustGetAddress("MipsSingleton"), - faultDisputeGameV2Impl: artifacts.mustGetAddress("FaultDisputeGame"), - permissionedDisputeGameV2Impl: artifacts.mustGetAddress("PermissionedDisputeGame"), aggregateVerifierImpl: artifacts.getAddress("AggregateVerifier"), teeProverRegistryImpl: artifacts.getAddress("TEEProverRegistryImpl"), teeVerifierImpl: artifacts.getAddress("TEEVerifier"), @@ -270,8 +239,6 @@ contract SystemDeploy is Script { function _deployInput() internal view returns (DeployInput memory input_) { Types.Implementations memory emptyImpls; input_ = DeployInput({ - deploySuperchain: true, - deployImplementations: true, saveArtifacts: true, superchainInput: SuperchainInput({ guardian: cfg.superchainConfigGuardian(), @@ -279,47 +246,28 @@ contract SystemDeploy is Script { superchainProxyAdminOwner: cfg.finalSystemOwner() }), superchainConfigProxy: ISuperchainConfig(address(0)), - implementationsInput: _configuredImplementationsInput( - ISuperchainConfig(address(0)), IProxyAdmin(address(0)) - ), + implementationsInput: _configuredImplementationsInput(), implementations: emptyImpls, opChainInput: _configuredOPChainInput() }); } - function _configuredImplementationsInput( - ISuperchainConfig _superchainConfigProxy, - IProxyAdmin _superchainProxyAdmin - ) - internal - view - returns (ImplementationInput memory input_) - { + function _configuredImplementationsInput() internal view returns (ImplementationInput memory input_) { input_ = ImplementationInput({ - withdrawalDelaySeconds: cfg.faultGameWithdrawalDelay(), - minProposalSizeBytes: cfg.preimageOracleMinProposalSize(), - challengePeriodSeconds: cfg.preimageOracleChallengePeriod(), + withdrawalDelaySeconds: cfg.delayedWETHWithdrawalDelay(), proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds(), - mipsVersion: STANDARD_MIPS_VERSION, - faultGameV2MaxGameDepth: cfg.faultGameV2MaxGameDepth(), - faultGameV2SplitDepth: cfg.faultGameV2SplitDepth(), - faultGameV2ClockExtension: cfg.faultGameV2ClockExtension(), - faultGameV2MaxClockDuration: cfg.faultGameV2MaxClockDuration(), teeImageHash: cfg.teeImageHash(), zkRangeHash: cfg.zkRangeHash(), zkAggregationHash: cfg.zkAggregationHash(), multiproofConfigHash: cfg.multiproofConfigHash(), multiproofGameType: cfg.multiproofGameType(), nitroEnclaveVerifier: cfg.nitroEnclaveVerifier(), - l2ChainID: cfg.l2ChainId(), multiproofBlockInterval: cfg.multiproofBlockInterval(), multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(), sp1Verifier: ISP1Verifier(cfg.sp1Verifier()), teeProposer: cfg.teeProposer(), teeChallenger: cfg.teeChallenger(), - superchainConfigProxy: _superchainConfigProxy, - superchainProxyAdmin: _superchainProxyAdmin, guardian: cfg.superchainConfigGuardian(), incidentResponder: cfg.superchainConfigIncidentResponder() }); @@ -331,46 +279,36 @@ contract SystemDeploy is Script { opChainProxyAdminOwner: cfg.finalSystemOwner(), systemConfigOwner: cfg.finalSystemOwner(), batcher: cfg.batchSenderAddress(), - unsafeBlockSigner: cfg.p2pSequencerAddress(), - proposer: cfg.l2OutputOracleProposer(), - challenger: cfg.l2OutputOracleChallenger() + unsafeBlockSigner: cfg.p2pSequencerAddress() }), basefeeScalar: cfg.basefeeScalar(), blobBasefeeScalar: cfg.blobbasefeeScalar(), l2ChainId: cfg.l2ChainId(), startingAnchorRoot: Proposal({ - root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), l2SequenceNumber: cfg.faultGameGenesisBlock() + root: Hash.wrap(cfg.multiproofGenesisOutputRoot()), l2SequenceNumber: cfg.multiproofGenesisBlockNumber() }), saltMixer: "salt mixer", - gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), - disputeGameType: GameTypes.PERMISSIONED_CANNON, - disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), - disputeMaxGameDepth: cfg.faultGameV2MaxGameDepth(), - disputeSplitDepth: cfg.faultGameV2SplitDepth(), - disputeClockExtension: Duration.wrap(uint64(cfg.faultGameV2ClockExtension())), - disputeMaxClockDuration: Duration.wrap(uint64(cfg.faultGameV2MaxClockDuration())) + gasLimit: uint64(cfg.l2GenesisBlockGasLimit()) }); } function deploy(DeployInput memory _input) public returns (DeployOutput memory output_) { - _assertValidDeployInput(_input); - output_.superchain = _deployOrLoadSuperchain(_input); - if (_input.deployImplementations) { - output_.implementationOutput = - _deployImplementations(_withSuperchainImplementationsInput(_input, output_.superchain)); + if (_implementationsEmpty(_input.implementations)) { + output_.impls = _deployImplementations(_input.implementationsInput); } else { - output_.implementationOutput = _existingImplementationOutput(_input.implementations); + _assertValidImplementations(_input.implementations); + output_.impls = _input.implementations; } Types.Implementations memory implementations; (output_.opChain, implementations) = _deployOPChain({ _input: _input.opChainInput, _superchainConfig: output_.superchain.superchainConfigProxy, - _impls: output_.implementationOutput.implementations, - _implementationsInput: _withSuperchainImplementationsInput(_input, output_.superchain) + _impls: output_.impls, + _implementationsInput: _input.implementationsInput }); - output_.implementationOutput.implementations = implementations; + output_.impls = implementations; if (_input.saveArtifacts) { _saveDeployArtifacts(output_); @@ -387,19 +325,19 @@ contract SystemDeploy is Script { _upgradeSuperchainConfigIfNeeded(_input.superchainConfigProxy, _input.implementations); } - for (uint256 i = 0; i < _input.opChainConfigs.length; i++) { - Types.OpChainConfig memory config = _input.opChainConfigs[i]; - DeployUtils.assertValidContractAddress(address(config.systemConfigProxy)); + if (address(_input.systemConfigProxy) != address(0)) { + ISystemConfig systemConfigProxy = _input.systemConfigProxy; + DeployUtils.assertValidContractAddress(address(systemConfigProxy)); - ISuperchainConfig superchainConfig = config.systemConfigProxy.superchainConfig(); + ISuperchainConfig superchainConfig = systemConfigProxy.superchainConfig(); if (SemverComp.lt( superchainConfig.version(), ISuperchainConfig(_input.implementations.superchainConfigImpl).version() )) { - revert SuperchainConfigNeedsUpgrade(i); + revert SuperchainConfigNeedsUpgrade(); } - _upgradeOPChain(config, _input.implementations); - output_.chainsUpgraded++; + _upgradeOPChain(systemConfigProxy, _input.implementations); + output_.chainUpgraded = true; } if (_input.saveArtifacts) { @@ -408,7 +346,7 @@ contract SystemDeploy is Script { } function _deployOrLoadSuperchain(DeployInput memory _input) internal returns (SuperchainOutput memory output_) { - if (_input.deploySuperchain) { + if (address(_input.superchainConfigProxy) == address(0)) { output_ = _deploySuperchain(_input.superchainInput); } else { DeployUtils.assertValidContractAddress(address(_input.superchainConfigProxy)); @@ -417,19 +355,6 @@ contract SystemDeploy is Script { } } - function _withSuperchainImplementationsInput( - DeployInput memory _input, - SuperchainOutput memory _superchain - ) - internal - pure - returns (ImplementationInput memory input_) - { - input_ = _input.implementationsInput; - input_.superchainConfigProxy = _superchain.superchainConfigProxy; - input_.superchainProxyAdmin = _superchain.superchainProxyAdmin; - } - function _deploySuperchain(SuperchainInput memory _input) internal returns (SuperchainOutput memory output_) { _assertValidSuperchainInput(_input); @@ -507,38 +432,21 @@ contract SystemDeploy is Script { function _deployImplementations(ImplementationInput memory _input) internal - returns (ImplementationOutput memory output_) + returns (Types.Implementations memory output_) { _assertValidImplementationInput(_input); - output_.implementations.superchainConfigImpl = - address(_deploySuperchainConfigImpl(_input.guardian, _input.incidentResponder)); - output_.implementations.systemConfigImpl = address(_deploySystemConfigImpl()); - output_.implementations.l1CrossDomainMessengerImpl = address(_deployL1CrossDomainMessengerImpl()); - output_.implementations.l1ERC721BridgeImpl = address(_deployL1ERC721BridgeImpl()); - output_.implementations.l1StandardBridgeImpl = address(_deployL1StandardBridgeImpl()); - output_.implementations.optimismMintableERC20FactoryImpl = address(_deployOptimismMintableERC20FactoryImpl()); - output_.implementations.optimismPortalImpl = address(_deployOptimismPortalImpl(_input)); - output_.implementations.ethLockboxImpl = address(_deployETHLockboxImpl()); - output_.implementations.delayedWETHImpl = address(_deployDelayedWETHImpl(_input)); - output_.preimageOracleSingleton = _deployPreimageOracleSingleton(_input); - output_.mipsSingleton = _deployMipsSingleton(_input, output_.preimageOracleSingleton); - output_.implementations.mipsImpl = address(output_.mipsSingleton); - output_.implementations.disputeGameFactoryImpl = address(_deployDisputeGameFactoryImpl()); - output_.implementations.anchorStateRegistryImpl = address(_deployAnchorStateRegistryImpl(_input)); - output_.implementations.faultDisputeGameV2Impl = address(_deployFaultDisputeGameV2Impl(_input)); - output_.implementations.permissionedDisputeGameV2Impl = address(_deployPermissionedDisputeGameV2Impl(_input)); - } - - function _existingImplementationOutput(Types.Implementations memory _impls) - internal - view - returns (ImplementationOutput memory output_) - { - _assertValidImplementations(_impls); - output_.implementations = _impls; - output_.mipsSingleton = IMIPS64(_impls.mipsImpl); - output_.preimageOracleSingleton = output_.mipsSingleton.oracle(); + output_.superchainConfigImpl = address(_deploySuperchainConfigImpl(_input.guardian, _input.incidentResponder)); + output_.systemConfigImpl = address(_deploySystemConfigImpl()); + output_.l1CrossDomainMessengerImpl = address(_deployL1CrossDomainMessengerImpl()); + output_.l1ERC721BridgeImpl = address(_deployL1ERC721BridgeImpl()); + output_.l1StandardBridgeImpl = address(_deployL1StandardBridgeImpl()); + output_.optimismMintableERC20FactoryImpl = address(_deployOptimismMintableERC20FactoryImpl()); + output_.optimismPortalImpl = address(_deployOptimismPortalImpl(_input)); + output_.ethLockboxImpl = address(_deployETHLockboxImpl()); + output_.delayedWETHImpl = address(_deployDelayedWETHImpl(_input)); + output_.disputeGameFactoryImpl = address(_deployDisputeGameFactoryImpl()); + output_.anchorStateRegistryImpl = address(_deployAnchorStateRegistryImpl(_input)); } function _deployOPChain( @@ -578,6 +486,7 @@ contract SystemDeploy is Script { IDisputeGameFactory(_deployProxy(_input, output_.opChainProxyAdmin, "DisputeGameFactory")); output_.anchorStateRegistryProxy = IAnchorStateRegistry(_deployProxy(_input, output_.opChainProxyAdmin, "AnchorStateRegistry")); + output_.delayedWETHProxy = IDelayedWETH(payable(_deployProxy(_input, output_.opChainProxyAdmin, "DelayedWETH"))); output_.l1StandardBridgeProxy = IL1StandardBridge( payable(_createDeterministic( @@ -609,16 +518,11 @@ contract SystemDeploy is Script { vm.broadcast(msg.sender); output_.opChainProxyAdmin.setImplementationName(address(output_.l1CrossDomainMessengerProxy), messengerName); - output_.delayedWETHPermissionedGameProxy = - IDelayedWETH(payable(_deployProxy(_input, output_.opChainProxyAdmin, "DelayedWETHPermissionedGame"))); - _initializeOPChain(_input, _superchainConfig, impls_, output_); - output_.delayedWETHPermissionlessGameProxy = - IDelayedWETH(payable(_deployProxy(_input, output_.opChainProxyAdmin, "DelayedWETHPermissionlessGame"))); _upgradeToAndCall( output_.opChainProxyAdmin, - address(output_.delayedWETHPermissionlessGameProxy), + address(output_.delayedWETHProxy), _impls.delayedWETHImpl, abi.encodeCall(IDelayedWETH.initialize, (output_.systemConfigProxy)) ); @@ -702,20 +606,12 @@ contract SystemDeploy is Script { ) ); - _upgradeToAndCall( - _output.opChainProxyAdmin, - address(_output.delayedWETHPermissionedGameProxy), - _impls.delayedWETHImpl, - abi.encodeCall(IDelayedWETH.initialize, (_output.systemConfigProxy)) - ); - _upgradeToAndCall( _output.opChainProxyAdmin, address(_output.disputeGameFactoryProxy), _impls.disputeGameFactoryImpl, abi.encodeCall(IDisputeGameFactory.initialize, (msg.sender)) ); - _setPermissionedGameImpl(_input, _impls, _output); _upgradeToAndCall( _output.opChainProxyAdmin, @@ -741,102 +637,57 @@ contract SystemDeploy is Script { upgraded_ = true; } - function _upgradeOPChain(Types.OpChainConfig memory _config, Types.Implementations memory _impls) internal { - IProxyAdmin proxyAdmin = _config.systemConfigProxy.proxyAdmin(); - uint256 l2ChainId = _config.systemConfigProxy.l2ChainId(); + function _upgradeOPChain(ISystemConfig _systemConfigProxy, Types.Implementations memory _impls) internal { + IProxyAdmin proxyAdmin = _systemConfigProxy.proxyAdmin(); + uint256 l2ChainId = _systemConfigProxy.l2ChainId(); - _upgradeTo(proxyAdmin, address(_config.systemConfigProxy), _impls.systemConfigImpl); + _upgradeTo(proxyAdmin, address(_systemConfigProxy), _impls.systemConfigImpl); - IOptimismPortal optimismPortal = IOptimismPortal(payable(_config.systemConfigProxy.optimismPortal())); + IOptimismPortal optimismPortal = IOptimismPortal(payable(_systemConfigProxy.optimismPortal())); _upgradeTo(proxyAdmin, address(optimismPortal), _impls.optimismPortalImpl); _upgradeTo(proxyAdmin, address(optimismPortal.anchorStateRegistry()), _impls.anchorStateRegistryImpl); _upgradeTo( - proxyAdmin, - _config.systemConfigProxy.optimismMintableERC20Factory(), - _impls.optimismMintableERC20FactoryImpl + proxyAdmin, _systemConfigProxy.optimismMintableERC20Factory(), _impls.optimismMintableERC20FactoryImpl ); - IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(_config.systemConfigProxy.disputeGameFactory()); + IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(_systemConfigProxy.disputeGameFactory()); _upgradeTo(proxyAdmin, address(disputeGameFactory), _impls.disputeGameFactoryImpl); + _upgradeMultiproofContracts(_systemConfigProxy, disputeGameFactory, _impls); - ISystemConfig.Addresses memory opChainAddrs = _config.systemConfigProxy.getAddresses(); + ISystemConfig.Addresses memory opChainAddrs = _systemConfigProxy.getAddresses(); _upgradeTo(proxyAdmin, opChainAddrs.l1CrossDomainMessenger, _impls.l1CrossDomainMessengerImpl); _upgradeTo(proxyAdmin, opChainAddrs.l1StandardBridge, _impls.l1StandardBridgeImpl); _upgradeTo(proxyAdmin, opChainAddrs.l1ERC721Bridge, _impls.l1ERC721BridgeImpl); + if (opChainAddrs.delayedWETH != address(0)) { + _upgradeTo(proxyAdmin, opChainAddrs.delayedWETH, _impls.delayedWETHImpl); + } - IDisputeGame permissionedGame = disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON); - _setNewPermissionedGameImplV2({ - _impls: _impls, - _l2ChainId: l2ChainId, - _disputeGame: permissionedGame, - _disputeGameFactory: disputeGameFactory, - _opChainConfig: _config - }); - - _upgradePermissionlessGames(_config, _impls, disputeGameFactory, l2ChainId); - emit Upgraded(l2ChainId, _config.systemConfigProxy, msg.sender); + emit Upgraded(l2ChainId, _systemConfigProxy, msg.sender); } - function _upgradePermissionlessGames( - Types.OpChainConfig memory _config, - Types.Implementations memory _impls, + function _upgradeMultiproofContracts( + ISystemConfig _systemConfigProxy, IDisputeGameFactory _disputeGameFactory, - uint256 l2ChainId + Types.Implementations memory _impls ) internal { - IDisputeGame cannonGame = _disputeGameFactory.gameImpls(GameTypes.CANNON); - bool cannonExists = address(cannonGame) != address(0); - - LibGameArgs.GameArgs memory cannonParams; - if (cannonExists) { - cannonParams = _resolveGameParams(_disputeGameFactory, cannonGame, GameTypes.CANNON); - Claim cannonPrestate = _config.cannonPrestate.raw() != bytes32(0) - ? _config.cannonPrestate - : Claim.wrap(cannonParams.absolutePrestate); - _setNewPermissionlessGameImplV2({ - _impls: _impls, - _l2ChainId: l2ChainId, - _newAbsolutePrestate: cannonPrestate, - _newDelayedWeth: IDelayedWETH(payable(cannonParams.weth)), - _newAnchorStateRegistryProxy: IAnchorStateRegistry(cannonParams.anchorStateRegistry), - _gameType: GameTypes.CANNON, - _disputeGameFactory: _disputeGameFactory - }); - } - - IDisputeGame cannonKonaGame = _disputeGameFactory.gameImpls(GameTypes.CANNON_KONA); - bool konaExists = address(cannonKonaGame) != address(0); - if (!cannonExists && !konaExists) return; - - LibGameArgs.GameArgs memory konaParams = cannonExists - ? cannonParams - : _resolveGameParams(_disputeGameFactory, cannonKonaGame, GameTypes.CANNON_KONA); - - Claim cannonKonaPrestate = _config.cannonKonaPrestate; - if (cannonKonaPrestate.raw() == bytes32(0) && konaExists) { - cannonKonaPrestate = Claim.wrap( - cannonExists - ? _resolveGameParams(_disputeGameFactory, cannonKonaGame, GameTypes.CANNON_KONA).absolutePrestate - : konaParams.absolutePrestate - ); + IDisputeGame currentGameImpl = _disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER); + if (address(currentGameImpl) == address(0)) return; + + if (_impls.teeProverRegistryImpl != address(0)) { + AggregateVerifier currentAggregateVerifier = AggregateVerifier(address(currentGameImpl)); + TEEProverRegistry teeProverRegistry = + TEEVerifier(address(currentAggregateVerifier.TEE_VERIFIER())).TEE_PROVER_REGISTRY(); + _upgradeTo(_systemConfigProxy.proxyAdmin(), address(teeProverRegistry), _impls.teeProverRegistryImpl); } - if (cannonKonaPrestate.raw() == bytes32(0)) return; - - _setNewPermissionlessGameImplV2({ - _impls: _impls, - _l2ChainId: l2ChainId, - _newAbsolutePrestate: cannonKonaPrestate, - _newDelayedWeth: IDelayedWETH(payable(konaParams.weth)), - _newAnchorStateRegistryProxy: IAnchorStateRegistry(konaParams.anchorStateRegistry), - _gameType: GameTypes.CANNON_KONA, - _disputeGameFactory: _disputeGameFactory - }); - uint256 cannonInitBond = _disputeGameFactory.initBonds(GameTypes.CANNON); - if (cannonInitBond != 0) { + if (_impls.aggregateVerifierImpl != address(0) && address(currentGameImpl) != _impls.aggregateVerifierImpl) { + DeployUtils.assertValidContractAddress(_impls.aggregateVerifierImpl); vm.broadcast(msg.sender); - _disputeGameFactory.setInitBond(GameTypes.CANNON_KONA, cannonInitBond); + _disputeGameFactory.setImplementation( + GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(_impls.aggregateVerifierImpl)) + ); } } @@ -855,7 +706,7 @@ contract SystemDeploy is Script { l1StandardBridge: address(_output.l1StandardBridgeProxy), optimismPortal: address(_output.optimismPortalProxy), optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy), - delayedWETH: address(0) + delayedWETH: address(_output.delayedWETHProxy) }); return abi.encodeCall( @@ -890,100 +741,11 @@ contract SystemDeploy is Script { _output.systemConfigProxy, _output.disputeGameFactoryProxy, _input.startingAnchorRoot, - GameTypes.PERMISSIONED_CANNON + GameTypes.AGGREGATE_VERIFIER ) ); } - function _setPermissionedGameImpl( - Types.DeployInput memory _input, - Types.Implementations memory _impls, - Types.DeployOutput memory _output - ) - internal - { - bytes memory gameArgs = LibGameArgs.encode( - LibGameArgs.GameArgs({ - absolutePrestate: _input.disputeAbsolutePrestate.raw(), - vm: _impls.mipsImpl, - anchorStateRegistry: address(_output.anchorStateRegistryProxy), - weth: address(_output.delayedWETHPermissionedGameProxy), - l2ChainId: _input.l2ChainId, - proposer: _input.roles.proposer, - challenger: _input.roles.challenger - }) - ); - - vm.broadcast(msg.sender); - _output.disputeGameFactoryProxy - .setImplementation( - GameTypes.PERMISSIONED_CANNON, IDisputeGame(_impls.permissionedDisputeGameV2Impl), gameArgs - ); - } - - function _setNewPermissionedGameImplV2( - Types.Implementations memory _impls, - uint256 _l2ChainId, - IDisputeGame _disputeGame, - IDisputeGameFactory _disputeGameFactory, - Types.OpChainConfig memory _opChainConfig - ) - internal - { - LibGameArgs.GameArgs memory existing = - _resolveGameParams(_disputeGameFactory, _disputeGame, GameTypes.PERMISSIONED_CANNON); - Claim absolutePrestate = _opChainConfig.cannonPrestate.raw() != bytes32(0) - ? _opChainConfig.cannonPrestate - : Claim.wrap(existing.absolutePrestate); - if (absolutePrestate.raw() == bytes32(0)) revert PrestateNotSet(); - - bytes memory gameArgs = LibGameArgs.encode( - LibGameArgs.GameArgs({ - absolutePrestate: absolutePrestate.raw(), - vm: _impls.mipsImpl, - anchorStateRegistry: existing.anchorStateRegistry, - weth: existing.weth, - l2ChainId: _l2ChainId, - proposer: existing.proposer, - challenger: existing.challenger - }) - ); - - vm.broadcast(msg.sender); - _disputeGameFactory.setImplementation( - GameTypes.PERMISSIONED_CANNON, IDisputeGame(_impls.permissionedDisputeGameV2Impl), gameArgs - ); - } - - function _setNewPermissionlessGameImplV2( - Types.Implementations memory _impls, - uint256 _l2ChainId, - Claim _newAbsolutePrestate, - IDelayedWETH _newDelayedWeth, - IAnchorStateRegistry _newAnchorStateRegistryProxy, - GameType _gameType, - IDisputeGameFactory _disputeGameFactory - ) - internal - { - if (_newAbsolutePrestate.raw() == bytes32(0)) revert PrestateNotSet(); - - bytes memory gameArgs = LibGameArgs.encode( - LibGameArgs.GameArgs({ - absolutePrestate: _newAbsolutePrestate.raw(), - vm: _impls.mipsImpl, - anchorStateRegistry: address(_newAnchorStateRegistryProxy), - weth: address(_newDelayedWeth), - l2ChainId: _l2ChainId, - proposer: address(0), - challenger: address(0) - }) - ); - - vm.broadcast(msg.sender); - _disputeGameFactory.setImplementation(_gameType, IDisputeGame(_impls.faultDisputeGameV2Impl), gameArgs); - } - function _deployProxy( Types.DeployInput memory _input, IProxyAdmin _proxyAdmin, @@ -1163,43 +925,6 @@ contract SystemDeploy is Script { ); } - function _deployPreimageOracleSingleton(ImplementationInput memory _input) internal returns (IPreimageOracle) { - return IPreimageOracle( - DeployUtils.createDeterministic({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IPreimageOracle.__constructor__, (_input.minProposalSizeBytes, _input.challengePeriodSeconds) - ) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - } - - function _deployMipsSingleton( - ImplementationInput memory _input, - IPreimageOracle _preimageOracle - ) - internal - returns (IMIPS64) - { - if (_input.mipsVersion < 2 && (block.chainid == ETH_MAINNET_CHAIN_ID || block.chainid == ETH_SEPOLIA_CHAIN_ID)) - { - revert("SystemDeploy: only MIPS64 should be deployed on Mainnet or Sepolia"); - } - - return IMIPS64( - DeployUtils.createDeterministic({ - _name: "MIPS64", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IMIPS64.__constructor__, (_preimageOracle, _input.mipsVersion)) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - } - function _deployDisputeGameFactoryImpl() internal returns (IDisputeGameFactory) { return IDisputeGameFactory( DeployUtils.createDeterministic({ @@ -1222,33 +947,6 @@ contract SystemDeploy is Script { ); } - function _deployFaultDisputeGameV2Impl(ImplementationInput memory _input) internal returns (IFaultDisputeGameV2) { - IFaultDisputeGameV2.GameConstructorParams memory params = _gameConstructorParams(_input); - return IFaultDisputeGameV2( - DeployUtils.createDeterministic({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGameV2.__constructor__, (params))), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - } - - function _deployPermissionedDisputeGameV2Impl(ImplementationInput memory _input) - internal - returns (IPermissionedDisputeGameV2) - { - IFaultDisputeGameV2.GameConstructorParams memory params = _gameConstructorParams(_input); - return IPermissionedDisputeGameV2( - DeployUtils.createDeterministic({ - _name: "PermissionedDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IPermissionedDisputeGameV2.__constructor__, (params)) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - } - function _deployMultiproofContracts( Types.DeployInput memory _opChainInput, ImplementationInput memory _input, @@ -1257,7 +955,7 @@ contract SystemDeploy is Script { internal returns (MultiproofOutput memory output_) { - _assertValidMultiproofInput(_opChainInput, _input); + _assertValidMultiproofInput(_input); GameType gameType = GameType.wrap(uint32(_input.multiproofGameType)); @@ -1302,14 +1000,14 @@ contract SystemDeploy is Script { AggregateVerifierInput({ multiproofGameType: gameType, anchorStateRegistry: _output.anchorStateRegistryProxy, - delayedWETH: _output.delayedWETHPermissionlessGameProxy, + delayedWETH: _output.delayedWETHProxy, teeVerifier: output_.teeVerifier, zkVerifier: output_.zkVerifier, teeImageHash: _input.teeImageHash, zkRangeHash: _input.zkRangeHash, zkAggregationHash: _input.zkAggregationHash, multiproofConfigHash: _input.multiproofConfigHash, - l2ChainID: _input.l2ChainID, + l2ChainId: _opChainInput.l2ChainId, multiproofBlockInterval: _input.multiproofBlockInterval, multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval }) @@ -1338,7 +1036,7 @@ contract SystemDeploy is Script { _input.teeImageHash, AggregateVerifier.ZkHashes(_input.zkRangeHash, _input.zkAggregationHash), _input.multiproofConfigHash, - _input.l2ChainID, + _input.l2ChainId, _input.multiproofBlockInterval, _input.multiproofIntermediateBlockInterval ) @@ -1346,103 +1044,28 @@ contract SystemDeploy is Script { ); } - function _gameConstructorParams(ImplementationInput memory _input) - internal - pure - returns (IFaultDisputeGameV2.GameConstructorParams memory params_) - { - params_.maxGameDepth = _input.faultGameV2MaxGameDepth; - params_.splitDepth = _input.faultGameV2SplitDepth; - params_.clockExtension = Duration.wrap(uint64(_input.faultGameV2ClockExtension)); - params_.maxClockDuration = Duration.wrap(uint64(_input.faultGameV2MaxClockDuration)); - } - - /// @notice Resolves a game's stored args, falling back to direct reads on the deployed game. - function _resolveGameParams( - IDisputeGameFactory _dgf, - IDisputeGame _disputeGame, - GameType _gameType - ) - internal - view - returns (LibGameArgs.GameArgs memory params_) - { - bytes memory gameArgsBytes = _dgf.gameArgs(_gameType); - if (gameArgsBytes.length != 0) { - return LibGameArgs.decode(gameArgsBytes); - } - IFaultDisputeGameV2 fault = IFaultDisputeGameV2(address(_disputeGame)); - params_.absolutePrestate = fault.absolutePrestate().raw(); - params_.weth = address(fault.weth()); - params_.anchorStateRegistry = address(fault.anchorStateRegistry()); - if (_gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { - IPermissionedDisputeGameV2 perm = IPermissionedDisputeGameV2(address(_disputeGame)); - params_.proposer = perm.proposer(); - params_.challenger = perm.challenger(); - } - } - - function _assertValidDeployInput(DeployInput memory _input) internal pure { - require( - _input.deploySuperchain || address(_input.superchainConfigProxy) != address(0), - "SystemDeploy: no superchain" - ); - require( - _input.deployImplementations || !_implementationsEmpty(_input.implementations), "SystemDeploy: no impls" - ); - } - function _assertValidOPChainInput(Types.DeployInput memory _input) internal view { if (_input.l2ChainId == 0 || _input.l2ChainId == block.chainid) revert InvalidChainId(); if (_input.roles.opChainProxyAdminOwner == address(0)) revert InvalidRoleAddress("opChainProxyAdminOwner"); if (_input.roles.systemConfigOwner == address(0)) revert InvalidRoleAddress("systemConfigOwner"); if (_input.roles.batcher == address(0)) revert InvalidRoleAddress("batcher"); if (_input.roles.unsafeBlockSigner == address(0)) revert InvalidRoleAddress("unsafeBlockSigner"); - if (_input.roles.proposer == address(0)) revert InvalidRoleAddress("proposer"); - if (_input.roles.challenger == address(0)) revert InvalidRoleAddress("challenger"); if (Hash.unwrap(_input.startingAnchorRoot.root) == bytes32(0)) { revert InvalidStartingAnchorRoot(); } } function _assertValidImplementationInput(ImplementationInput memory _input) internal pure { - require( - _input.faultGameV2MaxGameDepth > 0 && _input.faultGameV2MaxGameDepth <= 125, - "SystemDeploy: faultGameV2MaxGameDepth out of range" - ); - require( - _input.faultGameV2SplitDepth >= 2 && _input.faultGameV2SplitDepth + 1 < _input.faultGameV2MaxGameDepth, - "SystemDeploy: invalid faultGameV2SplitDepth" - ); - require(_input.faultGameV2ClockExtension <= type(uint64).max, "SystemDeploy: clockExtension too large"); - require(_input.faultGameV2MaxClockDuration <= type(uint64).max, "SystemDeploy: maxClockDuration too large"); - require( - _input.faultGameV2MaxClockDuration >= _input.faultGameV2ClockExtension, - "SystemDeploy: invalid clock durations" - ); - require(_input.faultGameV2ClockExtension > 0, "SystemDeploy: faultGameV2ClockExtension not set"); require(_input.withdrawalDelaySeconds != 0, "SystemDeploy: withdrawalDelaySeconds not set"); - require(_input.minProposalSizeBytes != 0, "SystemDeploy: minProposalSizeBytes not set"); - require(_input.challengePeriodSeconds != 0, "SystemDeploy: challengePeriodSeconds not set"); - require(_input.challengePeriodSeconds <= type(uint64).max, "SystemDeploy: challengePeriodSeconds too large"); require(_input.proofMaturityDelaySeconds != 0, "SystemDeploy: proofMaturityDelaySeconds not set"); require(_input.disputeGameFinalityDelaySeconds != 0, "SystemDeploy: finality delay not set"); - require(_input.mipsVersion != 0, "SystemDeploy: mipsVersion not set"); - require(address(_input.superchainConfigProxy) != address(0), "SystemDeploy: superchainConfigProxy not set"); - require(address(_input.superchainProxyAdmin) != address(0), "SystemDeploy: superchainProxyAdmin not set"); } function _multiproofEnabled(ImplementationInput memory _input) internal pure returns (bool) { return _input.multiproofConfigHash != bytes32(0); } - function _assertValidMultiproofInput( - Types.DeployInput memory _opChainInput, - ImplementationInput memory _input - ) - internal - view - { + function _assertValidMultiproofInput(ImplementationInput memory _input) internal view { require(_input.teeImageHash != bytes32(0), "SystemDeploy: teeImageHash not set"); require(_input.zkRangeHash != bytes32(0), "SystemDeploy: zkRangeHash not set"); require(_input.zkAggregationHash != bytes32(0), "SystemDeploy: zkAggregationHash not set"); @@ -1452,7 +1075,6 @@ contract SystemDeploy is Script { require(address(_input.sp1Verifier) != address(0), "SystemDeploy: sp1Verifier not set"); DeployUtils.assertValidContractAddress(_input.nitroEnclaveVerifier); DeployUtils.assertValidContractAddress(address(_input.sp1Verifier)); - require(_input.l2ChainID == _opChainInput.l2ChainId, "SystemDeploy: multiproof l2ChainID mismatch"); require(_input.multiproofBlockInterval != 0, "SystemDeploy: multiproof block interval not set"); require( _input.multiproofIntermediateBlockInterval != 0, "SystemDeploy: multiproof intermediate interval not set" @@ -1478,9 +1100,6 @@ contract SystemDeploy is Script { DeployUtils.assertValidContractAddress(_impls.disputeGameFactoryImpl); DeployUtils.assertValidContractAddress(_impls.anchorStateRegistryImpl); DeployUtils.assertValidContractAddress(_impls.delayedWETHImpl); - DeployUtils.assertValidContractAddress(_impls.mipsImpl); - DeployUtils.assertValidContractAddress(_impls.faultDisputeGameV2Impl); - DeployUtils.assertValidContractAddress(_impls.permissionedDisputeGameV2Impl); } function _implementationsEmpty(Types.Implementations memory _impls) internal pure returns (bool) { @@ -1489,11 +1108,10 @@ contract SystemDeploy is Script { } function _saveDeployArtifacts(DeployOutput memory _output) internal { - _saveUpgradeArtifacts(_output.implementationOutput.implementations); + _saveUpgradeArtifacts(_output.impls); artifacts.save("SuperchainProxyAdmin", address(_output.superchain.superchainProxyAdmin)); artifacts.save("SuperchainConfigProxy", address(_output.superchain.superchainConfigProxy)); - _saveIfSet("PreimageOracle", address(_output.implementationOutput.preimageOracleSingleton)); Types.DeployOutput memory chain = _output.opChain; artifacts.save("ProxyAdmin", address(chain.opChainProxyAdmin)); @@ -1505,8 +1123,7 @@ contract SystemDeploy is Script { artifacts.save("L1CrossDomainMessengerProxy", address(chain.l1CrossDomainMessengerProxy)); artifacts.save("ETHLockboxProxy", address(chain.ethLockboxProxy)); artifacts.save("DisputeGameFactoryProxy", address(chain.disputeGameFactoryProxy)); - artifacts.save("PermissionedDelayedWETHProxy", address(chain.delayedWETHPermissionedGameProxy)); - artifacts.save("DelayedWETHProxy", address(chain.delayedWETHPermissionlessGameProxy)); + artifacts.save("DelayedWETHProxy", address(chain.delayedWETHProxy)); artifacts.save("AnchorStateRegistryProxy", address(chain.anchorStateRegistryProxy)); artifacts.save("OptimismPortalProxy", address(chain.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(chain.optimismPortalProxy)); @@ -1534,9 +1151,6 @@ contract SystemDeploy is Script { artifacts.save("DisputeGameFactoryImpl", _impls.disputeGameFactoryImpl); artifacts.save("AnchorStateRegistryImpl", _impls.anchorStateRegistryImpl); artifacts.save("DelayedWETHImpl", _impls.delayedWETHImpl); - artifacts.save("MipsSingleton", _impls.mipsImpl); - artifacts.save("FaultDisputeGame", _impls.faultDisputeGameV2Impl); - artifacts.save("PermissionedDisputeGame", _impls.permissionedDisputeGameV2Impl); _saveIfSet("AggregateVerifier", _impls.aggregateVerifierImpl); _saveIfSet("TEEProverRegistryImpl", _impls.teeProverRegistryImpl); _saveIfSet("TEEVerifier", _impls.teeVerifierImpl); diff --git a/scripts/go-ffi/README.md b/scripts/go-ffi/README.md index e5aa3dd7c..f04c44409 100644 --- a/scripts/go-ffi/README.md +++ b/scripts/go-ffi/README.md @@ -50,8 +50,6 @@ There are three modes available in `go-ffi`: `diff`, `trie`, and `merkle`. Each | `hashWithdrawal` | Hashes a withdrawal message and prints the digest | | `hashOutputRootProof` | Hashes an output root proof and prints the digest | | `getProveWithdrawalTransactionInputs` | Generates the inputs for a `getProveWithdrawalTransaction` call to the `OptimismPortal` given a withdrawal message | -| `cannonMemoryProof` | Computes a merkle proof of Cannon's memory | -| `cannonMemoryProof2` | Generates a memory proof for a third address against a trie containing two preset values | | `encodeScalarEcotone` | Encodes Ecotone basefee and blob basefee scalars into a single packed scalar | | `decodeScalarEcotone` | Decodes a packed Ecotone scalar into its basefee and blob basefee scalar components | | `encodeSuperRootProof` | ABI-encodes a super root proof | diff --git a/scripts/go-ffi/differential-testing.go b/scripts/go-ffi/differential-testing.go index 71401fa64..4515fe722 100644 --- a/scripts/go-ffi/differential-testing.go +++ b/scripts/go-ffi/differential-testing.go @@ -11,8 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -58,15 +56,6 @@ var ( {Name: "inputs", Type: proveWithdrawalInputs}, } - // cannonMemoryProof inputs tuple (bytes32, bytes) - cannonMemoryProof, _ = abi.NewType("tuple", "CannonMemoryProof", []abi.ArgumentMarshaling{ - {Name: "memRoot", Type: "bytes32"}, - {Name: "proof", Type: "bytes"}, - }) - cannonMemoryProofArgs = abi.Arguments{ - {Name: "encodedCannonMemoryProof", Type: cannonMemoryProof}, - } - // Super root proof tuple (uint8, uint64, OutputRootWithChainId[]) superRootProof, _ = abi.NewType("tuple", "SuperRootProof", []abi.ArgumentMarshaling{ {Name: "version", Type: "bytes1"}, @@ -164,48 +153,6 @@ func DiffTestUtils() { case "getProveWithdrawalTransactionInputs": nonce, sender, target, value, gasLimit, data := parseCrossDomainArgs(args) packTupleAndPrint(proveWithdrawalInputsArgs, buildProveWithdrawalInputs(nonce, sender, target, value, gasLimit, data)) - case "cannonMemoryProof": - // - // Equivalent to the cannon STF prestate proofs of the program counter and memory access for instruction execution. - if len(args) != 3 && len(args) != 5 && len(args) != 7 { - panic("Error: cannonMemoryProof requires 2, 4, or 6 arguments") - } - mem := memory.NewMemory() - memAddr0 := wordArg(args[1]) - mem.SetWord(memAddr0, wordArg(args[2])) - - var lastExtraAddr arch.Word - for i := 3; i+1 < len(args); i += 2 { - lastExtraAddr = wordArg(args[i]) - mem.SetWord(lastExtraAddr, wordArg(args[i+1])) - } - - var proof1 []byte - if len(args) >= 5 { - p := mem.MerkleProof(lastExtraAddr) - proof1 = p[:] - } - proof0 := mem.MerkleProof(memAddr0) - - packTupleAndPrint(cannonMemoryProofArgs, &cannonMemoryProofOutput{ - MemRoot: mem.MerkleRoot(), - Proof: append(proof0[:], proof1...), - }) - case "cannonMemoryProof2": - // - // Generates memory proof of `memAddr2` for a trie containing `memValue0` and `memValue1` - if len(args) != 6 { - panic("Error: cannonMemoryProof2 requires 5 arguments") - } - mem := memory.NewMemory() - mem.SetWord(wordArg(args[1]), wordArg(args[2])) - mem.SetWord(wordArg(args[3]), wordArg(args[4])) - memProof := mem.MerkleProof(wordArg(args[5])) - - packTupleAndPrint(cannonMemoryProofArgs, &cannonMemoryProofOutput{ - MemRoot: mem.MerkleRoot(), - Proof: memProof[:], - }) case "encodeScalarEcotone": encoded := eth.EncodeScalar(eth.EcotoneScalars{ BaseFeeScalar: uint32(parseUintN(args[1], 32)), diff --git a/scripts/go-ffi/utils.go b/scripts/go-ffi/utils.go index 3bd435e50..698981727 100644 --- a/scripts/go-ffi/utils.go +++ b/scripts/go-ffi/utils.go @@ -7,7 +7,6 @@ import ( "math/big" "strconv" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-node/bindings" @@ -58,11 +57,6 @@ func parseUintN(s string, bits int) uint64 { return v } -// wordArg parses a base-10 cannon memory word from a CLI argument. -func wordArg(s string) arch.Word { - return arch.Word(parseUintN(s, arch.WordSize)) -} - // parseCrossDomainArgs reads the (nonce, sender, target, value, gasLimit, data) // tuple shared by the cross-domain-message and withdrawal CLI variants from positional // args starting at args[1]. @@ -90,12 +84,6 @@ func packTupleAndPrint(args abi.Arguments, v any) { fmt.Print(hexutil.Encode(packed[32:])) } -// cannonMemoryProofOutput is the ABI shape returned by all cannonMemoryProof variants. -type cannonMemoryProofOutput struct { - MemRoot common.Hash - Proof []byte -} - func encodeCrossDomainMessage(nonce *big.Int, sender common.Address, target common.Address, value *big.Int, gasLimit *big.Int, data []byte) ([]byte, error) { _, version := crossdomain.DecodeVersionedNonce(nonce) switch version.Uint64() { diff --git a/scripts/libraries/Types.sol b/scripts/libraries/Types.sol index 72f28c3f3..2431d705c 100644 --- a/scripts/libraries/Types.sol +++ b/scripts/libraries/Types.sol @@ -10,8 +10,6 @@ import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; @@ -20,7 +18,8 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; -import { Claim, Duration, GameType, Proposal } from "src/libraries/bridge/Types.sol"; +import { Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; library Types { /// @notice Represents the roles that can be set when deploying a standard OP Stack chain. @@ -29,8 +28,6 @@ library Types { address systemConfigOwner; address batcher; address unsafeBlockSigner; - address proposer; - address challenger; } /// @notice The full set of inputs to deploy a new OP Stack chain. @@ -42,12 +39,6 @@ library Types { Proposal startingAnchorRoot; string saltMixer; uint64 gasLimit; - GameType disputeGameType; - Claim disputeAbsolutePrestate; - uint256 disputeMaxGameDepth; - uint256 disputeSplitDepth; - Duration disputeClockExtension; - Duration disputeMaxClockDuration; } /// @notice The full set of outputs from deploying a new OP Stack chain. @@ -60,15 +51,10 @@ library Types { IL1StandardBridge l1StandardBridgeProxy; IL1CrossDomainMessenger l1CrossDomainMessengerProxy; IETHLockbox ethLockboxProxy; - // Fault proof contracts below. IOptimismPortal2 optimismPortalProxy; IDisputeGameFactory disputeGameFactoryProxy; IAnchorStateRegistry anchorStateRegistryProxy; - IFaultDisputeGameV2 faultDisputeGame; - IPermissionedDisputeGameV2 permissionedDisputeGame; - IDelayedWETH delayedWETHPermissionedGameProxy; - IDelayedWETH delayedWETHPermissionlessGameProxy; - // Multiproof contracts. + IDelayedWETH delayedWETHProxy; IVerifier aggregateVerifier; ITEEProverRegistry teeProverRegistryProxy; IVerifier teeVerifier; @@ -90,22 +76,12 @@ library Types { address disputeGameFactoryImpl; address anchorStateRegistryImpl; address delayedWETHImpl; - address mipsImpl; - address faultDisputeGameV2Impl; - address permissionedDisputeGameV2Impl; address aggregateVerifierImpl; address teeProverRegistryImpl; address teeVerifierImpl; address zkVerifierImpl; } - /// @notice The input required to identify a chain for upgrading, along with new prestate hashes. - struct OpChainConfig { - ISystemConfig systemConfigProxy; - Claim cannonPrestate; - Claim cannonKonaPrestate; - } - /// @notice Maps an L2 chain ID to the standard L1 batch inbox address. /// @dev The convention is `versionByte || keccak256(bytes32(chainId))[:19]`. function chainIdToBatchInboxAddress(uint256 _l2ChainId) internal pure returns (address) { diff --git a/scripts/multiproof/SeedGames.s.sol b/scripts/multiproof/SeedGames.s.sol index 50914abe4..e04121dbd 100644 --- a/scripts/multiproof/SeedGames.s.sol +++ b/scripts/multiproof/SeedGames.s.sol @@ -7,7 +7,8 @@ import { console2 as console } from "lib/forge-std/src/console2.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; -import { Claim, GameType } from "src/libraries/bridge/Types.sol"; +import { GameType } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; diff --git a/snapshots/abi/AnchorStateRegistry.json b/snapshots/abi/AnchorStateRegistry.json index effdf0ab2..8957b66af 100644 --- a/snapshots/abi/AnchorStateRegistry.json +++ b/snapshots/abi/AnchorStateRegistry.json @@ -15,7 +15,7 @@ "name": "anchorGame", "outputs": [ { - "internalType": "contract IFaultDisputeGameV2", + "internalType": "contract IDisputeGame", "name": "", "type": "address" } @@ -495,7 +495,7 @@ "inputs": [ { "indexed": true, - "internalType": "contract IFaultDisputeGameV2", + "internalType": "contract IDisputeGame", "name": "game", "type": "address" } diff --git a/snapshots/abi/FaultDisputeGameV2.json b/snapshots/abi/FaultDisputeGameV2.json deleted file mode 100644 index c3c9a6555..000000000 --- a/snapshots/abi/FaultDisputeGameV2.json +++ /dev/null @@ -1,1185 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "maxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "splitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "clockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "maxClockDuration", - "type": "uint64" - } - ], - "internalType": "struct FaultDisputeGameV2.GameConstructorParams", - "name": "_params", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "absolutePrestate", - "outputs": [ - { - "internalType": "Claim", - "name": "absolutePrestate_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_ident", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_execLeafIdx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - } - ], - "name": "addLocalData", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "anchorStateRegistry", - "outputs": [ - { - "internalType": "contract IAnchorStateRegistry", - "name": "registry_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_parentIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - } - ], - "name": "attack", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "bondDistributionMode", - "outputs": [ - { - "internalType": "enum BondDistributionMode", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "version", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "stateRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "messagePasserStorageRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "latestBlockhash", - "type": "bytes32" - } - ], - "internalType": "struct Types.OutputRootProof", - "name": "_outputRootProof", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_headerRLP", - "type": "bytes" - } - ], - "name": "challengeRootL2Block", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "claimCredit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "claimData", - "outputs": [ - { - "internalType": "uint32", - "name": "parentIndex", - "type": "uint32" - }, - { - "internalType": "address", - "name": "counteredBy", - "type": "address" - }, - { - "internalType": "address", - "name": "claimant", - "type": "address" - }, - { - "internalType": "uint128", - "name": "bond", - "type": "uint128" - }, - { - "internalType": "Claim", - "name": "claim", - "type": "bytes32" - }, - { - "internalType": "Position", - "name": "position", - "type": "uint128" - }, - { - "internalType": "Clock", - "name": "clock", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimDataLen", - "outputs": [ - { - "internalType": "uint256", - "name": "len_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Hash", - "name": "", - "type": "bytes32" - } - ], - "name": "claims", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "clockExtension", - "outputs": [ - { - "internalType": "Duration", - "name": "clockExtension_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "closeGame", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "createdAt", - "outputs": [ - { - "internalType": "Timestamp", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "credit", - "outputs": [ - { - "internalType": "uint256", - "name": "credit_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_parentIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - } - ], - "name": "defend", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "extraData", - "outputs": [ - { - "internalType": "bytes", - "name": "extraData_", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameCreator", - "outputs": [ - { - "internalType": "address", - "name": "creator_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameData", - "outputs": [ - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "rootClaim_", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "extraData_", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameType", - "outputs": [ - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - } - ], - "name": "getChallengerDuration", - "outputs": [ - { - "internalType": "Duration", - "name": "duration_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - } - ], - "name": "getNumToResolve", - "outputs": [ - { - "internalType": "uint256", - "name": "numRemainingChildren_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Position", - "name": "_position", - "type": "uint128" - } - ], - "name": "getRequiredBond", - "outputs": [ - { - "internalType": "uint256", - "name": "requiredBond_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "hasUnlockedCredit", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "l1Head", - "outputs": [ - { - "internalType": "Hash", - "name": "l1Head_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "l2BlockNumber_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenged", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenger", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2ChainId", - "outputs": [ - { - "internalType": "uint256", - "name": "l2ChainId_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2SequenceNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "l2SequenceNumber_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "maxClockDuration", - "outputs": [ - { - "internalType": "Duration", - "name": "maxClockDuration_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxGameDepth", - "outputs": [ - { - "internalType": "uint256", - "name": "maxGameDepth_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_challengeIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "_isAttack", - "type": "bool" - } - ], - "name": "move", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "normalModeCredit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "refundModeCredit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "resolutionCheckpoints", - "outputs": [ - { - "internalType": "bool", - "name": "initialCheckpointComplete", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "subgameIndex", - "type": "uint32" - }, - { - "internalType": "Position", - "name": "leftmostPosition", - "type": "uint128" - }, - { - "internalType": "address", - "name": "counteredBy", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "resolve", - "outputs": [ - { - "internalType": "enum GameStatus", - "name": "status_", - "type": "uint8" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_numToResolve", - "type": "uint256" - } - ], - "name": "resolveClaim", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "resolvedAt", - "outputs": [ - { - "internalType": "Timestamp", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "resolvedSubgames", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "rootClaim", - "outputs": [ - { - "internalType": "Claim", - "name": "rootClaim_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "splitDepth", - "outputs": [ - { - "internalType": "uint256", - "name": "splitDepth_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingBlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "startingBlockNumber_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingOutputRoot", - "outputs": [ - { - "internalType": "Hash", - "name": "root", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "l2SequenceNumber", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingRootHash", - "outputs": [ - { - "internalType": "Hash", - "name": "startingRootHash_", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "status", - "outputs": [ - { - "internalType": "enum GameStatus", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "_isAttack", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "_stateData", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "_proof", - "type": "bytes" - } - ], - "name": "step", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "subgames", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "vm", - "outputs": [ - { - "internalType": "contract IBigStepper", - "name": "vm_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "wasRespectedGameTypeWhenCreated", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "weth", - "outputs": [ - { - "internalType": "contract IDelayedWETH", - "name": "weth_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "enum BondDistributionMode", - "name": "bondDistributionMode", - "type": "uint8" - } - ], - "name": "GameClosed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "parentIndex", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "Claim", - "name": "claim", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "claimant", - "type": "address" - } - ], - "name": "Move", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "enum GameStatus", - "name": "status", - "type": "uint8" - } - ], - "name": "Resolved", - "type": "event" - }, - { - "inputs": [], - "name": "AlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "AnchorRootNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "BadExtraData", - "type": "error" - }, - { - "inputs": [], - "name": "BlockNumberMatches", - "type": "error" - }, - { - "inputs": [], - "name": "BondTransferFailed", - "type": "error" - }, - { - "inputs": [], - "name": "CannotDefendRootClaim", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAboveSplit", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAlreadyResolved", - "type": "error" - }, - { - "inputs": [], - "name": "ClockNotExpired", - "type": "error" - }, - { - "inputs": [], - "name": "ClockTimeExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "ContentLengthMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "DuplicateStep", - "type": "error" - }, - { - "inputs": [], - "name": "EmptyItem", - "type": "error" - }, - { - "inputs": [], - "name": "GameDepthExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotFinalized", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotInProgress", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotResolved", - "type": "error" - }, - { - "inputs": [], - "name": "GamePaused", - "type": "error" - }, - { - "inputs": [], - "name": "IncorrectBondAmount", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidBondDistributionMode", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidChallengePeriod", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidClockExtension", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidDataRemainder", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidDisputedClaimIndex", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeader", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeaderRLP", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidLocalIdent", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidOutputRootProof", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidParent", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPrestate", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidSplitDepth", - "type": "error" - }, - { - "inputs": [], - "name": "L2BlockNumberChallenged", - "type": "error" - }, - { - "inputs": [], - "name": "MaxDepthTooLarge", - "type": "error" - }, - { - "inputs": [], - "name": "NoCreditToClaim", - "type": "error" - }, - { - "inputs": [], - "name": "OutOfOrderResolution", - "type": "error" - }, - { - "inputs": [], - "name": "UnexpectedList", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "rootClaim", - "type": "bytes32" - } - ], - "name": "UnexpectedRootClaim", - "type": "error" - }, - { - "inputs": [], - "name": "UnexpectedString", - "type": "error" - }, - { - "inputs": [], - "name": "ValidStep", - "type": "error" - } -] \ No newline at end of file diff --git a/snapshots/abi/MIPS64.json b/snapshots/abi/MIPS64.json deleted file mode 100644 index 6c660085d..000000000 --- a/snapshots/abi/MIPS64.json +++ /dev/null @@ -1,116 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IPreimageOracle", - "name": "_oracle", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_stateVersion", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "oracle", - "outputs": [ - { - "internalType": "contract IPreimageOracle", - "name": "oracle_", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "stateVersion", - "outputs": [ - { - "internalType": "uint256", - "name": "stateVersion_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "_stateData", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "_proof", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "_localContext", - "type": "bytes32" - } - ], - "name": "step", - "outputs": [ - { - "internalType": "bytes32", - "name": "postState_", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "InvalidExitedValue", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidMemoryProof", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPC", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidRMWInstruction", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidSecondMemoryProof", - "type": "error" - }, - { - "inputs": [], - "name": "UnsupportedStateVersion", - "type": "error" - } -] \ No newline at end of file diff --git a/snapshots/abi/PermissionedDisputeGameV2.json b/snapshots/abi/PermissionedDisputeGameV2.json deleted file mode 100644 index 09352c2b7..000000000 --- a/snapshots/abi/PermissionedDisputeGameV2.json +++ /dev/null @@ -1,1216 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "maxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "splitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "clockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "maxClockDuration", - "type": "uint64" - } - ], - "internalType": "struct FaultDisputeGameV2.GameConstructorParams", - "name": "_params", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "absolutePrestate", - "outputs": [ - { - "internalType": "Claim", - "name": "absolutePrestate_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_ident", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_execLeafIdx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - } - ], - "name": "addLocalData", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "anchorStateRegistry", - "outputs": [ - { - "internalType": "contract IAnchorStateRegistry", - "name": "registry_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_parentIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - } - ], - "name": "attack", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "bondDistributionMode", - "outputs": [ - { - "internalType": "enum BondDistributionMode", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "version", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "stateRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "messagePasserStorageRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "latestBlockhash", - "type": "bytes32" - } - ], - "internalType": "struct Types.OutputRootProof", - "name": "_outputRootProof", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_headerRLP", - "type": "bytes" - } - ], - "name": "challengeRootL2Block", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "challenger", - "outputs": [ - { - "internalType": "address", - "name": "challenger_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "claimCredit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "claimData", - "outputs": [ - { - "internalType": "uint32", - "name": "parentIndex", - "type": "uint32" - }, - { - "internalType": "address", - "name": "counteredBy", - "type": "address" - }, - { - "internalType": "address", - "name": "claimant", - "type": "address" - }, - { - "internalType": "uint128", - "name": "bond", - "type": "uint128" - }, - { - "internalType": "Claim", - "name": "claim", - "type": "bytes32" - }, - { - "internalType": "Position", - "name": "position", - "type": "uint128" - }, - { - "internalType": "Clock", - "name": "clock", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimDataLen", - "outputs": [ - { - "internalType": "uint256", - "name": "len_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Hash", - "name": "", - "type": "bytes32" - } - ], - "name": "claims", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "clockExtension", - "outputs": [ - { - "internalType": "Duration", - "name": "clockExtension_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "closeGame", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "createdAt", - "outputs": [ - { - "internalType": "Timestamp", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "credit", - "outputs": [ - { - "internalType": "uint256", - "name": "credit_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_parentIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - } - ], - "name": "defend", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "extraData", - "outputs": [ - { - "internalType": "bytes", - "name": "extraData_", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameCreator", - "outputs": [ - { - "internalType": "address", - "name": "creator_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameData", - "outputs": [ - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "rootClaim_", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "extraData_", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "gameType", - "outputs": [ - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - } - ], - "name": "getChallengerDuration", - "outputs": [ - { - "internalType": "Duration", - "name": "duration_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - } - ], - "name": "getNumToResolve", - "outputs": [ - { - "internalType": "uint256", - "name": "numRemainingChildren_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Position", - "name": "_position", - "type": "uint128" - } - ], - "name": "getRequiredBond", - "outputs": [ - { - "internalType": "uint256", - "name": "requiredBond_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "hasUnlockedCredit", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "l1Head", - "outputs": [ - { - "internalType": "Hash", - "name": "l1Head_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "l2BlockNumber_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenged", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenger", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2ChainId", - "outputs": [ - { - "internalType": "uint256", - "name": "l2ChainId_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "l2SequenceNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "l2SequenceNumber_", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "maxClockDuration", - "outputs": [ - { - "internalType": "Duration", - "name": "maxClockDuration_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxGameDepth", - "outputs": [ - { - "internalType": "uint256", - "name": "maxGameDepth_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "_disputed", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_challengeIndex", - "type": "uint256" - }, - { - "internalType": "Claim", - "name": "_claim", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "_isAttack", - "type": "bool" - } - ], - "name": "move", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "normalModeCredit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proposer", - "outputs": [ - { - "internalType": "address", - "name": "proposer_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "refundModeCredit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "resolutionCheckpoints", - "outputs": [ - { - "internalType": "bool", - "name": "initialCheckpointComplete", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "subgameIndex", - "type": "uint32" - }, - { - "internalType": "Position", - "name": "leftmostPosition", - "type": "uint128" - }, - { - "internalType": "address", - "name": "counteredBy", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "resolve", - "outputs": [ - { - "internalType": "enum GameStatus", - "name": "status_", - "type": "uint8" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_numToResolve", - "type": "uint256" - } - ], - "name": "resolveClaim", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "resolvedAt", - "outputs": [ - { - "internalType": "Timestamp", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "resolvedSubgames", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "rootClaim", - "outputs": [ - { - "internalType": "Claim", - "name": "rootClaim_", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "splitDepth", - "outputs": [ - { - "internalType": "uint256", - "name": "splitDepth_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingBlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "startingBlockNumber_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingOutputRoot", - "outputs": [ - { - "internalType": "Hash", - "name": "root", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "l2SequenceNumber", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startingRootHash", - "outputs": [ - { - "internalType": "Hash", - "name": "startingRootHash_", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "status", - "outputs": [ - { - "internalType": "enum GameStatus", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_claimIndex", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "_isAttack", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "_stateData", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "_proof", - "type": "bytes" - } - ], - "name": "step", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "subgames", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "vm", - "outputs": [ - { - "internalType": "contract IBigStepper", - "name": "vm_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "wasRespectedGameTypeWhenCreated", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "weth", - "outputs": [ - { - "internalType": "contract IDelayedWETH", - "name": "weth_", - "type": "address" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "enum BondDistributionMode", - "name": "bondDistributionMode", - "type": "uint8" - } - ], - "name": "GameClosed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "parentIndex", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "Claim", - "name": "claim", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "claimant", - "type": "address" - } - ], - "name": "Move", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "enum GameStatus", - "name": "status", - "type": "uint8" - } - ], - "name": "Resolved", - "type": "event" - }, - { - "inputs": [], - "name": "AlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "AnchorRootNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "BadAuth", - "type": "error" - }, - { - "inputs": [], - "name": "BadExtraData", - "type": "error" - }, - { - "inputs": [], - "name": "BlockNumberMatches", - "type": "error" - }, - { - "inputs": [], - "name": "BondTransferFailed", - "type": "error" - }, - { - "inputs": [], - "name": "CannotDefendRootClaim", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAboveSplit", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimAlreadyResolved", - "type": "error" - }, - { - "inputs": [], - "name": "ClockNotExpired", - "type": "error" - }, - { - "inputs": [], - "name": "ClockTimeExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "ContentLengthMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "DuplicateStep", - "type": "error" - }, - { - "inputs": [], - "name": "EmptyItem", - "type": "error" - }, - { - "inputs": [], - "name": "GameDepthExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotFinalized", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotInProgress", - "type": "error" - }, - { - "inputs": [], - "name": "GameNotResolved", - "type": "error" - }, - { - "inputs": [], - "name": "GamePaused", - "type": "error" - }, - { - "inputs": [], - "name": "IncorrectBondAmount", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidBondDistributionMode", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidChallengePeriod", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidClockExtension", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidDataRemainder", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidDisputedClaimIndex", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeader", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeaderRLP", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidLocalIdent", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidOutputRootProof", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidParent", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPrestate", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidSplitDepth", - "type": "error" - }, - { - "inputs": [], - "name": "L2BlockNumberChallenged", - "type": "error" - }, - { - "inputs": [], - "name": "MaxDepthTooLarge", - "type": "error" - }, - { - "inputs": [], - "name": "NoCreditToClaim", - "type": "error" - }, - { - "inputs": [], - "name": "OutOfOrderResolution", - "type": "error" - }, - { - "inputs": [], - "name": "UnexpectedList", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "Claim", - "name": "rootClaim", - "type": "bytes32" - } - ], - "name": "UnexpectedRootClaim", - "type": "error" - }, - { - "inputs": [], - "name": "UnexpectedString", - "type": "error" - }, - { - "inputs": [], - "name": "ValidStep", - "type": "error" - } -] \ No newline at end of file diff --git a/snapshots/abi/PreimageOracle.json b/snapshots/abi/PreimageOracle.json deleted file mode 100644 index 8094e4ab9..000000000 --- a/snapshots/abi/PreimageOracle.json +++ /dev/null @@ -1,924 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "uint256", - "name": "_minProposalSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_challengePeriod", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "KECCAK_TREE_DEPTH", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_LEAF_COUNT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_BOND_SIZE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PRECOMPILE_CALL_RESERVED_GAS", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_inputStartBlock", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_input", - "type": "bytes" - }, - { - "internalType": "bytes32[]", - "name": "_stateCommitments", - "type": "bytes32[]" - }, - { - "internalType": "bool", - "name": "_finalize", - "type": "bool" - } - ], - "name": "addLeavesLPP", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimant", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "stateCommitment", - "type": "bytes32" - } - ], - "internalType": "struct PreimageOracle.Leaf", - "name": "_postState", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "_postStateProof", - "type": "bytes32[]" - } - ], - "name": "challengeFirstLPP", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimant", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint64[25]", - "name": "state", - "type": "uint64[25]" - } - ], - "internalType": "struct LibKeccak.StateMatrix", - "name": "_stateMatrix", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "stateCommitment", - "type": "bytes32" - } - ], - "internalType": "struct PreimageOracle.Leaf", - "name": "_preState", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "_preStateProof", - "type": "bytes32[]" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "stateCommitment", - "type": "bytes32" - } - ], - "internalType": "struct PreimageOracle.Leaf", - "name": "_postState", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "_postStateProof", - "type": "bytes32[]" - } - ], - "name": "challengeLPP", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "challengePeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "challengePeriod_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - } - ], - "name": "getTreeRootLPP", - "outputs": [ - { - "internalType": "bytes32", - "name": "treeRoot_", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_partOffset", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "_claimedSize", - "type": "uint32" - } - ], - "name": "initLPP", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_z", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_y", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_commitment", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "_proof", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - } - ], - "name": "loadBlobPreimagePart", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_preimage", - "type": "bytes" - } - ], - "name": "loadKeccak256PreimagePart", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_ident", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_localContext", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "_word", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_size", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - } - ], - "name": "loadLocalData", - "outputs": [ - { - "internalType": "bytes32", - "name": "key_", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_precompile", - "type": "address" - }, - { - "internalType": "uint64", - "name": "_requiredGas", - "type": "uint64" - }, - { - "internalType": "bytes", - "name": "_input", - "type": "bytes" - } - ], - "name": "loadPrecompilePreimagePart", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_partOffset", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_preimage", - "type": "bytes" - } - ], - "name": "loadSha256PreimagePart", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "minProposalSize", - "outputs": [ - { - "internalType": "uint256", - "name": "minProposalSize_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "preimageLengths", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "preimagePartOk", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "preimageParts", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposalBlocks", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimant", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - } - ], - "name": "proposalBlocksLen", - "outputs": [ - { - "internalType": "uint256", - "name": "len_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposalBonds", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposalBranches", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proposalCount", - "outputs": [ - { - "internalType": "uint256", - "name": "count_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposalMetadata", - "outputs": [ - { - "internalType": "LPPMetaData", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposalParts", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "proposals", - "outputs": [ - { - "internalType": "address", - "name": "claimant", - "type": "address" - }, - { - "internalType": "uint256", - "name": "uuid", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_key", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_offset", - "type": "uint256" - } - ], - "name": "readPreimage", - "outputs": [ - { - "internalType": "bytes32", - "name": "dat_", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "datLen_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimant", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_uuid", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint64[25]", - "name": "state", - "type": "uint64[25]" - } - ], - "internalType": "struct LibKeccak.StateMatrix", - "name": "_stateMatrix", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "stateCommitment", - "type": "bytes32" - } - ], - "internalType": "struct PreimageOracle.Leaf", - "name": "_preState", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "_preStateProof", - "type": "bytes32[]" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "stateCommitment", - "type": "bytes32" - } - ], - "internalType": "struct PreimageOracle.Leaf", - "name": "_postState", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "_postStateProof", - "type": "bytes32[]" - } - ], - "name": "squeezeLPP", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "zeroHashes", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ActiveProposal", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadyFinalized", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "BadProposal", - "type": "error" - }, - { - "inputs": [], - "name": "BondTransferFailed", - "type": "error" - }, - { - "inputs": [], - "name": "InsufficientBond", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidInputSize", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPreimage", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidProof", - "type": "error" - }, - { - "inputs": [], - "name": "NotEOA", - "type": "error" - }, - { - "inputs": [], - "name": "NotInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "PartOffsetOOB", - "type": "error" - }, - { - "inputs": [], - "name": "PostStateMatches", - "type": "error" - }, - { - "inputs": [], - "name": "StatesNotContiguous", - "type": "error" - }, - { - "inputs": [], - "name": "TreeSizeOverflow", - "type": "error" - }, - { - "inputs": [], - "name": "WrongStartingBlock", - "type": "error" - } -] \ No newline at end of file diff --git a/snapshots/abi_loader.go b/snapshots/abi_loader.go index a2bb9b330..aaccab83b 100644 --- a/snapshots/abi_loader.go +++ b/snapshots/abi_loader.go @@ -10,12 +10,6 @@ import ( //go:embed abi/DisputeGameFactory.json var disputeGameFactory []byte -//go:embed abi/PreimageOracle.json -var preimageOracle []byte - -//go:embed abi/MIPS64.json -var mips []byte - //go:embed abi/DelayedWETH.json var delayedWETH []byte @@ -25,12 +19,6 @@ var systemConfig []byte func LoadDisputeGameFactoryABI() *abi.ABI { return loadABI(disputeGameFactory) } -func LoadPreimageOracleABI() *abi.ABI { - return loadABI(preimageOracle) -} -func LoadMIPSABI() *abi.ABI { - return loadABI(mips) -} func LoadDelayedWETHABI() *abi.ABI { return loadABI(delayedWETH) } diff --git a/snapshots/abi_loader_test.go b/snapshots/abi_loader_test.go index 84da629ff..bf7adc5ff 100644 --- a/snapshots/abi_loader_test.go +++ b/snapshots/abi_loader_test.go @@ -13,8 +13,6 @@ func TestLoadABIs(t *testing.T) { method func() *abi.ABI }{ {"DisputeGameFactory", LoadDisputeGameFactoryABI}, - {"PreimageOracle", LoadPreimageOracleABI}, - {"MIPS", LoadMIPSABI}, {"DelayedWETH", LoadDelayedWETHABI}, } for _, test := range tests { diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 0b6c79c8b..333076881 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -17,7 +17,7 @@ }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { "initCodeHash": "0x2c01bc6c0a55a1a27263224e05c1b28703ff85c61075bae7ab384b3043820ed2", - "sourceCodeHash": "0x5dfc09c290c24b144f5af3b2d06478214da5406e8bf7397c69215b45934267ea" + "sourceCodeHash": "0xa1281f5da03fa0431eabad33da23f51a8d835b0dd04554603fd59e11efe9fa51" }, "src/L1/SuperchainConfig.sol:SuperchainConfig": { "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", @@ -29,11 +29,11 @@ }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { "initCodeHash": "0x10bb6a60f21de103d1da9ff310a38f571c53a8113d04a029087648debf3f0341", - "sourceCodeHash": "0xc8239431f25b0fa286b8db09eefed42ab3a46f6f03fa3c89b48cfe75fa3439ab" + "sourceCodeHash": "0x7024dabd2286101cc07920561f0fa5ce511739345533f939e3d5489c845e0bd9" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", - "sourceCodeHash": "0xc0027a6919b4682bc6ea3750974999840e8d879b20f168b6f600f1a01544256a" + "sourceCodeHash": "0x1094b0fcee1b6f22dfc3f2c0d22826a4273282a9b67dc604c1be518157e59e8a" }, "src/L1/proofs/DelayedWETH.sol:DelayedWETH": { "initCodeHash": "0x592a6e22e1d02797eeb6fa622806e58f867bb6066944852e120b5871d3110825", @@ -41,7 +41,7 @@ }, "src/L1/proofs/DisputeGameFactory.sol:DisputeGameFactory": { "initCodeHash": "0x727de8dbfd3432d95e19c39ece5ac7aec183dd67a09db769791d0e835e365d88", - "sourceCodeHash": "0x52926494b37fbf68ce1110d8097a399a23d15eb6e1a878eb56b0bed62c5cb926" + "sourceCodeHash": "0xf6ca679ff7463ade68e1ad1ae49f5b406e0c723ec1a2a718828f6bec12c8e8a6" }, "src/L1/proofs/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": { "initCodeHash": "0x82f42b4d578bfcf9dc35eaa2c4ada04a45e1eca63021bceceb2ec794b12a9dd6", @@ -49,20 +49,12 @@ }, "src/L1/proofs/tee/TEEProverRegistry.sol:TEEProverRegistry": { "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0", - "sourceCodeHash": "0x410542b3899800d884a2281ef234a28d2dbb707576f2ad7d21f2d36de6290ca1" + "sourceCodeHash": "0x131566fd89b381672f1c39928e5155b0b8acf1a9259be91fbd0511626470de38" }, "src/L1/proofs/tee/TEEVerifier.sol:TEEVerifier": { "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af", "sourceCodeHash": "0x6c1d839e48cc8497af7b0d74a9566e76000599b14d7db7b3163a69e774a91bb3" }, - "src/L1/proofs/v2/FaultDisputeGameV2.sol:FaultDisputeGameV2": { - "initCodeHash": "0x6fc59e2da083c9e2093e42b0fda705e8215cc216e4dcedbf728c08f69ec2d3bd", - "sourceCodeHash": "0x71fab473524447aee26c5b9111732e99ce1f9949d9db755fdaca32b4310808f3" - }, - "src/L1/proofs/v2/PermissionedDisputeGameV2.sol:PermissionedDisputeGameV2": { - "initCodeHash": "0x9896fd04e9a3f9fe4f1d6e93eb298b37a6bfa33424aa705e68cc58d0ba7f3f90", - "sourceCodeHash": "0xbfc32e0a21423ed579d7b1b46836a7c85f58bc05656c11d67111ae9072e8d3db" - }, "src/L1/proofs/zk/ZKVerifier.sol:ZKVerifier": { "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245", "sourceCodeHash": "0xf90e23a22c2e31d6bb11f9a8a9f7cb1c4eb0f20600d2d9aebf87023d1779972c" @@ -123,14 +115,6 @@ "initCodeHash": "0xbc2cd025153720943e51b79822c2dc374d270a78b92cf47d49548c468e218e46", "sourceCodeHash": "0x734a6b2aa6406bc145d848ad6071d3af1d40852aeb8f4b2f6f51beaad476e2d3" }, - "src/cannon/MIPS64.sol:MIPS64": { - "initCodeHash": "0x6a649986370d18e5fddcd89df73e520063fb373f7dba2f731a2b7e79a1c132a5", - "sourceCodeHash": "0x0d895592a6b55990ef70b8e9edde4d6970d161ebeb3776e370e9ccaa2c59a965" - }, - "src/cannon/PreimageOracle.sol:PreimageOracle": { - "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", - "sourceCodeHash": "0x39ce48ebad31c0eeebb231fc9ca03a6c43663349285b178a62daea6b84b3fd30" - }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658", "sourceCodeHash": "0xcc1113b0d405493e9d260cb716b6194e0e513d3667ae7d5a052e2cfa54a70990" diff --git a/snapshots/storageLayout/AnchorStateRegistry.json b/snapshots/storageLayout/AnchorStateRegistry.json index 9844ee23e..2d538dd3e 100644 --- a/snapshots/storageLayout/AnchorStateRegistry.json +++ b/snapshots/storageLayout/AnchorStateRegistry.json @@ -32,7 +32,7 @@ "label": "anchorGame", "offset": 0, "slot": "2", - "type": "contract IFaultDisputeGameV2" + "type": "contract IDisputeGame" }, { "bytes": "64", diff --git a/snapshots/storageLayout/FaultDisputeGameV2.json b/snapshots/storageLayout/FaultDisputeGameV2.json deleted file mode 100644 index efae9aab9..000000000 --- a/snapshots/storageLayout/FaultDisputeGameV2.json +++ /dev/null @@ -1,121 +0,0 @@ -[ - { - "bytes": "8", - "label": "createdAt", - "offset": 0, - "slot": "0", - "type": "Timestamp" - }, - { - "bytes": "8", - "label": "resolvedAt", - "offset": 8, - "slot": "0", - "type": "Timestamp" - }, - { - "bytes": "1", - "label": "status", - "offset": 16, - "slot": "0", - "type": "enum GameStatus" - }, - { - "bytes": "1", - "label": "initialized", - "offset": 17, - "slot": "0", - "type": "bool" - }, - { - "bytes": "1", - "label": "l2BlockNumberChallenged", - "offset": 18, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "l2BlockNumberChallenger", - "offset": 0, - "slot": "1", - "type": "address" - }, - { - "bytes": "32", - "label": "claimData", - "offset": 0, - "slot": "2", - "type": "struct FaultDisputeGameV2.ClaimData[]" - }, - { - "bytes": "32", - "label": "normalModeCredit", - "offset": 0, - "slot": "3", - "type": "mapping(address => uint256)" - }, - { - "bytes": "32", - "label": "claims", - "offset": 0, - "slot": "4", - "type": "mapping(Hash => bool)" - }, - { - "bytes": "32", - "label": "subgames", - "offset": 0, - "slot": "5", - "type": "mapping(uint256 => uint256[])" - }, - { - "bytes": "32", - "label": "resolvedSubgames", - "offset": 0, - "slot": "6", - "type": "mapping(uint256 => bool)" - }, - { - "bytes": "32", - "label": "resolutionCheckpoints", - "offset": 0, - "slot": "7", - "type": "mapping(uint256 => struct FaultDisputeGameV2.ResolutionCheckpoint)" - }, - { - "bytes": "64", - "label": "startingOutputRoot", - "offset": 0, - "slot": "8", - "type": "struct Proposal" - }, - { - "bytes": "1", - "label": "wasRespectedGameTypeWhenCreated", - "offset": 0, - "slot": "10", - "type": "bool" - }, - { - "bytes": "32", - "label": "refundModeCredit", - "offset": 0, - "slot": "11", - "type": "mapping(address => uint256)" - }, - { - "bytes": "32", - "label": "hasUnlockedCredit", - "offset": 0, - "slot": "12", - "type": "mapping(address => bool)" - }, - { - "bytes": "1", - "label": "bondDistributionMode", - "offset": 0, - "slot": "13", - "type": "enum BondDistributionMode" - } -] \ No newline at end of file diff --git a/snapshots/storageLayout/MIPS64.json b/snapshots/storageLayout/MIPS64.json deleted file mode 100644 index 0637a088a..000000000 --- a/snapshots/storageLayout/MIPS64.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/snapshots/storageLayout/PermissionedDisputeGameV2.json b/snapshots/storageLayout/PermissionedDisputeGameV2.json deleted file mode 100644 index efae9aab9..000000000 --- a/snapshots/storageLayout/PermissionedDisputeGameV2.json +++ /dev/null @@ -1,121 +0,0 @@ -[ - { - "bytes": "8", - "label": "createdAt", - "offset": 0, - "slot": "0", - "type": "Timestamp" - }, - { - "bytes": "8", - "label": "resolvedAt", - "offset": 8, - "slot": "0", - "type": "Timestamp" - }, - { - "bytes": "1", - "label": "status", - "offset": 16, - "slot": "0", - "type": "enum GameStatus" - }, - { - "bytes": "1", - "label": "initialized", - "offset": 17, - "slot": "0", - "type": "bool" - }, - { - "bytes": "1", - "label": "l2BlockNumberChallenged", - "offset": 18, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "l2BlockNumberChallenger", - "offset": 0, - "slot": "1", - "type": "address" - }, - { - "bytes": "32", - "label": "claimData", - "offset": 0, - "slot": "2", - "type": "struct FaultDisputeGameV2.ClaimData[]" - }, - { - "bytes": "32", - "label": "normalModeCredit", - "offset": 0, - "slot": "3", - "type": "mapping(address => uint256)" - }, - { - "bytes": "32", - "label": "claims", - "offset": 0, - "slot": "4", - "type": "mapping(Hash => bool)" - }, - { - "bytes": "32", - "label": "subgames", - "offset": 0, - "slot": "5", - "type": "mapping(uint256 => uint256[])" - }, - { - "bytes": "32", - "label": "resolvedSubgames", - "offset": 0, - "slot": "6", - "type": "mapping(uint256 => bool)" - }, - { - "bytes": "32", - "label": "resolutionCheckpoints", - "offset": 0, - "slot": "7", - "type": "mapping(uint256 => struct FaultDisputeGameV2.ResolutionCheckpoint)" - }, - { - "bytes": "64", - "label": "startingOutputRoot", - "offset": 0, - "slot": "8", - "type": "struct Proposal" - }, - { - "bytes": "1", - "label": "wasRespectedGameTypeWhenCreated", - "offset": 0, - "slot": "10", - "type": "bool" - }, - { - "bytes": "32", - "label": "refundModeCredit", - "offset": 0, - "slot": "11", - "type": "mapping(address => uint256)" - }, - { - "bytes": "32", - "label": "hasUnlockedCredit", - "offset": 0, - "slot": "12", - "type": "mapping(address => bool)" - }, - { - "bytes": "1", - "label": "bondDistributionMode", - "offset": 0, - "slot": "13", - "type": "enum BondDistributionMode" - } -] \ No newline at end of file diff --git a/snapshots/storageLayout/PreimageOracle.json b/snapshots/storageLayout/PreimageOracle.json deleted file mode 100644 index fbbea6e3d..000000000 --- a/snapshots/storageLayout/PreimageOracle.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "bytes": "32", - "label": "preimageLengths", - "offset": 0, - "slot": "0", - "type": "mapping(bytes32 => uint256)" - }, - { - "bytes": "32", - "label": "preimageParts", - "offset": 0, - "slot": "1", - "type": "mapping(bytes32 => mapping(uint256 => bytes32))" - }, - { - "bytes": "32", - "label": "preimagePartOk", - "offset": 0, - "slot": "2", - "type": "mapping(bytes32 => mapping(uint256 => bool))" - }, - { - "bytes": "512", - "label": "zeroHashes", - "offset": 0, - "slot": "3", - "type": "bytes32[16]" - }, - { - "bytes": "32", - "label": "proposals", - "offset": 0, - "slot": "19", - "type": "struct PreimageOracle.LargePreimageProposalKeys[]" - }, - { - "bytes": "32", - "label": "proposalBranches", - "offset": 0, - "slot": "20", - "type": "mapping(address => mapping(uint256 => bytes32[16]))" - }, - { - "bytes": "32", - "label": "proposalMetadata", - "offset": 0, - "slot": "21", - "type": "mapping(address => mapping(uint256 => LPPMetaData))" - }, - { - "bytes": "32", - "label": "proposalBonds", - "offset": 0, - "slot": "22", - "type": "mapping(address => mapping(uint256 => uint256))" - }, - { - "bytes": "32", - "label": "proposalParts", - "offset": 0, - "slot": "23", - "type": "mapping(address => mapping(uint256 => bytes32))" - }, - { - "bytes": "32", - "label": "proposalBlocks", - "offset": 0, - "slot": "24", - "type": "mapping(address => mapping(uint256 => uint64[]))" - } -] \ No newline at end of file diff --git a/src/L1/OptimismPortal2.sol b/src/L1/OptimismPortal2.sol index 3173a8922..e0bc4bf39 100644 --- a/src/L1/OptimismPortal2.sol +++ b/src/L1/OptimismPortal2.sol @@ -106,11 +106,11 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase /// @notice Mapping of withdrawal hashes to addresses that have submitted a proof for the /// withdrawal. Original OptimismPortal contract only allowed one proof to be submitted - /// for any given withdrawal hash. Fault Proofs version of this contract must allow - /// multiple proofs for the same withdrawal hash to prevent a malicious user from - /// blocking other withdrawals by proving them against invalid proposals. Submitters - /// are tracked in an array to simplify the off-chain process of determining which - /// proof submission should be used when finalizing a withdrawal. + /// for any given withdrawal hash. This contract allows multiple proofs for the same + /// withdrawal hash to prevent a malicious user from blocking other withdrawals by + /// proving them against invalid proposals. Submitters are tracked in an array to + /// simplify the off-chain process of determining which proof submission should be used + /// when finalizing a withdrawal. mapping(bytes32 => address[]) public proofSubmitters; /// @custom:legacy diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index 65a113659..85930a858 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -16,7 +16,8 @@ import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { Claim, GameStatus, GameType, Hash, Proposal, Timestamp } from "src/libraries/bridge/Types.sol"; +import { GameStatus, GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Timestamp, Claim } from "src/libraries/bridge/LibUDT.sol"; // Solady import { Clone } from "lib/solady/src/utils/Clone.sol"; diff --git a/src/L1/proofs/AnchorStateRegistry.sol b/src/L1/proofs/AnchorStateRegistry.sol index 805bc3bf4..d42728ec6 100644 --- a/src/L1/proofs/AnchorStateRegistry.sol +++ b/src/L1/proofs/AnchorStateRegistry.sol @@ -7,11 +7,11 @@ import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; // Libraries -import { GameType, Proposal, Claim, GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { GameType, Proposal, GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -20,9 +20,9 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title AnchorStateRegistry /// @notice The AnchorStateRegistry is a contract that stores the latest "anchor" state for each available -/// FaultDisputeGame type. The anchor state is the latest state that has been proposed on L1 and was not -/// challenged within the challenge period. By using stored anchor states, new FaultDisputeGame instances can -/// be initialized with a more recent starting state which reduces the amount of required offchain computation. +/// dispute game type. The anchor state is the latest state that has been proposed on L1 and finalized. +/// By using stored anchor states, new games can be initialized with a more recent starting state which +/// reduces the amount of required offchain computation. contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, ReinitializableBase, ISemver { /// @notice Semantic version. /// @custom:semver 3.7.0 @@ -38,7 +38,7 @@ contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, Reinitializa IDisputeGameFactory public disputeGameFactory; /// @notice The game whose claim is currently being used as the anchor state. - IFaultDisputeGameV2 public anchorGame; + IDisputeGame public anchorGame; /// @notice The starting anchor root. Proposal internal startingAnchorRoot; @@ -56,7 +56,7 @@ contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, Reinitializa /// @notice Emitted when an anchor state is updated. /// @param game Game that was used as the new anchor game. - event AnchorUpdated(IFaultDisputeGameV2 indexed game); + event AnchorUpdated(IDisputeGame indexed game); /// @notice Emitted when the respected game type is set. /// @param gameType The new respected game type. @@ -209,7 +209,7 @@ contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, Reinitializa // Grab the AnchorStateRegistry from the game. Awkward type conversion here but // IDisputeGame probably needs to have this function eventually anyway. - address asr = address(IFaultDisputeGameV2(address(_game)).anchorStateRegistry()); + address asr = address(IDisputeGame(address(_game)).anchorStateRegistry()); // Return whether the game is factory registered and uses this AnchorStateRegistry. We // check for both of these conditions because the game could be using a different @@ -338,12 +338,7 @@ contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, Reinitializa /// @notice Updates the anchor game. /// @param _game New candidate anchor game. function setAnchorState(IDisputeGame _game) public { - // Convert game to FaultDisputeGame. - // We can't use FaultDisputeGame in the interface because this function is called from the - // FaultDisputeGame contract which can't import IFaultDisputeGameV2 by convention. We should - // likely introduce a new interface (e.g., StateDisputeGame) that can act as a more useful - // version of IDisputeGame in the future. - IFaultDisputeGameV2 game = IFaultDisputeGameV2(address(_game)); + IDisputeGame game = IDisputeGame(address(_game)); // Check if the candidate game claim is valid. if (!isGameClaimValid(game)) { diff --git a/src/L1/proofs/DisputeGameFactory.sol b/src/L1/proofs/DisputeGameFactory.sol index be6695719..27dc1420b 100644 --- a/src/L1/proofs/DisputeGameFactory.sol +++ b/src/L1/proofs/DisputeGameFactory.sol @@ -8,7 +8,8 @@ import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; // Libraries import { LibClone } from "lib/solady/src/utils/LibClone.sol"; -import { GameType, Claim, GameId, Timestamp, Hash, LibGameId } from "src/libraries/bridge/Types.sol"; +import { GameType, Hash } from "src/libraries/bridge/Types.sol"; +import { LibGameId, Timestamp, GameId, Claim } from "src/libraries/bridge/LibUDT.sol"; import { NoImplementation, IncorrectBondAmount, GameAlreadyExists } from "src/libraries/bridge/Errors.sol"; // Interfaces diff --git a/src/L1/proofs/tee/TEEProverRegistry.sol b/src/L1/proofs/tee/TEEProverRegistry.sol index 2292154e0..37abdcc18 100644 --- a/src/L1/proofs/tee/TEEProverRegistry.sol +++ b/src/L1/proofs/tee/TEEProverRegistry.sol @@ -40,7 +40,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; /// @notice The game type used to look up the AggregateVerifier in the factory. - /// @dev Owner-settable to support game type migrations (e.g., moving to a permissioned type). + /// @dev Owner-settable to support game type migrations. GameType public gameType; /// @notice Mapping of whether a signer address is registered. diff --git a/src/L1/proofs/v2/FaultDisputeGameV2.sol b/src/L1/proofs/v2/FaultDisputeGameV2.sol deleted file mode 100644 index 413451276..000000000 --- a/src/L1/proofs/v2/FaultDisputeGameV2.sol +++ /dev/null @@ -1,1304 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Libraries -import { Math } from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; -import { FixedPointMathLib } from "lib/solady/src/utils/FixedPointMathLib.sol"; -import { Clone } from "lib/solady/src/utils/Clone.sol"; -import { Types } from "src/libraries/Types.sol"; -import { Hashing } from "src/libraries/Hashing.sol"; -import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; -import { - GameStatus, - GameType, - BondDistributionMode, - Claim, - Clock, - Duration, - Timestamp, - Hash, - Proposal, - LibClock, - LocalPreimageKey, - VMStatuses -} from "src/libraries/bridge/Types.sol"; -import { Position, LibPosition } from "src/libraries/bridge/LibPosition.sol"; -import { - InvalidParent, - ClaimAlreadyExists, - ClaimAlreadyResolved, - OutOfOrderResolution, - InvalidChallengePeriod, - InvalidSplitDepth, - InvalidClockExtension, - MaxDepthTooLarge, - AnchorRootNotFound, - AlreadyInitialized, - UnexpectedRootClaim, - GameNotInProgress, - InvalidPrestate, - ValidStep, - GameDepthExceeded, - L2BlockNumberChallenged, - InvalidDisputedClaimIndex, - ClockTimeExceeded, - DuplicateStep, - CannotDefendRootClaim, - IncorrectBondAmount, - InvalidLocalIdent, - BlockNumberMatches, - InvalidHeaderRLP, - ClockNotExpired, - BondTransferFailed, - NoCreditToClaim, - InvalidOutputRootProof, - ClaimAboveSplit, - GameNotFinalized, - InvalidBondDistributionMode, - GameNotResolved, - GamePaused, - BadExtraData -} from "src/libraries/bridge/Errors.sol"; - -// Interfaces -import { ISemver } from "interfaces/universal/ISemver.sol"; -import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; -import { IBigStepper, IPreimageOracle } from "interfaces/L1/proofs/IBigStepper.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; - -/// @title FaultDisputeGameV2 -/// @notice An implementation of the `IFaultDisputeGameV2` interface. -contract FaultDisputeGameV2 is Clone, ISemver { - //////////////////////////////////////////////////////////////// - // Structs // - //////////////////////////////////////////////////////////////// - - /// @notice The `ClaimData` struct represents the data associated with a Claim. - struct ClaimData { - uint32 parentIndex; - address counteredBy; - address claimant; - uint128 bond; - Claim claim; - Position position; - Clock clock; - } - - /// @notice The `ResolutionCheckpoint` struct represents the data associated with an in-progress claim resolution. - struct ResolutionCheckpoint { - bool initialCheckpointComplete; - uint32 subgameIndex; - Position leftmostPosition; - address counteredBy; - } - - /// @notice Parameters for creating a new FaultDisputeGame. We place this into a struct to - /// avoid stack-too-deep errors when compiling without the optimizer enabled. - struct GameConstructorParams { - uint256 maxGameDepth; - uint256 splitDepth; - Duration clockExtension; - Duration maxClockDuration; - } - - //////////////////////////////////////////////////////////////// - // Events // - //////////////////////////////////////////////////////////////// - - /// @notice Emitted when the game is resolved. - /// @param status The status of the game after resolution. - event Resolved(GameStatus indexed status); - - /// @notice Emitted when a new claim is added to the DAG by `claimant` - /// @param parentIndex The index within the `claimData` array of the parent claim - /// @param claim The claim being added - /// @param claimant The address of the claimant - event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant); - - /// @notice Emitted when the game is closed. - event GameClosed(BondDistributionMode bondDistributionMode); - - //////////////////////////////////////////////////////////////// - // State Vars // - //////////////////////////////////////////////////////////////// - - /// @notice The max depth of the game. - uint256 internal immutable MAX_GAME_DEPTH; - - /// @notice The max depth of the output bisection portion of the position tree. Immediately beneath - /// this depth, execution trace bisection begins. - uint256 internal immutable SPLIT_DEPTH; - - /// @notice The maximum duration that may accumulate on a team's chess clock before they may no longer respond. - Duration internal immutable MAX_CLOCK_DURATION; - - /// @notice The duration of the clock extension. Will be doubled if the grandchild is the root claim of an execution - /// trace bisection subgame. - Duration internal immutable CLOCK_EXTENSION; - - /// @notice The global root claim's position is always at gindex 1. - Position internal constant ROOT_POSITION = Position.wrap(1); - - /// @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 Semantic version. - /// @custom:semver 2.2.0 - function version() public pure virtual returns (string memory) { - return "2.2.0"; - } - - /// @notice The starting timestamp of the game - Timestamp public createdAt; - - /// @notice The timestamp of the game's global resolution. - Timestamp public resolvedAt; - - /// @notice Returns the current status of the game. - GameStatus public status; - - /// @notice Flag for the `initialize` function to prevent re-initialization. - bool internal initialized; - - /// @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; - - /// @notice An append-only array of all claims made during the dispute game. - ClaimData[] public claimData; - - /// @notice Credited balances for winning participants. - mapping(address => uint256) public normalModeCredit; - - /// @notice A mapping to allow for constant-time lookups of existing claims. - mapping(Hash => bool) public claims; - - /// @notice A mapping of subgames rooted at a claim index to other claim indices in the subgame. - mapping(uint256 => uint256[]) public subgames; - - /// @notice A mapping of resolved subgames rooted at a claim index. - mapping(uint256 => bool) public resolvedSubgames; - - /// @notice A mapping of claim indices to resolution checkpoints. - mapping(uint256 => ResolutionCheckpoint) public resolutionCheckpoints; - - /// @notice The latest finalized output root, serving as the anchor for output bisection. - Proposal public startingOutputRoot; - - /// @notice A boolean for whether or not the game type was respected when the game was created. - bool public wasRespectedGameTypeWhenCreated; - - /// @notice A mapping of each claimant's refund mode credit. - mapping(address => uint256) public refundModeCredit; - - /// @notice A mapping of whether a claimant has unlocked their credit. - mapping(address => bool) public hasUnlockedCredit; - - /// @notice The bond distribution mode of the game. - BondDistributionMode public bondDistributionMode; - - /// @param _params Parameters for creating a new FaultDisputeGame. - constructor(GameConstructorParams memory _params) { - // The max game depth may not be greater than `LibPosition.MAX_POSITION_BITLEN - 1`. - if (_params.maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge(); - - // The split depth plus one cannot be greater than or equal to the max game depth. We add - // an additional depth to the split depth to avoid a bug in trace ancestor lookup. We know - // that the case where the split depth is the max value for uint256 is equivalent to the - // second check though we do need to check it explicitly to avoid an overflow. - if (_params.splitDepth == type(uint256).max || _params.splitDepth + 1 >= _params.maxGameDepth) { - revert InvalidSplitDepth(); - } - - // The split depth cannot be 0 or 1 to stay in bounds of clock extension arithmetic. - if (_params.splitDepth < 2) revert InvalidSplitDepth(); - - // Validate clock extension bounds that don't require VM access. - // The split depth extension is always clockExtension * 2. - uint256 splitDepthExtension = uint256(_params.clockExtension.raw()) * 2; - - // The split depth extension must fit into a uint64. - if (splitDepthExtension > type(uint64).max) revert InvalidClockExtension(); - - // The split depth extension may not be greater than the maximum clock duration. - if (uint64(splitDepthExtension) > _params.maxClockDuration.raw()) revert InvalidClockExtension(); - - // Set up initial game state. - MAX_GAME_DEPTH = _params.maxGameDepth; - SPLIT_DEPTH = _params.splitDepth; - CLOCK_EXTENSION = _params.clockExtension; - MAX_CLOCK_DURATION = _params.maxClockDuration; - } - - /// @notice Initializes the contract. - /// @dev This function may only be called once. - function initialize() public payable virtual { - // SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and - // prevent the game from being created. - // - // Implicit assumptions: - // - The `gameStatus` state variable defaults to 0, which is `GameStatus.IN_PROGRESS` - // - The dispute game factory will enforce the required bond to initialize the game. - // - // Explicit checks: - // - The game must not have already been initialized. - // - An output root cannot be proposed at or before the starting block number. - - // INVARIANT: The game must not have already been initialized. - if (initialized) revert AlreadyInitialized(); - - // Revert if the calldata size is not the expected length. - // - // This is to prevent adding extra or omitting bytes from to `extraData` that result in a different game UUID - // in the factory, but are not used by the game, which would allow for multiple dispute games for the same - // output proposal to be created. - if (msg.data.length != expectedInitCallDataLength()) revert BadExtraData(); - - // Grab the latest anchor root. - (Hash root, uint256 rootBlockNumber) = anchorStateRegistry().getAnchorRoot(); - - // Should only happen if this is a new game type that hasn't been set up yet. - if (root.raw() == bytes32(0)) revert AnchorRootNotFound(); - - // 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()); - - // Validate parameters that require access to the VM. - // The PreimageOracle challenge period must fit into uint64 so we can safely use it here. - if (vm().oracle().challengePeriod() > type(uint64).max) revert InvalidChallengePeriod(); - - // Determine the maximum clock extension which is either the split depth extension or the - // maximum game depth extension depending on the configuration of these contracts. - uint256 splitDepthExtension = uint256(CLOCK_EXTENSION.raw()) * 2; - uint256 maxGameDepthExtension = uint256(CLOCK_EXTENSION.raw()) + uint64(vm().oracle().challengePeriod()); - uint256 maxClockExtension = Math.max(splitDepthExtension, maxGameDepthExtension); - - // The maximum clock extension must fit into a uint64. - if (maxClockExtension > type(uint64).max) revert InvalidClockExtension(); - - // The maximum clock extension may not be greater than the maximum clock duration. - if (uint64(maxClockExtension) > MAX_CLOCK_DURATION.raw()) revert InvalidClockExtension(); - - // Set the root claim - claimData.push( - ClaimData({ - parentIndex: type(uint32).max, - counteredBy: address(0), - claimant: gameCreator(), - bond: uint128(msg.value), - claim: rootClaim(), - position: ROOT_POSITION, - clock: LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))) - }) - ); - - // Set the game as initialized. - initialized = true; - - // Deposit the bond. - refundModeCredit[gameCreator()] += msg.value; - weth().deposit{ value: msg.value }(); - - // Set the game's starting timestamp - createdAt = Timestamp.wrap(uint64(block.timestamp)); - - // Set whether the game type was respected when the game was created. - wasRespectedGameTypeWhenCreated = - GameType.unwrap(anchorStateRegistry().respectedGameType()) == GameType.unwrap(gameType()); - } - - /// @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 Returns the byte count of the immutable args for this contract. - function immutableArgsByteCount() internal pure virtual returns (uint256) { - // Expected length: 240 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; - } - - //////////////////////////////////////////////////////////////// - // `IFaultDisputeGameV2` impl // - //////////////////////////////////////////////////////////////// - - /// @notice Perform an instruction step via an on-chain fault proof processor. - /// @dev This function should point to a fault proof processor in order to execute - /// a step in the fault proof program on-chain. The interface of the fault proof - /// processor contract should adhere to the `IBigStepper` interface. - /// @param _claimIndex The index of the challenged claim within `claimData`. - /// @param _isAttack Whether or not the step is an attack or a defense. - /// @param _stateData The stateData of the step is the preimage of the claim at the given - /// prestate, which is at `_stateIndex` if the move is an attack and `_claimIndex` if - /// the move is a defense. If the step is an attack on the first instruction, it is - /// the absolute prestate of the fault proof VM. - /// @param _proof Proof to access memory nodes in the VM's merkle state tree. - function step( - uint256 _claimIndex, - bool _isAttack, - bytes calldata _stateData, - bytes calldata _proof - ) - public - virtual - { - // INVARIANT: Steps cannot be made unless the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - // Get the parent. If it does not exist, the call will revert with OOB. - ClaimData storage parent = claimData[_claimIndex]; - - // Pull the parent position out of storage. - Position parentPos = parent.position; - // Determine the position of the step. - Position stepPos = parentPos.move(_isAttack); - - // INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH` - if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent(); - - // Determine the expected pre & post states of the step. - Claim preStateClaim; - ClaimData storage postState; - if (_isAttack) { - // If the step position's index at depth is 0, the prestate is the absolute - // prestate. - // If the step is an attack at a trace index > 0, the prestate exists elsewhere in - // the game state. - // NOTE: We localize the `indexAtDepth` for the current execution trace subgame by finding - // the remainder of the index at depth divided by 2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH), - // which is the number of leaves in each execution trace subgame. This is so that we can - // determine whether or not the step position is represents the `ABSOLUTE_PRESTATE`. - preStateClaim = (stepPos.indexAtDepth() % (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0 - ? absolutePrestate() - : _findTraceAncestor(Position.wrap(parentPos.raw() - 1), parent.parentIndex, false).claim; - // For all attacks, the poststate is the parent claim. - postState = parent; - } else { - // If the step is a defense, the poststate exists elsewhere in the game state, - // and the parent claim is the expected pre-state. - preStateClaim = parent.claim; - postState = _findTraceAncestor(Position.wrap(parentPos.raw() + 1), parent.parentIndex, false); - } - - // INVARIANT: The prestate is always invalid if the passed `_stateData` is not the - // preimage of the prestate claim hash. - // We ignore the highest order byte of the digest because it is used to - // indicate the VM Status and is added after the digest is computed. - if (keccak256(_stateData) << 8 != preStateClaim.raw() << 8) revert InvalidPrestate(); - - // Compute the local preimage context for the step. - Hash uuid = _findLocalContext(_claimIndex); - - // INVARIANT: If a step is an attack, the poststate is valid if the step produces - // the same poststate hash as the parent claim's value. - // If a step is a defense: - // 1. If the parent claim and the found post state agree with each other - // (depth diff % 2 == 0), the step is valid if it produces the same - // state hash as the post state's claim. - // 2. If the parent claim and the found post state disagree with each other - // (depth diff % 2 != 0), the parent cannot be countered unless the step - // produces the same state hash as `postState.claim`. - // SAFETY: While the `attack` path does not need an extra check for the post - // state's depth in relation to the parent, we don't need another - // branch because (n - n) % 2 == 0. - bool validStep = vm().step(_stateData, _proof, uuid.raw()) == postState.claim.raw(); - bool parentPostAgree = (parentPos.depth() - postState.position.depth()) % 2 == 0; - if (parentPostAgree == validStep) revert ValidStep(); - - // INVARIANT: A step cannot be made against a claim for a second time. - if (parent.counteredBy != address(0)) revert DuplicateStep(); - - // Set the parent claim as countered. We do not need to append a new claim to the game; - // instead, we can just set the existing parent as countered. - parent.counteredBy = msg.sender; - } - - /// @notice Generic move function, used for both `attack` and `defend` moves. - /// @param _disputed The disputed `Claim`. - /// @param _challengeIndex The index of the claim being moved against. - /// @param _claim The claim at the next logical position in the game. - /// @param _isAttack Whether or not the move is an attack or defense. - function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual { - // INVARIANT: Moves cannot be made unless the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - // Get the parent. If it does not exist, the call will revert with OOB. - ClaimData memory parent = claimData[_challengeIndex]; - - // INVARIANT: The claim at the _challengeIndex must be the disputed claim. - if (Claim.unwrap(parent.claim) != Claim.unwrap(_disputed)) revert InvalidDisputedClaimIndex(); - - // Compute the position that the claim commits to. Because the parent's position is already - // known, we can compute the next position by moving left or right depending on whether - // or not the move is an attack or defense. - Position parentPos = parent.position; - Position nextPosition = parentPos.move(_isAttack); - uint256 nextPositionDepth = nextPosition.depth(); - - // INVARIANT: A defense can never be made against the root claim of either the output root game or any - // of the execution trace bisection subgames. This is because the root claim commits to the - // entire state. Therefore, the only valid defense is to do nothing if it is agreed with. - if ((_challengeIndex == 0 || nextPositionDepth == SPLIT_DEPTH + 2) && !_isAttack) { - revert CannotDefendRootClaim(); - } - - // INVARIANT: No moves against the root claim can be made after it has been challenged with - // `challengeRootL2Block`.` - if (l2BlockNumberChallenged && _challengeIndex == 0) revert L2BlockNumberChallenged(); - - // INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a - // claim at this depth is to perform a single instruction step on-chain via - // the `step` function to prove that the state transition produces an unexpected - // post-state. - if (nextPositionDepth > MAX_GAME_DEPTH) revert GameDepthExceeded(); - - // When the next position surpasses the split depth (i.e., it is the root claim of an execution - // trace bisection sub-game), we need to perform some extra verification steps. - if (nextPositionDepth == SPLIT_DEPTH + 1) { - _verifyExecBisectionRoot(_claim, _challengeIndex, parentPos, _isAttack); - } - - // INVARIANT: The `msg.value` must exactly equal the required bond. - if (getRequiredBond(nextPosition) != msg.value) revert IncorrectBondAmount(); - - // Compute the duration of the next clock. This is done by adding the duration of the - // grandparent claim to the difference between the current block timestamp and the - // parent's clock timestamp. - Duration nextDuration = getChallengerDuration(_challengeIndex); - - // INVARIANT: A move can never be made once its clock has exceeded `MAX_CLOCK_DURATION` - // seconds of time. - if (nextDuration.raw() == MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded(); - - // Clock extension is a mechanism that automatically extends the clock for a potential - // grandchild claim when there would be less than the clock extension time left if a player - // is forced to inherit another team's clock when countering a freeloader claim. Exact - // amount of clock extension time depends exactly where we are within the game. - uint64 actualExtension; - if (nextPositionDepth == MAX_GAME_DEPTH - 1) { - // If the next position is `MAX_GAME_DEPTH - 1` then we're about to execute a step. Our - // clock extension must therefore account for the LPP challenge period in addition to - // the standard clock extension. - actualExtension = CLOCK_EXTENSION.raw() + uint64(vm().oracle().challengePeriod()); - } else if (nextPositionDepth == SPLIT_DEPTH - 1) { - // If the next position is `SPLIT_DEPTH - 1` then we're about to begin an execution - // trace bisection and we need to give extra time for the off-chain challenge agent to - // be able to generate the initial instruction trace on the native FPVM. - actualExtension = CLOCK_EXTENSION.raw() * 2; - } else { - // Otherwise, we just use the standard clock extension. - actualExtension = CLOCK_EXTENSION.raw(); - } - - // Check if we need to apply the clock extension. - if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - actualExtension) { - nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - actualExtension); - } - - // Construct the next clock with the new duration and the current block timestamp. - Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp))); - - // INVARIANT: There cannot be multiple identical claims with identical moves on the same challengeIndex. - // Multiple claims at the same position may dispute the same challengeIndex. However, they must have different - // values. - Hash claimHash = _claim.hashClaimPos(nextPosition, _challengeIndex); - if (claims[claimHash]) revert ClaimAlreadyExists(); - claims[claimHash] = true; - - // Create the new claim. - claimData.push( - ClaimData({ - parentIndex: uint32(_challengeIndex), - // This is updated during subgame resolution - counteredBy: address(0), - claimant: msg.sender, - bond: uint128(msg.value), - claim: _claim, - position: nextPosition, - clock: nextClock - }) - ); - - // Update the subgame rooted at the parent claim. - subgames[_challengeIndex].push(claimData.length - 1); - - // Deposit the bond. - refundModeCredit[msg.sender] += msg.value; - weth().deposit{ value: msg.value }(); - - // Emit the appropriate event for the attack or defense. - emit Move(_challengeIndex, _claim, msg.sender); - } - - /// @notice Attack a disagreed upon `Claim`. - /// @param _disputed The `Claim` being attacked. - /// @param _parentIndex Index of the `Claim` to attack in the `claimData` array. This must match the `_disputed` - /// claim. - /// @param _claim The `Claim` at the relative attack position. - function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable { - move(_disputed, _parentIndex, _claim, true); - } - - /// @notice Defend an agreed upon `Claim`. - /// @notice _disputed The `Claim` being defended. - /// @param _parentIndex Index of the claim to defend in the `claimData` array. This must match the `_disputed` - /// claim. - /// @param _claim The `Claim` at the relative defense position. - function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable { - move(_disputed, _parentIndex, _claim, false); - } - - /// @notice Posts the requested local data to the VM's `PreimageOralce`. - /// @param _ident The local identifier of the data to post. - /// @param _execLeafIdx The index of the leaf claim in an execution subgame that requires the local data for a step. - /// @param _partOffset The offset of the data to post. - function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external { - // INVARIANT: Local data can only be added if the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - (Claim starting, Position startingPos, Claim disputed, Position disputedPos) = - _findStartingAndDisputedOutputs(_execLeafIdx); - Hash uuid = _computeLocalContext(starting, startingPos, disputed, disputedPos); - - IPreimageOracle oracle = vm().oracle(); - if (_ident == LocalPreimageKey.L1_HEAD_HASH) { - // Load the L1 head hash - oracle.loadLocalData(_ident, uuid.raw(), l1Head().raw(), 32, _partOffset); - } else if (_ident == LocalPreimageKey.STARTING_OUTPUT_ROOT) { - // Load the starting proposal's output root. - oracle.loadLocalData(_ident, uuid.raw(), starting.raw(), 32, _partOffset); - } else if (_ident == LocalPreimageKey.DISPUTED_OUTPUT_ROOT) { - // Load the disputed proposal's output root - oracle.loadLocalData(_ident, uuid.raw(), disputed.raw(), 32, _partOffset); - } else if (_ident == LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER) { - // 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); - } else { - revert InvalidLocalIdent(); - } - } - - /// @notice Returns the number of children that still need to be resolved in order to fully resolve a subgame rooted - /// at `_claimIndex`. - /// @param _claimIndex The subgame root claim's index within `claimData`. - /// @return numRemainingChildren_ The number of children that still need to be checked to resolve the subgame. - function getNumToResolve(uint256 _claimIndex) public view returns (uint256 numRemainingChildren_) { - ResolutionCheckpoint storage checkpoint = resolutionCheckpoints[_claimIndex]; - uint256[] storage challengeIndices = subgames[_claimIndex]; - uint256 challengeIndicesLen = challengeIndices.length; - - numRemainingChildren_ = challengeIndicesLen - checkpoint.subgameIndex; - } - - /// @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). - function l2SequenceNumber() public pure returns (uint256 l2SequenceNumber_) { - l2SequenceNumber_ = l2BlockNumber(); - } - - /// @notice Only the starting block number of the game. - function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { - startingBlockNumber_ = startingOutputRoot.l2SequenceNumber; - } - - /// @notice Starting output root and block number of the game. - function startingRootHash() external view returns (Hash startingRootHash_) { - startingRootHash_ = startingOutputRoot.root; - } - - /// @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; - } - - //////////////////////////////////////////////////////////////// - // `IDisputeGame` impl // - //////////////////////////////////////////////////////////////// - - /// @notice If all necessary information has been gathered, this function should mark the game - /// status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of - /// the resolved game. It is at this stage that the bonds should be awarded to the - /// necessary parties. - /// @dev May only be called if the `status` is `IN_PROGRESS`. - /// @return status_ The status of the game after resolution. - function resolve() external returns (GameStatus status_) { - // INVARIANT: Resolution cannot occur unless the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - // INVARIANT: Resolution cannot occur unless the absolute root subgame has been resolved. - if (!resolvedSubgames[0]) revert OutOfOrderResolution(); - - // Update the global game status; The dispute has concluded. - status_ = claimData[0].counteredBy == address(0) ? GameStatus.DEFENDER_WINS : GameStatus.CHALLENGER_WINS; - resolvedAt = Timestamp.wrap(uint64(block.timestamp)); - - // Update the status and emit the resolved event, note that we're performing an assignment here. - emit Resolved(status = status_); - } - - /// @notice Resolves the subgame rooted at the given claim index. `_numToResolve` specifies how many children of - /// the subgame will be checked in this call. If `_numToResolve` is less than the number of children, an - /// internal cursor will be updated and this function may be called again to complete resolution of the - /// subgame. - /// @dev This function must be called bottom-up in the DAG - /// A subgame is a tree of claims that has a maximum depth of 1. - /// A subgame root claims is valid if, and only if, all of its child claims are invalid. - /// At the deepest level in the DAG, a claim is invalid if there's a successful step against it. - /// @param _claimIndex The index of the subgame root claim to resolve. - /// @param _numToResolve The number of subgames to resolve in this call. If the input is `0`, and this is the first - /// page, this function will attempt to check all of the subgame's children at once. - function resolveClaim(uint256 _claimIndex, uint256 _numToResolve) external { - // INVARIANT: Resolution cannot occur unless the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - ClaimData storage subgameRootClaim = claimData[_claimIndex]; - Duration challengeClockDuration = getChallengerDuration(_claimIndex); - - // INVARIANT: Cannot resolve a subgame unless the clock of its would-be counter has expired - // INVARIANT: Assuming ordered subgame resolution, challengeClockDuration is always >= MAX_CLOCK_DURATION if all - // descendant subgames are resolved - if (challengeClockDuration.raw() < MAX_CLOCK_DURATION.raw()) revert ClockNotExpired(); - - // INVARIANT: Cannot resolve a subgame twice. - if (resolvedSubgames[_claimIndex]) revert ClaimAlreadyResolved(); - - uint256[] storage challengeIndices = subgames[_claimIndex]; - uint256 challengeIndicesLen = challengeIndices.length; - - // Uncontested claims are resolved implicitly unless they are the root claim. Pay out the bond to the claimant - // and return early. - if (challengeIndicesLen == 0 && _claimIndex != 0) { - // In the event that the parent claim is at the max depth, there will always be 0 subgames. If the - // `counteredBy` field is set and there are no subgames, this implies that the parent claim was successfully - // stepped against. In this case, we pay out the bond to the party that stepped against the parent claim. - // Otherwise, the parent claim is uncontested, and the bond is returned to the claimant. - address counteredBy = subgameRootClaim.counteredBy; - address recipient = counteredBy == address(0) ? subgameRootClaim.claimant : counteredBy; - _distributeBond(recipient, subgameRootClaim); - resolvedSubgames[_claimIndex] = true; - return; - } - - // Fetch the resolution checkpoint from storage. - ResolutionCheckpoint memory checkpoint = resolutionCheckpoints[_claimIndex]; - - // If the checkpoint does not currently exist, initialize the current left most position as max u128. - if (!checkpoint.initialCheckpointComplete) { - checkpoint.leftmostPosition = Position.wrap(type(uint128).max); - checkpoint.initialCheckpointComplete = true; - - // If `_numToResolve == 0`, assume that we can check all child subgames in this one callframe. - if (_numToResolve == 0) _numToResolve = challengeIndicesLen; - } - - // Assume parent is honest until proven otherwise - uint256 lastToResolve = checkpoint.subgameIndex + _numToResolve; - uint256 finalCursor = lastToResolve > challengeIndicesLen ? challengeIndicesLen : lastToResolve; - for (uint256 i = checkpoint.subgameIndex; i < finalCursor; i++) { - uint256 challengeIndex = challengeIndices[i]; - - // INVARIANT: Cannot resolve a subgame containing an unresolved claim - if (!resolvedSubgames[challengeIndex]) revert OutOfOrderResolution(); - - ClaimData storage claim = claimData[challengeIndex]; - - // If the child subgame is uncountered and further left than the current left-most counter, - // update the parent subgame's `countered` address and the current `leftmostCounter`. - // The left-most correct counter is preferred in bond payouts in order to discourage attackers - // from countering invalid subgame roots via an invalid defense position. As such positions - // cannot be correctly countered. - // Note that correctly positioned defense, but invalid claimes can still be successfully countered. - if (claim.counteredBy == address(0) && checkpoint.leftmostPosition.raw() > claim.position.raw()) { - checkpoint.counteredBy = claim.claimant; - checkpoint.leftmostPosition = claim.position; - } - } - - // Increase the checkpoint's cursor position by the number of children that were checked. - checkpoint.subgameIndex = uint32(finalCursor); - - // Persist the checkpoint and allow for continuing in a separate transaction, if resolution is not already - // complete. - resolutionCheckpoints[_claimIndex] = checkpoint; - - // If all children have been traversed in the above loop, the subgame may be resolved. Otherwise, persist the - // checkpoint and allow for continuation in a separate transaction. - if (checkpoint.subgameIndex == challengeIndicesLen) { - address countered = checkpoint.counteredBy; - - // Mark the subgame as resolved. - resolvedSubgames[_claimIndex] = true; - - // Distribute the bond to the appropriate party. - 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); - - // 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; - } - } - } - - /// @notice Getter for the creator of the dispute game. - /// @dev `clones-with-immutable-args` argument #1 - /// @return creator_ The creator of the dispute game. - function gameCreator() public pure returns (address creator_) { - creator_ = _getArgAddress(0); - } - - /// @notice Getter for the root claim. - /// @dev `clones-with-immutable-args` argument #2 - /// @return rootClaim_ The root claim of the DisputeGame. - function rootClaim() public pure returns (Claim rootClaim_) { - rootClaim_ = Claim.wrap(_getArgBytes32(20)); - } - - /// @notice Getter for the parent hash of the L1 block when the dispute game was created. - /// @dev `clones-with-immutable-args` argument #3 - /// @return l1Head_ The parent hash of the L1 block when the dispute game was created. - function l1Head() public pure returns (Hash l1Head_) { - l1Head_ = Hash.wrap(_getArgBytes32(52)); - } - - /// @notice Getter for the game type. - /// @dev `clones-with-immutable-args` argument #4 - /// @return gameType_ The type of proof system being used. - function gameType() public pure returns (GameType gameType_) { - gameType_ = GameType.wrap(_getArgUint32(84)); - } - - /// @notice Getter for the extra data. - /// @dev `clones-with-immutable-args` argument #5 - /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. - function extraData() public pure returns (bytes memory extraData_) { - // The extra data starts at the second word within the cwia calldata and - // is 32 bytes long. - extraData_ = _getArgBytes(88, 32); - } - - /// @notice Getter for the absolute prestate of the instruction trace. - /// @dev `clones-with-immutable-args` argument #6 - /// @return absolutePrestate_ The absolute prestate of the instruction trace. - function absolutePrestate() public pure returns (Claim absolutePrestate_) { - absolutePrestate_ = Claim.wrap(_getArgBytes32(120)); - } - - /// @notice Getter for the VM implementation. - /// @dev `clones-with-immutable-args` argument #7 - /// @return vm_ The onchain VM implementation address. - function vm() public pure returns (IBigStepper vm_) { - vm_ = IBigStepper(_getArgAddress(152)); - } - - /// @notice Getter for the anchor state registry. - /// @dev `clones-with-immutable-args` argument #8 - /// @return registry_ The anchor state registry contract address. - function anchorStateRegistry() public pure returns (IAnchorStateRegistry registry_) { - registry_ = IAnchorStateRegistry(_getArgAddress(172)); - } - - /// @notice Getter for the WETH contract. - /// @dev `clones-with-immutable-args` argument #9 - /// @return weth_ The WETH contract for holding ETH. - function weth() public pure returns (IDelayedWETH weth_) { - weth_ = IDelayedWETH(payable(_getArgAddress(192))); - } - - /// @notice Getter for the L2 chain ID. - /// @dev `clones-with-immutable-args` argument #10 - /// @return l2ChainId_ The L2 chain ID. - function l2ChainId() public pure returns (uint256 l2ChainId_) { - l2ChainId_ = _getArgUint256(212); - } - - /// @notice A compliant implementation of this interface should return the components of the - /// game UUID's preimage provided in the cwia payload. The preimage of the UUID is - /// constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes - /// concatenation. - /// @return gameType_ The type of proof system being used. - /// @return rootClaim_ The root claim of the DisputeGame. - /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. - function gameData() external pure returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_) { - gameType_ = gameType(); - rootClaim_ = rootClaim(); - extraData_ = extraData(); - } - - //////////////////////////////////////////////////////////////// - // MISC EXTERNAL // - //////////////////////////////////////////////////////////////// - - /// @notice Returns the required bond for a given move kind. - /// @param _position The position of the bonded interaction. - /// @return requiredBond_ The required ETH bond for the given move, in wei. - function getRequiredBond(Position _position) public view returns (uint256 requiredBond_) { - uint256 depth = uint256(_position.depth()); - if (depth > MAX_GAME_DEPTH) revert GameDepthExceeded(); - - // Values taken from Big Bonds v1.5 (TM) spec. - uint256 assumedBaseFee = 200 gwei; - uint256 baseGasCharged = 400_000; - uint256 highGasCharged = 300_000_000; - - // Goal here is to compute the fixed multiplier that will be applied to the base gas - // charged to get the required gas amount for the given depth. We apply this multiplier - // some `n` times where `n` is the depth of the position. We are looking for some number - // that, when multiplied by itself `MAX_GAME_DEPTH` times and then multiplied by the base - // gas charged, will give us the maximum gas that we want to charge. - // We want to solve for (highGasCharged/baseGasCharged) ** (1/MAX_GAME_DEPTH). - // We know that a ** (b/c) is equal to e ** (ln(a) * (b/c)). - // We can compute e ** (ln(a) * (b/c)) quite easily with FixedPointMathLib. - - // Set up a, b, and c. - uint256 a = highGasCharged / baseGasCharged; - uint256 b = FixedPointMathLib.WAD; - uint256 c = MAX_GAME_DEPTH * FixedPointMathLib.WAD; - - // Compute ln(a). - // slither-disable-next-line divide-before-multiply - uint256 lnA = uint256(FixedPointMathLib.lnWad(int256(a * FixedPointMathLib.WAD))); - - // Computes (b / c) with full precision using WAD = 1e18. - uint256 bOverC = FixedPointMathLib.divWad(b, c); - - // Compute e ** (ln(a) * (b/c)) - // sMulWad can be used here since WAD = 1e18 maintains the same precision. - uint256 numerator = FixedPointMathLib.mulWad(lnA, bOverC); - int256 base = FixedPointMathLib.expWad(int256(numerator)); - - // Compute the required gas amount. - int256 rawGas = FixedPointMathLib.powWad(base, int256(depth * FixedPointMathLib.WAD)); - uint256 requiredGas = FixedPointMathLib.mulWad(baseGasCharged, uint256(rawGas)); - - // Compute the required bond. - requiredBond_ = assumedBaseFee * requiredGas; - } - - /// @notice Claim the credit belonging to the recipient address. Reverts if the game isn't - /// finalized, if the recipient has no credit to claim, or if the bond transfer - /// fails. If the game is finalized but no bond has been paid out yet, this method - /// will determine the bond distribution mode and also try to update anchor game. - /// @param _recipient The owner and recipient of the credit. - function claimCredit(address _recipient) external { - // Close out the game and determine the bond distribution mode if not already set. - // We call this as part of claim credit to reduce the number of additional calls that a - // Challenger needs to make to this contract. - closeGame(); - - // Fetch the recipient's credit balance based on the bond distribution mode. - uint256 recipientCredit; - if (bondDistributionMode == BondDistributionMode.REFUND) { - recipientCredit = refundModeCredit[_recipient]; - } else if (bondDistributionMode == BondDistributionMode.NORMAL) { - recipientCredit = normalModeCredit[_recipient]; - } else { - // We shouldn't get here, but sanity check just in case. - revert InvalidBondDistributionMode(); - } - - // If the game is in refund mode, and the recipient has not unlocked their refund mode - // credit, we unlock it and return early. - if (!hasUnlockedCredit[_recipient]) { - hasUnlockedCredit[_recipient] = true; - weth().unlock(_recipient, recipientCredit); - return; - } - - // Revert if the recipient has no credit to claim. - if (recipientCredit == 0) revert NoCreditToClaim(); - - // Set the recipient's credit balances to 0. - refundModeCredit[_recipient] = 0; - normalModeCredit[_recipient] = 0; - - // Try to withdraw the WETH amount so it can be used here. - weth().withdraw(_recipient, recipientCredit); - - // Transfer the credit to the recipient. - (bool success,) = _recipient.call{ value: recipientCredit }(hex""); - if (!success) revert BondTransferFailed(); - } - - /// @notice Closes out the game, determines the bond distribution mode, attempts to register - /// the game as the anchor game, and emits an event. - function closeGame() public { - // If the bond distribution mode has already been determined, we can return early. - if (bondDistributionMode == BondDistributionMode.REFUND || bondDistributionMode == BondDistributionMode.NORMAL) - { - // We can't revert or we'd break claimCredit(). - return; - } else if (bondDistributionMode != BondDistributionMode.UNDECIDED) { - // We shouldn't get here, but sanity check just in case. - revert InvalidBondDistributionMode(); - } - - // 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(); - } - - // Make sure that the game is resolved. - // AnchorStateRegistry should be checking this but we're being defensive here. - if (resolvedAt.raw() == 0) { - revert GameNotResolved(); - } - - // Game must be finalized according to the AnchorStateRegistry. - bool finalized = anchorStateRegistry().isGameFinalized(IDisputeGame(address(this))); - if (!finalized) { - revert GameNotFinalized(); - } - - // Try to update the anchor game first. Won't always succeed because delays can lead - // to situations in which this game might not be eligible to be a new anchor game. - // eip150-safe - try anchorStateRegistry().setAnchorState(IDisputeGame(address(this))) { } catch { } - - // Check if the game is a proper game, which will determine the bond distribution mode. - bool properGame = anchorStateRegistry().isGameProper(IDisputeGame(address(this))); - - // If the game is a proper game, the bonds should be distributed normally. Otherwise, go - // into refund mode and distribute bonds back to their original depositors. - if (properGame) { - bondDistributionMode = BondDistributionMode.NORMAL; - } else { - bondDistributionMode = BondDistributionMode.REFUND; - } - - // Emit an event to signal that the game has been closed. - emit GameClosed(bondDistributionMode); - } - - /// @notice Returns the amount of time elapsed on the potential challenger to `_claimIndex`'s chess clock. Maxes - /// out at `MAX_CLOCK_DURATION`. - /// @param _claimIndex The index of the subgame root claim. - /// @return duration_ The time elapsed on the potential challenger to `_claimIndex`'s chess clock. - function getChallengerDuration(uint256 _claimIndex) public view returns (Duration duration_) { - // INVARIANT: The game must be in progress to query the remaining time to respond to a given claim. - if (status != GameStatus.IN_PROGRESS) { - revert GameNotInProgress(); - } - - // Fetch the subgame root claim. - ClaimData storage subgameRootClaim = claimData[_claimIndex]; - - // Fetch the parent of the subgame root's clock, if it exists. - Clock parentClock; - if (subgameRootClaim.parentIndex != type(uint32).max) { - parentClock = claimData[subgameRootClaim.parentIndex].clock; - } - - // Compute the duration elapsed of the potential challenger's clock. - uint64 challengeDuration = - uint64(parentClock.duration().raw() + (block.timestamp - subgameRootClaim.clock.timestamp().raw())); - duration_ = challengeDuration > MAX_CLOCK_DURATION.raw() ? MAX_CLOCK_DURATION : Duration.wrap(challengeDuration); - } - - /// @notice Returns the length of the `claimData` array. - function claimDataLen() external view returns (uint256 len_) { - len_ = claimData.length; - } - - /// @notice Returns the credit balance of a given recipient. - /// @param _recipient The recipient of the credit. - /// @return credit_ The credit balance of the recipient. - function credit(address _recipient) external view returns (uint256 credit_) { - if (bondDistributionMode == BondDistributionMode.REFUND) { - credit_ = refundModeCredit[_recipient]; - } else { - // Always return normal credit balance by default unless we're in refund mode. - credit_ = normalModeCredit[_recipient]; - } - } - - //////////////////////////////////////////////////////////////// - // IMMUTABLE GETTERS // - //////////////////////////////////////////////////////////////// - - /// @notice Returns the max game depth. - function maxGameDepth() external view returns (uint256 maxGameDepth_) { - maxGameDepth_ = MAX_GAME_DEPTH; - } - - /// @notice Returns the split depth. - function splitDepth() external view returns (uint256 splitDepth_) { - splitDepth_ = SPLIT_DEPTH; - } - - /// @notice Returns the max clock duration. - function maxClockDuration() external view returns (Duration maxClockDuration_) { - maxClockDuration_ = MAX_CLOCK_DURATION; - } - - /// @notice Returns the clock extension constant. - function clockExtension() external view returns (Duration clockExtension_) { - clockExtension_ = CLOCK_EXTENSION; - } - - //////////////////////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////////////////////// - - /// @notice Pays out the bond of a claim to a given recipient. - /// @param _recipient The recipient of the bond. - /// @param _bonded The claim to pay out the bond of. - function _distributeBond(address _recipient, ClaimData storage _bonded) internal { - normalModeCredit[_recipient] += _bonded.bond; - } - - /// @notice Verifies the integrity of an execution bisection subgame's root claim. Reverts if the claim - /// is invalid. - /// @param _rootClaim The root claim of the execution bisection subgame. - function _verifyExecBisectionRoot( - Claim _rootClaim, - uint256 _parentIdx, - Position _parentPos, - bool _isAttack - ) - internal - view - { - // The root claim of an execution trace bisection sub-game must: - // 1. Signal that the VM panicked or resulted in an invalid transition if the disputed output root - // was made by the opposing party. - // 2. Signal that the VM resulted in a valid transition if the disputed output root was made by the same party. - - // If the move is a defense, the disputed output could have been made by either party. In this case, we - // need to search for the parent output to determine what the expected status byte should be. - Position disputedLeafPos = Position.wrap(_parentPos.raw() + 1); - ClaimData storage disputed = _findTraceAncestor({ _pos: disputedLeafPos, _start: _parentIdx, _global: true }); - uint8 vmStatus = uint8(_rootClaim.raw()[0]); - - if (_isAttack || disputed.position.depth() % 2 == SPLIT_DEPTH % 2) { - // If the move is an attack, the parent output is always deemed to be disputed. In this case, we only need - // to check that the root claim signals that the VM panicked or resulted in an invalid transition. - // If the move is a defense, and the disputed output and creator of the execution trace subgame disagree, - // the root claim should also signal that the VM panicked or resulted in an invalid transition. - if (!(vmStatus == VMStatuses.INVALID.raw() || vmStatus == VMStatuses.PANIC.raw())) { - revert UnexpectedRootClaim(_rootClaim); - } - } else if (vmStatus != VMStatuses.VALID.raw()) { - // The disputed output and the creator of the execution trace subgame agree. The status byte should - // have signaled that the VM succeeded. - revert UnexpectedRootClaim(_rootClaim); - } - } - - /// @notice Finds the trace ancestor of a given position within the DAG. - /// @param _pos The position to find the trace ancestor claim of. - /// @param _start The index to start searching from. - /// @param _global Whether or not to search the entire dag or just within an execution trace subgame. If set to - /// `true`, and `_pos` is at or above the split depth, this function will revert. - /// @return ancestor_ The ancestor claim that commits to the same trace index as `_pos`. - function _findTraceAncestor( - Position _pos, - uint256 _start, - bool _global - ) - internal - view - returns (ClaimData storage ancestor_) - { - // Grab the trace ancestor's expected position. - Position traceAncestorPos = _global ? _pos.traceAncestor() : _pos.traceAncestorBounded(SPLIT_DEPTH); - - // Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is - // guaranteed that such a claim exists. - ancestor_ = claimData[_start]; - while (ancestor_.position.raw() != traceAncestorPos.raw()) { - ancestor_ = claimData[ancestor_.parentIndex]; - } - } - - /// @notice Finds the starting and disputed output root for a given `ClaimData` within the DAG. This - /// `ClaimData` must be below the `SPLIT_DEPTH`. - /// @param _start The index within `claimData` of the claim to start searching from. - /// @return startingClaim_ The starting output root claim. - /// @return startingPos_ The starting output root position. - /// @return disputedClaim_ The disputed output root claim. - /// @return disputedPos_ The disputed output root position. - function _findStartingAndDisputedOutputs(uint256 _start) - internal - view - returns (Claim startingClaim_, Position startingPos_, Claim disputedClaim_, Position disputedPos_) - { - // Fatch the starting claim. - uint256 claimIdx = _start; - ClaimData storage claim = claimData[claimIdx]; - - // If the starting claim's depth is less than or equal to the split depth, we revert as this is UB. - if (claim.position.depth() <= SPLIT_DEPTH) revert ClaimAboveSplit(); - - // We want to: - // 1. Find the first claim at the split depth. - // 2. Determine whether it was the starting or disputed output for the exec game. - // 3. Find the complimentary claim depending on the info from #2 (pre or post). - - // Walk up the DAG until the ancestor's depth is equal to the split depth. - uint256 currentDepth; - ClaimData storage execRootClaim = claim; - while ((currentDepth = claim.position.depth()) > SPLIT_DEPTH) { - uint256 parentIndex = claim.parentIndex; - - // If we're currently at the split depth + 1, we're at the root of the execution sub-game. - // We need to keep track of the root claim here to determine whether the execution sub-game was - // started with an attack or defense against the output leaf claim. - if (currentDepth == SPLIT_DEPTH + 1) execRootClaim = claim; - - claim = claimData[parentIndex]; - claimIdx = parentIndex; - } - - // Determine whether the start of the execution sub-game was an attack or defense to the output root - // above. This is important because it determines which claim is the starting output root and which - // is the disputed output root. - (Position execRootPos, Position outputPos) = (execRootClaim.position, claim.position); - bool wasAttack = execRootPos.parent().raw() == outputPos.raw(); - - // Determine the starting and disputed output root indices. - // 1. If it was an attack, the disputed output root is `claim`, and the starting output root is - // elsewhere in the DAG (it must commit to the block # index at depth of `outputPos - 1`). - // 2. If it was a defense, the starting output root is `claim`, and the disputed output root is - // elsewhere in the DAG (it must commit to the block # index at depth of `outputPos + 1`). - if (wasAttack) { - // 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 - // 0, which can be easily identified due to 0 being an invalid Gindex. - if (outputPos.indexAtDepth() > 0) { - ClaimData storage starting = _findTraceAncestor(Position.wrap(outputPos.raw() - 1), claimIdx, true); - (startingClaim_, startingPos_) = (starting.claim, starting.position); - } else { - startingClaim_ = Claim.wrap(startingOutputRoot.root.raw()); - } - (disputedClaim_, disputedPos_) = (claim.claim, claim.position); - } else { - ClaimData storage disputed = _findTraceAncestor(Position.wrap(outputPos.raw() + 1), claimIdx, true); - (startingClaim_, startingPos_) = (claim.claim, claim.position); - (disputedClaim_, disputedPos_) = (disputed.claim, disputed.position); - } - } - - /// @notice Finds the local context hash for a given claim index that is present in an execution trace subgame. - /// @param _claimIndex The index of the claim to find the local context hash for. - /// @return uuid_ The local context hash. - function _findLocalContext(uint256 _claimIndex) internal view returns (Hash uuid_) { - (Claim starting, Position startingPos, Claim disputed, Position disputedPos) = - _findStartingAndDisputedOutputs(_claimIndex); - uuid_ = _computeLocalContext(starting, startingPos, disputed, disputedPos); - } - - /// @notice Computes the local context hash for a set of starting/disputed claim values and positions. - /// @param _starting The starting claim. - /// @param _startingPos The starting claim's position. - /// @param _disputed The disputed claim. - /// @param _disputedPos The disputed claim's position. - /// @return uuid_ The local context hash. - function _computeLocalContext( - Claim _starting, - Position _startingPos, - Claim _disputed, - Position _disputedPos - ) - internal - pure - returns (Hash uuid_) - { - // A position of 0 indicates that the starting claim is the absolute prestate. In this special case, - // we do not include the starting claim within the local context hash. - uuid_ = _startingPos.raw() == 0 - ? Hash.wrap(keccak256(abi.encode(_disputed, _disputedPos))) - : Hash.wrap(keccak256(abi.encode(_starting, _startingPos, _disputed, _disputedPos))); - } -} diff --git a/src/L1/proofs/v2/PermissionedDisputeGameV2.sol b/src/L1/proofs/v2/PermissionedDisputeGameV2.sol deleted file mode 100644 index 4a79a9b2e..000000000 --- a/src/L1/proofs/v2/PermissionedDisputeGameV2.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Contracts -import { FaultDisputeGameV2 } from "src/L1/proofs/v2/FaultDisputeGameV2.sol"; - -// Libraries -import { Claim } from "src/libraries/bridge/Types.sol"; -import { BadAuth } from "src/libraries/bridge/Errors.sol"; - -/// @title PermissionedDisputeGameV2 -/// @notice PermissionedDisputeGameV2 is a contract that inherits from `FaultDisputeGameV2`, and contains two roles: -/// - The `challenger` role, which is allowed to challenge a dispute. -/// - The `proposer` role, which is allowed to create proposals and participate in their game. -/// This contract exists as a way for networks to support the fault proof iteration of the OptimismPortal -/// contract without needing to support a fully permissionless system. Permissionless systems can introduce -/// costs that certain networks may not wish to support. This contract can also be used as a fallback mechanism -/// in case of a failure in the permissionless fault proof system in the stage one release. -contract PermissionedDisputeGameV2 is FaultDisputeGameV2 { - /// @notice Modifier that gates access to the `challenger` and `proposer` roles. - modifier onlyAuthorized() { - if (!(msg.sender == proposer() || msg.sender == challenger())) { - revert BadAuth(); - } - _; - } - - /// @notice Semantic version. - /// @custom:semver 2.2.0 - function version() public pure override returns (string memory) { - return "2.2.0"; - } - - /// @param _params Parameters for creating a new FaultDisputeGame. - constructor(GameConstructorParams memory _params) FaultDisputeGameV2(_params) { } - - /// @inheritdoc FaultDisputeGameV2 - function step( - uint256 _claimIndex, - bool _isAttack, - bytes calldata _stateData, - bytes calldata _proof - ) - public - override - onlyAuthorized - { - super.step(_claimIndex, _isAttack, _stateData, _proof); - } - - /// @notice Generic move function, used for both `attack` and `defend` moves. - /// @notice _disputed The disputed `Claim`. - /// @param _challengeIndex The index of the claim being moved against. This must match the `_disputed` claim. - /// @param _claim The claim at the next logical position in the game. - /// @param _isAttack Whether or not the move is an attack or defense. - function move( - Claim _disputed, - uint256 _challengeIndex, - Claim _claim, - bool _isAttack - ) - public - payable - override - onlyAuthorized - { - super.move(_disputed, _challengeIndex, _claim, _isAttack); - } - - /// @notice Initializes the contract. - function initialize() public payable override { - super.initialize(); - - // The creator of the dispute game must be the proposer EOA. - if (tx.origin != proposer()) revert BadAuth(); - } - - function immutableArgsByteCount() internal pure override returns (uint256) { - // Extend expected data length to account for proposer and challenger addresses - // - 20 bytes: proposer address - // - 20 bytes: challenger address - return super.immutableArgsByteCount() + 40; - } - - //////////////////////////////////////////////////////////////// - // IMMUTABLE GETTERS // - //////////////////////////////////////////////////////////////// - - /// @notice Returns the proposer address. The proposer role is allowed to create proposals and participate in the - /// dispute game. - function proposer() public pure returns (address proposer_) { - proposer_ = _getArgAddress(super.immutableArgsByteCount()); - } - - /// @notice Returns the challenger address. The challenger role is allowed to participate in the dispute game. - function challenger() public pure returns (address challenger_) { - challenger_ = _getArgAddress(super.immutableArgsByteCount() + 20); - } -} diff --git a/src/cannon/MIPS64.sol b/src/cannon/MIPS64.sol deleted file mode 100644 index 0bf50c8ad..000000000 --- a/src/cannon/MIPS64.sol +++ /dev/null @@ -1,983 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Libraries -import { - InvalidMemoryProof, - InvalidRMWInstruction, - InvalidSecondMemoryProof, - UnsupportedStateVersion -} from "src/cannon/libraries/CannonErrors.sol"; -import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; -import { MIPS64Instructions as ins } from "src/cannon/libraries/MIPS64Instructions.sol"; -import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; -import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; -import { MIPS64Syscalls as sys } from "src/cannon/libraries/MIPS64Syscalls.sol"; -import { VMStatuses } from "src/libraries/bridge/Types.sol"; - -// Interfaces -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { ISemver } from "interfaces/universal/ISemver.sol"; - -/// @title MIPS64 -/// @notice The MIPS64 contract emulates a single MIPS instruction. -/// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking. -contract MIPS64 is ISemver { - /// @notice The thread context. - /// Total state size: 8 + 1 + 1 + 8 + 8 + 8 + 8 + 32 * 8 = 298 bytes - struct ThreadState { - // metadata - uint64 threadID; - uint8 exitCode; - bool exited; - // state - uint64 pc; - uint64 nextPC; - uint64 lo; - uint64 hi; - uint64[32] registers; - } - - uint32 internal constant PACKED_THREAD_STATE_SIZE = 298; - - uint8 internal constant LL_STATUS_NONE = 0; - uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1; - uint8 internal constant LL_STATUS_ACTIVE_64_BIT = 0x2; - - /// @notice Stores the VM state. - /// Total state size: 32 + 32 + 8 + 8 + 1 + 8 + 8 + 1 + 1 + 8 + 8 + 1 + 32 + 32 + 8 = 188 bytes - /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot. - struct State { - bytes32 memRoot; - bytes32 preimageKey; - uint64 preimageOffset; - uint64 heap; - uint8 llReservationStatus; - uint64 llAddress; - uint64 llOwnerThread; - uint8 exitCode; - bool exited; - uint64 step; - uint64 stepsSinceLastContextSwitch; - bool traverseRight; - bytes32 leftThreadStack; - bytes32 rightThreadStack; - uint64 nextThreadID; - } - - /// @notice The semantic version of the MIPS64 contract. - /// @custom:semver 1.9.0 - string public constant version = "1.9.0"; - - /// @notice The preimage oracle contract. - IPreimageOracle internal immutable ORACLE; - - /// @notice The state version implemented. This identifies the specific state transition rules applied. - uint256 internal immutable STATE_VERSION; - - // The offset of the start of proof calldata (_threadWitness.offset) in the step() function - uint256 internal constant THREAD_PROOF_OFFSET = 356; - - // The offset of the start of proof calldata (_memProof.offset) in the step() function - uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32; - - // The empty thread root - keccak256(bytes32(0) ++ bytes32(0)) - bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; - - // State memory offset allocated during step - uint256 internal constant STATE_MEM_OFFSET = 0x80; - - // ThreadState memory offset allocated during step - uint256 internal constant TC_MEM_OFFSET = 0x260; - - /// @param _oracle The address of the preimage oracle contract. - constructor(IPreimageOracle _oracle, uint256 _stateVersion) { - // Supports VersionMultiThreaded64_v4 (7) and VersionMultiThreaded64_v5 (8) - if (_stateVersion != 7 && _stateVersion != 8) { - revert UnsupportedStateVersion(); - } - ORACLE = _oracle; - STATE_VERSION = _stateVersion; - } - - /// @notice Getter for the pre-image oracle contract. - /// @return oracle_ The IPreimageOracle contract. - function oracle() external view returns (IPreimageOracle oracle_) { - oracle_ = ORACLE; - } - - /// @notice Getter for the state version. - /// @return stateVersion_ The state version implemented by this contract. - function stateVersion() external view returns (uint256 stateVersion_) { - stateVersion_ = STATE_VERSION; - } - - /// @notice Executes a single step of the multi-threaded vm. - /// Will revert if any required input state is missing. - /// @param _stateData The encoded state witness data. - /// @param _proof The encoded proof data: <, . - /// Contains the thread context witness and the memory proof data for leaves within the MIPS VM's - /// memory. - /// The thread context witness is a packed tuple of the thread context and the immediate inner root of - /// the current thread stack. - /// @param _localContext The local key context for the preimage oracle. Optional, can be set as a constant - /// if the caller only requires one set of local keys. - /// @return postState_ The hash of the post state witness after the state transition. - function step( - bytes calldata _stateData, - bytes calldata _proof, - bytes32 _localContext - ) - public - returns (bytes32 postState_) - { - postState_ = doStep(_stateData, _proof, _localContext); - assertPostStateChecks(); - } - - function assertPostStateChecks() internal pure { - State memory state; - assembly { - state := STATE_MEM_OFFSET - } - - bytes32 activeStack = state.traverseRight ? state.rightThreadStack : state.leftThreadStack; - if (activeStack == EMPTY_THREAD_ROOT) { - revert("MIPS64: post-state active thread stack is empty"); - } - } - - function doStep(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext) - internal - returns (bytes32) - { - unchecked { - State memory state; - ThreadState memory thread; - uint32 exited; - assembly { - if iszero(eq(state, STATE_MEM_OFFSET)) { - // expected state mem offset check - revert(0, 0) - } - if iszero(eq(thread, TC_MEM_OFFSET)) { - // expected thread mem offset check - // STATE_MEM_OFFSET = 0x80 = 128 - // 32 bytes per state field = 32 * 15 = 480 - // TC_MEM_OFFSET = 480 + 128 = 608 = 0x260 - revert(0, 0) - } - if iszero(eq(mload(0x40), shl(5, 59))) { - // 4 + 15 state slots + 40 thread slots = 59 expected memory check - revert(0, 0) - } - if iszero(eq(_stateData.offset, 132)) { - // 32*4+4=132 expected state data offset - revert(0, 0) - } - if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) { - // _stateData.offset = 132 - // stateData.length = ceil(stateSize / 32) * 32 = 6 * 32 = 192 - // _proof size prefix = 32 - // expected thread proof offset equals the sum of the above is 356 - revert(0, 0) - } - - function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut { - // calldata is packed, thus starting left-aligned, shift-right to pad and right-align - let w := shr(shl(3, sub(32, size)), calldataload(callOffset)) - mstore(memOffset, w) - callOffsetOut := add(callOffset, size) - memOffsetOut := add(memOffset, 32) - } - - // Unpack state from calldata into memory - let c := _stateData.offset // calldata offset - let m := STATE_MEM_OFFSET // mem offset - c, m := putField(c, m, 32) // memRoot - c, m := putField(c, m, 32) // preimageKey - c, m := putField(c, m, 8) // preimageOffset - c, m := putField(c, m, 8) // heap - c, m := putField(c, m, 1) // llReservationStatus - c, m := putField(c, m, 8) // llAddress - c, m := putField(c, m, 8) // llOwnerThread - c, m := putField(c, m, 1) // exitCode - c, m := putField(c, m, 1) // exited - exited := mload(sub(m, 32)) - c, m := putField(c, m, 8) // step - c, m := putField(c, m, 8) // stepsSinceLastContextSwitch - c, m := putField(c, m, 1) // traverseRight - c, m := putField(c, m, 32) // leftThreadStack - c, m := putField(c, m, 32) // rightThreadStack - c, m := putField(c, m, 8) // nextThreadID - } - st.assertExitedIsValid(exited); - - if (state.exited) { - // thread state is unchanged - return outputState(); - } - - if ( - (state.leftThreadStack == EMPTY_THREAD_ROOT && !state.traverseRight) - || (state.rightThreadStack == EMPTY_THREAD_ROOT && state.traverseRight) - ) { - revert("MIPS64: active thread stack is empty"); - } - - state.step += 1; - - setThreadStateFromCalldata(thread); - validateCalldataThreadWitness(state, thread); - - if (thread.exited) { - popThread(state); - return outputState(); - } - - if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) { - preemptThread(state, thread); - return outputState(); - } - state.stepsSinceLastContextSwitch += 1; - - // instruction fetch - uint256 insnProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 0); - (uint32 insn, uint32 opcode, uint32 fun) = - ins.getInstructionDetails(thread.pc, state.memRoot, insnProofOffset); - - // Handle syscall separately - // syscall (can read and write) - if (opcode == 0 && fun == 0xC) { - return handleSyscall(_localContext); - } - - // Handle RMW (read-modify-write) ops - if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) { - return handleRMWOps(state, thread, insn, opcode); - } - if (opcode == ins.OP_LOAD_LINKED64 || opcode == ins.OP_STORE_CONDITIONAL64) { - return handleRMWOps(state, thread, insn, opcode); - } - - // Exec the rest of the step logic - st.CpuScalars memory cpu = getCpuScalars(thread); - ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({ - cpu: cpu, - registers: thread.registers, - memRoot: state.memRoot, - memProofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), - insn: insn, - opcode: opcode, - fun: fun - }); - bool memUpdated; - uint64 effMemAddr; - (state.memRoot, memUpdated, effMemAddr) = ins.execMipsCoreStepLogic(coreStepArgs); - setStateCpuScalars(thread, cpu); - updateCurrentThreadRoot(); - if (memUpdated) { - handleMemoryUpdate(state, effMemAddr); - } - - return outputState(); - } - } - - function handleMemoryUpdate(State memory _state, uint64 _effMemAddr) internal pure { - if (_effMemAddr == (arch.ADDRESS_MASK & _state.llAddress)) { - // Reserved address was modified, clear the reservation - clearLLMemoryReservation(_state); - } - } - - function clearLLMemoryReservation(State memory _state) internal pure { - _state.llReservationStatus = LL_STATUS_NONE; - _state.llAddress = 0; - _state.llOwnerThread = 0; - } - - function handleRMWOps( - State memory _state, - ThreadState memory _thread, - uint32 _insn, - uint32 _opcode - ) - internal - returns (bytes32) - { - unchecked { - uint64 base = _thread.registers[(_insn >> 21) & 0x1F]; - uint32 rtReg = (_insn >> 16) & 0x1F; - uint64 addr = base + ins.signExtendImmediate(_insn); - - // Determine some opcode-specific parameters - uint8 targetStatus = LL_STATUS_ACTIVE_32_BIT; - uint64 byteLength = 4; - if (_opcode == ins.OP_LOAD_LINKED64 || _opcode == ins.OP_STORE_CONDITIONAL64) { - // Use 64-bit params - targetStatus = LL_STATUS_ACTIVE_64_BIT; - byteLength = 8; - } - - uint64 retVal = 0; - uint64 threadId = _thread.threadID; - if (_opcode == ins.OP_LOAD_LINKED || _opcode == ins.OP_LOAD_LINKED64) { - retVal = loadSubWord(_state, addr, byteLength, true); - - _state.llReservationStatus = targetStatus; - _state.llAddress = addr; - _state.llOwnerThread = threadId; - } else if (_opcode == ins.OP_STORE_CONDITIONAL || _opcode == ins.OP_STORE_CONDITIONAL64) { - // Check if our memory reservation is still intact - if ( - _state.llReservationStatus == targetStatus && _state.llOwnerThread == threadId - && _state.llAddress == addr - ) { - // Complete atomic update: set memory and return 1 for success - clearLLMemoryReservation(_state); - - uint64 val = _thread.registers[rtReg]; - storeSubWord(_state, addr, byteLength, val); - - retVal = 1; - } else { - // Atomic update failed, return 0 for failure - retVal = 0; - } - } else { - revert InvalidRMWInstruction(); - } - - st.CpuScalars memory cpu = getCpuScalars(_thread); - ins.handleRd(cpu, _thread.registers, rtReg, retVal, true); - setStateCpuScalars(_thread, cpu); - updateCurrentThreadRoot(); - - return outputState(); - } - } - - /// @notice Loads a subword of byteLength size contained from memory based on the low-order bits of vaddr - /// @param _vaddr The virtual address of the the subword. - /// @param _byteLength The size of the subword. - /// @param _signExtend Whether to sign extend the selected subwrod. - function loadSubWord( - State memory _state, - uint64 _vaddr, - uint64 _byteLength, - bool _signExtend - ) - internal - pure - returns (uint64 val_) - { - uint64 effAddr = _vaddr & arch.ADDRESS_MASK; - uint256 memProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1); - uint64 mem = MIPS64Memory.readMem(_state.memRoot, effAddr, memProofOffset); - val_ = ins.selectSubWord(_vaddr, mem, _byteLength, _signExtend); - } - - /// @notice Stores a word that has been updated by the specified subword at bit positions determined by the virtual - /// address - /// @param _vaddr The virtual address of the subword. - /// @param _byteLength The size of the subword. - /// @param _value The subword that updates _memWord. - function storeSubWord(State memory _state, uint64 _vaddr, uint64 _byteLength, uint64 _value) internal pure { - uint64 effAddr = _vaddr & arch.ADDRESS_MASK; - uint256 memProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1); - uint64 mem = MIPS64Memory.readMem(_state.memRoot, effAddr, memProofOffset); - - uint64 newMemVal = ins.updateSubWord(_vaddr, mem, _byteLength, _value); - _state.memRoot = MIPS64Memory.writeMem(effAddr, memProofOffset, newMemVal); - } - - function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) { - unchecked { - // Load state from memory offsets to reduce stack pressure - State memory state; - ThreadState memory thread; - assembly { - state := STATE_MEM_OFFSET - thread := TC_MEM_OFFSET - } - - // Load the syscall numbers and args from the registers - (uint64 syscall_no, uint64 a0, uint64 a1, uint64 a2) = sys.getSyscallArgs(thread.registers); - // Syscalls that are unimplemented but known return with v0=0 and v1=0 - uint64 v0 = 0; - uint64 v1 = 0; - - if (syscall_no == sys.SYS_MMAP) { - (v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap); - } else if (syscall_no == sys.SYS_BRK) { - // brk: Returns a fixed address for the program break at 0x40000000 - v0 = sys.PROGRAM_BREAK; - } else if (syscall_no == sys.SYS_CLONE) { - if (sys.VALID_SYS_CLONE_FLAGS != a0) { - state.exited = true; - state.exitCode = VMStatuses.PANIC.raw(); - return outputState(); - } - v0 = state.nextThreadID; - v1 = 0; - ThreadState memory newThread; - newThread.threadID = state.nextThreadID; - newThread.exitCode = 0; - newThread.exited = false; - newThread.pc = thread.nextPC; - newThread.nextPC = thread.nextPC + 4; - newThread.lo = thread.lo; - newThread.hi = thread.hi; - for (uint256 i; i < 32; i++) { - newThread.registers[i] = thread.registers[i]; - } - newThread.registers[29] = a1; // set stack pointer - // the child will perceive a 0 value as returned value instead, and no error - newThread.registers[2] = 0; - newThread.registers[7] = 0; - state.nextThreadID++; - - // Preempt this thread for the new one. But not before updating PCs - st.CpuScalars memory cpu0 = getCpuScalars(thread); - sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1); - setStateCpuScalars(thread, cpu0); - updateCurrentThreadRoot(); - pushThread(state, newThread); - return outputState(); - } else if (syscall_no == sys.SYS_EXIT_GROUP) { - // exit group: Sets the Exited and ExitCode states to true and argument 0. - state.exited = true; - state.exitCode = uint8(a0); - updateCurrentThreadRoot(); - return outputState(); - } else if (syscall_no == sys.SYS_READ) { - sys.SysReadParams memory args = sys.SysReadParams({ - a0: a0, - a1: a1, - a2: a2, - preimageKey: state.preimageKey, - preimageOffset: state.preimageOffset, - localContext: _localContext, - oracle: ORACLE, - proofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), - memRoot: state.memRoot - }); - // Encapsulate execution to avoid stack-too-deep error - (v0, v1) = execSysRead(state, args); - } else if (syscall_no == sys.SYS_WRITE) { - sys.SysWriteParams memory args = sys.SysWriteParams({ - _a0: a0, - _a1: a1, - _a2: a2, - _preimageKey: state.preimageKey, - _preimageOffset: state.preimageOffset, - _proofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), - _memRoot: state.memRoot - }); - (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite(args); - } else if (syscall_no == sys.SYS_FCNTL) { - (v0, v1) = sys.handleSysFcntl(a0, a1); - } else if (syscall_no == sys.SYS_GETTID) { - v0 = thread.threadID; - v1 = 0; - } else if (syscall_no == sys.SYS_EXIT) { - thread.exited = true; - thread.exitCode = uint8(a0); - if (lastThreadRemaining(state)) { - state.exited = true; - state.exitCode = uint8(a0); - } - updateCurrentThreadRoot(); - return outputState(); - } else if (syscall_no == sys.SYS_FUTEX) { - // args: a0 = addr, a1 = op, a2 = val, a3 = timeout - // Futex value is 32-bit, so clear the lower 2 bits to get an effective address targeting a 4-byte value - uint64 effFutexAddr = a0 & 0xFFFFFFFFFFFFFFFC; - if (a1 == sys.FUTEX_WAIT_PRIVATE) { - uint32 futexVal = getFutexValue(effFutexAddr); - uint32 targetVal = uint32(a2); - if (futexVal != targetVal) { - v0 = sys.EAGAIN; - v1 = sys.SYS_ERROR_SIGNAL; - } else { - return syscallYield(state, thread); - } - } else if (a1 == sys.FUTEX_WAKE_PRIVATE) { - return syscallYield(state, thread); - } else { - v0 = sys.EINVAL; - v1 = sys.SYS_ERROR_SIGNAL; - } - } else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) { - return syscallYield(state, thread); - } else if (syscall_no == sys.SYS_OPEN) { - v0 = sys.EBADF; - v1 = sys.SYS_ERROR_SIGNAL; - } else if (syscall_no == sys.SYS_CLOCKGETTIME) { - if (a0 == sys.CLOCK_GETTIME_REALTIME_FLAG || a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) { - v0 = 0; - v1 = 0; - uint64 secs = 0; - uint64 nsecs = 0; - if (a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) { - secs = uint64(state.step / sys.HZ); - nsecs = uint64((state.step % sys.HZ) * (1_000_000_000 / sys.HZ)); - } - uint64 effAddr = a1 & arch.ADDRESS_MASK; - // First verify the effAddr path - if (!MIPS64Memory.isValidProof( - state.memRoot, effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1) - )) { - revert InvalidMemoryProof(); - } - // Recompute the new root after updating effAddr - state.memRoot = - MIPS64Memory.writeMem(effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs); - handleMemoryUpdate(state, effAddr); - // Verify the second memory proof against the newly computed root - if (!MIPS64Memory.isValidProof( - state.memRoot, effAddr + 8, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 2) - )) { - revert InvalidSecondMemoryProof(); - } - state.memRoot = - MIPS64Memory.writeMem(effAddr + 8, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs); - handleMemoryUpdate(state, effAddr + 8); - } else { - v0 = sys.EINVAL; - v1 = sys.SYS_ERROR_SIGNAL; - } - } else if (syscall_no == sys.SYS_GETPID) { - v0 = 0; - v1 = 0; - } else if (syscall_no == sys.SYS_GETRANDOM) { - if (st.featuresForVersion(STATE_VERSION).supportWorkingSysGetRandom) { - (v0, v1, state.memRoot) = syscallGetRandom(state, a0, a1); - } - // Otherwise, ignored (noop) - } else if (syscall_no == sys.SYS_MUNMAP) { - // ignored - } else if (syscall_no == sys.SYS_MPROTECT) { - // ignored - } else if (syscall_no == sys.SYS_GETAFFINITY) { - // ignored - } else if (syscall_no == sys.SYS_MADVISE) { - // ignored - } else if (syscall_no == sys.SYS_RTSIGPROCMASK) { - // ignored - } else if (syscall_no == sys.SYS_SIGALTSTACK) { - // ignored - } else if (syscall_no == sys.SYS_RTSIGACTION) { - // ignored - } else if (syscall_no == sys.SYS_PRLIMIT64) { - // ignored - } else if (syscall_no == sys.SYS_CLOSE) { - // ignored - } else if (syscall_no == sys.SYS_PREAD64) { - // ignored - } else if (syscall_no == sys.SYS_STAT) { - // ignored - } else if (syscall_no == sys.SYS_FSTAT) { - // ignored - } else if (syscall_no == sys.SYS_OPENAT) { - // ignored - } else if (syscall_no == sys.SYS_READLINK) { - // ignored - } else if (syscall_no == sys.SYS_READLINKAT) { - // ignored - } else if (syscall_no == sys.SYS_IOCTL) { - // ignored - } else if (syscall_no == sys.SYS_EPOLLCREATE1) { - // ignored - } else if (syscall_no == sys.SYS_PIPE2) { - // ignored - } else if (syscall_no == sys.SYS_EPOLLCTL) { - // ignored - } else if (syscall_no == sys.SYS_EPOLLPWAIT) { - // ignored - } else if (syscall_no == sys.SYS_UNAME) { - // ignored - } else if (syscall_no == sys.SYS_GETUID) { - // ignored - } else if (syscall_no == sys.SYS_GETGID) { - // ignored - } else if (syscall_no == sys.SYS_MINCORE) { - // ignored - } else if (syscall_no == sys.SYS_TGKILL) { - // ignored - } else if (syscall_no == sys.SYS_SETITIMER) { - // ignored - } else if (syscall_no == sys.SYS_TIMERCREATE) { - // ignored - } else if (syscall_no == sys.SYS_TIMERSETTIME) { - // ignored - } else if (syscall_no == sys.SYS_TIMERDELETE) { - // ignored - } else if (syscall_no == sys.SYS_GETRLIMIT) { - // ignored - } else if (syscall_no == sys.SYS_LSEEK) { - // ignored - } else if (syscall_no == sys.SYS_EVENTFD2) { - // a0 = initial value, a1 = flags - // Validate flags - if (a1 & sys.EFD_NONBLOCK == 0) { - // The non-block flag was not set, but we only support non-block requests, so error - v0 = sys.EINVAL; - v1 = sys.SYS_ERROR_SIGNAL; - } else { - v0 = sys.FD_EVENTFD; - } - } else { - revert("MIPS64: unimplemented syscall"); - } - - st.CpuScalars memory cpu = getCpuScalars(thread); - sys.handleSyscallUpdates(cpu, thread.registers, v0, v1); - setStateCpuScalars(thread, cpu); - - updateCurrentThreadRoot(); - out_ = outputState(); - } - } - - function syscallGetRandom( - State memory _state, - uint64 _a0, - uint64 _a1 - ) - internal - pure - returns (uint64 v0_, uint64 v1_, bytes32 memRoot_) - { - uint64 effAddr = _a0 & arch.ADDRESS_MASK; - uint256 memProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1); - uint64 memVal = MIPS64Memory.readMem(_state.memRoot, effAddr, memProofOffset); - - // Generate some pseudorandom data - uint64 randomWord = splitmix64(_state.step); - - // Calculate number of bytes to write - uint64 targetByteIndex = _a0 - effAddr; - uint64 maxBytes = arch.WORD_SIZE_BYTES - targetByteIndex; - uint64 byteCount = _a1; - if (maxBytes < byteCount) { - byteCount = maxBytes; - } - - // Write random data into target memory location - uint64 randDataMask = uint64((1 << (byteCount * 8)) - 1); - // Shift left to align with index 0, then shift right to target correct index - randDataMask <<= (arch.WORD_SIZE_BYTES - byteCount) * 8; - randDataMask >>= targetByteIndex * 8; - uint64 newMemVal = (memVal & ~randDataMask) | (randomWord & randDataMask); - - memRoot_ = MIPS64Memory.writeMem(effAddr, memProofOffset, newMemVal); - handleMemoryUpdate(_state, effAddr); - - v0_ = byteCount; - v1_ = 0; - } - - // splitmix64 generates a pseudorandom 64-bit value. - // See canonical implementation: https://prng.di.unimi.it/splitmix64.c - function splitmix64(uint64 _seed) internal pure returns (uint64) { - unchecked { - uint64 z = _seed + 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - } - - function syscallYield(State memory _state, ThreadState memory _thread) internal returns (bytes32 out_) { - uint64 v0 = 0; - uint64 v1 = 0; - st.CpuScalars memory cpu = getCpuScalars(_thread); - sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1); - setStateCpuScalars(_thread, cpu); - preemptThread(_state, _thread); - - return outputState(); - } - - function execSysRead( - State memory _state, - sys.SysReadParams memory _args - ) - internal - view - returns (uint64 v0_, uint64 v1_) - { - bool memUpdated; - uint64 memAddr; - (v0_, v1_, _state.preimageOffset, _state.memRoot, memUpdated, memAddr) = sys.handleSysRead(_args); - if (memUpdated) { - handleMemoryUpdate(_state, memAddr); - } - } - - /// @notice Computes the hash of the MIPS state. - /// @return out_ The hashed MIPS state. - function outputState() internal returns (bytes32 out_) { - uint32 exited; - assembly { - // copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data - function copyMem(from, to, size) -> fromOut, toOut { - mstore(to, mload(add(from, sub(32, size)))) - fromOut := add(from, 32) - toOut := add(to, size) - } - - // From points to the MIPS State - let from := STATE_MEM_OFFSET - - // Copy to the free memory pointer - let start := mload(0x40) - let to := start - - // Copy state to free memory - from, to := copyMem(from, to, 32) // memRoot - from, to := copyMem(from, to, 32) // preimageKey - from, to := copyMem(from, to, 8) // preimageOffset - from, to := copyMem(from, to, 8) // heap - from, to := copyMem(from, to, 1) // llReservationStatus - from, to := copyMem(from, to, 8) // llAddress - from, to := copyMem(from, to, 8) // llOwnerThread - let exitCode := mload(from) - from, to := copyMem(from, to, 1) // exitCode - exited := mload(from) - from, to := copyMem(from, to, 1) // exited - from, to := copyMem(from, to, 8) // step - from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch - from, to := copyMem(from, to, 1) // traverseRight - from, to := copyMem(from, to, 32) // leftThreadStack - from, to := copyMem(from, to, 32) // rightThreadStack - from, to := copyMem(from, to, 8) // nextThreadID - - // Clean up end of memory - mstore(to, 0) - - // Log the resulting MIPS state, for debugging - log0(start, sub(to, start)) - - // Determine the VM status - let status := 0 - switch exited - case 1 { - switch exitCode - // VMStatusValid - case 0 { status := 0 } - // VMStatusInvalid - case 1 { status := 1 } - // VMStatusPanic - default { status := 2 } - } - // VMStatusUnfinished - default { status := 3 } - - // Compute the hash of the resulting MIPS state and set the status byte - out_ := keccak256(start, sub(to, start)) - out_ := or(and(not(shl(248, 0xFF)), out_), shl(248, status)) - } - - st.assertExitedIsValid(exited); - } - - /// @notice Updates the current thread stack root via inner thread root in calldata - function updateCurrentThreadRoot() internal pure { - State memory state; - ThreadState memory thread; - assembly { - state := STATE_MEM_OFFSET - thread := TC_MEM_OFFSET - } - bytes32 updatedRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), thread); - if (state.traverseRight) { - state.rightThreadStack = updatedRoot; - } else { - state.leftThreadStack = updatedRoot; - } - } - - /// @notice Preempts the current thread for another and updates the VM state. - /// It reads the inner thread root from calldata to update the current thread stack root. - function preemptThread( - State memory _state, - ThreadState memory _thread - ) - internal - pure - returns (bool changedDirections_) - { - // pop thread from the current stack and push to the other stack - if (_state.traverseRight) { - require(_state.rightThreadStack != EMPTY_THREAD_ROOT, "MIPS64: empty right thread stack"); - _state.rightThreadStack = loadCalldataInnerThreadRoot(); - _state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread); - } else { - require(_state.leftThreadStack != EMPTY_THREAD_ROOT, "MIPS64: empty left thread stack"); - _state.leftThreadStack = loadCalldataInnerThreadRoot(); - _state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread); - } - bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; - if (current == EMPTY_THREAD_ROOT) { - _state.traverseRight = !_state.traverseRight; - changedDirections_ = true; - } - _state.stepsSinceLastContextSwitch = 0; - } - - /// @notice Pushes a thread to the current thread stack. - function pushThread(State memory _state, ThreadState memory _thread) internal pure { - if (_state.traverseRight) { - _state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread); - } else { - _state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread); - } - _state.stepsSinceLastContextSwitch = 0; - } - - /// @notice Removes the current thread from the stack. - function popThread(State memory _state) internal pure { - if (_state.traverseRight) { - _state.rightThreadStack = loadCalldataInnerThreadRoot(); - } else { - _state.leftThreadStack = loadCalldataInnerThreadRoot(); - } - bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; - if (current == EMPTY_THREAD_ROOT) { - _state.traverseRight = !_state.traverseRight; - } - _state.stepsSinceLastContextSwitch = 0; - } - - /// @notice Returns true if the number of threads is 1 - function lastThreadRemaining(State memory _state) internal pure returns (bool out_) { - bytes32 inactiveStack = _state.traverseRight ? _state.leftThreadStack : _state.rightThreadStack; - bool currentStackIsAlmostEmpty = loadCalldataInnerThreadRoot() == EMPTY_THREAD_ROOT; - return inactiveStack == EMPTY_THREAD_ROOT && currentStackIsAlmostEmpty; - } - - function computeThreadRoot(bytes32 _currentRoot, ThreadState memory _thread) internal pure returns (bytes32 out_) { - // w_i = hash(w_0 ++ hash(thread)) - bytes32 threadRoot = outputThreadState(_thread); - out_ = keccak256(abi.encodePacked(_currentRoot, threadRoot)); - } - - function outputThreadState(ThreadState memory _thread) internal pure returns (bytes32 out_) { - assembly { - // copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data - function copyMem(from, to, size) -> fromOut, toOut { - mstore(to, mload(add(from, sub(32, size)))) - fromOut := add(from, 32) - toOut := add(to, size) - } - - // From points to the ThreadState - let from := _thread - - // Copy to the free memory pointer - let start := mload(0x40) - let to := start - - // Copy state to free memory - from, to := copyMem(from, to, 8) // threadID - from, to := copyMem(from, to, 1) // exitCode - from, to := copyMem(from, to, 1) // exited - from, to := copyMem(from, to, 8) // pc - from, to := copyMem(from, to, 8) // nextPC - from, to := copyMem(from, to, 8) // lo - from, to := copyMem(from, to, 8) // hi - from := mload(from) // offset to registers - // Copy registers - for { let i := 0 } lt(i, 32) { i := add(i, 1) } { from, to := copyMem(from, to, 8) } - - // Clean up end of memory - mstore(to, 0) - - // Compute the hash of the resulting ThreadState - out_ := keccak256(start, sub(to, start)) - } - } - - function getCpuScalars(ThreadState memory _tc) internal pure returns (st.CpuScalars memory cpu_) { - cpu_ = st.CpuScalars({ pc: _tc.pc, nextPC: _tc.nextPC, lo: _tc.lo, hi: _tc.hi }); - } - - function setStateCpuScalars(ThreadState memory _tc, st.CpuScalars memory _cpu) internal pure { - _tc.pc = _cpu.pc; - _tc.nextPC = _cpu.nextPC; - _tc.lo = _cpu.lo; - _tc.hi = _cpu.hi; - } - - /// @notice Validates the thread witness in calldata against the current thread. - function validateCalldataThreadWitness(State memory _state, ThreadState memory _thread) internal pure { - bytes32 witnessRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), _thread); - bytes32 expectedRoot = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; - require(expectedRoot == witnessRoot, "MIPS64: invalid thread witness"); - } - - /// @notice Sets the thread context from calldata. - function setThreadStateFromCalldata(ThreadState memory _thread) internal pure { - uint256 s = 0; - assembly { - s := calldatasize() - } - // verify we have enough calldata - require( - s >= (THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE), "MIPS64: insufficient calldata for thread witness" - ); - - unchecked { - assembly { - function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut { - // calldata is packed, thus starting left-aligned, shift-right to pad and right-align - let w := shr(shl(3, sub(32, size)), calldataload(callOffset)) - mstore(memOffset, w) - callOffsetOut := add(callOffset, size) - memOffsetOut := add(memOffset, 32) - } - - let c := THREAD_PROOF_OFFSET - let m := _thread - c, m := putField(c, m, 8) // threadID - c, m := putField(c, m, 1) // exitCode - c, m := putField(c, m, 1) // exited - c, m := putField(c, m, 8) // pc - c, m := putField(c, m, 8) // nextPC - c, m := putField(c, m, 8) // lo - c, m := putField(c, m, 8) // hi - m := mload(m) // offset to registers - // Unpack register calldata into memory - for { let i := 0 } lt(i, 32) { i := add(i, 1) } { c, m := putField(c, m, 8) } - } - } - } - - /// @notice Loads the inner root for the current thread hash onion from calldata. - function loadCalldataInnerThreadRoot() internal pure returns (bytes32 innerThreadRoot_) { - uint256 s = 0; - assembly { - s := calldatasize() - innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, PACKED_THREAD_STATE_SIZE)) - } - // verify we have enough calldata - require( - s >= (THREAD_PROOF_OFFSET + (PACKED_THREAD_STATE_SIZE + 32)), - "MIPS64: insufficient calldata for thread witness" - ); - } - - /// @notice Loads a 32-bit futex value at _vAddr - function getFutexValue(uint64 _vAddr) internal pure returns (uint32 out_) { - State memory state; - assembly { - state := STATE_MEM_OFFSET - } - - uint64 subword = loadSubWord(state, _vAddr, 4, false); - return uint32(subword); - } -} diff --git a/src/cannon/PreimageKeyLib.sol b/src/cannon/PreimageKeyLib.sol deleted file mode 100644 index c3f36cb88..000000000 --- a/src/cannon/PreimageKeyLib.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @title PreimageKeyLib -/// @notice Shared utilities for localizing local keys in the preimage oracle. -library PreimageKeyLib { - /// @notice Generates a context-specific local key for the given local data identifier. - /// @dev See `localize` for a description of the localization operation. - /// @param _ident The identifier of the local data. [0, 32) bytes in size. - /// @param _localContext The local context for the key. - /// @return key_ The context-specific local key. - function localizeIdent(uint256 _ident, bytes32 _localContext) internal view returns (bytes32 key_) { - assembly { - // Set the type byte in the given identifier to `1` (Local). We only care about - // the [1, 32) bytes in this value. - key_ := or(shl(248, 1), and(_ident, not(shl(248, 0xFF)))) - } - // Localize the key with the given local context. - key_ = localize(key_, _localContext); - } - - /// @notice Localizes a given local data key for the caller's context. - /// @dev The localization operation is defined as: - /// localize(k) = H(k .. sender .. local_context) & ~(0xFF << 248) | (0x01 << 248) - /// where H is the Keccak-256 hash function. - /// @param _key The local data key to localize. - /// @param _localContext The local context for the key. - /// @return localizedKey_ The localized local data key. - function localize(bytes32 _key, bytes32 _localContext) internal view returns (bytes32 localizedKey_) { - assembly { - // Grab the current free memory pointer to restore later. - let ptr := mload(0x40) - // Store the local data key and caller next to each other in memory for hashing. - mstore(0, _key) - mstore(0x20, caller()) - mstore(0x40, _localContext) - // Localize the key with the above `localize` operation. - localizedKey_ := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1)) - // Restore the free memory pointer. - mstore(0x40, ptr) - } - } - - /// @notice Computes and returns the key for a global keccak pre-image. - /// @param _preimage The pre-image. - /// @return key_ The pre-image key. - function keccak256PreimageKey(bytes memory _preimage) internal pure returns (bytes32 key_) { - assembly { - // Grab the size of the `_preimage` - let size := mload(_preimage) - - // Compute the pre-image keccak256 hash (aka the pre-image key) - let h := keccak256(add(_preimage, 0x20), size) - - // Mask out prefix byte, replace with type 2 byte - key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 2)) - } - } -} diff --git a/src/cannon/PreimageOracle.sol b/src/cannon/PreimageOracle.sol deleted file mode 100644 index 696933ea6..000000000 --- a/src/cannon/PreimageOracle.sol +++ /dev/null @@ -1,884 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Libraries -import { LibKeccak } from "lib/lib-keccak/contracts/lib/LibKeccak.sol"; -import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; -import { - PartOffsetOOB, - InvalidProof, - InvalidPreimage, - InvalidInputSize, - WrongStartingBlock, - StatesNotContiguous, - PostStateMatches, - TreeSizeOverflow, - AlreadyFinalized, - ActiveProposal, - BadProposal, - NotInitialized, - AlreadyInitialized, - NotEOA, - InsufficientBond, - BondTransferFailed -} from "src/cannon/libraries/CannonErrors.sol"; -import { LPPMetaData } from "src/cannon/libraries/CannonTypes.sol"; - -// Interfaces -import { ISemver } from "interfaces/universal/ISemver.sol"; - -/// @title PreimageOracle -/// @notice A contract for storing permissioned pre-images. -/// @custom:attribution Solady -/// @custom:attribution Beacon Deposit Contract <0x00000000219ab540356cbb839cbe05303d7705fa> -contract PreimageOracle is ISemver { - //////////////////////////////////////////////////////////////// - // Constants & Immutables // - //////////////////////////////////////////////////////////////// - - /// @notice The duration of the large preimage proposal challenge period. - uint256 internal immutable CHALLENGE_PERIOD; - /// @notice The minimum size of a preimage that can be proposed in the large preimage path. - uint256 internal immutable MIN_LPP_SIZE_BYTES; - /// @notice The minimum bond size for large preimage proposals. - uint256 public constant MIN_BOND_SIZE = 0.25 ether; - /// @notice The depth of the keccak256 merkle tree. Supports up to 65,536 keccak blocks, or ~8.91MB preimages. - uint256 public constant KECCAK_TREE_DEPTH = 16; - /// @notice The maximum number of keccak blocks that can fit into the merkle tree. - uint256 public constant MAX_LEAF_COUNT = 2 ** KECCAK_TREE_DEPTH - 1; - /// @notice The reserved gas for precompile call setup. - uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000; - - /// @notice The semantic version of the Preimage Oracle contract. - /// @custom:semver 1.1.4 - string public constant version = "1.1.4"; - - //////////////////////////////////////////////////////////////// - // Authorized Preimage Parts // - //////////////////////////////////////////////////////////////// - - /// @notice Mapping of pre-image keys to pre-image lengths. - mapping(bytes32 => uint256) public preimageLengths; - /// @notice Mapping of pre-image keys to pre-image offsets to pre-image parts. - mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts; - /// @notice Mapping of pre-image keys to pre-image part offsets to preimage preparedness. - mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk; - - //////////////////////////////////////////////////////////////// - // Large Preimage Proposals // - //////////////////////////////////////////////////////////////// - - /// @notice A raw leaf of the large preimage proposal merkle tree. - struct Leaf { - /// @notice The input absorbed for the block, exactly 136 bytes. - bytes input; - /// @notice The index of the block in the absorption process. - uint256 index; - /// @notice The hash of the internal state after absorbing the input. - bytes32 stateCommitment; - } - - /// @notice Unpacked keys for large preimage proposals. - struct LargePreimageProposalKeys { - /// @notice The claimant of the large preimage proposal. - address claimant; - /// @notice The UUID of the large preimage proposal. - uint256 uuid; - } - - /// @notice Static padding hashes. These values are persisted in storage, but are entirely immutable - /// after the constructor's execution. - bytes32[KECCAK_TREE_DEPTH] public zeroHashes; - /// @notice Append-only array of large preimage proposals for off-chain reference. - LargePreimageProposalKeys[] public proposals; - /// @notice Mapping of claimants to proposal UUIDs to the current branch path of the merkleization process. - mapping(address => mapping(uint256 => bytes32[KECCAK_TREE_DEPTH])) public proposalBranches; - /// @notice Mapping of claimants to proposal UUIDs to the timestamp of creation of the proposal as well as the - /// challenged status. - mapping(address => mapping(uint256 => LPPMetaData)) public proposalMetadata; - /// @notice Mapping of claimants to proposal UUIDs to bond amounts. - mapping(address => mapping(uint256 => uint256)) public proposalBonds; - /// @notice Mapping of claimants to proposal UUIDs to the preimage part picked up during the absorbtion process. - mapping(address => mapping(uint256 => bytes32)) public proposalParts; - /// @notice Mapping of claimants to proposal UUIDs to blocks which leaves were added to the merkle tree. - mapping(address => mapping(uint256 => uint64[])) public proposalBlocks; - - //////////////////////////////////////////////////////////////// - // Constructor // - //////////////////////////////////////////////////////////////// - - constructor(uint256 _minProposalSize, uint256 _challengePeriod) { - MIN_LPP_SIZE_BYTES = _minProposalSize; - CHALLENGE_PERIOD = _challengePeriod; - - // Make sure challenge period fits within uint64 so that it can safely be used within the - // FaultDisputeGame contract to compute clock extensions. Adding this check is simpler than - // changing the existing contract ABI. - require(_challengePeriod <= type(uint64).max, "PreimageOracle: challenge period too large"); - - // Compute hashes in empty sparse Merkle tree. The first hash is not set, and kept as zero as the identity. - for (uint256 height = 0; height < KECCAK_TREE_DEPTH - 1; height++) { - zeroHashes[height + 1] = keccak256(abi.encodePacked(zeroHashes[height], zeroHashes[height])); - } - } - - //////////////////////////////////////////////////////////////// - // Standard Preimage Route (External) // - //////////////////////////////////////////////////////////////// - - /// @notice Reads a preimage from the oracle. - /// @param _key The key of the preimage to read. - /// @param _offset The offset of the preimage to read. - /// @return dat_ The preimage data. - /// @return datLen_ The length of the preimage data. - function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_) { - require(preimagePartOk[_key][_offset], "pre-image must exist"); - - // Calculate the length of the pre-image data - // Add 8 for the length-prefix part - datLen_ = 32; - uint256 length = preimageLengths[_key]; - if (_offset + 32 >= length + 8) { - datLen_ = length + 8 - _offset; - } - - // Retrieve the pre-image data - dat_ = preimageParts[_key][_offset]; - } - - /// @notice Loads local data parts into the preimage oracle. - /// @param _ident The identifier of the local data. - /// @param _localContext The local key context for the preimage oracle. Optionally, can be set as a constant - /// if the caller only requires one set of local keys. - /// @param _word The local data word. - /// @param _size The number of bytes in `_word` to load. - /// @param _partOffset The offset of the local data part to write to the oracle. - /// @dev The local data parts are loaded into the preimage oracle under the context - /// of the caller - no other account can write to the caller's context - /// specific data. - /// - /// There are 5 local data identifiers: - /// ┌────────────┬────────────────────────┐ - /// │ Identifier │ Data │ - /// ├────────────┼────────────────────────┤ - /// │ 1 │ L1 Head Hash (bytes32) │ - /// │ 2 │ Output Root (bytes32) │ - /// │ 3 │ Root Claim (bytes32) │ - /// │ 4 │ L2 Block Number (u64) │ - /// │ 5 │ Chain ID (u64) │ - /// └────────────┴────────────────────────┘ - function loadLocalData( - uint256 _ident, - bytes32 _localContext, - bytes32 _word, - uint256 _size, - uint256 _partOffset - ) - external - returns (bytes32 key_) - { - // Compute the localized key from the given local identifier. - key_ = PreimageKeyLib.localizeIdent(_ident, _localContext); - - // Revert if the given part offset is not within bounds. - if (_partOffset >= _size + 8 || _size > 32) { - revert PartOffsetOOB(); - } - - // Prepare the local data part at the given offset - bytes32 part; - assembly { - // Clean the memory in [0x20, 0x40) - mstore(0x20, 0x00) - - // Store the full local data in scratch space. - mstore(0x00, shl(192, _size)) - mstore(0x08, _word) - - // Prepare the local data part at the requested offset. - part := mload(_partOffset) - } - - // Store the first part with `_partOffset`. - preimagePartOk[key_][_partOffset] = true; - preimageParts[key_][_partOffset] = part; - // Assign the length of the preimage at the localized key. - preimageLengths[key_] = _size; - } - - /// @notice Prepares a preimage to be read by keccak256 key, starting at the given offset and up to 32 bytes - /// (clipped at preimage length, if out of data). - /// @param _partOffset The offset of the preimage to read. - /// @param _preimage The preimage data. - function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external { - uint256 size; - bytes32 key; - bytes32 part; - assembly { - // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44 - size := calldataload(0x44) - - // revert if part offset >= size+8 (i.e. parts must be within bounds) - if iszero(lt(_partOffset, add(size, 8))) { - // Store "PartOffsetOOB()" - mstore(0x00, 0xfe254987) - // Revert with "PartOffsetOOB()" - revert(0x1c, 0x04) - } - // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory. - let ptr := 0x80 - // put size as big-endian uint64 at start of pre-image - mstore(ptr, shl(192, size)) - ptr := add(ptr, 0x08) - // copy preimage payload into memory so we can hash and read it. - calldatacopy(ptr, _preimage.offset, size) - // Note that it includes the 8-byte big-endian uint64 length prefix. - // this will be zero-padded at the end, since memory at end is clean. - part := mload(add(sub(ptr, 0x08), _partOffset)) - let h := keccak256(ptr, size) // compute preimage keccak256 hash - // mask out prefix byte, replace with type 2 byte - key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x02)) - } - preimagePartOk[key][_partOffset] = true; - preimageParts[key][_partOffset] = part; - preimageLengths[key] = size; - } - - /// @notice Prepares a preimage to be read by sha256 key, starting at the given offset and up to 32 bytes - /// (clipped at preimage length, if out of data). - /// @param _partOffset The offset of the preimage to read. - /// @param _preimage The preimage data. - function loadSha256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external { - uint256 size; - bytes32 key; - bytes32 part; - assembly { - // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44 - size := calldataload(0x44) - - // revert if part offset >= size+8 (i.e. parts must be within bounds) - if iszero(lt(_partOffset, add(size, 8))) { - // Store "PartOffsetOOB()" - mstore(0, 0xfe254987) - // Revert with "PartOffsetOOB()" - revert(0x1c, 4) - } - // we leave solidity slots 0x40 and 0x60 untouched, - // and everything after as scratch-memory. - let ptr := 0x80 - // put size as big-endian uint64 at start of pre-image - mstore(ptr, shl(192, size)) - ptr := add(ptr, 8) - // copy preimage payload into memory so we can hash and read it. - calldatacopy(ptr, _preimage.offset, size) - // Note that it includes the 8-byte big-endian uint64 length prefix. - // this will be zero-padded at the end, since memory at end is clean. - part := mload(add(sub(ptr, 8), _partOffset)) - - // compute SHA2-256 hash with pre-compile - let success := - staticcall( - gas(), // Forward all available gas - 0x02, // Address of SHA-256 precompile - ptr, // Start of input data in memory - size, // Size of input data - 0, // Store output in scratch memory - 0x20 // Output is always 32 bytes - ) - // Check if the staticcall succeeded - if iszero(success) { revert(0, 0) } - let h := mload(0) // get return data - // mask out prefix byte, replace with type 4 byte - key := or(and(h, not(shl(248, 0xFF))), shl(248, 4)) - } - preimagePartOk[key][_partOffset] = true; - preimageParts[key][_partOffset] = part; - preimageLengths[key] = size; - } - - /// @notice Verifies that `p(_z) = _y` given `_commitment` that corresponds to the polynomial `p(x)` and a KZG - // proof. The value `y` is the pre-image, and the preimage key is `5 ++ keccak256(_commitment ++ z)[1:]`. - /// @param _z Big endian point value. Part of the preimage key. - /// @param _y Big endian point value. The preimage for the key. - /// @param _commitment The commitment to the polynomial. 48 bytes, part of the preimage key. - /// @param _proof The KZG proof, part of the preimage key. - /// @param _partOffset The offset of the preimage to store. - function loadBlobPreimagePart( - uint256 _z, - uint256 _y, - bytes calldata _commitment, - bytes calldata _proof, - uint256 _partOffset - ) - external - { - bytes32 key; - bytes32 part; - assembly { - // Compute the versioned hash. The SHA2 hash of the 48 byte commitment is masked with the version byte, - // which is currently 1. https://eips.ethereum.org/EIPS/eip-4844#parameters - // SAFETY: We're only reading 48 bytes from `_commitment` into scratch space, so we're not reading into the - // free memory ptr region. Since the exact number of btyes that is copied into scratch space is - // the same size as the hash input, there's no concern of dirty memory being read into the hash - // input. - calldatacopy(0x00, _commitment.offset, 0x30) - let success := staticcall(gas(), 0x02, 0x00, 0x30, 0x00, 0x20) - if iszero(success) { - // Store the "ShaFailed()" error selector. - mstore(0x00, 0xf9112969) - // revert with "ShaFailed()" - revert(0x1C, 0x04) - } - // Set the `VERSIONED_HASH_VERSION_KZG` byte = 1 in the high-order byte of the hash. - let versionedHash := or(and(mload(0x00), not(shl(248, 0xFF))), shl(248, 0x01)) - - // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory. - let ptr := 0x80 - - // Load the inputs for the point evaluation precompile into memory. The inputs to the point evaluation - // precompile are packed, and not supposed to be ABI-encoded. - mstore(ptr, versionedHash) - mstore(add(ptr, 0x20), _z) - mstore(add(ptr, 0x40), _y) - calldatacopy(add(ptr, 0x60), _commitment.offset, 0x30) - calldatacopy(add(ptr, 0x90), _proof.offset, 0x30) - - // Verify the KZG proof by calling the point evaluation precompile. If the proof is invalid, the precompile - // will revert. - success := staticcall( - gas(), // forward all gas - 0x0A, // point evaluation precompile address - ptr, // input ptr - 0xC0, // input size = 192 bytes - 0x00, // output ptr - 0x00 // output size - ) - if iszero(success) { - // Store the "InvalidProof()" error selector. - mstore(0x00, 0x09bde339) - // revert with "InvalidProof()" - revert(0x1C, 0x04) - } - - // revert if part offset >= 32+8 (i.e. parts must be within bounds) - if iszero(lt(_partOffset, 0x28)) { - // Store "PartOffsetOOB()" - mstore(0x00, 0xfe254987) - // Revert with "PartOffsetOOB()" - revert(0x1C, 0x04) - } - // Clean the word at `ptr + 0x28` to ensure that data out of bounds of the preimage is zero, if the part - // offset requires a partial read. - mstore(add(ptr, 0x28), 0x00) - // put size (32) as a big-endian uint64 at start of pre-image - mstore(ptr, shl(192, 0x20)) - // copy preimage payload into memory so we can hash and read it. - mstore(add(ptr, 0x08), _y) - // Note that it includes the 8-byte big-endian uint64 length prefix. This will be zero-padded at the end, - // since memory at end is guaranteed to be clean. - part := mload(add(ptr, _partOffset)) - - // Compute the key: `keccak256(commitment ++ z)`. Since the exact number of btyes that is copied into - // scratch space is the same size as the hash input, there's no concern of dirty memory being read into - // the hash input. - calldatacopy(ptr, _commitment.offset, 0x30) - mstore(add(ptr, 0x30), _z) - let h := keccak256(ptr, 0x50) - // mask out prefix byte, replace with type 5 byte - key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x05)) - } - preimagePartOk[key][_partOffset] = true; - preimageParts[key][_partOffset] = part; - preimageLengths[key] = 32; - } - - /// @notice Prepares a precompile result to be read by a precompile key for the specified offset. - /// The precompile result data is a concatenation of the precompile call status byte and its return data. - /// The preimage key is `6 ++ keccak256(precompile ++ input)[1:]`. - /// @param _partOffset The offset of the precompile result being loaded. - /// @param _precompile The precompile address - /// @param _requiredGas The gas required to fully execute an L1 precompile. - /// @param _input The input to the precompile call. - function loadPrecompilePreimagePart( - uint256 _partOffset, - address _precompile, - uint64 _requiredGas, - bytes calldata _input - ) - external - { - bytes32 res; - bytes32 key; - bytes32 part; - uint256 size; - assembly { - // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory. - let ptr := 0x80 - - // copy precompile address, requiredGas, and input into memory to compute the key - mstore(ptr, shl(96, _precompile)) - mstore(add(ptr, 20), shl(192, _requiredGas)) - calldatacopy(add(28, ptr), _input.offset, _input.length) - // compute the hash - let h := keccak256(ptr, add(28, _input.length)) - // mask out prefix byte, replace with type 6 byte - key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06)) - - // Check if the precompile call has at least the required gas. - // This assumes there are no further memory expansion costs until after the staticall on the precompile - // Also assumes that the gas expended in setting up the staticcall is less than PRECOMPILE_CALL_RESERVED_GAS - // require(gas() >= (requiredGas * 64 / 63) + reservedGas) - if lt(mul(gas(), 63), add(mul(_requiredGas, 64), mul(PRECOMPILE_CALL_RESERVED_GAS, 63))) { - // Store "NotEnoughGas()" - mstore(0, 0xdd629f86) - revert(0x1c, 4) - } - - // Call the precompile to get the result. - // SAFETY: Given the above gas check, the staticall cannot fail due to insufficient gas. - res := staticcall( - gas(), // forward all gas - _precompile, - add(28, ptr), // input ptr - _input.length, - 0x0, // Unused as we don't copy anything - 0x00 // don't copy anything - ) - - size := add(1, returndatasize()) - // revert if part offset >= size+8 (i.e. parts must be within bounds) - if iszero(lt(_partOffset, add(size, 8))) { - // Store "PartOffsetOOB()" - mstore(0, 0xfe254987) - // Revert with "PartOffsetOOB()" - revert(0x1c, 4) - } - - // Reuse the `ptr` to store the preimage part: - // put size as big-endian uint64 at start of pre-image - mstore(ptr, shl(192, size)) - ptr := add(ptr, 0x08) - - // write precompile result status to the first byte of `ptr` - mstore8(ptr, res) - // write precompile return data to the rest of `ptr` - returndatacopy(add(ptr, 0x01), 0x0, returndatasize()) - - // compute part given ofset - part := mload(add(sub(ptr, 0x08), _partOffset)) - } - preimagePartOk[key][_partOffset] = true; - preimageParts[key][_partOffset] = part; - preimageLengths[key] = size; - } - - //////////////////////////////////////////////////////////////// - // Large Preimage Proposals (External) // - //////////////////////////////////////////////////////////////// - - /// @notice Returns the length of the `proposals` array - function proposalCount() external view returns (uint256 count_) { - count_ = proposals.length; - } - - /// @notice Returns the length of the array with the block numbers of `addLeavesLPP` calls for a given large - /// preimage proposal. - function proposalBlocksLen(address _claimant, uint256 _uuid) external view returns (uint256 len_) { - len_ = proposalBlocks[_claimant][_uuid].length; - } - - /// @notice Returns the length of the large preimage proposal challenge period. - function challengePeriod() external view returns (uint256 challengePeriod_) { - challengePeriod_ = CHALLENGE_PERIOD; - } - - /// @notice Returns the minimum size (in bytes) of a large preimage proposal. - function minProposalSize() external view returns (uint256 minProposalSize_) { - minProposalSize_ = MIN_LPP_SIZE_BYTES; - } - - /// @notice Initialize a large preimage proposal. Must be called before adding any leaves. - function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable { - // The bond provided must be at least `MIN_BOND_SIZE`. - if (msg.value < MIN_BOND_SIZE) revert InsufficientBond(); - - // Legacy check, no longer technically required but keeping for now. Can be bypassed using - // EIP-7702. Challenger loads this information directly from the proposals array instead of - // looking at transaction calldata. - if (msg.sender != tx.origin) revert NotEOA(); - - // The part offset must be within the bounds of the claimed size + 8. - if (_partOffset >= _claimedSize + 8) revert PartOffsetOOB(); - - // The claimed size must be at least `MIN_LPP_SIZE_BYTES`. - if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize(); - - // Initialize the proposal metadata. - LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; - - // Revert if the proposal has already been initialized. 0-size preimages are *not* allowed. - if (metaData.claimedSize() != 0) revert AlreadyInitialized(); - - proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize); - proposals.push(LargePreimageProposalKeys(msg.sender, _uuid)); - - // Assign the bond to the proposal. - proposalBonds[msg.sender][_uuid] = msg.value; - } - - /// @notice Adds a contiguous list of keccak state matrices to the merkle tree. - function addLeavesLPP( - uint256 _uuid, - uint256 _inputStartBlock, - bytes calldata _input, - bytes32[] calldata _stateCommitments, - bool _finalize - ) - external - { - // If we're finalizing, pad the input for the submitter. If not, copy the input into memory verbatim. - bytes memory input; - if (_finalize) { - input = LibKeccak.pad(_input); - } else { - input = _input; - } - - // Pull storage variables onto the stack / into memory for operations. - bytes32[KECCAK_TREE_DEPTH] memory branch = proposalBranches[msg.sender][_uuid]; - LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; - uint256 blocksProcessed = metaData.blocksProcessed(); - - // Legacy check, no longer technically required but keeping for now. Can be bypassed using - // EIP-7702. Challenger loads this information from the log at the end of this function - // instead of looking at transaction calldata. - if (msg.sender != tx.origin) revert NotEOA(); - - // Revert if the proposal has not been initialized. 0-size preimages are *not* allowed. - if (metaData.claimedSize() == 0) revert NotInitialized(); - - // Revert if the proposal has already been finalized. No leaves can be added after this point. - if (metaData.timestamp() != 0) revert AlreadyFinalized(); - - // Revert if the starting block is not the next block to be added. This is to aid submitters in ensuring that - // they don't corrupt an in-progress proposal by submitting input out of order. - if (blocksProcessed != _inputStartBlock) revert WrongStartingBlock(); - - // Attempt to extract the preimage part from the input data, if the part offset is present in the current - // chunk of input. This function has side effects, and will persist the preimage part to the caller's large - // preimage proposal storage if the part offset is present in the input data. - _extractPreimagePart(_input, _uuid, _finalize, metaData); - - assembly { - let inputLen := mload(input) - let inputPtr := add(input, 0x20) - - // The input length must be a multiple of 136 bytes - // The input length / 136 must be equal to the number of state commitments. - if or(mod(inputLen, 136), iszero(eq(_stateCommitments.length, div(inputLen, 136)))) { - // Store "InvalidInputSize()" error selector - mstore(0x00, 0x7b1daf1) - revert(0x1C, 0x04) - } - - // Allocate a hashing buffer the size of the leaf preimage. - let hashBuf := mload(0x40) - mstore(0x40, add(hashBuf, 0xC8)) - - for { let i := 0 } lt(i, inputLen) { i := add(i, 136) } { - // Copy the leaf preimage into the hashing buffer. - let inputStartPtr := add(inputPtr, i) - mstore(hashBuf, mload(inputStartPtr)) - mstore(add(hashBuf, 0x20), mload(add(inputStartPtr, 0x20))) - mstore(add(hashBuf, 0x40), mload(add(inputStartPtr, 0x40))) - mstore(add(hashBuf, 0x60), mload(add(inputStartPtr, 0x60))) - mstore(add(hashBuf, 0x80), mload(add(inputStartPtr, 0x80))) - mstore(add(hashBuf, 136), blocksProcessed) - mstore(add(hashBuf, 168), calldataload(add(_stateCommitments.offset, shl(0x05, div(i, 136))))) - - // Hash the leaf preimage to get the node to add. - let node := keccak256(hashBuf, 0xC8) - - // Increment the number of blocks processed. - blocksProcessed := add(blocksProcessed, 0x01) - - // Add the node to the tree. - let size := blocksProcessed - for { let height := 0x00 } lt(height, shl(0x05, KECCAK_TREE_DEPTH)) { height := add(height, 0x20) } { - if and(size, 0x01) { - mstore(add(branch, height), node) - break - } - - // Hash the node at `height` in the branch and the node together. - mstore(0x00, mload(add(branch, height))) - mstore(0x20, node) - node := keccak256(0x00, 0x40) - size := shr(0x01, size) - } - } - } - - // Do not allow for posting preimages larger than the merkle tree can support. The incremental merkle tree - // algorithm only supports 2**height - 1 leaves, the right most leaf must always be kept empty. - // Reference: https://daejunpark.github.io/papers/deposit.pdf - Page 10, Section 5.1. - if (blocksProcessed > MAX_LEAF_COUNT) revert TreeSizeOverflow(); - - // Update the proposal metadata to include the number of blocks processed and total bytes processed. - metaData = metaData.setBlocksProcessed(uint32(blocksProcessed)) - .setBytesProcessed(uint32(_input.length + metaData.bytesProcessed())); - // If the proposal is being finalized, set the timestamp to the current block timestamp. This begins the - // challenge period, which must be waited out before the proposal can be finalized. - if (_finalize) { - metaData = metaData.setTimestamp(uint64(block.timestamp)); - - // If the number of bytes processed is not equal to the claimed size, the proposal cannot be finalized. - if (metaData.bytesProcessed() != metaData.claimedSize()) revert InvalidInputSize(); - } - - // Perist the latest branch to storage. - proposalBranches[msg.sender][_uuid] = branch; - // Persist the block number that these leaves were added in. This assists off-chain observers in reconstructing - // the proposal merkle tree by querying block bodies. - proposalBlocks[msg.sender][_uuid].push(uint64(block.number)); - // Persist the updated metadata to storage. - proposalMetadata[msg.sender][_uuid] = metaData; - - // Clobber memory and `log0` all calldata. This is safe because there is no execution afterwards within - // this callframe. - assembly { - mstore(0x00, shl(96, caller())) - calldatacopy(0x14, 0x00, calldatasize()) - log0(0x00, add(0x14, calldatasize())) - } - } - - /// @notice Challenge a keccak256 block that was committed to in the merkle tree. - function challengeLPP( - address _claimant, - uint256 _uuid, - LibKeccak.StateMatrix memory _stateMatrix, - Leaf calldata _preState, - bytes32[] calldata _preStateProof, - Leaf calldata _postState, - bytes32[] calldata _postStateProof - ) - external - { - // Verify that both leaves are present in the merkle tree. - bytes32 root = getTreeRootLPP(_claimant, _uuid); - if (!(_verify(_preStateProof, root, _preState.index, _hashLeaf(_preState)) - && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState)))) revert InvalidProof(); - - // Verify that the prestate passed matches the intermediate state claimed in the leaf. - if (keccak256(abi.encode(_stateMatrix)) != _preState.stateCommitment) revert InvalidPreimage(); - - // Verify that the pre/post state are contiguous. - if (_preState.index + 1 != _postState.index) revert StatesNotContiguous(); - - // Absorb and permute the input bytes. - LibKeccak.absorb(_stateMatrix, _postState.input); - LibKeccak.permutation(_stateMatrix); - - // Verify that the post state hash doesn't match the expected hash. - if (keccak256(abi.encode(_stateMatrix)) == _postState.stateCommitment) revert PostStateMatches(); - - // Mark the keccak claim as countered. - proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true); - - // Pay out the bond to the challenger. - _payoutBond(_claimant, _uuid, msg.sender); - } - - /// @notice Challenge the first keccak256 block that was absorbed. - function challengeFirstLPP( - address _claimant, - uint256 _uuid, - Leaf calldata _postState, - bytes32[] calldata _postStateProof - ) - external - { - // Verify that the leaf is present in the merkle tree. - bytes32 root = getTreeRootLPP(_claimant, _uuid); - if (!_verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))) revert InvalidProof(); - - // The poststate index must be 0 in order to challenge it with this function. - if (_postState.index != 0) revert StatesNotContiguous(); - - // Absorb and permute the input bytes into a fresh state matrix. - LibKeccak.StateMatrix memory stateMatrix; - LibKeccak.absorb(stateMatrix, _postState.input); - LibKeccak.permutation(stateMatrix); - - // Verify that the post state hash doesn't match the expected hash. - if (keccak256(abi.encode(stateMatrix)) == _postState.stateCommitment) revert PostStateMatches(); - - // Mark the keccak claim as countered. - proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true); - - // Pay out the bond to the challenger. - _payoutBond(_claimant, _uuid, msg.sender); - } - - /// @notice Finalize a large preimage proposal after the challenge period has passed. - function squeezeLPP( - address _claimant, - uint256 _uuid, - LibKeccak.StateMatrix memory _stateMatrix, - Leaf calldata _preState, - bytes32[] calldata _preStateProof, - Leaf calldata _postState, - bytes32[] calldata _postStateProof - ) - external - { - LPPMetaData metaData = proposalMetadata[_claimant][_uuid]; - - // Check if the proposal was countered. - if (metaData.countered()) revert BadProposal(); - - // Check if the proposal has been finalized at all. - if (metaData.timestamp() == 0) revert ActiveProposal(); - - // Check if the challenge period has passed since the proposal was finalized. - if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal(); - - // Verify that both leaves are present in the merkle tree. - bytes32 root = getTreeRootLPP(_claimant, _uuid); - if (!(_verify(_preStateProof, root, _preState.index, _hashLeaf(_preState)) - && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState)))) revert InvalidProof(); - - // Verify that the prestate passed matches the intermediate state claimed in the leaf. - if (keccak256(abi.encode(_stateMatrix)) != _preState.stateCommitment) revert InvalidPreimage(); - - // Verify that the pre/post state are contiguous. - if (_preState.index + 1 != _postState.index || _postState.index != metaData.blocksProcessed() - 1) { - revert StatesNotContiguous(); - } - - // Absorb and permute the input bytes. We perform no final verification on the state matrix here, since the - // proposal has passed the challenge period and is considered valid. - LibKeccak.absorb(_stateMatrix, _postState.input); - LibKeccak.permutation(_stateMatrix); - bytes32 finalDigest = LibKeccak.squeeze(_stateMatrix); - assembly { - finalDigest := or(and(finalDigest, not(shl(248, 0xFF))), shl(248, 0x02)) - } - - // Write the preimage part to the authorized preimage parts mapping. - uint256 partOffset = metaData.partOffset(); - preimagePartOk[finalDigest][partOffset] = true; - preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid]; - preimageLengths[finalDigest] = metaData.claimedSize(); - - // Pay out the bond to the claimant. - _payoutBond(_claimant, _uuid, _claimant); - } - - /// @notice Gets the current merkle root of the large preimage proposal tree. - function getTreeRootLPP(address _owner, uint256 _uuid) public view returns (bytes32 treeRoot_) { - uint256 size = proposalMetadata[_owner][_uuid].blocksProcessed(); - for (uint256 height = 0; height < KECCAK_TREE_DEPTH; height++) { - if ((size & 1) == 1) { - treeRoot_ = keccak256(abi.encode(proposalBranches[_owner][_uuid][height], treeRoot_)); - } else { - treeRoot_ = keccak256(abi.encode(treeRoot_, zeroHashes[height])); - } - size >>= 1; - } - } - - /// @notice Attempts to persist the preimage part to the caller's large preimage proposal storage, if the preimage - /// part is present in the input data being posted. - /// @param _input The portion of the preimage being posted. - /// @param _uuid The UUID of the large preimage proposal. - /// @param _finalize Whether or not the proposal is being finalized in the current call. - /// @param _metaData The metadata of the large preimage proposal. - function _extractPreimagePart( - bytes calldata _input, - uint256 _uuid, - bool _finalize, - LPPMetaData _metaData - ) - internal - { - uint256 offset = _metaData.partOffset(); - uint256 claimedSize = _metaData.claimedSize(); - uint256 currentSize = _metaData.bytesProcessed(); - - // Check if the part offset is present in the input data being posted. If it is, assign the part to the mapping. - if (offset < 8 && currentSize == 0) { - bytes32 preimagePart; - assembly { - mstore(0x00, shl(192, claimedSize)) - mstore(0x08, calldataload(_input.offset)) - preimagePart := mload(offset) - } - proposalParts[msg.sender][_uuid] = preimagePart; - } else if (offset >= 8 && (offset = offset - 8) >= currentSize && offset < currentSize + _input.length) { - uint256 relativeOffset = offset - currentSize; - - // Revert if the full preimage part is not available in the data we're absorbing. The submitter must - // supply data that contains the full preimage part so that no partial preimage parts are stored in the - // oracle. Partial parts are *only* allowed at the tail end of the preimage, where no more data is available - // to be absorbed. - if (relativeOffset + 32 >= _input.length && !_finalize) revert PartOffsetOOB(); - - // If the preimage part is in the data we're about to absorb, persist the part to the caller's large - // preimaage metadata. - bytes32 preimagePart; - assembly { - preimagePart := calldataload(add(_input.offset, relativeOffset)) - } - proposalParts[msg.sender][_uuid] = preimagePart; - } - } - - /// @notice Check if leaf` at `index` verifies against the Merkle `root` and `branch`. - /// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_valid_merkle_branch - function _verify( - bytes32[] calldata _proof, - bytes32 _root, - uint256 _index, - bytes32 _leaf - ) - internal - pure - returns (bool isValid_) - { - /// @solidity memory-safe-assembly - assembly { - function hashTwo(a, b) -> hash { - mstore(0x00, a) - mstore(0x20, b) - hash := keccak256(0x00, 0x40) - } - - let value := _leaf - for { let i := 0x00 } lt(i, KECCAK_TREE_DEPTH) { i := add(i, 0x01) } { - let branchValue := calldataload(add(_proof.offset, shl(0x05, i))) - - switch and(shr(i, _index), 0x01) - case 1 { value := hashTwo(branchValue, value) } - default { value := hashTwo(value, branchValue) } - } - - isValid_ := eq(value, _root) - } - } - - /// @notice Pay out a proposal's bond. Reverts if the transfer fails. - function _payoutBond(address _claimant, uint256 _uuid, address _to) internal { - // Pay out the bond to the claimant. - uint256 bond = proposalBonds[_claimant][_uuid]; - proposalBonds[_claimant][_uuid] = 0; - (bool success,) = _to.call{ value: bond }(""); - if (!success) revert BondTransferFailed(); - } - - /// @notice Hashes leaf data for the preimage proposals tree - function _hashLeaf(Leaf memory _leaf) internal pure returns (bytes32 leaf_) { - leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment)); - } -} diff --git a/src/cannon/libraries/CannonErrors.sol b/src/cannon/libraries/CannonErrors.sol deleted file mode 100644 index f2402e160..000000000 --- a/src/cannon/libraries/CannonErrors.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @notice Thrown when a passed part offset is out of bounds. -error PartOffsetOOB(); - -/// @notice Thrown when insufficient gas is provided when loading precompile preimages. -error NotEnoughGas(); - -/// @notice Thrown when a merkle proof fails to verify. -error InvalidProof(); - -/// @notice Thrown when the prestate preimage doesn't match the claimed preimage. -error InvalidPreimage(); - -/// @notice Thrown when a leaf with an invalid input size is added. -error InvalidInputSize(); - -/// @notice Thrown when data is submitted out of order in a large preimage proposal. -error WrongStartingBlock(); - -/// @notice Thrown when the pre and post states passed aren't contiguous. -error StatesNotContiguous(); - -/// @notice Thrown when the permutation yields the expected result. -error PostStateMatches(); - -/// @notice Thrown when the preimage is too large to fit in the tree. -error TreeSizeOverflow(); - -/// @notice Thrown when the preimage proposal has already been finalized. -error AlreadyFinalized(); - -/// @notice Thrown when the proposal has not matured past the challenge period. -error ActiveProposal(); - -/// @notice Thrown when attempting to finalize a proposal that has been challenged. -error BadProposal(); - -/// @notice Thrown when attempting to add leaves to a preimage proposal that has not been initialized. -error NotInitialized(); - -/// @notice Thrown when attempting to re-initialize an existing large preimage proposal. -error AlreadyInitialized(); - -/// @notice Thrown when the caller of a function is not an EOA. -error NotEOA(); - -/// @notice Thrown when an insufficient bond is provided for a large preimage proposal. -error InsufficientBond(); - -/// @notice Thrown when a bond transfer fails. -error BondTransferFailed(); - -/// @notice Thrown when the value of the exited boolean is not 0 or 1. -error InvalidExitedValue(); - -/// @notice Thrown when reading an invalid memory -error InvalidMemoryProof(); - -/// @notice Thrown when the second memory location is invalid -error InvalidSecondMemoryProof(); - -/// @notice Thrown when an RMW instruction is expected, but a different instruction is provided. -error InvalidRMWInstruction(); - -/// @notice Thrown when the state version set is not supported. -error UnsupportedStateVersion(); diff --git a/src/cannon/libraries/CannonTypes.sol b/src/cannon/libraries/CannonTypes.sol deleted file mode 100644 index 26d0a17ed..000000000 --- a/src/cannon/libraries/CannonTypes.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -using LPPMetadataLib for LPPMetaData global; - -/// @notice Packed LPP metadata. -/// ┌─────────────┬────────────────────────────────────────────┐ -/// │ Bit Offsets │ Description │ -/// ├─────────────┼────────────────────────────────────────────┤ -/// │ [0, 64) │ Timestamp (Finalized - All data available) │ -/// │ [64, 96) │ Part Offset │ -/// │ [96, 128) │ Claimed Size │ -/// │ [128, 160) │ Blocks Processed (Inclusive of Padding) │ -/// │ [160, 192) │ Bytes Processed (Non-inclusive of Padding) │ -/// │ [192, 256) │ Countered │ -/// └─────────────┴────────────────────────────────────────────┘ -type LPPMetaData is bytes32; - -/// @notice LPP metadata UDT extension functions. -library LPPMetadataLib { - uint256 private constant U64_MASK = 0xFFFFFFFFFFFFFFFF; - uint256 private constant U32_MASK = 0xFFFFFFFF; - - function setTimestamp(LPPMetaData _self, uint64 _timestamp) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(shl(192, _timestamp), and(_self, not(shl(192, U64_MASK)))) - } - } - - function setPartOffset(LPPMetaData _self, uint32 _partOffset) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(shl(160, _partOffset), and(_self, not(shl(160, U32_MASK)))) - } - } - - function setClaimedSize(LPPMetaData _self, uint32 _claimedSize) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(shl(128, _claimedSize), and(_self, not(shl(128, U32_MASK)))) - } - } - - function setBlocksProcessed(LPPMetaData _self, uint32 _blocksProcessed) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(shl(96, _blocksProcessed), and(_self, not(shl(96, U32_MASK)))) - } - } - - function setBytesProcessed(LPPMetaData _self, uint32 _bytesProcessed) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(shl(64, _bytesProcessed), and(_self, not(shl(64, U32_MASK)))) - } - } - - function setCountered(LPPMetaData _self, bool _countered) internal pure returns (LPPMetaData self_) { - assembly { - self_ := or(_countered, and(_self, not(U64_MASK))) - } - } - - function timestamp(LPPMetaData _self) internal pure returns (uint64 timestamp_) { - assembly { - timestamp_ := shr(192, _self) - } - } - - function partOffset(LPPMetaData _self) internal pure returns (uint64 partOffset_) { - assembly { - partOffset_ := and(shr(160, _self), U32_MASK) - } - } - - function claimedSize(LPPMetaData _self) internal pure returns (uint32 claimedSize_) { - assembly { - claimedSize_ := and(shr(128, _self), U32_MASK) - } - } - - function blocksProcessed(LPPMetaData _self) internal pure returns (uint32 blocksProcessed_) { - assembly { - blocksProcessed_ := and(shr(96, _self), U32_MASK) - } - } - - function bytesProcessed(LPPMetaData _self) internal pure returns (uint32 bytesProcessed_) { - assembly { - bytesProcessed_ := and(shr(64, _self), U32_MASK) - } - } - - function countered(LPPMetaData _self) internal pure returns (bool countered_) { - assembly { - countered_ := and(_self, U64_MASK) - } - } -} diff --git a/src/cannon/libraries/MIPS64Arch.sol b/src/cannon/libraries/MIPS64Arch.sol deleted file mode 100644 index a1d689e73..000000000 --- a/src/cannon/libraries/MIPS64Arch.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -library MIPS64Arch { - uint64 internal constant WORD_SIZE = 64; - uint64 internal constant WORD_SIZE_BYTES = 8; - uint64 internal constant EXT_MASK = 0x7; - uint64 internal constant ADDRESS_MASK = 0xFFFFFFFFFFFFFFF8; -} diff --git a/src/cannon/libraries/MIPS64Instructions.sol b/src/cannon/libraries/MIPS64Instructions.sol deleted file mode 100644 index b5484440c..000000000 --- a/src/cannon/libraries/MIPS64Instructions.sol +++ /dev/null @@ -1,946 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; -import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; -import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; - -library MIPS64Instructions { - uint32 internal constant OP_LOAD_LINKED = 0x30; - uint32 internal constant OP_STORE_CONDITIONAL = 0x38; - uint32 internal constant OP_LOAD_LINKED64 = 0x34; - uint32 internal constant OP_STORE_CONDITIONAL64 = 0x3C; - uint32 internal constant OP_LOAD_DOUBLE_LEFT = 0x1A; - uint32 internal constant OP_LOAD_DOUBLE_RIGHT = 0x1B; - uint32 internal constant REG_RA = 31; - uint64 internal constant U64_MASK = 0xFFFFFFFFFFFFFFFF; - uint32 internal constant U32_MASK = 0xFFffFFff; - - error InvalidPC(); - - struct CoreStepLogicParams { - /// @param opcode The opcode value parsed from insn. - st.CpuScalars cpu; - /// @param registers The CPU registers. - uint64[32] registers; - /// @param memRoot The current merkle root of the memory. - bytes32 memRoot; - /// @param memProofOffset The offset in calldata specify where the memory merkle proof is located. - uint256 memProofOffset; - /// @param insn The current MIPS instruction at the pc. - uint32 insn; - /// @param cpu The CPU scalar fields. - uint32 opcode; - /// @param fun The function value parsed from insn. - uint32 fun; - } - - struct ExecuteMipsInstructionParams { - /// @param insn The current MIPS instruction at the pc. - uint32 insn; - /// @param opcode The opcode value parsed from insn. - uint32 opcode; - /// @param fun The function value parsed from insn. - uint32 fun; - /// @param rs The source register 1 value. - uint64 rs; - /// @param rt The source register 2 value. - uint64 rt; - /// @param mem The value fetched from memory for the current instruction. - uint64 mem; - } - - /// @param _pc The program counter. - /// @param _memRoot The current memory root. - /// @param _insnProofOffset The calldata offset of the memory proof for the current instruction. - /// @return insn_ The current 32-bit instruction at the pc. - /// @return opcode_ The opcode value parsed from insn_. - /// @return fun_ The function value parsed from insn_. - function getInstructionDetails( - uint64 _pc, - bytes32 _memRoot, - uint256 _insnProofOffset - ) - internal - pure - returns (uint32 insn_, uint32 opcode_, uint32 fun_) - { - unchecked { - if (_pc & 0x3 != 0) { - revert InvalidPC(); - } - uint64 word = MIPS64Memory.readMem(_memRoot, _pc & arch.ADDRESS_MASK, _insnProofOffset); - insn_ = uint32(selectSubWord(_pc, word, 4, false)); - opcode_ = insn_ >> 26; // First 6-bits - fun_ = insn_ & 0x3f; // Last 6-bits - - return (insn_, opcode_, fun_); - } - } - - /// @notice Execute core MIPS step logic. - /// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged. - /// @return memUpdated_ True if memory was modified. - /// @return effMemAddr_ Holds the effective address that was updated if memUpdated_ is true. - function execMipsCoreStepLogic(CoreStepLogicParams memory _args) - internal - pure - returns (bytes32 newMemRoot_, bool memUpdated_, uint64 effMemAddr_) - { - unchecked { - newMemRoot_ = _args.memRoot; - memUpdated_ = false; - effMemAddr_ = 0; - - // j-type j/jal - if (_args.opcode == 2 || _args.opcode == 3) { - // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset - uint64 target = (_args.cpu.nextPC & signExtend(0xF0000000, 32)) | uint64((_args.insn & 0x03FFFFFF) << 2); - handleJump(_args, _args.opcode == 2 ? 0 : REG_RA, target); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - - // register fetch - uint64 rs = 0; // source register 1 value - uint64 rt = 0; // source register 2 / temp value - uint64 rtReg = uint64((_args.insn >> 16) & 0x1F); - - // R-type or I-type (stores rt) - rs = _args.registers[(_args.insn >> 21) & 0x1F]; - uint64 rdReg = rtReg; - - // 64-bit opcodes lwu, ldl, ldr - if (_args.opcode == 0x27 || _args.opcode == 0x1A || _args.opcode == 0x1B) { - rt = _args.registers[rtReg]; - rdReg = rtReg; - } else if (_args.opcode == 0 || _args.opcode == 0x1c) { - // R-type (stores rd) - rt = _args.registers[rtReg]; - rdReg = uint64((_args.insn >> 11) & 0x1F); - } else if (_args.opcode < 0x20) { - // rt is SignExtImm - // don't sign extend for andi, ori, xori - if (_args.opcode == 0xC || _args.opcode == 0xD || _args.opcode == 0xe) { - // ZeroExtImm - rt = uint64(_args.insn & 0xFFFF); - } else { - // SignExtImm - rt = signExtendImmediate(_args.insn); - } - } else if (_args.opcode >= 0x28 || _args.opcode == 0x22 || _args.opcode == 0x26) { - // store rt value with store - rt = _args.registers[rtReg]; - - // store actual rt with lwl and lwr - rdReg = rtReg; - } - - if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) { - handleBranch({ - _cpu: _args.cpu, - _registers: _args.registers, - _opcode: _args.opcode, - _insn: _args.insn, - _rtReg: rtReg, - _rs: rs - }); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - - uint64 storeAddr = U64_MASK; - // memory fetch (all I-type) - // we do the load for stores also - uint64 mem = 0; - if (_args.opcode >= 0x20 || _args.opcode == OP_LOAD_DOUBLE_LEFT || _args.opcode == OP_LOAD_DOUBLE_RIGHT) { - // M[R[rs]+SignExtImm] - rs += signExtendImmediate(_args.insn); - uint64 addr = rs & arch.ADDRESS_MASK; - mem = MIPS64Memory.readMem(_args.memRoot, addr, _args.memProofOffset); - if (_args.opcode >= 0x28) { - // store for 32-bit - // for 64-bit: ld (0x37) is the only non-store opcode >= 0x28 - if (_args.opcode != 0x37) { - // store - storeAddr = addr; - // store opcodes don't write back to a register - rdReg = 0; - } - } - } - - // ALU - // Note: swr outputs more than 8 bytes without the u64_mask - ExecuteMipsInstructionParams memory params = ExecuteMipsInstructionParams({ - insn: _args.insn, opcode: _args.opcode, fun: _args.fun, rs: rs, rt: rt, mem: mem - }); - uint64 val = executeMipsInstruction(params) & U64_MASK; - - uint64 funSel = 0x20; - if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < funSel) { - if (_args.fun == 8 || _args.fun == 9) { - // jr/jalr - handleJump(_args, _args.fun == 8 ? 0 : rdReg, rs); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - - if (_args.fun == 0xa) { - // movz - handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - if (_args.fun == 0xb) { - // movn - handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - - // lo and hi registers - // can write back - if (_args.fun >= 0x10 && _args.fun < funSel) { - handleHiLo({ - _cpu: _args.cpu, - _registers: _args.registers, - _fun: _args.fun, - _rs: rs, - _rt: rt, - _storeReg: rdReg - }); - return (newMemRoot_, memUpdated_, effMemAddr_); - } - } - - // write memory - if (storeAddr != U64_MASK) { - newMemRoot_ = MIPS64Memory.writeMem(storeAddr, _args.memProofOffset, val); - memUpdated_ = true; - effMemAddr_ = storeAddr; - } - - // write back the value to destination register - handleRd(_args.cpu, _args.registers, rdReg, val, true); - - return (newMemRoot_, memUpdated_, effMemAddr_); - } - } - - function signExtendImmediate(uint32 _insn) internal pure returns (uint64 offset_) { - unchecked { - return signExtend(_insn & 0xFFFF, 16); - } - } - - /// @notice Execute an instruction. - function executeMipsInstruction(ExecuteMipsInstructionParams memory _args) internal pure returns (uint64 out_) { - uint32 insn = _args.insn; - uint32 opcode = _args.opcode; - uint32 fun = _args.fun; - uint64 rs = _args.rs; - uint64 rt = _args.rt; - uint64 mem = _args.mem; - unchecked { - if (opcode == 0 || (opcode >= 8 && opcode < 0xF) || opcode == 0x18 || opcode == 0x19) { - assembly { - // transform ArithLogI to SPECIAL - switch opcode - // addi - case 0x8 { fun := 0x20 } - // addiu - case 0x9 { fun := 0x21 } - // stli - case 0xA { fun := 0x2A } - // sltiu - case 0xB { fun := 0x2B } - // andi - case 0xC { fun := 0x24 } - // ori - case 0xD { fun := 0x25 } - // xori - case 0xE { fun := 0x26 } - // daddi - case 0x18 { fun := 0x2C } - // daddiu - case 0x19 { fun := 0x2D } - } - - // sll - if (fun == 0x00) { - uint32 shiftAmt = (insn >> 6) & 0x1F; - return signExtend((rt << shiftAmt) & U32_MASK, 32); - } - // srl - else if (fun == 0x02) { - return signExtend((rt & U32_MASK) >> ((insn >> 6) & 0x1F), 32); - } - // sra - else if (fun == 0x03) { - uint32 shamt = (insn >> 6) & 0x1F; - return signExtend((rt & U32_MASK) >> shamt, 32 - shamt); - } - // sllv - else if (fun == 0x04) { - uint64 shiftAmt = rs & 0x1F; - return signExtend((rt << shiftAmt) & U32_MASK, 32); - } - // srlv - else if (fun == 0x6) { - return signExtend((rt & U32_MASK) >> (rs & 0x1F), 32); - } - // srav - else if (fun == 0x07) { - // shamt here is different than the typical shamt which comes from the - // instruction itself, here it comes from the rs register - uint64 shamt = rs & 0x1F; - return signExtend((rt & U32_MASK) >> shamt, 32 - shamt); - } - // functs in range [0x8, 0x1b] are handled specially by other functions - // Explicitly enumerate each funct in range to reduce code diff against Go Vm - // jr - else if (fun == 0x08) { - return rs; - } - // jalr - else if (fun == 0x09) { - return rs; - } - // movz - else if (fun == 0x0a) { - return rs; - } - // movn - else if (fun == 0x0b) { - return rs; - } - // syscall - else if (fun == 0x0c) { - return rs; - } - // 0x0d - break not supported - // sync - else if (fun == 0x0f) { - return rs; - } - // mfhi - else if (fun == 0x10) { - return rs; - } - // mthi - else if (fun == 0x11) { - return rs; - } - // mflo - else if (fun == 0x12) { - return rs; - } - // mtlo - else if (fun == 0x13) { - return rs; - } - // dsllv - else if (fun == 0x14) { - return rt; - } - // dsrlv - else if (fun == 0x16) { - return rt; - } - // dsrav - else if (fun == 0x17) { - return rt; - } - // mult - else if (fun == 0x18) { - return rs; - } - // multu - else if (fun == 0x19) { - return rs; - } - // div - else if (fun == 0x1a) { - return rs; - } - // divu - else if (fun == 0x1b) { - return rs; - } - // dmult - else if (fun == 0x1c) { - return rs; - } - // dmultu - else if (fun == 0x1d) { - return rs; - } - // ddiv - else if (fun == 0x1e) { - return rs; - } - // ddivu - else if (fun == 0x1f) { - return rs; - } - // The rest includes transformed R-type arith imm instructions - // add - else if (fun == 0x20) { - return signExtend(uint64(uint32(rs) + uint32(rt)), 32); - } - // addu - else if (fun == 0x21) { - return signExtend(uint64(uint32(rs) + uint32(rt)), 32); - } - // sub - else if (fun == 0x22) { - return signExtend(uint64(uint32(rs) - uint32(rt)), 32); - } - // subu - else if (fun == 0x23) { - return signExtend(uint64(uint32(rs) - uint32(rt)), 32); - } - // and - else if (fun == 0x24) { - return (rs & rt); - } - // or - else if (fun == 0x25) { - return (rs | rt); - } - // xor - else if (fun == 0x26) { - return (rs ^ rt); - } - // nor - else if (fun == 0x27) { - return ~(rs | rt); - } - // slti - else if (fun == 0x2a) { - return int64(rs) < int64(rt) ? 1 : 0; - } - // sltiu - else if (fun == 0x2b) { - return rs < rt ? 1 : 0; - } - // dadd - else if (fun == 0x2c) { - return (rs + rt); - } - // daddu - else if (fun == 0x2d) { - return (rs + rt); - } - // dsub - else if (fun == 0x2e) { - return (rs - rt); - } - // dsubu - else if (fun == 0x2f) { - return (rs - rt); - } - // dsll - else if (fun == 0x38) { - return rt << ((insn >> 6) & 0x1f); - } - // dsrl - else if (fun == 0x3A) { - return rt >> ((insn >> 6) & 0x1f); - } - // dsra - else if (fun == 0x3B) { - return uint64(int64(rt) >> ((insn >> 6) & 0x1f)); - } - // dsll32 - else if (fun == 0x3c) { - return rt << (((insn >> 6) & 0x1f) + 32); - } - // dsrl32 - else if (fun == 0x3e) { - return rt >> (((insn >> 6) & 0x1f) + 32); - } - // dsra32 - else if (fun == 0x3f) { - return uint64(int64(rt) >> (((insn >> 6) & 0x1f) + 32)); - } else { - revert("MIPS64: invalid instruction"); - } - } else { - // SPECIAL2 - if (opcode == 0x1C) { - // mul - if (fun == 0x2) { - return signExtend(uint32(int32(uint32(rs)) * int32(uint32(rt))), 32); - } - // clz, clo - else if (fun == 0x20 || fun == 0x21) { - if (fun == 0x20) { - rs = ~rs; - } - uint32 i = 0; - while (rs & 0x80000000 != 0) { - i++; - rs <<= 1; - } - return i; - } - // dclz, dclo - else if (fun == 0x24 || fun == 0x25) { - if (fun == 0x24) { - rs = ~rs; - } - uint32 i = 0; - while (rs & 0x80000000_00000000 != 0) { - i++; - rs <<= 1; - } - return i; - } - } - // lui - else if (opcode == 0x0F) { - return signExtend(rt << 16, 32); - } - // lb - else if (opcode == 0x20) { - return selectSubWord(rs, mem, 1, true); - } - // lh - else if (opcode == 0x21) { - return selectSubWord(rs, mem, 2, true); - } - // lwl - else if (opcode == 0x22) { - uint32 w = uint32(selectSubWord(rs, mem, 4, false)); - uint32 val = w << uint32((rs & 3) * 8); - uint64 mask = uint64(U32_MASK << uint32((rs & 3) * 8)); - return signExtend(((rt & ~mask) | uint64(val)) & U32_MASK, 32); - } - // lw - else if (opcode == 0x23) { - return selectSubWord(rs, mem, 4, true); - } - // lbu - else if (opcode == 0x24) { - return selectSubWord(rs, mem, 1, false); - } - // lhu - else if (opcode == 0x25) { - return selectSubWord(rs, mem, 2, false); - } - // lwr - else if (opcode == 0x26) { - uint32 w = uint32(selectSubWord(rs, mem, 4, false)); - uint32 val = w >> (24 - (rs & 3) * 8); - uint32 mask = U32_MASK >> (24 - (rs & 3) * 8); - uint64 lwrResult = (uint32(rt) & ~mask) | val; - if (rs & 3 == 3) { - // loaded bit 31 - return signExtend(uint64(lwrResult), 32); - } else { - // NOTE: cannon64 implementation specific: We leave the upper word untouched - uint64 rtMask = 0xFF_FF_FF_FF_00_00_00_00; - return ((rt & rtMask) | uint64(lwrResult)); - } - } - // sb - else if (opcode == 0x28) { - return updateSubWord(rs, mem, 1, rt); - } - // sh - else if (opcode == 0x29) { - return updateSubWord(rs, mem, 2, rt); - } - // swl - else if (opcode == 0x2a) { - uint64 sr = (rs & 3) << 3; - uint64 val = ((rt & U32_MASK) >> sr) << (32 - ((rs & 0x4) << 3)); - uint64 mask = (uint64(U32_MASK) >> sr) << (32 - ((rs & 0x4) << 3)); - return (mem & ~mask) | val; - } - // sw - else if (opcode == 0x2b) { - return updateSubWord(rs, mem, 4, rt); - } - // swr - else if (opcode == 0x2e) { - uint32 w = uint32(selectSubWord(rs, mem, 4, false)); - uint64 val = rt << (24 - (rs & 3) * 8); - uint64 mask = U32_MASK << uint32(24 - (rs & 3) * 8); - uint64 swrResult = (w & ~mask) | uint32(val); - return updateSubWord(rs, mem, 4, swrResult); - } - // MIPS64 - // ldl - else if (opcode == 0x1a) { - uint64 sl = (rs & 0x7) << 3; - uint64 val = mem << sl; - uint64 mask = U64_MASK << sl; - return (val | (rt & ~mask)); - } - // ldr - else if (opcode == 0x1b) { - uint64 sr = 56 - ((rs & 0x7) << 3); - uint64 val = mem >> sr; - uint64 mask = U64_MASK << (64 - sr); - return (val | (rt & mask)); - } - // lwu - else if (opcode == 0x27) { - return ((mem >> (32 - ((rs & 0x4) << 3))) & U32_MASK); - } - // sdl - else if (opcode == 0x2c) { - uint64 sr = (rs & 0x7) << 3; - uint64 val = rt >> sr; - uint64 mask = U64_MASK >> sr; - return (val | (mem & ~mask)); - } - // sdr - else if (opcode == 0x2d) { - uint64 sl = 56 - ((rs & 0x7) << 3); - uint64 val = rt << sl; - uint64 mask = U64_MASK << sl; - return (val | (mem & ~mask)); - } - // ld - else if (opcode == 0x37) { - return mem; - } - // sd - else if (opcode == 0x3F) { - return rt; - } else { - revert("MIPS64: invalid instruction"); - } - } - revert("MIPS64: invalid instruction"); - } - } - - /// @notice Extends the value leftwards with its most significant bit (sign extension). - function signExtend(uint64 _dat, uint64 _idx) internal pure returns (uint64 out_) { - unchecked { - bool isSigned = (_dat >> (_idx - 1)) & 1 != 0; - uint256 signed = ((1 << (arch.WORD_SIZE - _idx)) - 1) << _idx; - uint256 mask = (1 << _idx) - 1; - return uint64(_dat & mask | (isSigned ? signed : 0)); - } - } - - /// @notice Handles a branch instruction, updating the MIPS state PC where needed. - /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. - /// @param _registers Holds the current state of the cpu registers. - /// @param _opcode The opcode of the branch instruction. - /// @param _insn The instruction to be executed. - /// @param _rtReg The register to be used for the branch. - /// @param _rs The register to be compared with the branch register. - function handleBranch( - st.CpuScalars memory _cpu, - uint64[32] memory _registers, - uint32 _opcode, - uint32 _insn, - uint64 _rtReg, - uint64 _rs - ) - internal - pure - { - unchecked { - bool shouldBranch = false; - - if (_cpu.nextPC != _cpu.pc + 4) { - revert("MIPS64: branch in delay slot"); - } - - // beq/bne: Branch on equal / not equal - if (_opcode == 4 || _opcode == 5) { - uint64 rt = _registers[_rtReg]; - shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); - } - // blez: Branches if instruction is less than or equal to zero - else if (_opcode == 6) { - shouldBranch = int64(_rs) <= 0; - } - // bgtz: Branches if instruction is greater than zero - else if (_opcode == 7) { - shouldBranch = int64(_rs) > 0; - } - // bltz/bgez: Branch on less than zero / greater than or equal to zero - else if (_opcode == 1) { - // regimm - uint32 rtv = ((_insn >> 16) & 0x1F); - if (rtv == 0) { - shouldBranch = int64(_rs) < 0; - } - // bltzal - if (rtv == 0x10) { - shouldBranch = int64(_rs) < 0; - _registers[REG_RA] = _cpu.pc + 8; // always set regardless of branch taken - } - if (rtv == 1) { - shouldBranch = int64(_rs) >= 0; - } - // bgezal (i.e. bal mnemonic) - if (rtv == 0x11) { - shouldBranch = int64(_rs) >= 0; - _registers[REG_RA] = _cpu.pc + 8; // always set regardless of branch taken - } - } - - // Update the state's previous PC - uint64 prevPC = _cpu.pc; - - // Execute the delay slot first - _cpu.pc = _cpu.nextPC; - - // If we should branch, update the PC to the branch target - // Otherwise, proceed to the next instruction - if (shouldBranch) { - _cpu.nextPC = prevPC + 4 + (signExtend(_insn & 0xFFFF, 16) << 2); - } else { - _cpu.nextPC = _cpu.nextPC + 4; - } - } - } - - /// @notice Handles HI and LO register instructions. It also additionally handles doubleword variable shift - /// operations - /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. - /// @param _registers Holds the current state of the cpu registers. - /// @param _fun The function code of the instruction. - /// @param _rs The value of the RS register. - /// @param _rt The value of the RT register. - /// @param _storeReg The register to store the result in. - function handleHiLo( - st.CpuScalars memory _cpu, - uint64[32] memory _registers, - uint32 _fun, - uint64 _rs, - uint64 _rt, - uint64 _storeReg - ) - internal - pure - { - unchecked { - uint64 val = 0; - - // mfhi: Move the contents of the HI register into the destination - if (_fun == 0x10) { - val = _cpu.hi; - } - // mthi: Move the contents of the source into the HI register - else if (_fun == 0x11) { - _cpu.hi = _rs; - } - // mflo: Move the contents of the LO register into the destination - else if (_fun == 0x12) { - val = _cpu.lo; - } - // mtlo: Move the contents of the source into the LO register - else if (_fun == 0x13) { - _cpu.lo = _rs; - } - // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_fun == 0x18) { - uint64 acc = uint64(int64(int32(uint32(_rs))) * int64(int32(uint32(_rt)))); - _cpu.hi = signExtend(uint64(acc >> 32), 32); - _cpu.lo = signExtend(uint64(uint32(acc)), 32); - } - // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_fun == 0x19) { - uint64 acc = uint64(uint32(_rs)) * uint64(uint32(_rt)); - _cpu.hi = signExtend(uint64(acc >> 32), 32); - _cpu.lo = signExtend(uint64(uint32(acc)), 32); - } - // div: Divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_fun == 0x1a) { - if (uint32(_rt) == 0) { - revert("MIPS64: division by zero"); - } - _cpu.hi = signExtend(uint32(int32(uint32(_rs)) % int32(uint32(_rt))), 32); - _cpu.lo = signExtend(uint32(int32(uint32(_rs)) / int32(uint32(_rt))), 32); - } - // divu: Unsigned divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_fun == 0x1b) { - if (uint32(_rt) == 0) { - revert("MIPS64: division by zero"); - } - _cpu.hi = signExtend(uint64(uint32(_rs) % uint32(_rt)), 32); - _cpu.lo = signExtend(uint64(uint32(_rs) / uint32(_rt)), 32); - } - // dsllv - else if (_fun == 0x14) { - val = _rt << (_rs & 0x3F); - } - // dsrlv - else if (_fun == 0x16) { - val = _rt >> (_rs & 0x3F); - } - // dsrav - else if (_fun == 0x17) { - val = uint64(int64(_rt) >> (_rs & 0x3F)); - } - // dmult - else if (_fun == 0x1c) { - int128 res = int128(int64(_rs)) * int128(int64(_rt)); - _cpu.hi = uint64(int64(res >> 64)); - _cpu.lo = uint64(uint128(res) & U64_MASK); - } - // dmultu - else if (_fun == 0x1d) { - uint128 res = uint128(_rs) * uint128(_rt); - _cpu.hi = uint64(res >> 64); - _cpu.lo = uint64(res); - } - // ddiv - else if (_fun == 0x1e) { - if (_rt == 0) { - revert("MIPS64: division by zero"); - } - _cpu.hi = uint64(int64(_rs) % int64(_rt)); - _cpu.lo = uint64(int64(_rs) / int64(_rt)); - } - // ddivu - else if (_fun == 0x1f) { - if (_rt == 0) { - revert("MIPS64: division by zero"); - } - _cpu.hi = _rs % _rt; - _cpu.lo = _rs / _rt; - } - - // Store the result in the destination register, if applicable - if (_storeReg != 0) { - _registers[_storeReg] = val; - } - - // Update the PC - _cpu.pc = _cpu.nextPC; - _cpu.nextPC = _cpu.nextPC + 4; - } - } - - /// @notice Handles a jump instruction, updating the MIPS state PC where needed. - /// @dev The _cpuAndRegisters is stored in memory to avoid stack limit issues. - /// @param _cpuAndRegisters Holds the state of cpu scalars (pc, nextPC, hi, lo) and the current state of the cpu - /// registers. - /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. - /// @param _dest The destination to jump to. - function handleJump(CoreStepLogicParams memory _cpuAndRegisters, uint64 _linkReg, uint64 _dest) internal pure { - unchecked { - if (_cpuAndRegisters.cpu.nextPC != _cpuAndRegisters.cpu.pc + 4) { - revert("MIPS64: jump in delay slot"); - } - - // Update the next PC to the jump destination. - uint64 prevPC = _cpuAndRegisters.cpu.pc; - _cpuAndRegisters.cpu.pc = _cpuAndRegisters.cpu.nextPC; - _cpuAndRegisters.cpu.nextPC = _dest; - - // Update the link-register to the instruction after the delay slot instruction. - if (_linkReg != 0) { - _cpuAndRegisters.registers[_linkReg] = prevPC + 8; - } - } - } - - /// @notice Handles a storing a value into a register. - /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. - /// @param _registers Holds the current state of the cpu registers. - /// @param _storeReg The register to store the value into. - /// @param _val The value to store. - /// @param _conditional Whether or not the store is conditional. - function handleRd( - st.CpuScalars memory _cpu, - uint64[32] memory _registers, - uint64 _storeReg, - uint64 _val, - bool _conditional - ) - internal - pure - { - unchecked { - // The destination register must be valid. - require(_storeReg < 32, "MIPS64: valid register"); - - // Never write to reg 0, and it can be conditional (movz, movn). - if (_storeReg != 0 && _conditional) { - _registers[_storeReg] = _val; - } - - // Update the PC. - _cpu.pc = _cpu.nextPC; - _cpu.nextPC = _cpu.nextPC + 4; - } - } - - /// @notice Selects a subword of byteLength size contained in memWord based on the low-order bits of vaddr - /// @param _vaddr The virtual address of the the subword. - /// @param _memWord The full word to select a subword from. - /// @param _byteLength The size of the subword. - /// @param _signExtend Whether to sign extend the selected subwrod. - function selectSubWord( - uint64 _vaddr, - uint64 _memWord, - uint64 _byteLength, - bool _signExtend - ) - internal - pure - returns (uint64 retval_) - { - (uint64 dataMask, uint64 bitOffset, uint64 bitLength) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); - retval_ = (_memWord >> bitOffset) & dataMask; - if (_signExtend) { - retval_ = signExtend(retval_, bitLength); - } - return retval_; - } - - /// @notice Returns a word that has been updated by the specified subword at bit positions determined by the virtual - /// address - /// @param _vaddr The virtual address of the subword. - /// @param _memWord The full word to update. - /// @param _byteLength The size of the subword. - /// @param _value The subword that updates _memWord. - function updateSubWord( - uint64 _vaddr, - uint64 _memWord, - uint64 _byteLength, - uint64 _value - ) - internal - pure - returns (uint64 word_) - { - (uint64 dataMask, uint64 bitOffset,) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); - uint64 subWordValue = dataMask & _value; - uint64 memUpdateMask = dataMask << bitOffset; - return subWordValue << bitOffset | (~memUpdateMask) & _memWord; - } - - function calculateSubWordMaskAndOffset( - uint64 _vaddr, - uint64 _byteLength - ) - internal - pure - returns (uint64 dataMask_, uint64 bitOffset_, uint64 bitLength_) - { - uint64 bitLength = _byteLength << 3; - uint64 dataMask = ~uint64(0) >> (arch.WORD_SIZE - bitLength); - - // Figure out sub-word index based on the low-order bits in vaddr - uint64 byteIndexMask = _vaddr & arch.EXT_MASK & ~(_byteLength - 1); - uint64 maxByteShift = arch.WORD_SIZE_BYTES - _byteLength; - uint64 byteIndex = _vaddr & byteIndexMask; - uint64 bitOffset = (maxByteShift - byteIndex) << 3; - - return (dataMask, bitOffset, bitLength); - } -} diff --git a/src/cannon/libraries/MIPS64Memory.sol b/src/cannon/libraries/MIPS64Memory.sol deleted file mode 100644 index 2f77fcd59..000000000 --- a/src/cannon/libraries/MIPS64Memory.sol +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; - -library MIPS64Memory { - uint64 internal constant EXT_MASK = 0x7; - uint64 internal constant MEM_PROOF_LEAF_COUNT = 60; - uint256 internal constant U64_MASK = 0xFFFFFFFFFFFFFFFF; - - /// @notice Reads a 64-bit word from memory. - /// @param _memRoot The current memory root - /// @param _addr The address to read from. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @return out_ The hashed MIPS state. - function readMem(bytes32 _memRoot, uint64 _addr, uint256 _proofOffset) internal pure returns (uint64 out_) { - bool valid; - (out_, valid) = readMemUnchecked(_memRoot, _addr, _proofOffset); - if (!valid) { - revert InvalidMemoryProof(); - } - } - - /// @notice Reads a 64-bit word from memory. - /// @param _memRoot The current memory root - /// @param _addr The address to read from. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @return out_ The hashed MIPS state. - /// valid_ Whether the proof is valid. - function readMemUnchecked( - bytes32 _memRoot, - uint64 _addr, - uint256 _proofOffset - ) - internal - pure - returns (uint64 out_, bool valid_) - { - unchecked { - validateMemoryProofAvailability(_proofOffset); - assembly { - // Validate the address alignment. - if and(_addr, EXT_MASK) { - // revert InvalidAddress(); - let ptr := mload(0x40) - mstore(ptr, shl(224, 0xe6c4247b)) - revert(ptr, 0x4) - } - - // Load the leaf value. - let leaf := calldataload(_proofOffset) - _proofOffset := add(_proofOffset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - let end := sub(MEM_PROOF_LEAF_COUNT, 1) - for { let i := 0 } lt(i, end) { i := add(i, 1) } { - let sibling := calldataload(_proofOffset) - _proofOffset := add(_proofOffset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - // Verify the root matches. - valid_ := eq(node, _memRoot) - if valid_ { - // Bits to shift = (32 - 8 - (addr % 32)) * 8 - let shamt := shl(3, sub(sub(32, 8), and(_addr, 31))) - out_ := and(shr(shamt, leaf), U64_MASK) - } - } - } - } - - /// @notice Writes a 64-bit word to memory. - /// This function first overwrites the part of the leaf. - /// Then it recomputes the memory merkle root. - /// @param _addr The address to write to. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @param _val The value to write. - /// @return newMemRoot_ The new memory root after modification - function writeMem(uint64 _addr, uint256 _proofOffset, uint64 _val) internal pure returns (bytes32 newMemRoot_) { - unchecked { - validateMemoryProofAvailability(_proofOffset); - assembly { - // Validate the address alignment. - if and(_addr, EXT_MASK) { - // revert InvalidAddress(); - let ptr := mload(0x40) - mstore(ptr, shl(224, 0xe6c4247b)) - revert(ptr, 0x4) - } - - // Load the leaf value. - let leaf := calldataload(_proofOffset) - let shamt := shl(3, sub(sub(32, 8), and(_addr, 31))) - - // Mask out 8 bytes, and OR in the value - leaf := or(and(leaf, not(shl(shamt, U64_MASK))), shl(shamt, _val)) - _proofOffset := add(_proofOffset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - let end := sub(MEM_PROOF_LEAF_COUNT, 1) - for { let i := 0 } lt(i, end) { i := add(i, 1) } { - let sibling := calldataload(_proofOffset) - _proofOffset := add(_proofOffset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - newMemRoot_ := node - } - return newMemRoot_; - } - } - - /// @notice Verifies a memory proof. - /// @param _memRoot The expected memory root - /// @param _addr The _addr proven. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @return valid_ True iff it is a valid proof. - function isValidProof(bytes32 _memRoot, uint64 _addr, uint256 _proofOffset) internal pure returns (bool valid_) { - (, valid_) = readMemUnchecked(_memRoot, _addr, _proofOffset); - } - - /// @notice Computes the offset of a memory proof in the calldata. - /// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset) - /// Equal to the offset of the first memory proof (at _proofIndex 0). - /// @param _proofIndex The index of the proof in the calldata. - /// @return offset_ The offset of the memory proof at the given _proofIndex in the calldata. - function memoryProofOffset(uint256 _proofDataOffset, uint8 _proofIndex) internal pure returns (uint256 offset_) { - unchecked { - // A proof of 64-bit memory, with 32-byte leaf values, is (64-5)=59 bytes32 entries. - // And the leaf value itself needs to be encoded as well: (59 + 1) = 60 bytes32 entries. - offset_ = _proofDataOffset + (uint256(_proofIndex) * (MEM_PROOF_LEAF_COUNT * 32)); - return offset_; - } - } - - /// @notice Validates that enough calldata is available to hold a full memory proof at the given offset - /// @param _proofStartOffset The index of the first byte of the target memory proof in calldata - function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure { - unchecked { - uint256 s = 0; - assembly { - s := calldatasize() - } - // A memory proof consists of MEM_PROOF_LEAF_COUNT bytes32 values - verify we have enough calldata - require( - s >= (_proofStartOffset + MEM_PROOF_LEAF_COUNT * 32), - "MIPS64Memory: check that there is enough calldata" - ); - } - } -} diff --git a/src/cannon/libraries/MIPS64State.sol b/src/cannon/libraries/MIPS64State.sol deleted file mode 100644 index c19d1f66a..000000000 --- a/src/cannon/libraries/MIPS64State.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol"; - -library MIPS64State { - struct CpuScalars { - uint64 pc; - uint64 nextPC; - uint64 lo; - uint64 hi; - } - - struct Features { - bool supportWorkingSysGetRandom; - } - - function assertExitedIsValid(uint32 _exited) internal pure { - if (_exited > 1) { - revert InvalidExitedValue(); - } - } - - function featuresForVersion(uint256 _version) internal pure returns (Features memory features_) { - if (_version >= 8) { - features_.supportWorkingSysGetRandom = true; - } - } -} diff --git a/src/cannon/libraries/MIPS64Syscalls.sol b/src/cannon/libraries/MIPS64Syscalls.sol deleted file mode 100644 index f237f8c38..000000000 --- a/src/cannon/libraries/MIPS64Syscalls.sol +++ /dev/null @@ -1,455 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; -import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; -import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; - -library MIPS64Syscalls { - struct SysReadParams { - /// @param _a0 The file descriptor. - uint64 a0; - /// @param _a1 The memory location where data should be read to. - uint64 a1; - /// @param _a2 The number of bytes to read from the file - uint64 a2; - /// @param _preimageKey The key of the preimage to read. - bytes32 preimageKey; - /// @param _preimageOffset The offset of the preimage to read. - uint64 preimageOffset; - /// @param _localContext The local context for the preimage key. - bytes32 localContext; - /// @param _oracle The address of the preimage oracle. - IPreimageOracle oracle; - /// @param _proofOffset The offset of the memory proof in calldata. - uint256 proofOffset; - /// @param _memRoot The current memory root. - bytes32 memRoot; - } - - /// @custom:field _a0 The file descriptor. - /// @custom:field _a1 The memory address to read from. - /// @custom:field _a2 The number of bytes to read. - /// @custom:field _preimageKey The current preimaageKey. - /// @custom:field _preimageOffset The current preimageOffset. - /// @custom:field _proofOffset The offset of the memory proof in calldata. - /// @custom:field _memRoot The current memory root. - struct SysWriteParams { - uint64 _a0; - uint64 _a1; - uint64 _a2; - bytes32 _preimageKey; - uint64 _preimageOffset; - uint256 _proofOffset; - bytes32 _memRoot; - } - - uint64 internal constant U64_MASK = 0xFFffFFffFFffFFff; - uint64 internal constant PAGE_ADDR_MASK = 4095; - uint64 internal constant PAGE_SIZE = 4096; - - uint32 internal constant SYS_MMAP = 5009; - uint32 internal constant SYS_MPROTECT = 5010; - uint32 internal constant SYS_BRK = 5012; - uint32 internal constant SYS_CLONE = 5055; - uint32 internal constant SYS_EXIT_GROUP = 5205; - uint32 internal constant SYS_READ = 5000; - uint32 internal constant SYS_WRITE = 5001; - uint32 internal constant SYS_FCNTL = 5070; - uint32 internal constant SYS_EXIT = 5058; - uint32 internal constant SYS_SCHED_YIELD = 5023; - uint32 internal constant SYS_GETTID = 5178; - uint32 internal constant SYS_FUTEX = 5194; - uint32 internal constant SYS_OPEN = 5002; - uint32 internal constant SYS_NANOSLEEP = 5034; - uint32 internal constant SYS_CLOCKGETTIME = 5222; - uint32 internal constant SYS_GETPID = 5038; - uint32 internal constant SYS_GETRANDOM = 5313; - // no-op syscalls - uint32 internal constant SYS_MUNMAP = 5011; - uint32 internal constant SYS_GETAFFINITY = 5196; - uint32 internal constant SYS_MADVISE = 5027; - uint32 internal constant SYS_RTSIGPROCMASK = 5014; - uint32 internal constant SYS_SIGALTSTACK = 5129; - uint32 internal constant SYS_RTSIGACTION = 5013; - uint32 internal constant SYS_PRLIMIT64 = 5297; - uint32 internal constant SYS_CLOSE = 5003; - uint32 internal constant SYS_PREAD64 = 5016; - uint32 internal constant SYS_STAT = 5004; - uint32 internal constant SYS_FSTAT = 5005; - //uint32 internal constant SYS_FSTAT64 = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 - uint32 internal constant SYS_OPENAT = 5247; - uint32 internal constant SYS_READLINK = 5087; - uint32 internal constant SYS_READLINKAT = 5257; - uint32 internal constant SYS_IOCTL = 5015; - uint32 internal constant SYS_EPOLLCREATE1 = 5285; - uint32 internal constant SYS_PIPE2 = 5287; - uint32 internal constant SYS_EPOLLCTL = 5208; - uint32 internal constant SYS_EPOLLPWAIT = 5272; - uint32 internal constant SYS_UNAME = 5061; - //uint32 internal constant SYS_STAT64 = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 - uint32 internal constant SYS_GETUID = 5100; - uint32 internal constant SYS_GETGID = 5102; - //uint32 internal constant SYS_LLSEEK = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 - uint32 internal constant SYS_MINCORE = 5026; - uint32 internal constant SYS_TGKILL = 5225; - uint32 internal constant SYS_GETRLIMIT = 5095; - uint32 internal constant SYS_LSEEK = 5008; - uint32 internal constant SYS_EVENTFD2 = 5284; - // profiling-related syscalls - ignored - uint32 internal constant SYS_SETITIMER = 5036; - uint32 internal constant SYS_TIMERCREATE = 5216; - uint32 internal constant SYS_TIMERSETTIME = 5217; - uint32 internal constant SYS_TIMERDELETE = 5220; - - uint32 internal constant FD_STDIN = 0; - uint32 internal constant FD_STDOUT = 1; - uint32 internal constant FD_STDERR = 2; - uint32 internal constant FD_HINT_READ = 3; - uint32 internal constant FD_HINT_WRITE = 4; - uint32 internal constant FD_PREIMAGE_READ = 5; - uint32 internal constant FD_PREIMAGE_WRITE = 6; - uint64 internal constant FD_EVENTFD = 100; - - uint64 internal constant SYS_ERROR_SIGNAL = U64_MASK; - uint64 internal constant EBADF = 0x9; - uint64 internal constant EINVAL = 0x16; - uint64 internal constant EAGAIN = 0xb; - uint64 internal constant ETIMEDOUT = 0x91; - - uint64 internal constant FUTEX_WAIT_PRIVATE = 128; - uint64 internal constant FUTEX_WAKE_PRIVATE = 129; - - uint64 internal constant SCHED_QUANTUM = 100_000; - uint64 internal constant HZ = 10_000_000; - uint64 internal constant CLOCK_GETTIME_REALTIME_FLAG = 0; - uint64 internal constant CLOCK_GETTIME_MONOTONIC_FLAG = 1; - /// @notice Start of the data segment. - uint64 internal constant PROGRAM_BREAK = 0x00_00_40_00_00_00_00_00; - uint64 internal constant HEAP_END = 0x00_00_60_00_00_00_00_00; - - // SYS_CLONE flags - uint64 internal constant CLONE_VM = 0x100; - uint64 internal constant CLONE_FS = 0x200; - uint64 internal constant CLONE_FILES = 0x400; - uint64 internal constant CLONE_SIGHAND = 0x800; - uint64 internal constant CLONE_PTRACE = 0x2000; - uint64 internal constant CLONE_VFORK = 0x4000; - uint64 internal constant CLONE_PARENT = 0x8000; - uint64 internal constant CLONE_THREAD = 0x10000; - uint64 internal constant CLONE_NEWNS = 0x20000; - uint64 internal constant CLONE_SYSVSEM = 0x40000; - uint64 internal constant CLONE_SETTLS = 0x80000; - uint64 internal constant CLONE_PARENTSETTID = 0x100000; - uint64 internal constant CLONE_CHILDCLEARTID = 0x200000; - uint64 internal constant CLONE_UNTRACED = 0x800000; - uint64 internal constant CLONE_CHILDSETTID = 0x1000000; - uint64 internal constant CLONE_STOPPED = 0x2000000; - uint64 internal constant CLONE_NEWUTS = 0x4000000; - uint64 internal constant CLONE_NEWIPC = 0x8000000; - uint64 internal constant VALID_SYS_CLONE_FLAGS = - CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD; - - // eventfd flags - // From: - // https://github.com/golang/go/blob/7a2cfb70b01f069c2125adcf7126d7f3376cb8b7/src/internal/runtime/syscall/defs_linux_mips64x.go#L18-L18 - uint64 internal constant EFD_NONBLOCK = 0x80; - - // FYI: https://en.wikibooks.org/wiki/MIPS_Assembly/Register_File - // https://refspecs.linuxfoundation.org/elf/mipsabi.pdf - uint32 internal constant REG_V0 = 2; - uint32 internal constant REG_A0 = 4; - uint32 internal constant REG_A1 = 5; - uint32 internal constant REG_A2 = 6; - uint32 internal constant REG_A3 = 7; - - // FYI: https://web.archive.org/web/20231223163047/https://www.linux-mips.org/wiki/Syscall - uint32 internal constant REG_SYSCALL_NUM = REG_V0; - uint32 internal constant REG_SYSCALL_ERRNO = REG_A3; - uint32 internal constant REG_SYSCALL_RET1 = REG_V0; - uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; - uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; - uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; - - // Constants copied from MIPS64Arch for use in Yul - uint64 internal constant WORD_SIZE_BYTES = 8; - uint64 internal constant EXT_MASK = 0x7; - - /// @notice Extract syscall num and arguments from registers. - /// @param _registers The cpu registers. - /// @return sysCallNum_ The syscall number. - /// @return a0_ The first argument available to the syscall operation. - /// @return a1_ The second argument available to the syscall operation. - /// @return a2_ The third argument available to the syscall operation. - function getSyscallArgs(uint64[32] memory _registers) - internal - pure - returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_) - { - unchecked { - sysCallNum_ = _registers[REG_SYSCALL_NUM]; - - a0_ = _registers[REG_SYSCALL_PARAM1]; - a1_ = _registers[REG_SYSCALL_PARAM2]; - a2_ = _registers[REG_SYSCALL_PARAM3]; - - return (sysCallNum_, a0_, a1_, a2_); - } - } - - /// @notice Like a Linux mmap syscall. Allocates a page from the heap. - /// @param _a0 The address for the new mapping - /// @param _a1 The size of the new mapping - /// @param _heap The current value of the heap pointer - /// @return v0_ The address of the new mapping or error code on error - /// @return v1_ 0 if there is no error, non-zero on error - /// @return newHeap_ The new value for the heap, may be unchanged - function handleSysMmap( - uint64 _a0, - uint64 _a1, - uint64 _heap - ) - internal - pure - returns (uint64 v0_, uint64 v1_, uint64 newHeap_) - { - unchecked { - v1_ = uint64(0); - newHeap_ = _heap; - - uint64 sz = _a1; - if (sz & PAGE_ADDR_MASK != 0) { - // adjust size to align with page size - sz += PAGE_SIZE - (sz & PAGE_ADDR_MASK); - } - if (_a0 == 0) { - v0_ = _heap; - newHeap_ += sz; - // Fail if new heap exceeds memory limit, newHeap overflows to low memory, or sz overflows - if (newHeap_ > HEAP_END || newHeap_ < _heap || sz < _a1) { - v0_ = EINVAL; - v1_ = SYS_ERROR_SIGNAL; - return (v0_, v1_, _heap); - } - } else { - v0_ = _a0; - } - - return (v0_, v1_, newHeap_); - } - } - - /// @notice Like a Linux read syscall. Splits unaligned reads into aligned reads. - /// Args are provided as a struct to reduce stack pressure. - /// @return v0_ The number of bytes read, error code on error. - /// @return v1_ 0 if there is no error, non-zero on error - /// @return newPreimageOffset_ The new value for the preimage offset. - /// @return newMemRoot_ The new memory root. - function handleSysRead(SysReadParams memory _args) - internal - view - returns ( - uint64 v0_, - uint64 v1_, - uint64 newPreimageOffset_, - bytes32 newMemRoot_, - bool memUpdated_, - uint64 memAddr_ - ) - { - unchecked { - v0_ = uint64(0); - v1_ = uint64(0); - newMemRoot_ = _args.memRoot; - newPreimageOffset_ = _args.preimageOffset; - memUpdated_ = false; - memAddr_ = 0; - - // args: _a0 = fd, _a1 = addr, _a2 = count - // returns: v0_ = read, v1_ = err code - if (_args.a0 == FD_STDIN) { - // Leave v0_ and v1_ zero: read nothing, no error - } - // pre-image oracle read - else if (_args.a0 == FD_PREIMAGE_READ) { - uint64 effAddr = _args.a1 & arch.ADDRESS_MASK; - // verify proof is correct, and get the existing memory. - // mask the addr to align it to 4 bytes - uint64 mem = MIPS64Memory.readMem(_args.memRoot, effAddr, _args.proofOffset); - // If the preimage key is a local key, localize it in the context of the caller. - if (uint8(_args.preimageKey[0]) == 1) { - _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); - } - (bytes32 dat, uint256 datLen) = _args.oracle.readPreimage(_args.preimageKey, _args.preimageOffset); - - // Transform data for writing to memory - // We use assembly for more precise ops, and no var count limit - uint64 a1 = _args.a1; - uint64 a2 = _args.a2; - assembly { - let alignment := and(a1, EXT_MASK) // the read might not start at an aligned address - let space := sub(WORD_SIZE_BYTES, alignment) // remaining space in memory word - if lt(space, datLen) { datLen := space } // if less space than data, shorten data - if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less - dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data - // position data to insert into memory word - dat := shl(mul(sub(sub(WORD_SIZE_BYTES, datLen), alignment), 8), dat) - // mask all bytes after start - let mask := sub(shl(mul(sub(WORD_SIZE_BYTES, alignment), 8), 1), 1) - // mask of all bytes - let suffixMask := sub(shl(mul(sub(sub(WORD_SIZE_BYTES, alignment), datLen), 8), 1), 1) - // starting from end, maybe none - mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert - mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data - } - - // Write memory back - newMemRoot_ = MIPS64Memory.writeMem(effAddr, _args.proofOffset, mem); - memUpdated_ = true; - memAddr_ = effAddr; - newPreimageOffset_ += uint64(datLen); - v0_ = uint64(datLen); - } - // hint response - else if (_args.a0 == FD_HINT_READ) { - // Don't read into memory, just say we read it all - // The result is ignored anyway - v0_ = _args.a2; - } else if (_args.a0 == FD_EVENTFD) { - // Always act in non blocking mode as if the counter has not been signalled - v0_ = EAGAIN; - v1_ = SYS_ERROR_SIGNAL; - } else { - v0_ = EBADF; - v1_ = SYS_ERROR_SIGNAL; - } - - return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_); - } - } - - /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. - /// @return v0_ The number of bytes written, or error code on error. - /// @return v1_ 0 if there is no error, non-zero on error - /// @return newPreimageKey_ The new preimageKey. - /// @return newPreimageOffset_ The new preimageOffset. - function handleSysWrite(SysWriteParams memory _args) - internal - pure - returns (uint64 v0_, uint64 v1_, bytes32 newPreimageKey_, uint64 newPreimageOffset_) - { - unchecked { - // args: _a0 = fd, _a1 = addr, _a2 = count - // returns: v0_ = written, v1_ = err code - v0_ = uint64(0); - v1_ = uint64(0); - newPreimageKey_ = _args._preimageKey; - newPreimageOffset_ = _args._preimageOffset; - - if (_args._a0 == FD_STDOUT || _args._a0 == FD_STDERR || _args._a0 == FD_HINT_WRITE) { - v0_ = _args._a2; // tell program we have written everything - } - // pre-image oracle - else if (_args._a0 == FD_PREIMAGE_WRITE) { - // mask the addr to align it to 4 bytes - uint64 mem = MIPS64Memory.readMem(_args._memRoot, _args._a1 & arch.ADDRESS_MASK, _args._proofOffset); - bytes32 key = _args._preimageKey; - - // Construct pre-image key from memory - // We use assembly for more precise ops, and no var count limit - uint64 _a1 = _args._a1; - uint64 _a2 = _args._a2; - assembly { - let alignment := and(_a1, EXT_MASK) // the read might not start at an aligned address - let space := sub(WORD_SIZE_BYTES, alignment) // remaining space in memory word - if lt(space, _a2) { _a2 := space } // if less space than data, shorten data - key := shl(mul(_a2, 8), key) // shift key, make space for new info - let mask := sub(shl(mul(_a2, 8), 1), 1) // mask for extracting value from memory - mem := and(shr(mul(sub(space, _a2), 8), mem), mask) // align value to right, mask it - key := or(key, mem) // insert into key - } - _args._a2 = _a2; - - // Write pre-image key to oracle - newPreimageKey_ = key; - newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start - v0_ = _args._a2; - } else if (_args._a0 == FD_EVENTFD) { - // Always report that the write could not be completed - // This acts as if the counter has already reached the maximum value - v0_ = EAGAIN; - v1_ = SYS_ERROR_SIGNAL; - } else { - v0_ = EBADF; - v1_ = SYS_ERROR_SIGNAL; - } - - return (v0_, v1_, newPreimageKey_, newPreimageOffset_); - } - } - - /// @notice Like Linux fcntl (file control) syscall, but only supports minimal file-descriptor control commands, to - /// retrieve the file-descriptor R/W flags. - /// @param _a0 The file descriptor. - /// @param _a1 The control command. - /// @param v0_ The file status flag (only supported commands are F_GETFD and F_GETFL), or error code on error. - /// @param v1_ 0 if there is no error, non-zero on error - function handleSysFcntl(uint64 _a0, uint64 _a1) internal pure returns (uint64 v0_, uint64 v1_) { - unchecked { - v0_ = uint64(0); - v1_ = uint64(0); - - // args: _a0 = fd, _a1 = cmd - if (_a1 == 1) { - // F_GETFD: get file descriptor flags - if ( - _a0 == FD_STDIN || _a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_READ - || _a0 == FD_HINT_READ || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE - ) { - v0_ = 0; // No flags set - } else { - v0_ = EBADF; - v1_ = SYS_ERROR_SIGNAL; - } - } else if (_a1 == 3) { - // F_GETFL: get file status flags - if (_a0 == FD_STDIN || _a0 == FD_PREIMAGE_READ || _a0 == FD_HINT_READ) { - v0_ = 0; // O_RDONLY - } else if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE) { - v0_ = 1; // O_WRONLY - } else { - v0_ = EBADF; - v1_ = SYS_ERROR_SIGNAL; - } - } else { - v0_ = EINVAL; // cmd not recognized by this kernel - v1_ = SYS_ERROR_SIGNAL; - } - - return (v0_, v1_); - } - } - - function handleSyscallUpdates( - st.CpuScalars memory _cpu, - uint64[32] memory _registers, - uint64 _v0, - uint64 _v1 - ) - internal - pure - { - unchecked { - // Write the results back to the state registers - _registers[REG_SYSCALL_RET1] = _v0; - _registers[REG_SYSCALL_ERRNO] = _v1; - - // Update the PC and nextPC - _cpu.pc = _cpu.nextPC; - _cpu.nextPC = _cpu.nextPC + 4; - } - } -} diff --git a/src/libraries/bridge/Errors.sol b/src/libraries/bridge/Errors.sol index fdacec175..c305a7241 100644 --- a/src/libraries/bridge/Errors.sol +++ b/src/libraries/bridge/Errors.sol @@ -16,21 +16,23 @@ error NoImplementation(GameType gameType); /// @param uuid The UUID of the dispute game that already exists. error GameAlreadyExists(Hash uuid); -/// @notice Thrown when the root claim has an unexpected VM status. -/// Some games can only start with a root-claim with a specific status. -/// @param rootClaim is the claim that was unexpected. -error UnexpectedRootClaim(Claim rootClaim); +/// @notice Thrown when a supplied bond is not equal to the required bond amount to cover the cost of the interaction. +error IncorrectBondAmount(); + +//////////////////////////////////////////////////////////////// +// `OptimismPortal2` Errors. // +//////////////////////////////////////////////////////////////// + +/// @notice Thrown when an output root proof is invalid. +error InvalidOutputRootProof(); //////////////////////////////////////////////////////////////// -// `FaultDisputeGame` Errors // +// `AggregateVerifier` Errors // //////////////////////////////////////////////////////////////// /// @notice Thrown when a dispute game has already been initialized. error AlreadyInitialized(); -/// @notice Thrown when a supplied bond is not equal to the required bond amount to cover the cost of the interaction. -error IncorrectBondAmount(); - /// @notice Thrown when a credit claim is attempted for a value of 0. error NoCreditToClaim(); @@ -40,126 +42,22 @@ error BondTransferFailed(); /// @notice Thrown when the `extraData` passed to the CWIA proxy is of improper length, or contains invalid information. error BadExtraData(); -/// @notice Thrown when a defense against the root claim is attempted. -error CannotDefendRootClaim(); - -/// @notice Thrown when a claim is attempting to be made that already exists. -error ClaimAlreadyExists(); - -/// @notice Thrown when a disputed claim does not match its index in the game. -error InvalidDisputedClaimIndex(); - /// @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when /// the game is not in progress. error GameNotInProgress(); -/// @notice Thrown when a move is attempted to be made after the clock has timed out. -error ClockTimeExceeded(); - -/// @notice Thrown when the game is attempted to be resolved too early. -error ClockNotExpired(); - -/// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game. -error GameDepthExceeded(); - -/// @notice Thrown when a step is attempted above the maximum game depth. -error InvalidParent(); - -/// @notice Thrown when an invalid prestate is supplied to `step`. -error InvalidPrestate(); - -/// @notice Thrown when a step is made that computes the expected post state correctly. -error ValidStep(); - -/// @notice Thrown when a game is attempted to be initialized with an L1 head that does -/// not contain the disputed output root. -error L1HeadTooOld(); - -/// @notice Thrown when an invalid local identifier is passed to the `addLocalData` function. -error InvalidLocalIdent(); - -/// @notice Thrown when resolving claims out of order. -error OutOfOrderResolution(); - /// @notice Thrown when resolving a claim that has already been resolved. error ClaimAlreadyResolved(); -/// @notice Thrown when a parent output root is attempted to be found on a claim that is in -/// the output root portion of the tree. -error ClaimAboveSplit(); - -/// @notice Thrown on deployment if the split depth is greater than or equal to the max -/// depth of the game. -error InvalidSplitDepth(); - -/// @notice Thrown on deployment if the max clock duration is less than or equal to the clock extension. -error InvalidClockExtension(); - -/// @notice Thrown on deployment if the PreimageOracle challenge period is too high. -error InvalidChallengePeriod(); - -/// @notice Thrown on deployment if the max depth is greater than `LibPosition.` -error MaxDepthTooLarge(); - -/// @notice Thrown when trying to step against a claim for a second time, after it has already been countered with -/// an instruction step. -error DuplicateStep(); - -/// @notice Thrown when an anchor root is not found for a given game type. -error AnchorRootNotFound(); - -/// @notice Thrown when an output root proof is invalid. -error InvalidOutputRootProof(); - -/// @notice Thrown when header RLP is invalid with respect to the block hash in an output root proof. -error InvalidHeaderRLP(); - -/// @notice Thrown when there is a match between the block number in the output root proof and the block number -/// claimed in the dispute game. -error BlockNumberMatches(); - -/// @notice Thrown when the L2 block number claim has already been challenged. -error L2BlockNumberChallenged(); - /// @notice Thrown when the game is not yet finalized. error GameNotFinalized(); -/// @notice Thrown when an invalid bond distribution mode is supplied. -error InvalidBondDistributionMode(); - /// @notice Thrown when the game is not yet resolved. error GameNotResolved(); -/// @notice Thrown when a reserved game type is used. -error ReservedGameType(); - -//////////////////////////////////////////////////////////////// -// `PermissionedDisputeGame` Errors // -//////////////////////////////////////////////////////////////// - -/// @notice Thrown when an unauthorized address attempts to interact with the game. -error BadAuth(); - /// @notice Thrown when trying to close a game while the system is paused. error GamePaused(); -//////////////////////////////////////////////////////////////// -// `LibGameArgs` Errors // -//////////////////////////////////////////////////////////////// - -/// @notice Thrown when the length of the game arguments is invalid. -error InvalidGameArgsLength(); - -//////////////////////////////////////////////////////////////// -// `OPSuccinctFaultDisputeGame` Errors // -//////////////////////////////////////////////////////////////// - -/// @notice Thrown when the claim has already been challenged. -error ClaimAlreadyChallenged(); - -/// @notice Thrown when the game type of the parent game does not match the current game. -error UnexpectedGameType(); - /// @notice Thrown when the parent game is invalid. error InvalidParentGame(); @@ -171,9 +69,3 @@ error GameOver(); /// @notice Thrown when the game is not over. error GameNotOver(); - -/// @notice Thrown when the proposal status is invalid. -error InvalidProposalStatus(); - -/// @notice Thrown when the game is initialized by an incorrect factory. -error IncorrectDisputeGameFactory(); diff --git a/src/libraries/bridge/LibGameArgs.sol b/src/libraries/bridge/LibGameArgs.sol deleted file mode 100644 index fb38a2434..000000000 --- a/src/libraries/bridge/LibGameArgs.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import { InvalidGameArgsLength } from "src/libraries/bridge/Errors.sol"; - -/// @title LibGameArgs -/// @notice Library for decoding the game arguments used in dispute games. -library LibGameArgs { - uint256 public constant PERMISSIONLESS_ARGS_LENGTH = 124; - uint256 public constant PERMISSIONED_ARGS_LENGTH = 164; - - /// @notice Struct representing the game arguments. - struct GameArgs { - bytes32 absolutePrestate; - address vm; - address anchorStateRegistry; - address weth; - uint256 l2ChainId; - address proposer; - address challenger; - } - - /// @notice Encodes the game arguments into a bytes array. - function encode(GameArgs memory _args) internal pure returns (bytes memory) { - if (_args.proposer == address(0) && _args.challenger == address(0)) { - return abi.encodePacked( - _args.absolutePrestate, _args.vm, _args.anchorStateRegistry, _args.weth, _args.l2ChainId - ); - } else { - return abi.encodePacked( - _args.absolutePrestate, - _args.vm, - _args.anchorStateRegistry, - _args.weth, - _args.l2ChainId, - _args.proposer, - _args.challenger - ); - } - } - - /// @notice Decodes the game arguments from a bytes array. - /// @param _gameArgs The bytes array containing the encoded game arguments. - function decode(bytes memory _gameArgs) internal pure returns (GameArgs memory) { - uint256 len = _gameArgs.length; - if (len != PERMISSIONED_ARGS_LENGTH && len != PERMISSIONLESS_ARGS_LENGTH) { - revert InvalidGameArgsLength(); - } - - bytes32 absolutePrestate; - address vm; - address asr; - address weth; - uint256 l2ChainId; - address proposer; - address challenger; - - assembly { - // skip length prefix - let d := add(_gameArgs, 32) - absolutePrestate := mload(d) - vm := shr(96, mload(add(d, 32))) - asr := shr(96, mload(add(d, 52))) - weth := shr(96, mload(add(d, 72))) - l2ChainId := mload(add(d, 92)) - } - - if (len == PERMISSIONED_ARGS_LENGTH) { - assembly { - // skip length prefix - let d := add(_gameArgs, 32) - proposer := shr(96, mload(add(d, 124))) - challenger := shr(96, mload(add(d, 144))) - } - } - return GameArgs({ - absolutePrestate: absolutePrestate, - vm: vm, - anchorStateRegistry: asr, - weth: weth, - l2ChainId: l2ChainId, - proposer: proposer, - challenger: challenger - }); - } - - /// @notice Checks if the provided game arguments are valid for a permissionless game. - function isValidPermissionlessArgs(bytes memory _args) internal pure returns (bool) { - return _args.length == PERMISSIONLESS_ARGS_LENGTH; - } - - /// @notice Checks if the provided game arguments are valid for a permissioned game. - function isValidPermissionedArgs(bytes memory _args) internal pure returns (bool) { - return _args.length == PERMISSIONED_ARGS_LENGTH; - } -} diff --git a/src/libraries/bridge/LibPosition.sol b/src/libraries/bridge/LibPosition.sol deleted file mode 100644 index 52dcc7baa..000000000 --- a/src/libraries/bridge/LibPosition.sol +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -using LibPosition for Position global; - -/// @notice A `Position` represents a position of a claim within the game tree. -/// @dev This is represented as a "generalized index" where the high-order bit -/// is the level in the tree and the remaining bits is a unique bit pattern, allowing -/// a unique identifier for each node in the tree. Mathematically, it is calculated -/// as 2^{depth} + indexAtDepth. -type Position is uint128; - -/// @title LibPosition -/// @notice This library contains helper functions for working with the `Position` type. -library LibPosition { - /// @notice the `MAX_POSITION_BITLEN` is the number of bits that the `Position` type, and the implementation of - /// its behavior within this library, can safely support. - uint8 internal constant MAX_POSITION_BITLEN = 126; - - /// @notice Computes a generalized index (2^{depth} + indexAtDepth). - /// @param _depth The depth of the position. - /// @param _indexAtDepth The index at the depth of the position. - /// @return position_ The computed generalized index. - function wrap(uint8 _depth, uint128 _indexAtDepth) internal pure returns (Position position_) { - assembly { - // gindex = 2^{_depth} + _indexAtDepth - position_ := add(shl(_depth, 1), _indexAtDepth) - } - } - - /// @notice Pulls the `depth` out of a `Position` type. - /// @param _position The generalized index to get the `depth` of. - /// @return depth_ The `depth` of the `position` gindex. - /// @custom:attribution Solady - function depth(Position _position) internal pure returns (uint8 depth_) { - // Return the most significant bit offset, which signifies the depth of the gindex. - assembly { - depth_ := or(depth_, shl(6, lt(0xffffffffffffffff, shr(depth_, _position)))) - depth_ := or(depth_, shl(5, lt(0xffffffff, shr(depth_, _position)))) - - // For the remaining 32 bits, use a De Bruijn lookup. - _position := shr(depth_, _position) - _position := or(_position, shr(1, _position)) - _position := or(_position, shr(2, _position)) - _position := or(_position, shr(4, _position)) - _position := or(_position, shr(8, _position)) - _position := or(_position, shr(16, _position)) - - depth_ := or( - depth_, - byte( - shr(251, mul(_position, shl(224, 0x07c4acdd))), - 0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f - ) - ) - } - } - - /// @notice Pulls the `indexAtDepth` out of a `Position` type. - /// The `indexAtDepth` is the left/right index of a position at a specific depth within - /// the binary tree, starting from index 0. For example, at gindex 2, the `depth` = 1 - /// and the `indexAtDepth` = 0. - /// @param _position The generalized index to get the `indexAtDepth` of. - /// @return indexAtDepth_ The `indexAtDepth` of the `position` gindex. - function indexAtDepth(Position _position) internal pure returns (uint128 indexAtDepth_) { - // Return bits p_{msb-1}...p_{0}. This effectively pulls the 2^{depth} out of the gindex, - // leaving only the `indexAtDepth`. - uint256 msb = depth(_position); - assembly { - indexAtDepth_ := sub(_position, shl(msb, 1)) - } - } - - /// @notice Get the left child of `_position`. - /// @param _position The position to get the left position of. - /// @return left_ The position to the left of `position`. - function left(Position _position) internal pure returns (Position left_) { - assembly { - left_ := shl(1, _position) - } - } - - /// @notice Get the right child of `_position` - /// @param _position The position to get the right position of. - /// @return right_ The position to the right of `position`. - function right(Position _position) internal pure returns (Position right_) { - assembly { - right_ := or(1, shl(1, _position)) - } - } - - /// @notice Get the parent position of `_position`. - /// @param _position The position to get the parent position of. - /// @return parent_ The parent position of `position`. - function parent(Position _position) internal pure returns (Position parent_) { - assembly { - parent_ := shr(1, _position) - } - } - - /// @notice Get the deepest, right most gindex relative to the `position`. This is equivalent to - /// calling `right` on a position until the maximum depth is reached. - /// @param _position The position to get the relative deepest, right most gindex of. - /// @param _maxDepth The maximum depth of the game. - /// @return rightIndex_ The deepest, right most gindex relative to the `position`. - function rightIndex(Position _position, uint256 _maxDepth) internal pure returns (Position rightIndex_) { - uint256 msb = depth(_position); - assembly { - let remaining := sub(_maxDepth, msb) - rightIndex_ := or(shl(remaining, _position), sub(shl(remaining, 1), 1)) - } - } - - /// @notice Get the deepest, right most trace index relative to the `position`. This is - /// equivalent to calling `right` on a position until the maximum depth is reached and - /// then finding its index at depth. - /// @param _position The position to get the relative trace index of. - /// @param _maxDepth The maximum depth of the game. - /// @return traceIndex_ The trace index relative to the `position`. - function traceIndex(Position _position, uint256 _maxDepth) internal pure returns (uint256 traceIndex_) { - uint256 msb = depth(_position); - assembly { - let remaining := sub(_maxDepth, msb) - traceIndex_ := sub(or(shl(remaining, _position), sub(shl(remaining, 1), 1)), shl(_maxDepth, 1)) - } - } - - /// @notice Gets the position of the highest ancestor of `_position` that commits to the same - /// trace index. - /// @param _position The position to get the highest ancestor of. - /// @return ancestor_ The highest ancestor of `position` that commits to the same trace index. - function traceAncestor(Position _position) internal pure returns (Position ancestor_) { - // Create a field with only the lowest unset bit of `_position` set. - Position lsb; - assembly { - lsb := and(not(_position), add(_position, 1)) - } - // Find the index of the lowest unset bit within the field. - uint256 msb = depth(lsb); - // The highest ancestor that commits to the same trace index is the original position - // shifted right by the index of the lowest unset bit. - assembly { - let a := shr(msb, _position) - // Bound the ancestor to the minimum gindex, 1. - ancestor_ := or(a, iszero(a)) - } - } - - /// @notice Gets the position of the highest ancestor of `_position` that commits to the same - /// trace index, while still being below `_upperBoundExclusive`. - /// @param _position The position to get the highest ancestor of. - /// @param _upperBoundExclusive The exclusive upper depth bound, used to inform where to stop in order - /// to not escape a sub-tree. - /// @return ancestor_ The highest ancestor of `position` that commits to the same trace index. - function traceAncestorBounded( - Position _position, - uint256 _upperBoundExclusive - ) - internal - pure - returns (Position ancestor_) - { - // This function only works for positions that are below the upper bound. - if (_position.depth() <= _upperBoundExclusive) { - assembly { - // Revert with `ClaimAboveSplit()` - mstore(0x00, 0xb34b5c22) - revert(0x1C, 0x04) - } - } - - // Grab the global trace ancestor. - ancestor_ = traceAncestor(_position); - - // If the ancestor is above or at the upper bound, shift it to be below the upper bound. - // This should be a special case that only covers positions that commit to the final leaf - // in a sub-tree. - if (ancestor_.depth() <= _upperBoundExclusive) { - ancestor_ = ancestor_.rightIndex(_upperBoundExclusive + 1); - } - } - - /// @notice Get the move position of `_position`, which is the left child of: - /// 1. `_position` if `_isAttack` is true. - /// 2. `_position | 1` if `_isAttack` is false. - /// @param _position The position to get the relative attack/defense position of. - /// @param _isAttack Whether or not the move is an attack move. - /// @return move_ The move position relative to `position`. - function move(Position _position, bool _isAttack) internal pure returns (Position move_) { - assembly { - move_ := shl(1, or(iszero(_isAttack), _position)) - } - } - - /// @notice Get the value of a `Position` type in the form of the underlying uint128. - /// @param _position The position to get the value of. - /// @return raw_ The value of the `position` as a uint128 type. - function raw(Position _position) internal pure returns (uint128 raw_) { - assembly { - raw_ := _position - } - } -} diff --git a/src/libraries/bridge/LibUDT.sol b/src/libraries/bridge/LibUDT.sol index 89bf14d13..db962e0ed 100644 --- a/src/libraries/bridge/LibUDT.sol +++ b/src/libraries/bridge/LibUDT.sol @@ -1,72 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -// Libraries -import { Position } from "src/libraries/bridge/LibPosition.sol"; - using LibClaim for Claim global; using LibHash for Hash global; -using LibDuration for Duration global; -using LibClock for Clock global; using LibGameId for GameId global; using LibTimestamp for Timestamp global; -using LibVMStatus for VMStatus global; using LibGameType for GameType global; -/// @notice A `Clock` represents a packed `Duration` and `Timestamp` -/// @dev The packed layout of this type is as follows: -/// ┌────────────┬────────────────┐ -/// │ Bits │ Value │ -/// ├────────────┼────────────────┤ -/// │ [0, 64) │ Duration │ -/// │ [64, 128) │ Timestamp │ -/// └────────────┴────────────────┘ -type Clock is uint128; - -/// @title LibClock -/// @notice This library contains helper functions for working with the `Clock` type. -library LibClock { - /// @notice Packs a `Duration` and `Timestamp` into a `Clock` type. - /// @param _duration The `Duration` to pack into the `Clock` type. - /// @param _timestamp The `Timestamp` to pack into the `Clock` type. - /// @return clock_ The `Clock` containing the `_duration` and `_timestamp`. - function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) { - assembly { - clock_ := or(shl(0x40, _duration), _timestamp) - } - } - - /// @notice Pull the `Duration` out of a `Clock` type. - /// @param _clock The `Clock` type to pull the `Duration` out of. - /// @return duration_ The `Duration` pulled out of `_clock`. - function duration(Clock _clock) internal pure returns (Duration duration_) { - // Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`. - assembly { - duration_ := shr(0x40, _clock) - } - } - - /// @notice Pull the `Timestamp` out of a `Clock` type. - /// @param _clock The `Clock` type to pull the `Timestamp` out of. - /// @return timestamp_ The `Timestamp` pulled out of `_clock`. - function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) { - // Clean the high-order 192 bits by shifting the clock left and then right again, leaving - // only the `timestamp`. - assembly { - timestamp_ := shr(0xC0, shl(0xC0, _clock)) - } - } - - /// @notice Get the value of a `Clock` type in the form of the underlying uint128. - /// @param _clock The `Clock` type to get the value of. - /// @return clock_ The value of the `Clock` type as a uint128 type. - function raw(Clock _clock) internal pure returns (uint128 clock_) { - assembly { - clock_ := _clock - } - } -} - /// @notice A `GameId` represents a packed 4 byte game ID, a 8 byte timestamp, and a 20 byte address. /// @dev The packed layout of this type is as follows: /// ┌───────────┬───────────┐ @@ -110,7 +50,7 @@ library LibGameId { } } -/// @notice A claim represents an MPT root representing the state of the fault proof program. +/// @notice A claim represents a proposed output root. type Claim is bytes32; /// @title LibClaim @@ -124,44 +64,6 @@ library LibClaim { claim_ := _claim } } - - /// @notice Hashes a claim and a position together. - /// @param _claim A Claim type. - /// @param _position The position of `claim`. - /// @param _challengeIndex The index of the claim being moved against. - /// @return claimHash_ A hash of abi.encodePacked(claim, position|challengeIndex); - function hashClaimPos( - Claim _claim, - Position _position, - uint256 _challengeIndex - ) - internal - pure - returns (Hash claimHash_) - { - assembly { - mstore(0x00, _claim) - mstore(0x20, or(shl(128, _position), and(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, _challengeIndex))) - claimHash_ := keccak256(0x00, 0x40) - } - } -} - -/// @notice A dedicated duration type. -/// @dev Unit: seconds -type Duration is uint64; - -/// @title LibDuration -/// @notice This library contains helper functions for working with the `Duration` type. -library LibDuration { - /// @notice Get the value of a `Duration` type in the form of the underlying uint64. - /// @param _duration The `Duration` type to get the value of. - /// @return duration_ The value of the `Duration` type as a uint64 type. - function raw(Duration _duration) internal pure returns (uint64 duration_) { - assembly { - duration_ := _duration - } - } } /// @notice A custom type for a generic hash. @@ -196,22 +98,6 @@ library LibTimestamp { } } -/// @notice A `VMStatus` represents the status of a VM execution. -type VMStatus is uint8; - -/// @title LibVMStatus -/// @notice This library contains helper functions for working with the `VMStatus` type. -library LibVMStatus { - /// @notice Get the value of a `VMStatus` type in the form of the underlying uint8. - /// @param _vmstatus The `VMStatus` type to get the value of. - /// @return vmstatus_ The value of the `VMStatus` type as a uint8 type. - function raw(VMStatus _vmstatus) internal pure returns (uint8 vmstatus_) { - assembly { - vmstatus_ := _vmstatus - } - } -} - /// @notice A `GameType` represents the type of game being played. type GameType is uint32; diff --git a/src/libraries/bridge/Types.sol b/src/libraries/bridge/Types.sol index 95d3b4ee9..bf978b8ff 100644 --- a/src/libraries/bridge/Types.sol +++ b/src/libraries/bridge/Types.sol @@ -2,19 +2,7 @@ pragma solidity ^0.8.15; // Libraries -import { - Position, - Hash, - GameType, - VMStatus, - Timestamp, - Duration, - Clock, - GameId, - Claim, - LibGameId, - LibClock -} from "src/libraries/bridge/LibUDT.sol"; +import { Hash, GameType } from "src/libraries/bridge/LibUDT.sol"; /// @notice The current status of the dispute game. enum GameStatus { @@ -26,17 +14,6 @@ enum GameStatus { DEFENDER_WINS } -/// @notice The game's bond distribution type. Games are expected to start in the `UNDECIDED` -/// state, and then choose either `NORMAL` or `REFUND`. -enum BondDistributionMode { - // Bond distribution strategy has not been chosen. - UNDECIDED, - // Bonds should be distributed as normal. - NORMAL, - // Bonds should be refunded to claimants. - REFUND -} - /// @notice Represents an L2 root and the L2 sequence number at which it was generated. /// @custom:field root The output root. /// @custom:field l2SequenceNumber The L2 Sequence Number ( e.g. block number / timestamp) at which the root was @@ -49,96 +26,6 @@ struct Proposal { /// @title GameTypes /// @notice A library that defines the IDs of games that can be played. library GameTypes { - /// @dev A dispute game type the uses the cannon vm. - GameType internal constant CANNON = GameType.wrap(0); - - /// @dev A permissioned dispute game type that uses the cannon vm. - GameType internal constant PERMISSIONED_CANNON = GameType.wrap(1); - - /// @notice A dispute game type that uses the asterisc vm. - GameType internal constant ASTERISC = GameType.wrap(2); - - /// @notice A dispute game type that uses the asterisc vm with Kona. - GameType internal constant ASTERISC_KONA = GameType.wrap(3); - - /// @notice A dispute game type that uses the cannon vm (Super Roots). - GameType internal constant SUPER_CANNON = GameType.wrap(4); - - /// @notice A dispute game type that uses the permissioned cannon vm (Super Roots). - GameType internal constant SUPER_PERMISSIONED_CANNON = GameType.wrap(5); - - /// @notice A dispute game type that uses OP Succinct - GameType internal constant OP_SUCCINCT = GameType.wrap(6); - - /// @notice A dispute game type that uses the asterisc vm with Kona (Super Roots). - GameType internal constant SUPER_ASTERISC_KONA = GameType.wrap(7); - - /// @notice A dispute game type that uses the cannon vm with Kona. - GameType internal constant CANNON_KONA = GameType.wrap(8); - - /// @notice A dispute game type that uses the cannon vm with Kona (Super Roots). - GameType internal constant SUPER_CANNON_KONA = GameType.wrap(9); - - /// @notice A dispute game type with short game duration for testing withdrawals. - /// Not intended for production use. - GameType internal constant FAST = GameType.wrap(254); - - /// @notice A dispute game type that uses an alphabet vm. - /// Not intended for production use. - GameType internal constant ALPHABET = GameType.wrap(255); - - /// @notice A dispute game type that uses RISC Zero's Kailua - GameType internal constant KAILUA = GameType.wrap(1337); -} - -/// @title VMStatuses -/// @notice Named type aliases for the various valid VM status bytes. -library VMStatuses { - /// @notice The VM has executed successfully and the outcome is valid. - VMStatus internal constant VALID = VMStatus.wrap(0); - - /// @notice The VM has executed successfully and the outcome is invalid. - VMStatus internal constant INVALID = VMStatus.wrap(1); - - /// @notice The VM has paniced. - VMStatus internal constant PANIC = VMStatus.wrap(2); - - /// @notice The VM execution is still in progress. - VMStatus internal constant UNFINISHED = VMStatus.wrap(3); -} - -/// @title LocalPreimageKey -/// @notice Named type aliases for local `PreimageOracle` key identifiers. -library LocalPreimageKey { - /// @notice The identifier for the L1 head hash. - uint256 internal constant L1_HEAD_HASH = 0x01; - - /// @notice The identifier for the starting output root. - uint256 internal constant STARTING_OUTPUT_ROOT = 0x02; - - /// @notice The identifier for the disputed output root. - uint256 internal constant DISPUTED_OUTPUT_ROOT = 0x03; - - /// @notice The identifier for the disputed L2 block number. - uint256 internal constant DISPUTED_L2_BLOCK_NUMBER = 0x04; - - /// @notice The identifier for the chain ID. - uint256 internal constant CHAIN_ID = 0x05; -} - -//////////////////////////////////////////////////////////////// -// `OPSuccinctFaultDisputeGame` Types // -//////////////////////////////////////////////////////////////// - -uint32 constant OP_SUCCINCT_FAULT_DISPUTE_GAME_TYPE = 42; - -/// @notice The public values committed to for an OP Succinct aggregation program. -struct AggregationOutputs { - bytes32 l1Head; - bytes32 l2PreRoot; - bytes32 claimRoot; - uint256 claimBlockNum; - bytes32 rollupConfigHash; - bytes32 rangeVkeyCommitment; - address proverAddress; + /// @notice A dispute game type for aggregating results from TEE + ZK verifiers (multiproofs). + GameType internal constant AGGREGATE_VERIFIER = GameType.wrap(621); } diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index 5fc756e2f..5b8120b5a 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -9,6 +9,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { DisputeGameFactory_TestInit } from "test/L1/proofs/DisputeGameFactory.t.sol"; +import { MockVerifier } from "test/mocks/MockVerifier.sol"; // Scripts import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; @@ -20,23 +21,26 @@ import { Constants } from "src/libraries/Constants.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Features } from "src/libraries/Features.sol"; +import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import "src/libraries/bridge/Types.sol"; +import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; // Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { address depositor; Types.WithdrawalTransaction _defaultTx; - IFaultDisputeGameV2 game; + IAggregateVerifier game; uint256 _proposedGameIndex; uint256 _proposedBlockNumber; bytes32 _stateRoot; @@ -75,43 +79,40 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { /// @dev Setup the system for a ready-to-use state. function setUp() public virtual override { - if (isForkTest()) { - // Set the proposed block number to be the next block number on the forked network - (, _proposedBlockNumber) = anchorStateRegistry.getAnchorRoot(); - _proposedBlockNumber += 1; - - // Set the init bond of anchor game type 0 to be 0. - // It is a mapping so the storage slot is calculated as keccak256(abi.encode(key, slot)). - // The storage slot for the initBond mapping is 102, see `snapshots/storageLayout/DisputeGameFactory.json`. - vm.store( - address(disputeGameFactory), keccak256(abi.encode(GameType.wrap(0), uint256(102))), bytes32(uint256(0)) - ); - } else { - // Set up the dummy game. - _proposedBlockNumber = 0xFF; - } + respectedGameType = optimismPortal2.respectedGameType(); + MockVerifier teeVerifier = new MockVerifier(anchorStateRegistry); + MockVerifier zkVerifier = new MockVerifier(anchorStateRegistry); + AggregateVerifier gameImpl = new AggregateVerifier( + respectedGameType, + anchorStateRegistry, + delayedWeth, + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + bytes32(uint256(1)), + AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), + bytes32(uint256(4)), + deploy.cfg().l2ChainId(), + 100, + 10 + ); + disputeGameFactory.setImplementation(respectedGameType, IDisputeGame(address(gameImpl))); + disputeGameFactory.setInitBond(respectedGameType, 0); - depositor = makeAddr("depositor"); + Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); + _proposedBlockNumber = startingRoot.l2SequenceNumber + gameImpl.BLOCK_INTERVAL(); - setupFaultDisputeGame(Claim.wrap(_outputRoot)); + depositor = makeAddr("depositor"); // Warp forward in time to ensure that the game is created after the retirement timestamp. vm.warp(anchorStateRegistry.retirementTimestamp() + 1); - respectedGameType = optimismPortal2.respectedGameType(); - game = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: disputeGameFactory.initBonds(respectedGameType) }( - respectedGameType, Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber) - ) - )) - ); + game = _createDisputeGame(Claim.wrap(_outputRoot), 0); // Grab the index of the game we just created. _proposedGameIndex = disputeGameFactory.gameCount() - 1; - // Warp beyond the chess clocks and finalize the game. - vm.warp(block.timestamp + game.maxClockDuration().raw() + 1 seconds); + // Warp beyond the resolution delay. + vm.warp(game.expectedResolution().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. vm.deal(address(optimismPortal2), 0xFFFFFFFF); @@ -120,6 +121,28 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { } } + function _createDisputeGame(Claim _rootClaim, uint256 _salt) internal returns (IAggregateVerifier game_) { + bytes memory intermediateRoots; + AggregateVerifier gameImpl = AggregateVerifier(address(disputeGameFactory.gameImpls(respectedGameType))); + for (uint256 i = 1; i < gameImpl.intermediateOutputRootsCount(); i++) { + intermediateRoots = + abi.encodePacked(intermediateRoots, keccak256(abi.encode(_proposedBlockNumber, i, _salt))); + } + bytes memory extraData = + abi.encodePacked(_proposedBlockNumber, address(anchorStateRegistry), intermediateRoots, _rootClaim.raw()); + bytes memory proof = abi.encodePacked( + uint8(AggregateVerifier.ProofType.TEE), blockhash(block.number - 1), block.number - 1, bytes32(0) + ); + + game_ = IAggregateVerifier( + payable(address( + disputeGameFactory.createWithInitData{ value: disputeGameFactory.initBonds(respectedGameType) }( + respectedGameType, _rootClaim, extraData, proof + ) + )) + ); + } + /// @notice Asserts that the reentrant call will revert. function callPortalAndExpectRevert() external payable { vm.expectRevert(IOptimismPortal.OptimismPortal_NoReentrancy.selector); @@ -687,11 +710,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test }); // Create a new dispute game, and mock both games to be CHALLENGER_WINS. - IDisputeGame game2 = disputeGameFactory.create{ - value: disputeGameFactory.initBonds(optimismPortal2.respectedGameType()) - }( - optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); + IDisputeGame game2 = _createDisputeGame(Claim.wrap(_outputRoot), 1); _proposedGameIndex = disputeGameFactory.gameCount() - 1; vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); vm.mockCall(address(game2), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); @@ -779,9 +798,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); // Create a new game to re-prove against - disputeGameFactory.create{ value: disputeGameFactory.initBonds(respectedGameType) }( - respectedGameType, Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); + _createDisputeGame(Claim.wrap(_outputRoot), 1); _proposedGameIndex = disputeGameFactory.gameCount() - 1; // Warp 1 second into the future so we're not in the same block as the dispute game. @@ -815,11 +832,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test }); // Create a new game. - IDisputeGame newGame = disputeGameFactory.create{ - value: disputeGameFactory.initBonds(optimismPortal2.respectedGameType()) - }( - GameType.wrap(0), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); + IDisputeGame newGame = _createDisputeGame(Claim.wrap(_outputRoot), 1); // Update the respected game type to 0xbeef. vm.prank(optimismPortal2.guardian()); @@ -891,7 +904,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof); // Warp and resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); @@ -921,18 +933,12 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T latestBlockhash: bytes32(uint256(0)) }); - IFaultDisputeGameV2 game_noData = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: disputeGameFactory.initBonds(respectedGameType) }( - respectedGameType, Claim.wrap(_outputRoot_noData), abi.encode(_proposedBlockNumber) - ) - )) - ); + IAggregateVerifier game_noData = _createDisputeGame(Claim.wrap(_outputRoot_noData), 0); uint256 _proposedGameIndex_noData = disputeGameFactory.gameCount() - 1; - // Warp beyond the chess clocks and finalize the game. - vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); + // Warp beyond the resolution delay. + vm.warp(game_noData.expectedResolution().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); @@ -955,7 +961,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Warp and resolve the dispute game. - game_noData.resolveClaim(0, 0); game_noData.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); @@ -982,7 +987,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Warp and resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); @@ -999,11 +1003,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = address(bob).balance; // Create a secondary dispute game. - IDisputeGame secondGame = disputeGameFactory.create{ - value: disputeGameFactory.initBonds(optimismPortal2.respectedGameType()) - }( - optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); + IDisputeGame secondGame = _createDisputeGame(Claim.wrap(_outputRoot), 1); // Warp 1 second into the future so that the proof is submitted after the timestamp of game creation. vm.warp(block.timestamp + 1); @@ -1038,7 +1038,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Warp and resolve the original dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); @@ -1178,7 +1177,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1219,7 +1217,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1249,7 +1246,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1290,7 +1286,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1334,7 +1329,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T optimismPortal2.proveWithdrawalTransaction(_testTx, _proposedGameIndex, outputRootProof, withdrawalProof); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1409,7 +1403,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T assertTrue(_game.rootClaim().raw() != bytes32(0)); // Resolve the dispute game - game.resolveClaim(0, 0); game.resolve(); // Warp past the finalization period @@ -1488,7 +1481,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T assertTrue(_game.rootClaim().raw() != bytes32(0)); // Resolve the dispute game - game.resolveClaim(0, 0); game.resolve(); // Warp past the finalization period @@ -1519,7 +1511,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.prank(optimismPortal2.guardian()); @@ -1549,7 +1540,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); // Attempt to finalize the withdrawal directly after the game resolves. This should fail. @@ -1580,7 +1570,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); // Warp past the dispute game finality delay. @@ -1614,7 +1603,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); // Warp past the dispute game finality delay. @@ -1646,7 +1634,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); // Warp past the dispute game finality delay. @@ -1689,7 +1676,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Finalize the dispute game and attempt to finalize the withdrawal again. This should // also fail, since the air gap dispute game delay has not elapsed. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); @@ -1737,7 +1723,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransactionExternalProof_Test is Opti }); // Warp and resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); @@ -1800,7 +1785,6 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { }); // Warp and resolve the dispute game. - game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index b30267362..5b0de1741 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -6,7 +6,8 @@ import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry. import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { Claim, GameStatus, Hash, Timestamp } from "src/libraries/bridge/Types.sol"; +import { GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index 5452e4ee0..604f75214 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -2,40 +2,66 @@ pragma solidity ^0.8.15; // Testing -import { BaseFaultDisputeGame_TestInit, _changeClaimStatus } from "test/L1/proofs/FaultDisputeGame.t.sol"; +import { BaseTest } from "test/L1/proofs/BaseTest.t.sol"; // Libraries -import { GameType, GameStatus, Hash, Claim, VMStatuses, Proposal } from "src/libraries/bridge/Types.sol"; +import { GameType, GameStatus, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; // Interfaces import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; +import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +// Contracts +import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; /// @title AnchorStateRegistry_TestInit /// @notice Reusable test initialization for `AnchorStateRegistry` tests. -abstract contract AnchorStateRegistry_TestInit is BaseFaultDisputeGame_TestInit { +abstract contract AnchorStateRegistry_TestInit is BaseTest { + IDisputeGameFactory disputeGameFactory; + ISuperchainConfig superchainConfig; + IDisputeGame gameProxy; + /// @dev A valid l2BlockNumber that comes after the current anchor root block. uint256 validL2BlockNumber; - event AnchorUpdated(IFaultDisputeGameV2 indexed game); + event AnchorUpdated(IDisputeGame indexed game); event RespectedGameTypeSet(GameType gameType); event RetirementTimestampSet(uint256 timestamp); + event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); function setUp() public virtual override { - // Duplicating the initialization/setup logic of FaultDisputeGame_Test. - bytes memory absolutePrestateData = abi.encode(0); - Claim absolutePrestate = _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData)), VMStatuses.UNFINISHED); - super.setUp(); + disputeGameFactory = IDisputeGameFactory(address(factory)); + superchainConfig = ISuperchainConfig(makeAddr("superchain-config")); + vm.mockCall(address(superchainConfig), abi.encodeCall(superchainConfig.guardian, ()), abi.encode(address(this))); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.superchainConfig, ()), abi.encode(superchainConfig) + ); + // Get the actual anchor roots - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2BlockNumber + 1; - Claim rootClaim = Claim.wrap(Hash.unwrap(root)); - super.init({ rootClaim: rootClaim, absolutePrestate: absolutePrestate, l2BlockNumber: validL2BlockNumber }); + (, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + validL2BlockNumber = l2BlockNumber + BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(validL2BlockNumber))); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + gameProxy = IDisputeGame( + address( + _createAggregateVerifierGame( + TEE_PROVER, rootClaim, validL2BlockNumber, address(anchorStateRegistry), proof + ) + ) + ); + } + + function skipIfForkTest(string memory) public pure { } + + function _setPaused(bool _paused) internal { + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.paused, ()), abi.encode(_paused)); } } @@ -52,12 +78,12 @@ contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_TestInit { /// @notice Tests the `initialize` function of the `AnchorStateRegistry` contract. contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// @notice Tests that initialization is successful. - function test_initialize_succeeds() public { + function test_initialize_succeeds() public view { skipIfForkTest("State has changed since initialization on a forked network."); // Verify starting anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF); + assertEq(root.raw(), keccak256(abi.encode(uint256(0)))); assertEq(l2BlockNumber, 0); // Verify contract addresses. @@ -192,15 +218,13 @@ contract AnchorStateRegistry_Paused_Test is AnchorStateRegistry_TestInit { /// @notice Tests that paused() will return the correct value. function test_paused_succeeds() public { // Pause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _setPaused(true); // Paused should return true. assertTrue(anchorStateRegistry.paused()); // Unpause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.unpause(address(0)); + _setPaused(false); // Paused should return false. assertFalse(anchorStateRegistry.paused()); @@ -368,7 +392,7 @@ contract AnchorStateRegistry_Anchors_Test is AnchorStateRegistry_TestInit { contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit { /// @notice Tests that getAnchorRoot will return the value of the starting anchor root when no /// anchor game exists yet. - function test_getAnchorRoot_noAnchorGame_succeeds() public { + function test_getAnchorRoot_noAnchorGame_succeeds() public view { skipIfForkTest("On a forked network, there would most likely be an anchor game already."); // Assert that we nave no anchor game yet. @@ -376,7 +400,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit // We should get the starting anchor root back. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF); + assertEq(root.raw(), keccak256(abi.encode(uint256(0)))); assertEq(l2BlockNumber, 0); } @@ -384,7 +408,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit function test_getAnchorRoot_anchorGameExists_succeeds() public { // Mock the game to be resolved. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Mock the game to be the defender wins. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); @@ -403,7 +427,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit function test_getAnchorRoot_superchainPaused_succeeds() public { // Mock the game to be resolved. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Mock the game to be the defender wins. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); @@ -412,8 +436,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit anchorStateRegistry.setAnchorState(gameProxy); // Pause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _setPaused(true); // We should get the anchor root back. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); @@ -425,7 +448,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit function test_getAnchorRoot_blacklistedGame_succeeds() public { // Mock the game to be resolved. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Mock the game to be the defender wins. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); @@ -452,7 +475,7 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T function test_getStartingAnchorRoot_afterUpdate_succeeds() public { // Mock the game to be resolved. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Mock the game to be the defender wins. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); @@ -723,8 +746,7 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_TestInit { /// @notice Tests that isGameProper will return false if the superchain is paused. function test_isGameProper_superchainPaused_succeeds() public { // Pause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _setPaused(true); // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); @@ -755,12 +777,12 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameFinalized_isFinalized_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); // Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past. // Must be greater than 0. _resolvedAtTimestamp = - bound(_resolvedAtTimestamp, 1, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds() - 1); + bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); // Mock the resolvedAt timestamp. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); @@ -776,11 +798,13 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameFinalized_isNotAirgapped_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); // Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past. _resolvedAtTimestamp = bound( - _resolvedAtTimestamp, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds(), block.timestamp + _resolvedAtTimestamp, + block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds(), + block.timestamp ); // Mock the resolvedAt timestamp. @@ -793,7 +817,7 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameFinalized will return false if the game is not resolved. function test_isGameFinalized_isNotResolved_succeeds() public { // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); // Mock the status call to be IN_PROGRESS. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); @@ -810,11 +834,11 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameClaimValid_claimIsValid_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); // Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past. _resolvedAtTimestamp = - bound(_resolvedAtTimestamp, 1, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds() - 1); + bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); // Mock that the game was respected. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); @@ -903,11 +927,13 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameClaimValid_notAirgapped_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); // Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past. _resolvedAtTimestamp = bound( - _resolvedAtTimestamp, block.timestamp - optimismPortal2.disputeGameFinalityDelaySeconds(), block.timestamp + _resolvedAtTimestamp, + block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds(), + block.timestamp ); // Mock the resolvedAt timestamp. @@ -920,8 +946,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the superchain is paused. function test_isGameClaimValid_superchainPaused_succeeds() public { // Pause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _setPaused(true); // Game should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -953,7 +978,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Update the anchor state. vm.prank(address(gameProxy)); @@ -967,7 +992,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit assertEq(root.raw(), gameProxy.rootClaim().raw()); // Confirm that the anchor game is now set. - IFaultDisputeGameV2 anchorGame = anchorStateRegistry.anchorGame(); + IDisputeGame anchorGame = anchorStateRegistry.anchorGame(); assertEq(address(anchorGame), address(gameProxy)); } @@ -992,7 +1017,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1064,7 +1089,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1098,7 +1123,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1128,7 +1153,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Mock that the game was not respected when created. vm.mockCall( @@ -1164,7 +1189,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the resolvedAt timestamp and fast forward to beyond the delay. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); // Blacklist the game. vm.prank(superchainConfig.guardian()); @@ -1221,8 +1246,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit /// @notice Tests that setAnchorState will revert if the superchain is paused. function test_setAnchorState_superchainPaused_fails() public { // Pause the superchain. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _setPaused(true); // Update the anchor state. vm.prank(address(gameProxy)); diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 5e7da237f..068d62dc0 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -12,7 +12,8 @@ import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { Claim, GameStatus, GameType, Hash, Proposal, Timestamp } from "src/libraries/bridge/Types.sol"; +import { GameStatus, GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; // OpenZeppelin import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index 27c754758..977518aa9 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -4,7 +4,8 @@ pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/libraries/bridge/Errors.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { Claim, GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { Verifier } from "src/L1/proofs/Verifier.sol"; diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index 6c7ce22cb..dfd9a233d 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -6,21 +6,21 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Scripts import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries +import { Timestamp } from "src/libraries/bridge/LibUDT.sol"; import "src/libraries/bridge/Types.sol"; import "src/libraries/bridge/Errors.sol"; +import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; // Interfaces import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol"; +import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; + // Mocks -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; +import { MockVerifier } from "test/mocks/MockVerifier.sol"; /// @notice A fake clone used for testing the `DisputeGameFactory` contract's `create` function. contract DisputeGameFactory_FakeClone_Harness { @@ -62,32 +62,6 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { disputeGameFactory.transferOwnership(address(this)); } - /// @notice Creates a new VM instance with the given absolute prestate - function _createVM(Claim _absolutePrestate) internal returns (AlphabetVM vm_, IPreimageOracle preimageOracle_) { - // Set preimage oracle challenge period to something arbitrary (4 seconds) just so we can - // actually test the clock extensions later on. This is not a realistic value. - preimageOracle_ = IPreimageOracle( - DeployUtils.create1({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 4))) - }) - ); - vm_ = new AlphabetVM(_absolutePrestate, preimageOracle_); - } - - function _getGameConstructorParamsV2() - internal - pure - returns (IFaultDisputeGameV2.GameConstructorParams memory params_) - { - return IFaultDisputeGameV2.GameConstructorParams({ - maxGameDepth: 2 ** 3, - splitDepth: 2 ** 2, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - }); - } - function _setGame(address _gameImpl, GameType _gameType) internal { _setGame(_gameImpl, _gameType, false, ""); } @@ -106,119 +80,6 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { disputeGameFactory.setInitBond(_gameType, 0.08 ether); vm.stopPrank(); } - - /// @notice Sets up a fault game implementation - function setupFaultDisputeGame(Claim _absolutePrestate) - internal - returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - return setupFaultDisputeGameV2(_absolutePrestate); - } - - /// @notice Sets up immutable data for fault game v2 implementation - function getFaultDisputeGameV2ImmutableArgs(Claim _absolutePrestate) - internal - returns (bytes memory immutableArgs_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - (vm_, preimageOracle_) = _createVM(_absolutePrestate); - // Encode the implementation args for CWIA (tightly packed) - immutableArgs_ = abi.encodePacked( - _absolutePrestate, // 32 bytes - vm_, // 20 bytes - anchorStateRegistry, // 20 bytes - delayedWeth, // 20 bytes - l2ChainId // 32 bytes (l2ChainId) - ); - } - - /// @notice Sets up a fault game v2 implementation - function setupFaultDisputeGameV2(Claim _absolutePrestate) - internal - returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - bytes memory immutableArgs; - (immutableArgs, vm_, preimageOracle_) = getFaultDisputeGameV2ImmutableArgs(_absolutePrestate); - gameImpl_ = setupFaultDisputeGameV2(immutableArgs); - } - - function setupFaultDisputeGameV2(bytes memory immutableArgs) internal returns (address gameImpl_) { - gameImpl_ = DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IFaultDisputeGameV2.__constructor__, (_getGameConstructorParamsV2())) - ) - }); - - _setGame(gameImpl_, GameTypes.CANNON, immutableArgs); - } - - function setupPermissionedDisputeGame( - Claim _absolutePrestate, - address _proposer, - address _challenger - ) - internal - returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - return setupPermissionedDisputeGameV2(_absolutePrestate, _proposer, _challenger); - } - - function changeClaimStatus(Claim _claim, VMStatus _status) public pure returns (Claim out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _claim), shl(248, _status)) - } - } - - /// @notice Sets up immutable args for PDG v2 implementation - function getPermissionedDisputeGameV2ImmutableArgs( - Claim _absolutePrestate, - address _proposer, - address _challenger - ) - internal - returns (bytes memory implArgs_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - (vm_, preimageOracle_) = _createVM(_absolutePrestate); - - // Encode the implementation args for CWIA (tightly packed) - implArgs_ = abi.encodePacked( - _absolutePrestate, // 32 bytes - vm_, // 20 bytes - anchorStateRegistry, // 20 bytes - delayedWeth, // 20 bytes - l2ChainId, // 32 bytes (l2ChainId), - _proposer, // 20 bytes - _challenger // 20 bytes - ); - } - - /// @notice Deploys PDG v2 implementation and sets it on the DGF - function setupPermissionedDisputeGameV2( - Claim _absolutePrestate, - address _proposer, - address _challenger - ) - internal - returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) - { - bytes memory implArgs; - (implArgs, vm_, preimageOracle_) = - getPermissionedDisputeGameV2ImmutableArgs(_absolutePrestate, _proposer, _challenger); - - gameImpl_ = setupPermissionedDisputeGameV2(implArgs); - } - - /// @notice Deploys PDG v2 implementation and sets it on the DGF - function setupPermissionedDisputeGameV2(bytes memory _implArgs) internal returns (address gameImpl_) { - gameImpl_ = DeployUtils.create1({ - _name: "PermissionedDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IPermissionedDisputeGameV2.__constructor__, (_getGameConstructorParamsV2())) - ) - }); - - _setGame(gameImpl_, GameTypes.PERMISSIONED_CANNON, _implArgs); - } } /// @title DisputeGameFactory_Initialize_Test @@ -280,8 +141,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { // values. uint32 maxGameType = 8; GameType gt = GameType.wrap(uint8(bound(gameType, 0, maxGameType))); - // Ensure the rootClaim has a VMStatus that disagrees with the validity. - rootClaim = changeClaimStatus(rootClaim, VMStatuses.INVALID); // Set all three implementations to the same `FakeClone` contract. for (uint8 i; i < maxGameType + 1; i++) { @@ -325,8 +184,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible // values. GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2))); - // Ensure the rootClaim has a VMStatus that disagrees with the validity. - rootClaim = changeClaimStatus(rootClaim, VMStatuses.INVALID); // Set all three implementations to the same `FakeClone` contract. for (uint8 i; i < 3; i++) { @@ -347,8 +204,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { // that game type. uint32 maxGameType = 8; GameType gt = GameType.wrap(uint32(bound(gameType, maxGameType + 1, type(uint32).max))); - // Ensure the rootClaim has a VMStatus that disagrees with the validity. - rootClaim = changeClaimStatus(rootClaim, VMStatuses.INVALID); vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt)); disputeGameFactory.create(gt, rootClaim, extraData); @@ -361,8 +216,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { // values. uint32 maxGameType = 8; GameType gt = GameType.wrap(uint8(bound(gameType, 0, maxGameType))); - // Ensure the rootClaim has a VMStatus that disagrees with the validity. - rootClaim = changeClaimStatus(rootClaim, VMStatuses.INVALID); // Set all three implementations to the same `FakeClone` contract. for (uint8 i; i < maxGameType + 1; i++) { @@ -390,44 +243,68 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { } function test_create_implArgs_succeeds() public { - Claim absolutePrestate = Claim.wrap(bytes32(hex"dead")); - (, AlphabetVM vm_,) = setupFaultDisputeGameV2(absolutePrestate); - - Claim rootClaim = changeClaimStatus(Claim.wrap(bytes32(hex"beef")), VMStatuses.INVALID); - // extraData should contain the l2BlockNumber as first 32 bytes - bytes memory extraData = bytes.concat(bytes32(uint256(type(uint32).max))); + MockVerifier teeVerifier = new MockVerifier(anchorStateRegistry); + MockVerifier zkVerifier = new MockVerifier(anchorStateRegistry); + AggregateVerifier gameImpl = new AggregateVerifier( + GameTypes.AGGREGATE_VERIFIER, + anchorStateRegistry, + delayedWeth, + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + bytes32(uint256(1)), + AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), + bytes32(uint256(4)), + l2ChainId, + 100, + 10 + ); + _setGame(address(gameImpl), GameTypes.AGGREGATE_VERIFIER); + + Claim rootClaim = Claim.wrap(bytes32(hex"beef")); + Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); + bytes memory intermediateRoots; + for (uint256 i = 1; i < gameImpl.intermediateOutputRootsCount(); i++) { + intermediateRoots = + abi.encodePacked(intermediateRoots, keccak256(abi.encode(startingRoot.l2SequenceNumber + 10 * i))); + } + intermediateRoots = abi.encodePacked(intermediateRoots, rootClaim.raw()); + bytes memory extraData = + abi.encodePacked(startingRoot.l2SequenceNumber + 100, address(anchorStateRegistry), intermediateRoots); + bytes memory proof = abi.encodePacked( + uint8(AggregateVerifier.ProofType.TEE), blockhash(block.number - 1), block.number - 1, bytes32(0) + ); - uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.CANNON); + uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER); vm.deal(address(this), bondAmount); // Create the game - IDisputeGame proxy = disputeGameFactory.create{ value: bondAmount }(GameTypes.CANNON, rootClaim, extraData); + IDisputeGame proxy = disputeGameFactory.createWithInitData{ value: bondAmount }( + GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, proof + ); // Verify the game was created and stored - (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(GameTypes.CANNON, rootClaim, extraData); + (IDisputeGame game, Timestamp timestamp) = + disputeGameFactory.games(GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData); assertEq(address(game), address(proxy)); assertEq(Timestamp.unwrap(timestamp), block.timestamp); // Verify the game has the correct parameters via CWIA - IFaultDisputeGameV2 gameV2 = IFaultDisputeGameV2(address(proxy)); + AggregateVerifier gameV2 = AggregateVerifier(address(proxy)); // Test CWIA getters - assertEq(Claim.unwrap(gameV2.absolutePrestate()), Claim.unwrap(absolutePrestate)); assertEq(Claim.unwrap(gameV2.rootClaim()), Claim.unwrap(rootClaim)); assertEq(gameV2.extraData(), extraData); - assertEq(gameV2.l2ChainId(), l2ChainId); + assertEq(gameV2.L2_CHAIN_ID(), l2ChainId); assertEq(address(gameV2.gameCreator()), address(this)); - assertEq(gameV2.l2BlockNumber(), uint256(type(uint32).max)); - assertEq(address(gameV2.vm()), address(vm_)); - assertEq(address(gameV2.weth()), address(delayedWeth)); + assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + 100); + assertEq(gameV2.parentAddress(), address(anchorStateRegistry)); + assertEq(address(gameV2.DELAYED_WETH()), address(delayedWeth)); assertEq(address(gameV2.anchorStateRegistry()), address(anchorStateRegistry)); // Test Constructor args - assertEq(GameType.unwrap(gameV2.gameType()), GameType.unwrap(GameTypes.CANNON)); - assertEq(gameV2.maxGameDepth(), 2 ** 3); - assertEq(gameV2.splitDepth(), 2 ** 2); - assertEq(Duration.unwrap(gameV2.clockExtension()), Duration.unwrap(Duration.wrap(3 hours))); - assertEq(Duration.unwrap(gameV2.maxClockDuration()), Duration.unwrap(Duration.wrap(3.5 days))); + assertEq(GameType.unwrap(gameV2.gameType()), GameType.unwrap(GameTypes.AGGREGATE_VERIFIER)); + assertEq(gameV2.BLOCK_INTERVAL(), 100); + assertEq(gameV2.INTERMEDIATE_BLOCK_INTERVAL(), 10); } } @@ -438,13 +315,13 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni /// given `GameType`. function test_setImplementation_succeeds() public { vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit ImplementationSet(address(1), GameTypes.CANNON); + emit ImplementationSet(address(1), GameTypes.AGGREGATE_VERIFIER); - // Set the implementation for the `GameTypes.CANNON` enum value. - disputeGameFactory.setImplementation(GameTypes.CANNON, IDisputeGame(address(1))); + // Set the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value. + disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); - // Ensure that the implementation for the `GameTypes.CANNON` enum value is set. - assertEq(address(disputeGameFactory.gameImpls(GameTypes.CANNON)), address(1)); + // Ensure that the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. + assertEq(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER)), address(1)); } /// @notice Tests that the `setImplementation` function reverts when called by a non-owner. @@ -452,38 +329,34 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni // Ensure that the `setImplementation` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); - disputeGameFactory.setImplementation(GameTypes.CANNON, IDisputeGame(address(1))); + disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); } /// @notice Tests that the `setImplementation` function with args properly sets the implementation /// and args for a given `GameType`. function test_setImplementation_withArgs_succeeds() public { address fakeGame = address(1); - Claim absolutePrestate = Claim.wrap(bytes32(hex"dead")); - AlphabetVM vm_; - IPreimageOracle preimageOracle_; - (vm_, preimageOracle_) = _createVM(absolutePrestate); bytes memory args = abi.encodePacked( - absolutePrestate, // 32 bytes - vm_, // 20 bytes + bytes32(hex"dead"), // 32 bytes + address(0xbeef), // 20 bytes anchorStateRegistry, // 20 bytes delayedWeth, // 20 bytes l2ChainId // 32 bytes (l2ChainId) ); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit ImplementationSet(address(1), GameTypes.CANNON); + emit ImplementationSet(address(1), GameTypes.AGGREGATE_VERIFIER); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit ImplementationArgsSet(GameTypes.CANNON, args); + emit ImplementationArgsSet(GameTypes.AGGREGATE_VERIFIER, args); - // Set the implementation and args for the `GameTypes.CANNON` enum value. - disputeGameFactory.setImplementation(GameTypes.CANNON, IDisputeGame(fakeGame), args); + // Set the implementation and args for the `GameTypes.AGGREGATE_VERIFIER` enum value. + disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(fakeGame), args); - // Ensure that the implementation for the `GameTypes.CANNON` enum value is set. - assertEq(address(disputeGameFactory.gameImpls(GameTypes.CANNON)), address(1)); - // Ensure that the args for the `GameTypes.CANNON` enum value are set. - assertEq(disputeGameFactory.gameArgs(GameTypes.CANNON), args); + // Ensure that the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. + assertEq(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER)), address(1)); + // Ensure that the args for the `GameTypes.AGGREGATE_VERIFIER` enum value are set. + assertEq(disputeGameFactory.gameArgs(GameTypes.AGGREGATE_VERIFIER), args); } /// @notice Tests that the `setImplementation` function with args reverts when called by a non-owner. @@ -493,7 +366,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni // Ensure that the `setImplementation` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); - disputeGameFactory.setImplementation(GameTypes.CANNON, IDisputeGame(address(1)), args); + disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1)), args); } } @@ -504,22 +377,22 @@ contract DisputeGameFactory_SetInitBond_Test is DisputeGameFactory_TestInit { /// `GameType`. function test_setInitBond_succeeds() public { vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit InitBondUpdated(GameTypes.CANNON, 1 ether); + emit InitBondUpdated(GameTypes.AGGREGATE_VERIFIER, 1 ether); - // Set the init bond for the `GameTypes.CANNON` enum value. - disputeGameFactory.setInitBond(GameTypes.CANNON, 1 ether); + // Set the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value. + disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); - // Ensure that the init bond for the `GameTypes.CANNON` enum value is set. - assertEq(disputeGameFactory.initBonds(GameTypes.CANNON), 1 ether); + // Ensure that the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. + assertEq(disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER), 1 ether); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit InitBondUpdated(GameTypes.CANNON, 2 ether); + emit InitBondUpdated(GameTypes.AGGREGATE_VERIFIER, 2 ether); - // Set the init bond for the `GameTypes.CANNON` enum value. - disputeGameFactory.setInitBond(GameTypes.CANNON, 2 ether); + // Set the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value. + disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 2 ether); - // Ensure that the init bond for the `GameTypes.CANNON` enum value is set. - assertEq(disputeGameFactory.initBonds(GameTypes.CANNON), 2 ether); + // Ensure that the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. + assertEq(disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER), 2 ether); } /// @notice Tests that the `setInitBond` function reverts when called by a non-owner. @@ -527,7 +400,7 @@ contract DisputeGameFactory_SetInitBond_Test is DisputeGameFactory_TestInit { // Ensure that the `setInitBond` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); - disputeGameFactory.setInitBond(GameTypes.CANNON, 1 ether); + disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); } } @@ -579,7 +452,7 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit // The array's length should always be 0. IDisputeGameFactory.GameSearchResult[] memory games = - disputeGameFactory.findLatestGames(GameTypes.CANNON, _start, 1); + disputeGameFactory.findLatestGames(GameTypes.AGGREGATE_VERIFIER, _start, 1); assertEq(games.length, 0); } diff --git a/test/L1/proofs/FaultDisputeGame.t.sol b/test/L1/proofs/FaultDisputeGame.t.sol deleted file mode 100644 index 3c08845d4..000000000 --- a/test/L1/proofs/FaultDisputeGame.t.sol +++ /dev/null @@ -1,3173 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Testing -import { Vm } from "lib/forge-std/src/Vm.sol"; -import { DisputeGameFactory_TestInit } from "test/L1/proofs/DisputeGameFactory.t.sol"; -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; -import { stdError } from "lib/forge-std/src/StdError.sol"; - -// Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -// Contracts -import { DisputeActor, HonestDisputeActor } from "test/actors/FaultDisputeActors.sol"; - -// Libraries -import { Types } from "src/libraries/Types.sol"; -import { Hashing } from "src/libraries/Hashing.sol"; -import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; -import { LibClock } from "src/libraries/bridge/LibUDT.sol"; -import { LibPosition } from "src/libraries/bridge/LibPosition.sol"; -import "src/libraries/bridge/Types.sol"; -import "src/libraries/bridge/Errors.sol"; - -// Interfaces -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IPreimageOracle } from "interfaces/L1/proofs/IBigStepper.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; - -contract ClaimCreditReenter { - Vm internal immutable vm; - IFaultDisputeGameV2 internal immutable GAME; - uint256 public numCalls; - - constructor(IFaultDisputeGameV2 _gameProxy, Vm _vm) { - GAME = _gameProxy; - vm = _vm; - } - - function claimCredit(address _recipient) public { - numCalls += 1; - if (numCalls > 1) { - vm.expectRevert(NoCreditToClaim.selector); - } - GAME.claimCredit(_recipient); - } - - receive() external payable { - if (numCalls == 5) { - return; - } - claimCredit(address(this)); - } -} - -/// @notice Helper to change the VM status byte of a claim. -function _changeClaimStatus(Claim _claim, VMStatus _status) pure returns (Claim out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _claim), shl(248, _status)) - } -} - -/// @title BaseFaultDisputeGame_TestInit -/// @notice Base test initializer that can be used by other contracts outside of this test suite. -abstract contract BaseFaultDisputeGame_TestInit is DisputeGameFactory_TestInit { - /// @dev The type of the game being tested. - GameType internal immutable GAME_TYPE = GameTypes.CANNON; - - /// @dev The initial bond for the game. - uint256 internal initBond; - - /// @dev The implementation of the game. - IFaultDisputeGameV2 internal gameImpl; - /// @dev The `Clone` proxy of the game. - IFaultDisputeGameV2 internal gameProxy; - - /// @dev The extra data passed to the game for initialization. - bytes internal extraData; - - event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); - event GameClosed(BondDistributionMode bondDistributionMode); - - event ReceiveETH(uint256 amount); - - function init(Claim rootClaim, Claim absolutePrestate, uint256 l2BlockNumber) public { - // Set the time to a realistic date. - if (!isForkTest()) { - vm.warp(1690906994); - } - - // Set the extra data for the game creation - extraData = abi.encode(l2BlockNumber); - - (address _impl, AlphabetVM _vm,) = setupFaultDisputeGame(absolutePrestate); - gameImpl = IFaultDisputeGameV2(_impl); - - // Set the init bond for the given game type. - initBond = disputeGameFactory.initBonds(GAME_TYPE); - - // Warp ahead of the game retirement timestamp if needed. - if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { - vm.warp(anchorStateRegistry.retirementTimestamp() + 1); - } - - // Create a new game. - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, rootClaim, extraData))) - ); - - // Check immutables - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - assertEq(gameProxy.absolutePrestate().raw(), absolutePrestate.raw()); - assertEq(gameProxy.maxGameDepth(), 2 ** 3); - assertEq(gameProxy.splitDepth(), 2 ** 2); - assertEq(gameProxy.clockExtension().raw(), 3 hours); - assertEq(gameProxy.maxClockDuration().raw(), 3.5 days); - assertEq(address(gameProxy.weth()), address(delayedWeth)); - assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); - assertEq(address(gameProxy.vm()), address(_vm)); - assertEq(address(gameProxy.gameCreator()), address(this)); - assertEq(gameProxy.l2ChainId(), l2ChainId); - - // Label the proxy - vm.label(address(gameProxy), "FaultDisputeGame_Clone"); - } - - fallback() external payable { } - - receive() external payable { } - - function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { - uint256 byteCount = src.length < dest.length ? src.length : dest.length; - for (uint256 i = 0; i < byteCount; i++) { - dest[i] = src[i]; - } - return dest; - } -} - -/// @title FaultDisputeGame_TestInit -/// @notice Reusable test initialization for `FaultDisputeGame` tests. -abstract contract FaultDisputeGame_TestInit is BaseFaultDisputeGame_TestInit { - /// @dev The root claim of the game. - Claim internal ROOT_CLAIM; - /// @dev An arbitrary root claim for testing. - Claim internal arbitaryRootClaim = Claim.wrap(bytes32(uint256(123))); - - /// @dev The preimage of the absolute prestate claim - bytes internal absolutePrestateData; - /// @dev The absolute prestate of the trace. - Claim internal absolutePrestate; - /// @dev A valid l2BlockNumber that comes after the current anchor root block. - uint256 internal validL2BlockNumber; - - function setUp() public virtual override { - absolutePrestateData = abi.encode(0); - absolutePrestate = _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData)), VMStatuses.UNFINISHED); - - super.setUp(); - - // Get the actual anchor roots - (Hash root, uint256 l2Bn) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2Bn + 1; - - ROOT_CLAIM = Claim.wrap(Hash.unwrap(root)); - - super.init({ rootClaim: ROOT_CLAIM, absolutePrestate: absolutePrestate, l2BlockNumber: validL2BlockNumber }); - } - - /// @notice Helper to generate a mock RLP encoded header (with only a real block number) & an - /// output root proof. - function _generateOutputRootProof( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - bytes memory _l2BlockNumber - ) - internal - pure - returns (Types.OutputRootProof memory proof_, bytes32 root_, bytes memory rlp_) - { - // L2 Block header - bytes[] memory rawHeaderRLP = new bytes[](9); - rawHeaderRLP[0] = hex"83FACADE"; - rawHeaderRLP[1] = hex"83FACADE"; - rawHeaderRLP[2] = hex"83FACADE"; - rawHeaderRLP[3] = hex"83FACADE"; - rawHeaderRLP[4] = hex"83FACADE"; - rawHeaderRLP[5] = hex"83FACADE"; - rawHeaderRLP[6] = hex"83FACADE"; - rawHeaderRLP[7] = hex"83FACADE"; - rawHeaderRLP[8] = RLPWriter.writeBytes(_l2BlockNumber); - rlp_ = RLPWriter.writeList(rawHeaderRLP); - - // Output root - proof_ = Types.OutputRootProof({ - version: 0, - stateRoot: _storageRoot, - messagePasserStorageRoot: _withdrawalRoot, - latestBlockhash: keccak256(rlp_) - }); - root_ = Hashing.hashOutputRootProof(proof_); - } - - /// @notice Helper to get the required bond for the given claim index. - function _getRequiredBond(uint256 _claimIndex) internal view returns (uint256 bond_) { - (,,,,, Position parent,) = gameProxy.claimData(_claimIndex); - Position pos = parent.move(true); - bond_ = gameProxy.getRequiredBond(pos); - } - - /// @notice Helper to return a pseudo-random claim - function _dummyClaim() internal view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(gasleft()))); - } - - /// @notice Helper to get the localized key for an identifier in the context of the game proxy. - function _getKey(uint256 _ident, bytes32 _localContext) internal view returns (bytes32) { - bytes32 h = keccak256(abi.encode(_ident | (1 << 248), address(gameProxy), _localContext)); - return bytes32((uint256(h) & ~uint256(0xFF << 248)) | (1 << 248)); - } -} - -/// @title FaultDisputeGame_Version_Test -/// @notice Tests the `version` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_Version_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's version function returns a string. - function test_version_works() public view { - assertTrue(bytes(gameProxy.version()).length > 0); - } -} - -/// @title FaultDisputeGame_Constructor_Test -/// @notice Tests the constructor of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Constructor_Test is FaultDisputeGame_TestInit { - function setUp() public virtual override { - super.setUp(); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the - /// `MAX_GAME_DEPTH` parameter is greater than `LibPosition.MAX_POSITION_BITLEN - 1`. - function testFuzz_constructor_maxDepthTooLarge_reverts(uint256 _maxGameDepth) public { - _maxGameDepth = bound(_maxGameDepth, LibPosition.MAX_POSITION_BITLEN, type(uint256).max - 1); - vm.expectRevert(MaxDepthTooLarge.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - (IFaultDisputeGameV2.GameConstructorParams({ - maxGameDepth: _maxGameDepth, - splitDepth: _maxGameDepth + 1, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - })) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` - /// parameter is greater than or equal to the `MAX_GAME_DEPTH` - function testFuzz_constructor_invalidSplitDepth_reverts(uint256 _splitDepth) public { - uint256 maxGameDepth = 2 ** 3; - _splitDepth = bound(_splitDepth, maxGameDepth - 1, type(uint256).max); - vm.expectRevert(InvalidSplitDepth.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - (IFaultDisputeGameV2.GameConstructorParams({ - maxGameDepth: maxGameDepth, - splitDepth: _splitDepth, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - })) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` - /// parameter is less than the minimum split depth (currently 2). - function testFuzz_constructor_lowSplitDepth_reverts(uint256 _splitDepth) public { - uint256 minSplitDepth = 2; - _splitDepth = bound(_splitDepth, 0, minSplitDepth - 1); - vm.expectRevert(InvalidSplitDepth.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - (IFaultDisputeGameV2.GameConstructorParams({ - maxGameDepth: 2 ** 3, - splitDepth: _splitDepth, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - })) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when clock - /// extension * 2 is greater than the max clock duration. - function testFuzz_constructor_clockExtensionTooLong_reverts( - uint64 _maxClockDuration, - uint64 _clockExtension - ) - public - { - // Force the clock extension * 2 to be greater than the max clock duration, but keep things - // within bounds of the uint64 type. - _maxClockDuration = uint64(bound(_maxClockDuration, 0, type(uint64).max / 2 - 1)); - _clockExtension = uint64(bound(_clockExtension, _maxClockDuration / 2 + 1, type(uint64).max / 2)); - - vm.expectRevert(InvalidClockExtension.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - (IFaultDisputeGameV2.GameConstructorParams({ - maxGameDepth: 16, - splitDepth: 8, - clockExtension: Duration.wrap(_clockExtension), - maxClockDuration: Duration.wrap(_maxClockDuration) - })) - ) - ) - }); - } -} - -/// @title FaultDisputeGame_Initialize_Test -/// @notice Tests the initialization of the `FaultDisputeGame` contract. -contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game cannot be initialized with an output root that commits to <= - /// the configured starting block number - function testFuzz_initialize_cannotProposeGenesis_reverts(uint256 _blockNumber) public { - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - _blockNumber = bound(_blockNumber, 0, startingL2Block); - - Claim claim = _dummyClaim(); - vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, claim)); - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(_blockNumber)))) - ); - } - - /// @notice Tests that the proxy receives ETH from the dispute game factory. - function test_initialize_receivesETH_succeeds() public { - uint256 _value = disputeGameFactory.initBonds(GAME_TYPE); - vm.deal(address(this), _value); - - assertEq(address(gameProxy).balance, 0); - gameProxy = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: _value }( - GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber) - ) - )) - ); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), _value); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by extraData of the wrong length - function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { - // The `DisputeGameFactory` will pack the root claim and the extra data into a single - // array, which is enforced to be at least 64 bytes long. - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - // [0 bytes, 31 bytes] u [33 bytes, 23.5 KB] - _extraDataLen = bound(_extraDataLen, 0, 23_500); - if (_extraDataLen == 32) { - _extraDataLen++; - } - bytes memory _extraData = new bytes(_extraDataLen); - - // Assign the first 32 bytes in `extraData` to a valid L2 block number passed the starting - // block. - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - assembly { - mstore(add(_extraData, 0x20), add(startingL2Block, 1)) - } - - Claim claim = _dummyClaim(); - vm.expectRevert(abi.encodeWithSelector(BadExtraData.selector)); - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, _extraData))) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by additional immutable args data - function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { - (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); - - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - _extraByteCount = bound(_extraByteCount, 1, 23_500); - bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupFaultDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber)) - )) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by missing immutable args data - function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { - (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); - - _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; - bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupFaultDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber)) - )) - ); - } - - /// @notice Tests that the game is initialized with the correct data. - function test_initialize_correctData_succeeds() public view { - // Assert that the root claim is initialized correctly. - ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ) = gameProxy.claimData(0); - assertEq(parentIndex, type(uint32).max); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, initBond); - assertEq(claim.raw(), ROOT_CLAIM.raw()); - assertEq(position.raw(), 1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // Assert that the `createdAt` timestamp is correct. - assertEq(gameProxy.createdAt().raw(), block.timestamp); - - // Assert that the blockhash provided is correct. - assertEq(gameProxy.l1Head().raw(), blockhash(block.number - 1)); - } - - /// @notice Tests that the game cannot be initialized when the anchor root is not found. - function test_initialize_anchorRootNotFound_reverts() public { - // Mock the AnchorStateRegistry to return a zero root. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), - abi.encode(Hash.wrap(bytes32(0)), 0) - ); - - // Creation should fail. - vm.expectRevert(AnchorRootNotFound.selector); - gameProxy = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }(GAME_TYPE, _dummyClaim(), new bytes(uint256(32))) - )) - ); - } - - /// @notice Tests that the game cannot be initialized twice. - function test_initialize_onlyOnce_succeeds() public { - vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(); - } - - /// @notice Tests that initialization reverts when oracle challenge period is too large. - /// @dev V2 validates oracle challenge period during initialize(), not constructor - function testFuzz_initialize_oracleChallengePeriodTooLarge_reverts(uint256 _challengePeriod) public { - // Bound to values larger than uint64.max - _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); - - // Get the current AlphabetVM from the setup - (, AlphabetVM vm_,) = setupFaultDisputeGameV2(absolutePrestate); - - // Mock the VM's oracle to return invalid challenge period - vm.mockCall( - address(vm_.oracle()), abi.encodeCall(IPreimageOracle.challengePeriod, ()), abi.encode(_challengePeriod) - ); - - // Expect the initialize call to revert with InvalidChallengePeriod - vm.expectRevert(InvalidChallengePeriod.selector); - - // Create game via factory - initialize() is called automatically and should revert - gameProxy = IFaultDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }( - GAME_TYPE, _dummyClaim(), abi.encode(validL2BlockNumber) - ) - )) - ); - } -} - -/// @title FaultDisputeGame_Step_Test -/// @notice Tests the step functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_Step_Test is FaultDisputeGame_TestInit { - /// @notice Tests that a claim cannot be stepped against twice. - function test_step_duplicateStep_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - vm.expectRevert(DuplicateStep.selector); - gameProxy.step(8, true, absolutePrestateData, hex""); - } - - /// @notice Tests that successfully step with true attacking claim when there is a true defend - /// claim(claim5) in the middle of the dispute game. - function test_stepAttackDummyClaim_defendTrueClaimInTheMiddle_succeeds() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bytes memory claimData5 = abi.encode(5, 5); - Claim claim5 = Claim.wrap(keccak256(claimData5)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, claim5); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, claimData5, hex""); - } - - /// @notice Tests that successfully step with true defend claim when there is a true defend - /// claim(claim7) in the middle of the dispute game. - function test_stepDefendDummyClaim_defendTrueClaimInTheMiddle_succeeds() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(7, 7); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, postState_); - (,,,, disputed,,) = gameProxy.claimData(7); - - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, Claim.wrap(keccak256(claimData7))); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, false, claimData7, hex""); - } - - /// @notice Tests that step reverts with false attacking claim when there is a true defend - /// claim(claim5) in the middle of the dispute game. - function test_stepAttackTrueClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bytes memory claimData5 = abi.encode(5, 5); - Claim claim5 = Claim.wrap(keccak256(claimData5)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, claim5); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData5, hex"", bytes32(0))); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, postState_); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, true, claimData5, hex""); - } - - /// @notice Tests that step reverts with false defending claim when there is a true defend - /// claim(postState_) in the middle of the dispute game. - function test_stepDefendDummyClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(5, 5); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - - bytes memory _dummyClaimData = abi.encode(gasleft(), gasleft()); - Claim dummyClaim7 = Claim.wrap(keccak256(_dummyClaimData)); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, dummyClaim7); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, false, _dummyClaimData, hex""); - } - - /// @notice Tests that step reverts with true defending claim when there is a true defend - /// claim(postState_) in the middle of the dispute game. - function test_stepDefendTrueClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(5, 5); - Claim claim7 = Claim.wrap(keccak256(claimData7)); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, claim7); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, false, claimData7, hex""); - } -} - -/// @title FaultDisputeGame_Move_Test -/// @notice Tests the move functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_Move_Test is FaultDisputeGame_TestInit { - /// @notice Tests that a move while the game status is not `IN_PROGRESS` causes the call to - /// revert with the `GameNotInProgress` error - function test_move_gameNotInProgress_reverts() public { - uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS); - - // Replace the game status in storage. It exists in slot 0 at offset 16. - uint256 slot = uint256(vm.load(address(gameProxy), bytes32(0))); - uint256 offset = 16 << 3; - uint256 mask = 0xFF << offset; - // Replace the byte in the slot value with the challenger wins status. - slot = (slot & ~mask) | (chalWins << offset); - vm.store(address(gameProxy), bytes32(0), bytes32(slot)); - - // Ensure that the game status was properly updated. - GameStatus status = gameProxy.status(); - assertEq(uint256(status), chalWins); - - (,,,, Claim root,,) = gameProxy.claimData(0); - // Attempt to make a move. Should revert. - vm.expectRevert(GameNotInProgress.selector); - gameProxy.attack(root, 0, Claim.wrap(0)); - } - - /// @notice Tests that an attempt to defend the root claim reverts with the - /// `CannotDefendRootClaim` error. - function test_move_defendRoot_reverts() public { - (,,,, Claim root,,) = gameProxy.claimData(0); - vm.expectRevert(CannotDefendRootClaim.selector); - gameProxy.defend(root, 0, _dummyClaim()); - } - - /// @notice Tests that an attempt to move against a claim that does not exist reverts with the - /// `ParentDoesNotExist` error. - function test_move_nonExistentParent_reverts() public { - Claim claim = _dummyClaim(); - - // Expect an out of bounds revert for an attack - vm.expectRevert(stdError.indexOOBError); - gameProxy.attack(_dummyClaim(), 1, claim); - - // Expect an out of bounds revert for a defense - vm.expectRevert(stdError.indexOOBError); - gameProxy.defend(_dummyClaim(), 1, claim); - } - - /// @notice Tests that an attempt to move at the maximum game depth reverts with the - /// `GameDepthExceeded` error. - function test_move_gameDepthExceeded_reverts() public { - Claim claim = _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC); - - uint256 maxDepth = gameProxy.maxGameDepth(); - - for (uint256 i = 0; i <= maxDepth; i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - // At the max game depth, the `_move` function should revert with - // the `GameDepthExceeded` error. - if (i == maxDepth) { - vm.expectRevert(GameDepthExceeded.selector); - gameProxy.attack{ value: 100 ether }(disputed, i, claim); - } else { - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - } - } - - /// @notice Tests that a move made after the clock time has exceeded reverts with the - /// `ClockTimeExceeded` error. - function test_move_clockTimeExceeded_reverts() public { - // Warp ahead past the clock time for the first move (3 1/2 days) - vm.warp(block.timestamp + 3 days + 12 hours + 1); - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(ClockTimeExceeded.selector); - gameProxy.attack{ value: bond }(disputed, 0, _dummyClaim()); - } - - /// @notice Static unit test for the correctness of the chess clock incrementation. - function test_move_clockCorrectness_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - Claim claim = _dummyClaim(); - - vm.warp(block.timestamp + 15); - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - (,,,,,, clock) = gameProxy.claimData(1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(15), Timestamp.wrap(uint64(block.timestamp))).raw()); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, claim); - (,,,,,, clock) = gameProxy.claimData(2); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(10), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // We are at the split depth, so we need to set the status byte of the claim for the next - // move. - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, claim); - (,,,,,, clock) = gameProxy.claimData(3); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(25), Timestamp.wrap(uint64(block.timestamp))).raw()); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(3); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: bond }(disputed, 3, claim); - (,,,,,, clock) = gameProxy.claimData(4); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(20), Timestamp.wrap(uint64(block.timestamp))).raw()); - } - - /// @notice Tests that the standard clock extension is triggered for a move that is not the - /// split depth or the max game depth. - function test_move_standardClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Warp ahead so that the next move will trigger a clock extension. We warp to the very - // first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - clockExtension + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(0); - (,,,, disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - (,,,,,, clock) = gameProxy.claimData(1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension); - - // Warp ahead again so that clock extensions will also trigger for the other team. Here we - // only warp to the clockExtension time because we'll be warping ahead by one second during - // each additional move. - vm.warp(block.timestamp + halfGameDuration - clockExtension); - - // Work our way down to the split depth. - for (uint256 i = 1; i < splitDepth - 2; i++) { - // Warp ahead by one second so that the next move will trigger a clock extension. - vm.warp(block.timestamp + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - (,,,,,, clock) = gameProxy.claimData(i + 1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension); - } - } - - function test_move_splitDepthClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Work our way down to the split depth without moving ahead in time, we don't care about - // the exact clock here, just don't want take the clock below the clock extension time that - // we're trying to test here. - for (uint256 i = 0; i < splitDepth - 2; i++) { - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - } - - // Warp ahead to the very first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - clockExtension * 2 + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(splitDepth - 2); - (,,,, disputed,,) = gameProxy.claimData(splitDepth - 2); - gameProxy.attack{ value: bond }(disputed, splitDepth - 2, claim); - (,,,,,, clock) = gameProxy.claimData(splitDepth - 1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension * 2); - } - - function test_move_maxGameDepthClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Work our way down to the split depth without moving ahead in time, we don't care about - // the exact clock here, just don't want take the clock below the clock extension time that - // we're trying to test here. - for (uint256 i = 0; i < gameProxy.maxGameDepth() - 2; i++) { - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - - // Change the claim status when we're crossing the split depth. - if (i == splitDepth - 2) { - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - } - } - - // Warp ahead to the very first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - (clockExtension + gameProxy.vm().oracle().challengePeriod()) + 1); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(gameProxy.maxGameDepth() - 2); - (,,,, disputed,,) = gameProxy.claimData(gameProxy.maxGameDepth() - 2); - gameProxy.attack{ value: bond }(disputed, gameProxy.maxGameDepth() - 2, claim); - (,,,,,, clock) = gameProxy.claimData(gameProxy.maxGameDepth() - 1); - - // The clock should have been pushed back to the clock extension time. - assertEq( - clock.duration().raw(), halfGameDuration - (clockExtension + gameProxy.vm().oracle().challengePeriod()) - ); - } - - /// @notice Tests that an identical claim cannot be made twice. The duplicate claim attempt - /// should revert with the `ClaimAlreadyExists` error. - function test_move_duplicateClaim_reverts() public { - Claim claim = _dummyClaim(); - - // Make the first move. This should succeed. - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - - // Attempt to make the same move again. - vm.expectRevert(ClaimAlreadyExists.selector); - gameProxy.attack{ value: bond }(disputed, 0, claim); - } - - /// @notice Static unit test asserting that identical claims at the same position can be made - /// in different subgames. - function test_move_duplicateClaimsDifferentSubgames_succeeds() public { - Claim claimA = _dummyClaim(); - Claim claimB = _dummyClaim(); - - // Make the first moves. This should succeed. - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claimA); - gameProxy.attack{ value: bond }(disputed, 0, claimB); - - // Perform an attack at the same position with the same claim value in both subgames. - // These both should succeed. - bond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, claimA); - bond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, claimA); - } - - /// @notice Static unit test for the correctness of an opening attack. - function test_move_simpleAttack_succeeds() public { - // Warp ahead 5 seconds. - vm.warp(block.timestamp + 5); - - Claim counter = _dummyClaim(); - - // Perform the attack. - uint256 reqBond = _getRequiredBond(0); - vm.expectEmit(true, true, true, false); - emit Move(0, counter, address(this)); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: reqBond }(disputed, 0, counter); - - // Grab the claim data of the attack. - ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ) = gameProxy.claimData(1); - - // Assert correctness of the attack claim's data. - assertEq(parentIndex, 0); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, reqBond); - assertEq(claim.raw(), counter.raw()); - assertEq(position.raw(), Position.wrap(1).move(true).raw()); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // Grab the claim data of the parent. - (parentIndex, counteredBy, claimant, bond, claim, position, clock) = gameProxy.claimData(0); - - // Assert correctness of the parent claim's data. - assertEq(parentIndex, type(uint32).max); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, initBond); - assertEq(claim.raw(), ROOT_CLAIM.raw()); - assertEq(position.raw(), 1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp - 5))).raw()); - } - - /// @notice Tests that making a claim at the execution trace bisection root level with an - /// invalid status byte reverts with the `UnexpectedRootClaim` error. - function test_move_incorrectStatusExecRoot_reverts() public { - Claim disputed; - for (uint256 i; i < 4; i++) { - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, _dummyClaim()); - } - - uint256 bond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, bytes32(0))); - gameProxy.attack{ value: bond }(disputed, 4, Claim.wrap(bytes32(0))); - } - - /// @notice Tests that making a claim at the execution trace bisection root level with a valid - /// status byte succeeds. - function test_move_correctStatusExecRoot_succeeds() public { - Claim disputed; - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, _dummyClaim()); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - } - - /// @notice Static unit test asserting that a move reverts when the bonded amount is incorrect. - function test_move_incorrectBondAmount_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(IncorrectBondAmount.selector); - gameProxy.attack{ value: 0 }(disputed, 0, _dummyClaim()); - } - - /// @notice Static unit test asserting that a move reverts when the disputed claim does not - /// match its index. - function test_move_incorrectDisputedIndex_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - uint256 bond = _getRequiredBond(1); - vm.expectRevert(InvalidDisputedClaimIndex.selector); - gameProxy.attack{ value: bond }(disputed, 1, _dummyClaim()); - } -} - -/// @title FaultDisputeGame_AddLocalData_Test -/// @notice Tests the addLocalData functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_AddLocalData_Test is FaultDisputeGame_TestInit { - /// @notice Tests that adding local data with an out of bounds identifier reverts. - function testFuzz_addLocalData_oob_reverts(uint256 _ident) public { - Claim disputed; - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, _dummyClaim()); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - // [1, 5] are valid local data identifiers. - if (_ident <= 5) _ident = 0; - - vm.expectRevert(InvalidLocalIdent.selector); - gameProxy.addLocalData(_ident, 5, 0); - } - - /// @notice Tests that local data is loaded into the preimage oracle correctly in the subgame - /// that is disputing the transition from `GENESIS -> GENESIS + 1` - function test_addLocalDataGenesisTransition_static_succeeds() public { - IPreimageOracle oracle = IPreimageOracle(address(gameProxy.vm().oracle())); - Claim disputed; - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - // Expected start/disputed claims - (Hash root,) = gameProxy.startingOutputRoot(); - bytes32 startingClaim = root.raw(); - bytes32 disputedClaim = bytes32(uint256(3)); - Position disputedPos = LibPosition.wrap(4, 0); - - // Expected local data - bytes32[5] memory data = [ - gameProxy.l1Head().raw(), - startingClaim, - disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) - ]; - - for (uint256 i = 1; i <= 5; i++) { - uint256 expectedLen = i > 3 ? 8 : 32; - bytes32 key = _getKey(i, keccak256(abi.encode(disputedClaim, disputedPos))); - - gameProxy.addLocalData(i, 5, 0); - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - // Account for the length prefix if i > 3 (the data stored at identifiers i <= 3 are - // 32 bytes long, so the expected length is already correct. If i > 3, the data is only - // 8 bytes long, so the length prefix + the data is 16 bytes total.) - assertEq(datLen, expectedLen + (i > 3 ? 8 : 0)); - - gameProxy.addLocalData(i, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, data[i - 1]); - assertEq(datLen, expectedLen); - } - } - - /// @notice Tests that local data is loaded into the preimage oracle correctly. - function test_addLocalDataMiddle_static_succeeds() public { - IPreimageOracle oracle = IPreimageOracle(address(gameProxy.vm().oracle())); - Claim disputed; - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.defend{ value: lastBond }(disputed, 4, _changeClaimStatus(ROOT_CLAIM, VMStatuses.VALID)); - - // Expected start/disputed claims - bytes32 startingClaim = bytes32(uint256(3)); - Position startingPos = LibPosition.wrap(4, 0); - bytes32 disputedClaim = bytes32(uint256(2)); - Position disputedPos = LibPosition.wrap(3, 0); - - // Expected local data - bytes32[5] memory data = [ - gameProxy.l1Head().raw(), - startingClaim, - disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) - ]; - - for (uint256 i = 1; i <= 5; i++) { - uint256 expectedLen = i > 3 ? 8 : 32; - bytes32 key = _getKey(i, keccak256(abi.encode(startingClaim, startingPos, disputedClaim, disputedPos))); - - gameProxy.addLocalData(i, 5, 0); - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - // Account for the length prefix if i > 3 (the data stored at identifiers i <= 3 are - // 32 bytes long, so the expected length is already correct. If i > 3, the data is only - // 8 bytes long, so the length prefix + the data is 16 bytes total.) - assertEq(datLen, expectedLen + (i > 3 ? 8 : 0)); - - gameProxy.addLocalData(i, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, data[i - 1]); - assertEq(datLen, expectedLen); - } - } - - /// @notice Tests that the L2 block number claim is favored over the bisected-to block when - /// adding data. - function test_addLocalData_l2BlockNumberExtension_succeeds() public { - // Deploy a new dispute game with a L2 block number claim of 8. This is directly in the - // middle of the leaves in our output bisection test tree, at SPLIT_DEPTH = 2 ** 2 - IFaultDisputeGameV2 game = IFaultDisputeGameV2( - address( - disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(bytes32(uint256(0xFF))), abi.encode(validL2BlockNumber) - ) - ) - ); - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - { - Claim disputed; - Position parent; - Position pos; - - for (uint256 i; i < 4; i++) { - (,,,,, parent,) = game.claimData(i); - pos = parent.move(true); - uint256 bond = game.getRequiredBond(pos); - - (,,,, disputed,,) = game.claimData(i); - if (i == 0) { - game.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } else { - game.defend{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - } - (,,,,, parent,) = game.claimData(4); - pos = parent.move(true); - uint256 lastBond = game.getRequiredBond(pos); - (,,,, disputed,,) = game.claimData(4); - game.defend{ value: lastBond }(disputed, 4, _changeClaimStatus(ROOT_CLAIM, VMStatuses.INVALID)); - } - - // Expected start/disputed claims - bytes32 startingClaim = bytes32(uint256(3)); - Position startingPos = LibPosition.wrap(4, 14); - bytes32 disputedClaim = bytes32(uint256(0xFF)); - Position disputedPos = LibPosition.wrap(0, 0); - - // Expected local data. This should be `l2BlockNumber`, and not the actual bisected-to - // block, as we choose the minimum between the two. - bytes32 expectedNumber = bytes32(validL2BlockNumber << 0xC0); - uint256 expectedLen = 8; - uint256 l2NumberIdent = LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER; - - // Compute the preimage key for the local data - bytes32 localContext = keccak256(abi.encode(startingClaim, startingPos, disputedClaim, disputedPos)); - bytes32 rawKey = keccak256(abi.encode(l2NumberIdent | (1 << 248), address(game), localContext)); - bytes32 key = bytes32((uint256(rawKey) & ~uint256(0xFF << 248)) | (1 << 248)); - - IPreimageOracle oracle = IPreimageOracle(address(game.vm().oracle())); - game.addLocalData(l2NumberIdent, 5, 0); - - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - assertEq(datLen, expectedLen + 8); - - game.addLocalData(l2NumberIdent, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, expectedNumber); - assertEq(datLen, expectedLen); - } -} - -/// @title FaultDisputeGame_ChallengeRootL2Block_Test -/// @notice Tests the challengeRootL2Block functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_ChallengeRootL2Block_Test is FaultDisputeGame_TestInit { - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root succeeds. - function testFuzz_challengeRootL2Block_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - uint256 wrongL2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(wrongL2BlockNumber) - ); - - // Challenge the L2 block number. - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Ensure that a duplicate challenge reverts. - vm.expectRevert(L2BlockNumberChallenged.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertTrue(fdg.l2BlockNumberChallenged()); - } - - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root succeeds. Also, this claim should always receive the - /// bond when there is another counter that is as far left as possible. - function testFuzz_challengeRootL2Block_receivesBond_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - vm.deal(address(0xb0b), 1 ether); - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - disputeGameFactory.setInitBond(GAME_TYPE, 0.1 ether); - uint256 balanceBefore = address(this).balance; - _l2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = disputeGameFactory.create{ value: 0.1 ether }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber) - ); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - // Attack the root as 0xb0b - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = fdg.claimData(0); - vm.prank(address(0xb0b)); - fdg.attack{ value: bond }(disputed, 0, Claim.wrap(0)); - - // Challenge the L2 block number as 0xace. This claim should receive the root claim's bond. - vm.prank(address(0xace)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(1, 0); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - fdg.closeGame(); - - // Claim credit once to trigger unlock period. - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit - vm.expectRevert(NoCreditToClaim.selector); - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Ensure that the party who challenged the L2 block number with the special move received - // the bond. - // - Root claim loses their bond - // - 0xace receives the root claim's bond - // - 0xb0b receives their bond back - assertEq(address(this).balance, balanceBefore - 0.1 ether); - assertEq(address(0xb0b).balance, 1 ether); - assertEq(address(0xace).balance, 0.1 ether); - } - - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root never succeeds. - function testFuzz_challengeRootL2Block_rightBlockNumber_reverts( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = - disputeGameFactory.create{ value: initBond }(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); - - // Challenge the L2 block number. - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - vm.expectRevert(BlockNumberMatches.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badProof_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - - vm.expectRevert(InvalidOutputRootProof.selector); - gameProxy.challengeRootL2Block(outputRootProof, hex""); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badHeaderRLP_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - bytes32 outputRoot = Hashing.hashOutputRootProof(outputRootProof); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber) - ); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badHeaderRLPBlockNumberLength_reverts() public { - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot,) = - _generateOutputRootProof(0, 0, new bytes(64)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber) - ); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } -} - -/// @title FaultDisputeGame_Resolve_Test -/// @notice Tests the resolve functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_Resolve_Test is FaultDisputeGame_TestInit { - /// @notice Static unit test for the correctness an uncontested root resolution. - function test_resolve_rootUncontested_succeeds() public { - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test for the correctness an uncontested root resolution. - function test_resolve_rootUncontestedClockNotExpired_succeeds() public { - vm.warp(block.timestamp + 3 days + 12 hours - 1 seconds); - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test for the correctness of a multi-part resolution of a single claim. - function test_resolve_multiPart_succeeds() public { - vm.deal(address(this), 10_000 ether); - - uint256 bond = _getRequiredBond(0); - for (uint256 i = 0; i < 2048; i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, Claim.wrap(bytes32(i))); - } - - // Warp past the clock period. - vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds); - - // Resolve all children of the root subgame. Every single one of these will be uncontested. - for (uint256 i = 1; i <= 2048; i++) { - gameProxy.resolveClaim(i, 0); - } - - // Resolve the first half of the root claim subgame. - gameProxy.resolveClaim(0, 1024); - - // Fetch the resolution checkpoint for the root subgame and assert correctness. - (bool initCheckpoint, uint32 subgameIndex, Position leftmostPosition, address counteredBy) = - gameProxy.resolutionCheckpoints(0); - assertTrue(initCheckpoint); - assertEq(subgameIndex, 1024); - assertEq(leftmostPosition.raw(), Position.wrap(1).move(true).raw()); - assertEq(counteredBy, address(this)); - - // The root subgame should not be resolved. - assertFalse(gameProxy.resolvedSubgames(0)); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolve(); - - // Resolve the second half of the root claim subgame. - uint256 numToResolve = gameProxy.getNumToResolve(0); - assertEq(numToResolve, 1024); - gameProxy.resolveClaim(0, numToResolve); - - // Fetch the resolution checkpoint for the root subgame and assert correctness. - (initCheckpoint, subgameIndex, leftmostPosition, counteredBy) = gameProxy.resolutionCheckpoints(0); - assertTrue(initCheckpoint); - assertEq(subgameIndex, 2048); - assertEq(leftmostPosition.raw(), Position.wrap(1).move(true).raw()); - assertEq(counteredBy, address(this)); - - // The root subgame should now be resolved - assertTrue(gameProxy.resolvedSubgames(0)); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test asserting that resolve reverts when the absolute root - /// subgame has not been resolved. - function test_resolve_rootUncontestedButUnresolved_reverts() public { - vm.warp(block.timestamp + 3 days + 12 hours); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolve(); - } - - /// @notice Static unit test asserting that resolve reverts when the game state is - /// not in progress. - function test_resolve_notInProgress_reverts() public { - uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS); - - // Replace the game status in storage. It exists in slot 0 at offset 16. - uint256 slot = uint256(vm.load(address(gameProxy), bytes32(0))); - uint256 offset = 16 << 3; - uint256 mask = 0xFF << offset; - // Replace the byte in the slot value with the challenger wins status. - slot = (slot & ~mask) | (chalWins << offset); - - vm.store(address(gameProxy), bytes32(uint256(0)), bytes32(slot)); - vm.expectRevert(GameNotInProgress.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test for the correctness of resolving a single attack game state. - function test_resolve_rootContested_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game with a contested challenge - /// claim. - function test_resolve_challengeContested_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game with multiplayer moves. - function test_resolve_teamDeathmatch_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(4, 0); - gameProxy.resolveClaim(3, 0); - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game that reaches max game - /// depth. - function test_resolve_stepReached_succeeds() public { - Claim claim = _dummyClaim(); - for (uint256 i; i < gameProxy.splitDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - for (uint256 i = gameProxy.claimDataLen() - 1; i < gameProxy.maxGameDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.warp(block.timestamp + 3 days + 12 hours); - - for (uint256 i = 9; i > 0; i--) { - gameProxy.resolveClaim(i - 1, 0); - } - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve a - /// subgame multiple times - function test_resolve_claimAlreadyResolved_reverts() public { - Claim claim = _dummyClaim(); - uint256 firstBond = _getRequiredBond(0); - vm.deal(address(this), firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - uint256 secondBond = _getRequiredBond(1); - vm.deal(address(this), secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - - vm.expectRevert(ClaimAlreadyResolved.selector); - gameProxy.resolveClaim(1, 0); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve a - /// subgame at max depth - function test_resolve_claimAtMaxDepthAlreadyResolved_reverts() public { - Claim claim = _dummyClaim(); - for (uint256 i; i < gameProxy.splitDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.deal(address(this), 10000 ether); - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - for (uint256 i = gameProxy.claimDataLen() - 1; i < gameProxy.maxGameDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(8, 0); - - vm.expectRevert(ClaimAlreadyResolved.selector); - gameProxy.resolveClaim(8, 0); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve - /// subgames out of order - function test_resolve_outOfOrderResolution_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test asserting that resolve pays out bonds on step, output bisection, - /// and execution trace moves. - function test_resolve_bondPayouts_succeeds() public { - // Give the test contract some ether - uint256 bal = 1000 ether; - vm.deal(address(this), bal); - - // Make claims all the way down the tree. - uint256 bond = _getRequiredBond(0); - uint256 totalBonded = bond; - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, _dummyClaim()); - bond = _getRequiredBond(1); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, _dummyClaim()); - bond = _getRequiredBond(2); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, _dummyClaim()); - bond = _getRequiredBond(3); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: bond }(disputed, 3, _dummyClaim()); - bond = _getRequiredBond(4); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: bond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bond = _getRequiredBond(5); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: bond }(disputed, 5, _dummyClaim()); - bond = _getRequiredBond(6); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: bond }(disputed, 6, _dummyClaim()); - bond = _getRequiredBond(7); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: bond }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - // Ensure that the step successfully countered the leaf claim. - (, address counteredBy,,,,,) = gameProxy.claimData(8); - assertEq(counteredBy, address(this)); - - // Ensure we bonded the correct amounts - assertEq(address(this).balance, bal - totalBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + totalBonded); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit again to get the bond back. - gameProxy.claimCredit(address(this)); - - // Ensure that bonds were paid out correctly. - assertEq(address(this).balance, bal + initBond); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that resolve pays out bonds on step, output bisection, - /// and execution trace moves with 2 actors and a dishonest root claim. - function test_resolve_bondPayoutsSeveralActors_succeeds() public { - // Give the test contract and bob some ether - // We use the "1000 ether" literal for `bal`, the initial balance, to avoid stack too deep - //uint256 bal = 1000 ether; - address bob = address(0xb0b); - vm.deal(address(this), 1000 ether); - vm.deal(bob, 1000 ether); - - // Make claims all the way down the tree, trading off between bob and the test contract. - uint256 firstBond = _getRequiredBond(0); - uint256 thisBonded = firstBond; - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, _dummyClaim()); - - uint256 secondBond = _getRequiredBond(1); - uint256 bobBonded = secondBond; - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - - uint256 thirdBond = _getRequiredBond(2); - thisBonded += thirdBond; - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: thirdBond }(disputed, 2, _dummyClaim()); - - uint256 fourthBond = _getRequiredBond(3); - bobBonded += fourthBond; - (,,,, disputed,,) = gameProxy.claimData(3); - vm.prank(bob); - gameProxy.attack{ value: fourthBond }(disputed, 3, _dummyClaim()); - - uint256 fifthBond = _getRequiredBond(4); - thisBonded += fifthBond; - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: fifthBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - uint256 sixthBond = _getRequiredBond(5); - bobBonded += sixthBond; - (,,,, disputed,,) = gameProxy.claimData(5); - vm.prank(bob); - gameProxy.attack{ value: sixthBond }(disputed, 5, _dummyClaim()); - - uint256 seventhBond = _getRequiredBond(6); - thisBonded += seventhBond; - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: seventhBond }(disputed, 6, _dummyClaim()); - - uint256 eighthBond = _getRequiredBond(7); - bobBonded += eighthBond; - (,,,, disputed,,) = gameProxy.claimData(7); - vm.prank(bob); - gameProxy.attack{ value: eighthBond }(disputed, 7, _dummyClaim()); - - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - // Ensure that the step successfully countered the leaf claim. - (, address counteredBy,,,,,) = gameProxy.claimData(8); - assertEq(counteredBy, address(this)); - - // Ensure we bonded the correct amounts - assertEq(address(this).balance, 1000 ether - thisBonded); - assertEq(bob.balance, 1000 ether - bobBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + thisBonded + bobBonded); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - - // Resolve the game. - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit again to get the bond back. - gameProxy.claimCredit(address(this)); - - // Bob's claim should revert since it's value is 0 - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(bob); - - // Ensure that bonds were paid out correctly. - assertEq(address(this).balance, 1000 ether + initBond + bobBonded); - assertEq(bob.balance, 1000 ether - bobBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that resolve pays out bonds on moves to the leftmost - /// actor in subgames containing successful counters. - function test_resolve_leftmostBondPayout_succeeds() public { - uint256 bal = 1000 ether; - address alice = address(0xa11ce); - address bob = address(0xb0b); - address charlie = address(0xc0c); - vm.deal(address(this), bal); - vm.deal(alice, bal); - vm.deal(bob, bal); - vm.deal(charlie, bal); - - // Make claims with bob, charlie and the test contract on defense, and alice as the - // challenger charlie is successfully countered by alice alice is successfully countered by - // both bob and the test contract - uint256 firstBond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, _dummyClaim()); - - uint256 secondBond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.defend{ value: secondBond }(disputed, 1, _dummyClaim()); - vm.prank(charlie); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - - uint256 thirdBond = _getRequiredBond(3); - (,,,, disputed,,) = gameProxy.claimData(3); - vm.prank(alice); - gameProxy.attack{ value: thirdBond }(disputed, 3, _dummyClaim()); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - gameProxy.claimCredit(charlie); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // All of these claims should work. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Charlie's claim should revert since it's value is 0 - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(charlie); - - // Ensure that bonds were paid out correctly. - uint256 aliceLosses = firstBond; - uint256 charlieLosses = secondBond; - assertEq(address(this).balance, bal + aliceLosses + initBond, "incorrect this balance"); - assertEq(alice.balance, bal - aliceLosses + charlieLosses, "incorrect alice balance"); - assertEq(bob.balance, bal, "incorrect bob balance"); - assertEq(charlie.balance, bal - charlieLosses, "incorrect charlie balance"); - assertEq(address(gameProxy).balance, 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that the anchor state updates when the game resolves in - /// favor of the defender and the anchor state is older than the game state. - function test_resolve_validNewerStateUpdatesAnchor_succeeds() public { - // Confirm that the anchor state is older than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber < gameProxy.l2BlockNumber()); - - // Resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is now the same as the game state. - (root, l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); - assertEq(root.raw(), gameProxy.rootClaim().raw()); - } - - /// @notice Static unit test asserting that the anchor state does not change when the game - /// resolves in favor of the defender but the game state is not newer than the anchor - /// state. - function test_resolve_validOlderStateSameAnchor_succeeds() public { - // Mock the game block to be older than the game state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(0)); - - // Confirm that the anchor state is newer than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber >= gameProxy.l2SequenceNumber()); - - // Resolve the game. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(0)); - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is the same as the initial anchor state. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); - } - - /// @notice Static unit test asserting that the anchor state does not change when the game - /// resolves in favor of the challenger, even if the game state is newer than the - /// anchor state. - function test_resolve_invalidStateSameAnchor_succeeds() public { - // Confirm that the anchor state is older than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber < gameProxy.l2BlockNumber()); - - // Challenge the claim and resolve it. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is the same as the initial anchor state. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); - } -} - -/// @title FaultDisputeGame_GameType_Test -/// @notice Tests the `gameType` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_GameType_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's type is set correctly. - function test_gameType_succeeds() public view { - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - } -} - -/// @title FaultDisputeGame_RootClaim_Test -/// @notice Tests the `rootClaim` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_RootClaim_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's root claim is set correctly. - function test_rootClaim_succeeds() public view { - assertEq(gameProxy.rootClaim().raw(), ROOT_CLAIM.raw()); - } -} - -/// @title FaultDisputeGame_ExtraData_Test -/// @notice Tests the `extraData` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_ExtraData_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's extra data is set correctly. - function test_extraData_succeeds() public view { - assertEq(gameProxy.extraData(), extraData); - } -} - -/// @title FaultDisputeGame_GameData_Test -/// @notice Tests the `gameData` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_GameData_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's data is set correctly. - function test_gameData_succeeds() public view { - (GameType gameType, Claim rootClaim, bytes memory _extraData) = gameProxy.gameData(); - - assertEq(gameType.raw(), GAME_TYPE.raw()); - assertEq(rootClaim.raw(), ROOT_CLAIM.raw()); - assertEq(_extraData, extraData); - } -} - -/// @title FaultDisputeGame_GetRequiredBond_Test -/// @notice Tests the `getRequiredBond` function of the `FaultDisputeGame` contract. -contract FaultDisputeGame_GetRequiredBond_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the bond during the bisection game depths is correct. - function test_getRequiredBond_succeeds() public view { - for (uint8 i = 0; i < uint8(gameProxy.splitDepth()); i++) { - Position pos = LibPosition.wrap(i, 0); - uint256 bond = gameProxy.getRequiredBond(pos); - - // Reasonable approximation for a max depth of 8. - uint256 expected = 0.08 ether; - for (uint64 j = 0; j < i; j++) { - expected = expected * 22876; - expected = expected / 10000; - } - - assertApproxEqAbs(bond, expected, 0.01 ether); - } - } - - /// @notice Tests that the bond at a depth greater than the maximum game depth reverts. - function test_getRequiredBond_outOfBounds_reverts() public { - Position pos = LibPosition.wrap(uint8(gameProxy.maxGameDepth() + 1), 0); - vm.expectRevert(GameDepthExceeded.selector); - gameProxy.getRequiredBond(pos); - } -} - -/// @title FaultDisputeGame_ClaimCredit_Test -/// @notice Tests the claimCredit functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_ClaimCredit_Test is FaultDisputeGame_TestInit { - function test_claimCredit_refundMode_succeeds() public { - // Set up actors. - address alice = address(0xa11ce); - address bob = address(0xb0b); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - - // Bond the first claim. - uint256 firstBond = _getRequiredBond(0); - vm.deal(alice, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - - // Bond the second claim. - uint256 secondBond = _getRequiredBond(1); - vm.deal(bob, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve the game. - // Second claim wins, so bob should get alice's credit. - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Mock that the game proxy is not proper, trigger refund mode. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.isGameProper, (gameProxy)), - abi.encode(false) - ); - - // Close the game. - gameProxy.closeGame(); - - // Assert bond distribution mode is refund mode. - assertTrue(gameProxy.bondDistributionMode() == BondDistributionMode.REFUND); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Grab balances before claim. - uint256 aliceBalanceBefore = alice.balance; - uint256 bobBalanceBefore = bob.balance; - - // Claim credit again to get the bond back. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Should have original balance again. - assertEq(alice.balance, aliceBalanceBefore + firstBond); - assertEq(bob.balance, bobBalanceBefore + secondBond); - } - - /// @notice Tests that claimCredit reverts if the game is paused. - function test_claimCredit_gamePaused_reverts() public { - // Pause the system with the Superchain-wide identifier (address(0)). - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); - - // Attempting to claim credit should now revert. - vm.expectRevert(GamePaused.selector); - gameProxy.claimCredit(address(0)); - } - - /// @notice Static unit test asserting that credit may not be drained past allowance through - /// reentrancy. - function test_claimCredit_claimAlreadyResolved_reverts() public { - ClaimCreditReenter reenter = new ClaimCreditReenter(gameProxy, vm); - vm.startPrank(address(reenter)); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - uint256 firstBond = _getRequiredBond(0); - vm.deal(address(reenter), firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - uint256 secondBond = _getRequiredBond(1); - vm.deal(address(reenter), secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - uint256 reenterBond = firstBond + secondBond; - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Ensure that we bonded all the test contract's ETH - assertEq(address(reenter).balance, 0); - // Ensure the game proxy has 1 ether in it. - assertEq(address(gameProxy).balance, 1 ether); - // Ensure the game has a balance of reenterBond in the delayedWeth contract. - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + reenterBond); - - // Resolve the claim at index 2 first so that index 1 can be resolved. - gameProxy.resolveClaim(2, 0); - - // Resolve the claim at index 1 and claim the reenter contract's credit. - gameProxy.resolveClaim(1, 0); - - // Ensure that the game registered the `reenter` contract's credit. - assertEq(gameProxy.credit(address(reenter)), reenterBond); - - // Resolve the root claim. - gameProxy.resolveClaim(0, 0); - - // Resolve the game. - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(reenter)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Initiate the reentrant credit claim. - reenter.claimCredit(address(reenter)); - - // The reenter contract should have performed 2 calls to `claimCredit`. - // Once all the credit is claimed, all subsequent calls will revert since there is 0 credit - // left to claim. - // The claimant must only have received the amount bonded for the gindex 1 subgame. - // The root claim bond and the unregistered ETH should still exist in the game proxy. - assertEq(reenter.numCalls(), 2); - assertEq(address(reenter).balance, reenterBond); - assertEq(address(gameProxy).balance, 1 ether); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond); - - vm.stopPrank(); - } - - /// @notice Tests that claimCredit reverts when recipient can't receive value. - function test_claimCredit_recipientCantReceiveValue_reverts() public { - // Set up actors. - address alice = address(0xa11ce); - address bob = address(0xb0b); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - - // Bond the first claim. - uint256 firstBond = _getRequiredBond(0); - vm.deal(alice, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - - // Bond the second claim. - uint256 secondBond = _getRequiredBond(1); - vm.deal(bob, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve the game. - // Second claim wins, so bob should get alice's credit. - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Make bob not be able to receive value by setting his contract code to something without - // `receive` - vm.etch(address(bob), address(L1Token).code); - - vm.expectRevert(BondTransferFailed.selector); - gameProxy.claimCredit(address(bob)); - } -} - -/// @title FaultDisputeGame_CloseGame_Test -/// @notice Tests the closeGame functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGame_CloseGame_Test is FaultDisputeGame_TestInit { - /// @notice Tests that closeGame reverts if the game is not resolved - function test_closeGame_gameNotResolved_reverts() public { - vm.expectRevert(GameNotResolved.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame reverts if the game is paused - function test_closeGame_gamePaused_reverts() public { - // Pause the system with the Superchain-wide identifier (address(0)). - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); - - // Attempting to close the game should now revert. - vm.expectRevert(GamePaused.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame reverts if the game is not finalized - function test_closeGame_gameNotFinalized_reverts() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Don't wait the finalization delay - vm.expectRevert(GameNotFinalized.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame succeeds for a proper game (normal distribution) - function test_closeGame_properGame_succeeds() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game and verify normal distribution mode - vm.expectEmit(true, true, true, true); - emit GameClosed(BondDistributionMode.NORMAL); - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - // Check that the anchor state was set correctly. - assertEq(address(gameProxy.anchorStateRegistry().anchorGame()), address(gameProxy)); - } - - /// @notice Tests that closeGame succeeds for an improper game (refund mode) - function test_closeGame_improperGame_succeeds() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Mock the anchor registry to return improper game - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.isGameProper, (IDisputeGame(address(gameProxy)))), - abi.encode(false, "") - ); - - // Close the game and verify refund mode - vm.expectEmit(true, true, true, true); - emit GameClosed(BondDistributionMode.REFUND); - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.REFUND)); - } - - /// @notice Tests that multiple calls to closeGame succeed after initial distribution mode is - /// set - function test_closeGame_multipleCallsAfterSet_succeeds() public { - // Resolve and close the game first - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // First close sets the mode - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - // Subsequent closes should succeed without changing the mode - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - } - - /// @notice Tests that closeGame called with any amount of gas either reverts (with OOG) or - /// updates the anchor state. This is specifically to verify that the try/catch inside - /// closeGame can't be called with just enough gas to OOG when calling the - /// AnchorStateRegistry but successfully execute the remainder of the function. - /// @param _gas Amount of gas to provide to closeGame. - function testFuzz_closeGame_canUpdateAnchorStateAndDoes_succeeds(uint256 _gas) public { - // Resolve and close the game first - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Since providing *too* much gas isn't the issue here, bounding it to half the block gas - // limit is sufficient. We want to know that either (1) the function reverts or (2) the - // anchor state gets updated. If the function doesn't revert and the anchor state isn't - // updated then we have a problem. - _gas = bound(_gas, 0, block.gaslimit / 2); - - // The anchor state should not be the game proxy. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) != address(gameProxy)); - - // Try closing the game. - try gameProxy.closeGame{ gas: _gas }() { - // If we got here, the function didn't revert, so the anchor state should have updated. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) == address(gameProxy)); - } catch { - // Ok, function reverted. - } - } -} - -/// @title FaultDisputeGame_GetChallengerDuration_Test -/// @notice Tests the getChallengerDuration functionality and related resolution tests. -contract FaultDisputeGame_GetChallengerDuration_Test is FaultDisputeGame_TestInit { - /// @notice Tests that if the game is not in progress, querying of `getChallengerDuration` - /// reverts - function test_getChallengerDuration_gameNotInProgress_reverts() public { - // resolve the game - vm.warp(block.timestamp + gameProxy.maxClockDuration().raw()); - - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - vm.expectRevert(GameNotInProgress.selector); - gameProxy.getChallengerDuration(1); - } - - /// @notice Static unit test asserting that resolveClaim isn't possible if there's time left - /// for a counter. - function test_resolution_lastSecondDisputes_succeeds() public { - // The honest proposer created an honest root claim during setup - node 0 - - // Defender's turn - vm.warp(block.timestamp + 3.5 days - 1 seconds); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days - 1 seconds); - assertEq(gameProxy.getChallengerDuration(1).raw(), 0); - - // Advance time by 1 second, so that the root claim challenger clock is expired. - vm.warp(block.timestamp + 1 seconds); - // Attempt a second attack against the root claim. This should revert since the challenger - // clock is expired. - uint256 expectedBond = _getRequiredBond(0); - vm.expectRevert(ClockTimeExceeded.selector); - gameProxy.attack{ value: expectedBond }(disputed, 0, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 1 seconds); - - // Should not be able to resolve the root claim or second counter yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // Warp to the last second of the root claim defender clock. - vm.warp(block.timestamp + 3.5 days - 2 seconds); - // Attack the challenge to the root claim. This should succeed, since the defender clock is - // not expired. - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days - 1 seconds); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days - gameProxy.clockExtension().raw()); - - // Should not be able to resolve any claims yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(2, 0); - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - vm.warp(block.timestamp + gameProxy.clockExtension().raw() - 1 seconds); - - // Should not be able to resolve any claims yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(2, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days - 1 seconds); - - // Warp past the challenge period for the root claim defender. Defending the root claim - // should now revert. - vm.warp(block.timestamp + 1 seconds); - expectedBond = _getRequiredBond(1); - vm.expectRevert(ClockTimeExceeded.selector); // no further move can be made - gameProxy.attack{ value: expectedBond }(disputed, 1, _dummyClaim()); - expectedBond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - vm.expectRevert(ClockTimeExceeded.selector); // no further move can be made - gameProxy.attack{ value: expectedBond }(disputed, 2, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days); - - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // All clocks are expired. Resolve the game. - gameProxy.resolveClaim(2, 0); // Node 2 is resolved as UNCOUNTERED by default since it has no children - gameProxy.resolveClaim(1, 0); // Node 1 is resolved as COUNTERED since it has an UNCOUNTERED child - gameProxy.resolveClaim(0, 0); // Node 0 is resolved as UNCOUNTERED since it has no UNCOUNTERED children - - // Defender wins game since the root claim is uncountered - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } -} - -/// @title FaultDisputeGame_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `FaultDisputeGame` -/// contract or are testing multiple functions at once. -contract FaultDisputeGame_Uncategorized_Test is FaultDisputeGame_TestInit { - /// @notice Tests that the game's starting timestamp is set correctly. - function test_createdAt_succeeds() public view { - assertEq(gameProxy.createdAt().raw(), block.timestamp); - } - - /// @notice Tests that startingOutputRoot and it's getters are set correctly. - function test_startingOutputRootGetters_succeeds() public view { - (Hash root, uint256 l2BlockNumber) = gameProxy.startingOutputRoot(); - (Hash anchorRoot, uint256 anchorRootBlockNumber) = anchorStateRegistry.anchors(GAME_TYPE); - - assertEq(gameProxy.startingBlockNumber(), l2BlockNumber); - assertEq(gameProxy.startingBlockNumber(), anchorRootBlockNumber); - assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(root)); - assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(anchorRoot)); - } - - /// @notice Tests that the user cannot control the first 4 bytes of the CWIA data, disallowing - /// them to control the entrypoint when no calldata is provided to a call. - function test_cwiaCalldata_userCannotControlSelector_succeeds() public { - // Construct the expected CWIA data that the proxy will pass to the implementation, - // alongside any extra calldata passed by the user. - Hash l1Head = gameProxy.l1Head(); - bytes memory cwiaData = - abi.encodePacked(address(this), gameProxy.rootClaim(), l1Head, gameProxy.gameType(), gameProxy.extraData()); - - // We expect a `ReceiveETH` event to be emitted when 0 bytes of calldata are sent; The - // fallback is always reached *within the minimal proxy* in `LibClone`'s version of - // `clones-with-immutable-args` - vm.expectEmit(false, false, false, true); - emit ReceiveETH(0); - // We expect no delegatecall to the implementation contract if 0 bytes are sent. Assert - // that this happens 0 times. - vm.expectCall(address(gameImpl), cwiaData, 0); - (bool successA,) = address(gameProxy).call(hex""); - assertTrue(successA); - - // When calldata is forwarded, we do expect a delegatecall to the implementation. - bytes memory data = abi.encodePacked(gameProxy.l1Head.selector); - vm.expectCall(address(gameImpl), abi.encodePacked(data, cwiaData), 1); - (bool successB, bytes memory returnData) = address(gameProxy).call(data); - assertTrue(successB); - assertEq(returnData, abi.encode(l1Head)); - } -} - -contract FaultDispute_1v1_Actors_Test is FaultDisputeGame_TestInit { - /// @notice The honest actor - DisputeActor internal honest; - /// @notice The dishonest actor - DisputeActor internal dishonest; - - function setUp() public override { - // Setup the `FaultDisputeGame` - super.setUp(); - } - - /// @notice Fuzz test for a 1v1 output bisection dispute. - /// @notice The alphabet game has a constant status byte, and is not safe from someone being - /// dishonest in output bisection and then posting a correct execution trace bisection - /// root claim. This test does not cover this case (i.e. root claim of output bisection - /// is dishonest, root claim of execution trace bisection is made by the dishonest - /// actor but is honest, honest actor cannot attack it without risk of losing). - function testFuzz_outputBisection1v1honestRoot_succeeds(uint8 _divergeOutput, uint8 _divergeStep) public { - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - uint256 divergeAtOutput = bound(_divergeOutput, 0, 15); - uint256 divergeAtStep = bound(_divergeStep, 0, 7); - uint256 divergeStepOffset = (divergeAtOutput << 4) + divergeAtStep; - - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i >= divergeAtOutput ? 0xFF : i + 1; - } - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i >= divergeStepOffset ? bytes1(uint8(0xFF)) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRootGenesisAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all set bits. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = bytes1(0xFF); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootGenesisAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of all set bits. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = bytes1(0xFF); - } - - // Run the actor test - _actorTest({ - _rootClaim: 17, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRoot_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all zeros. - bytes memory dishonestTrace = new bytes(256); - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRoot_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all zeros. - bytes memory dishonestTrace = new bytes(256); - - // Run the actor test - _actorTest({ - _rootClaim: 17, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1correctRootHalfWay_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 4) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootHalfWay_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 4) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1correctAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace correct is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > 127 ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace correct is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > 127 ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRootFinalInstruction_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, and correct all the way up to the final instruction - // of the exec subgame. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 7) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootFinalInstruction_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, and correct all the way up to the final instruction - // of the exec subgame. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 7) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - //////////////////////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////////////////////// - - /// @notice Helper to run a 1v1 actor test - function _actorTest( - uint256 _rootClaim, - uint256 _absolutePrestateData, - bytes memory _honestTrace, - uint256[] memory _honestL2Outputs, - bytes memory _dishonestTrace, - uint256[] memory _dishonestL2Outputs, - GameStatus _expectedStatus - ) - internal - { - if (isForkTest()) { - // Mock the call anchorStateRegistry.getAnchorRoot() to return 0 as the block number - (Hash root,) = anchorStateRegistry.getAnchorRoot(); - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), - abi.encode(root, 0) - ); - } - - // Setup the environment - bytes memory absolutePrestateData = - _setup({ _absolutePrestateData: _absolutePrestateData, _rootClaim: _rootClaim }); - - // Create actors - _createActors({ - _honestTrace: _honestTrace, - _honestPreStateData: absolutePrestateData, - _honestL2Outputs: _honestL2Outputs, - _dishonestTrace: _dishonestTrace, - _dishonestPreStateData: absolutePrestateData, - _dishonestL2Outputs: _dishonestL2Outputs - }); - - // Exhaust all moves from both actors - _exhaustMoves(); - - // Resolve the game and assert that the defender won - _warpAndResolve(); - assertEq(uint8(gameProxy.status()), uint8(_expectedStatus)); - } - - /// @notice Helper to setup the 1v1 test - function _setup( - uint256 _absolutePrestateData, - uint256 _rootClaim - ) - internal - returns (bytes memory absolutePrestateData_) - { - absolutePrestateData_ = abi.encode(_absolutePrestateData); - Claim absolutePrestateExec = - _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData_)), VMStatuses.UNFINISHED); - Claim rootClaim = Claim.wrap(bytes32(uint256(_rootClaim))); - super.init({ rootClaim: rootClaim, absolutePrestate: absolutePrestateExec, l2BlockNumber: _rootClaim }); - } - - /// @notice Helper to create actors for the 1v1 dispute. - function _createActors( - bytes memory _honestTrace, - bytes memory _honestPreStateData, - uint256[] memory _honestL2Outputs, - bytes memory _dishonestTrace, - bytes memory _dishonestPreStateData, - uint256[] memory _dishonestL2Outputs - ) - internal - { - honest = new HonestDisputeActor({ - _gameProxy: gameProxy, - _l2Outputs: _honestL2Outputs, - _trace: _honestTrace, - _preStateData: _honestPreStateData - }); - dishonest = new HonestDisputeActor({ - _gameProxy: gameProxy, - _l2Outputs: _dishonestL2Outputs, - _trace: _dishonestTrace, - _preStateData: _dishonestPreStateData - }); - - vm.deal(address(honest), 100 ether); - vm.deal(address(dishonest), 100 ether); - vm.label(address(honest), "HonestActor"); - vm.label(address(dishonest), "DishonestActor"); - } - - /// @notice Helper to exhaust all moves from both actors. - function _exhaustMoves() internal { - while (true) { - // Allow the dishonest actor to make their moves, and then the honest actor. - (uint256 numMovesA,) = dishonest.move(); - (uint256 numMovesB, bool success) = honest.move(); - - require(success, "FaultDispute_1v1_Actors_Test: Honest actor's moves should always be successful"); - - // If both actors have run out of moves, we're done. - if (numMovesA == 0 && numMovesB == 0) break; - } - } - - /// @notice Helper to warp past the chess clock and resolve all claims within the dispute game. - function _warpAndResolve() internal { - // Warp past the chess clock - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve all claims in reverse order. We allow `resolveClaim` calls to fail due to the - // check that prevents claims with no subgames attached from being passed to - // `resolveClaim`. There's also a check in `resolve` to ensure all children have been - // resolved before global resolution, which catches any unresolved subgames here. - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - } -} diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index 7a5e1a2e3..b70b74b8d 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -2,7 +2,8 @@ pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/libraries/bridge/Errors.sol"; -import { Claim, GameStatus } from "src/libraries/bridge/Types.sol"; +import { GameStatus } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; diff --git a/test/L1/proofs/PermissionedDisputeGame.t.sol b/test/L1/proofs/PermissionedDisputeGame.t.sol deleted file mode 100644 index 266c26303..000000000 --- a/test/L1/proofs/PermissionedDisputeGame.t.sol +++ /dev/null @@ -1,417 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Testing -import { DisputeGameFactory_TestInit } from "test/L1/proofs/DisputeGameFactory.t.sol"; -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; - -// Libraries -import "src/libraries/bridge/Types.sol"; -import "src/libraries/bridge/Errors.sol"; - -// Interfaces -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol"; - -/// @title PermissionedDisputeGame_TestInit -/// @notice Reusable test initialization for `PermissionedDisputeGame` tests. -abstract contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { - /// @notice The type of the game being tested. - GameType internal immutable GAME_TYPE = GameTypes.PERMISSIONED_CANNON; - /// @notice Mock proposer key - address internal constant PROPOSER = address(0xfacade9); - /// @notice Mock challenger key - address internal constant CHALLENGER = address(0xfacadec); - - /// @dev The initial bond for the game. - uint256 internal initBond; - - /// @notice The implementation of the game. - IPermissionedDisputeGameV2 internal gameImpl; - /// @notice The `Clone` proxy of the game. - IPermissionedDisputeGameV2 internal gameProxy; - - /// @notice The extra data passed to the game for initialization. - bytes internal extraData; - - /// @notice The root claim of the game. - Claim internal rootClaim; - /// @notice An arbitrary root claim for testing. - Claim internal arbitaryRootClaim = Claim.wrap(bytes32(uint256(123))); - /// @notice Minimum bond value that covers all possible moves. - uint256 internal constant MIN_BOND = 50 ether; - - /// @notice The preimage of the absolute prestate claim - bytes internal absolutePrestateData; - /// @notice The absolute prestate of the trace. - Claim internal absolutePrestate; - /// @notice A valid l2BlockNumber that comes after the current anchor root block. - uint256 validL2BlockNumber; - - event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); - - function init(Claim _rootClaim, Claim _absolutePrestate, uint256 _l2BlockNumber) public { - // Set the time to a realistic date. - if (!isForkTest()) { - vm.warp(1690906994); - } - - // Fund the proposer on this fork. - vm.deal(PROPOSER, 100 ether); - - // Set the extra data for the game creation - extraData = abi.encode(_l2BlockNumber); - - (address _impl, AlphabetVM _vm,) = setupPermissionedDisputeGame(_absolutePrestate, PROPOSER, CHALLENGER); - gameImpl = IPermissionedDisputeGameV2(_impl); - - // Create a new game. - initBond = disputeGameFactory.initBonds(GAME_TYPE); - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.anchors, (GAME_TYPE)), - abi.encode(_rootClaim, 0) - ); - vm.prank(PROPOSER, PROPOSER); - gameProxy = IPermissionedDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _rootClaim, extraData))) - ); - - // Check immutables - assertEq(gameProxy.proposer(), PROPOSER); - assertEq(gameProxy.challenger(), CHALLENGER); - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - assertEq(gameProxy.absolutePrestate().raw(), _absolutePrestate.raw()); - assertEq(gameProxy.maxGameDepth(), 2 ** 3); - assertEq(gameProxy.splitDepth(), 2 ** 2); - assertEq(gameProxy.clockExtension().raw(), 3 hours); - assertEq(gameProxy.maxClockDuration().raw(), 3.5 days); - assertEq(address(gameProxy.weth()), address(delayedWeth)); - assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); - assertEq(address(gameProxy.vm()), address(_vm)); - assertEq(address(gameProxy.gameCreator()), PROPOSER); - assertEq(gameProxy.l2ChainId(), l2ChainId); - - // Label the proxy - vm.label(address(gameProxy), "PermissionedDisputeGame_Clone"); - } - - function setUp() public override { - absolutePrestateData = abi.encode(0); - absolutePrestate = _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData)), VMStatuses.UNFINISHED); - - super.setUp(); - - // Get the actual anchor roots - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2BlockNumber + 1; - rootClaim = Claim.wrap(Hash.unwrap(root)); - init({ _rootClaim: rootClaim, _absolutePrestate: absolutePrestate, _l2BlockNumber: validL2BlockNumber }); - } - - /// @dev Helper to return a pseudo-random claim - function _dummyClaim() internal view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(gasleft()))); - } - - /// @dev Helper to get the required bond for the given claim index. - function _getRequiredBond(uint256 _claimIndex) internal view returns (uint256 bond_) { - (,,,,, Position parent,) = gameProxy.claimData(_claimIndex); - Position pos = parent.move(true); - bond_ = gameProxy.getRequiredBond(pos); - } - - /// @dev Helper to change the VM status byte of a claim. - function _changeClaimStatus(Claim _claim, VMStatus _status) internal pure returns (Claim out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _claim), shl(248, _status)) - } - } - - fallback() external payable { } - - receive() external payable { } - - function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { - uint256 byteCount = src.length < dest.length ? src.length : dest.length; - for (uint256 i = 0; i < byteCount; i++) { - dest[i] = src[i]; - } - return dest; - } -} - -/// @title PermissionedDisputeGame_Version_Test -/// @notice Tests the `version` function of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGame_Version_Test is PermissionedDisputeGame_TestInit { - /// @notice Tests that the game's version function returns a string. - function test_version_works() public view { - assertTrue(bytes(gameProxy.version()).length > 0); - } -} - -/// @title PermissionedDisputeGame_Step_Test -/// @notice Tests the `step` function of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { - /// @notice Tests that step works properly for the challenger. - function test_step_fromChallenger_succeeds() public { - validateStepForActor(CHALLENGER); - } - - /// @notice Tests that step works properly for the proposer. - function test_step_fromProposer_succeeds() public { - validateStepForActor(PROPOSER); - } - - function validateStepForActor(address actor) internal { - vm.deal(actor, 1_000 ether); - vm.startPrank(actor, actor); - - // Set up and perform the step - setupGameForStep(); - performStep(); - assertEq(gameProxy.claimDataLen(), 9); - - // Resolve the game and check that the expected actor countered the root claim - resolveGame(); - assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); - assertEq(gameProxy.resolvedAt().raw(), block.timestamp); - (, address counteredBy,,,,,) = gameProxy.claimData(0); - assertEq(counteredBy, actor); - - vm.stopPrank(); - } - - /// @notice Tests that step reverts for unauthorized addresses. - function test_step_notAuthorized_reverts(address _unauthorized) internal { - vm.assume(_unauthorized != PROPOSER && _unauthorized != CHALLENGER); - vm.deal(_unauthorized, 1_000 ether); - vm.deal(CHALLENGER, 1_000 ether); - - // Set up for the step using an authorized actor - vm.startPrank(CHALLENGER, CHALLENGER); - setupGameForStep(); - vm.stopPrank(); - - // Perform step with the unauthorized actor - vm.startPrank(_unauthorized, _unauthorized); - vm.expectRevert(BadAuth.selector); - performStep(); - - // Game should still be in progress, leaf claim should be missing - assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); - assertEq(gameProxy.claimDataLen(), 8); - - vm.stopPrank(); - } - - function setupGameForStep() internal { - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - - // Verify game state and add local data - assertEq(uint256(gameProxy.status()), uint256(GameStatus.IN_PROGRESS)); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - } - - function performStep() internal { - gameProxy.step(8, true, absolutePrestateData, hex""); - } - - function resolveGame() internal { - vm.warp(block.timestamp + gameProxy.maxClockDuration().raw() + 1); - gameProxy.resolveClaim(8, 0); - gameProxy.resolveClaim(7, 0); - gameProxy.resolveClaim(6, 0); - gameProxy.resolveClaim(5, 0); - gameProxy.resolveClaim(4, 0); - gameProxy.resolveClaim(3, 0); - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - } -} - -/// @title PermissionedDisputeGame_Initialize_Test -/// @notice Tests the initialization of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGame_Initialize_Test is PermissionedDisputeGame_TestInit { - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by extraData of the wrong length - function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { - // The `DisputeGameFactory` will pack the root claim and the extra data into a single - // array, which is enforced to be at least 64 bytes long. - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - // [0 bytes, 31 bytes] u [33 bytes, 23.5 KB] - _extraDataLen = bound(_extraDataLen, 0, 23_500); - if (_extraDataLen == 32) { - _extraDataLen++; - } - bytes memory _extraData = new bytes(_extraDataLen); - - // Assign the first 32 bytes in `extraData` to a valid L2 block number passed the starting - // block. - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - assembly { - mstore(add(_extraData, 0x20), add(startingL2Block, 1)) - } - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, _extraData))) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by additional immutable args data - function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { - (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); - - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - _extraByteCount = bound(_extraByteCount, 1, 23_500); - bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupPermissionedDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber)) - )) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by missing immutable args data - function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { - (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); - - _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; - bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupPermissionedDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable(address( - disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber)) - )) - ); - } -} - -/// @title PermissionedDisputeGame_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the -/// `PermissionedDisputeGame` contract or are testing multiple functions at once. -contract PermissionedDisputeGame_Uncategorized_Test is PermissionedDisputeGame_TestInit { - /// @notice Tests that the proposer can create a permissioned dispute game. - function test_createGame_proposer_succeeds() public { - vm.prank(PROPOSER, PROPOSER); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the permissioned game cannot be created by the challenger. - function test_createGame_challenger_reverts() public { - vm.deal(CHALLENGER, initBond); - vm.prank(CHALLENGER, CHALLENGER); - vm.expectRevert(BadAuth.selector); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the permissioned game cannot be created by any address other than the - /// proposer. - function testFuzz_createGame_notProposer_reverts(address _p) public { - vm.assume(_p != PROPOSER); - - vm.deal(_p, initBond); - vm.prank(_p, _p); - vm.expectRevert(BadAuth.selector); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the challenger can participate in a permissioned dispute game. - function test_participateInGame_challenger_succeeds() public { - vm.startPrank(CHALLENGER, CHALLENGER); - uint256 firstBond = _getRequiredBond(0); - vm.deal(CHALLENGER, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, Claim.wrap(0)); - uint256 secondBond = _getRequiredBond(1); - vm.deal(CHALLENGER, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: secondBond }(disputed, 1, Claim.wrap(0)); - uint256 thirdBond = _getRequiredBond(2); - vm.deal(CHALLENGER, thirdBond); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.move{ value: thirdBond }(disputed, 2, Claim.wrap(0), true); - vm.stopPrank(); - } - - /// @notice Tests that the proposer can participate in a permissioned dispute game. - function test_participateInGame_proposer_succeeds() public { - vm.startPrank(PROPOSER, PROPOSER); - uint256 firstBond = _getRequiredBond(0); - vm.deal(PROPOSER, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, Claim.wrap(0)); - uint256 secondBond = _getRequiredBond(1); - vm.deal(PROPOSER, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: secondBond }(disputed, 1, Claim.wrap(0)); - uint256 thirdBond = _getRequiredBond(2); - vm.deal(PROPOSER, thirdBond); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.move{ value: thirdBond }(disputed, 2, Claim.wrap(0), true); - vm.stopPrank(); - } - - /// @notice Tests that addresses that are not the proposer or challenger cannot participate in - /// a permissioned dispute game. - function test_participateInGame_notAuthorized_reverts(address _p) public { - vm.assume(_p != PROPOSER && _p != CHALLENGER); - - vm.startPrank(_p, _p); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(BadAuth.selector); - gameProxy.attack(disputed, 0, Claim.wrap(0)); - vm.expectRevert(BadAuth.selector); - gameProxy.defend(disputed, 0, Claim.wrap(0)); - vm.expectRevert(BadAuth.selector); - gameProxy.move(disputed, 0, Claim.wrap(0), true); - vm.expectRevert(BadAuth.selector); - gameProxy.step(0, true, absolutePrestateData, hex""); - vm.stopPrank(); - } -} diff --git a/test/actors/FaultDisputeActors.sol b/test/actors/FaultDisputeActors.sol deleted file mode 100644 index fad83ccee..000000000 --- a/test/actors/FaultDisputeActors.sol +++ /dev/null @@ -1,419 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Testing -import { CommonBase } from "lib/forge-std/src/Base.sol"; - -// Libraries -import "src/libraries/bridge/Types.sol"; - -// Interfaces -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; - -/// @title GameSolver -/// @notice The `GameSolver` contract is a contract that can produce an array of available -/// moves for a given `FaultDisputeGame` contract, from the eyes of an honest -/// actor. The `GameSolver` does not implement functionality for acting on the `Move`s -/// it suggests. -abstract contract GameSolver is CommonBase { - /// @notice The `FaultDisputeGame` proxy that the `GameSolver` will be solving. - IFaultDisputeGameV2 public immutable GAME; - /// @notice The split depth of the game - uint256 internal immutable SPLIT_DEPTH; - /// @notice The max depth of the game - uint256 internal immutable MAX_DEPTH; - /// @notice The maximum L2 block number that the output bisection portion of the position tree - /// can handle. - uint256 internal immutable MAX_L2_BLOCK_NUMBER; - - /// @notice The L2 outputs that the `GameSolver` will be representing, keyed by L2 block number - 1. - uint256[] public l2Outputs; - /// @notice The execution trace that the `GameSolver` will be representing. - bytes public trace; - /// @notice The raw absolute prestate data. - bytes public absolutePrestateData; - /// @notice The offset of previously processed claims in the `GAME` contract's `claimData` array. - /// Starts at 0 and increments by 1 for each claim processed. - uint256 public processedBuf; - /// @notice Signals whether or not the `GameSolver` agrees with the root claim of the - /// `GAME` contract. - bool public agreeWithRoot; - - /// @notice The `MoveKind` enum represents a kind of interaction with the `FaultDisputeGame` contract. - enum MoveKind { - Attack, - Defend, - Step, - AddLocalData - } - - /// @notice The `Move` struct represents a move in the game, and contains information - /// about the kind of move, the sender of the move, and the calldata to be sent - /// to the `FaultDisputeGame` contract by a consumer of this contract. - struct Move { - MoveKind kind; - bytes data; - uint256 value; - } - - constructor( - IFaultDisputeGameV2 _gameProxy, - uint256[] memory _l2Outputs, - bytes memory _trace, - bytes memory _preStateData - ) { - GAME = _gameProxy; - SPLIT_DEPTH = GAME.splitDepth(); - MAX_DEPTH = GAME.maxGameDepth(); - MAX_L2_BLOCK_NUMBER = 2 ** (MAX_DEPTH - SPLIT_DEPTH); - - l2Outputs = _l2Outputs; - trace = _trace; - absolutePrestateData = _preStateData; - } - - /// @notice Returns an array of `Move`s that can be taken from the perspective of an honest - /// actor in the `FaultDisputeGame` contract. - function solveGame() external virtual returns (Move[] memory moves_); -} - -/// @title HonestGameSolver -/// @notice The `HonestGameSolver` is an implementation of `GameSolver` which responds accordingly depending -/// on the state of the `FaultDisputeGame` contract in relation to their local opinion of the correct -/// order of output roots and the execution trace between each block `n` -> `n + 1` state transition. -contract HonestGameSolver is GameSolver { - /// @notice The `Direction` enum represents the direction of a proposed move in the game, - /// or a lack thereof. - enum Direction { - Defend, - Attack, - Noop - } - - constructor( - IFaultDisputeGameV2 _gameProxy, - uint256[] memory _l2Outputs, - bytes memory _trace, - bytes memory _preStateData - ) - GameSolver(_gameProxy, _l2Outputs, _trace, _preStateData) - { - // Mark agreement with the root claim if the local opinion of the root claim is the same as the - // observed root claim. - agreeWithRoot = Claim.unwrap(outputAt(MAX_L2_BLOCK_NUMBER)) == Claim.unwrap(_gameProxy.rootClaim()); - } - - //////////////////////////////////////////////////////////////// - // EXTERNAL // - //////////////////////////////////////////////////////////////// - - /// @notice Returns an array of `Move`s that can be taken from the perspective of an honest - /// actor in the `FaultDisputeGame` contract. - function solveGame() external override returns (Move[] memory moves_) { - uint256 numClaims = GAME.claimDataLen(); - - // Pre-allocate the `moves_` array to the maximum possible length. Test environment, so - // over-allocation is fine, and more easy to read than making a linked list in asm. - moves_ = new Move[](numClaims - processedBuf); - - uint256 numMoves = 0; - for (uint256 i = processedBuf; i < numClaims; i++) { - // Grab the observed claim. - IFaultDisputeGameV2.ClaimData memory observed = getClaimData(i); - - // Determine the direction of the next move to be taken. - (Direction moveDirection, Position movePos) = determineDirection(observed); - - // Continue if there is no move to be taken against the observed claim. - if (moveDirection == Direction.Noop) continue; - - if (movePos.depth() <= MAX_DEPTH) { - // bisection - moves_[numMoves++] = handleBisectionMove(moveDirection, movePos, i); - } else { - // instruction step - moves_[numMoves++] = handleStepMove(moveDirection, observed.position, movePos, i); - } - } - - // Update the length of the `moves_` array to the number of moves that were added. This is - // always a no-op or a truncation operation. - assembly { - mstore(moves_, numMoves) - } - - // Increment `processedBuf` by the number of claims processed, so that next time around, - // we don't attempt to process the same claims again. - processedBuf += numClaims - processedBuf; - } - - //////////////////////////////////////////////////////////////// - // INTERNAL // - //////////////////////////////////////////////////////////////// - - /// @dev Helper function to determine the direction of the next move to be taken. - function determineDirection(IFaultDisputeGameV2.ClaimData memory _claimData) - internal - view - returns (Direction direction_, Position movePos_) - { - bool rightLevel = isRightLevel(_claimData.position); - bool localAgree = Claim.unwrap(claimAt(_claimData.position)) == Claim.unwrap(_claimData.claim); - if (_claimData.parentIndex == type(uint32).max) { - // If we agree with the parent claim and it is on a level we agree with, ignore it. - if (localAgree && rightLevel) { - return (Direction.Noop, Position.wrap(0)); - } - - // The parent claim is the root claim. We must attack if we disagree per the game rules. - direction_ = Direction.Attack; - movePos_ = _claimData.position.move(true); - } else { - // Never attempt to defend an execution trace subgame root. Only attack if we disagree with it, - // otherwise do nothing. - // NOTE: This is not correct behavior in the context of the honest actor; The alphabet game has - // a constant status byte, and is not safe from someone being dishonest in output bisection - // and then posting a correct execution trace bisection root claim. - if (_claimData.position.depth() == SPLIT_DEPTH + 1 && localAgree) { - return (Direction.Noop, Position.wrap(0)); - } - - // If the parent claim is not the root claim, first check if the observed claim is on a level that - // agrees with the local view of the root claim. If it is, noop. If it is not, perform an attack or - // defense depending on the local view of the observed claim. - if (rightLevel) { - // Never move against a claim on the right level. Even if it's wrong, if it's uncountered, it furthers - // our goals. - return (Direction.Noop, Position.wrap(0)); - } else { - // Fetch the local opinion of the parent claim. - Claim localParent = claimAt(_claimData.position); - - // NOTE: Poison not handled. - if (Claim.unwrap(localParent) != Claim.unwrap(_claimData.claim)) { - // If we disagree with the observed claim, we must attack it. - movePos_ = _claimData.position.move(true); - direction_ = Direction.Attack; - } else { - // If we agree with the observed claim, we must defend the observed claim. - movePos_ = _claimData.position.move(false); - direction_ = Direction.Defend; - } - } - } - } - - /// @notice Returns a `Move` struct that represents an attack or defense move in the bisection portion - /// of the game. - /// - /// @dev Note: This function assumes that the `movePos` and `challengeIndex` are valid within the - /// output bisection context. This is enforced by the `solveGame` function. - function handleBisectionMove( - Direction _direction, - Position _movePos, - uint256 _challengeIndex - ) - internal - view - returns (Move memory move_) - { - bool isAttack = _direction == Direction.Attack; - - uint256 bond = GAME.getRequiredBond(_movePos); - (,,,, Claim disputed,,) = GAME.claimData(_challengeIndex); - - move_ = Move({ - kind: isAttack ? MoveKind.Attack : MoveKind.Defend, - value: bond, - data: abi.encodeCall(IFaultDisputeGameV2.move, (disputed, _challengeIndex, claimAt(_movePos), isAttack)) - }); - } - - /// @notice Returns a `Move` struct that represents a step move in the execution trace - /// bisection portion of the dispute game. - /// @dev Note: This function assumes that the `movePos` and `challengeIndex` are valid within the - /// execution trace bisection context. This is enforced by the `solveGame` function. - function handleStepMove( - Direction _direction, - Position _parentPos, - Position _movePos, - uint256 _challengeIndex - ) - internal - view - returns (Move memory move_) - { - bool isAttack = _direction == Direction.Attack; - bytes memory preStateTrace; - - // First, we need to find the pre/post state index depending on whether we - // are making an attack step or a defense step. If the relative index at depth of the - // move position is 0, the prestate is the absolute prestate and we need to - // do nothing. - if ((_movePos.indexAtDepth() % (2 ** (MAX_DEPTH - SPLIT_DEPTH))) != 0) { - // Grab the trace up to the prestate's trace index. - if (isAttack) { - Position leafPos = Position.wrap(Position.unwrap(_parentPos) - 1); - preStateTrace = abi.encode(leafPos.traceIndex(MAX_DEPTH), stateAt(leafPos)); - } else { - preStateTrace = abi.encode(_parentPos.traceIndex(MAX_DEPTH), stateAt(_parentPos)); - } - } else { - preStateTrace = absolutePrestateData; - } - - move_ = Move({ - kind: MoveKind.Step, - value: 0, - data: abi.encodeCall(IFaultDisputeGameV2.step, (_challengeIndex, isAttack, preStateTrace, hex"")) - }); - } - - //////////////////////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////////////////////// - - /// @dev Helper function to get the `ClaimData` struct at a given index in the `GAME` contract's - /// `claimData` array. - function getClaimData(uint256 _claimIndex) internal view returns (IFaultDisputeGameV2.ClaimData memory claimData_) { - // thanks, solc - ( - uint32 parentIndex, - address countered, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ) = GAME.claimData(_claimIndex); - claimData_ = IFaultDisputeGameV2.ClaimData({ - parentIndex: parentIndex, - counteredBy: countered, - claimant: claimant, - bond: bond, - claim: claim, - position: position, - clock: clock - }); - } - - /// @notice Returns the player's claim that commits to a given position, swapping between - /// output bisection claims and execution trace bisection claims depending on the depth. - /// @dev Prefer this function over `outputAt` or `statehashAt` directly. - function claimAt(Position _position) internal view returns (Claim claim_) { - return _position.depth() > SPLIT_DEPTH ? statehashAt(_position) : outputAt(_position); - } - - /// @notice Returns the mock output at the given position. - function outputAt(Position _position) internal view returns (Claim claim_) { - // Don't allow for positions that are deeper than the split depth. - if (_position.depth() > SPLIT_DEPTH) { - revert("GameSolver: invalid position depth"); - } - - return outputAt(_position.traceIndex(SPLIT_DEPTH) + 1); - } - - /// @notice Returns the mock output at the given L2 block number. - function outputAt(uint256 _l2BlockNumber) internal view returns (Claim claim_) { - return Claim.wrap(bytes32(l2Outputs[_l2BlockNumber - 1])); - } - - /// @notice Returns the player's claim that commits to a given trace index. - function statehashAt(uint256 _traceIndex) internal view returns (Claim claim_) { - bytes32 hash = - keccak256(abi.encode(_traceIndex >= trace.length ? trace.length - 1 : _traceIndex, stateAt(_traceIndex))); - assembly { - claim_ := or(and(hash, not(shl(248, 0xFF))), shl(248, 1)) - } - } - - /// @notice Returns the player's claim that commits to a given trace index. - function statehashAt(Position _position) internal view returns (Claim claim_) { - return statehashAt(_position.traceIndex(MAX_DEPTH)); - } - - /// @notice Returns the state at the trace index within the player's trace. - function stateAt(Position _position) internal view returns (uint256 state_) { - return stateAt(_position.traceIndex(MAX_DEPTH)); - } - - /// @notice Returns the state at the trace index within the player's trace. - function stateAt(uint256 _traceIndex) internal view returns (uint256 state_) { - return uint256(uint8(_traceIndex >= trace.length ? trace[trace.length - 1] : trace[_traceIndex])); - } - - /// @notice Returns whether or not the position is on a level which opposes the local opinion of the - /// root claim. - function isRightLevel(Position _position) internal view returns (bool isRightLevel_) { - isRightLevel_ = agreeWithRoot == (_position.depth() % 2 == 0); - } -} - -/// @title DisputeActor -/// @notice The `DisputeActor` contract is an abstract contract that represents an actor -/// that consumes the suggested moves from a `GameSolver` contract. -abstract contract DisputeActor { - /// @notice The `GameSolver` contract used to determine the moves to be taken. - GameSolver public solver; - - /// @notice Performs all available moves deemed by the attached solver. - /// @return numMoves_ The number of moves that the actor took. - /// @return success_ True if all moves were successful, false otherwise. - function move() external virtual returns (uint256 numMoves_, bool success_); -} - -/// @title HonestDisputeActor -/// @notice An actor that consumes the suggested moves from an `HonestGameSolver` contract. Note -/// that this actor *can* be dishonest if the trace is faulty, but it will always follow -/// the rules of the honest actor. -contract HonestDisputeActor is DisputeActor { - IFaultDisputeGameV2 public immutable GAME; - - constructor( - IFaultDisputeGameV2 _gameProxy, - uint256[] memory _l2Outputs, - bytes memory _trace, - bytes memory _preStateData - ) { - GAME = _gameProxy; - solver = GameSolver(new HonestGameSolver(_gameProxy, _l2Outputs, _trace, _preStateData)); - } - - /// @inheritdoc DisputeActor - function move() external override returns (uint256 numMoves_, bool success_) { - GameSolver.Move[] memory moves = solver.solveGame(); - numMoves_ = moves.length; - - // Optimistically assume success, will be set to false if any move fails. - success_ = true; - - // Perform all available moves given to the actor by the solver. - for (uint256 i = 0; i < moves.length; i++) { - GameSolver.Move memory localMove = moves[i]; - - // If the move is a step, we first need to add the starting L2 block number to the `PreimageOracle` - // via the `FaultDisputeGame` contract. - // TODO: This is leaky. Could be another move kind. - if (localMove.kind == GameSolver.MoveKind.Step) { - bytes memory moveData = localMove.data; - uint256 challengeIndex; - assembly { - challengeIndex := mload(add(moveData, 0x24)) - } - GAME.addLocalData({ - _ident: LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, _execLeafIdx: challengeIndex, _partOffset: 0 - }); - } - - (bool innerSuccess,) = address(GAME).call{ value: localMove.value }(localMove.data); - assembly { - success_ := and(success_, innerSuccess) - } - } - } - - fallback() external payable { } - - receive() external payable { } -} diff --git a/test/cannon/MIPS64Memory.t.sol b/test/cannon/MIPS64Memory.t.sol deleted file mode 100644 index 1c816410e..000000000 --- a/test/cannon/MIPS64Memory.t.sol +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { CommonTest } from "test/setup/CommonTest.sol"; -import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; -import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; - -contract MIPS64MemoryWithCalldata { - function readMem( - bytes32 _root, - uint64 _addr, - uint8 _proofIndex, - bytes calldata /* _proof */ - ) - external - pure - returns (uint64 out_) - { - uint256 proofDataOffset = 4 + 32 + 32 + 32 + 32 + 32; - uint256 proofOffset = MIPS64Memory.memoryProofOffset(proofDataOffset, _proofIndex); - return MIPS64Memory.readMem(_root, _addr, proofOffset); - } - - function writeMem( - uint64 _addr, - uint64 _value, - uint8 _proofIndex, - bytes calldata /* _proof */ - ) - external - pure - returns (bytes32 root_) - { - uint256 proofDataOffset = 4 + 32 + 32 + 32 + 32 + 32; - uint256 proofOffset = MIPS64Memory.memoryProofOffset(proofDataOffset, _proofIndex); - return MIPS64Memory.writeMem(_addr, proofOffset, _value); - } -} - -/// @title MIPS64Memory_TestInit -/// @notice Reusable test initialization for `MIPS64Memory` tests. -abstract contract MIPS64Memory_TestInit is CommonTest { - MIPS64MemoryWithCalldata mem; - - error InvalidAddress(); - - function setUp() public virtual override { - super.setUp(); - mem = new MIPS64MemoryWithCalldata(); - } -} - -/// @title MIPS64Memory_ReadMem_Test -/// @notice Tests the `readMem` function of the `MIPS64Memory` contract. -contract MIPS64Memory_ReadMem_Test is MIPS64Memory_TestInit { - /// @notice Static unit test for basic memory access - function test_readMem_succeeds() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word); - uint64 readWord = mem.readMem(root, addr, 0, proof); - assertEq(readWord, word); - } - - /// @notice Static unit test asserting that reading from the zero address succeeds. - function test_readMemAtZero_succeeds() external { - uint64 addr = 0x0; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word); - uint64 readWord = mem.readMem(root, addr, 0, proof); - assertEq(readWord, word); - } - - /// @notice Static unit test asserting that reading from high memory area succeeds. - function test_readMemHighMem_succeeds() external { - uint64 addr = 0xFF_FF_FF_FF_00_00_00_88; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word); - uint64 readWord = mem.readMem(root, addr, 0, proof); - assertEq(readWord, word); - } - - /// @notice Static unit test asserting that reads revert when a misaligned memory address is - /// provided. - function test_readMem_readInvalidAddress_reverts() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word); - vm.expectRevert(InvalidAddress.selector); - mem.readMem(root, addr + 4, 0, proof); - } - - /// @notice Static unit test asserting that reads revert when an invalid proof is provided. - function test_readMem_readInvalidProof_reverts() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word); - vm.assertTrue(proof[64] != 0x0); // make sure the proof is tampered - proof[64] = 0x00; - vm.expectRevert(InvalidMemoryProof.selector); - mem.readMem(root, addr, 0, proof); - } - - /// @notice Static unit test asserting that reads from a non-zero proof index succeeds. - function test_readMem_nonZeroProofIndex_succeeds() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - uint64 addr2 = 0xFF_FF_FF_FF_00_00_00_88; - uint64 word2 = 0xF1_F2_F3_F4_F5_F6_F7_F8; - bytes32 root; - bytes memory proof; - (root, proof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); - - uint64 readWord = mem.readMem(root, addr, 0, proof); - assertEq(readWord, word); - - readWord = mem.readMem(root, addr2, 1, proof); - assertEq(readWord, word2); - } -} - -/// @title MIPS64Memory_WriteMem_Test -/// @notice Tests the `writeMem` function of the `MIPS64Memory` contract. -contract MIPS64Memory_WriteMem_Test is MIPS64Memory_TestInit { - /// @notice Static unit test asserting basic memory write functionality. - function test_writeMem_succeeds() external { - uint64 addr = 0x100; - bytes memory zeroProof; - (, zeroProof) = ffi.getCannonMemory64Proof(addr, 0); - - uint64 word = 0x11_22_33_44_55_66_77_88; - (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word); - - bytes32 newRoot = mem.writeMem(addr, word, 0, zeroProof); - assertEq(newRoot, expectedRoot); - } - - /// @notice Static unit test asserting that writes to high memory succeeds. - function test_writeMem_highMem_succeeds() external { - uint64 addr = 0xFF_FF_FF_FF_00_00_00_88; - bytes memory zeroProof; - (, zeroProof) = ffi.getCannonMemory64Proof(addr, 0); - - uint64 word = 0x11_22_33_44_55_66_77_88; - (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word); - - bytes32 newRoot = mem.writeMem(addr, word, 0, zeroProof); - assertEq(newRoot, expectedRoot); - } - - /// @notice Static unit test asserting that non-zero memory word is overwritten. - function test_writeMem_nonZeroProofOffset_succeeds() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - uint64 addr2 = 0x108; - uint64 word2 = 0x55_55_55_55_77_77_77_77; - bytes memory initProof; - (, initProof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); - - uint64 word3 = 0x44_44_44_44_44_44_44_44; - (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr2, word2, addr2, word3); - - bytes32 newRoot = mem.writeMem(addr2, word3, 1, initProof); - assertEq(newRoot, expectedRoot); - } - - /// @notice Static unit test asserting that a zerod memory word is set for a non-zero memory - /// proof. - function test_writeMem_uniqueAccess_succeeds() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - uint64 addr2 = 0x108; - uint64 word2 = 0x55_55_55_55_77_77_77_77; - bytes memory initProof; - (, initProof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); - - uint64 addr3 = 0xAA_AA_AA_AA_00; - uint64 word3 = 0x44_44_44_44_44_44_44_44; - (, bytes memory addr3Proof) = ffi.getCannonMemory64Proof2(addr, word, addr2, word2, addr3); - (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr2, word2, addr3, word3); - - bytes32 newRoot = mem.writeMem(addr3, word3, 0, addr3Proof); - assertEq(newRoot, expectedRoot); - - newRoot = mem.writeMem(addr3 + 8, word3, 0, addr3Proof); - assertNotEq(newRoot, expectedRoot); - - newRoot = mem.writeMem(addr3, word3 + 1, 0, addr3Proof); - assertNotEq(newRoot, expectedRoot); - } - - /// @notice Static unit test asserting that writes succeeds in overwriting a non-zero memory - /// word. - function test_writeMem_nonZeroMem_succeeds() external { - uint64 addr = 0x100; - uint64 word = 0x11_22_33_44_55_66_77_88; - bytes memory initProof; - (, initProof) = ffi.getCannonMemory64Proof(addr, word); - - uint64 word2 = 0x55_55_55_55_77_77_77_77; - (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr + 8, word2); - - bytes32 newRoot = mem.writeMem(addr + 8, word2, 0, initProof); - assertEq(newRoot, expectedRoot); - } - - /// @notice Static unit test asserting that writes revert when a misaligned memory address is - /// provided. - function test_writeMem_writeMemInvalidAddress_reverts() external { - bytes memory zeroProof; - (, zeroProof) = ffi.getCannonMemory64Proof(0x100, 0); - vm.expectRevert(InvalidAddress.selector); - mem.writeMem(0x104, 0x0, 0, zeroProof); - } -} diff --git a/test/cannon/PreimageOracle.t.sol b/test/cannon/PreimageOracle.t.sol deleted file mode 100644 index 72964261d..000000000 --- a/test/cannon/PreimageOracle.t.sol +++ /dev/null @@ -1,1540 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing -import { Test, Vm, console2 as console } from "lib/forge-std/src/Test.sol"; - -// Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -// Libraries -import { LibKeccak } from "lib/lib-keccak/contracts/lib/LibKeccak.sol"; -import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; -import { Bytes } from "src/libraries/Bytes.sol"; -import "src/cannon/libraries/CannonErrors.sol"; -import "src/cannon/libraries/CannonTypes.sol"; - -// Interfaces -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; - -/// @notice Sets the status byte of a hash. -function _setStatusByte(bytes32 _hash, uint8 _status) pure returns (bytes32 out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _hash), shl(248, _status)) - } -} - -/// @notice Computes a precompile key for a given precompile address and input. -function precompilePreimageKey(address _precompile, uint64 _gas, bytes memory _input) pure returns (bytes32 key_) { - bytes memory p = abi.encodePacked(_precompile, _gas, _input); - uint256 sz = 20 + 8 + _input.length; - assembly { - let h := keccak256(add(0x20, p), sz) - // Mask out prefix byte, replace with type 6 byte - key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 6)) - } -} - -/// @title PreimageOracle_TestInit -/// @notice Reusable test initialization for `PreimageOracle` tests. -abstract contract PreimageOracle_TestInit is Test { - /// @notice The PreimageOracle contract to test. - IPreimageOracle internal oracle; - - uint256 internal constant MIN_SIZE_BYTES = 0; - uint256 internal constant CHALLENGE_PERIOD = 1 days; - - /// @notice The test UUID to use for the LPP. - uint256 internal constant TEST_UUID = 0xFACADE; - - /// @notice Sets up the testing suite. - function setUp() public virtual { - // Deploy the PreimageOracle contract. - oracle = IPreimageOracle( - DeployUtils.create1({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) - }) - ); - - // Label the contract. - vm.label(address(oracle), "PreimageOracle"); - - // Set `tx.origin` and `msg.sender` to `address(this)` so that it may behave like an EOA - // for `addLeavesLPP`. - vm.startPrank(address(this), address(this)); - - // Give this address some ETH to work with. - vm.deal(address(this), 100 ether); - } - - /// @notice Hashes leaf data for the preimage proposals tree - function _hashLeaf(IPreimageOracle.Leaf memory _leaf) internal pure returns (bytes32 leaf_) { - leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment)); - } - - /// @notice Helper to construct the keccak merkle tree's leaves from a given input `_data`. - function _generateLeaves( - LibKeccak.StateMatrix memory _stateMatrix, - bytes memory _data - ) - internal - pure - returns (IPreimageOracle.Leaf[] memory leaves_) - { - bytes memory data = LibKeccak.padMemory(_data); - uint256 numCommitments = data.length / LibKeccak.BLOCK_SIZE_BYTES; - - leaves_ = new IPreimageOracle.Leaf[](numCommitments); - for (uint256 i = 0; i < numCommitments; i++) { - bytes memory blockSlice = Bytes.slice(data, i * LibKeccak.BLOCK_SIZE_BYTES, LibKeccak.BLOCK_SIZE_BYTES); - LibKeccak.absorb(_stateMatrix, blockSlice); - LibKeccak.permutation(_stateMatrix); - - leaves_[i] = IPreimageOracle.Leaf({ - input: blockSlice, index: uint32(i), stateCommitment: keccak256(abi.encode(_stateMatrix)) - }); - } - } - - /// @notice Helper to get the keccak state matrix before applying the block at `_blockIndex` - /// within `_data`. - function _stateMatrixAtBlockIndex( - bytes memory _data, - uint256 _blockIndex - ) - internal - pure - returns (LibKeccak.StateMatrix memory matrix_) - { - bytes memory data = LibKeccak.padMemory(_data); - - for (uint256 i = 0; i < _blockIndex; i++) { - bytes memory blockSlice = Bytes.slice(data, i * LibKeccak.BLOCK_SIZE_BYTES, LibKeccak.BLOCK_SIZE_BYTES); - LibKeccak.absorb(matrix_, blockSlice); - LibKeccak.permutation(matrix_); - } - } - - /// @notice Helper to construct the keccak state commitments for each block processed in the - /// input `_data`. - function _generateStateCommitments( - LibKeccak.StateMatrix memory _stateMatrix, - bytes memory _data - ) - internal - pure - returns (bytes32[] memory stateCommitments_) - { - bytes memory data = LibKeccak.padMemory(_data); - uint256 numCommitments = data.length / LibKeccak.BLOCK_SIZE_BYTES; - - stateCommitments_ = new bytes32[](numCommitments); - for (uint256 i = 0; i < numCommitments; i++) { - bytes memory blockSlice = Bytes.slice(data, i * LibKeccak.BLOCK_SIZE_BYTES, LibKeccak.BLOCK_SIZE_BYTES); - LibKeccak.absorb(_stateMatrix, blockSlice); - LibKeccak.permutation(_stateMatrix); - - stateCommitments_[i] = keccak256(abi.encode(_stateMatrix)); - } - } - - /// @notice Calls out to the `go-ffi` tool to generate a merkle proof for the leaf at - /// `_leafIdx` in a merkle tree constructed with `_leaves`. - function _generateProof( - uint256 _leafIdx, - IPreimageOracle.Leaf[] memory _leaves - ) - internal - returns (bytes32 root_, bytes32[] memory proof_) - { - bytes32[] memory leaves = new bytes32[](_leaves.length); - for (uint256 i = 0; i < _leaves.length; i++) { - leaves[i] = _hashLeaf(_leaves[i]); - } - - string[] memory commands = new string[](5); - commands[0] = "scripts/go-ffi/go-ffi"; - commands[1] = "merkle"; - commands[2] = "gen_proof"; - commands[3] = vm.toString(abi.encodePacked(leaves)); - commands[4] = vm.toString(_leafIdx); - (root_, proof_) = abi.decode(vm.ffi(commands), (bytes32, bytes32[])); - } - - fallback() external payable { } - - receive() external payable { } -} - -/// @title PreimageOracle_Constructor_Test -/// @notice Tests the constructor of the `PreimageOracle` contract. -contract PreimageOracle_Constructor_Test is PreimageOracle_TestInit { - /// @notice Tests that the challenge period cannot be made too large. - /// @param _challengePeriod The challenge period to test. - function testFuzz_constructor_challengePeriodTooLarge_reverts(uint256 _challengePeriod) public { - _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); - vm.expectRevert("PreimageOracle: challenge period too large"); - DeployUtils.create1({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, _challengePeriod))) - }); - } -} - -/// @title PreimageOracle_ReadPreimage_Test -/// @notice Tests the `readPreimage` function of the `PreimageOracle` contract. -contract PreimageOracle_ReadPreimage_Test is PreimageOracle_TestInit { - /// @notice Reading a pre-image part that has not been set should revert. - function testFuzz_readPreimage_missingPreimage_reverts(bytes32 key, uint256 offset) public { - vm.expectRevert("pre-image must exist"); - oracle.readPreimage(key, offset); - } -} - -/// @title PreimageOracle_LoadLocalData_Test -/// @notice Tests the `loadLocalData` function of the `PreimageOracle` contract. -contract PreimageOracle_LoadLocalData_Test is PreimageOracle_TestInit { - /// @notice Tests that context-specific data [0, 24] bytes in length can be loaded correctly. - function test_loadLocalData_onePart_succeeds() public { - uint256 ident = 1; - bytes32 word = bytes32(uint256(0xdeadbeef) << 224); - uint8 size = 4; - uint8 partOffset = 0; - - // Load the local data into the preimage oracle under the test contract's context. - bytes32 contextKey = oracle.loadLocalData(ident, 0, word, size, partOffset); - - // Validate that the pre-image part is set - bool ok = oracle.preimagePartOk(contextKey, partOffset); - assertTrue(ok); - - // Validate the local data part - bytes32 expectedPart = 0x0000000000000004deadbeef0000000000000000000000000000000000000000; - assertEq(oracle.preimageParts(contextKey, partOffset), expectedPart); - - // Validate the local data length - uint256 length = oracle.preimageLengths(contextKey); - assertEq(length, size); - } - - /// @notice Tests that multiple local key contexts can be used by the same address for the same - /// local data identifier. - function test_loadLocalData_multipleContexts_succeeds() public { - uint256 ident = 1; - uint8 size = 4; - uint8 partOffset = 0; - - // Form the words we'll be storing - bytes32[2] memory words = [bytes32(uint256(0xdeadbeef) << 224), bytes32(uint256(0xbeefbabe) << 224)]; - - for (uint256 i; i < words.length; i++) { - // Load the local data into the preimage oracle under the test contract's context - // and the given local context. - bytes32 contextKey = oracle.loadLocalData(ident, bytes32(i), words[i], size, partOffset); - - // Validate that the pre-image part is set - bool ok = oracle.preimagePartOk(contextKey, partOffset); - assertTrue(ok); - - // Validate the local data part - bytes32 expectedPart = bytes32(uint256(words[i] >> 64) | uint256(size) << 192); - assertEq(oracle.preimageParts(contextKey, partOffset), expectedPart); - - // Validate the local data length - uint256 length = oracle.preimageLengths(contextKey); - assertEq(length, size); - } - } - - /// @notice Tests that context-specific data [0, 32] bytes in length can be loaded correctly. - function testFuzz_loadLocalData_varyingLength_succeeds( - uint256 ident, - bytes32 localContext, - bytes32 word, - uint256 size, - uint256 partOffset - ) - public - { - // Bound the size to [0, 32] - size = bound(size, 0, 32); - // Bound the part offset to [0, size + 8) - partOffset = bound(partOffset, 0, size + 7); - - // Load the local data into the preimage oracle under the test contract's context. - bytes32 contextKey = oracle.loadLocalData(ident, localContext, word, uint8(size), uint8(partOffset)); - - // Validate that the first local data part is set - bool ok = oracle.preimagePartOk(contextKey, partOffset); - assertTrue(ok); - // Validate the first local data part - bytes32 expectedPart; - assembly { - mstore(0x20, 0x00) - - mstore(0x00, shl(192, size)) - mstore(0x08, word) - - expectedPart := mload(partOffset) - } - assertEq(oracle.preimageParts(contextKey, partOffset), expectedPart); - - // Validate the local data length - uint256 length = oracle.preimageLengths(contextKey); - assertEq(length, size); - } - - /// @notice Tests that a pre-image cannot be set with an out-of-bounds offset. - function test_loadLocalData_outOfBoundsOffset_reverts() public { - bytes32 preimage = bytes32(uint256(0xdeadbeef)); - uint256 offset = preimage.length + 9; - - vm.expectRevert(PartOffsetOOB.selector); - oracle.loadLocalData(1, 0, preimage, 32, offset); - } -} - -/// @title PreimageOracle_LoadKeccak256PreimagePart_Test -/// @notice Tests the `loadKeccak256PreimagePart` function of the `PreimageOracle` contract. -contract PreimageOracle_LoadKeccak256PreimagePart_Test is PreimageOracle_TestInit { - /// @notice Tests that a pre-image is correctly set. - function test_loadKeccak256PreimagePart_succeeds() public { - // Set the pre-image - bytes memory preimage = hex"deadbeef"; - bytes32 key = PreimageKeyLib.keccak256PreimageKey(preimage); - uint256 offset = 0; - oracle.loadKeccak256PreimagePart(offset, preimage); - - // Validate the pre-image part - bytes32 part = oracle.preimageParts(key, offset); - bytes32 expectedPart = 0x0000000000000004deadbeef0000000000000000000000000000000000000000; - assertEq(part, expectedPart); - - // Validate the pre-image length - uint256 length = oracle.preimageLengths(key); - assertEq(length, preimage.length); - - // Validate that the pre-image part is set - bool ok = oracle.preimagePartOk(key, offset); - assertTrue(ok); - } - - /// @notice Tests that adding a global keccak256 pre-image at the part boundary reverts. - function test_loadKeccak256PreimagePart_partBoundary_reverts() public { - bytes memory preimage = hex"deadbeef"; - uint256 offset = preimage.length + 8; - - vm.expectRevert(PartOffsetOOB.selector); - oracle.loadKeccak256PreimagePart(offset, preimage); - } - - /// @notice Tests that a pre-image cannot be set with an out-of-bounds offset. - function test_loadKeccak256PreimagePart_outOfBoundsOffset_reverts() public { - bytes memory preimage = hex"deadbeef"; - uint256 offset = preimage.length + 9; - - vm.expectRevert(PartOffsetOOB.selector); - oracle.loadKeccak256PreimagePart(offset, preimage); - } -} - -/// @title PreimageOracle_LoadPrecompilePreimagePart_Test -/// @notice Tests the `loadPrecompilePreimagePart` function of the `PreimageOracle` contract. -contract PreimageOracle_LoadPrecompilePreimagePart_Test is PreimageOracle_TestInit { - /// @notice Tests that a precompile pre-image result is correctly set. - function test_loadPrecompilePreimagePart_succeeds() public { - bytes memory input = hex"deadbeef"; - uint256 offset = 0; - address precompile = address(bytes20(uint160(0x02))); // sha256 - uint64 gas = 72; - bytes32 key = precompilePreimageKey(precompile, gas, input); - oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); - - bytes32 part = oracle.preimageParts(key, offset); - // size prefix - 1-byte result + 32-byte sha return data - assertEq(hex"0000000000000021", bytes8(part)); - // precompile result - assertEq(bytes1(0x01), bytes1(part << 64)); - // precompile call return data - assertEq(bytes23(sha256(input)), bytes23(part << 72)); - - // Validate the local data length - uint256 length = oracle.preimageLengths(key); - assertEq(length, 1 + 32); - - // Validate that the first local data part is set - bool ok = oracle.preimagePartOk(key, offset); - assertTrue(ok); - } - - /// @notice Tests that a precompile pre-image result is correctly set at its return data - /// offset. - function test_loadPrecompilePreimagePart_atReturnOffset_succeeds() public { - bytes memory input = hex"deadbeef"; - uint256 offset = 9; - address precompile = address(bytes20(uint160(0x02))); // sha256 - uint64 gas = 72; - bytes32 key = precompilePreimageKey(precompile, gas, input); - oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); - - bytes32 part = oracle.preimageParts(key, offset); - // 32-byte sha return data - assertEq(sha256(input), part); - - // Validate the local data length - uint256 length = oracle.preimageLengths(key); - assertEq(length, 1 + 32); - - // Validate that the first local data part is set - bool ok = oracle.preimagePartOk(key, offset); - assertTrue(ok); - } - - /// @notice Tests that a failed precompile call has a zero status byte in preimage - function test_loadPrecompilePreimagePart_failedCall_succeeds() public { - bytes memory input = new bytes(193); // invalid input to induce a failed precompile call - uint256 offset = 0; - address precompile = address(bytes20(uint160(0x08))); // bn256Pairing - uint64 gas = 72; - bytes32 key = precompilePreimageKey(precompile, gas, input); - oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); - - bytes32 part = oracle.preimageParts(key, offset); - // size prefix - 1-byte result + 0-byte sha return data - assertEq(hex"0000000000000001", bytes8(part)); - // precompile result - assertEq(bytes1(0x00), bytes1(part << 64)); - // precompile call return data - assertEq(bytes23(0), bytes23(part << 72)); - - // Validate the local data length - uint256 length = oracle.preimageLengths(key); - assertEq(length, 1); - - // Validate that the first local data part is set - bool ok = oracle.preimagePartOk(key, offset); - assertTrue(ok); - } - - /// @notice Tests that adding a global precompile result at the part boundary reverts. - function test_loadPrecompilePreimagePart_partBoundary_reverts() public { - bytes memory input = hex"deadbeef"; - uint256 offset = 41; // 8-byte prefix + 1-byte result + 32-byte sha return data - address precompile = address(bytes20(uint160(0x02))); // sha256 - uint64 gas = 72; - vm.expectRevert(PartOffsetOOB.selector); - oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); - } - - /// @notice Tests that a global precompile result cannot be set with an out-of-bounds offset. - function test_loadPrecompilePreimagePart_outOfBoundsOffset_reverts() public { - bytes memory input = hex"deadbeef"; - uint256 offset = 42; - address precompile = address(bytes20(uint160(0x02))); // sha256 - uint64 gas = 72; - vm.expectRevert(PartOffsetOOB.selector); - oracle.loadPrecompilePreimagePart(offset, precompile, gas, input); - } - - /// @notice Tests that a global precompile load succeeds on a variety of gas inputs. - function testFuzz_loadPrecompilePreimagePart_withVaryingGas_succeeds(uint64 _gas) public { - uint64 requiredGas = 100_000; - bytes memory input = hex"deadbeef"; - address precompile = address(uint160(0xdeadbeef)); - vm.mockCall(precompile, input, hex"abba"); - uint256 offset = 0; - uint64 minGas = uint64(bound(_gas, requiredGas * 3, 20_000_000)); - vm.expectCallMinGas(precompile, 0, requiredGas, input); - oracle.loadPrecompilePreimagePart{ gas: minGas }(offset, precompile, requiredGas, input); - } - - /// @notice Tests that a global precompile load succeeds on insufficient gas. - function test_loadPrecompilePreimagePart_withInsufficientGas_reverts() public { - uint64 requiredGas = 1_000_000; - bytes memory input = hex"deadbeef"; - uint256 offset = 0; - address precompile = address(uint160(0xdeadbeef)); - // This gas is sufficient to reach the gas checks in `loadPrecompilePreimagePart` but not - // enough to pass those checks. - uint64 insufficientGas = requiredGas * 63 / 64; - vm.expectRevert(NotEnoughGas.selector); - oracle.loadPrecompilePreimagePart{ gas: insufficientGas }(offset, precompile, requiredGas, input); - } -} - -/// @title PreimageOracle_InitLPP_Test -/// @notice Tests the `initLPP` function of the `PreimageOracle` contract. -contract PreimageOracle_InitLPP_Test is PreimageOracle_TestInit { - /// @notice Tests that the `initLPP` function reverts when the part offset is out of bounds of - /// the full preimage. - function test_initLPP_partOffsetOOB_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - uint256 bondSize = oracle.MIN_BOND_SIZE(); - vm.expectRevert(PartOffsetOOB.selector); - oracle.initLPP{ value: bondSize }(TEST_UUID, 136 + 8, uint32(data.length)); - } - - /// @notice Tests that the `initLPP` function reverts when the part offset is out of bounds of - /// the full preimage. - function test_initLPP_sizeTooSmall_reverts() public { - oracle = IPreimageOracle( - DeployUtils.create1({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IPreimageOracle.__constructor__, (1000, CHALLENGE_PERIOD)) - ) - }) - ); - - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - uint256 bondSize = oracle.MIN_BOND_SIZE(); - vm.expectRevert(InvalidInputSize.selector); - oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(data.length)); - } - - /// @notice Tests that the `initLPP` function reverts if the proposal has already been - /// initialized. - function test_initLPP_alreadyInitialized_reverts() public { - // Initialize the proposal. - uint256 bondSize = oracle.MIN_BOND_SIZE(); - oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(500)); - - // Re-initialize the proposal. - vm.expectRevert(AlreadyInitialized.selector); - oracle.initLPP{ value: bondSize }(TEST_UUID, 0, uint32(500)); - } -} - -/// @title PreimageOracle_AddLeavesLPP_Test -/// @notice Tests the `addLeavesLPP` function of the `PreimageOracle` contract. -contract PreimageOracle_AddLeavesLPP_Test is PreimageOracle_TestInit { - /// @notice Gas snapshot for `addLeaves` - function test_addLeavesLPP_gasSnapshot_benchmark() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 500); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - // Allocate the calldata so it isn't included in the gas measurement. - bytes memory cd = abi.encodeCall(oracle.addLeavesLPP, (TEST_UUID, 0, data, stateCommitments, true)); - - // Record logs from the call. `expectEmit` does not capture assembly logs. - bytes memory expectedLog = abi.encodePacked(address(this), cd); - vm.recordLogs(); - - uint256 gas = gasleft(); - (bool success,) = address(oracle).call(cd); - uint256 gasUsed = gas - gasleft(); - assertTrue(success); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - assertEq(logs[0].data, expectedLog); - assertEq(logs[0].emitter, address(oracle)); - - console.log("Gas used: %d", gasUsed); - console.log("Gas per byte (%d bytes streamed): %d", data.length, gasUsed / data.length); - console.log("Gas for 4MB: %d", (gasUsed / data.length) * 4000000); - } - - /// @notice Tests that `addLeavesLPP` sets the proposal as countered when `_finalize = true` - /// and the number of bytes processed is less than the claimed size. - function test_addLeavesLPP_mismatchedSize_succeeds() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length + 1)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - vm.expectRevert(InvalidInputSize.selector); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - } - - /// @notice Tests that the `addLeavesLPP` function may never be called when `tx.origin != - /// msg.sender`. - function test_addLeavesLPP_notEOA_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 500); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - // Replace the global prank, set `tx.origin` to `address(0)`, and set `msg.sender` to - // `address(this)`. - vm.stopPrank(); - vm.prank(address(0), address(this)); - - vm.expectRevert(NotEOA.selector); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - } - - /// @notice Tests that the `addLeavesLPP` function reverts when the starting block index is not - /// what is expected. - function test_addLeavesLPP_notContiguous_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 500); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - vm.expectRevert(WrongStartingBlock.selector); - oracle.addLeavesLPP(TEST_UUID, 1, data, stateCommitments, true); - } - - /// @notice Tests that leaves can be added the large preimage proposal mapping and proven to be - /// contained within the computed merkle root. - function test_addLeavesLPP_multipleParts_succeeds() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 3); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - // Ensure that the proposal keys are present in the array. - (address claimant, uint256 uuid) = oracle.proposals(0); - assertEq(oracle.proposalCount(), 1); - assertEq(claimant, address(this)); - assertEq(uuid, TEST_UUID); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - uint256 midPoint = stateCommitments.length / 2; - bytes32[] memory commitmentsA = new bytes32[](midPoint); - bytes32[] memory commitmentsB = new bytes32[](midPoint); - for (uint256 i = 0; i < midPoint; i++) { - commitmentsA[i] = stateCommitments[i]; - commitmentsB[i] = stateCommitments[i + midPoint]; - } - - oracle.addLeavesLPP(TEST_UUID, 0, Bytes.slice(data, 0, 136 * 2), commitmentsA, false); - - // MetaData assertions - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertEq(metaData.timestamp(), 0); - assertEq(metaData.partOffset(), 0); - assertEq(metaData.claimedSize(), data.length); - assertEq(metaData.blocksProcessed(), 2); - assertEq(metaData.bytesProcessed(), 136 * 2); - assertFalse(metaData.countered()); - - // Move ahead one block. - vm.roll(block.number + 1); - - oracle.addLeavesLPP(TEST_UUID, 2, Bytes.slice(data, 136 * 2, 136), commitmentsB, true); - - // MetaData assertions - metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertEq(metaData.timestamp(), 1); - assertEq(metaData.partOffset(), 0); - assertEq(metaData.claimedSize(), data.length); - assertEq(metaData.blocksProcessed(), 4); - assertEq(metaData.bytesProcessed(), data.length); - assertFalse(metaData.countered()); - - // Preimage part assertions - bytes32 expectedPart = bytes32((~uint256(0) & ~(uint256(type(uint64).max) << 192)) | (data.length << 192)); - assertEq(oracle.proposalParts(address(this), TEST_UUID), expectedPart); - - assertEq(oracle.proposalBlocks(address(this), TEST_UUID, 0), block.number - 1); - assertEq(oracle.proposalBlocks(address(this), TEST_UUID, 1), block.number); - - // Should revert if we try to add new leaves. - vm.expectRevert(AlreadyFinalized.selector); - oracle.addLeavesLPP(TEST_UUID, 4, data, stateCommitments, true); - } - - /// @notice Tests that leaves cannot be added until the large preimage proposal has been - /// initialized. - function test_addLeavesLPP_notInitialized_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 500); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - - // Allocate the calldata so it isn't included in the gas measurement. - vm.expectRevert(NotInitialized.selector); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - } -} - -contract PreimageOracle_ChallengeLPP_Test is PreimageOracle_TestInit { - /// @notice Tests that a valid leaf cannot be countered with the `challenge` function in the - /// middle of the tree. - function test_challengeLPP_validCommitment_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, 1); - - vm.expectRevert(PostStateMatches.selector); - oracle.challengeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertFalse(metaData.countered()); - } - - /// @notice Tests that an invalid leaf can not be countered with non-contiguous states. - function test_challengeLPP_statesNotContiguous_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 2); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136 * 2); - for (uint256 i = 0; i < phonyData.length / 2; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - leaves[2].stateCommitment = stateCommitments[2]; - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - preProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - postProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - for (uint256 i = 2; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, 2); - - vm.expectRevert(StatesNotContiguous.selector); - oracle.challengeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[1], - _preStateProof: postProof, - _postState: leaves[0], - _postStateProof: preProof - }); - } - - /// @notice Tests that an invalid leaf can not be countered with an incorrect prestate matrix - /// reveal. - function test_challengeLPP_invalidPreimage_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 2); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136 * 2); - for (uint256 i = 0; i < phonyData.length / 2; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - leaves[2].stateCommitment = stateCommitments[2]; - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - preProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - postProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - for (uint256 i = 2; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, 2); - - vm.expectRevert(InvalidPreimage.selector); - oracle.challengeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - } - - /// @notice Tests that an invalid leaf can be countered with the `challenge` function in the - /// middle of the tree. - function test_challengeLPP_invalidCommitment_succeeds() public { - // Allocate the preimage data. - bytes memory data = new bytes(136 * 2); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136 * 2); - for (uint256 i = 0; i < phonyData.length / 2; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - leaves[2].stateCommitment = stateCommitments[2]; - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - preProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - postProof[1] = keccak256(abi.encode(_hashLeaf(leaves[2]), bytes32(0))); - for (uint256 i = 2; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - uint256 balanceBefore = address(this).balance; - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, 1); - oracle.challengeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - assertEq(address(this).balance, balanceBefore + oracle.MIN_BOND_SIZE()); - assertEq(oracle.proposalBonds(address(this), TEST_UUID), 0); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertTrue(metaData.countered()); - } - - /// @notice Tests that challenging the first divergence in a large preimage proposal at an - /// arbitrary location in the leaf values always succeeds. - function testFuzz_challengeLPP_arbitraryLocation_succeeds(uint256 _lastCorrectLeafIdx, uint256 _numBlocks) public { - _numBlocks = bound(_numBlocks, 1, 2 ** 8); - _lastCorrectLeafIdx = bound(_lastCorrectLeafIdx, 0, _numBlocks - 1); - - // Allocate the preimage data. - bytes memory data = new bytes(136 * _numBlocks); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with corrupted state commitments. - LibKeccak.StateMatrix memory matrixA; - bytes32[] memory stateCommitments = _generateStateCommitments(matrixA, data); - for (uint256 i = _lastCorrectLeafIdx + 1; i < stateCommitments.length; i++) { - stateCommitments[i] = 0; - } - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added and corrupt the state commitments. - LibKeccak.StateMatrix memory matrixB; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); - for (uint256 i = _lastCorrectLeafIdx + 1; i < leaves.length; i++) { - leaves[i].stateCommitment = 0; - } - - // Avoid stack too deep - uint256 agreedLeafIdx = _lastCorrectLeafIdx; - uint256 disputedLeafIdx = agreedLeafIdx + 1; - - // Fetch the merkle proofs for the pre/post state leaves in the proposal tree. - bytes32 canonicalRoot = oracle.getTreeRootLPP(address(this), TEST_UUID); - (bytes32 rootA, bytes32[] memory preProof) = _generateProof(agreedLeafIdx, leaves); - assertEq(rootA, canonicalRoot); - (bytes32 rootB, bytes32[] memory postProof) = _generateProof(disputedLeafIdx, leaves); - assertEq(rootB, canonicalRoot); - - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, disputedLeafIdx); - oracle.challengeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[agreedLeafIdx], - _preStateProof: preProof, - _postState: leaves[disputedLeafIdx], - _postStateProof: postProof - }); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertTrue(metaData.countered()); - } -} - -contract PreimageOracle_ChallengeFirstLPP_Test is PreimageOracle_TestInit { - /// @notice Tests that a valid leaf cannot be countered with the `challengeFirst` function. - function test_challengeFirst_validCommitment_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory p = new bytes32[](16); - p[0] = _hashLeaf(leaves[1]); - for (uint256 i = 1; i < p.length; i++) { - p[i] = oracle.zeroHashes(i); - } - - vm.expectRevert(PostStateMatches.selector); - oracle.challengeFirstLPP({ - _claimant: address(this), _uuid: TEST_UUID, _postState: leaves[0], _postStateProof: p - }); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertFalse(metaData.countered()); - } - - /// @notice Tests that an invalid leaf cannot be countered with `challengeFirst` if it is not - /// the first leaf. - function test_challengeFirst_statesNotContiguous_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - - // Create a proof array with 16 elements. - bytes32[] memory p = new bytes32[](16); - p[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < p.length; i++) { - p[i] = oracle.zeroHashes(i); - } - - // Should succeed since the commitment was wrong. - vm.expectRevert(StatesNotContiguous.selector); - oracle.challengeFirstLPP({ - _claimant: address(this), _uuid: TEST_UUID, _postState: leaves[1], _postStateProof: p - }); - } - - /// @notice Tests that an invalid leaf can be countered with the `challengeFirst` function. - function test_challengeFirst_invalidCommitment_succeeds() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - - // Create a proof array with 16 elements. - bytes32[] memory p = new bytes32[](16); - p[0] = _hashLeaf(leaves[1]); - for (uint256 i = 1; i < p.length; i++) { - p[i] = oracle.zeroHashes(i); - } - - // Should succeed since the commitment was wrong. - uint256 balanceBefore = address(this).balance; - oracle.challengeFirstLPP({ - _claimant: address(this), _uuid: TEST_UUID, _postState: leaves[0], _postStateProof: p - }); - assertEq(address(this).balance, balanceBefore + oracle.MIN_BOND_SIZE()); - assertEq(oracle.proposalBonds(address(this), TEST_UUID), 0); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertTrue(metaData.countered()); - } - - /// @notice Tests that challenging the a divergence in a large preimage proposal at the first - /// leaf always succeeds. - function testFuzz_challengeFirstLPP_succeeds(uint256 _numBlocks) public { - _numBlocks = bound(_numBlocks, 1, 2 ** 8); - - // Allocate the preimage data. - bytes memory data = new bytes(136 * _numBlocks); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with corrupted state commitments. - bytes32[] memory stateCommitments = new bytes32[](_numBlocks + 1); - for (uint256 i = 0; i < stateCommitments.length; i++) { - stateCommitments[i] = 0; - } - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added and corrupt the state commitments. - LibKeccak.StateMatrix memory matrixB; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); - for (uint256 i = 0; i < leaves.length; i++) { - leaves[i].stateCommitment = 0; - } - - // Fetch the merkle proofs for the pre/post state leaves in the proposal tree. - bytes32 canonicalRoot = oracle.getTreeRootLPP(address(this), TEST_UUID); - (bytes32 rootA, bytes32[] memory postProof) = _generateProof(0, leaves); - assertEq(rootA, canonicalRoot); - - oracle.challengeFirstLPP({ - _claimant: address(this), _uuid: TEST_UUID, _postState: leaves[0], _postStateProof: postProof - }); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertTrue(metaData.countered()); - } -} - -/// @title PreimageOracle_SqueezeLPP_Test -/// @notice Tests the `squeezeLPP` function of the `PreimageOracle` contract. -contract PreimageOracle_SqueezeLPP_Test is PreimageOracle_TestInit { - /// @notice Tests that leaves can be added the large preimage proposal mapping and finalized to - /// be added to the authorized mappings. - function test_squeezeLPP_challengePeriodPassed_succeeds() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); - - // Finalize the proposal. - uint256 balanceBefore = address(this).balance; - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 1), - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - assertEq(address(this).balance, balanceBefore + oracle.MIN_BOND_SIZE()); - assertEq(oracle.proposalBonds(address(this), TEST_UUID), 0); - - bytes32 finalDigest = _setStatusByte(keccak256(data), 2); - bytes32 expectedPart = bytes32((~uint256(0) & ~(uint256(type(uint64).max) << 192)) | (data.length << 192)); - assertTrue(oracle.preimagePartOk(finalDigest, 0)); - assertEq(oracle.preimageLengths(finalDigest), data.length); - assertEq(oracle.preimageParts(finalDigest, 0), expectedPart); - } - - /// @notice Tests that a proposal cannot be finalized until it has passed the challenge period. - function test_squeezeLPP_proposalChallenged_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - bytes memory phonyData = new bytes(136); - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree with mismatching state commitments. - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, phonyData, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); - leaves[0].stateCommitment = stateCommitments[0]; - leaves[1].stateCommitment = stateCommitments[1]; - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - // Should succeed since the commitment was wrong. - oracle.challengeFirstLPP({ - _claimant: address(this), _uuid: TEST_UUID, _postState: leaves[0], _postStateProof: preProof - }); - - LPPMetaData metaData = oracle.proposalMetadata(address(this), TEST_UUID); - assertTrue(metaData.countered()); - - vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); - - // Finalize the proposal. - vm.expectRevert(BadProposal.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 1), - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - } - - /// @notice Tests that a proposal cannot be squeezed if the proposal has not been finalized. - function test_squeezeLPP_notFinalized_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Generate the padded input data. - // Since the data is 136 bytes, which is exactly one keccak block, we will add one extra - // keccak block of empty padding to the input data. We need to do this here because the - // addLeavesLPP function will normally perform this padding internally when _finalize is - // set to true but we're explicitly testing the case where _finalize is not true. - bytes memory paddedData = new bytes(136 * 2); - for (uint256 i; i < data.length; i++) { - paddedData[i] = data[i]; - } - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, paddedData, stateCommitments, false); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - // Warp past the challenge period. - vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); - - // Finalize the proposal. - vm.expectRevert(ActiveProposal.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 1), - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - } - - /// @notice Tests that a proposal cannot be finalized until it has passed the challenge period. - function test_squeezeLPP_challengePeriodActive_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Finalize the proposal. - vm.expectRevert(ActiveProposal.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 1), - _preState: leaves[0], - _preStateProof: new bytes32[](16), - _postState: leaves[1], - _postStateProof: new bytes32[](16) - }); - } - - /// @notice Tests that a proposal cannot be finalized until it has passed the challenge period. - function test_squeezeLPP_incompleteAbsorbtion_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Finalize the proposal. - vm.expectRevert(ActiveProposal.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 1), - _preState: leaves[0], - _preStateProof: new bytes32[](16), - _postState: leaves[1], - _postStateProof: new bytes32[](16) - }); - } - - /// @notice Tests that the `squeeze` function reverts when the passed states are not - /// contiguous. - function test_squeezeLPP_statesNotContiguous_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); - - // Finalize the proposal. - vm.expectRevert(StatesNotContiguous.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 2), - _preState: leaves[1], - _preStateProof: postProof, - _postState: leaves[0], - _postStateProof: preProof - }); - } - - /// @notice Tests that the `squeeze` function reverts when the post state passed - function test_squeezeLPP_invalidPreimage_reverts() public { - // Allocate the preimage data. - bytes memory data = new bytes(136); - for (uint256 i; i < data.length; i++) { - data[i] = 0xFF; - } - - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, 0, uint32(data.length)); - - // Add the leaves to the tree (2 keccak blocks.) - LibKeccak.StateMatrix memory stateMatrix; - bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrix; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); - - // Create a proof array with 16 elements. - bytes32[] memory preProof = new bytes32[](16); - preProof[0] = _hashLeaf(leaves[1]); - bytes32[] memory postProof = new bytes32[](16); - postProof[0] = _hashLeaf(leaves[0]); - for (uint256 i = 1; i < preProof.length; i++) { - bytes32 zeroHash = oracle.zeroHashes(i); - preProof[i] = zeroHash; - postProof[i] = zeroHash; - } - - vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds); - - // Finalize the proposal. - vm.expectRevert(InvalidPreimage.selector); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: _stateMatrixAtBlockIndex(data, 2), - _preState: leaves[0], - _preStateProof: preProof, - _postState: leaves[1], - _postStateProof: postProof - }); - } - - /// @notice Tests that squeezing a large preimage proposal after the challenge period has - /// passed always succeeds and persists the correct data. - function testFuzz_squeezeLPP_succeeds(uint256 _numBlocks, uint32 _partOffset) public { - _numBlocks = bound(_numBlocks, 1, 2 ** 8); - _partOffset = uint32(bound(_partOffset, 0, _numBlocks * LibKeccak.BLOCK_SIZE_BYTES + 8 - 1)); - - // Allocate the preimage data. - bytes memory data = new bytes(136 * _numBlocks); - for (uint256 i; i < data.length; i++) { - data[i] = bytes1(uint8(i % 256)); - } - - // Propose and squeeze a large preimage. - { - // Initialize the proposal. - oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, _partOffset, uint32(data.length)); - - // Add the leaves to the tree with correct state commitments. - LibKeccak.StateMatrix memory matrixA; - bytes32[] memory stateCommitments = _generateStateCommitments(matrixA, data); - oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true); - - // Construct the leaf preimage data for the blocks added. - LibKeccak.StateMatrix memory matrixB; - IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); - - // Fetch the merkle proofs for the pre/post state leaves in the proposal tree. - bytes32 canonicalRoot = oracle.getTreeRootLPP(address(this), TEST_UUID); - (bytes32 rootA, bytes32[] memory preProof) = _generateProof(leaves.length - 2, leaves); - assertEq(rootA, canonicalRoot); - (bytes32 rootB, bytes32[] memory postProof) = _generateProof(leaves.length - 1, leaves); - assertEq(rootB, canonicalRoot); - - // Warp past the challenge period. - vm.warp(block.timestamp + CHALLENGE_PERIOD + 1 seconds); - - // Squeeze the LPP. - LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, leaves.length - 1); - oracle.squeezeLPP({ - _claimant: address(this), - _uuid: TEST_UUID, - _stateMatrix: preMatrix, - _preState: leaves[leaves.length - 2], - _preStateProof: preProof, - _postState: leaves[leaves.length - 1], - _postStateProof: postProof - }); - } - - // Validate the preimage part - { - bytes32 finalDigest = _setStatusByte(keccak256(data), 2); - bytes32 expectedPart; - assembly { - switch lt(_partOffset, 0x08) - case true { - mstore(0x00, shl(192, mload(data))) - mstore(0x08, mload(add(data, 0x20))) - expectedPart := mload(_partOffset) - } - default { - // Clean the word after `data` so we don't get any dirty bits. - mstore(add(add(data, 0x20), mload(data)), 0x00) - expectedPart := mload(add(add(data, 0x20), sub(_partOffset, 0x08))) - } - } - - assertTrue(oracle.preimagePartOk(finalDigest, _partOffset)); - assertEq(oracle.preimageLengths(finalDigest), data.length); - assertEq(oracle.preimageParts(finalDigest, _partOffset), expectedPart); - } - } -} - -/// @title PreimageOracle_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `PreimageOracle` -/// contract or are testing multiple functions at once. -contract PreimageOracle_Uncategorized_Test is PreimageOracle_TestInit { - /// @notice Test the pre-image key computation with a known pre-image. - function test_keccak256PreimageKey_succeeds() public pure { - bytes memory preimage = hex"deadbeef"; - bytes32 key = PreimageKeyLib.keccak256PreimageKey(preimage); - bytes32 known = 0x02fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1; - assertEq(key, known); - } -} diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index e45a94f5f..f45b456d8 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -8,17 +8,16 @@ import { SystemDeploy } from "scripts/deploy/SystemDeploy.s.sol"; import { Types } from "scripts/libraries/Types.sol"; import { SystemDeployAssertions } from "test/deploy/SystemDeployAssertions.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { LibGameArgs } from "src/libraries/bridge/LibGameArgs.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; -import { Claim, Duration, GameType, GameTypes, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; contract MockNitroEnclaveVerifier { address public proofSubmitter; @@ -35,8 +34,6 @@ contract MockSP1Verifier { contract SystemDeploy_Test is Test, SystemDeployAssertions { Artifacts internal constant artifacts = Artifacts(address(uint160(uint256(keccak256(abi.encode("optimism.artifacts")))))); - uint256 internal constant STANDARD_MIPS_VERSION = 8; - SystemDeploy internal systemDeploy; address internal owner = address(this); @@ -130,178 +127,77 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertNotEq(address(output.opChain.systemConfigProxy), address(0), "system config"); assertNotEq(address(output.opChain.optimismPortalProxy), address(0), "portal"); assertNotEq(address(output.opChain.ethLockboxProxy), address(0), "lockbox"); - assertNotEq(address(output.opChain.delayedWETHPermissionlessGameProxy), address(0), "permissionless weth"); + assertNotEq(address(output.opChain.delayedWETHProxy), address(0), "delayed weth"); assertEq(output.opChain.opChainProxyAdmin.owner(), owner, "op chain proxy admin owner"); assertEq(output.opChain.systemConfigProxy.batchInbox(), Types.chainIdToBatchInboxAddress(l2ChainId), "inbox"); - assertEq( - address(output.opChain.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), - output.implementationOutput.implementations.permissionedDisputeGameV2Impl, - "permissioned game impl" - ); _assertMultiproofDeployed(output, input); assertEq( address(output.opChain.systemConfigProxy.superchainConfig()), address(output.superchain.superchainConfigProxy), "superchain config" ); - assertValidStandardSystem(_expected(output, input, false, false, absolutePrestate, Claim.wrap(bytes32(0)))); + assertValidStandardSystem(_expected(output, input)); } function test_upgrade_withoutManagerDelegatecall_succeeds() public { SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_defaultDeployInput()); - Types.OpChainConfig[] memory opChainConfigs = new Types.OpChainConfig[](1); - opChainConfigs[0] = Types.OpChainConfig({ - systemConfigProxy: output.opChain.systemConfigProxy, - cannonPrestate: absolutePrestate, - cannonKonaPrestate: Claim.wrap(bytes32(0)) - }); - SystemDeploy.UpgradeOutput memory upgradeOutput = systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, superchainConfigProxy: output.superchain.superchainConfigProxy, - implementations: output.implementationOutput.implementations, - opChainConfigs: opChainConfigs + implementations: output.impls, + systemConfigProxy: output.opChain.systemConfigProxy }) ); assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); - assertEq(upgradeOutput.chainsUpgraded, 1, "chains upgraded"); - assertEq( - address(output.opChain.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), - output.implementationOutput.implementations.permissionedDisputeGameV2Impl, - "permissioned game impl after upgrade" - ); + assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); _assertUpgradedProxyImplementations(output); - assertValidStandardSystem( - _expected(output, _defaultDeployInput(), false, false, absolutePrestate, Claim.wrap(bytes32(0))) - ); - } - - function test_upgrade_existingCannonKonaFallsBackToCurrentPrestate_succeeds() public { - SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_defaultDeployInput()); - - Claim currentCannonPrestate = Claim.wrap(bytes32(uint256(3))); - Claim currentCannonKonaPrestate = Claim.wrap(bytes32(uint256(4))); - output.opChain.disputeGameFactoryProxy - .setImplementation( - GameTypes.CANNON, - IDisputeGame(output.implementationOutput.implementations.permissionedDisputeGameV2Impl), - _permissionlessGameArgs(output, currentCannonPrestate) - ); - output.opChain.disputeGameFactoryProxy - .setImplementation( - GameTypes.CANNON_KONA, - IDisputeGame(output.implementationOutput.implementations.permissionedDisputeGameV2Impl), - _permissionlessGameArgs(output, currentCannonKonaPrestate) - ); - output.opChain.disputeGameFactoryProxy.setInitBond(GameTypes.CANNON, 1 ether); - - Types.OpChainConfig[] memory opChainConfigs = new Types.OpChainConfig[](1); - opChainConfigs[0] = Types.OpChainConfig({ - systemConfigProxy: output.opChain.systemConfigProxy, - cannonPrestate: Claim.wrap(bytes32(0)), - cannonKonaPrestate: Claim.wrap(bytes32(0)) - }); - - systemDeploy.upgrade( - SystemDeploy.UpgradeInput({ - saveArtifacts: false, - superchainConfigProxy: output.superchain.superchainConfigProxy, - implementations: output.implementationOutput.implementations, - opChainConfigs: opChainConfigs - }) - ); - - assertEq( - address(output.opChain.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON)), - output.implementationOutput.implementations.faultDisputeGameV2Impl, - "cannon impl" - ); - assertEq( - address(output.opChain.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON_KONA)), - output.implementationOutput.implementations.faultDisputeGameV2Impl, - "cannon kona impl" - ); - - LibGameArgs.GameArgs memory cannonArgs = - LibGameArgs.decode(output.opChain.disputeGameFactoryProxy.gameArgs(GameTypes.CANNON)); - LibGameArgs.GameArgs memory cannonKonaArgs = - LibGameArgs.decode(output.opChain.disputeGameFactoryProxy.gameArgs(GameTypes.CANNON_KONA)); - assertEq(cannonArgs.absolutePrestate, currentCannonPrestate.raw(), "cannon prestate"); - assertEq(cannonKonaArgs.absolutePrestate, currentCannonKonaPrestate.raw(), "cannon kona prestate"); - assertEq(cannonKonaArgs.weth, cannonArgs.weth, "shared weth"); - assertEq(cannonKonaArgs.anchorStateRegistry, cannonArgs.anchorStateRegistry, "shared asr"); - assertEq( - output.opChain.disputeGameFactoryProxy.initBonds(GameTypes.CANNON_KONA), - output.opChain.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), - "cannon kona bond" - ); - assertValidStandardSystem( - _expected(output, _defaultDeployInput(), true, true, currentCannonPrestate, currentCannonKonaPrestate) - ); + assertValidStandardSystem(_expected(output, _defaultDeployInput())); } function test_deploy_reusingImplementations_doesNotSaveZeroImplementationOnlyArtifacts() public { SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_defaultDeployInput()); SystemDeploy.DeployInput memory input = _defaultDeployInput(); - input.deploySuperchain = false; - input.deployImplementations = false; input.saveArtifacts = true; input.superchainConfigProxy = output.superchain.superchainConfigProxy; - input.implementations = output.implementationOutput.implementations; + input.implementations = output.impls; input.opChainInput.l2ChainId = l2ChainId + 1; - input.implementationsInput.l2ChainID = input.opChainInput.l2ChainId; input.opChainInput.saltMixer = "system-deploy-reuse-test"; vm.mockCallRevert( - address(artifacts), abi.encodeCall(Artifacts.save, ("PreimageOracle", address(0))), "zero preimage oracle" + address(artifacts), + abi.encodeCall(Artifacts.save, ("AggregateVerifier", address(0))), + "zero aggregate verifier" ); SystemDeploy.DeployOutput memory reuseOutput = systemDeploy.deploy(input); - assertEq( - address(reuseOutput.implementationOutput.preimageOracleSingleton), - address(output.implementationOutput.preimageOracleSingleton), - "preimage oracle" - ); _assertMultiproofDeployed(reuseOutput, input); } function _defaultDeployInput() internal view returns (SystemDeploy.DeployInput memory input_) { - input_.deploySuperchain = true; - input_.deployImplementations = true; input_.saveArtifacts = false; input_.superchainInput = SystemDeploy.SuperchainInput({ guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: owner }); input_.implementationsInput = SystemDeploy.ImplementationInput({ withdrawalDelaySeconds: 100, - minProposalSizeBytes: 200, - challengePeriodSeconds: 300, proofMaturityDelaySeconds: 400, disputeGameFinalityDelaySeconds: 500, - mipsVersion: STANDARD_MIPS_VERSION, - faultGameV2MaxGameDepth: 73, - faultGameV2SplitDepth: 30, - faultGameV2ClockExtension: 10_800, - faultGameV2MaxClockDuration: 302_400, teeImageHash: bytes32(uint256(1)), zkRangeHash: bytes32(uint256(2)), zkAggregationHash: bytes32(uint256(3)), multiproofConfigHash: bytes32(uint256(4)), multiproofGameType: 621, nitroEnclaveVerifier: address(nitroEnclaveVerifier), - l2ChainID: l2ChainId, multiproofBlockInterval: 100, multiproofIntermediateBlockInterval: 10, sp1Verifier: ISP1Verifier(address(sp1Verifier)), teeProposer: proposer, teeChallenger: challenger, - superchainConfigProxy: ISuperchainConfig(address(0)), - superchainProxyAdmin: IProxyAdmin(address(0)), guardian: guardian, incidentResponder: incidentResponder }); @@ -310,46 +206,17 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { opChainProxyAdminOwner: owner, systemConfigOwner: owner, batcher: batcher, - unsafeBlockSigner: unsafeBlockSigner, - proposer: proposer, - challenger: challenger + unsafeBlockSigner: unsafeBlockSigner }), basefeeScalar: 100, blobBasefeeScalar: 200, l2ChainId: l2ChainId, startingAnchorRoot: Proposal({ root: Hash.wrap(bytes32(uint256(1))), l2SequenceNumber: 0 }), saltMixer: "system-deploy-test", - gasLimit: 60_000_000, - disputeGameType: GameTypes.PERMISSIONED_CANNON, - disputeAbsolutePrestate: absolutePrestate, - disputeMaxGameDepth: 73, - disputeSplitDepth: 30, - disputeClockExtension: Duration.wrap(10_800), - disputeMaxClockDuration: Duration.wrap(302_400) + gasLimit: 60_000_000 }); } - function _permissionlessGameArgs( - SystemDeploy.DeployOutput memory _output, - Claim _absolutePrestate - ) - internal - view - returns (bytes memory) - { - return LibGameArgs.encode( - LibGameArgs.GameArgs({ - absolutePrestate: _absolutePrestate.raw(), - vm: _output.implementationOutput.implementations.mipsImpl, - anchorStateRegistry: address(_output.opChain.anchorStateRegistryProxy), - weth: address(_output.opChain.delayedWETHPermissionlessGameProxy), - l2ChainId: l2ChainId, - proposer: address(0), - challenger: address(0) - }) - ); - } - function _assertMultiproofDeployed( SystemDeploy.DeployOutput memory _output, SystemDeploy.DeployInput memory _input @@ -362,7 +229,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { address teeProverRegistryProxyAddr = address(_output.opChain.teeProverRegistryProxy); address teeVerifierAddr = address(_output.opChain.teeVerifier); address zkVerifierAddr = address(_output.opChain.zkVerifier); - Types.Implementations memory impls = _output.implementationOutput.implementations; + Types.Implementations memory impls = _output.impls; assertNotEq(aggregateVerifierAddr, address(0), "aggregate verifier"); assertNotEq(teeProverRegistryProxyAddr, address(0), "tee prover registry proxy"); @@ -393,7 +260,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { "aggregate verifier asr" ); assertEq(address(aggregateVerifier.DISPUTE_GAME_FACTORY()), address(_output.opChain.disputeGameFactoryProxy)); - assertEq(address(aggregateVerifier.DELAYED_WETH()), address(_output.opChain.delayedWETHPermissionlessGameProxy)); + assertEq(address(aggregateVerifier.DELAYED_WETH()), address(_output.opChain.delayedWETHProxy)); assertEq(address(aggregateVerifier.TEE_VERIFIER()), teeVerifierAddr); assertEq(address(aggregateVerifier.ZK_VERIFIER()), zkVerifierAddr); assertEq(aggregateVerifier.TEE_IMAGE_HASH(), _input.implementationsInput.teeImageHash); @@ -429,7 +296,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { function _assertUpgradedProxyImplementations(SystemDeploy.DeployOutput memory _output) internal view { IProxyAdmin superchainProxyAdmin = _output.superchain.superchainProxyAdmin; IProxyAdmin opChainProxyAdmin = _output.opChain.opChainProxyAdmin; - Types.Implementations memory impls = _output.implementationOutput.implementations; + Types.Implementations memory impls = _output.impls; assertEq( superchainProxyAdmin.getProxyImplementation(address(_output.superchain.superchainConfigProxy)), @@ -480,11 +347,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { function _expected( SystemDeploy.DeployOutput memory _output, - SystemDeploy.DeployInput memory _input, - bool _expectCannon, - bool _expectCannonKona, - Claim _cannonPrestate, - Claim _cannonKonaPrestate + SystemDeploy.DeployInput memory _input ) internal pure @@ -492,26 +355,21 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { { expected_ = SystemDeployAssertions.ExpectedSystemDeployState({ systemConfig: _output.opChain.systemConfigProxy, + anchorStateRegistry: _output.opChain.anchorStateRegistryProxy, superchainConfig: _output.superchain.superchainConfigProxy, - implementations: _output.implementationOutput.implementations, + implementations: _output.impls, + delayedWETH: _output.opChain.delayedWETHProxy, ethLockbox: _output.opChain.ethLockboxProxy, proxyAdminOwner: _input.opChainInput.roles.opChainProxyAdminOwner, - challenger: _input.opChainInput.roles.challenger, - proposer: _input.opChainInput.roles.proposer, + multiproofGameType: GameType.wrap(uint32(_input.implementationsInput.multiproofGameType)), + teeImageHash: _input.implementationsInput.teeImageHash, + zkRangeHash: _input.implementationsInput.zkRangeHash, + zkAggregationHash: _input.implementationsInput.zkAggregationHash, + multiproofConfigHash: _input.implementationsInput.multiproofConfigHash, l2ChainId: _input.opChainInput.l2ChainId, - permissionedCannonPrestate: _input.opChainInput.disputeAbsolutePrestate, - cannonPrestate: _cannonPrestate, - cannonKonaPrestate: _cannonKonaPrestate, - expectCannon: _expectCannon, - expectCannonKona: _expectCannonKona, - withdrawalDelaySeconds: _input.implementationsInput.withdrawalDelaySeconds, - minProposalSizeBytes: _input.implementationsInput.minProposalSizeBytes, - challengePeriodSeconds: _input.implementationsInput.challengePeriodSeconds, - mipsVersion: _input.implementationsInput.mipsVersion, - disputeMaxGameDepth: _input.opChainInput.disputeMaxGameDepth, - disputeSplitDepth: _input.opChainInput.disputeSplitDepth, - disputeClockExtension: _input.opChainInput.disputeClockExtension, - disputeMaxClockDuration: _input.opChainInput.disputeMaxClockDuration + multiproofBlockInterval: _input.implementationsInput.multiproofBlockInterval, + multiproofIntermediateBlockInterval: _input.implementationsInput.multiproofIntermediateBlockInterval, + withdrawalDelaySeconds: _input.implementationsInput.withdrawalDelaySeconds }); } } diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 40c2599ee..6b805ee8d 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -1,23 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Test } from "lib/forge-std/src/Test.sol"; +import { Test, console } from "lib/forge-std/src/Test.sol"; import { Types } from "scripts/libraries/Types.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Features } from "src/libraries/Features.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { LibGameArgs } from "src/libraries/bridge/LibGameArgs.sol"; -import { Claim, Duration, GameType, GameTypes, Hash } from "src/libraries/bridge/Types.sol"; +import { GameType, Hash } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; +import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; @@ -34,26 +32,21 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; abstract contract SystemDeployAssertions is Test { struct ExpectedSystemDeployState { ISystemConfig systemConfig; + IAnchorStateRegistry anchorStateRegistry; ISuperchainConfig superchainConfig; Types.Implementations implementations; + IDelayedWETH delayedWETH; IETHLockbox ethLockbox; address proxyAdminOwner; - address challenger; - address proposer; + GameType multiproofGameType; + bytes32 teeImageHash; + bytes32 zkRangeHash; + bytes32 zkAggregationHash; + bytes32 multiproofConfigHash; uint256 l2ChainId; - Claim permissionedCannonPrestate; - Claim cannonPrestate; - Claim cannonKonaPrestate; - bool expectCannon; - bool expectCannonKona; + uint256 multiproofBlockInterval; + uint256 multiproofIntermediateBlockInterval; uint256 withdrawalDelaySeconds; - uint256 minProposalSizeBytes; - uint256 challengePeriodSeconds; - uint256 mipsVersion; - uint256 disputeMaxGameDepth; - uint256 disputeSplitDepth; - Duration disputeClockExtension; - Duration disputeMaxClockDuration; } function assertValidStandardSystem(ExpectedSystemDeployState memory _expected) internal view { @@ -64,7 +57,7 @@ abstract contract SystemDeployAssertions is Test { _assertSystemConfig(_expected, proxyAdmin); _assertBridgeAndPortalWiring(_expected, proxyAdmin); _assertDisputeGameFactory(_expected, proxyAdmin); - _assertGames(_expected, proxyAdmin); + _assertGame(_expected, proxyAdmin, _expected.multiproofGameType); _assertETHLockbox(_expected, proxyAdmin); } @@ -98,6 +91,7 @@ abstract contract SystemDeployAssertions is Test { assertEq(address(sysCfg.superchainConfig()), address(_expected.superchainConfig), "SYSCON-130"); assertEq(sysCfg.batchInbox(), Types.chainIdToBatchInboxAddress(_expected.l2ChainId), "SYSCON-140"); assertEq(sysCfg.l2ChainId(), _expected.l2ChainId, "SYSCON-150"); + assertEq(sysCfg.delayedWETH(), address(_expected.delayedWETH), "SYSCON-160"); } function _assertBridgeAndPortalWiring( @@ -178,8 +172,8 @@ abstract contract SystemDeployAssertions is Test { ); assertEq(address(portal.disputeGameFactory()), address(dgf), "PORTAL-30"); assertEq(address(portal.systemConfig()), address(sysCfg), "PORTAL-40"); - LibGameArgs.GameArgs memory permissionedArgs = LibGameArgs.decode(dgf.gameArgs(GameTypes.PERMISSIONED_CANNON)); - assertEq(address(portal.anchorStateRegistry()), permissionedArgs.anchorStateRegistry, "PORTAL-50"); + IDisputeGame av = dgf.gameImpls(_expected.multiproofGameType); + assertEq(address(portal.anchorStateRegistry()), address(av.anchorStateRegistry()), "PORTAL-50"); assertEq(portal.l2Sender(), Constants.DEFAULT_L2_SENDER, "PORTAL-80"); assertEq(address(_proxyAdminFor(address(portal))), address(_proxyAdmin), "PORTAL-90"); } @@ -202,53 +196,27 @@ abstract contract SystemDeployAssertions is Test { assertEq(address(_proxyAdminFor(address(factory))), address(_proxyAdmin), "DF-40"); } - function _assertGames(ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin) private view { - _assertGame(_expected, _proxyAdmin, GameTypes.PERMISSIONED_CANNON, true, "PDDG"); - _assertGame(_expected, _proxyAdmin, GameTypes.CANNON, _expected.expectCannon, "PLDG"); - _assertGame(_expected, _proxyAdmin, GameTypes.CANNON_KONA, _expected.expectCannonKona, "CKDG"); - } - function _assertGame( ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin, - GameType _gameType, - bool _expectSet, - string memory _prefix + GameType _gameType ) private view { IDisputeGameFactory factory = IDisputeGameFactory(_expected.systemConfig.disputeGameFactory()); IDisputeGame game = factory.gameImpls(_gameType); - if (!_expectSet) { - assertEq(address(game), address(0), string.concat(_prefix, "-10")); - assertEq(factory.gameArgs(_gameType).length, 0, string.concat(_prefix, "-GARGS-10")); - return; - } - assertNotEq(address(game), address(0), string.concat(_prefix, "-10")); - bool permissioned = _gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw(); - address expectedImpl = permissioned - ? _expected.implementations.permissionedDisputeGameV2Impl - : _expected.implementations.faultDisputeGameV2Impl; - assertEq(address(game), expectedImpl, string.concat(_prefix, "-15")); - assertEq(_version(address(game)), _version(expectedImpl), string.concat(_prefix, "-20")); + assertNotEq(address(game), address(0), "AV-10"); + address expectedImpl = _expected.implementations.aggregateVerifierImpl; + assertEq(address(game), expectedImpl, "AV-15"); + assertEq(_version(address(game)), _version(expectedImpl), "AV-20"); - bytes memory rawArgs = factory.gameArgs(_gameType); - if (permissioned) { - assertTrue(LibGameArgs.isValidPermissionedArgs(rawArgs), string.concat(_prefix, "-GARGS-10")); - } else { - assertTrue(LibGameArgs.isValidPermissionlessArgs(rawArgs), string.concat(_prefix, "-GARGS-10")); - } _assertGameArgsAndContracts({ _expected: _expected, _proxyAdmin: _proxyAdmin, _factory: factory, - _faultGame: IFaultDisputeGameV2(address(game)), - _gameType: _gameType, - _args: LibGameArgs.decode(rawArgs), - _permissioned: permissioned, - _prefix: _prefix + _aggregateVerifier: IAggregateVerifier(address(game)) }); } @@ -256,68 +224,55 @@ abstract contract SystemDeployAssertions is Test { ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin, IDisputeGameFactory _factory, - IFaultDisputeGameV2 _faultGame, - GameType _gameType, - LibGameArgs.GameArgs memory _args, - bool _permissioned, - string memory _prefix + IAggregateVerifier _aggregateVerifier ) private view { - Claim expectedPrestate = _expectedPrestate(_expected, _gameType); - assertEq(_args.absolutePrestate, expectedPrestate.raw(), string.concat(_prefix, "-40")); - assertEq(_args.vm, _expected.implementations.mipsImpl, string.concat(_prefix, "-VM-10")); - assertEq(_args.l2ChainId, _expected.l2ChainId, string.concat(_prefix, "-60")); - _assertGameImmutableArgs(_expected, _faultGame, _prefix); - - (Hash anchorRoot,) = IAnchorStateRegistry(_args.anchorStateRegistry).getAnchorRoot(); - assertNotEq(anchorRoot.raw(), bytes32(0), string.concat(_prefix, "-120")); - - if (_permissioned) { - assertEq(_args.challenger, _expected.challenger, "PDDG-130"); - assertEq(_args.proposer, _expected.proposer, "PDDG-140"); - } else { - assertEq(_args.challenger, address(0), string.concat(_prefix, "-130")); - assertEq(_args.proposer, address(0), string.concat(_prefix, "-140")); - } - - _assertDelayedWETH(_expected, _proxyAdmin, IDelayedWETH(payable(_args.weth)), _prefix); - _assertAnchorStateRegistry( - _expected, _proxyAdmin, _factory, IAnchorStateRegistry(_args.anchorStateRegistry), _prefix - ); - _assertMipsAndPreimageOracle(_expected, IMIPS64(_args.vm), _prefix); + IAnchorStateRegistry asr = _aggregateVerifier.anchorStateRegistry(); + IDelayedWETH weth = _aggregateVerifier.DELAYED_WETH(); + _assertGameImmutableArgs(_expected, _factory, _aggregateVerifier); + + (Hash anchorRoot,) = asr.getAnchorRoot(); + assertNotEq(anchorRoot.raw(), bytes32(0), "AV-200"); + _assertDelayedWETH(_expected, _proxyAdmin, weth); + _assertAnchorStateRegistry(_expected, _proxyAdmin, _factory, asr); } function _assertGameImmutableArgs( ExpectedSystemDeployState memory _expected, - IFaultDisputeGameV2 _faultGame, - string memory _prefix + IDisputeGameFactory _factory, + IAggregateVerifier _aggregateVerifier ) private view { - assertEq(_faultGame.l2SequenceNumber(), 0, string.concat(_prefix, "-70")); + assertEq(_aggregateVerifier.gameType().raw(), _expected.multiproofGameType.raw(), "AV-30"); + assertEq(address(_aggregateVerifier.anchorStateRegistry()), address(_expected.anchorStateRegistry), "AV-40"); + assertEq(address(_aggregateVerifier.DISPUTE_GAME_FACTORY()), address(_factory), "AV-50"); + assertEq(address(_aggregateVerifier.DELAYED_WETH()), address(_expected.delayedWETH), "AV-60"); + assertEq(address(_aggregateVerifier.TEE_VERIFIER()), _expected.implementations.teeVerifierImpl, "AV-70"); + assertEq(address(_aggregateVerifier.ZK_VERIFIER()), _expected.implementations.zkVerifierImpl, "AV-80"); + assertEq(_aggregateVerifier.TEE_IMAGE_HASH(), _expected.teeImageHash, "AV-90"); + assertEq(_aggregateVerifier.ZK_RANGE_HASH(), _expected.zkRangeHash, "AV-100"); + assertEq(_aggregateVerifier.ZK_AGGREGATE_HASH(), _expected.zkAggregationHash, "AV-110"); + assertEq(_aggregateVerifier.CONFIG_HASH(), _expected.multiproofConfigHash, "AV-120"); + assertEq(_aggregateVerifier.L2_CHAIN_ID(), _expected.l2ChainId, "AV-130"); + assertEq(_aggregateVerifier.BLOCK_INTERVAL(), _expected.multiproofBlockInterval, "AV-140"); assertEq( - _faultGame.clockExtension().raw(), _expected.disputeClockExtension.raw(), string.concat(_prefix, "-80") - ); - assertEq(_faultGame.splitDepth(), _expected.disputeSplitDepth, string.concat(_prefix, "-90")); - assertEq(_faultGame.maxGameDepth(), _expected.disputeMaxGameDepth, string.concat(_prefix, "-100")); - assertEq( - _faultGame.maxClockDuration().raw(), _expected.disputeMaxClockDuration.raw(), string.concat(_prefix, "-110") + _aggregateVerifier.INTERMEDIATE_BLOCK_INTERVAL(), _expected.multiproofIntermediateBlockInterval, "AV-150" ); } function _assertDelayedWETH( ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin, - IDelayedWETH _weth, - string memory _prefix + IDelayedWETH _weth ) private view { - string memory prefix = string.concat(_prefix, "-DWETH"); + string memory prefix = "AV-DWETH"; assertEq( _version(address(_weth)), _version(_expected.implementations.delayedWETHImpl), string.concat(prefix, "-10") ); @@ -336,13 +291,12 @@ abstract contract SystemDeployAssertions is Test { ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin, IDisputeGameFactory _factory, - IAnchorStateRegistry _asr, - string memory _prefix + IAnchorStateRegistry _asr ) private view { - string memory prefix = string.concat(_prefix, "-ANCHORP"); + string memory prefix = "AV-ANCHORP"; assertEq( _version(address(_asr)), _version(_expected.implementations.anchorStateRegistryImpl), @@ -359,26 +313,6 @@ abstract contract SystemDeployAssertions is Test { assertGt(_asr.retirementTimestamp(), 0, string.concat(prefix, "-60")); } - function _assertMipsAndPreimageOracle( - ExpectedSystemDeployState memory _expected, - IMIPS64 _mips, - string memory _prefix - ) - private - view - { - string memory vmPrefix = string.concat(_prefix, "-VM"); - assertEq(address(_mips), _expected.implementations.mipsImpl, string.concat(vmPrefix, "-10")); - assertEq(_version(address(_mips)), _version(_expected.implementations.mipsImpl), string.concat(vmPrefix, "-20")); - assertEq(_mips.stateVersion(), _expected.mipsVersion, string.concat(vmPrefix, "-30")); - - IPreimageOracle oracle = _mips.oracle(); - string memory oraclePrefix = string.concat(_prefix, "-PIMGO"); - assertGt(bytes(_version(address(oracle))).length, 0, string.concat(oraclePrefix, "-10")); - assertEq(oracle.challengePeriod(), _expected.challengePeriodSeconds, string.concat(oraclePrefix, "-20")); - assertEq(oracle.minProposalSize(), _expected.minProposalSizeBytes, string.concat(oraclePrefix, "-30")); - } - function _assertETHLockbox(ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin) private view { IOptimismPortal2 portal = IOptimismPortal2(payable(_expected.systemConfig.optimismPortal())); IETHLockbox lockbox = _expected.ethLockbox; @@ -397,23 +331,6 @@ abstract contract SystemDeployAssertions is Test { } } - function _expectedPrestate( - ExpectedSystemDeployState memory _expected, - GameType _gameType - ) - private - pure - returns (Claim) - { - if (_gameType.raw() == GameTypes.CANNON_KONA.raw()) { - return _expected.cannonKonaPrestate; - } - if (_gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { - return _expected.permissionedCannonPrestate; - } - return _expected.cannonPrestate; - } - function _proxyAdminFor(address _contract) private view returns (IProxyAdmin) { return IProxyAdminOwnedBase(_contract).proxyAdmin(); } diff --git a/test/invariants/FaultDisputeGame.t.sol b/test/invariants/FaultDisputeGame.t.sol deleted file mode 100644 index b9cc18d89..000000000 --- a/test/invariants/FaultDisputeGame.t.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing -import { Vm } from "lib/forge-std/src/Vm.sol"; -import { StdUtils } from "lib/forge-std/src/StdUtils.sol"; -import { BaseFaultDisputeGame_TestInit } from "test/L1/proofs/FaultDisputeGame.t.sol"; - -// Libraries -import "src/libraries/bridge/Types.sol"; -import "src/libraries/bridge/Errors.sol"; - -// Interfaces -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; - -contract FaultDisputeGame_Solvency_Invariant is BaseFaultDisputeGame_TestInit { - Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10))); - Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32((uint256(3) << 248) | uint256(0))); - - RandomClaimActor internal actor; - uint256 internal defaultSenderBalance; - - function setUp() public override { - super.setUp(); - super.init({ rootClaim: ROOT_CLAIM, absolutePrestate: ABSOLUTE_PRESTATE, l2BlockNumber: 0x10 }); - - actor = new RandomClaimActor(gameProxy, vm); - - targetContract(address(actor)); - vm.startPrank(address(actor)); - } - - /// @custom:invariant FaultDisputeGame always returns all ETH on total resolution - /// - /// The FaultDisputeGame contract should always return all ETH in the contract to the correct recipients upon - /// resolution of all outstanding claims. There may never be any ETH left in the contract after a full resolution. - function invariant_faultDisputeGame_solvency() public { - vm.warp(block.timestamp + 7 days + 1 seconds); - - (,,, uint256 rootBond,,,) = gameProxy.claimData(0); - - // Ensure the game creator has locked up the root bond. - assertEq(address(this).balance, type(uint96).max - rootBond); - - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(address(actor)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + 7 days + 1 seconds); - - if (gameProxy.credit(address(this)) == 0) { - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(address(this)); - } else { - gameProxy.claimCredit(address(this)); - } - - if (gameProxy.credit(address(actor)) == 0) { - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(address(actor)); - } else { - gameProxy.claimCredit(address(actor)); - } - - if (gameProxy.status() == GameStatus.DEFENDER_WINS) { - // In the event that the defender wins, they receive their bond back. The root claim is never paid out - // bonds from claims below it, so the actor that has challenged the root claim (and potentially their) - // own receives all of their bonds back. - assertEq(address(this).balance, type(uint96).max); - assertEq(address(actor).balance, actor.totalBonded()); - } else if (gameProxy.status() == GameStatus.CHALLENGER_WINS) { - // If the defender wins, the game creator loses the root bond and the actor receives it. The actor also - // is the only party that may have challenged their own claims, so we expect them to receive all of them - // back. - assertEq(address(this).balance, type(uint96).max - rootBond); - assertEq(address(actor).balance, actor.totalBonded() + rootBond); - } else { - revert("FaultDisputeGame_Solvency_Invariant: unreachable"); - } - - assertEq(address(gameProxy).balance, 0); - } -} - -contract RandomClaimActor is StdUtils { - IFaultDisputeGameV2 internal immutable GAME; - Vm internal immutable VM; - - uint256 public totalBonded; - - constructor(IFaultDisputeGameV2 _gameProxy, Vm _vm) { - GAME = _gameProxy; - VM = _vm; - } - - function move(bool _isAttack, uint256 _parentIndex, Claim _claim) public { - _parentIndex = bound(_parentIndex, 0, GAME.claimDataLen() - 1); - - (,,,,, Position parentPos,) = GAME.claimData(_parentIndex); - Position nextPosition = parentPos.move(_isAttack); - uint256 bondAmount = GAME.getRequiredBond(nextPosition); - - VM.deal(address(this), bondAmount); - totalBonded += bondAmount; - - (,,,, Claim disputed,,) = GAME.claimData(_parentIndex); - GAME.move{ value: bondAmount }(disputed, _parentIndex, _claim, _isAttack); - } - - fallback() external payable { } - - receive() external payable { } -} diff --git a/test/libraries/bridge/LibClock.t.sol b/test/libraries/bridge/LibClock.t.sol deleted file mode 100644 index c1092dbf4..000000000 --- a/test/libraries/bridge/LibClock.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import { Test } from "lib/forge-std/src/Test.sol"; -import { LibClock } from "src/libraries/bridge/LibUDT.sol"; -import "src/libraries/bridge/Types.sol"; - -/// @title LibClock_Wrap_Test -/// @notice Tests the `wrap` function of the `LibClock` library. -contract LibClock_Wrap_Test is Test { - /// @notice Tests that the `wrap` function correctly shifts out the `Duration` from a packed - /// `Clock` type. - function testFuzz_wrap_duration_succeeds(Duration _duration, Timestamp _timestamp) public pure { - Clock clock = LibClock.wrap(_duration, _timestamp); - assertEq(Duration.unwrap(clock.duration()), Duration.unwrap(_duration)); - } - - /// @notice Tests that the `wrap` function correctly shifts out the `Timestamp` from a packed - /// `Clock` type. - function testFuzz_wrap_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public pure { - Clock clock = LibClock.wrap(_duration, _timestamp); - assertEq(Timestamp.unwrap(clock.timestamp()), Timestamp.unwrap(_timestamp)); - } -} diff --git a/test/libraries/bridge/LibGameArgs.t.sol b/test/libraries/bridge/LibGameArgs.t.sol deleted file mode 100644 index 91b45dcc4..000000000 --- a/test/libraries/bridge/LibGameArgs.t.sol +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import { Test } from "lib/forge-std/src/Test.sol"; -import { LibGameArgs } from "src/libraries/bridge/LibGameArgs.sol"; -import { InvalidGameArgsLength } from "src/libraries/bridge/Errors.sol"; - -contract LibGameArgs_Harness { - function encode(LibGameArgs.GameArgs memory _args) public pure returns (bytes memory) { - return LibGameArgs.encode(_args); - } - - function decode(bytes memory _buf) public pure returns (LibGameArgs.GameArgs memory) { - return LibGameArgs.decode(_buf); - } -} - -/// @title LibGameArgs_Decode_Test -/// @notice Test contract for the LibGameArgs library's decode function. -contract LibGameArgs_Decode_Test is Test { - LibGameArgs_Harness internal harness; - - function setUp() public { - harness = new LibGameArgs_Harness(); - } - - /// @notice Struct to hold game arguments for testing purposes. - /// Avoids "stack too deep" errors in the test functions. - struct GameArgs { - bytes32 absolutePrestate; - address vm; - address asr; - address weth; - uint256 l2ChainId; - address proposer; - address challenger; - } - - function test_encodeAndDecodeRoundTrip_succeeds() public { - LibGameArgs.GameArgs memory args = LibGameArgs.GameArgs({ - absolutePrestate: keccak256(abi.encodePacked("absolutePrestate")), - vm: vm.randomAddress(), - anchorStateRegistry: address(0x2), - weth: address(0x3), - l2ChainId: 42, - proposer: address(0x123), - challenger: address(0x456) - }); - - bytes memory encoded = harness.encode(args); - LibGameArgs.GameArgs memory decoded = harness.decode(encoded); - - assertEq(decoded.absolutePrestate, args.absolutePrestate); - assertEq(decoded.vm, args.vm); - assertEq(decoded.anchorStateRegistry, args.anchorStateRegistry); - assertEq(decoded.weth, args.weth); - assertEq(decoded.l2ChainId, args.l2ChainId); - assertEq(decoded.proposer, args.proposer); - assertEq(decoded.challenger, args.challenger); - } - - function test_encodePartialRoundTrip_succeeds() public { - LibGameArgs.GameArgs memory args = LibGameArgs.GameArgs({ - absolutePrestate: keccak256(abi.encodePacked("absolutePrestate")), - vm: vm.randomAddress(), - anchorStateRegistry: address(0x2), - weth: address(0x3), - l2ChainId: 42, - proposer: address(0), - challenger: address(0) - }); - - bytes memory encoded = harness.encode(args); - bytes memory expected = - abi.encodePacked(args.absolutePrestate, args.vm, args.anchorStateRegistry, args.weth, args.l2ChainId); - assertEq(encoded, expected); - } - - function test_decodeFull_succeeds() public { - GameArgs memory args = GameArgs({ - absolutePrestate: keccak256(abi.encodePacked("absolutePrestate")), - vm: vm.randomAddress(), - asr: address(0x2), - weth: address(0x3), - l2ChainId: 42, - proposer: address(0x123), - challenger: address(0x456) - }); - bytes memory buf = abi.encodePacked( - args.absolutePrestate, args.vm, args.asr, args.weth, args.l2ChainId, args.proposer, args.challenger - ); - - LibGameArgs.GameArgs memory decoded = harness.decode(buf); - assertEq(decoded.absolutePrestate, args.absolutePrestate); - assertEq(decoded.vm, args.vm); - assertEq(decoded.anchorStateRegistry, args.asr); - assertEq(decoded.weth, args.weth); - assertEq(decoded.l2ChainId, args.l2ChainId); - assertEq(decoded.proposer, args.proposer); - assertEq(decoded.challenger, args.challenger); - } - - function test_decodeShort_succeeds() public { - GameArgs memory args = GameArgs({ - absolutePrestate: keccak256(abi.encodePacked("absolutePrestate")), - vm: vm.randomAddress(), - asr: address(0x2), - weth: address(0x3), - l2ChainId: 42, - proposer: address(0x123), - challenger: address(0x456) - }); - bytes memory buf = abi.encodePacked(args.absolutePrestate, args.vm, args.asr, args.weth, args.l2ChainId); - - LibGameArgs.GameArgs memory decoded = harness.decode(buf); - assertEq(decoded.absolutePrestate, args.absolutePrestate); - assertEq(decoded.vm, args.vm); - assertEq(decoded.anchorStateRegistry, args.asr); - assertEq(decoded.weth, args.weth); - assertEq(decoded.l2ChainId, args.l2ChainId); - assertEq(decoded.proposer, address(0)); - assertEq(decoded.challenger, address(0)); - } - - function test_decode_invalidLengthOverfull_reverts() public { - GameArgs memory args = GameArgs({ - absolutePrestate: keccak256(abi.encodePacked("absolutePrestate")), - vm: vm.randomAddress(), - asr: address(0x2), - weth: address(0x3), - l2ChainId: 42, - proposer: address(0x123), - challenger: address(0x456) - }); - bytes memory buf = abi.encodePacked( - args.absolutePrestate, - args.vm, - args.asr, - args.weth, - args.l2ChainId, - args.proposer, - args.challenger, - uint256(999) - ); - - vm.expectRevert(InvalidGameArgsLength.selector); - harness.decode(buf); - } - - function testFuzz_decode_invalidLength_reverts(bytes memory _buf) public { - bool ok = - (_buf.length == LibGameArgs.PERMISSIONLESS_ARGS_LENGTH - || _buf.length == LibGameArgs.PERMISSIONED_ARGS_LENGTH); - vm.assume(!ok); - vm.expectRevert(InvalidGameArgsLength.selector); - harness.decode(_buf); - } - - function test_isValidPermissionlessArgs_works() public pure { - bytes memory validBuf = new bytes(LibGameArgs.PERMISSIONLESS_ARGS_LENGTH); - assertTrue(LibGameArgs.isValidPermissionlessArgs(validBuf)); - validBuf = new bytes(LibGameArgs.PERMISSIONED_ARGS_LENGTH); - assertFalse(LibGameArgs.isValidPermissionlessArgs(validBuf)); - } - - function test_isValidPermissionedArgs_works() public pure { - bytes memory validBuf = new bytes(LibGameArgs.PERMISSIONED_ARGS_LENGTH); - assertTrue(LibGameArgs.isValidPermissionedArgs(validBuf)); - validBuf = new bytes(LibGameArgs.PERMISSIONLESS_ARGS_LENGTH); - assertFalse(LibGameArgs.isValidPermissionedArgs(validBuf)); - } -} diff --git a/test/libraries/bridge/LibGameId.t.sol b/test/libraries/bridge/LibGameId.t.sol index f7d47d5ec..02f78005d 100644 --- a/test/libraries/bridge/LibGameId.t.sol +++ b/test/libraries/bridge/LibGameId.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; +import { Timestamp, GameId, LibGameId } from "src/libraries/bridge/LibUDT.sol"; import "src/libraries/bridge/Types.sol"; /// @title LibGameId_Pack_Test diff --git a/test/libraries/bridge/LibPosition.t.sol b/test/libraries/bridge/LibPosition.t.sol deleted file mode 100644 index a3c6ee1a6..000000000 --- a/test/libraries/bridge/LibPosition.t.sol +++ /dev/null @@ -1,473 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import { Test } from "lib/forge-std/src/Test.sol"; -import { LibPosition } from "src/libraries/bridge/LibPosition.sol"; -import "src/libraries/bridge/Types.sol"; - -/// @title LibPosition_TestInit -/// @notice Reusable test initialization for `LibPosition` tests. -abstract contract LibPosition_TestInit is Test { - /// @dev Assumes a MAX depth of 126 for the Position type. Any greater depth can cause - /// overflows. - /// @dev At the lowest level of the tree, this allows for 2 ** 126 leaves. In reality, the max - /// game depth will likely be much lower. - uint8 internal constant MAX_DEPTH = 126; - /// @dev Arbitrary split depth around half way down the tree. - uint8 internal constant SPLIT_DEPTH = 64; - - function boundIndexAtDepth(uint8 _depth, uint128 _indexAtDepth) internal pure returns (uint128) { - // Index at depth bound: [0, 2 ** _depth-1] - if (_depth > 0) { - return uint128(bound(_indexAtDepth, 0, 2 ** (_depth - 1))); - } else { - return 0; - } - } -} - -/// @title LibPosition_Depth_Test -/// @notice Tests the `depth` function of the `LibPosition` contract. -contract LibPosition_Depth_Test is LibPosition_TestInit { - /// @notice Tests that the `depth` function correctly shifts out the `depth` from a packed - /// `Position` type. - function testFuzz_depth_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, 0, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - Position position = LibPosition.wrap(_depth, _indexAtDepth); - assertEq(position.depth(), _depth); - } -} - -/// @title LibPosition_IndexAtDepth_Test -/// @notice Tests the `indexAtDepth` function of the `LibPosition` contract. -contract LibPosition_IndexAtDepth_Test is LibPosition_TestInit { - /// @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from - /// a packed `Position` type. - function testFuzz_indexAtDepth_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, 0, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - Position position = LibPosition.wrap(_depth, _indexAtDepth); - assertEq(position.indexAtDepth(), _indexAtDepth); - } -} - -/// @title LibPosition_Left_Test -/// @notice Tests the `left` function of the `LibPosition` contract. -contract LibPosition_Left_Test is LibPosition_TestInit { - /// @notice Tests that the `left` function correctly computes the position of the left child. - function testFuzz_left_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, 0, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position left = position.left(); - - assertEq(left.depth(), _depth + 1); - assertEq(left.indexAtDepth(), _indexAtDepth * 2); - } -} - -/// @title LibPosition_Right_Test -/// @notice Tests the `right` function of the `LibPosition` contract. -contract LibPosition_Right_Test is LibPosition_TestInit { - /// @notice Tests that the `right` function correctly computes the position of the right child. - function testFuzz_right_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - // Depth bound: [0, 63] - _depth = uint8(bound(_depth, 0, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position right = position.right(); - - assertEq(right.depth(), _depth + 1); - assertEq(right.indexAtDepth(), _indexAtDepth * 2 + 1); - } -} - -/// @title LibPosition_Parent_Test -/// @notice Tests the `parent` function of the `LibPosition` contract. -contract LibPosition_Parent_Test is LibPosition_TestInit { - /// @notice Tests that the `parent` function correctly computes the position of the parent. - function testFuzz_parent_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, 1, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position parent = position.parent(); - - assertEq(parent.depth(), _depth - 1); - assertEq(parent.indexAtDepth(), _indexAtDepth / 2); - } -} - -/// @title LibPosition_RightIndex_Test -/// @notice Tests the `rightIndex` function of the `LibPosition` contract. -contract LibPosition_RightIndex_Test is LibPosition_TestInit { - /// @notice Tests that the `rightIndex` function correctly computes the deepest, right most - /// index relative to a given position. - function testFuzz_rightIndex_correctness_succeeds( - uint8 _maxDepth, - uint8 _depth, - uint128 _indexAtDepth - ) - public - pure - { - // Max depth bound: [1, 63] - // The max game depth MUST be at least 1. - _maxDepth = uint8(bound(_maxDepth, 1, MAX_DEPTH)); - // Depth bound: [0, _maxDepth] - _depth = uint8(bound(_depth, 0, _maxDepth)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position rightIndex = position.rightIndex(_maxDepth); - - // Find the deepest, rightmost index in Solidity rather than Yul - for (uint256 i = _depth; i < _maxDepth; ++i) { - position = position.right(); - } - - assertEq(Position.unwrap(rightIndex), Position.unwrap(position)); - } -} - -/// @title LibPosition_TraceAncestor_Test -/// @notice Tests the `traceAncestor` function of the `LibPosition` contract. -contract LibPosition_TraceAncestor_Test is LibPosition_TestInit { - /// @notice Tests that the `traceAncestor` function correctly computes the position of the - /// highest ancestor that commits to the same trace index. - function testFuzz_traceAncestor_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, 1, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position ancestor = position.traceAncestor(); - Position loopAncestor = position; - while (loopAncestor.parent().traceIndex(MAX_DEPTH) == position.traceIndex(MAX_DEPTH)) { - loopAncestor = loopAncestor.parent(); - } - - assertEq(Position.unwrap(ancestor), Position.unwrap(loopAncestor)); - } -} - -/// @title LibPosition_TraceAncestorBounded_Test -/// @notice Tests the `traceAncestorBounded` function of the `LibPosition` contract. -contract LibPosition_TraceAncestorBounded_Test is LibPosition_TestInit { - /// @notice Tests that the `traceAncestorBounded` function correctly computes the position of - /// the highest ancestor (below `SPLIT_DEPTH`) that commits to the same trace index. - function testFuzz_traceAncestorBounded_correctness_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - _depth = uint8(bound(_depth, SPLIT_DEPTH + 1, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position ancestor = position.traceAncestorBounded(SPLIT_DEPTH); - Position loopAncestor = position; - - // Stop at 1 below the split depth. - while ( - loopAncestor.parent().traceIndex(MAX_DEPTH) == position.traceIndex(MAX_DEPTH) - && loopAncestor.depth() != SPLIT_DEPTH + 1 - ) { - loopAncestor = loopAncestor.parent(); - } - - assertEq(Position.unwrap(ancestor), Position.unwrap(loopAncestor)); - } -} - -/// @title LibPosition_Move_Test -/// @notice Tests the `move` function of the `LibPosition` contract. -contract LibPosition_Move_Test is LibPosition_TestInit { - /// @notice Tests that the `attack` function correctly computes the position of the attack - /// relative to a given position. - /// @dev `attack` is an alias for `left`, but we test it separately for completeness. - function testFuzz_move_attack_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - // Depth bound: [0, 63] - _depth = uint8(bound(_depth, 0, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position attack = position.move(true); - - assertEq(attack.depth(), _depth + 1); - assertEq(attack.indexAtDepth(), _indexAtDepth * 2); - } - - /// @notice Tests that the `defend` function correctly computes the position of the defense - /// relative to a given position. - /// @dev A defense can only be given if the position does not belong to the root claim, hence - /// the bound of [1, 127] on the depth. - function testFuzz_move_defend_succeeds(uint8 _depth, uint128 _indexAtDepth) public pure { - // Depth bound: [1, 63] - _depth = uint8(bound(_depth, 1, MAX_DEPTH)); - _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); - - Position position = LibPosition.wrap(_depth, _indexAtDepth); - Position defend = position.move(false); - - assertEq(defend.depth(), _depth + 1); - assertEq(defend.indexAtDepth(), ((_indexAtDepth / 2) * 2 + 1) * 2); - } -} - -/// @title LibPosition_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `LibPosition` contract -/// or are testing multiple functions at once. -contract LibPosition_Uncategorized_Test is LibPosition_TestInit { - /// @notice A static unit test for the correctness of all gindicies, (depth, index) combos, and - /// the trace index in a tree of max depth = 4. - function test_pos_correctness_succeeds() public pure { - uint256 maxDepth = 4; - - Position p = LibPosition.wrap(0, 0); - assertEq(Position.unwrap(p), 1); // gindex = 1 - assertEq(p.depth(), 0); // depth = 0 - assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 - Position r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 31); // right gindex = 31 - assertEq(r.indexAtDepth(), 15); // trace index = 15 - - p = LibPosition.wrap(1, 0); - assertEq(Position.unwrap(p), 2); // gindex = 2 - assertEq(p.depth(), 1); // depth = 1 - assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 23); // right gindex = 23 - assertEq(r.indexAtDepth(), 7); // trace index = 7 - - p = LibPosition.wrap(1, 1); - assertEq(Position.unwrap(p), 3); // gindex = 3 - assertEq(p.depth(), 1); // depth = 1 - assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 31); // right gindex = 31 - assertEq(r.indexAtDepth(), 15); // trace index = 15 - - p = LibPosition.wrap(2, 0); - assertEq(Position.unwrap(p), 4); // gindex = 4 - assertEq(p.depth(), 2); // depth = 2 - assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 19); // right gindex = 19 - assertEq(r.indexAtDepth(), 3); // trace index = 3 - - p = LibPosition.wrap(2, 1); - assertEq(Position.unwrap(p), 5); // gindex = 5 - assertEq(p.depth(), 2); // depth = 2 - assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 23); // right gindex = 23 - assertEq(r.indexAtDepth(), 7); // trace index = 7 - - p = LibPosition.wrap(2, 2); - assertEq(Position.unwrap(p), 6); // gindex = 6 - assertEq(p.depth(), 2); // depth = 2 - assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 27); // right gindex = 27 - assertEq(r.indexAtDepth(), 11); // trace index = 11 - - p = LibPosition.wrap(2, 3); - assertEq(Position.unwrap(p), 7); // gindex = 7 - assertEq(p.depth(), 2); // depth = 2 - assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 31); // right gindex = 31 - assertEq(r.indexAtDepth(), 15); // trace index = 15 - - p = LibPosition.wrap(3, 0); - assertEq(Position.unwrap(p), 8); // gindex = 8 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 17); // right gindex = 17 - assertEq(r.indexAtDepth(), 1); // trace index = 1 - - p = LibPosition.wrap(3, 1); - assertEq(Position.unwrap(p), 9); // gindex = 9 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 19); // right gindex = 19 - assertEq(r.indexAtDepth(), 3); // trace index = 3 - - p = LibPosition.wrap(3, 2); - assertEq(Position.unwrap(p), 10); // gindex = 10 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 21); // right gindex = 21 - assertEq(r.indexAtDepth(), 5); // trace index = 5 - - p = LibPosition.wrap(3, 3); - assertEq(Position.unwrap(p), 11); // gindex = 11 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 23); // right gindex = 23 - assertEq(r.indexAtDepth(), 7); // trace index = 7 - - p = LibPosition.wrap(3, 4); - assertEq(Position.unwrap(p), 12); // gindex = 12 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 25); // right gindex = 25 - assertEq(r.indexAtDepth(), 9); // trace index = 9 - - p = LibPosition.wrap(3, 5); - assertEq(Position.unwrap(p), 13); // gindex = 13 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 27); // right gindex = 27 - assertEq(r.indexAtDepth(), 11); // trace index = 11 - - p = LibPosition.wrap(3, 6); - assertEq(Position.unwrap(p), 14); // gindex = 14 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 29); // right gindex = 29 - assertEq(r.indexAtDepth(), 13); // trace index = 13 - - p = LibPosition.wrap(3, 7); - assertEq(Position.unwrap(p), 15); // gindex = 15 - assertEq(p.depth(), 3); // depth = 3 - assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 31); // right gindex = 31 - assertEq(r.indexAtDepth(), 15); // trace index = 15 - - p = LibPosition.wrap(4, 0); - assertEq(Position.unwrap(p), 16); // gindex = 16 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 16); // right gindex = 16 - assertEq(r.indexAtDepth(), 0); // trace index = 0 - - p = LibPosition.wrap(4, 1); - assertEq(Position.unwrap(p), 17); // gindex = 17 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 17); // right gindex = 17 - assertEq(r.indexAtDepth(), 1); // trace index = 1 - - p = LibPosition.wrap(4, 2); - assertEq(Position.unwrap(p), 18); // gindex = 18 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 18); // right gindex = 18 - assertEq(r.indexAtDepth(), 2); // trace index = 2 - - p = LibPosition.wrap(4, 3); - assertEq(Position.unwrap(p), 19); // gindex = 19 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 19); // right gindex = 19 - assertEq(r.indexAtDepth(), 3); // trace index = 3 - - p = LibPosition.wrap(4, 4); - assertEq(Position.unwrap(p), 20); // gindex = 20 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 20); // right gindex = 20 - assertEq(r.indexAtDepth(), 4); // trace index = 4 - - p = LibPosition.wrap(4, 5); - assertEq(Position.unwrap(p), 21); // gindex = 21 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 21); // right gindex = 21 - assertEq(r.indexAtDepth(), 5); // trace index = 5 - - p = LibPosition.wrap(4, 6); - assertEq(Position.unwrap(p), 22); // gindex = 22 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 22); // right gindex = 22 - assertEq(r.indexAtDepth(), 6); // trace index = 6 - - p = LibPosition.wrap(4, 7); - assertEq(Position.unwrap(p), 23); // gindex = 23 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 23); // right gindex = 23 - assertEq(r.indexAtDepth(), 7); // trace index = 7 - - p = LibPosition.wrap(4, 8); - assertEq(Position.unwrap(p), 24); // gindex = 24 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 8); // indexAtDepth = 8 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 24); // right gindex = 24 - assertEq(r.indexAtDepth(), 8); // trace index = 8 - - p = LibPosition.wrap(4, 9); - assertEq(Position.unwrap(p), 25); // gindex = 25 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 9); // indexAtDepth = 9 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 25); // right gindex = 25 - assertEq(r.indexAtDepth(), 9); // trace index = 9 - - p = LibPosition.wrap(4, 10); - assertEq(Position.unwrap(p), 26); // gindex = 26 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 10); // indexAtDepth = 10 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 26); // right gindex = 26 - assertEq(r.indexAtDepth(), 10); // trace index = 10 - - p = LibPosition.wrap(4, 11); - assertEq(Position.unwrap(p), 27); // gindex = 27 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 11); // indexAtDepth = 11 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 27); // right gindex = 27 - assertEq(r.indexAtDepth(), 11); // trace index = 11 - - p = LibPosition.wrap(4, 12); - assertEq(Position.unwrap(p), 28); // gindex = 28 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 12); // indexAtDepth = 12 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 28); // right gindex = 28 - assertEq(r.indexAtDepth(), 12); // trace index = 12 - - p = LibPosition.wrap(4, 13); - assertEq(Position.unwrap(p), 29); // gindex = 29 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 13); // indexAtDepth = 13 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 29); // right gindex = 29 - assertEq(r.indexAtDepth(), 13); // trace index = 13 - - p = LibPosition.wrap(4, 14); - assertEq(Position.unwrap(p), 30); // gindex = 30 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 14); // indexAtDepth = 14 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 30); // right gindex = 30 - assertEq(r.indexAtDepth(), 14); // trace index = 14 - - p = LibPosition.wrap(4, 15); - assertEq(Position.unwrap(p), 31); // gindex = 31 - assertEq(p.depth(), 4); // depth = 4 - assertEq(p.indexAtDepth(), 15); // indexAtDepth = 15 - r = p.rightIndex(maxDepth); - assertEq(Position.unwrap(r), 31); // right gindex = 31 - assertEq(r.indexAtDepth(), 15); // trace index = 15 - } -} diff --git a/test/mocks/AlphabetVM.sol b/test/mocks/AlphabetVM.sol deleted file mode 100644 index bfe9ca87a..000000000 --- a/test/mocks/AlphabetVM.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Libraries -import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; -import "src/libraries/bridge/Types.sol"; - -// Interfaces -import { IBigStepper, IPreimageOracle } from "interfaces/L1/proofs/IBigStepper.sol"; - -/// @title AlphabetVM -/// @dev A mock VM for the purpose of testing the dispute game infrastructure. Note that this only works -/// for games with an execution trace subgame max depth of 3 (8 instructions per subgame). -contract AlphabetVM is IBigStepper { - Claim internal immutable ABSOLUTE_PRESTATE; - IPreimageOracle public oracle; - - constructor(Claim _absolutePrestate, IPreimageOracle _oracle) { - ABSOLUTE_PRESTATE = _absolutePrestate; - oracle = _oracle; - } - - /// @inheritdoc IBigStepper - function step( - bytes calldata _stateData, - bytes calldata, - bytes32 _localContext - ) - external - view - returns (bytes32 postState_) - { - uint256 traceIndex; - uint256 claim; - if ((keccak256(_stateData) << 8) == (Claim.unwrap(ABSOLUTE_PRESTATE) << 8)) { - // If the state data is empty, then the absolute prestate is the claim. - (bytes32 dat,) = oracle.readPreimage( - PreimageKeyLib.localizeIdent(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, _localContext), 0 - ); - uint256 startingL2BlockNumber = ((uint256(dat) >> 128) & 0xFFFFFFFF) - 1; - traceIndex = startingL2BlockNumber << 4; - (uint256 absolutePrestateClaim) = abi.decode(_stateData, (uint256)); - claim = absolutePrestateClaim + traceIndex; - } else { - // Otherwise, decode the state data. - (traceIndex, claim) = abi.decode(_stateData, (uint256, uint256)); - traceIndex++; - claim++; - } - - // STF: n -> n + 1 - postState_ = keccak256(abi.encode(traceIndex, claim)); - assembly { - postState_ := or(and(postState_, not(shl(248, 0xFF))), shl(248, 1)) - } - } -} diff --git a/test/setup/Events.sol b/test/setup/Events.sol index 3967f0b2c..d3119ed82 100644 --- a/test/setup/Events.sol +++ b/test/setup/Events.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // Libraries import { Types } from "src/libraries/Types.sol"; +import { Timestamp } from "src/libraries/bridge/LibUDT.sol"; import "src/libraries/bridge/Types.sol"; // Interfaces diff --git a/test/setup/FFIInterface.sol b/test/setup/FFIInterface.sol index 2b5a62f15..ca2fd4d8f 100644 --- a/test/setup/FFIInterface.sol +++ b/test/setup/FFIInterface.sol @@ -232,90 +232,6 @@ contract FFIInterface { return abi.decode(vm.ffi(cmds), (bytes32, bytes, bytes, bytes[])); } - function getCannonMemory64Proof(uint64 addr, uint64 value) external returns (bytes32, bytes memory) { - string[] memory cmds = new string[](5); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "cannonMemoryProof"; - cmds[3] = vm.toString(addr); - cmds[4] = vm.toString(value); - bytes memory result = vm.ffi(cmds); - (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); - return (memRoot, proof); - } - - function getCannonMemory64Proof( - uint64 addr0, - uint64 value0, - uint64 addr1, - uint64 value1 - ) - external - returns (bytes32, bytes memory) - { - string[] memory cmds = new string[](7); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "cannonMemoryProof"; - cmds[3] = vm.toString(addr0); - cmds[4] = vm.toString(value0); - cmds[5] = vm.toString(addr1); - cmds[6] = vm.toString(value1); - bytes memory result = vm.ffi(cmds); - (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); - return (memRoot, proof); - } - - function getCannonMemory64Proof( - uint64 addr0, - uint64 value0, - uint64 addr1, - uint64 value1, - uint64 memAddr2, - uint64 memVal2 - ) - external - returns (bytes32, bytes memory) - { - string[] memory cmds = new string[](9); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "cannonMemoryProof"; - cmds[3] = vm.toString(addr0); - cmds[4] = vm.toString(value0); - cmds[5] = vm.toString(addr1); - cmds[6] = vm.toString(value1); - cmds[7] = vm.toString(memAddr2); - cmds[8] = vm.toString(memVal2); - bytes memory result = vm.ffi(cmds); - (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); - return (memRoot, proof); - } - - function getCannonMemory64Proof2( - uint64 addr0, - uint64 value0, - uint64 addr1, - uint64 value1, - uint64 memAddrForProof - ) - external - returns (bytes32, bytes memory) - { - string[] memory cmds = new string[](8); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "cannonMemoryProof2"; - cmds[3] = vm.toString(addr0); - cmds[4] = vm.toString(value0); - cmds[5] = vm.toString(addr1); - cmds[6] = vm.toString(value1); - cmds[7] = vm.toString(memAddrForProof); - bytes memory result = vm.ffi(cmds); - (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); - return (memRoot, proof); - } - function encodeScalarEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external returns (bytes32) { string[] memory cmds = new string[](5); cmds[0] = "scripts/go-ffi/go-ffi"; diff --git a/test/setup/ForkLive.s.sol b/test/setup/ForkLive.s.sol index 32e0d367a..281a82cbd 100644 --- a/test/setup/ForkLive.s.sol +++ b/test/setup/ForkLive.s.sol @@ -14,16 +14,17 @@ import { SystemDeploy } from "scripts/deploy/SystemDeploy.s.sol"; import { Config } from "scripts/libraries/Config.sol"; // Libraries -import { GameTypes, Claim } from "src/libraries/bridge/Types.sol"; +import { GameTypes } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { LibGameArgs } from "src/libraries/bridge/LibGameArgs.sol"; import { Types } from "scripts/libraries/Types.sol"; // Interfaces import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { IBigStepper } from "interfaces/L1/proofs/IBigStepper.sol"; +import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; +import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; +import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -59,7 +60,6 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { struct GameAddresses { address anchorStateRegistry; address weth; - address mips; } /// @notice Thrown when testing with an unsupported chain ID. @@ -174,23 +174,14 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { _saveProxyAndImpl("L1StandardBridge", systemConfigAddresses.l1StandardBridge); _saveProxyAndImpl("L1ERC721Bridge", systemConfigAddresses.l1ERC721Bridge); - // Fault proof proxied contracts IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(systemConfig.disputeGameFactory()); - IFaultDisputeGameV2 permissionedDisputeGame = - IFaultDisputeGameV2(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))); - GameAddresses memory gameAddresses = _permissionedGameAddresses(disputeGameFactory, permissionedDisputeGame); + IAggregateVerifier aggregateVerifier = + IAggregateVerifier(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER))); + GameAddresses memory gameAddresses = _aggregateVerifierAddresses(aggregateVerifier); _saveProxyAndImpl("AnchorStateRegistry", gameAddresses.anchorStateRegistry); _saveProxyAndImpl("DisputeGameFactory", address(disputeGameFactory)); - // Fault proof non-proxied contracts - IBigStepper mips = IBigStepper(gameAddresses.mips); - artifacts.save("PreimageOracle", address(mips.oracle())); - artifacts.save("MipsSingleton", address(mips)); - - artifacts.save("PermissionedDisputeGame", address(permissionedDisputeGame)); - artifacts.save("PermissionedDelayedWETHProxy", gameAddresses.weth); - - // Pull the DelayedWETH addresses from the PermissionedDisputeGame so stale local data cannot break this. + // Pull DelayedWETH from the AggregateVerifier so stale local data cannot break this. artifacts.save("DelayedWETHProxy", gameAddresses.weth); artifacts.save("DelayedWETHImpl", EIP1967Helper.getImplementation(gameAddresses.weth)); } @@ -208,13 +199,7 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { SystemDeploy systemDeploy = new SystemDeploy(); Types.Implementations memory implementations = _latestImplementations(); - ISystemConfig systemConfig = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); - Types.OpChainConfig[] memory opChains = new Types.OpChainConfig[](1); - opChains[0] = Types.OpChainConfig({ - systemConfigProxy: systemConfig, - cannonPrestate: Claim.wrap(bytes32(keccak256("cannonPrestate"))), - cannonKonaPrestate: Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))) - }); + ISystemConfig systemConfigProxy = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); IProxyAdmin superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); @@ -228,7 +213,7 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { saveArtifacts: false, superchainConfigProxy: superchainConfig, implementations: implementations, - opChainConfigs: new Types.OpChainConfig[](0) + systemConfigProxy: ISystemConfig(address(0)) }) ); @@ -239,7 +224,7 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { saveArtifacts: false, superchainConfigProxy: ISuperchainConfig(address(0)), implementations: implementations, - opChainConfigs: opChains + systemConfigProxy: systemConfigProxy }) ); } @@ -269,12 +254,10 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { // A new ASR and new dispute games were deployed, so we need to update them IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); - address permissionedDisputeGame = address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)); - artifacts.save("PermissionedDisputeGame", permissionedDisputeGame); + IDisputeGame av = disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER); + artifacts.save("AggregateVerifier", address(av)); - IAnchorStateRegistry newAnchorStateRegistry = IAnchorStateRegistry( - LibGameArgs.decode(disputeGameFactory.gameArgs(GameTypes.PERMISSIONED_CANNON)).anchorStateRegistry - ); + IAnchorStateRegistry newAnchorStateRegistry = av.anchorStateRegistry(); artifacts.save("AnchorStateRegistryProxy", address(newAnchorStateRegistry)); // Get the lockbox address from the portal, and save it @@ -283,8 +266,7 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { artifacts.save("ETHLockboxProxy", lockboxAddress); // Get the new DelayedWETH address and save it (might be a new proxy). - IDelayedWETH newDelayedWeth = - IDelayedWETH(payable(LibGameArgs.decode(disputeGameFactory.gameArgs(GameTypes.PERMISSIONED_CANNON)).weth)); + IDelayedWETH newDelayedWeth = IAggregateVerifier(address(av)).DELAYED_WETH(); artifacts.save("DelayedWETHProxy", address(newDelayedWeth)); artifacts.save("DelayedWETHImpl", EIP1967Helper.getImplementation(address(newDelayedWeth))); } @@ -309,31 +291,15 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { return address(uint160(uint256(vm.load(_proxy, keccak256(abi.encode(_proxy, uint256(1))))))); } - /// @notice Returns the addresses encoded for the permissioned dispute game. - function _permissionedGameAddresses( - IDisputeGameFactory _disputeGameFactory, - IFaultDisputeGameV2 _permissionedDisputeGame - ) + /// @notice Returns the addresses used by the AggregateVerifier. + function _aggregateVerifierAddresses(IAggregateVerifier _aggregateVerifier) internal view returns (GameAddresses memory game_) { - bytes memory gameArgs = _disputeGameFactory.gameArgs(GameTypes.PERMISSIONED_CANNON); - if ( - gameArgs.length == LibGameArgs.PERMISSIONED_ARGS_LENGTH - || gameArgs.length == LibGameArgs.PERMISSIONLESS_ARGS_LENGTH - ) { - LibGameArgs.GameArgs memory decoded = LibGameArgs.decode(gameArgs); - return - GameAddresses({ - anchorStateRegistry: decoded.anchorStateRegistry, weth: decoded.weth, mips: decoded.vm - }); - } - return GameAddresses({ - anchorStateRegistry: address(_permissionedDisputeGame.anchorStateRegistry()), - weth: address(_permissionedDisputeGame.weth()), - mips: address(_permissionedDisputeGame.vm()) + anchorStateRegistry: address(_aggregateVerifier.anchorStateRegistry()), + weth: address(_aggregateVerifier.DELAYED_WETH()) }); } } diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index 7bad9cd3c..dd70a798d 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -34,7 +34,6 @@ import { IOptimismMintableERC721Factory } from "interfaces/L2/IOptimismMintableE import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IBigStepper } from "interfaces/L1/proofs/IBigStepper.sol"; import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; import { IL2StandardBridge } from "interfaces/L2/IL2StandardBridge.sol"; import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; @@ -49,8 +48,6 @@ import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IWETH98 } from "interfaces/universal/IWETH98.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/L1/proofs/v2/IPermissionedDisputeGameV2.sol"; -import { IFaultDisputeGameV2 } from "interfaces/L1/proofs/v2/IFaultDisputeGameV2.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; @@ -92,10 +89,7 @@ abstract contract Setup is FeatureFlags { // L1 contracts - dispute IDisputeGameFactory disputeGameFactory; IAnchorStateRegistry anchorStateRegistry; - IFaultDisputeGameV2 faultDisputeGame; IDelayedWETH delayedWeth; - IPermissionedDisputeGameV2 permissionedDisputeGame; - IDelayedWETH delayedWETHPermissionedGameProxy; // L1 contracts - core address proxyAdminOwner; @@ -111,7 +105,6 @@ abstract contract Setup is FeatureFlags { IL1ERC721Bridge l1ERC721Bridge; IOptimismMintableERC20Factory l1OptimismMintableERC20Factory; ISuperchainConfig superchainConfig; - IBigStepper mips; // L2 contracts IL2CrossDomainMessenger l2CrossDomainMessenger = @@ -263,7 +256,6 @@ abstract contract Setup is FeatureFlags { proxyAdminOwner = proxyAdmin.owner(); superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); superchainProxyAdminOwner = superchainProxyAdmin.owner(); - mips = IBigStepper(artifacts.mustGetAddress("MipsSingleton")); aggregateVerifier = IVerifier(artifacts.getAddress("AggregateVerifier")); teeProverRegistry = TEEProverRegistry(artifacts.getAddress("TEEProverRegistry")); diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 9885f122d..e36f9fd36 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -325,12 +325,6 @@ contract Initializer_Test is CommonTest { string[] memory excludes = new string[](20); // Periphery contracts don't get deployed as part of the standard deployment script. excludes[j++] = "src/periphery/*"; - // TODO: Deployment script is currently "broken" in the sense that it doesn't properly - // label the FaultDisputeGame, PermissionedDisputeGame - // contracts and instead simply deploys them anonymously. Means that functions like "getInitializedSlot" - // don't work properly. Remove these exclusions once the deployment script is fixed. - excludes[j++] = "src/L1/proofs/v2/FaultDisputeGameV2.sol"; - excludes[j++] = "src/L1/proofs/v2/PermissionedDisputeGameV2.sol"; // L2 contract initialization is tested in Predeploys.t.sol excludes[j++] = "src/L2/*"; // Contract is not deployed as part of the standard deployment script.