diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 3eff5166a5cfe..2faa6cdcb1179 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -157,24 +157,23 @@ contract DeployOPChain is Script { pure returns (IOPContractsManagerV2.FullConfig memory config_) { + // Only PERMISSIONED_CANNON is allowed for initial deployment since no prestate exists for permissionless games. + require( + _input.disputeGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw(), + "DeployOPChain: only PERMISSIONED_CANNON game type is supported for initial deployment" + ); + // Build dispute game configs - OPCMV2 requires exactly 3 configs: CANNON, PERMISSIONED_CANNON, CANNON_KONA IOPContractsManagerUtils.DisputeGameConfig[] memory disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](3); - // Determine which games should be enabled based on the starting respected game type - bool cannonEnabled = _input.disputeGameType.raw() == GameTypes.CANNON.raw(); - bool permissionedCannonEnabled = true; // PERMISSIONED_CANNON must always be enabled - bool cannonKonaEnabled = _input.disputeGameType.raw() == GameTypes.CANNON_KONA.raw(); - // Config 0: CANNON - IOPContractsManagerUtils.FaultDisputeGameConfig memory cannonConfig = - IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: _input.disputeAbsolutePrestate }); - + // Must be disabled for the initial deployment since no prestate exists for permissionless games. disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: cannonEnabled, - initBond: cannonEnabled ? DEFAULT_INIT_BOND : 0, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON, - gameArgs: abi.encode(cannonConfig) + gameArgs: bytes("") }); // Config 1: PERMISSIONED_CANNON (must be enabled) @@ -186,21 +185,19 @@ contract DeployOPChain is Script { }); disputeGameConfigs[1] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: permissionedCannonEnabled, + enabled: true, initBond: DEFAULT_INIT_BOND, gameType: GameTypes.PERMISSIONED_CANNON, gameArgs: abi.encode(pdgConfig) }); // Config 2: CANNON_KONA - IOPContractsManagerUtils.FaultDisputeGameConfig memory cannonKonaConfig = - IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: _input.disputeAbsolutePrestate }); - + // Must be disabled for the initial deployment since no prestate exists for permissionless games. disputeGameConfigs[2] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: cannonKonaEnabled, - initBond: cannonKonaEnabled ? DEFAULT_INIT_BOND : 0, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode(cannonKonaConfig) + gameArgs: bytes("") }); config_ = IOPContractsManagerV2.FullConfig({ @@ -211,7 +208,7 @@ contract DeployOPChain is Script { unsafeBlockSigner: _input.unsafeBlockSigner, batcher: _input.batcher, startingAnchorRoot: ScriptConstants.DEFAULT_OUTPUT_ROOT(), - startingRespectedGameType: _input.disputeGameType, + startingRespectedGameType: GameTypes.PERMISSIONED_CANNON, basefeeScalar: _input.basefeeScalar, blobBasefeeScalar: _input.blobBaseFeeScalar, gasLimit: _input.gasLimit, diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index a66ada9df9502..c1b2cd9b2f531 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -52,8 +52,8 @@ "sourceCodeHash": "0xb3184aa5d95a82109e7134d1f61941b30e25f655b9849a0e303d04bbce0cde0b" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0xe9c4ba721d8bd3390b184e243d598a522b7efe8b7426ec4d0898529c5654e705", - "sourceCodeHash": "0x3898bab02581ae16ac529358dab8e80477fa34f1354ce8783cef3d36354d1810" + "initCodeHash": "0x5cbc998e57035d8658824e16dacaab8c702f9e18f482e16989b9420e5a7e8190", + "sourceCodeHash": "0x11678225efb1fb4593085febd8f438eeb4752c0ab3dfd2ee1c4fe47970dda953" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x838bbd7f381e84e21887f72bd1da605bfc4588b3c39aed96cbce67c09335b3ee", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 478e2822525d5..c17aa044d2346 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -147,9 +147,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.0.7 + /// @custom:semver 7.0.8 function version() public pure returns (string memory) { - return "7.0.7"; + return "7.0.8"; } /// @param _standardValidator The standard validator for this OPCM release. @@ -643,7 +643,7 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { /// @notice Validates the deployment/upgrade config. /// @param _cfg The full config. - function _assertValidFullConfig(FullConfig memory _cfg) internal pure { + function _assertValidFullConfig(FullConfig memory _cfg, bool _isInitialDeployment) internal pure { // Start validating the dispute game configs. Put allowed game types here. GameType[] memory validGameTypes = new GameType[](3); validGameTypes[0] = GameTypes.CANNON; @@ -667,6 +667,15 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { if (!_cfg.disputeGameConfigs[i].enabled && _cfg.disputeGameConfigs[i].initBond != 0) { revert OPContractsManagerV2_InvalidGameConfigs(); } + + // During initial deployment, only PERMISSIONED_CANNON can be enabled, because no prestate exists for + // permissionless games. + if ( + _isInitialDeployment && (validGameTypes[i].raw() != GameTypes.PERMISSIONED_CANNON.raw()) + && _cfg.disputeGameConfigs[i].enabled + ) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } } // We currently REQUIRE that the PermissionedDisputeGame is enabled. We may be able to @@ -691,7 +700,7 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { returns (ChainContracts memory) { // Validate the config. - _assertValidFullConfig(_cfg); + _assertValidFullConfig(_cfg, _isInitialDeployment); // Load the implementations. IOPContractsManagerContainer.Implementations memory impls = implementations(); diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 9780b948066d8..e8c184634df70 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -1030,10 +1030,10 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { address initialProposer = DisputeGames.permissionedGameProposer(disputeGameFactory); deployConfig.disputeGameConfigs.push( IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: DEFAULT_DISPUTE_GAME_INIT_BOND, // Standard init bond + enabled: false, + initBond: 0, gameType: GameTypes.CANNON, - gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + gameArgs: bytes("") }) ); deployConfig.disputeGameConfigs.push( @@ -1052,12 +1052,10 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { ); deployConfig.disputeGameConfigs.push( IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: DEFAULT_DISPUTE_GAME_INIT_BOND, // Standard init bond + enabled: false, + initBond: 0, gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode( - IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate }) - ) + gameArgs: bytes("") }) ); } @@ -1065,7 +1063,9 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { /// @notice Tests that the deploy function succeeds and passes standard validation. function test_deploy_succeeds() public { // Run the deploy and standard validator checks. - IOPContractsManagerV2.ChainContracts memory cts = runDeployV2(deployConfig); + // We expect PLDG-10 and CKDG-10 validator errors because CANNON and CANNON_KONA are + // disabled during initial deployment (no implementations registered). + IOPContractsManagerV2.ChainContracts memory cts = runDeployV2(deployConfig, bytes(""), "PLDG-10,CKDG-10"); // Verify key contracts are deployed. assertTrue(address(cts.systemConfig) != address(0), "systemConfig not deployed"); @@ -1138,6 +1138,26 @@ contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) ); } + + function test_deploy_cannonGameEnabled_reverts() public { + deployConfig.disputeGameConfigs[0].enabled = true; + deployConfig.disputeGameConfigs[0].initBond = 1 ether; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + function test_deploy_cannonKonaGameEnabled_reverts() public { + deployConfig.disputeGameConfigs[2].enabled = true; + deployConfig.disputeGameConfigs[2].initBond = 1 ether; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } } /// @title OPContractsManagerV2_DevFeatureBitmap_Test @@ -1188,10 +1208,10 @@ contract OPContractsManagerV2_Migrate_Test is OPContractsManagerV2_TestInit { IOPContractsManagerUtils.DisputeGameConfig[] memory dgConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](3); dgConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: 0.08 ether, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON, - gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + gameArgs: bytes("") }); dgConfigs[1] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: true, @@ -1206,10 +1226,10 @@ contract OPContractsManagerV2_Migrate_Test is OPContractsManagerV2_TestInit { ) }); dgConfigs[2] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: 0.08 ether, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + gameArgs: bytes("") }); // Set up the deploy config using struct literal for compile-time field checking. @@ -1544,10 +1564,10 @@ contract OPContractsManagerV2_FeatBatchUpgrade_Test is OPContractsManagerV2_Test address initialProposer = makeAddr("proposer"); baseConfig.disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](3); baseConfig.disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: DEFAULT_DISPUTE_GAME_INIT_BOND, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON, - gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + gameArgs: bytes("") }); baseConfig.disputeGameConfigs[1] = IOPContractsManagerUtils.DisputeGameConfig({ enabled: true, @@ -1562,10 +1582,10 @@ contract OPContractsManagerV2_FeatBatchUpgrade_Test is OPContractsManagerV2_Test ) }); baseConfig.disputeGameConfigs[2] = IOPContractsManagerUtils.DisputeGameConfig({ - enabled: true, - initBond: DEFAULT_DISPUTE_GAME_INIT_BOND, + enabled: false, + initBond: 0, gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + gameArgs: bytes("") }); // 3. Deploy 15 separate chains using opcmV2.deploy(). diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index b8f141bae8d5f..961e05cccd1e5 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -242,70 +242,20 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { _checkDeploymentAssertions(doo); } - function test_run_cannonGameType_succeeds() public { - // Skip test if OPCM v2 is not enabled because OPCM v1 registers PERMISSIONED_CANNON only regardles of the game - // type. + function test_run_cannonGameType_reverts() public { skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); deployOPChainInput.disputeGameType = GameTypes.CANNON; - DeployOPChain.Output memory doo = deployOPChain.run(deployOPChainInput); - - // CANNON should be enabled with init bond - assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), - deployOPChain.DEFAULT_INIT_BOND(), - "CANNON init bond" - ); - assertNotEq(address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON)), address(0), "CANNON impl"); - - // PERMISSIONED_CANNON must always be enabled - assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.PERMISSIONED_CANNON), - deployOPChain.DEFAULT_INIT_BOND(), - "PERMISSIONED_CANNON init bond" - ); - assertNotEq( - address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), - address(0), - "PERMISSIONED_CANNON impl" - ); - - // CANNON_KONA should not be enabled - assertEq(doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON_KONA), 0, "CANNON_KONA init bond"); + vm.expectRevert("DeployOPChain: only PERMISSIONED_CANNON game type is supported for initial deployment"); + deployOPChain.run(deployOPChainInput); } - function test_run_cannonKonaGameType_succeeds() public { - // Skip test if OPCM v2 is not enabled because OPCM v1 registers PERMISSIONED_CANNON only regardles of the game - // type. + function test_run_cannonKonaGameType_reverts() public { skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); deployOPChainInput.disputeGameType = GameTypes.CANNON_KONA; - DeployOPChain.Output memory doo = deployOPChain.run(deployOPChainInput); - - // CANNON_KONA should be enabled with init bond - assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON_KONA), - deployOPChain.DEFAULT_INIT_BOND(), - "CANNON_KONA init bond" - ); - assertNotEq( - address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON_KONA)), address(0), "CANNON_KONA impl" - ); - - // PERMISSIONED_CANNON must always be enabled in OPCM v2 - assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.PERMISSIONED_CANNON), - deployOPChain.DEFAULT_INIT_BOND(), - "PERMISSIONED_CANNON init bond" - ); - assertNotEq( - address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), - address(0), - "PERMISSIONED_CANNON impl" - ); - - // CANNON should not be enabled - assertEq(doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), 0, "CANNON init bond"); + vm.expectRevert("DeployOPChain: only PERMISSIONED_CANNON game type is supported for initial deployment"); + deployOPChain.run(deployOPChainInput); } /// @notice Tests that faultDisputeGame is set to address(0) and permissionedDisputeGame is set to the correct @@ -313,28 +263,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { function test_run_faultDisputeGamePermissionedCannon_succeeds() public { skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - _assertDisputeGames(GameTypes.PERMISSIONED_CANNON); - } - - /// @notice Tests that faultDisputeGame is set to address(0) when disputeGameType is GameTypes.CANNON. - function test_run_faultDisputeGameCannon_succeeds() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - _assertDisputeGames(GameTypes.CANNON); - } - - /// @notice Tests that faultDisputeGame is set to address(0) when disputeGameType is GameTypes.CANNON_KONA. - function test_run_faultDisputeGameCannonKona_succeeds() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - _assertDisputeGames(GameTypes.CANNON_KONA); - } - - /// @notice Helper function that runs DeployOPChain.run and asserts DeployOPChain.Output.faultDisputeGame is set to - /// address(0) and DeployOPChain.Output.permissionedDisputeGame is set to the correct implementation. - function _assertDisputeGames(GameType _gameType) internal { - deployOPChainInput.disputeGameType = _gameType; - + deployOPChainInput.disputeGameType = GameTypes.PERMISSIONED_CANNON; DeployOPChain.Output memory doo = deployOPChain.run(deployOPChainInput); address expectedPermissioned = address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); @@ -382,25 +311,23 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { ); assertNotEq(address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), address(0)); - // CANNON is only enabled if it's the starting game type - bool cannonEnabled = deployOPChainInput.disputeGameType.raw() == GameTypes.CANNON.raw(); + // CANNON must be disabled for initial deployment + assertEq(doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), 0, "CANNON init bond should be 0"); assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), - cannonEnabled ? deployOPChain.DEFAULT_INIT_BOND() : 0 + address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON)), + address(0), + "CANNON impl should be the zero address" ); - if (cannonEnabled) { - assertNotEq(address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON)), address(0)); - } - // CANNON_KONA is only enabled if it's the starting game type - bool cannonKonaEnabled = deployOPChainInput.disputeGameType.raw() == GameTypes.CANNON_KONA.raw(); + // CANNON_KONA must be disabled for initial deployment + assertEq( + doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON_KONA), 0, "CANNON_KONA init bond should be 0" + ); assertEq( - doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON_KONA), - cannonKonaEnabled ? deployOPChain.DEFAULT_INIT_BOND() : 0 + address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON_KONA)), + address(0), + "CANNON_KONA impl should be the zero address" ); - if (cannonKonaEnabled) { - assertNotEq(address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON_KONA)), address(0)); - } } } }