diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 4ea1c35b27a..c16a52711ad 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -39,6 +39,7 @@ type Implementations struct { PermissionedDisputeGameImpl common.Address `json:"PermissionedDisputeGameImpl"` SuperFaultDisputeGameImpl common.Address `json:"SuperFaultDisputeGameImpl"` SuperPermissionedDisputeGameImpl common.Address `json:"SuperPermissionedDisputeGameImpl"` + ZkDisputeGameImpl common.Address `json:"ZkDisputeGameImpl"` StorageSetterImpl common.Address `json:"StorageSetterImpl"` } diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index 41139a9f885..2d236078b6a 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -1012,6 +1012,11 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat InitBond: big.NewInt(0), GameType: embedded.GameTypeSuperCannonKona, }, + { + Enabled: false, + InitBond: big.NewInt(0), + GameType: embedded.GameTypeZKDisputeGame, + }, }, ExtraInstructions: []embedded.ExtraInstruction{ { @@ -1034,27 +1039,29 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat // Structure breakdown: // - Tuple offset (0x20) // - SystemConfig address (0x034edd2a225f7f429a63e0f1d2084b9e0a93b538) - // - DisputeGameConfigs array offset (0x60) and ExtraInstructions array offset (0x580) - // - DisputeGameConfigs[]: 6 configs + // - DisputeGameConfigs array offset (0x60) and ExtraInstructions array offset (0x640) + // - DisputeGameConfigs[]: 7 configs // [0] Cannon: enabled=true, initBond=1e18, gameType=0, gameArgs="PRESTATE" // [1] PermissionedCannon: enabled=true, initBond=1e18, gameType=1, gameArgs="PRESTATE"+proposer+challenger // [2] CannonKona: enabled=false, initBond=0, gameType=8, gameArgs=empty // [3] SuperCannon: enabled=false, initBond=0, gameType=4, gameArgs=empty // [4] SuperPermCannon: enabled=false, initBond=0, gameType=5, gameArgs=empty // [5] SuperCannonKona: enabled=false, initBond=0, gameType=9, gameArgs=empty + // [6] ZKDisputeGame: enabled=false, initBond=0, gameType=10, gameArgs=empty // - ExtraInstructions[]: 1 instruction // [0] key="PermittedProxyDeployment", data="DelayedWETH" expected := "0000000000000000000000000000000000000000000000000000000000000020" + // offset to tuple "000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538" + // systemConfig address "0000000000000000000000000000000000000000000000000000000000000060" + // offset to disputeGameConfigs - "0000000000000000000000000000000000000000000000000000000000000580" + // offset to extraInstructions - "0000000000000000000000000000000000000000000000000000000000000006" + // disputeGameConfigs.length (6) - "00000000000000000000000000000000000000000000000000000000000000c0" + // offset to disputeGameConfigs[0] - "0000000000000000000000000000000000000000000000000000000000000180" + // offset to disputeGameConfigs[1] - "0000000000000000000000000000000000000000000000000000000000000280" + // offset to disputeGameConfigs[2] - "0000000000000000000000000000000000000000000000000000000000000320" + // offset to disputeGameConfigs[3] - "00000000000000000000000000000000000000000000000000000000000003c0" + // offset to disputeGameConfigs[4] - "0000000000000000000000000000000000000000000000000000000000000460" + // offset to disputeGameConfigs[5] + "0000000000000000000000000000000000000000000000000000000000000640" + // offset to extraInstructions + "0000000000000000000000000000000000000000000000000000000000000007" + // disputeGameConfigs.length (7) + "00000000000000000000000000000000000000000000000000000000000000e0" + // offset to disputeGameConfigs[0] + "00000000000000000000000000000000000000000000000000000000000001a0" + // offset to disputeGameConfigs[1] + "00000000000000000000000000000000000000000000000000000000000002a0" + // offset to disputeGameConfigs[2] + "0000000000000000000000000000000000000000000000000000000000000340" + // offset to disputeGameConfigs[3] + "00000000000000000000000000000000000000000000000000000000000003e0" + // offset to disputeGameConfigs[4] + "0000000000000000000000000000000000000000000000000000000000000480" + // offset to disputeGameConfigs[5] + "0000000000000000000000000000000000000000000000000000000000000520" + // offset to disputeGameConfigs[6] // DisputeGameConfigs[0] - Cannon "0000000000000000000000000000000000000000000000000000000000000001" + // enabled=true "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + // initBond=1e18 @@ -1095,6 +1102,12 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat "0000000000000000000000000000000000000000000000000000000000000009" + // gameType=9 (SuperCannonKona) "0000000000000000000000000000000000000000000000000000000000000080" + // offset to gameArgs "0000000000000000000000000000000000000000000000000000000000000000" + // gameArgs.length (0) + // DisputeGameConfigs[6] - ZKDisputeGame (disabled) + "0000000000000000000000000000000000000000000000000000000000000000" + // enabled=false + "0000000000000000000000000000000000000000000000000000000000000000" + // initBond=0 + "000000000000000000000000000000000000000000000000000000000000000a" + // gameType=10 (ZKDisputeGame) + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to gameArgs + "0000000000000000000000000000000000000000000000000000000000000000" + // gameArgs.length (0) // ExtraInstructions array "0000000000000000000000000000000000000000000000000000000000000001" + // extraInstructions.length (1) "0000000000000000000000000000000000000000000000000000000000000020" + // offset to extraInstructions[0] diff --git a/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go b/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go index 8ddd7e89a6f..99db87438ea 100644 --- a/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go +++ b/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go @@ -179,6 +179,11 @@ func TestManageAddGameTypeV2_Integration(t *testing.T) { InitBond: big.NewInt(0), GameType: embedded.GameTypeSuperCannonKona, }, + { + Enabled: false, + InitBond: big.NewInt(0), + GameType: embedded.GameTypeZKDisputeGame, + }, }, ExtraInstructions: []embedded.ExtraInstruction{ { diff --git a/op-deployer/pkg/deployer/integration_test/shared/shared.go b/op-deployer/pkg/deployer/integration_test/shared/shared.go index d391ea59386..a799689f56b 100644 --- a/op-deployer/pkg/deployer/integration_test/shared/shared.go +++ b/op-deployer/pkg/deployer/integration_test/shared/shared.go @@ -296,6 +296,11 @@ func buildV2OPCMUpgradeConfig(t *testing.T, prank, opcmAddr, systemConfigProxy c InitBond: big.NewInt(0), GameType: embedded.GameTypeSuperCannonKona, }, + { + Enabled: false, + InitBond: big.NewInt(0), + GameType: embedded.GameTypeZKDisputeGame, + }, } // Sort by game type (required by OPCM) diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index f1b5a8609d9..ab1046aef6a 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -58,6 +58,7 @@ type DeployImplementationsOutput struct { PermissionedDisputeGameImpl common.Address `json:"permissionedDisputeGameImplAddress"` SuperFaultDisputeGameImpl common.Address `json:"superFaultDisputeGameImplAddress"` SuperPermissionedDisputeGameImpl common.Address `json:"superPermissionedDisputeGameImplAddress"` + ZkDisputeGameImpl common.Address `json:"zkDisputeGameImplAddress" abi:"zkDisputeGameImpl"` StorageSetterImpl common.Address `json:"storageSetterImplAddress"` } diff --git a/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go b/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go index fd0d3b30adb..1b6be94b890 100644 --- a/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go +++ b/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go @@ -22,6 +22,7 @@ const ( GameTypeSuperPermCannon GameType = 5 GameTypeCannonKona GameType = 8 GameTypeSuperCannonKona GameType = 9 + GameTypeZKDisputeGame GameType = 10 ) var ( diff --git a/op-devstack/sysgo/add_game_type.go b/op-devstack/sysgo/add_game_type.go index d8d08bfc0cf..5aced90edbc 100644 --- a/op-devstack/sysgo/add_game_type.go +++ b/op-devstack/sysgo/add_game_type.go @@ -122,8 +122,8 @@ func addGameTypesForRuntime( initBond := eth.GWei(80_000_000).ToBig() // 0.08 ETH - // OPCMv2 requires all 6 game configs in order: - // CANNON, PERMISSIONED_CANNON, CANNON_KONA, SUPER_CANNON, SUPER_PERMISSIONED_CANNON, SUPER_CANNON_KONA. + // OPCMv2 requires all 7 game configs in order: + // CANNON, PERMISSIONED_CANNON, CANNON_KONA, SUPER_CANNON, SUPER_PERMISSIONED_CANNON, SUPER_CANNON_KONA, ZK_DISPUTE_GAME. cannonPrestate := PrestateForGameType(t, gameTypes.CannonGameType) cannonKonaPrestate := PrestateForGameType(t, gameTypes.CannonKonaGameType) @@ -169,6 +169,11 @@ func addGameTypesForRuntime( InitBond: new(big.Int), GameType: embedded.GameTypeSuperCannonKona, }, + { + Enabled: false, + InitBond: new(big.Int), + GameType: embedded.GameTypeZKDisputeGame, + }, } // Zero out init bond for disabled games. diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 663619472c5..c45a4e4f7a6 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -19,13 +19,14 @@ optimizer_runs = 999999 # entire build directory. additional_compiler_profiles = [ { name = "dispute", optimizer_runs = 5000 }, + { name = "validator", optimizer_runs = 200 }, ] compilation_restrictions = [ { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/SuperFaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/SuperPermissionedDisputeGame.sol", optimizer_runs = 5000 }, - { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 200 }, { paths = "src/L1/opcm/OPContractsManagerV2.sol", optimizer_runs = 5000 }, { paths = "src/L1/opcm/OPContractsManagerContainer.sol", optimizer_runs = 5000 }, { paths = "src/L1/opcm/OPContractsManagerMigrator.sol", optimizer_runs = 5000 }, diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol index decd32d33e3..7a108b446dd 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol @@ -22,6 +22,7 @@ interface IOPContractsManagerStandardValidator { address permissionedDisputeGameImpl; address superFaultDisputeGameImpl; address superPermissionedDisputeGameImpl; + address zkDisputeGameImpl; } struct ValidationInput { @@ -61,6 +62,7 @@ interface IOPContractsManagerStandardValidator { function permissionedDisputeGameImpl() external view returns (address); function superFaultDisputeGameImpl() external view returns (address); function superPermissionedDisputeGameImpl() external view returns (address); + function zkDisputeGameImpl() external view returns (address); function optimismMintableERC20FactoryImpl() external view returns (address); function optimismPortalImpl() external view returns (address); function ethLockboxImpl() external view returns (address); diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol index aeb5c17f95b..813af0eff73 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol @@ -28,6 +28,7 @@ interface IOPContractsManagerContainer { address permissionedDisputeGameImpl; address superFaultDisputeGameImpl; address superPermissionedDisputeGameImpl; + address zkDisputeGameImpl; address storageSetterImpl; } diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol index cc4af21404a..bfa77c4696c 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol @@ -7,7 +7,8 @@ import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; -import { Claim, GameType } from "src/dispute/lib/Types.sol"; +import { IZKVerifier } from "interfaces/dispute/zk/IZKVerifier.sol"; +import { Claim, Duration, GameType } from "src/dispute/lib/Types.sol"; interface IOPContractsManagerUtils { struct ProxyDeployArgs { @@ -34,6 +35,15 @@ interface IOPContractsManagerUtils { address challenger; } + /// @notice Configuration struct for the ZKDisputeGame. + struct ZKDisputeGameConfig { + Claim absolutePrestate; + IZKVerifier verifier; + Duration maxChallengeDuration; + Duration maxProveDuration; + uint256 challengerBond; + } + /// @notice Generic dispute game configuration data. struct DisputeGameConfig { bool enabled; diff --git a/packages/contracts-bedrock/interfaces/dispute/zk/IZKDisputeGame.sol b/packages/contracts-bedrock/interfaces/dispute/zk/IZKDisputeGame.sol index 5005d9c1b0e..a91385c0788 100644 --- a/packages/contracts-bedrock/interfaces/dispute/zk/IZKDisputeGame.sol +++ b/packages/contracts-bedrock/interfaces/dispute/zk/IZKDisputeGame.sol @@ -59,6 +59,7 @@ interface IZKDisputeGame is IDisputeGame, ISemver { function disputeGameFactory() external view returns (IDisputeGameFactory); function totalBonds() external view returns (uint256); + function __constructor__() external; function initialize() external payable; function l2SequenceNumber() external pure returns (uint256 l2SequenceNumber_); function parentIndex() external pure returns (uint32 parentIndex_); diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index d06bcdeeac6..55a254b20b5 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -37,6 +37,7 @@ import { IProxyAdminOwnedBase } from "interfaces/universal/IProxyAdminOwnedBase. import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; +import { IZKDisputeGame } from "interfaces/dispute/zk/IZKDisputeGame.sol"; library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -459,6 +460,13 @@ library ChainAssertions { } } + /// @notice Asserts that the ZKDisputeGame implementation is setup correctly. + function checkZKDisputeGameImpl(IZKDisputeGame _impl) internal view { + console.log("Running chain assertions on the ZKDisputeGame implementation at %s", address(_impl)); + require(address(_impl) != address(0), "CHECK-ZKDG-10"); + require(bytes(_impl.version()).length > 0, "CHECK-ZKDG-20"); + } + /// @notice Converts variables needed from the DeployConfig to a DeployOPChainInput contract function dioToContractSet(DeployImplementations.Output memory _output) internal diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index c0b3f00a208..df52aa2269b 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -404,7 +404,7 @@ contract Deploy is Deployer { function getSuperRootDeployInputV2() public view returns (IOPContractsManagerV2.FullConfig memory) { IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: false, initBond: 0, @@ -451,6 +451,12 @@ contract Deploy is Deployer { }) ) }); + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: bytes("") + }); return IOPContractsManagerV2.FullConfig({ saltMixer: "salt mixer", @@ -476,7 +482,7 @@ contract Deploy is Deployer { function getDeployInputV2() public view returns (IOPContractsManagerV2.FullConfig memory) { IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: false, initBond: 0, @@ -527,6 +533,12 @@ contract Deploy is Deployer { gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: bytes("") }); + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: bytes("") + }); return IOPContractsManagerV2.FullConfig({ saltMixer: "salt mixer", diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index eeffeebcf13..fbf85b02dae 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -96,6 +96,14 @@ contract DeployConfig is Script { uint256 public faultGameV2ClockExtension; uint256 public faultGameV2MaxClockDuration; + // ZK Dispute Game Configuration + uint256 public zkDisputeGameInitBond; + bytes32 public zkDisputeGameAbsolutePrestate; + address public zkDisputeGameVerifier; + uint256 public zkDisputeGameMaxChallengeDuration; + uint256 public zkDisputeGameMaxProveDuration; + uint256 public zkDisputeGameChallengerBond; + bool public useUpgradedFork; bool public useInterop; bytes32 public devFeatureBitmap; @@ -203,6 +211,13 @@ contract DeployConfig is Script { faultGameV2SplitDepth = _readOr(_json, "$.faultGameV2SplitDepth", uint256(30)); faultGameV2ClockExtension = _readOr(_json, "$.faultGameV2ClockExtension", uint256(10800)); faultGameV2MaxClockDuration = _readOr(_json, "$.faultGameV2MaxClockDuration", uint256(302400)); + + zkDisputeGameInitBond = _readOr(_json, "$.zkDisputeGameInitBond", uint256(1 ether)); + zkDisputeGameAbsolutePrestate = bytes32(_readOr(_json, "$.zkDisputeGameAbsolutePrestate", uint256(0))); + zkDisputeGameVerifier = _readOr(_json, "$.zkDisputeGameVerifier", address(0)); + zkDisputeGameMaxChallengeDuration = _readOr(_json, "$.zkDisputeGameMaxChallengeDuration", uint256(604800)); + zkDisputeGameMaxProveDuration = _readOr(_json, "$.zkDisputeGameMaxProveDuration", uint256(259200)); + zkDisputeGameChallengerBond = _readOr(_json, "$.zkDisputeGameChallengerBond", uint256(1 ether)); } function fork() public view returns (Fork fork_) { @@ -401,6 +416,10 @@ contract DeployConfig is Script { faultGameV2SplitDepth = 30; faultGameV2ClockExtension = 10800; faultGameV2MaxClockDuration = 302400; + zkDisputeGameInitBond = 1 ether; + zkDisputeGameMaxChallengeDuration = 604800; + zkDisputeGameMaxProveDuration = 259200; + zkDisputeGameChallengerBond = 1 ether; useInterop = false; useUpgradedFork = false; devFeatureBitmap = bytes32(0); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 2b06b8b1ba8..285af44a8e1 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -19,6 +19,7 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; import { ISuperPermissionedDisputeGame } from "interfaces/dispute/ISuperPermissionedDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IZKDisputeGame } from "interfaces/dispute/zk/IZKDisputeGame.sol"; import { Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; @@ -93,6 +94,7 @@ contract DeployImplementations is Script { IPermissionedDisputeGame permissionedDisputeGameImpl; ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; + IZKDisputeGame zkDisputeGameImpl; IStorageSetter storageSetterImpl; } @@ -133,6 +135,9 @@ contract DeployImplementations is Script { deploySuperFaultDisputeGameImpl(_input, output_); deploySuperPermissionedDisputeGameImpl(_input, output_); } + if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.ZK_DISPUTE_GAME)) { + deployZKDisputeGameImpl(output_); + } deployStorageSetterImpl(output_); // Deploy the OP Contracts Manager with the new implementations set. @@ -178,6 +183,7 @@ contract DeployImplementations is Script { permissionedDisputeGameImpl: address(_output.permissionedDisputeGameImpl), superFaultDisputeGameImpl: address(_output.superFaultDisputeGameImpl), superPermissionedDisputeGameImpl: address(_output.superPermissionedDisputeGameImpl), + zkDisputeGameImpl: address(_output.zkDisputeGameImpl), storageSetterImpl: address(_output.storageSetterImpl) }); @@ -519,6 +525,18 @@ contract DeployImplementations is Script { _output.superPermissionedDisputeGameImpl = impl; } + function deployZKDisputeGameImpl(Output memory _output) private { + IZKDisputeGame impl = IZKDisputeGame( + DeployUtils.createDeterministic({ + _name: "ZKDisputeGame", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IZKDisputeGame.__constructor__, ())), + _salt: _salt + }) + ); + vm.label(address(impl), "ZKDisputeGameImpl"); + _output.zkDisputeGameImpl = impl; + } + function deployOPCMContainer( Input memory _input, Output memory _output, @@ -594,6 +612,7 @@ contract DeployImplementations is Script { opcmImplementations.permissionedDisputeGameImpl = _implementations.permissionedDisputeGameImpl; opcmImplementations.superFaultDisputeGameImpl = _implementations.superFaultDisputeGameImpl; opcmImplementations.superPermissionedDisputeGameImpl = _implementations.superPermissionedDisputeGameImpl; + opcmImplementations.zkDisputeGameImpl = _implementations.zkDisputeGameImpl; IOPContractsManagerStandardValidator impl = IOPContractsManagerStandardValidator( DeployUtils.createDeterministic({ @@ -734,6 +753,10 @@ contract DeployImplementations is Script { addrs2 = Solarray.extend(addrs2, superGameAddrs); } + if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.ZK_DISPUTE_GAME)) { + addrs2 = Solarray.extend(addrs2, Solarray.addresses(address(_output.zkDisputeGameImpl))); + } + DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); require(address(_output.opcmV2) != address(0), "DeployImplementations: OPCM V2 not deployed"); @@ -752,6 +775,18 @@ contract DeployImplementations is Script { ); } + if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.ZK_DISPUTE_GAME)) { + require( + address(_output.zkDisputeGameImpl) != address(0), + "DeployImplementations: ZK_DISPUTE_GAME flag enabled but ZKDisputeGame was not deployed" + ); + } else { + require( + address(_output.zkDisputeGameImpl) == address(0), + "DeployImplementations: ZK_DISPUTE_GAME flag disabled but ZKDisputeGame was deployed" + ); + } + Types.ContractSet memory impls = ChainAssertions.dioToContractSet(_output); ChainAssertions.checkDelayedWETHImpl(_output.delayedWETHImpl, _input.withdrawalDelaySeconds); @@ -772,6 +807,10 @@ contract DeployImplementations is Script { ChainAssertions.checkL1StandardBridgeImpl(_output.l1StandardBridgeImpl); ChainAssertions.checkMIPS(_output.mipsSingleton, _output.preimageOracleSingleton); + if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.ZK_DISPUTE_GAME)) { + ChainAssertions.checkZKDisputeGameImpl(_output.zkDisputeGameImpl); + } + ChainAssertions.checkOptimismMintableERC20FactoryImpl(_output.optimismMintableERC20FactoryImpl); ChainAssertions.checkOptimismPortal2({ _contracts: impls, diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 2002a440899..7ca0cd5bd33 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -125,10 +125,10 @@ contract DeployOPChain is Script { challenger: _input.challenger }); - // Build dispute game configs - OPCMV2 requires all 6 game type configs. + // Build dispute game configs - OPCMV2 requires all 7 game type configs. // Order must match validGameTypes in OPContractsManagerV2._assertValidFullConfig(). IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); // Config 0: CANNON (disabled for initial deployment — no prestate exists) disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ @@ -192,6 +192,14 @@ contract DeployOPChain is Script { gameArgs: bytes("") }); + // Config 6: ZK_DISPUTE_GAME (disabled for initial deployment) + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: bytes("") + }); + config_ = IOPContractsManagerV2.FullConfig({ saltMixer: _input.saltMixer, superchainConfig: _input.superchainConfig, @@ -328,7 +336,6 @@ contract DeployOPChain is Script { ChainAssertions.checkDisputeGameFactory( _o.disputeGameFactoryProxy, _i.opChainProxyAdminOwner, expectedPDGImpl, true, permGameType ); - ChainAssertions.checkAnchorStateRegistryProxy(_o.anchorStateRegistryProxy, true); ChainAssertions.checkL1CrossDomainMessenger(_o.l1CrossDomainMessengerProxy, vm, true); ChainAssertions.checkOptimismPortal2({ diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 21a3879304b..ffa56a82e0a 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -199,6 +199,7 @@ contract VerifyOPCM is Script { fieldNameOverrides["storageSetterImpl"] = "StorageSetter"; fieldNameOverrides["opcmV2"] = "OPContractsManagerV2"; fieldNameOverrides["opcmUtils"] = "OPContractsManagerUtils"; + fieldNameOverrides["zkDisputeGameImpl"] = "ZKDisputeGame"; // Expected getter functions and their verification methods. // CRITICAL: Any getter in the ABI that's not in this list will cause verification to fail. @@ -247,6 +248,7 @@ contract VerifyOPCM is Script { validatorGetterChecks["permissionedDisputeGameImpl"] = "CONTAINER_IMPL"; validatorGetterChecks["superFaultDisputeGameImpl"] = "CONTAINER_IMPL"; validatorGetterChecks["superPermissionedDisputeGameImpl"] = "CONTAINER_IMPL"; + validatorGetterChecks["zkDisputeGameImpl"] = "CONTAINER_IMPL"; // Verify against env vars validatorGetterChecks["superchainConfig"] = "ENV:ADDRESS:EXPECTED_SUPERCHAIN_CONFIG"; diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json index 67171bfb9ce..38075b30dce 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json @@ -120,6 +120,11 @@ "name": "superPermissionedDisputeGameImpl", "type": "address" }, + { + "internalType": "address", + "name": "zkDisputeGameImpl", + "type": "address" + }, { "internalType": "address", "name": "storageSetterImpl", @@ -283,6 +288,11 @@ "name": "superPermissionedDisputeGameImpl", "type": "address" }, + { + "internalType": "address", + "name": "zkDisputeGameImpl", + "type": "address" + }, { "internalType": "address", "name": "storageSetterImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json index cc6fb61677b..506a38cc6e9 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json @@ -77,6 +77,11 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "zkDisputeGameImpl", + "type": "address" } ], "internalType": "struct OPContractsManagerStandardValidator.Implementations", @@ -626,6 +631,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "zkDisputeGameImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "InvalidGameArgsLength", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json index cd231961bf0..02bfc26ea76 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json @@ -310,6 +310,11 @@ "name": "superPermissionedDisputeGameImpl", "type": "address" }, + { + "internalType": "address", + "name": "zkDisputeGameImpl", + "type": "address" + }, { "internalType": "address", "name": "storageSetterImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index bed5faf833d..d675a962063 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -401,6 +401,11 @@ "name": "superPermissionedDisputeGameImpl", "type": "address" }, + { + "internalType": "address", + "name": "zkDisputeGameImpl", + "type": "address" + }, { "internalType": "address", "name": "storageSetterImpl", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 683f4f62a78..e1bcd03d6ff 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0x9d16e900a764cd7f19db3656cf7a9e555b23b9c7e018641ed21566657847a314" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { - "initCodeHash": "0x7f91525e45ea13bd680259e061f15d1bdfbdef7d9fd6c72bc62827b7fa732efe", - "sourceCodeHash": "0x1a0e19b0b6ec15affdb6888267046ddcf3419b3053c77fb8fb8810470f2486a7" + "initCodeHash": "0x029a46237037de3323bfc60b148ebe7eeaa4306f97ea49d0563bc2725758dae6", + "sourceCodeHash": "0x90afce90a5d84c9ec696554ab40ae9d9f04e27ce3f3d758f4ac6001aef89b45e" }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { "initCodeHash": "0x1101a3124e60908ec3e7690b33389d835d0405416d107114edd1684528f39bee", @@ -44,8 +44,8 @@ "sourceCodeHash": "0xb09cb2f7cbde8585fad5c5beb6811fa9044b156b4203da8005d3f6a7a68c30b2" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0xd8b7a5e075e0fd404d8e0aed10b345bb6064c21ac110caf4cf9ee26a66cc9b86", - "sourceCodeHash": "0xbddd81f0a8091778052008a67735b46b9f788872b40562d1906b4a7cbe823649" + "initCodeHash": "0x397abf0952ec7a694ec98964cc6fb4b09f2bf7176e31e7e3a9f59426f0bef4c3", + "sourceCodeHash": "0xe74c4ce2ebd584e69a24a13b18d43569e2996da415b2f44ed54e44159a95c5dd" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0xf1fb169c6dd4eceb5cec6ed6dfa3affc45970e5a01e00827d06af1f9e8df026d", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json index 7609df72ae4..891f048bf30 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json @@ -7,7 +7,7 @@ "type": "struct OPContractsManagerContainer.Blueprints" }, { - "bytes": "576", + "bytes": "608", "label": "impls", "offset": 0, "slot": "5", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerStandardValidator.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerStandardValidator.json index 0346a8ca900..f4421e6c9a1 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerStandardValidator.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerStandardValidator.json @@ -132,11 +132,18 @@ "slot": "18", "type": "address" }, + { + "bytes": "20", + "label": "zkDisputeGameImpl", + "offset": 0, + "slot": "19", + "type": "address" + }, { "bytes": "32", "label": "devFeatureBitmap", "offset": 0, - "slot": "19", + "slot": "20", "type": "bytes32" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol index 1789ebb5aba..1bff322c82c 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol @@ -40,8 +40,8 @@ import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; /// before and after an upgrade. contract OPContractsManagerStandardValidator is ISemver { /// @notice The semantic version of the OPContractsManagerStandardValidator contract. - /// @custom:semver 2.7.0 - string public constant version = "2.7.0"; + /// @custom:semver 2.8.0 + string public constant version = "2.8.0"; /// @notice The SuperchainConfig contract. ISuperchainConfig public superchainConfig; @@ -102,6 +102,9 @@ contract OPContractsManagerStandardValidator is ISemver { /// @notice The SuperPermissionedDisputeGame implementation address. address public superPermissionedDisputeGameImpl; + /// @notice The ZKDisputeGame implementation address. + address public zkDisputeGameImpl; + /// @notice Bitmap of development features, verification may depend on these features. bytes32 public devFeatureBitmap; @@ -122,6 +125,7 @@ contract OPContractsManagerStandardValidator is ISemver { address permissionedDisputeGameImpl; address superFaultDisputeGameImpl; address superPermissionedDisputeGameImpl; + address zkDisputeGameImpl; } /// @notice Struct containing the input parameters for the validation process. @@ -198,6 +202,7 @@ contract OPContractsManagerStandardValidator is ISemver { permissionedDisputeGameImpl = _implementations.permissionedDisputeGameImpl; superFaultDisputeGameImpl = _implementations.superFaultDisputeGameImpl; superPermissionedDisputeGameImpl = _implementations.superPermissionedDisputeGameImpl; + zkDisputeGameImpl = _implementations.zkDisputeGameImpl; } /// @notice Returns a string representing the overrides that are set. @@ -661,6 +666,7 @@ contract OPContractsManagerStandardValidator is ISemver { errors_ = _initialErrors; bool isPermissioned = _gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw() || _gameType.raw() == GameTypes.SUPER_PERMISSIONED_CANNON.raw(); + bool isZK = _gameType.raw() == GameTypes.ZK_DISPUTE_GAME.raw(); IDisputeGameFactory _factory = IDisputeGameFactory(_sysCfg.disputeGameFactory()); IPermissionedDisputeGame _game = IPermissionedDisputeGame(address(_factory.gameImpls(_gameType))); @@ -674,7 +680,7 @@ contract OPContractsManagerStandardValidator is ISemver { bytes memory _gameArgs = _factory.gameArgs(_gameType); bool lenCheckFailed; - (errors_, lenCheckFailed) = assertGameArgsLength(errors_, _gameArgs, isPermissioned, _errorPrefix); + (errors_, lenCheckFailed) = assertGameArgsLength(errors_, _gameArgs, isPermissioned, isZK, _errorPrefix); if (lenCheckFailed) { // Return early to avoid decoding invalid game args failed_ = true; @@ -763,6 +769,7 @@ contract OPContractsManagerStandardValidator is ISemver { if (raw == GameTypes.SUPER_PERMISSIONED_CANNON.raw()) return superPermissionedDisputeGameImpl; if (raw == GameTypes.SUPER_CANNON.raw()) return superFaultDisputeGameImpl; if (raw == GameTypes.SUPER_CANNON_KONA.raw()) return superFaultDisputeGameImpl; + if (raw == GameTypes.ZK_DISPUTE_GAME.raw()) return zkDisputeGameImpl; return faultDisputeGameImpl; } @@ -1016,6 +1023,19 @@ contract OPContractsManagerStandardValidator is ISemver { ); } + // ZK dispute game validation: gated on the ZK_DISPUTE_GAME dev feature flag. + if (DevFeatures.isDevFeatureEnabled(devFeatureBitmap, DevFeatures.ZK_DISPUTE_GAME)) { + _errors = assertValidZKDisputeGame(_errors, _input.sysCfg, _input.l2ChainID, _proxyAdmin, _overrides); + } else { + // ZK game type must not be registered when the ZK feature is not enabled. + _errors = internalRequire( + address(IDisputeGameFactory(_input.sysCfg.disputeGameFactory()).gameImpls(GameTypes.ZK_DISPUTE_GAME)) + == address(0), + "ZKDG-NOSHAPE", + _errors + ); + } + _errors = assertValidETHLockbox(_errors, _input.sysCfg, _proxyAdmin); string memory overridesString = getOverridesString(_overrides); @@ -1055,6 +1075,7 @@ contract OPContractsManagerStandardValidator is ISemver { string memory _errors, bytes memory _gameArgsBytes, bool _isPermissioned, + bool _isZK, string memory _errorPrefix ) internal @@ -1062,7 +1083,11 @@ contract OPContractsManagerStandardValidator is ISemver { returns (string memory errors_, bool failed_) { _errorPrefix = string.concat(_errorPrefix, "-GARGS"); - if (_isPermissioned) { + if (_isZK) { + bool ok = LibGameArgs.isValidZKArgs(_gameArgsBytes); + _errors = internalRequire(ok, string.concat(_errorPrefix, "-10"), _errors); + return (_errors, !ok); + } else if (_isPermissioned) { bool ok = LibGameArgs.isValidPermissionedArgs(_gameArgsBytes); _errors = internalRequire(ok, string.concat(_errorPrefix, "-10"), _errors); return (_errors, !ok); @@ -1073,6 +1098,56 @@ contract OPContractsManagerStandardValidator is ISemver { } } + /// @notice Validates the decoded ZK game args (chainId, weth, asr) against the chain config. + function _assertValidZKGameArgs( + string memory _errors, + ISystemConfig _sysCfg, + uint256 _l2ChainID, + IProxyAdmin _admin, + ValidationOverrides memory _overrides, + string memory _errorPrefix + ) + private + view + returns (string memory) + { + IDisputeGameFactory factory = IDisputeGameFactory(_sysCfg.disputeGameFactory()); + (address _asr, address _weth, uint256 chainId) = + LibGameArgs.decodeZK(factory.gameArgs(GameTypes.ZK_DISPUTE_GAME)); + IAnchorStateRegistry asr = IAnchorStateRegistry(_asr); + IDelayedWETH weth = IDelayedWETH(payable(_weth)); + _errors = internalRequire(chainId == _l2ChainID, string.concat(_errorPrefix, "-60"), _errors); + _errors = assertValidDelayedWETH(_errors, _sysCfg, weth, _admin, _overrides, _errorPrefix); + _errors = assertValidAnchorStateRegistry(_errors, _sysCfg, factory, asr, _admin, _errorPrefix); + return _errors; + } + + /// @notice Asserts that the ZKDisputeGame contract registered in the factory is valid. + function assertValidZKDisputeGame( + string memory _errors, + ISystemConfig _sysCfg, + uint256 _l2ChainID, + IProxyAdmin _admin, + ValidationOverrides memory _overrides + ) + internal + view + returns (string memory) + { + string memory errorPrefix = "ZKDG"; + DisputeGameImplementation memory gameImpl; + bool failedToGetImpl; + (gameImpl, _errors, failedToGetImpl) = + getGameImplementation(_errors, GameTypes.ZK_DISPUTE_GAME, _sysCfg, errorPrefix); + if (failedToGetImpl) return _errors; + _errors = internalRequire( + LibString.eq(getVersion(gameImpl.gameAddress), getVersion(zkDisputeGameImpl)), + string.concat(errorPrefix, "-20"), + _errors + ); + return _assertValidZKGameArgs(_errors, _sysCfg, _l2ChainID, _admin, _overrides, errorPrefix); + } + // @notice Internal function to read all information from a dispute game while supporting both v1 and v2 dispute /// games. function _decodeDisputeGameImpl( @@ -1084,6 +1159,12 @@ contract OPContractsManagerStandardValidator is ISemver { view returns (DisputeGameImplementation memory gameImpl_) { + if (_gameType.raw() == GameTypes.ZK_DISPUTE_GAME.raw()) { + gameImpl_.gameAddress = address(_game); + gameImpl_.gameType = _gameType; + return gameImpl_; + } + LibGameArgs.GameArgs memory gameArgs = LibGameArgs.decode(_gameArgsBytes); gameImpl_ = DisputeGameImplementation({ diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol index 6c127ec8de6..c29026316b9 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol @@ -39,6 +39,7 @@ contract OPContractsManagerContainer { address permissionedDisputeGameImpl; address superFaultDisputeGameImpl; address superPermissionedDisputeGameImpl; + address zkDisputeGameImpl; address storageSetterImpl; } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol index 0f0ef8e3c79..ca103970451 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol @@ -263,6 +263,7 @@ contract OPContractsManagerMigrator is OPContractsManagerUtilsCaller { existingDGF.setImplementation(GameTypes.SUPER_PERMISSIONED_CANNON, IDisputeGame(address(0)), hex""); existingDGF.setImplementation(GameTypes.CANNON_KONA, IDisputeGame(address(0)), hex""); existingDGF.setImplementation(GameTypes.SUPER_CANNON_KONA, IDisputeGame(address(0)), hex""); + existingDGF.setImplementation(GameTypes.ZK_DISPUTE_GAME, IDisputeGame(address(0)), hex""); // Enable the ETH lockbox feature on the SystemConfig if not already enabled. // This is needed for the SystemConfig's paused() function to use the correct identifier. diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol index fa5f666bef8..1515202ff24 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol @@ -397,6 +397,8 @@ contract OPContractsManagerUtils { return IDisputeGame(impls.superPermissionedDisputeGameImpl); } else if (_gameType.raw() == GameTypes.SUPER_CANNON_KONA.raw()) { return IDisputeGame(impls.superFaultDisputeGameImpl); + } else if (_gameType.raw() == GameTypes.ZK_DISPUTE_GAME.raw()) { + return IDisputeGame(impls.zkDisputeGameImpl); } else { revert IOPContractsManagerUtils.OPContractsManagerUtils_UnsupportedGameType(); } @@ -450,6 +452,19 @@ contract OPContractsManagerUtils { parsedInputArgs.proposer, parsedInputArgs.challenger ); + } else if (rawGT == GameTypes.ZK_DISPUTE_GAME.raw()) { + IOPContractsManagerUtils.ZKDisputeGameConfig memory parsedInputArgs = + abi.decode(_gcfg.gameArgs, (IOPContractsManagerUtils.ZKDisputeGameConfig)); + return abi.encodePacked( + parsedInputArgs.absolutePrestate, + parsedInputArgs.verifier, + parsedInputArgs.maxChallengeDuration, + parsedInputArgs.maxProveDuration, + parsedInputArgs.challengerBond, + address(_anchorStateRegistry), + address(_delayedWETH), + chainId + ); } else { revert IOPContractsManagerUtils.OPContractsManagerUtils_UnsupportedGameType(); } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 79aff092eb1..f4f6af01e23 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -152,9 +152,9 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { /// - Major bump: New required sequential upgrade /// - Minor bump: Replacement OPCM for same upgrade /// - Patch bump: Development changes (expected for normal dev work) - /// @custom:semver 7.1.15 + /// @custom:semver 7.1.16 function version() public pure returns (string memory) { - return "7.1.15"; + return "7.1.16"; } /// @param _standardValidator The standard validator for this OPCM release. @@ -685,16 +685,17 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { /// @notice Validates the deployment/upgrade config. /// @param _cfg The full config. /// @param _isInitialDeployment Whether or not this is an initial deployment. - function _assertValidFullConfig(FullConfig memory _cfg, bool _isInitialDeployment) internal pure { + function _assertValidFullConfig(FullConfig memory _cfg, bool _isInitialDeployment) internal view { // All valid game types. StandardValidator is responsible for rejecting game types that // should not be used in a given mode (e.g., legacy types in super root mode). - GameType[] memory validGameTypes = new GameType[](6); + GameType[] memory validGameTypes = new GameType[](7); validGameTypes[0] = GameTypes.CANNON; validGameTypes[1] = GameTypes.PERMISSIONED_CANNON; validGameTypes[2] = GameTypes.CANNON_KONA; validGameTypes[3] = GameTypes.SUPER_CANNON; validGameTypes[4] = GameTypes.SUPER_PERMISSIONED_CANNON; validGameTypes[5] = GameTypes.SUPER_CANNON_KONA; + validGameTypes[6] = GameTypes.ZK_DISPUTE_GAME; // We must have a config for each valid game type. if (_cfg.disputeGameConfigs.length != validGameTypes.length) { @@ -723,6 +724,14 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { if (_isInitialDeployment && !isPermissioned && _cfg.disputeGameConfigs[i].enabled) { revert OPContractsManagerV2_InvalidGameConfigs(); } + + // ZK_DISPUTE_GAME can only be enabled when the dev flag is on (upgrade path). + if ( + validGameTypes[i].raw() == GameTypes.ZK_DISPUTE_GAME.raw() && _cfg.disputeGameConfigs[i].enabled + && !isDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME) + ) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } } // Validate that the starting respected game type corresponds to an enabled game config. diff --git a/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol b/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol index 9191e34d34e..98b8dd48008 100644 --- a/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol +++ b/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol @@ -93,4 +93,32 @@ library LibGameArgs { function isValidPermissionedArgs(bytes memory _args) internal pure returns (bool) { return _args.length == PERMISSIONED_ARGS_LENGTH; } + + uint256 public constant ZK_ARGS_LENGTH = 172; + + /// @notice Checks if the provided game arguments are valid for a ZK dispute game. + function isValidZKArgs(bytes memory _args) internal pure returns (bool) { + return _args.length == ZK_ARGS_LENGTH; + } + + /// @notice Decodes the anchorStateRegistry, weth, and l2ChainId from packed ZK game template + /// args as produced by OPContractsManagerUtils._encodeGameArgs for ZK_DISPUTE_GAME. + /// Layout (abi.encodePacked, ZK_ARGS_LENGTH bytes): + /// [0-31] absolutePrestate (bytes32) + /// [32-51] verifier (address) + /// [52-59] maxChallengeDuration (uint64) + /// [60-67] maxProveDuration (uint64) + /// [68-99] challengerBond (uint256) + /// [100-119] anchorStateRegistry (address) + /// [120-139] weth (address) + /// [140-171] l2ChainId (uint256) + function decodeZK(bytes memory _args) internal pure returns (address asr_, address weth_, uint256 l2ChainId_) { + if (_args.length != ZK_ARGS_LENGTH) revert InvalidGameArgsLength(); + assembly { + let base := add(_args, 0x20) + asr_ := shr(96, mload(add(base, 100))) + weth_ := shr(96, mload(add(base, 120))) + l2ChainId_ := mload(add(base, 140)) + } + } } diff --git a/packages/contracts-bedrock/src/dispute/lib/Types.sol b/packages/contracts-bedrock/src/dispute/lib/Types.sol index 94be3d0d219..109f132a3b8 100644 --- a/packages/contracts-bedrock/src/dispute/lib/Types.sol +++ b/packages/contracts-bedrock/src/dispute/lib/Types.sol @@ -92,6 +92,7 @@ library GameTypes { /// @notice A dispute game type that uses RISC Zero's Kailua GameType internal constant KAILUA = GameType.wrap(1337); + /// @notice A dispute game type that uses optimistic + ZK proofs for dispute resolution. GameType internal constant ZK_DISPUTE_GAME = GameType.wrap(10); /// @notice Returns true if the game type uses super roots. diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 739a10d4deb..b1ceac32619 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -11,6 +11,7 @@ import { GameType, Hash } from "src/dispute/lib/LibUDT.sol"; import { GameTypes, Duration, Claim } from "src/dispute/lib/Types.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import { Features } from "src/libraries/Features.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { Config } from "scripts/libraries/Config.sol"; // Interfaces @@ -40,6 +41,8 @@ import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; +import { IZKVerifier } from "interfaces/dispute/zk/IZKVerifier.sol"; +import { LibGameArgs } from "src/dispute/lib/LibGameArgs.sol"; /// @title BadDisputeGameFactoryReturner /// @notice Used to return a bad DisputeGameFactory address to the OPContractsManagerStandardValidator. Far easier @@ -147,6 +150,11 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { if (Config.devFeatureSuperRootGamesMigration()) { vm.skip(true, "Skipping: standard configs incompatible with SUPER_ROOT_GAMES_MIGRATION"); } + // Standard validator tests do not deploy a ZK dispute game, so they are incompatible + // with ZK_DISPUTE_GAME mode which expects one to be registered. + if (Config.devFeatureZkDisputeGame()) { + vm.skip(true, "Skipping: standard configs incompatible with ZK_DISPUTE_GAME"); + } super.setUp(); // Load the dgf @@ -228,7 +236,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { // Prepare the upgrade input. IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: true, initBond: disputeGameFactory.initBonds(GameTypes.CANNON), @@ -273,6 +281,12 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: hex"" }); + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: hex"" + }); // Call upgrade to all games to be enabled. prankDelegateCall(owner); @@ -1767,7 +1781,7 @@ abstract contract OPContractsManagerStandardValidator_SuperMode_TestInit is Comm address owner = proxyAdmin.owner(); IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); // Legacy types (all disabled). disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ @@ -1814,6 +1828,12 @@ abstract contract OPContractsManagerStandardValidator_SuperMode_TestInit is Comm gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) }); + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: hex"" + }); IOPContractsManagerUtils.ExtraInstruction[] memory extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](1); @@ -2010,3 +2030,252 @@ contract OPContractsManagerStandardValidator_SuperPermissionlessDisputeGame_Test assertEq("SCKDG-VM-10,SCKDG-VM-20", _validate(true)); } } + +/// @title OPContractsManagerStandardValidator_ZKDisputeGame_Test +/// @notice Tests that ZK dispute game validation is gated on the ZK_DISPUTE_GAME dev feature flag. +/// These tests run in non-ZK deployment mode and verify both branches of the gating logic. +contract OPContractsManagerStandardValidator_ZKDisputeGame_Test is OPContractsManagerStandardValidator_TestInit { + /// @notice Returns the devFeatureBitmap storage slot in standardValidator. + function _devFeatureBitmapSlot() internal returns (bytes32) { + return bytes32(ForgeArtifacts.getSlot("OPContractsManagerStandardValidator", "devFeatureBitmap").slot); + } + + /// @notice Enables the ZK_DISPUTE_GAME dev feature flag in standardValidator via vm.store. + function _enableZKFeature() internal { + vm.store(address(standardValidator), _devFeatureBitmapSlot(), DevFeatures.ZK_DISPUTE_GAME); + } + + /// @notice Tests ZKDG-NOSHAPE when ZK feature is not enabled but a ZK game is registered. + /// This is the negative test ensuring the non-ZK branch of the validation is exercised. + function test_validate_zkDisputeGameNotExpected_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME); + vm.mockCall( + address(disputeGameFactory), + abi.encodeCall(IDisputeGameFactory.gameImpls, (GameTypes.ZK_DISPUTE_GAME)), + abi.encode(address(0xdead)) + ); + assertEq("ZKDG-NOSHAPE", _validate(true)); + } + + /// @notice Tests ZKDG-10 when ZK feature is enabled but no ZK game impl is registered. + /// This is the positive test ensuring the ZK validation branch is exercised. + function test_validate_zkDisputeGameNullImpl_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME); + // Enable the ZK feature flag; factory still returns address(0) for ZK_DISPUTE_GAME. + _enableZKFeature(); + assertEq("ZKDG-10", _validate(true)); + } +} + +/// @title OPContractsManagerStandardValidator_ZKMode_TestInit +/// @notice Base contract for ZK dispute game validator tests. +/// Skips unless DEV_FEATURE__ZK_DISPUTE_GAME is enabled. +/// Deploys the chain with a ZK dispute game via OPCM so the full validation path is exercised. +abstract contract OPContractsManagerStandardValidator_ZKMode_TestInit is CommonTest { + /// @notice The l2ChainId from the deploy config. + uint256 l2ChainId; + + /// @notice The cannon absolute prestate from the deploy config. + Claim cannonPrestate; + + /// @notice The CannonKona absolute prestate. + Claim cannonKonaPrestate = Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))); + + /// @notice The proposer role from the deploy config. + address proposer; + + /// @notice The challenger role from the deploy config. + address challenger; + + /// @notice The DisputeGameFactory instance. + IDisputeGameFactory dgf; + + /// @notice The OPContractsManagerStandardValidator instance. + IOPContractsManagerStandardValidator standardValidator; + + /// @notice Sets up the ZK-mode test suite. Skips if the ZK feature is not enabled. + function setUp() public virtual override { + if (!Config.devFeatureZkDisputeGame()) { + vm.skip(true, "Skipping: DEV_FEATURE__ZK_DISPUTE_GAME is not enabled"); + } + if (Config.devFeatureSuperRootGamesMigration()) { + vm.skip(true, "Skipping: standard configs incompatible with SUPER_ROOT_GAMES_MIGRATION"); + } + super.setUp(); + + dgf = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + standardValidator = opcmV2.opcmStandardValidator(); + + if (isL1ForkTest()) { + // In fork mode read the actual values from the deployed contracts so _validate() + // is consistent with the real on-chain state. + LibGameArgs.GameArgs memory cannonArgs = LibGameArgs.decode(dgf.gameArgs(GameTypes.CANNON)); + cannonPrestate = Claim.wrap(cannonArgs.absolutePrestate); + l2ChainId = cannonArgs.l2ChainId; + + LibGameArgs.GameArgs memory pddgArgs = LibGameArgs.decode(dgf.gameArgs(GameTypes.PERMISSIONED_CANNON)); + proposer = pddgArgs.proposer; + challenger = pddgArgs.challenger; + + cannonKonaPrestate = Claim.wrap(LibGameArgs.decode(dgf.gameArgs(GameTypes.CANNON_KONA)).absolutePrestate); + + // ZK game is not deployed on mainnet. Mock it using the same ASR and WETH as CANNON + // (same on-chain infrastructure) so _assertValidZKGameArgs passes its checks. + bytes memory zkArgs = abi.encodePacked( + bytes32(keccak256("zkPrestate")), + address(0xBEEF), + uint64(7 days), + uint64(3 days), + uint256(0.08 ether), + cannonArgs.anchorStateRegistry, + cannonArgs.weth, + l2ChainId + ); + vm.mockCall( + address(dgf), + abi.encodeCall(IDisputeGameFactory.gameImpls, (GameTypes.ZK_DISPUTE_GAME)), + abi.encode(standardValidator.zkDisputeGameImpl()) + ); + vm.mockCall( + address(dgf), + abi.encodeCall(IDisputeGameFactory.gameArgs, (GameTypes.ZK_DISPUTE_GAME)), + abi.encode(zkArgs) + ); + } else { + l2ChainId = deploy.cfg().l2ChainID(); + cannonPrestate = Claim.wrap(bytes32(deploy.cfg().faultGameAbsolutePrestate())); + proposer = deploy.cfg().l2OutputOracleProposer(); + challenger = deploy.cfg().l2OutputOracleChallenger(); + + address owner = proxyAdmin.owner(); + + IOPContractsManagerUtils.DisputeGameConfig[] memory configs = + new IOPContractsManagerUtils.DisputeGameConfig[](7); + configs[0] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON), + gameType: GameTypes.CANNON, + gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }); + configs[1] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerUtils.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: proposer, + challenger: challenger + }) + ) + }); + configs[2] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate }) + ) + }); + configs[3] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.SUPER_CANNON, + gameArgs: hex"" + }); + configs[4] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.SUPER_PERMISSIONED_CANNON, + gameArgs: hex"" + }); + configs[5] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.SUPER_CANNON_KONA, + gameArgs: hex"" + }); + configs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: 0.08 ether, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("zkPrestate"))), + verifier: IZKVerifier(address(0xBEEF)), + maxChallengeDuration: Duration.wrap(uint64(7 days)), + maxProveDuration: Duration.wrap(uint64(3 days)), + challengerBond: 0.08 ether + }) + ) + }); + + prankDelegateCall(owner); + (bool success,) = address(opcmV2).delegatecall( + abi.encodeCall( + IOPContractsManagerV2.upgrade, + ( + IOPContractsManagerV2.UpgradeInput({ + systemConfig: systemConfig, + disputeGameConfigs: configs, + extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0) + }) + ) + ) + ); + assertTrue(success, "ZK upgrade failed"); + } + } + + /// @notice Runs the OPContractsManagerStandardValidator.validate function. + function _validate(bool _allowFailure) internal view returns (string memory) { + return standardValidator.validate( + IOPContractsManagerStandardValidator.ValidationInputDev({ + sysCfg: systemConfig, + cannonPrestate: cannonPrestate.raw(), + cannonKonaPrestate: cannonKonaPrestate.raw(), + l2ChainID: l2ChainId, + proposer: proposer + }), + _allowFailure + ); + } +} + +/// @title OPContractsManagerStandardValidator_ZKValidation_Test +/// @notice Tests for the ZK dispute game validation path in the standard validator. +/// Only runs when DEV_FEATURE__ZK_DISPUTE_GAME is enabled. +contract OPContractsManagerStandardValidator_ZKValidation_Test is + OPContractsManagerStandardValidator_ZKMode_TestInit +{ + /// @notice Tests that validate succeeds when the ZK game is properly configured. + function test_validate_zkDisputeGame_succeeds() public view { + string memory errors = _validate(false); + assertEq(errors, ""); + } + + /// @notice Tests ZKDG-10 when the ZK game implementation is not registered in the factory. + function test_validate_zkDisputeGameNullImpl_succeeds() public { + vm.mockCall( + address(dgf), + abi.encodeCall(IDisputeGameFactory.gameImpls, (GameTypes.ZK_DISPUTE_GAME)), + abi.encode(address(0)) + ); + assertEq("ZKDG-10", _validate(true)); + } + + /// @notice Tests ZKDG-20 when the ZK game implementation version does not match the expected. + function test_validate_zkDisputeGameInvalidVersion_succeeds() public { + address zkImpl = address(dgf.gameImpls(GameTypes.ZK_DISPUTE_GAME)); + BadVersionReturner bad = new BadVersionReturner(standardValidator, ISemver(zkImpl), "0.0.0"); + bytes32 slot = bytes32(ForgeArtifacts.getSlot("OPContractsManagerStandardValidator", "zkDisputeGameImpl").slot); + vm.store(address(standardValidator), slot, bytes32(uint256(uint160(address(bad))))); + assertEq("ZKDG-20", _validate(true)); + } + + /// @notice Tests ZKDG-60 when the l2ChainId encoded in the ZK game args does not match. + function test_validate_zkDisputeGameWrongChainId_succeeds() public { + DisputeGames.mockZKGameImplL2ChainId(dgf, GameTypes.ZK_DISPUTE_GAME, l2ChainId + 1); + assertEq("ZKDG-60", _validate(true)); + } +} diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol index 2cfe2e9f407..a120634efe2 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol @@ -43,6 +43,7 @@ contract OPContractsManagerContainer_TestInit is Test { permissionedDisputeGameImpl: makeAddr("permissionedDisputeGameImpl"), superFaultDisputeGameImpl: makeAddr("superFaultDisputeGameImpl"), superPermissionedDisputeGameImpl: makeAddr("superPermissionedDisputeGameImpl"), + zkDisputeGameImpl: makeAddr("zkDisputeGameImpl"), storageSetterImpl: makeAddr("storageSetterImpl") }); } diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol index c8bc625f148..3d005147fc0 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.15; // Testing import { Test } from "test/setup/Test.sol"; +import { FeatureFlags } from "test/setup/FeatureFlags.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Contracts import { OPContractsManagerUtils } from "src/L1/opcm/OPContractsManagerUtils.sol"; @@ -21,6 +23,11 @@ import { IProxy } from "interfaces/universal/IProxy.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; +import { Claim, Duration } from "src/dispute/lib/LibUDT.sol"; +import { GameTypes } from "src/dispute/lib/Types.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IZKVerifier } from "interfaces/dispute/zk/IZKVerifier.sol"; /// @title ImplV1_Harness /// @notice Implementation contract with version 1.0.0 for testing upgrades. @@ -69,7 +76,7 @@ contract OPContractsManagerUtils_ImplV2Interop_Harness is ISemver { /// @title OPContractsManagerUtils_TestInit /// @notice Shared setup for OPContractsManagerUtils tests. -contract OPContractsManagerUtils_TestInit is Test { +contract OPContractsManagerUtils_TestInit is Test, FeatureFlags { OPContractsManagerUtils internal utils; OPContractsManagerContainer internal container; OPContractsManagerContainer.Blueprints internal blueprints; @@ -79,6 +86,8 @@ contract OPContractsManagerUtils_TestInit is Test { IStorageSetter internal storageSetter; function setUp() public virtual { + resolveFeaturesFromEnv(); + // Etch code into the magic testing address so we're recognized as a test env. vm.etch(Constants.TESTING_ENVIRONMENT_ADDRESS, hex"01"); @@ -118,6 +127,7 @@ contract OPContractsManagerUtils_TestInit is Test { permissionedDisputeGameImpl: makeAddr("permissionedDisputeGameImpl"), superFaultDisputeGameImpl: makeAddr("superFaultDisputeGameImpl"), superPermissionedDisputeGameImpl: makeAddr("superPermissionedDisputeGameImpl"), + zkDisputeGameImpl: makeAddr("zkDisputeGameImpl"), storageSetterImpl: address(storageSetter) }); @@ -897,3 +907,81 @@ contract OPContractsManagerUtils_IsMatchingInstructionByKey_Test is OPContractsM assertFalse(utils.isMatchingInstructionByKey(_instruction, _key)); } } + +/// @title OPContractsManagerUtils_GetGameImpl_Test +/// @notice Tests OPContractsManagerUtils.getGameImpl for the ZK dispute game type. +contract OPContractsManagerUtils_GetGameImpl_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that getGameImpl returns the ZK dispute game implementation. + function test_getGameImpl_zkDisputeGame_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + address impl = address(utils.getGameImpl(GameTypes.ZK_DISPUTE_GAME)); + assertEq(impl, makeAddr("zkDisputeGameImpl"), "ZK game impl address mismatch"); + assertTrue(impl != address(0), "ZK game impl should not be zero"); + } + + /// @notice Tests that getGameImpl reverts for an unsupported game type. + function test_getGameImpl_unsupportedType_reverts() public { + vm.expectRevert(IOPContractsManagerUtils.OPContractsManagerUtils_UnsupportedGameType.selector); + utils.getGameImpl(GameTypes.KAILUA); + } +} + +/// @title OPContractsManagerUtils_MakeGameArgs_Test +/// @notice Tests OPContractsManagerUtils.makeGameArgs for the ZK dispute game type. +contract OPContractsManagerUtils_MakeGameArgs_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that makeGameArgs encodes the correct CWIA layout for ZKDisputeGame. + function test_makeGameArgs_zkDisputeGame_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + Claim absolutePrestate = Claim.wrap(bytes32(keccak256("zk prestate"))); + IZKVerifier verifier = IZKVerifier(address(0xBEEF)); + Duration maxChallengeDuration = Duration.wrap(uint64(7 days)); + Duration maxProveDuration = Duration.wrap(uint64(3 days)); + uint256 challengerBond = 1 ether; + IAnchorStateRegistry anchorStateRegistry = IAnchorStateRegistry(makeAddr("anchorStateRegistry")); + IDelayedWETH delayedWETH = IDelayedWETH(payable(makeAddr("delayedWETH"))); + uint256 l2ChainId = 42; + + IOPContractsManagerUtils.DisputeGameConfig memory cfg = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: absolutePrestate, + verifier: verifier, + maxChallengeDuration: maxChallengeDuration, + maxProveDuration: maxProveDuration, + challengerBond: challengerBond + }) + ) + }); + + bytes memory result = utils.makeGameArgs(l2ChainId, anchorStateRegistry, delayedWETH, cfg); + + // Verify the CWIA layout: absolutePrestate | verifier | maxChallengeDuration | maxProveDuration | + // challengerBond | anchorStateRegistry | delayedWETH | l2ChainId + bytes memory expected = abi.encodePacked( + absolutePrestate, + verifier, + maxChallengeDuration, + maxProveDuration, + challengerBond, + address(anchorStateRegistry), + address(delayedWETH), + l2ChainId + ); + assertEq(keccak256(result), keccak256(expected), "ZK game args CWIA layout mismatch"); + } + + /// @notice Tests that makeGameArgs reverts for an unsupported game type. + function test_makeGameArgs_unsupportedType_reverts() public { + IOPContractsManagerUtils.DisputeGameConfig memory cfg = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: true, + initBond: 0, + gameType: GameTypes.KAILUA, + gameArgs: bytes("") + }); + vm.expectRevert(IOPContractsManagerUtils.OPContractsManagerUtils_UnsupportedGameType.selector); + utils.makeGameArgs(1, IAnchorStateRegistry(address(0)), IDelayedWETH(payable(address(0))), cfg); + } +} diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index e805a06652a..8f5ec6ed577 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -11,7 +11,7 @@ import { BatchUpgrader } from "test/L1/opcm/helpers/BatchUpgrader.sol"; // Libraries import { Config } from "scripts/libraries/Config.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { Claim, Hash } from "src/dispute/lib/LibUDT.sol"; +import { Claim, Duration, Hash } from "src/dispute/lib/LibUDT.sol"; import { GameType, GameTypes, Proposal } from "src/dispute/lib/Types.sol"; import { Constants } from "src/libraries/Constants.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; @@ -31,6 +31,7 @@ import { IOPContractsManagerMigrator } from "interfaces/L1/opcm/IOPContractsMana import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IZKVerifier } from "interfaces/dispute/zk/IZKVerifier.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; /// @title OPContractsManagerV2_TestInit @@ -307,6 +308,22 @@ contract OPContractsManagerV2_Upgrade_TestInit is OPContractsManagerV2_TestInit gameArgs: bytes("") }) ); + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)), + verifier: IZKVerifier(address(0)), + maxChallengeDuration: Duration.wrap(0), + maxProveDuration: Duration.wrap(0), + challengerBond: 0 + }) + ) + }) + ); // Allow the DelayedWETH proxy to be (re)deployed during upgrades if it is missing. v2UpgradeInput.extraInstructions.push( @@ -408,6 +425,19 @@ contract OPContractsManagerV2_Upgrade_TestInit is OPContractsManagerV2_TestInit // try to apply to this function call instead. IOPContractsManagerStandardValidator validator = _opcm.opcmStandardValidator(); + // When the ZK dispute game dev feature is enabled but no ZK game is registered in the + // factory (post-upgrade), the validator will always produce a ZKDG-10 error. Append it + // automatically so callers don't have to repeat this everywhere. + bool zkFeature = isDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME); + bool zkGameDeployed = address(disputeGameFactory.gameImpls(GameTypes.ZK_DISPUTE_GAME)) != address(0); + if (zkFeature && !zkGameDeployed) { + if (bytes(_expectedValidatorErrors).length == 0) { + _expectedValidatorErrors = "ZKDG-10"; + } else { + _expectedValidatorErrors = string.concat(_expectedValidatorErrors, ",ZKDG-10"); + } + } + // Expect validator errors if the user provides them. We always expect the L1PAOMultisig // and Challenger overrides so we don't need to repeat them here. if (bytes(_expectedValidatorErrors).length > 0) { @@ -951,6 +981,116 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI ); } + /// @notice Mocks the ZK_DISPUTE_GAME dev feature as enabled and sets a non-zero zkDisputeGameImpl. + /// @notice Tests that enabling the ZK dispute game registers it in the factory. + function test_upgrade_enableZKGame_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + + address zkImpl = opcmV2.implementations().zkDisputeGameImpl; + v2UpgradeInput.disputeGameConfigs[6].enabled = true; + v2UpgradeInput.disputeGameConfigs[6].initBond = 1 ether; + v2UpgradeInput.disputeGameConfigs[6].gameArgs = abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("zk prestate"))), + verifier: IZKVerifier(address(0xBEEF)), + maxChallengeDuration: Duration.wrap(uint64(7 days)), + maxProveDuration: Duration.wrap(uint64(3 days)), + challengerBond: 1 ether + }) + ); + + runCurrentUpgradeV2(chainPAO); + + assertEq( + address(disputeGameFactory.gameImpls(GameTypes.ZK_DISPUTE_GAME)), + zkImpl, + "ZK game impl not registered in factory" + ); + assertEq(disputeGameFactory.initBonds(GameTypes.ZK_DISPUTE_GAME), 1 ether, "ZK init bond not set"); + } + + /// @notice Tests that setting ZK config to enabled without the dev feature reverts. + function test_upgrade_enableZKGameWithoutDevFeature_reverts() public { + // Mock the container to report ZK_DISPUTE_GAME dev feature as disabled, regardless of + // what the real deployed container has in its bitmap. + vm.mockCall( + address(opcmV2.contractsContainer()), + abi.encodeCall(IOPContractsManagerContainer.isDevFeatureEnabled, (DevFeatures.ZK_DISPUTE_GAME)), + abi.encode(false) + ); + + // Set ZK config to enabled (but the dev feature is mocked as disabled above). + v2UpgradeInput.disputeGameConfigs[6].enabled = true; + + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the ZK prestate and verifier can be rotated via a config update. + function test_upgrade_updateZKPrestateAndVerifier_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + + // Enable ZK with initial config. + v2UpgradeInput.disputeGameConfigs[6].enabled = true; + v2UpgradeInput.disputeGameConfigs[6].initBond = 1 ether; + v2UpgradeInput.disputeGameConfigs[6].gameArgs = abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("zk prestate v1"))), + verifier: IZKVerifier(address(0xBEEF)), + maxChallengeDuration: Duration.wrap(uint64(7 days)), + maxProveDuration: Duration.wrap(uint64(3 days)), + challengerBond: 1 ether + }) + ); + runCurrentUpgradeV2(chainPAO); + bytes memory argsV1 = disputeGameFactory.gameArgs(GameTypes.ZK_DISPUTE_GAME); + + // Rotate to new prestate and verifier. + v2UpgradeInput.disputeGameConfigs[6].gameArgs = abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("zk prestate v2"))), + verifier: IZKVerifier(address(0xDEAD)), + maxChallengeDuration: Duration.wrap(uint64(7 days)), + maxProveDuration: Duration.wrap(uint64(3 days)), + challengerBond: 1 ether + }) + ); + runCurrentUpgradeV2(chainPAO); + bytes memory argsV2 = disputeGameFactory.gameArgs(GameTypes.ZK_DISPUTE_GAME); + + assertTrue(keccak256(argsV1) != keccak256(argsV2), "ZK game args should have changed after rotation"); + } + + /// @notice Tests that disabling the ZK game sets the init bond to zero in the factory. + function test_upgrade_disableZKGame_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + + // First enable ZK game. + v2UpgradeInput.disputeGameConfigs[6].enabled = true; + v2UpgradeInput.disputeGameConfigs[6].initBond = 1 ether; + v2UpgradeInput.disputeGameConfigs[6].gameArgs = abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("zk prestate"))), + verifier: IZKVerifier(address(0xBEEF)), + maxChallengeDuration: Duration.wrap(uint64(7 days)), + maxProveDuration: Duration.wrap(uint64(3 days)), + challengerBond: 1 ether + }) + ); + runCurrentUpgradeV2(chainPAO); + assertEq(disputeGameFactory.initBonds(GameTypes.ZK_DISPUTE_GAME), 1 ether, "ZK init bond should be set"); + + // Now disable ZK game. + v2UpgradeInput.disputeGameConfigs[6].enabled = false; + v2UpgradeInput.disputeGameConfigs[6].initBond = 0; + runCurrentUpgradeV2(chainPAO); + assertEq( + disputeGameFactory.initBonds(GameTypes.ZK_DISPUTE_GAME), 0, "ZK init bond should be zero after disable" + ); + } + /// @notice Tests that override instructions for the super root migration are blocked when /// the SUPER_ROOT_GAMES_MIGRATION feature flag is not enabled. function test_upgrade_overrideBlockedWithoutMigrationFlag_reverts() public { @@ -1033,8 +1173,6 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI gameArgs: hex"" }) ); - - // Super types. v2UpgradeInput.disputeGameConfigs.push( IOPContractsManagerUtils.DisputeGameConfig({ enabled: true, @@ -1070,6 +1208,22 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI }) ); } + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)), + verifier: IZKVerifier(address(0)), + maxChallengeDuration: Duration.wrap(0), + maxProveDuration: Duration.wrap(0), + challengerBond: 0 + }) + ) + }) + ); // Add override instructions. v2UpgradeInput.extraInstructions.push( @@ -1515,7 +1669,7 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { maximumBaseFee: type(uint128).max }); - // Set up dispute game configs. All 6 game types are required. + // Set up dispute game configs. All 7 game types are required. // In super root mode, SUPER_PERMISSIONED_CANNON is enabled; otherwise PERMISSIONED_CANNON. address initialChallenger = makeAddr("deployChallenger"); address initialProposer = makeAddr("deployProposer"); @@ -1574,6 +1728,22 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { gameArgs: bytes("") }) ); + deployConfig.disputeGameConfigs.push( + IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)), + verifier: IZKVerifier(address(0)), + maxChallengeDuration: Duration.wrap(0), + maxProveDuration: Duration.wrap(0), + challengerBond: 0 + }) + ) + }) + ); } /// @notice Tests that the deploy function succeeds and passes standard validation. @@ -1582,7 +1752,9 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { // In standard mode, CANNON and CANNON_KONA are disabled → PLDG-10,CKDG-10. // In super root mode, SUPER_CANNON_KONA is disabled → SCKDG-10. bool superRoot = isDevFeatureEnabled(DevFeatures.SUPER_ROOT_GAMES_MIGRATION); + bool zk = isDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME); string memory expectedErrors = superRoot ? "SCKDG-10" : "PLDG-10,CKDG-10"; + if (zk) expectedErrors = string.concat(expectedErrors, ",ZKDG-10"); IOPContractsManagerV2.ChainContracts memory cts = runDeployV2(deployConfig, bytes(""), expectedErrors); // Verify key contracts are deployed. @@ -1621,6 +1793,19 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { ); } + /// @notice Tests that enabling the ZK game during initial deployment reverts. + function test_deploy_enableZKGame_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + + deployConfig.disputeGameConfigs[6].enabled = true; + deployConfig.disputeGameConfigs[6].initBond = 1 ether; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + /// @notice Tests that deploy reverts when game configs are in wrong order. function test_deploy_wrongGameConfigOrder_reverts() public { // Swap the game config order. @@ -1706,9 +1891,11 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { /// @notice PERMISSIONED_CANNON as respected game type succeeds during deploy. function test_deploy_permissionedCannonRespectedGameType_succeeds() public { bool superRoot = isDevFeatureEnabled(DevFeatures.SUPER_ROOT_GAMES_MIGRATION); + bool zk = isDevFeatureEnabled(DevFeatures.ZK_DISPUTE_GAME); GameType permType = superRoot ? GameTypes.SUPER_PERMISSIONED_CANNON : GameTypes.PERMISSIONED_CANNON; deployConfig.startingRespectedGameType = permType; string memory expectedErrors = superRoot ? "SCKDG-10" : "PLDG-10,CKDG-10"; + if (zk) expectedErrors = string.concat(expectedErrors, ",ZKDG-10"); IOPContractsManagerV2.ChainContracts memory cts = runDeployV2(deployConfig, bytes(""), expectedErrors); assertEq(cts.anchorStateRegistry.respectedGameType().raw(), permType.raw(), "respected game type mismatch"); } @@ -1811,7 +1998,7 @@ contract OPContractsManagerV2_Migrate_Test is OPContractsManagerV2_TestInit { address initialChallenger = DisputeGames.permissionedGameChallenger(disputeGameFactory); address initialProposer = DisputeGames.permissionedGameProposer(disputeGameFactory); IOPContractsManagerUtils.DisputeGameConfig[] memory dgConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); dgConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: false, initBond: 0, @@ -1854,6 +2041,20 @@ contract OPContractsManagerV2_Migrate_Test is OPContractsManagerV2_TestInit { gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: bytes("") }); + dgConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)), + verifier: IZKVerifier(address(0)), + maxChallengeDuration: Duration.wrap(0), + maxProveDuration: Duration.wrap(0), + challengerBond: 0 + }) + ) + }); // Set up the deploy config using struct literal for compile-time field checking. IOPContractsManagerV2.FullConfig memory deployConfig = IOPContractsManagerV2.FullConfig({ @@ -1958,6 +2159,7 @@ contract OPContractsManagerV2_Migrate_Test is OPContractsManagerV2_TestInit { _assertGameIsEmpty(_disputeGameFactory, GameTypes.SUPER_PERMISSIONED_CANNON, "SUPER_PERMISSIONED_CANNON"); _assertGameIsEmpty(_disputeGameFactory, GameTypes.CANNON_KONA, "CANNON_KONA"); _assertGameIsEmpty(_disputeGameFactory, GameTypes.SUPER_CANNON_KONA, "SUPER_CANNON_KONA"); + _assertGameIsEmpty(_disputeGameFactory, GameTypes.ZK_DISPUTE_GAME, "ZK_DISPUTE_GAME"); } /// @notice Helper function to assert a game is empty. @@ -2211,7 +2413,7 @@ contract OPContractsManagerV2_FeatBatchUpgrade_Test is OPContractsManagerV2_Test // Set up dispute game configs. address initialChallenger = makeAddr("challenger"); address initialProposer = makeAddr("proposer"); - baseConfig.disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](6); + baseConfig.disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](7); baseConfig.disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: false, initBond: 0, @@ -2254,6 +2456,20 @@ contract OPContractsManagerV2_FeatBatchUpgrade_Test is OPContractsManagerV2_Test gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: bytes("") }); + baseConfig.disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: abi.encode( + IOPContractsManagerUtils.ZKDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)), + verifier: IZKVerifier(address(0)), + maxChallengeDuration: Duration.wrap(0), + maxProveDuration: Duration.wrap(0), + challengerBond: 0 + }) + ) + }); // 3. Deploy 15 separate chains using opcmV2.deploy(). IOPContractsManagerV2.ChainContracts[] memory chains = diff --git a/packages/contracts-bedrock/test/setup/DisputeGames.sol b/packages/contracts-bedrock/test/setup/DisputeGames.sol index 74a11a9f006..9c5ba9d58c5 100644 --- a/packages/contracts-bedrock/test/setup/DisputeGames.sol +++ b/packages/contracts-bedrock/test/setup/DisputeGames.sol @@ -174,6 +174,17 @@ library DisputeGames { _mockGameArg(_dgf, _gameType, GameArg.L2_CHAIN_ID, value); } + /// @notice Mocks the l2ChainId in a ZK dispute game's packed args (offset 140). + /// ZK game args have a different layout than LibGameArgs, so a dedicated helper is needed. + function mockZKGameImplL2ChainId(IDisputeGameFactory _dgf, GameType _gameType, uint256 _chainId) internal { + bytes memory modifiedGameArgs = _dgf.gameArgs(_gameType); + bytes memory value = abi.encodePacked(_chainId); + modifiedGameArgs.overwriteAtOffset(140, value); + vm.mockCall( + address(_dgf), abi.encodeCall(IDisputeGameFactory.gameArgs, (_gameType)), abi.encode(modifiedGameArgs) + ); + } + function mockGameImplProposer(IDisputeGameFactory _dgf, GameType _gameType, address _proposer) internal { bytes memory value = abi.encodePacked(_proposer); _mockGameArg(_dgf, _gameType, GameArg.PROPOSER, value); diff --git a/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol b/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol index fbd8ad94e24..b57cfbcd1f4 100644 --- a/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol @@ -264,7 +264,7 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { // Migration upgrade: legacy types disabled, super types enabled. // Order must match validGameTypes in OPContractsManagerV2._assertValidFullConfig(). - disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](6); + disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](7); disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: false, initBond: 0, @@ -320,6 +320,12 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { gameArgs: hex"" }); } + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: hex"" + }); // Migration needs 3 extra instructions: DelayedWETH proxy + anchor root + game type overrides. extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](3); @@ -340,7 +346,7 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { } else { // Standard upgrade path: legacy types enabled, super types disabled. // Order must match validGameTypes in OPContractsManagerV2._assertValidFullConfig(). - disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](6); + disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](7); disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: true, initBond: disputeGameFactory.initBonds(GameTypes.CANNON), @@ -391,6 +397,12 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { gameType: GameTypes.SUPER_CANNON_KONA, gameArgs: hex"" }); + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: hex"" + }); // Standard path only needs DelayedWETH proxy deployment permission. extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](1); diff --git a/packages/contracts-bedrock/test/setup/PastUpgrades.sol b/packages/contracts-bedrock/test/setup/PastUpgrades.sol index 4a56b612a42..246d28aad1e 100644 --- a/packages/contracts-bedrock/test/setup/PastUpgrades.sol +++ b/packages/contracts-bedrock/test/setup/PastUpgrades.sol @@ -35,6 +35,7 @@ library PastUpgrades { /// @notice Dummy prestates used for testing (actual values don't matter for upgrade tests) bytes32 internal constant DUMMY_CANNON_PRESTATE = keccak256("CANNON"); bytes32 internal constant DUMMY_CANNON_KONA_PRESTATE = keccak256("CANNON_KONA"); + bytes32 internal constant DUMMY_ZK_PRESTATE = keccak256("ZK"); /// @notice Struct representing an OPCM from the registry (returned by FFI). /// Note: releaseVersion is NOT the OPCM semver - query opcm.version() on-chain for that. @@ -189,7 +190,7 @@ library PastUpgrades { // Build dispute game configs with dummy prestates. // Order must match validGameTypes in OPContractsManagerV2._assertValidFullConfig(). IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerUtils.DisputeGameConfig[](6); + new IOPContractsManagerUtils.DisputeGameConfig[](7); // CANNON (game type 0) disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ @@ -249,6 +250,14 @@ library PastUpgrades { gameArgs: hex"" }); + // ZK_DISPUTE_GAME — always disabled, registered separately via deployer pipeline + disputeGameConfigs[6] = IOPContractsManagerUtils.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.ZK_DISPUTE_GAME, + gameArgs: hex"" + }); + _sortDisputeGameConfigs(disputeGameConfigs); // Execute the V2 upgrade diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index ed6754d15cc..b76eb81ef5e 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -275,7 +275,8 @@ abstract contract Setup is FeatureFlags { console.log("Setup: mocking unoptimized proxy implementations"); string memory delayedWETHVersion = ISemver(_delayedWETHImpl).version(); - GameType[3] memory gameTypes = [GameTypes.CANNON, GameTypes.PERMISSIONED_CANNON, GameTypes.CANNON_KONA]; + GameType[4] memory gameTypes = + [GameTypes.CANNON, GameTypes.PERMISSIONED_CANNON, GameTypes.CANNON_KONA, GameTypes.ZK_DISPUTE_GAME]; for (uint256 i = 0; i < gameTypes.length; i++) { IDelayedWETH delayedWETHProxy = DisputeGames.getGameImplDelayedWeth(_dgf, gameTypes[i]); if (address(delayedWETHProxy) != address(0)) {