From 396ee8945148b2c97421a6b8cd5d04f799a802be Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 9 Sep 2025 21:04:03 -0400 Subject: [PATCH 01/75] feat: integrate v2 implementations into opcm --- .../contracts-bedrock/interfaces/L1/IOPContractsManager.sol | 2 ++ .../scripts/deploy/DeployImplementations.s.sol | 4 +++- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index adb75fe8c8de7..72fc2a1aaa3eb 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -216,6 +216,8 @@ interface IOPContractsManager { address anchorStateRegistryImpl; address delayedWETHImpl; address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; } /// @notice The input required to identify a chain for upgrading. diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index dced3b7d2c3f4..430c07bc6770d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -158,7 +158,9 @@ contract DeployImplementations is Script { disputeGameFactoryImpl: address(_output.disputeGameFactoryImpl), anchorStateRegistryImpl: address(_output.anchorStateRegistryImpl), delayedWETHImpl: address(_output.delayedWETHImpl), - mipsImpl: address(_output.mipsSingleton) + mipsImpl: address(_output.mipsSingleton), + faultDisputeGameV2Impl: address(_output.faultDisputeGameV2Impl), + permissionedDisputeGameV2Impl: address(_output.permissionedDisputeGameV2Impl) }); deployOPCMBPImplsContainer(_input, _output, _blueprints, implementations); diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 424066f1e6fb1..857e004d42e1c 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1778,6 +1778,8 @@ contract OPContractsManager is ISemver { address anchorStateRegistryImpl; address delayedWETHImpl; address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; } /// @notice The input required to identify a chain for upgrading, along with new prestate hashes From 5ba217842011fe73592f223ed6f4472e298fbe4d Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 10 Sep 2025 17:24:40 -0400 Subject: [PATCH 02/75] fix: skip v2 implementations if not deployed --- .../scripts/deploy/VerifyOPCM.s.sol | 19 +++++++++++++++++ .../test/scripts/VerifyOPCM.t.sol | 21 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 39ae8d5eb02c6..2b95ffbc42ff0 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -398,6 +398,14 @@ contract VerifyOPCM is Script { console.log(string.concat(" Contract: ", _target.name)); console.log(string.concat(" Address: ", vm.toString(_target.addr))); + // Skip verification for V2 implementations based on contract name + // V2 implementations follow the pattern "*DisputeGameV2" or "*DisputeGameV2Impl" + if (_isV2Implementation(_target.name)) { + console.log(" Status: [SKIP] V2 implementation, not yet deployed"); + console.log(string.concat(" Status: [SKIP] Skipping verification for ", _target.name)); + return true; + } + // Build the expected path to the artifact file. string memory artifactPath = _buildArtifactPath(_target.name); console.log(string.concat(" Expected Runtime Artifact: ", artifactPath)); @@ -811,6 +819,17 @@ contract VerifyOPCM is Script { return string(fieldBytes); } + /// @notice Checks if a contract name corresponds to a V2 implementation. + /// @param _contractName The contract name to check. + /// @return True if this is a V2 implementation, false otherwise. + function _isV2Implementation(string memory _contractName) internal pure returns (bool) { + // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts + return ( + keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) + || keccak256(bytes(_contractName)) == keccak256(bytes("PermissionedDisputeGameV2")) + ); + } + /// @notice Checks if a position is inside an immutable reference. /// @param _pos The position to check. /// @param _artifact The artifact info. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index fd64d07b948fa..f40a595250c32 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -78,6 +78,17 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { setupEnvVars(); } + /// @notice Helper function to check if a contract name corresponds to a V2 implementation. + /// @param _contractName The contract name to check. + /// @return True if this is a V2 implementation, false otherwise. + function _isV2Implementation(string memory _contractName) internal pure returns (bool) { + // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts + return ( + keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) + || keccak256(bytes(_contractName)) == keccak256(bytes("PermissionedDisputeGameV2")) + ); + } + /// @notice Tests that the script succeeds when no changes are introduced. function test_run_succeeds() public { // Coverage changes bytecode and causes failures, skip. @@ -126,6 +137,11 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { uint256 randomImplIndex = vm.randomUint(0, refs.length - 1); VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; + // Skip V2 implementations (not yet deployed) + if (_isV2Implementation(ref.name)) { + continue; + } + // Get the code for the implementation. bytes memory implCode = ref.addr.code; @@ -186,6 +202,11 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { uint256 randomImplIndex = vm.randomUint(0, refs.length - 1); VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; + // Skip V2 implementations (not yet deployed) + if (_isV2Implementation(ref.name)) { + continue; + } + // Get the code for the implementation. bytes memory implCode = ref.addr.code; From 97b34abacbbceb71e0e16dc37b66bab092a138cd Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 11 Sep 2025 09:37:14 -0400 Subject: [PATCH 03/75] chore: bump semver version --- .../snapshots/abi/OPContractsManager.json | 10 ++++++++++ .../OPContractsManagerContractsContainer.json | 20 +++++++++++++++++++ .../abi/OPContractsManagerDeployer.json | 10 ++++++++++ .../abi/OPContractsManagerGameTypeAdder.json | 10 ++++++++++ .../OPContractsManagerInteropMigrator.json | 10 ++++++++++ .../abi/OPContractsManagerUpgrader.json | 10 ++++++++++ .../OPContractsManagerContractsContainer.json | 2 +- 7 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index f4ef1718aa42b..db5c0b7734ded 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -530,6 +530,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json index c49484fc7224c..6b4c6228c2736 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -144,6 +144,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", @@ -340,6 +350,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index 7cd7a44502c06..85e42e8fc7e0f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -428,6 +428,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index 80b80512aebb9..d9ac76fa83420 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -321,6 +321,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json index b06cd541bb38d..e8d1dec02c225 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json @@ -223,6 +223,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index 512a83ae75cff..a092f31799459 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -223,6 +223,16 @@ "internalType": "address", "name": "mipsImpl", "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" } ], "internalType": "struct OPContractsManager.Implementations", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index d87deb94bc76b..2193053869827 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -7,7 +7,7 @@ "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "448", + "bytes": "512", "label": "implementation", "offset": 0, "slot": "13", From 961e8cd605d1e058b3d353184e2e103b5d738093 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 14:09:03 -0400 Subject: [PATCH 04/75] feat: conditionally set the v2 games based on feature flag --- .../interfaces/L1/IOPContractsManager.sol | 5 ++ .../src/L1/OPContractsManager.sol | 71 +++++++++++++++---- .../test/L1/OPContractsManager.t.sol | 43 +++++++++++ 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 72fc2a1aaa3eb..be3b864b94e88 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -14,6 +14,8 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; +import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -177,6 +179,9 @@ interface IOPContractsManager { IPermissionedDisputeGame permissionedDisputeGame; IDelayedWETH delayedWETHPermissionedGameProxy; IDelayedWETH delayedWETHPermissionlessGameProxy; + // V2 dispute game contracts (deployed when DEPLOY_V2_DISPUTE_GAMES flag is set) + IFaultDisputeGameV2 faultDisputeGameV2; + IPermissionedDisputeGameV2 permissionedDisputeGameV2; } /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 857e004d42e1c..7492639dacd6e 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -26,6 +26,8 @@ import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; +import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; import { ISuperPermissionedDisputeGame } from "interfaces/dispute/ISuperPermissionedDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; @@ -541,15 +543,32 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { ); } - // Deploy the new game type. - outputs[i].faultDisputeGame = IFaultDisputeGame( - Blueprint.deployFrom( - blueprint1, - blueprint2, - computeSalt(l2ChainId, gameConfig.saltMixer, gameContractName), - constructorData - ) - ); + // Deploy the new game type or use v2 implementation + if ( + isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) + && gameConfig.disputeGameType.raw() == GameTypes.CANNON.raw() + && address(getImplementations().faultDisputeGameV2Impl) != address(0) + ) { + // Use v2 FaultDisputeGame implementation + outputs[i].faultDisputeGame = IFaultDisputeGame(getImplementations().faultDisputeGameV2Impl); + } else if ( + isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) + && gameConfig.disputeGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw() + && address(getImplementations().permissionedDisputeGameV2Impl) != address(0) + ) { + // Use v2 PermissionedDisputeGame implementation + outputs[i].faultDisputeGame = IFaultDisputeGame(getImplementations().permissionedDisputeGameV2Impl); + } else { + // Deploy v1 game using blueprint + outputs[i].faultDisputeGame = IFaultDisputeGame( + Blueprint.deployFrom( + blueprint1, + blueprint2, + computeSalt(l2ChainId, gameConfig.saltMixer, gameContractName), + constructorData + ) + ); + } // As a last step, register the new game type with the DisputeGameFactory. If the game // type already exists, then its implementation will be overwritten. @@ -1075,6 +1094,13 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { ) ); + // Store v2 implementation addresses if the feature flag is enabled + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + // Store v2 implementation addresses for later registration with DisputeGameFactory + output.permissionedDisputeGameV2 = IPermissionedDisputeGameV2(implementation.permissionedDisputeGameV2Impl); + output.faultDisputeGameV2 = IFaultDisputeGameV2(implementation.faultDisputeGameV2Impl); + } + // -------- Set and Initialize Proxy Implementations -------- bytes memory data; @@ -1159,11 +1185,25 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { implementation.disputeGameFactoryImpl, data ); - setDGFImplementation( - output.disputeGameFactoryProxy, - GameTypes.PERMISSIONED_CANNON, - IDisputeGame(address(output.permissionedDisputeGame)) - ); + // Register the appropriate dispute game implementation based on the feature flag + if ( + isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) + && address(output.permissionedDisputeGameV2) != address(0) + ) { + // Register v2 implementation for PERMISSIONED_CANNON game type + setDGFImplementation( + output.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + IDisputeGame(address(output.permissionedDisputeGameV2)) + ); + } else { + // Register v1 implementation for PERMISSIONED_CANNON game type + setDGFImplementation( + output.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + IDisputeGame(address(output.permissionedDisputeGame)) + ); + } transferOwnership(address(output.disputeGameFactoryProxy), address(_input.roles.opChainProxyAdminOwner)); @@ -1739,6 +1779,9 @@ contract OPContractsManager is ISemver { IPermissionedDisputeGame permissionedDisputeGame; IDelayedWETH delayedWETHPermissionedGameProxy; IDelayedWETH delayedWETHPermissionlessGameProxy; + // V2 dispute game contracts (deployed when DEPLOY_V2_DISPUTE_GAMES flag is set) + IFaultDisputeGameV2 faultDisputeGameV2; + IPermissionedDisputeGameV2 permissionedDisputeGameV2; } /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index eeeff41eaab90..7e211d5772cde 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -29,6 +29,8 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; +import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; @@ -1785,3 +1787,44 @@ contract OPContractsManager_Version_Test is OPContractsManager_TestInit { assertNotEq(abi.encode(prestateUpdater.version()), abi.encode(0)); } } + +/// @title OPContractsManager_V2_Test +/// @notice Tests for v2 dispute game implementations in OPContractsManager +contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { + + /// @notice Test that deploy without v2 flag doesn't set v2 implementations + function test_deploy_withoutV2Flag_noV2Implementations() public { + // Convert DOI to OPCM input and deploy + IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); + vm.prank(address(this)); + IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); + + // Check that v2 implementations are not set (since flag is not enabled by default) + assertEq(address(output.permissionedDisputeGameV2), address(0)); + assertEq(address(output.faultDisputeGameV2), address(0)); + + // Check that v1 implementation is registered for PERMISSIONED_CANNON + address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertEq(registeredImpl, address(output.permissionedDisputeGame)); + } + + /// @notice Test that deploy with v2 flag would set v2 implementations + function test_deploy_withV2Flag_concept() public { + // This test demonstrates the v2 flag concept + // In a real deployment with v2 flag enabled: + // 1. The OPContractsManagerContractsContainer would be created with DEPLOY_V2_DISPUTE_GAMES flag + // 2. The v2 implementation addresses would be non-zero + // 3. The deploy function would register v2 implementations with DisputeGameFactory + + // Get the current implementations from OPCM (these would be v2 in a v2-enabled deployment) + IOPContractsManager.Implementations memory impls = opcm.implementations(); + + // In a v2-enabled deployment, these would be non-zero addresses + // For now, we just verify they are zero since v2 flag is not enabled + assertEq(address(impls.permissionedDisputeGameV2Impl), address(0)); + assertEq(address(impls.faultDisputeGameV2Impl), address(0)); + + // Test that we can check if v2 flag is enabled (it shouldn't be in default setup) + assertFalse(opcm.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)); + } +} From 92870137534caf231d76180af6ccb77e5f4c2c8f Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 14:36:04 -0400 Subject: [PATCH 05/75] feat: add override for setup to re-deploy opcm with feature flag --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 7e211d5772cde..ff9f788911b53 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1696,7 +1696,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - function setUp() public override { + function setUp() public virtual override { DeployOPChain_TestBase.setUp(); doi.set(doi.opChainProxyAdminOwner.selector, opChainProxyAdminOwner); @@ -1723,6 +1723,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // to the input struct type defined in OPContractsManager.sol. function toOPCMDeployInput(DeployOPChainInput _doi) internal + virtual view returns (IOPContractsManager.DeployInput memory) { From ef29b42c0d7ce48048d7bce8d25165004e689235 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 14:55:54 -0400 Subject: [PATCH 06/75] test: improve test by deploying with the feature flag and asserting on output --- .../test/L1/OPContractsManager.t.sol | 81 +++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index ff9f788911b53..da2c7dd17ecd7 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -14,6 +14,10 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Deploy } from "scripts/deploy/Deploy.s.sol"; import { VerifyOPCM } from "scripts/deploy/VerifyOPCM.s.sol"; import { Config } from "scripts/libraries/Config.sol"; +import { DeployImplementations } from "scripts/deploy/DeployImplementations.s.sol"; +import { DeploySuperchain } from "scripts/deploy/DeploySuperchain.s.sol"; +import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; +import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; // Libraries import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; @@ -1793,12 +1797,55 @@ contract OPContractsManager_Version_Test is OPContractsManager_TestInit { /// @notice Tests for v2 dispute game implementations in OPContractsManager contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { + /// @notice Helper function to deploy OPCM with v2 flag enabled + function _deployOPCMWithV2Flag() internal returns (IOPContractsManager) { + // Deploy Superchain contracts first + DeploySuperchain deploySuperchain = new DeploySuperchain(); + DeploySuperchain.Output memory dso = deploySuperchain.run( + DeploySuperchain.Input({ + superchainProxyAdminOwner: makeAddr("superchainProxyAdminOwner"), + protocolVersionsOwner: makeAddr("protocolVersionsOwner"), + guardian: makeAddr("guardian"), + paused: false, + requiredProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(1))), + recommendedProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(2))) + }) + ); + + // Deploy implementations with v2 flag enabled + DeployImplementations deployImplementations = new DeployImplementations(); + DeployImplementations.Output memory dio = deployImplementations.run( + DeployImplementations.Input({ + withdrawalDelaySeconds: 100, + minProposalSizeBytes: 200, + challengePeriodSeconds: 300, + proofMaturityDelaySeconds: 400, + disputeGameFinalityDelaySeconds: 500, + mipsVersion: StandardConstants.MIPS_VERSION, + faultGameV2MaxGameDepth: 73, + faultGameV2SplitDepth: 30, + faultGameV2ClockExtension: 10800, + faultGameV2MaxClockDuration: 302400, + superchainConfigProxy: dso.superchainConfigProxy, + protocolVersionsProxy: dso.protocolVersionsProxy, + superchainProxyAdmin: dso.superchainProxyAdmin, + upgradeController: dso.superchainProxyAdmin.owner(), + proposer: proposer, + challenger: challenger, + devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here + }) + ); + + return dio.opcm; + } + /// @notice Test that deploy without v2 flag doesn't set v2 implementations function test_deploy_withoutV2Flag_noV2Implementations() public { // Convert DOI to OPCM input and deploy IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); - vm.prank(address(this)); + vm.startPrank(address(this)); IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); + vm.stopPrank(); // Check that v2 implementations are not set (since flag is not enabled by default) assertEq(address(output.permissionedDisputeGameV2), address(0)); @@ -1811,21 +1858,27 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { /// @notice Test that deploy with v2 flag would set v2 implementations function test_deploy_withV2Flag_concept() public { - // This test demonstrates the v2 flag concept - // In a real deployment with v2 flag enabled: - // 1. The OPContractsManagerContractsContainer would be created with DEPLOY_V2_DISPUTE_GAMES flag - // 2. The v2 implementation addresses would be non-zero - // 3. The deploy function would register v2 implementations with DisputeGameFactory + IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); - // Get the current implementations from OPCM (these would be v2 in a v2-enabled deployment) - IOPContractsManager.Implementations memory impls = opcm.implementations(); + assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); + + IOPContractsManager.Implementations memory impls = opcmV2.implementations(); + + // Verify that v2 implementations are set (non-zero) + assertTrue(address(impls.permissionedDisputeGameV2Impl) != address(0), "PermissionedDisputeGameV2 implementation should be non-zero"); + assertTrue(address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero"); + + // Set up deploy input for the v2-enabled OPCM + doi.set(doi.opcm.selector, address(opcmV2)); + IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); + + vm.startPrank(address(this)); + IOPContractsManager.DeployOutput memory output = opcmV2.deploy(opcmInput); + vm.stopPrank(); - // In a v2-enabled deployment, these would be non-zero addresses - // For now, we just verify they are zero since v2 flag is not enabled - assertEq(address(impls.permissionedDisputeGameV2Impl), address(0)); - assertEq(address(impls.faultDisputeGameV2Impl), address(0)); + assertTrue(address(output.permissionedDisputeGameV2) != address(0), "PermissionedDisputeGameV2 should be deployed"); + assertTrue(address(output.faultDisputeGameV2) != address(0), "FaultDisputeGameV2 should be deployed"); - // Test that we can check if v2 flag is enabled (it shouldn't be in default setup) - assertFalse(opcm.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)); + assertTrue(address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 is still deployed, but not registered"); } } From 249ba99a697076e5a9ba404af94acba99a06dbd3 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 14:59:38 -0400 Subject: [PATCH 07/75] test: add assertion that the deployment configuration on the dgf is correct --- .../contracts-bedrock/test/L1/OPContractsManager.t.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index da2c7dd17ecd7..74f026811fa11 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1876,9 +1876,16 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { IOPContractsManager.DeployOutput memory output = opcmV2.deploy(opcmInput); vm.stopPrank(); + // Verify that v2 dispute game contracts are deployed and non-zero assertTrue(address(output.permissionedDisputeGameV2) != address(0), "PermissionedDisputeGameV2 should be deployed"); assertTrue(address(output.faultDisputeGameV2) != address(0), "FaultDisputeGameV2 should be deployed"); - assertTrue(address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 is still deployed, but not registered"); + // Verify that v1 permissioned dispute game is still deployed (for backward compatibility) + assertTrue(address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 should still be deployed"); + + // Verify that the DisputeGameFactory has registered the v2 implementation for PERMISSIONED_CANNON game type + address registeredPermissionedImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertEq(registeredPermissionedImpl, address(output.permissionedDisputeGameV2), + "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON"); } } From e3584b76b77aa09f87b63fec67fac5768291d663 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 15:08:08 -0400 Subject: [PATCH 08/75] chore: improve test name --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 74f026811fa11..e1b00226187e5 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1857,7 +1857,7 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { } /// @notice Test that deploy with v2 flag would set v2 implementations - function test_deploy_withV2Flag_concept() public { + function test_deploy_withV2Flag_setsV2Implementations() public { IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); From 3022729a25f98568fc32abd1da1b414184a83b30 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 16:29:19 -0400 Subject: [PATCH 09/75] refactor: add helper function for feature toggled deployment of opcm --- .../test/L1/OPContractsManager.t.sol | 117 ++++++++---------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index e1b00226187e5..f79bd42de0223 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1687,15 +1687,7 @@ contract OPContractsManager_Migrate_CannonKonaEnabled_Test is OPContractsManager } } -/// @title OPContractsManager_Deploy_Test -/// @notice Tests the `deploy` function of the `OPContractsManager` contract. -/// @dev Unlike other test suites, we intentionally do not inherit from CommonTest or Setup. This -/// is because OPContractsManager acts as a deploy script, so we start from a clean slate here -/// and work OPContractsManager's deployment into the existing test setup, instead of using -/// the existing test setup to deploy OPContractsManager. We do however inherit from -/// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly -/// to how a real deployment would happen. -contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { +contract OPContractsManager_Init is DeployOPChain_TestBase { using stdStorage for StdStorage; event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); @@ -1727,7 +1719,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // to the input struct type defined in OPContractsManager.sol. function toOPCMDeployInput(DeployOPChainInput _doi) internal - virtual view returns (IOPContractsManager.DeployInput memory) { @@ -1755,48 +1746,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { }); } - function test_deploy_l2ChainIdEqualsZero_reverts() public { - IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); - deployInput.l2ChainId = 0; - vm.expectRevert(IOPContractsManager.InvalidChainId.selector); - opcm.deploy(deployInput); - } - - function test_deploy_l2ChainIdEqualsCurrentChainId_reverts() public { - IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); - deployInput.l2ChainId = block.chainid; - - vm.expectRevert(IOPContractsManager.InvalidChainId.selector); - opcm.deploy(deployInput); - } - - function test_deploy_succeeds() public { - vm.expectEmit(true, true, true, false); // TODO precompute the expected `deployOutput`. - emit Deployed(doi.l2ChainId(), address(this), bytes("")); - opcm.deploy(toOPCMDeployInput(doi)); - } -} - -/// @title OPContractsManager_Version_Test -/// @notice Tests the `version` function of the `OPContractsManager` contract. -contract OPContractsManager_Version_Test is OPContractsManager_TestInit { - IOPContractsManager internal prestateUpdater; - OPContractsManager.AddGameInput[] internal gameInput; - - function setUp() public override { - super.setUp(); - prestateUpdater = opcm; - } - - function test_semver_works() public view { - assertNotEq(abi.encode(prestateUpdater.version()), abi.encode(0)); - } -} - -/// @title OPContractsManager_V2_Test -/// @notice Tests for v2 dispute game implementations in OPContractsManager -contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { - /// @notice Helper function to deploy OPCM with v2 flag enabled function _deployOPCMWithV2Flag() internal returns (IOPContractsManager) { // Deploy Superchain contracts first @@ -1832,12 +1781,43 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { upgradeController: dso.superchainProxyAdmin.owner(), proposer: proposer, challenger: challenger, - devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here - }) + devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here + }) ); return dio.opcm; } +} + +/// @title OPContractsManager_Deploy_Test +/// @notice Tests the `deploy` function of the `OPContractsManager` contract. +/// @dev Unlike other test suites, we intentionally do not inherit from CommonTest or Setup. This +/// is because OPContractsManager acts as a deploy script, so we start from a clean slate here +/// and work OPContractsManager's deployment into the existing test setup, instead of using +/// the existing test setup to deploy OPContractsManager. We do however inherit from +/// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly +/// to how a real deployment would happen. +contract OPContractsManager_Deploy_Test is OPContractsManager_Init { + function test_deploy_l2ChainIdEqualsZero_reverts() public { + IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); + deployInput.l2ChainId = 0; + vm.expectRevert(IOPContractsManager.InvalidChainId.selector); + opcm.deploy(deployInput); + } + + function test_deploy_l2ChainIdEqualsCurrentChainId_reverts() public { + IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); + deployInput.l2ChainId = block.chainid; + + vm.expectRevert(IOPContractsManager.InvalidChainId.selector); + opcm.deploy(deployInput); + } + + function test_deploy_succeeds() public { + vm.expectEmit(true, true, true, false); // TODO precompute the expected `deployOutput`. + emit Deployed(doi.l2ChainId(), address(this), bytes("")); + opcm.deploy(toOPCMDeployInput(doi)); + } /// @notice Test that deploy without v2 flag doesn't set v2 implementations function test_deploy_withoutV2Flag_noV2Implementations() public { @@ -1865,8 +1845,13 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { IOPContractsManager.Implementations memory impls = opcmV2.implementations(); // Verify that v2 implementations are set (non-zero) - assertTrue(address(impls.permissionedDisputeGameV2Impl) != address(0), "PermissionedDisputeGameV2 implementation should be non-zero"); - assertTrue(address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero"); + assertTrue( + address(impls.permissionedDisputeGameV2Impl) != address(0), + "PermissionedDisputeGameV2 implementation should be non-zero" + ); + assertTrue( + address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero" + ); // Set up deploy input for the v2-enabled OPCM doi.set(doi.opcm.selector, address(opcmV2)); @@ -1877,15 +1862,23 @@ contract OPContractsManager_V2_Test is OPContractsManager_Deploy_Test { vm.stopPrank(); // Verify that v2 dispute game contracts are deployed and non-zero - assertTrue(address(output.permissionedDisputeGameV2) != address(0), "PermissionedDisputeGameV2 should be deployed"); + assertTrue( + address(output.permissionedDisputeGameV2) != address(0), "PermissionedDisputeGameV2 should be deployed" + ); assertTrue(address(output.faultDisputeGameV2) != address(0), "FaultDisputeGameV2 should be deployed"); // Verify that v1 permissioned dispute game is still deployed (for backward compatibility) - assertTrue(address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 should still be deployed"); + assertTrue( + address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 should still be deployed" + ); // Verify that the DisputeGameFactory has registered the v2 implementation for PERMISSIONED_CANNON game type - address registeredPermissionedImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); - assertEq(registeredPermissionedImpl, address(output.permissionedDisputeGameV2), - "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON"); + address registeredPermissionedImpl = + address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertEq( + registeredPermissionedImpl, + address(output.permissionedDisputeGameV2), + "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" + ); } -} +} \ No newline at end of file From 78656fd1dc65c284458ddf46d98cd7782f23530c Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 15 Sep 2025 20:06:16 -0400 Subject: [PATCH 10/75] fix: bytecode size check failing --- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 2b95ffbc42ff0..647cc073ff471 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -896,6 +896,12 @@ contract VerifyOPCM is Script { sourceName = sourceNameOverrides[_contractName]; } + // Check if the dispute profile artifact exists and should be used. + string memory disputePath = string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); + if (vm.exists(disputePath)) { + return disputePath; + } + // Return computed path, relative to the contracts-bedrock directory. return string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".json"); } From 3749303f60a91bb8069eefd1796feb1a2d14be3c Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 16 Sep 2025 08:13:56 -0400 Subject: [PATCH 11/75] chore: run forge fmt --- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 3 ++- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 647cc073ff471..e60d97816ec20 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -897,7 +897,8 @@ contract VerifyOPCM is Script { } // Check if the dispute profile artifact exists and should be used. - string memory disputePath = string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); + string memory disputePath = + string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); if (vm.exists(disputePath)) { return disputePath; } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index f79bd42de0223..1eebe94396b28 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1881,4 +1881,4 @@ contract OPContractsManager_Deploy_Test is OPContractsManager_Init { "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" ); } -} \ No newline at end of file +} From ddd42de55732172578184747ba24940cc2c0abbd Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 16 Sep 2025 09:05:44 -0400 Subject: [PATCH 12/75] fix: address CI naming and semver bump --- .../snapshots/abi/OPContractsManager.json | 10 ++++++++++ .../snapshots/abi/OPContractsManagerDeployer.json | 10 ++++++++++ .../contracts-bedrock/test/L1/OPContractsManager.t.sol | 10 ++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index db5c0b7734ded..7af882bfa8638 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -432,6 +432,16 @@ "internalType": "contract IDelayedWETH", "name": "delayedWETHPermissionlessGameProxy", "type": "address" + }, + { + "internalType": "contract IFaultDisputeGameV2", + "name": "faultDisputeGameV2", + "type": "address" + }, + { + "internalType": "contract IPermissionedDisputeGameV2", + "name": "permissionedDisputeGameV2", + "type": "address" } ], "internalType": "struct OPContractsManager.DeployOutput", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index 85e42e8fc7e0f..28e694d5e4f5f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -330,6 +330,16 @@ "internalType": "contract IDelayedWETH", "name": "delayedWETHPermissionlessGameProxy", "type": "address" + }, + { + "internalType": "contract IFaultDisputeGameV2", + "name": "faultDisputeGameV2", + "type": "address" + }, + { + "internalType": "contract IPermissionedDisputeGameV2", + "name": "permissionedDisputeGameV2", + "type": "address" } ], "internalType": "struct OPContractsManager.DeployOutput", diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 1eebe94396b28..57146dbaee274 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -33,8 +33,6 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; @@ -1687,7 +1685,7 @@ contract OPContractsManager_Migrate_CannonKonaEnabled_Test is OPContractsManager } } -contract OPContractsManager_Init is DeployOPChain_TestBase { +contract OPContractsManager_DeployBase is DeployOPChain_TestBase { using stdStorage for StdStorage; event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); @@ -1797,7 +1795,7 @@ contract OPContractsManager_Init is DeployOPChain_TestBase { /// the existing test setup to deploy OPContractsManager. We do however inherit from /// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly /// to how a real deployment would happen. -contract OPContractsManager_Deploy_Test is OPContractsManager_Init { +contract OPContractsManager_Deploy_Test is OPContractsManager_DeployBase { function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; @@ -1820,7 +1818,7 @@ contract OPContractsManager_Deploy_Test is OPContractsManager_Init { } /// @notice Test that deploy without v2 flag doesn't set v2 implementations - function test_deploy_withoutV2Flag_noV2Implementations() public { + function test_deployWithoutV2Flag_succeeds() public { // Convert DOI to OPCM input and deploy IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); vm.startPrank(address(this)); @@ -1837,7 +1835,7 @@ contract OPContractsManager_Deploy_Test is OPContractsManager_Init { } /// @notice Test that deploy with v2 flag would set v2 implementations - function test_deploy_withV2Flag_setsV2Implementations() public { + function test_deployWithV2Flag_succeeds() public { IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); From 015c220e6c7a52757b1cbb2f8dd0092a37243b1e Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 16 Sep 2025 11:35:22 -0400 Subject: [PATCH 13/75] fix: rename setup contract name to align with ci checks --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 57146dbaee274..349cc04699e18 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1685,7 +1685,7 @@ contract OPContractsManager_Migrate_CannonKonaEnabled_Test is OPContractsManager } } -contract OPContractsManager_DeployBase is DeployOPChain_TestBase { +contract OPContractsManager_Deploy_TestInit is DeployOPChain_TestBase { using stdStorage for StdStorage; event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); @@ -1795,7 +1795,7 @@ contract OPContractsManager_DeployBase is DeployOPChain_TestBase { /// the existing test setup to deploy OPContractsManager. We do however inherit from /// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly /// to how a real deployment would happen. -contract OPContractsManager_Deploy_Test is OPContractsManager_DeployBase { +contract OPContractsManager_Deploy_Test is OPContractsManager_Deploy_TestInit { function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; From 71374afc6a6f8947f3fffb7f0e91b3b764a0950a Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Sep 2025 13:17:55 -0400 Subject: [PATCH 14/75] chore: comment out added fix --- .../scripts/deploy/VerifyOPCM.s.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index e60d97816ec20..1d598b933dcc5 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -896,12 +896,12 @@ contract VerifyOPCM is Script { sourceName = sourceNameOverrides[_contractName]; } - // Check if the dispute profile artifact exists and should be used. - string memory disputePath = - string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); - if (vm.exists(disputePath)) { - return disputePath; - } + // // Check if the dispute profile artifact exists and should be used. + // string memory disputePath = + // string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); + // if (vm.exists(disputePath)) { + // return disputePath; + // } // Return computed path, relative to the contracts-bedrock directory. return string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".json"); From d491a4bd043d574294ee99f06307bc02caec7ac6 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Sep 2025 13:50:19 -0400 Subject: [PATCH 15/75] fix: verify opcm --- .../scripts/deploy/VerifyOPCM.s.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 1d598b933dcc5..adcb1fcf3c596 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -113,6 +113,8 @@ contract VerifyOPCM is Script { fieldNameOverrides["optimismPortalInteropImpl"] = "OptimismPortalInterop"; fieldNameOverrides["mipsImpl"] = "MIPS64"; fieldNameOverrides["ethLockboxImpl"] = "ETHLockbox"; + fieldNameOverrides["faultDisputeGameV2Impl"] = "FaultDisputeGameV2"; + fieldNameOverrides["permissionedDisputeGameV2Impl"] = "PermissionedDisputeGameV2"; fieldNameOverrides["permissionlessDisputeGame1"] = "FaultDisputeGame"; fieldNameOverrides["permissionlessDisputeGame2"] = "FaultDisputeGame"; fieldNameOverrides["permissionedDisputeGame1"] = "PermissionedDisputeGame"; @@ -896,12 +898,12 @@ contract VerifyOPCM is Script { sourceName = sourceNameOverrides[_contractName]; } - // // Check if the dispute profile artifact exists and should be used. - // string memory disputePath = - // string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); - // if (vm.exists(disputePath)) { - // return disputePath; - // } + // Check if the dispute profile artifact exists and should be used. + string memory disputePath = + string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); + if (vm.exists(disputePath)) { + return disputePath; + } // Return computed path, relative to the contracts-bedrock directory. return string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".json"); From 2ebc280a15f1b908f900941aeea109047aec78e6 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Sep 2025 14:12:23 -0400 Subject: [PATCH 16/75] fix: naming convention for test contracts --- .../test/L1/OPContractsManager.t.sol | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 349cc04699e18..b67968c928dc5 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1685,7 +1685,15 @@ contract OPContractsManager_Migrate_CannonKonaEnabled_Test is OPContractsManager } } -contract OPContractsManager_Deploy_TestInit is DeployOPChain_TestBase { +/// @title OPContractsManager_Deploy_Test +/// @notice Tests the `deploy` function of the `OPContractsManager` contract. +/// @dev Unlike other test suites, we intentionally do not inherit from CommonTest or Setup. This +/// is because OPContractsManager acts as a deploy script, so we start from a clean slate here +/// and work OPContractsManager's deployment into the existing test setup, instead of using +/// the existing test setup to deploy OPContractsManager. We do however inherit from +/// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly +/// to how a real deployment would happen. +contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { using stdStorage for StdStorage; event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); @@ -1785,17 +1793,7 @@ contract OPContractsManager_Deploy_TestInit is DeployOPChain_TestBase { return dio.opcm; } -} -/// @title OPContractsManager_Deploy_Test -/// @notice Tests the `deploy` function of the `OPContractsManager` contract. -/// @dev Unlike other test suites, we intentionally do not inherit from CommonTest or Setup. This -/// is because OPContractsManager acts as a deploy script, so we start from a clean slate here -/// and work OPContractsManager's deployment into the existing test setup, instead of using -/// the existing test setup to deploy OPContractsManager. We do however inherit from -/// DeployOPChain_TestBase so we can use its setup to deploy the implementations similarly -/// to how a real deployment would happen. -contract OPContractsManager_Deploy_Test is OPContractsManager_Deploy_TestInit { function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; From 67194d8d97c6a7860e95abb6fb50a31dd7ba9f7f Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Sep 2025 14:34:50 -0400 Subject: [PATCH 17/75] fix: move the updates for addGameType to the addGameType pr --- .../src/L1/OPContractsManager.sol | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 7492639dacd6e..132a61638eca1 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -543,32 +543,15 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { ); } - // Deploy the new game type or use v2 implementation - if ( - isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) - && gameConfig.disputeGameType.raw() == GameTypes.CANNON.raw() - && address(getImplementations().faultDisputeGameV2Impl) != address(0) - ) { - // Use v2 FaultDisputeGame implementation - outputs[i].faultDisputeGame = IFaultDisputeGame(getImplementations().faultDisputeGameV2Impl); - } else if ( - isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) - && gameConfig.disputeGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw() - && address(getImplementations().permissionedDisputeGameV2Impl) != address(0) - ) { - // Use v2 PermissionedDisputeGame implementation - outputs[i].faultDisputeGame = IFaultDisputeGame(getImplementations().permissionedDisputeGameV2Impl); - } else { - // Deploy v1 game using blueprint - outputs[i].faultDisputeGame = IFaultDisputeGame( - Blueprint.deployFrom( - blueprint1, - blueprint2, - computeSalt(l2ChainId, gameConfig.saltMixer, gameContractName), - constructorData - ) - ); - } + // Deploy the new game type. + outputs[i].faultDisputeGame = IFaultDisputeGame( + Blueprint.deployFrom( + blueprint1, + blueprint2, + computeSalt(l2ChainId, gameConfig.saltMixer, gameContractName), + constructorData + ) + ); // As a last step, register the new game type with the DisputeGameFactory. If the game // type already exists, then its implementation will be overwritten. From 3b769e000d4277a91612438439d8c594d4e49003 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 24 Sep 2025 14:36:08 -0400 Subject: [PATCH 18/75] fix: proposer removed --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index b67968c928dc5..a57b4a1dcf1d3 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1785,7 +1785,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { protocolVersionsProxy: dso.protocolVersionsProxy, superchainProxyAdmin: dso.superchainProxyAdmin, upgradeController: dso.superchainProxyAdmin.owner(), - proposer: proposer, challenger: challenger, devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here }) From 8734e9d6aad5a2b396c8bbd8ba8bbfabf04af326 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 24 Sep 2025 15:03:33 -0400 Subject: [PATCH 19/75] fix: rename function for clarity in VerifyOPCM script Rename _isV2Implementation to _isV2DisputeGameImplementation for better clarity about what types of contracts this function checks for. --- .../contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 8 ++++---- packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index adcb1fcf3c596..3632546bc928f 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -402,7 +402,7 @@ contract VerifyOPCM is Script { // Skip verification for V2 implementations based on contract name // V2 implementations follow the pattern "*DisputeGameV2" or "*DisputeGameV2Impl" - if (_isV2Implementation(_target.name)) { + if (_isV2DisputeGameImplementation(_target.name)) { console.log(" Status: [SKIP] V2 implementation, not yet deployed"); console.log(string.concat(" Status: [SKIP] Skipping verification for ", _target.name)); return true; @@ -821,10 +821,10 @@ contract VerifyOPCM is Script { return string(fieldBytes); } - /// @notice Checks if a contract name corresponds to a V2 implementation. + /// @notice Checks if a contract name corresponds to a V2 dispute game implementation. /// @param _contractName The contract name to check. - /// @return True if this is a V2 implementation, false otherwise. - function _isV2Implementation(string memory _contractName) internal pure returns (bool) { + /// @return True if this is a V2 dispute game implementation, false otherwise. + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts return ( keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index f40a595250c32..4019098adb265 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -81,7 +81,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { /// @notice Helper function to check if a contract name corresponds to a V2 implementation. /// @param _contractName The contract name to check. /// @return True if this is a V2 implementation, false otherwise. - function _isV2Implementation(string memory _contractName) internal pure returns (bool) { + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts return ( keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) @@ -138,7 +138,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 implementations (not yet deployed) - if (_isV2Implementation(ref.name)) { + if (_isV2DisputeGameImplementation(ref.name)) { continue; } @@ -203,7 +203,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 implementations (not yet deployed) - if (_isV2Implementation(ref.name)) { + if (_isV2DisputeGameImplementation(ref.name)) { continue; } From 1fb7ca5362720135ebc8b78c70df5e74a7bba6de Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 24 Sep 2025 15:52:50 -0400 Subject: [PATCH 20/75] fix: add proper gameArgs for V2 dispute game registration and fix setting output implementation on outupt struct --- .../src/L1/OPContractsManager.sol | 99 +++++++++++-------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 132a61638eca1..441e0a703b0b8 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -364,8 +364,23 @@ abstract contract OPContractsManagerBase { } /// @notice Sets a game implementation on the dispute game factory - function setDGFImplementation(IDisputeGameFactory _dgf, GameType _gameType, IDisputeGame _newGame) internal { - _dgf.setImplementation(_gameType, _newGame); + /// @param _dgf The dispute game factory + /// @param _gameType The game type + /// @param _newGame The new game implementation + /// @param _gameArgs Optional game arguments. If empty, calls without gameArgs + function setDGFImplementation( + IDisputeGameFactory _dgf, + GameType _gameType, + IDisputeGame _newGame, + bytes memory _gameArgs + ) + internal + { + if (_gameArgs.length > 0) { + _dgf.setImplementation(_gameType, _newGame, _gameArgs); + } else { + _dgf.setImplementation(_gameType, _newGame); + } } } @@ -555,7 +570,9 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // As a last step, register the new game type with the DisputeGameFactory. If the game // type already exists, then its implementation will be overwritten. - setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame))); + setDGFImplementation( + dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame)), bytes("") + ); dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond); // Emit event for the newly added game type with the new and old implementations. @@ -942,7 +959,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { IDisputeGameFactory dgf = IDisputeGameFactory(_opChainConfig.systemConfigProxy.disputeGameFactory()); // Set the new implementation. - setDGFImplementation(dgf, _gameType, IDisputeGame(newGame)); + setDGFImplementation(dgf, _gameType, IDisputeGame(newGame), bytes("")); } } @@ -1052,36 +1069,30 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { ) ); - // While not a proxy, we deploy the PermissionedDisputeGame here as well because it's bespoke per chain. - output.permissionedDisputeGame = IPermissionedDisputeGame( - Blueprint.deployFrom( - blueprint.permissionedDisputeGame1, - blueprint.permissionedDisputeGame2, - computeSalt(_input.l2ChainId, _input.saltMixer, "PermissionedDisputeGame"), - encodePermissionedFDGConstructor( - IFaultDisputeGame.GameConstructorParams({ - gameType: GameTypes.PERMISSIONED_CANNON, - absolutePrestate: _input.disputeAbsolutePrestate, - maxGameDepth: _input.disputeMaxGameDepth, - splitDepth: _input.disputeSplitDepth, - clockExtension: _input.disputeClockExtension, - maxClockDuration: _input.disputeMaxClockDuration, - vm: IBigStepper(implementation.mipsImpl), - weth: IDelayedWETH(payable(address(output.delayedWETHPermissionedGameProxy))), - anchorStateRegistry: IAnchorStateRegistry(address(output.anchorStateRegistryProxy)), - l2ChainId: _input.l2ChainId - }), - _input.roles.proposer, - _input.roles.challenger + if (!isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + output.permissionedDisputeGame = IPermissionedDisputeGame( + Blueprint.deployFrom( + blueprint.permissionedDisputeGame1, + blueprint.permissionedDisputeGame2, + computeSalt(_input.l2ChainId, _input.saltMixer, "PermissionedDisputeGame"), + encodePermissionedFDGConstructor( + IFaultDisputeGame.GameConstructorParams({ + gameType: GameTypes.PERMISSIONED_CANNON, + absolutePrestate: _input.disputeAbsolutePrestate, + maxGameDepth: _input.disputeMaxGameDepth, + splitDepth: _input.disputeSplitDepth, + clockExtension: _input.disputeClockExtension, + maxClockDuration: _input.disputeMaxClockDuration, + vm: IBigStepper(implementation.mipsImpl), + weth: IDelayedWETH(payable(address(output.delayedWETHPermissionedGameProxy))), + anchorStateRegistry: IAnchorStateRegistry(address(output.anchorStateRegistryProxy)), + l2ChainId: _input.l2ChainId + }), + _input.roles.proposer, + _input.roles.challenger + ) ) - ) - ); - - // Store v2 implementation addresses if the feature flag is enabled - if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { - // Store v2 implementation addresses for later registration with DisputeGameFactory - output.permissionedDisputeGameV2 = IPermissionedDisputeGameV2(implementation.permissionedDisputeGameV2Impl); - output.faultDisputeGameV2 = IFaultDisputeGameV2(implementation.faultDisputeGameV2Impl); + ); } // -------- Set and Initialize Proxy Implementations -------- @@ -1169,22 +1180,30 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { data ); // Register the appropriate dispute game implementation based on the feature flag - if ( - isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) - && address(output.permissionedDisputeGameV2) != address(0) - ) { - // Register v2 implementation for PERMISSIONED_CANNON game type + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + // Register v2 implementation for PERMISSIONED_CANNON game type with CWIA args + bytes memory gameArgs = abi.encodePacked( + _input.disputeAbsolutePrestate, // 32 bytes + implementation.mipsImpl, // 20 bytes + address(output.anchorStateRegistryProxy), // 20 bytes + address(output.delayedWETHPermissionedGameProxy), // 20 bytes + _input.l2ChainId, // 32 bytes + _input.roles.proposer, // 20 bytes + _input.roles.challenger // 20 bytes + ); setDGFImplementation( output.disputeGameFactoryProxy, GameTypes.PERMISSIONED_CANNON, - IDisputeGame(address(output.permissionedDisputeGameV2)) + IDisputeGame(implementation.permissionedDisputeGameV2Impl), + gameArgs ); } else { // Register v1 implementation for PERMISSIONED_CANNON game type setDGFImplementation( output.disputeGameFactoryProxy, GameTypes.PERMISSIONED_CANNON, - IDisputeGame(address(output.permissionedDisputeGame)) + IDisputeGame(address(output.permissionedDisputeGame)), + bytes("") ); } From cef1fc317a4b1b9b37de606a8f7d178ed7371a9f Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 07:00:42 -0400 Subject: [PATCH 21/75] test: add helper functin for creating game proxies --- .../test/L1/OPContractsManager.t.sol | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index a57b4a1dcf1d3..41a36cd5a5ba8 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1793,6 +1793,58 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { return dio.opcm; } + /// @notice Helper function to create a permissioned game v2 through the factory + function _createPermissionedGameV2( + IDisputeGameFactory factory, + IOPContractsManager.DeployOutput memory output, + address proposer + ) + internal + returns (IFaultDisputeGame) + { + // Check if there's an init bond required for the game type + uint256 initBond = factory.initBonds(GameTypes.PERMISSIONED_CANNON); + + // Fund the proposer if needed + if (initBond > 0) { + vm.deal(proposer, initBond); + } + + // We use vm.startPrank to set both msg.sender and tx.origin to the proposer + vm.startPrank(proposer, proposer); + + IDisputeGame gameProxy = factory.create{ value: initBond }( + GameTypes.PERMISSIONED_CANNON, Claim.wrap(bytes32(uint256(1))), abi.encode(bytes32(uint256(2))) + ); + + vm.stopPrank(); + + return IFaultDisputeGame(address(gameProxy)); + } + + /// @notice Helper function to create a fault dispute game v2 (non-permissioned) through the factory + function _createFaultDisputeGameV2( + IDisputeGameFactory factory, + IOPContractsManager.DeployOutput memory output + ) + internal + returns (IFaultDisputeGame) + { + // Check if there's an init bond required for the game type + uint256 initBond = factory.initBonds(GameTypes.CANNON); + + // Fund the test contract if needed + if (initBond > 0) { + vm.deal(address(this), initBond); + } + + IDisputeGame gameProxy = factory.create{ value: initBond }( + GameTypes.CANNON, Claim.wrap(bytes32(uint256(1))), abi.encode(bytes32(uint256(2))) + ); + + return IFaultDisputeGame(address(gameProxy)); + } + function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; From 503835f8bf47e2dc4e929530966e4887a2081d28 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 10:37:49 -0400 Subject: [PATCH 22/75] test: refactor helper functions for test and remove impl from output struct --- .../src/L1/OPContractsManager.sol | 47 ++++++++++++------- .../test/L1/OPContractsManager.t.sol | 18 +------ 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 441e0a703b0b8..517c6cd9c5a3f 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -311,6 +311,35 @@ abstract contract OPContractsManagerBase { return _disputeGame.challenger(); } + /// @notice Helper function to register permissioned game V2 implementation + /// @dev Extracted to avoid stack too deep error + /// @param _input The deployment input data containing all necessary parameters + /// @param _implementation The implementation addresses struct + /// @param _output The deployment output containing proxy addresses + function _registerPermissionedGameV2( + OPContractsManager.DeployInput calldata _input, + OPContractsManager.Implementations memory _implementation, + OPContractsManager.DeployOutput memory _output + ) + internal + { + bytes memory gameArgs = abi.encodePacked( + _input.disputeAbsolutePrestate, // 32 bytes + _implementation.mipsImpl, // 20 bytes + address(_output.anchorStateRegistryProxy), // 20 bytes + address(_output.delayedWETHPermissionedGameProxy), // 20 bytes + _input.l2ChainId, // 32 bytes + _input.roles.proposer, // 20 bytes + _input.roles.challenger // 20 bytes + ); + setDGFImplementation( + _output.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + IDisputeGame(_implementation.permissionedDisputeGameV2Impl), + gameArgs + ); + } + /// @notice Retrieves the DisputeGameFactory address for a given SystemConfig function getDisputeGameFactory(ISystemConfig _systemConfig) internal view returns (IDisputeGameFactory) { return IDisputeGameFactory(_systemConfig.disputeGameFactory()); @@ -1181,22 +1210,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { ); // Register the appropriate dispute game implementation based on the feature flag if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { - // Register v2 implementation for PERMISSIONED_CANNON game type with CWIA args - bytes memory gameArgs = abi.encodePacked( - _input.disputeAbsolutePrestate, // 32 bytes - implementation.mipsImpl, // 20 bytes - address(output.anchorStateRegistryProxy), // 20 bytes - address(output.delayedWETHPermissionedGameProxy), // 20 bytes - _input.l2ChainId, // 32 bytes - _input.roles.proposer, // 20 bytes - _input.roles.challenger // 20 bytes - ); - setDGFImplementation( - output.disputeGameFactoryProxy, - GameTypes.PERMISSIONED_CANNON, - IDisputeGame(implementation.permissionedDisputeGameV2Impl), - gameArgs - ); + // Extracted to helper function to avoid stack too deep error + _registerPermissionedGameV2(_input, implementation, output); } else { // Register v1 implementation for PERMISSIONED_CANNON game type setDGFImplementation( diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 41a36cd5a5ba8..e318e6b74da4c 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1903,28 +1903,14 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // Set up deploy input for the v2-enabled OPCM doi.set(doi.opcm.selector, address(opcmV2)); IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); - - vm.startPrank(address(this)); IOPContractsManager.DeployOutput memory output = opcmV2.deploy(opcmInput); - vm.stopPrank(); - - // Verify that v2 dispute game contracts are deployed and non-zero - assertTrue( - address(output.permissionedDisputeGameV2) != address(0), "PermissionedDisputeGameV2 should be deployed" - ); - assertTrue(address(output.faultDisputeGameV2) != address(0), "FaultDisputeGameV2 should be deployed"); - - // Verify that v1 permissioned dispute game is still deployed (for backward compatibility) - assertTrue( - address(output.permissionedDisputeGame) != address(0), "PermissionedDisputeGame v1 should still be deployed" - ); // Verify that the DisputeGameFactory has registered the v2 implementation for PERMISSIONED_CANNON game type address registeredPermissionedImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); - assertEq( + assertNotEq( registeredPermissionedImpl, - address(output.permissionedDisputeGameV2), + address(0), "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" ); } From 30ef2f36a9f2ad179bec5aea21cb4d27cb0b44f2 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 11:49:45 -0400 Subject: [PATCH 23/75] test: test args correct on created proxy games --- .../test/L1/OPContractsManager.t.sol | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index e318e6b74da4c..2e06825355750 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1796,11 +1796,10 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { /// @notice Helper function to create a permissioned game v2 through the factory function _createPermissionedGameV2( IDisputeGameFactory factory, - IOPContractsManager.DeployOutput memory output, address proposer ) internal - returns (IFaultDisputeGame) + returns (IPermissionedDisputeGame) { // Check if there's an init bond required for the game type uint256 initBond = factory.initBonds(GameTypes.PERMISSIONED_CANNON); @@ -1819,17 +1818,11 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { vm.stopPrank(); - return IFaultDisputeGame(address(gameProxy)); + return IPermissionedDisputeGame(address(gameProxy)); } /// @notice Helper function to create a fault dispute game v2 (non-permissioned) through the factory - function _createFaultDisputeGameV2( - IDisputeGameFactory factory, - IOPContractsManager.DeployOutput memory output - ) - internal - returns (IFaultDisputeGame) - { + function _createFaultDisputeGameV2(IDisputeGameFactory factory) internal returns (IFaultDisputeGame) { // Check if there's an init bond required for the game type uint256 initBond = factory.initBonds(GameTypes.CANNON); @@ -1866,13 +1859,12 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { opcm.deploy(toOPCMDeployInput(doi)); } - /// @notice Test that deploy without v2 flag doesn't set v2 implementations - function test_deployWithoutV2Flag_succeeds() public { + /// @notice Test that deploy without v2 flag doesn't set v2 implementations for PERMISSIONED_CANNON + function test_deployPermissionedWithoutV2Flag_succeeds() public { // Convert DOI to OPCM input and deploy IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); - vm.startPrank(address(this)); IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); - vm.stopPrank(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); // Check that v2 implementations are not set (since flag is not enabled by default) assertEq(address(output.permissionedDisputeGameV2), address(0)); @@ -1881,10 +1873,38 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // Check that v1 implementation is registered for PERMISSIONED_CANNON address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); assertEq(registeredImpl, address(output.permissionedDisputeGame)); + + address registeredPermissionedImpl = + address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertNotEq( + registeredPermissionedImpl, + address(0), + "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" + ); + assertEq( + registeredPermissionedImpl, address(output.permissionedDisputeGame), "Should be using v2 implementation" + ); + + // Create a game proxy to test immutable fields + IPermissionedDisputeGame gameV2Proxy = + _createPermissionedGameV2(output.disputeGameFactoryProxy, opcmInput.roles.proposer); + + // Verify immutable fields on the game proxy + assertEq(address(gameV2Proxy.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); + assertEq( + address(gameV2Proxy.anchorStateRegistry()), address(output.anchorStateRegistryProxy), "ASR should match" + ); + assertEq(address(gameV2Proxy.weth()), address(output.delayedWETHPermissionedGameProxy), "WETH should match"); + assertEq(gameV2Proxy.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); + + // For permissioned game, check proposer and challenger + IPermissionedDisputeGame permissionedProxy = IPermissionedDisputeGame(address(gameV2Proxy)); + assertEq(permissionedProxy.proposer(), opcmInput.roles.proposer, "Proposer should match"); + assertEq(permissionedProxy.challenger(), opcmInput.roles.challenger, "Challenger should match"); } - /// @notice Test that deploy with v2 flag would set v2 implementations - function test_deployWithV2Flag_succeeds() public { + /// @notice Test that deploy with v2 flag would set v2 implementations for PERMISSIONED_CANNON + function test_deployPermissionedWithV2Flag_succeeds() public { IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); @@ -1913,5 +1933,27 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { address(0), "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" ); + assertEq( + registeredPermissionedImpl, + address(impls.permissionedDisputeGameV2Impl), + "Should be using v2 implementation" + ); + + // Create a game proxy to test immutable fields + IPermissionedDisputeGame gameV2Proxy = + _createPermissionedGameV2(output.disputeGameFactoryProxy, opcmInput.roles.proposer); + + // Verify immutable fields on the game proxy + assertEq(address(gameV2Proxy.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); + assertEq( + address(gameV2Proxy.anchorStateRegistry()), address(output.anchorStateRegistryProxy), "ASR should match" + ); + assertEq(address(gameV2Proxy.weth()), address(output.delayedWETHPermissionedGameProxy), "WETH should match"); + assertEq(gameV2Proxy.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); + + // For permissioned game, check proposer and challenger + IPermissionedDisputeGame permissionedProxy = IPermissionedDisputeGame(address(gameV2Proxy)); + assertEq(permissionedProxy.proposer(), opcmInput.roles.proposer, "Proposer should match"); + assertEq(permissionedProxy.challenger(), opcmInput.roles.challenger, "Challenger should match"); } } From bcbe98f9f9260b3f8700ccbf03d598cefd0af144 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 12:09:28 -0400 Subject: [PATCH 24/75] chore: remove unused helper for now --- .../test/L1/OPContractsManager.t.sol | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 2e06825355750..6283e822bb52a 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1821,23 +1821,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { return IPermissionedDisputeGame(address(gameProxy)); } - /// @notice Helper function to create a fault dispute game v2 (non-permissioned) through the factory - function _createFaultDisputeGameV2(IDisputeGameFactory factory) internal returns (IFaultDisputeGame) { - // Check if there's an init bond required for the game type - uint256 initBond = factory.initBonds(GameTypes.CANNON); - - // Fund the test contract if needed - if (initBond > 0) { - vm.deal(address(this), initBond); - } - - IDisputeGame gameProxy = factory.create{ value: initBond }( - GameTypes.CANNON, Claim.wrap(bytes32(uint256(1))), abi.encode(bytes32(uint256(2))) - ); - - return IFaultDisputeGame(address(gameProxy)); - } - function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; From 78f65b60602606dd06f87a9e660082f9b6a2f4f2 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 16:04:13 -0400 Subject: [PATCH 25/75] fix: remove guard clause from local issue --- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 3632546bc928f..155bd3baa7c15 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -898,13 +898,6 @@ contract VerifyOPCM is Script { sourceName = sourceNameOverrides[_contractName]; } - // Check if the dispute profile artifact exists and should be used. - string memory disputePath = - string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".dispute.json"); - if (vm.exists(disputePath)) { - return disputePath; - } - // Return computed path, relative to the contracts-bedrock directory. return string.concat("forge-artifacts/", sourceName, ".sol/", _contractName, ".json"); } From 0a464cda0a177db0c4f1b755841aba0a77b553f7 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 16:04:21 -0400 Subject: [PATCH 26/75] chore: bump semver --- .../contracts-bedrock/src/L1/OPContractsManager.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 517c6cd9c5a3f..6992480928344 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -719,8 +719,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade - /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in - /// `_opChainConfigs`'s ProxyAdmin. + /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { // Grab the implementations. @@ -888,7 +887,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. - /// @dev This function is intended to be DELEGATECALLed by the superchainConfig's ProxyAdminOwner. + /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { // Only upgrade the superchainConfig if the current version is less than the target version. @@ -2011,8 +2010,7 @@ contract OPContractsManager is ISemver { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade - /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in - /// `_opChainConfigs`'s ProxyAdmin. + /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); @@ -2024,7 +2022,7 @@ contract OPContractsManager is ISemver { /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. - /// @dev This function is intended to be DELEGATECALLed by the superchainConfig's ProxyAdminOwner. + /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); From ac768a5fcfd3dfc2f7ab99f2c4f05646fb408cdd Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 16:17:26 -0400 Subject: [PATCH 27/75] chore: revert changes to ignore v2 implementations --- .../scripts/deploy/VerifyOPCM.s.sol | 19 ---------------- .../test/scripts/VerifyOPCM.t.sol | 22 +++---------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 155bd3baa7c15..acb666bbcb1d9 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -400,14 +400,6 @@ contract VerifyOPCM is Script { console.log(string.concat(" Contract: ", _target.name)); console.log(string.concat(" Address: ", vm.toString(_target.addr))); - // Skip verification for V2 implementations based on contract name - // V2 implementations follow the pattern "*DisputeGameV2" or "*DisputeGameV2Impl" - if (_isV2DisputeGameImplementation(_target.name)) { - console.log(" Status: [SKIP] V2 implementation, not yet deployed"); - console.log(string.concat(" Status: [SKIP] Skipping verification for ", _target.name)); - return true; - } - // Build the expected path to the artifact file. string memory artifactPath = _buildArtifactPath(_target.name); console.log(string.concat(" Expected Runtime Artifact: ", artifactPath)); @@ -821,17 +813,6 @@ contract VerifyOPCM is Script { return string(fieldBytes); } - /// @notice Checks if a contract name corresponds to a V2 dispute game implementation. - /// @param _contractName The contract name to check. - /// @return True if this is a V2 dispute game implementation, false otherwise. - function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { - // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts - return ( - keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) - || keccak256(bytes(_contractName)) == keccak256(bytes("PermissionedDisputeGameV2")) - ); - } - /// @notice Checks if a position is inside an immutable reference. /// @param _pos The position to check. /// @param _artifact The artifact info. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 4019098adb265..7094d573c90cf 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -78,17 +78,6 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { setupEnvVars(); } - /// @notice Helper function to check if a contract name corresponds to a V2 implementation. - /// @param _contractName The contract name to check. - /// @return True if this is a V2 implementation, false otherwise. - function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { - // V2 implementations are specifically the FaultDisputeGameV2 and PermissionedDisputeGameV2 contracts - return ( - keccak256(bytes(_contractName)) == keccak256(bytes("FaultDisputeGameV2")) - || keccak256(bytes(_contractName)) == keccak256(bytes("PermissionedDisputeGameV2")) - ); - } - /// @notice Tests that the script succeeds when no changes are introduced. function test_run_succeeds() public { // Coverage changes bytecode and causes failures, skip. @@ -137,11 +126,6 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { uint256 randomImplIndex = vm.randomUint(0, refs.length - 1); VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; - // Skip V2 implementations (not yet deployed) - if (_isV2DisputeGameImplementation(ref.name)) { - continue; - } - // Get the code for the implementation. bytes memory implCode = ref.addr.code; @@ -203,9 +187,9 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 implementations (not yet deployed) - if (_isV2DisputeGameImplementation(ref.name)) { - continue; - } + // if (_isV2DisputeGameImplementation(ref.name)) { + // continue; + // } // Get the code for the implementation. bytes memory implCode = ref.addr.code; From 5fda712204c8c743098ca690451eecb49ae2c4e4 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 25 Sep 2025 21:24:03 -0400 Subject: [PATCH 28/75] fix: check bitmap and contract name for ref --- .../scripts/deploy/VerifyOPCM.s.sol | 41 +++++++++++++++++++ .../test/scripts/VerifyOPCM.t.sol | 26 +++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index acb666bbcb1d9..a90e5da547e8d 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -12,6 +12,7 @@ import { LibString } from "@solady/utils/LibString.sol"; import { Process } from "scripts/libraries/Process.sol"; import { Config } from "scripts/libraries/Config.sol"; import { Bytes } from "src/libraries/Bytes.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; @@ -106,6 +107,8 @@ contract VerifyOPCM is Script { /// @notice Setup flag. bool internal ready; + /// @notice The OPCM address being verified, stored to access during contract verification. + address internal currentOpcmAddress; /// @notice Populates override mappings. function setUp() public { // Overrides for situations where field names do not cleanly map to contract names. @@ -201,6 +204,9 @@ contract VerifyOPCM is Script { console.log(" Do NOT do this in production"); } + // Store OPCM address for use in verification functions + currentOpcmAddress = _opcmAddress; + // Fetch Implementations & Blueprints from OPCM IOPContractsManager opcm = IOPContractsManager(_opcmAddress); @@ -396,6 +402,25 @@ contract VerifyOPCM is Script { { console.log(); console.log(string.concat("Checking Contract: ", _target.field)); + // Check if this is a V2 dispute game that should be skipped + if (_isV2DisputeGameImplementation(_target.name)) { + IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); + + if (!_isV2DisputeGamesEnabled(opcm)) { + if (_target.addr == address(0)) { + console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); + console.log(string.concat(" Contract: ", _target.name)); + return true; // Consider this "verified" when feature is off + } else { + console.log(" [FAIL] V2 dispute game deployed but feature disabled"); + console.log(string.concat(" Contract: ", _target.name)); + console.log(string.concat(" Address: ", vm.toString(_target.addr))); + return false; + } + } + // If feature is enabled, continue with normal verification + } + console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); console.log(string.concat(" Contract: ", _target.name)); console.log(string.concat(" Address: ", vm.toString(_target.addr))); @@ -500,6 +525,22 @@ contract VerifyOPCM is Script { return success; } + /// @notice Checks if V2 dispute games feature is enabled in the dev feature bitmap. + /// @param _opcm The OPContractsManager to check. + /// @return True if V2 dispute games are enabled. + function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { + bytes32 bitmap = _opcm.devFeatureBitmap(); + return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + + /// @notice Checks if a contract is a V2 dispute game implementation. + /// @param _contractName The name to check. + /// @return True if this is a V2 dispute game. + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { + return LibString.eq(_contractName, "FaultDisputeGameV2") || + LibString.eq(_contractName, "PermissionedDisputeGameV2"); + } + /// @notice Verifies that the immutable variables in the OPCM contract match expected values. /// @param _opcm The OPCM contract to verify immutable variables for. /// @return True if all immutable variables are verified, false otherwise. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 7094d573c90cf..7856a07bccee0 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Libraries import { LibString } from "@solady/utils/LibString.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Tests import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; @@ -120,12 +121,22 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { // Grab the list of implementations. VerifyOPCM.OpcmContractRef[] memory refs = harness.getOpcmContractRefs(opcm, "implementations", false); + // Check if V2 dispute games feature is enabled + bytes32 bitmap = opcm.devFeatureBitmap(); + bool v2FeatureEnabled = DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + // Change 256 bytes at random. - for (uint8 i = 0; i < 255; i++) { + for (uint256 i = 0; i < 255; i++) { // Pick a random implementation to change. uint256 randomImplIndex = vm.randomUint(0, refs.length - 1); VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; + // Skip V2 dispute games when feature disabled + bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + if (isV2DisputeGame && !v2FeatureEnabled) { + continue; + } + // Get the code for the implementation. bytes memory implCode = ref.addr.code; @@ -180,16 +191,21 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { // Grab the list of implementations. VerifyOPCM.OpcmContractRef[] memory refs = harness.getOpcmContractRefs(opcm, "implementations", false); + // Check if V2 dispute games feature is enabled + bytes32 bitmap = opcm.devFeatureBitmap(); + bool v2FeatureEnabled = DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + // Change 256 bytes at random. for (uint8 i = 0; i < 255; i++) { // Pick a random implementation to change. uint256 randomImplIndex = vm.randomUint(0, refs.length - 1); VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; - // Skip V2 implementations (not yet deployed) - // if (_isV2DisputeGameImplementation(ref.name)) { - // continue; - // } + // Skip V2 dispute games when feature disabled + bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + if (isV2DisputeGame && !v2FeatureEnabled) { + continue; + } // Get the code for the implementation. bytes memory implCode = ref.addr.code; From d6881869db29c9758c9f604d16e8962a592a8cb6 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 26 Sep 2025 05:32:31 -0400 Subject: [PATCH 29/75] test: with feature toggle on --- .../scripts/deploy/VerifyOPCM.s.sol | 71 ++++----- .../test/scripts/VerifyOPCM.t.sol | 145 +++++++++++++++++- 2 files changed, 179 insertions(+), 37 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index a90e5da547e8d..371eb0118d358 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -107,9 +107,10 @@ contract VerifyOPCM is Script { /// @notice Setup flag. bool internal ready; - /// @notice The OPCM address being verified, stored to access during contract verification. - address internal currentOpcmAddress; + /// @notice The OPCM address being verified, stored to access during contract verification. + address internal currentOpcmAddress; /// @notice Populates override mappings. + function setUp() public { // Overrides for situations where field names do not cleanly map to contract names. fieldNameOverrides["optimismPortalImpl"] = "OptimismPortal2"; @@ -205,7 +206,7 @@ contract VerifyOPCM is Script { } // Store OPCM address for use in verification functions - currentOpcmAddress = _opcmAddress; + currentOpcmAddress = _opcmAddress; // Fetch Implementations & Blueprints from OPCM IOPContractsManager opcm = IOPContractsManager(_opcmAddress); @@ -402,24 +403,24 @@ contract VerifyOPCM is Script { { console.log(); console.log(string.concat("Checking Contract: ", _target.field)); - // Check if this is a V2 dispute game that should be skipped - if (_isV2DisputeGameImplementation(_target.name)) { - IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); - - if (!_isV2DisputeGamesEnabled(opcm)) { - if (_target.addr == address(0)) { - console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); - console.log(string.concat(" Contract: ", _target.name)); - return true; // Consider this "verified" when feature is off - } else { - console.log(" [FAIL] V2 dispute game deployed but feature disabled"); - console.log(string.concat(" Contract: ", _target.name)); - console.log(string.concat(" Address: ", vm.toString(_target.addr))); - return false; - } - } - // If feature is enabled, continue with normal verification - } + // Check if this is a V2 dispute game that should be skipped + if (_isV2DisputeGameImplementation(_target.name)) { + IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); + + if (!_isV2DisputeGamesEnabled(opcm)) { + if (_target.addr == address(0)) { + console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); + console.log(string.concat(" Contract: ", _target.name)); + return true; // Consider this "verified" when feature is off + } else { + console.log(" [FAIL] V2 dispute game deployed but feature disabled"); + console.log(string.concat(" Contract: ", _target.name)); + console.log(string.concat(" Address: ", vm.toString(_target.addr))); + return false; + } + } + // If feature is enabled, continue with normal verification + } console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); console.log(string.concat(" Contract: ", _target.name)); @@ -526,20 +527,20 @@ contract VerifyOPCM is Script { } /// @notice Checks if V2 dispute games feature is enabled in the dev feature bitmap. - /// @param _opcm The OPContractsManager to check. - /// @return True if V2 dispute games are enabled. - function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { - bytes32 bitmap = _opcm.devFeatureBitmap(); - return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - } - - /// @notice Checks if a contract is a V2 dispute game implementation. - /// @param _contractName The name to check. - /// @return True if this is a V2 dispute game. - function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { - return LibString.eq(_contractName, "FaultDisputeGameV2") || - LibString.eq(_contractName, "PermissionedDisputeGameV2"); - } + /// @param _opcm The OPContractsManager to check. + /// @return True if V2 dispute games are enabled. + function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { + bytes32 bitmap = _opcm.devFeatureBitmap(); + return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + + /// @notice Checks if a contract is a V2 dispute game implementation. + /// @param _contractName The name to check. + /// @return True if this is a V2 dispute game. + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { + return LibString.eq(_contractName, "FaultDisputeGameV2") + || LibString.eq(_contractName, "PermissionedDisputeGameV2"); + } /// @notice Verifies that the immutable variables in the OPCM contract match expected values. /// @param _opcm The OPCM contract to verify immutable variables for. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 7856a07bccee0..55e0930163ffe 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -4,12 +4,16 @@ pragma solidity 0.8.15; // Libraries import { LibString } from "@solady/utils/LibString.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; // Tests import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; // Scripts import { VerifyOPCM } from "scripts/deploy/VerifyOPCM.s.sol"; +import { DeploySuperchain } from "scripts/deploy/DeploySuperchain.s.sol"; +import { DeployImplementations } from "scripts/deploy/DeployImplementations.s.sol"; +import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; // Interfaces import { IOPContractsManager, IOPContractsManagerUpgrader } from "interfaces/L1/IOPContractsManager.sol"; @@ -69,6 +73,48 @@ contract VerifyOPCM_TestInit is OPContractsManager_TestInit { harness = new VerifyOPCM_Harness(); harness.setUp(); } + + /// @notice Deploys a new OPCM with V2 dispute games feature enabled. + /// @return The deployed OPCM with V2 contracts. + function deployOPCMWithV2Enabled() internal returns (IOPContractsManager) { + // Deploy Superchain contracts first + DeploySuperchain deploySuperchain = new DeploySuperchain(); + DeploySuperchain.Output memory dso = deploySuperchain.run( + DeploySuperchain.Input({ + superchainProxyAdminOwner: makeAddr("superchainProxyAdminOwner"), + protocolVersionsOwner: makeAddr("protocolVersionsOwner"), + guardian: makeAddr("guardian"), + paused: false, + requiredProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(1))), + recommendedProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(2))) + }) + ); + + // Deploy implementations with v2 flag enabled + DeployImplementations deployImplementations = new DeployImplementations(); + DeployImplementations.Output memory dio = deployImplementations.run( + DeployImplementations.Input({ + withdrawalDelaySeconds: 100, + minProposalSizeBytes: 200, + challengePeriodSeconds: 300, + proofMaturityDelaySeconds: 400, + disputeGameFinalityDelaySeconds: 500, + mipsVersion: StandardConstants.MIPS_VERSION, + faultGameV2MaxGameDepth: 73, + faultGameV2SplitDepth: 30, + faultGameV2ClockExtension: 10800, + faultGameV2MaxClockDuration: 302400, + superchainConfigProxy: dso.superchainConfigProxy, + protocolVersionsProxy: dso.protocolVersionsProxy, + superchainProxyAdmin: dso.superchainProxyAdmin, + upgradeController: dso.superchainProxyAdmin.owner(), + challenger: makeAddr("challenger"), + devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here + }) + ); + + return dio.opcm; + } } /// @title VerifyOPCM_Run_Test @@ -132,7 +178,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = + LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } @@ -202,7 +249,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = + LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } @@ -476,4 +524,97 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { vm.expectRevert(abi.encodeWithSelector(VerifyOPCM.VerifyOPCM_UnaccountedGetters.selector, expectedUnaccounted)); harness.validateAllGettersAccounted(); } + + /// @notice Tests that the script succeeds when V2 dispute games are deployed (feature enabled). + /// @dev This test will fail with ProtocolVersions bytecode mismatch - this is a known issue. + function test_run_withV2DisputeGamesEnabled_succeeds() public { + // Coverage changes bytecode and causes failures, skip. + skipIfCoverage(); + + // Deploy OPCM with V2 feature enabled + IOPContractsManager opcmV2 = deployOPCMWithV2Enabled(); + + // Verify that V2 dispute games feature is enabled + assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); + + // Verify that V2 contracts are deployed (not address(0)) + IOPContractsManager.Implementations memory impls = opcmV2.implementations(); + assertTrue( + address(impls.faultDisputeGameV2Impl) != address(0), + "FaultDisputeGameV2 implementation should be non-zero" + ); + assertTrue( + address(impls.permissionedDisputeGameV2Impl) != address(0), + "PermissionedDisputeGameV2 implementation should be non-zero" + ); + + // Update environment variables for the new OPCM deployment + vm.setEnv("EXPECTED_SUPERCHAIN_CONFIG", vm.toString(address(opcmV2.superchainConfig()))); + vm.setEnv("EXPECTED_PROTOCOL_VERSIONS", vm.toString(address(opcmV2.protocolVersions()))); + vm.setEnv("EXPECTED_SUPERCHAIN_PROXY_ADMIN", vm.toString(address(opcmV2.superchainProxyAdmin()))); + vm.setEnv("EXPECTED_UPGRADE_CONTROLLER", vm.toString(opcmV2.upgradeController())); + + // Run verification - should succeed WITH V2 contracts properly verified + harness.run(address(opcmV2), true); + } + + /// @notice Tests that the script reverts when V2 contracts have non-immutable modifications. + function test_run_v2EnabledNonImmutableModifications_reverts() public { + // Coverage changes bytecode and causes failures, skip. + skipIfCoverage(); + + // Deploy OPCM with V2 feature enabled + IOPContractsManager opcmV2 = deployOPCMWithV2Enabled(); + + // Update environment variables for the new OPCM deployment + vm.setEnv("EXPECTED_SUPERCHAIN_CONFIG", vm.toString(address(opcmV2.superchainConfig()))); + vm.setEnv("EXPECTED_PROTOCOL_VERSIONS", vm.toString(address(opcmV2.protocolVersions()))); + vm.setEnv("EXPECTED_SUPERCHAIN_PROXY_ADMIN", vm.toString(address(opcmV2.superchainProxyAdmin()))); + vm.setEnv("EXPECTED_UPGRADE_CONTROLLER", vm.toString(opcmV2.upgradeController())); + + // Grab the list of implementations including V2 + VerifyOPCM.OpcmContractRef[] memory refs = harness.getOpcmContractRefs(opcmV2, "implementations", false); + + // Find and modify a V2 contract outside immutable references + for (uint256 i = 0; i < refs.length; i++) { + if (LibString.eq(refs[i].name, "FaultDisputeGameV2")) { + // Get the code for the implementation + bytes memory implCode = refs[i].addr.code; + + // Grab the artifact info for the implementation + VerifyOPCM.ArtifactInfo memory artifact = harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); + + // Find a byte that's NOT in an immutable reference + bool inImmutable = true; + uint256 modifyPos = 0; + while (inImmutable && modifyPos < implCode.length) { + inImmutable = false; + for (uint256 j = 0; j < artifact.immutableRefs.length; j++) { + VerifyOPCM.ImmutableRef memory immRef = artifact.immutableRefs[j]; + if (modifyPos >= immRef.offset && modifyPos < immRef.offset + immRef.length) { + inImmutable = true; + break; + } + } + if (inImmutable) { + modifyPos++; + } + } + + // Modify the byte outside immutable references + if (modifyPos < implCode.length) { + bytes1 existingByte = implCode[modifyPos]; + bytes1 newByte = bytes1(uint8(uint256(uint8(existingByte)) + 1)); + implCode[modifyPos] = newByte; + vm.etch(refs[i].addr, implCode); + } + + break; // Only modify one V2 contract for this test + } + } + + // Run verification - should revert due to non-immutable modification + vm.expectRevert(VerifyOPCM.VerifyOPCM_Failed.selector); + harness.run(address(opcmV2), true); + } } From 5a585a34aec09c260726c980e08dea5eab0eebac Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 26 Sep 2025 06:00:03 -0400 Subject: [PATCH 30/75] chore: forge fmt --- packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 55e0930163ffe..0ce0986489057 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -110,7 +110,7 @@ contract VerifyOPCM_TestInit is OPContractsManager_TestInit { upgradeController: dso.superchainProxyAdmin.owner(), challenger: makeAddr("challenger"), devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here - }) + }) ); return dio.opcm; @@ -540,8 +540,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { // Verify that V2 contracts are deployed (not address(0)) IOPContractsManager.Implementations memory impls = opcmV2.implementations(); assertTrue( - address(impls.faultDisputeGameV2Impl) != address(0), - "FaultDisputeGameV2 implementation should be non-zero" + address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero" ); assertTrue( address(impls.permissionedDisputeGameV2Impl) != address(0), @@ -582,7 +581,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { bytes memory implCode = refs[i].addr.code; // Grab the artifact info for the implementation - VerifyOPCM.ArtifactInfo memory artifact = harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); + VerifyOPCM.ArtifactInfo memory artifact = + harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); // Find a byte that's NOT in an immutable reference bool inImmutable = true; From 3c4c5290b6580a8ea737604e9ce0a45f08597992 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 26 Sep 2025 06:30:59 -0400 Subject: [PATCH 31/75] Revert "chore: forge fmt" This reverts commit d029fdb8dd12445a839026972e3859be7aae1470. --- packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 0ce0986489057..55e0930163ffe 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -110,7 +110,7 @@ contract VerifyOPCM_TestInit is OPContractsManager_TestInit { upgradeController: dso.superchainProxyAdmin.owner(), challenger: makeAddr("challenger"), devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here - }) + }) ); return dio.opcm; @@ -540,7 +540,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { // Verify that V2 contracts are deployed (not address(0)) IOPContractsManager.Implementations memory impls = opcmV2.implementations(); assertTrue( - address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero" + address(impls.faultDisputeGameV2Impl) != address(0), + "FaultDisputeGameV2 implementation should be non-zero" ); assertTrue( address(impls.permissionedDisputeGameV2Impl) != address(0), @@ -581,8 +582,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { bytes memory implCode = refs[i].addr.code; // Grab the artifact info for the implementation - VerifyOPCM.ArtifactInfo memory artifact = - harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); + VerifyOPCM.ArtifactInfo memory artifact = harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); // Find a byte that's NOT in an immutable reference bool inImmutable = true; From e6e7888796943e9681a0a515b209a7e85ef28102 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 26 Sep 2025 06:31:10 -0400 Subject: [PATCH 32/75] Revert "test: with feature toggle on" This reverts commit a932eb608d25f9acf486ce49aeb5318742c666de. --- .../scripts/deploy/VerifyOPCM.s.sol | 71 +++++---- .../test/scripts/VerifyOPCM.t.sol | 145 +----------------- 2 files changed, 37 insertions(+), 179 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 371eb0118d358..a90e5da547e8d 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -107,10 +107,9 @@ contract VerifyOPCM is Script { /// @notice Setup flag. bool internal ready; - /// @notice The OPCM address being verified, stored to access during contract verification. - address internal currentOpcmAddress; + /// @notice The OPCM address being verified, stored to access during contract verification. + address internal currentOpcmAddress; /// @notice Populates override mappings. - function setUp() public { // Overrides for situations where field names do not cleanly map to contract names. fieldNameOverrides["optimismPortalImpl"] = "OptimismPortal2"; @@ -206,7 +205,7 @@ contract VerifyOPCM is Script { } // Store OPCM address for use in verification functions - currentOpcmAddress = _opcmAddress; + currentOpcmAddress = _opcmAddress; // Fetch Implementations & Blueprints from OPCM IOPContractsManager opcm = IOPContractsManager(_opcmAddress); @@ -403,24 +402,24 @@ contract VerifyOPCM is Script { { console.log(); console.log(string.concat("Checking Contract: ", _target.field)); - // Check if this is a V2 dispute game that should be skipped - if (_isV2DisputeGameImplementation(_target.name)) { - IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); - - if (!_isV2DisputeGamesEnabled(opcm)) { - if (_target.addr == address(0)) { - console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); - console.log(string.concat(" Contract: ", _target.name)); - return true; // Consider this "verified" when feature is off - } else { - console.log(" [FAIL] V2 dispute game deployed but feature disabled"); - console.log(string.concat(" Contract: ", _target.name)); - console.log(string.concat(" Address: ", vm.toString(_target.addr))); - return false; - } - } - // If feature is enabled, continue with normal verification - } + // Check if this is a V2 dispute game that should be skipped + if (_isV2DisputeGameImplementation(_target.name)) { + IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); + + if (!_isV2DisputeGamesEnabled(opcm)) { + if (_target.addr == address(0)) { + console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); + console.log(string.concat(" Contract: ", _target.name)); + return true; // Consider this "verified" when feature is off + } else { + console.log(" [FAIL] V2 dispute game deployed but feature disabled"); + console.log(string.concat(" Contract: ", _target.name)); + console.log(string.concat(" Address: ", vm.toString(_target.addr))); + return false; + } + } + // If feature is enabled, continue with normal verification + } console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); console.log(string.concat(" Contract: ", _target.name)); @@ -527,20 +526,20 @@ contract VerifyOPCM is Script { } /// @notice Checks if V2 dispute games feature is enabled in the dev feature bitmap. - /// @param _opcm The OPContractsManager to check. - /// @return True if V2 dispute games are enabled. - function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { - bytes32 bitmap = _opcm.devFeatureBitmap(); - return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - } - - /// @notice Checks if a contract is a V2 dispute game implementation. - /// @param _contractName The name to check. - /// @return True if this is a V2 dispute game. - function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { - return LibString.eq(_contractName, "FaultDisputeGameV2") - || LibString.eq(_contractName, "PermissionedDisputeGameV2"); - } + /// @param _opcm The OPContractsManager to check. + /// @return True if V2 dispute games are enabled. + function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { + bytes32 bitmap = _opcm.devFeatureBitmap(); + return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + + /// @notice Checks if a contract is a V2 dispute game implementation. + /// @param _contractName The name to check. + /// @return True if this is a V2 dispute game. + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { + return LibString.eq(_contractName, "FaultDisputeGameV2") || + LibString.eq(_contractName, "PermissionedDisputeGameV2"); + } /// @notice Verifies that the immutable variables in the OPCM contract match expected values. /// @param _opcm The OPCM contract to verify immutable variables for. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 55e0930163ffe..7856a07bccee0 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -4,16 +4,12 @@ pragma solidity 0.8.15; // Libraries import { LibString } from "@solady/utils/LibString.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; -import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; // Tests import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; // Scripts import { VerifyOPCM } from "scripts/deploy/VerifyOPCM.s.sol"; -import { DeploySuperchain } from "scripts/deploy/DeploySuperchain.s.sol"; -import { DeployImplementations } from "scripts/deploy/DeployImplementations.s.sol"; -import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; // Interfaces import { IOPContractsManager, IOPContractsManagerUpgrader } from "interfaces/L1/IOPContractsManager.sol"; @@ -73,48 +69,6 @@ contract VerifyOPCM_TestInit is OPContractsManager_TestInit { harness = new VerifyOPCM_Harness(); harness.setUp(); } - - /// @notice Deploys a new OPCM with V2 dispute games feature enabled. - /// @return The deployed OPCM with V2 contracts. - function deployOPCMWithV2Enabled() internal returns (IOPContractsManager) { - // Deploy Superchain contracts first - DeploySuperchain deploySuperchain = new DeploySuperchain(); - DeploySuperchain.Output memory dso = deploySuperchain.run( - DeploySuperchain.Input({ - superchainProxyAdminOwner: makeAddr("superchainProxyAdminOwner"), - protocolVersionsOwner: makeAddr("protocolVersionsOwner"), - guardian: makeAddr("guardian"), - paused: false, - requiredProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(1))), - recommendedProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(2))) - }) - ); - - // Deploy implementations with v2 flag enabled - DeployImplementations deployImplementations = new DeployImplementations(); - DeployImplementations.Output memory dio = deployImplementations.run( - DeployImplementations.Input({ - withdrawalDelaySeconds: 100, - minProposalSizeBytes: 200, - challengePeriodSeconds: 300, - proofMaturityDelaySeconds: 400, - disputeGameFinalityDelaySeconds: 500, - mipsVersion: StandardConstants.MIPS_VERSION, - faultGameV2MaxGameDepth: 73, - faultGameV2SplitDepth: 30, - faultGameV2ClockExtension: 10800, - faultGameV2MaxClockDuration: 302400, - superchainConfigProxy: dso.superchainConfigProxy, - protocolVersionsProxy: dso.protocolVersionsProxy, - superchainProxyAdmin: dso.superchainProxyAdmin, - upgradeController: dso.superchainProxyAdmin.owner(), - challenger: makeAddr("challenger"), - devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here - }) - ); - - return dio.opcm; - } } /// @title VerifyOPCM_Run_Test @@ -178,8 +132,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = - LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } @@ -249,8 +202,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = - LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } @@ -524,97 +476,4 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { vm.expectRevert(abi.encodeWithSelector(VerifyOPCM.VerifyOPCM_UnaccountedGetters.selector, expectedUnaccounted)); harness.validateAllGettersAccounted(); } - - /// @notice Tests that the script succeeds when V2 dispute games are deployed (feature enabled). - /// @dev This test will fail with ProtocolVersions bytecode mismatch - this is a known issue. - function test_run_withV2DisputeGamesEnabled_succeeds() public { - // Coverage changes bytecode and causes failures, skip. - skipIfCoverage(); - - // Deploy OPCM with V2 feature enabled - IOPContractsManager opcmV2 = deployOPCMWithV2Enabled(); - - // Verify that V2 dispute games feature is enabled - assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); - - // Verify that V2 contracts are deployed (not address(0)) - IOPContractsManager.Implementations memory impls = opcmV2.implementations(); - assertTrue( - address(impls.faultDisputeGameV2Impl) != address(0), - "FaultDisputeGameV2 implementation should be non-zero" - ); - assertTrue( - address(impls.permissionedDisputeGameV2Impl) != address(0), - "PermissionedDisputeGameV2 implementation should be non-zero" - ); - - // Update environment variables for the new OPCM deployment - vm.setEnv("EXPECTED_SUPERCHAIN_CONFIG", vm.toString(address(opcmV2.superchainConfig()))); - vm.setEnv("EXPECTED_PROTOCOL_VERSIONS", vm.toString(address(opcmV2.protocolVersions()))); - vm.setEnv("EXPECTED_SUPERCHAIN_PROXY_ADMIN", vm.toString(address(opcmV2.superchainProxyAdmin()))); - vm.setEnv("EXPECTED_UPGRADE_CONTROLLER", vm.toString(opcmV2.upgradeController())); - - // Run verification - should succeed WITH V2 contracts properly verified - harness.run(address(opcmV2), true); - } - - /// @notice Tests that the script reverts when V2 contracts have non-immutable modifications. - function test_run_v2EnabledNonImmutableModifications_reverts() public { - // Coverage changes bytecode and causes failures, skip. - skipIfCoverage(); - - // Deploy OPCM with V2 feature enabled - IOPContractsManager opcmV2 = deployOPCMWithV2Enabled(); - - // Update environment variables for the new OPCM deployment - vm.setEnv("EXPECTED_SUPERCHAIN_CONFIG", vm.toString(address(opcmV2.superchainConfig()))); - vm.setEnv("EXPECTED_PROTOCOL_VERSIONS", vm.toString(address(opcmV2.protocolVersions()))); - vm.setEnv("EXPECTED_SUPERCHAIN_PROXY_ADMIN", vm.toString(address(opcmV2.superchainProxyAdmin()))); - vm.setEnv("EXPECTED_UPGRADE_CONTROLLER", vm.toString(opcmV2.upgradeController())); - - // Grab the list of implementations including V2 - VerifyOPCM.OpcmContractRef[] memory refs = harness.getOpcmContractRefs(opcmV2, "implementations", false); - - // Find and modify a V2 contract outside immutable references - for (uint256 i = 0; i < refs.length; i++) { - if (LibString.eq(refs[i].name, "FaultDisputeGameV2")) { - // Get the code for the implementation - bytes memory implCode = refs[i].addr.code; - - // Grab the artifact info for the implementation - VerifyOPCM.ArtifactInfo memory artifact = harness.loadArtifactInfo(harness.buildArtifactPath(refs[i].name)); - - // Find a byte that's NOT in an immutable reference - bool inImmutable = true; - uint256 modifyPos = 0; - while (inImmutable && modifyPos < implCode.length) { - inImmutable = false; - for (uint256 j = 0; j < artifact.immutableRefs.length; j++) { - VerifyOPCM.ImmutableRef memory immRef = artifact.immutableRefs[j]; - if (modifyPos >= immRef.offset && modifyPos < immRef.offset + immRef.length) { - inImmutable = true; - break; - } - } - if (inImmutable) { - modifyPos++; - } - } - - // Modify the byte outside immutable references - if (modifyPos < implCode.length) { - bytes1 existingByte = implCode[modifyPos]; - bytes1 newByte = bytes1(uint8(uint256(uint8(existingByte)) + 1)); - implCode[modifyPos] = newByte; - vm.etch(refs[i].addr, implCode); - } - - break; // Only modify one V2 contract for this test - } - } - - // Run verification - should revert due to non-immutable modification - vm.expectRevert(VerifyOPCM.VerifyOPCM_Failed.selector); - harness.run(address(opcmV2), true); - } } From 3bd2507e9c09dec9f6af2f04d35d7993759500b8 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 26 Sep 2025 06:31:50 -0400 Subject: [PATCH 33/75] chore: forge fmt --- .../scripts/deploy/VerifyOPCM.s.sol | 71 ++++++++++--------- .../test/scripts/VerifyOPCM.t.sol | 6 +- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index a90e5da547e8d..371eb0118d358 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -107,9 +107,10 @@ contract VerifyOPCM is Script { /// @notice Setup flag. bool internal ready; - /// @notice The OPCM address being verified, stored to access during contract verification. - address internal currentOpcmAddress; + /// @notice The OPCM address being verified, stored to access during contract verification. + address internal currentOpcmAddress; /// @notice Populates override mappings. + function setUp() public { // Overrides for situations where field names do not cleanly map to contract names. fieldNameOverrides["optimismPortalImpl"] = "OptimismPortal2"; @@ -205,7 +206,7 @@ contract VerifyOPCM is Script { } // Store OPCM address for use in verification functions - currentOpcmAddress = _opcmAddress; + currentOpcmAddress = _opcmAddress; // Fetch Implementations & Blueprints from OPCM IOPContractsManager opcm = IOPContractsManager(_opcmAddress); @@ -402,24 +403,24 @@ contract VerifyOPCM is Script { { console.log(); console.log(string.concat("Checking Contract: ", _target.field)); - // Check if this is a V2 dispute game that should be skipped - if (_isV2DisputeGameImplementation(_target.name)) { - IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); - - if (!_isV2DisputeGamesEnabled(opcm)) { - if (_target.addr == address(0)) { - console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); - console.log(string.concat(" Contract: ", _target.name)); - return true; // Consider this "verified" when feature is off - } else { - console.log(" [FAIL] V2 dispute game deployed but feature disabled"); - console.log(string.concat(" Contract: ", _target.name)); - console.log(string.concat(" Address: ", vm.toString(_target.addr))); - return false; - } - } - // If feature is enabled, continue with normal verification - } + // Check if this is a V2 dispute game that should be skipped + if (_isV2DisputeGameImplementation(_target.name)) { + IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); + + if (!_isV2DisputeGamesEnabled(opcm)) { + if (_target.addr == address(0)) { + console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); + console.log(string.concat(" Contract: ", _target.name)); + return true; // Consider this "verified" when feature is off + } else { + console.log(" [FAIL] V2 dispute game deployed but feature disabled"); + console.log(string.concat(" Contract: ", _target.name)); + console.log(string.concat(" Address: ", vm.toString(_target.addr))); + return false; + } + } + // If feature is enabled, continue with normal verification + } console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); console.log(string.concat(" Contract: ", _target.name)); @@ -526,20 +527,20 @@ contract VerifyOPCM is Script { } /// @notice Checks if V2 dispute games feature is enabled in the dev feature bitmap. - /// @param _opcm The OPContractsManager to check. - /// @return True if V2 dispute games are enabled. - function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { - bytes32 bitmap = _opcm.devFeatureBitmap(); - return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - } - - /// @notice Checks if a contract is a V2 dispute game implementation. - /// @param _contractName The name to check. - /// @return True if this is a V2 dispute game. - function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { - return LibString.eq(_contractName, "FaultDisputeGameV2") || - LibString.eq(_contractName, "PermissionedDisputeGameV2"); - } + /// @param _opcm The OPContractsManager to check. + /// @return True if V2 dispute games are enabled. + function _isV2DisputeGamesEnabled(IOPContractsManager _opcm) internal view returns (bool) { + bytes32 bitmap = _opcm.devFeatureBitmap(); + return DevFeatures.isDevFeatureEnabled(bitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + + /// @notice Checks if a contract is a V2 dispute game implementation. + /// @param _contractName The name to check. + /// @return True if this is a V2 dispute game. + function _isV2DisputeGameImplementation(string memory _contractName) internal pure returns (bool) { + return LibString.eq(_contractName, "FaultDisputeGameV2") + || LibString.eq(_contractName, "PermissionedDisputeGameV2"); + } /// @notice Verifies that the immutable variables in the OPCM contract match expected values. /// @param _opcm The OPCM contract to verify immutable variables for. diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 7856a07bccee0..31dac9fa35d47 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -132,7 +132,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = + LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } @@ -202,7 +203,8 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + bool isV2DisputeGame = + LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); if (isV2DisputeGame && !v2FeatureEnabled) { continue; } From a253141c80894541322b0e2ae1c7c032a15ff633 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 15:02:48 -0400 Subject: [PATCH 34/75] fix: test compiler restriction fix --- packages/contracts-bedrock/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 12d9cb5f206ad..284e2e2d5010a 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -147,7 +147,7 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, - { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 }, + { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 } ] ################################################################ From df9a7673e1f2a6f37d24daafb2800571fb17b07a Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 15:08:25 -0400 Subject: [PATCH 35/75] Revert "fix: test compiler restriction fix" This reverts commit 50282cf4fbf32b2a3615a4d9537e0887436f682d. --- packages/contracts-bedrock/foundry.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 284e2e2d5010a..e834a9b0ec167 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -28,8 +28,7 @@ compilation_restrictions = [ { paths = "src/dispute/v2/PermissionedDisputeGameV2.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, - { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, - { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 } + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 } ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -147,7 +146,6 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, - { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 } ] ################################################################ From 7adc269c5a330c488e7d965d8ba8d329eb3423f4 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 15:13:23 -0400 Subject: [PATCH 36/75] fix: compiler bump --- packages/contracts-bedrock/foundry.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index e834a9b0ec167..9a1d5901f09a8 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -28,7 +28,8 @@ compilation_restrictions = [ { paths = "src/dispute/v2/PermissionedDisputeGameV2.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, - { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 } + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, + { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 } ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -146,6 +147,7 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, + { paths = "src/L1/ProtocolVerions.sol", optimizer_runs = 0 }, ] ################################################################ From 05f87503a5ce2aa7dacf9f7fdcfb99814c4d34ae Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 15:15:23 -0400 Subject: [PATCH 37/75] fix: typo --- packages/contracts-bedrock/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 9a1d5901f09a8..12d9cb5f206ad 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -147,7 +147,7 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, - { paths = "src/L1/ProtocolVerions.sol", optimizer_runs = 0 }, + { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 }, ] ################################################################ From 113651bb705b5d464ac1a442ba1a4612bdd8d8db Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 15:57:59 -0400 Subject: [PATCH 38/75] test: add test for verify opcm with v2 dispute games deployed --- .../test/scripts/VerifyOPCM.t.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 31dac9fa35d47..a2714ffd00218 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -479,3 +479,22 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { harness.validateAllGettersAccounted(); } } + +/// @title VerifyOPCM_Run_V2DisputeGamesEnabled_Test +/// @notice Tests the `run` function with V2 dispute games enabled. +contract VerifyOPCM_Run_V2DisputeGamesEnabled_Test is VerifyOPCM_TestInit { + function setUp() public override { + setDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + super.setUp(); + setupEnvVars(); + } + + /// @notice Tests that the script succeeds when V2 dispute games are enabled. + function test_run_succeeds() public { + // Coverage changes bytecode and causes failures, skip. + skipIfCoverage(); + + // Run the script with V2-enabled OPCM. + harness.run(address(opcm), true); + } +} From 8e8d8a5d84ac5f35754c75d77faf47d2a5727b12 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 29 Sep 2025 17:25:22 -0400 Subject: [PATCH 39/75] test: add skips for v1 tests if v2 deployed --- .../contracts-bedrock/test/dispute/FaultDisputeGame.t.sol | 4 ++++ .../test/dispute/PermissionedDisputeGame.t.sol | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index 686b4489f5dad..e08a0da2130b6 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -21,6 +21,7 @@ import { LibClock } from "src/dispute/lib/LibUDT.sol"; import { LibPosition } from "src/dispute/lib/LibPosition.sol"; import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; @@ -150,6 +151,9 @@ contract FaultDisputeGame_TestInit is BaseFaultDisputeGame_TestInit { super.setUp(); + // Skip V1 tests when V2 dispute games are enabled to avoid game type conflicts + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + // Get the actual anchor roots (Hash root, uint256 l2Bn) = anchorStateRegistry.getAnchorRoot(); validL2BlockNumber = l2Bn + 1; diff --git a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol index 30c0e547a2981..d49459df52ef8 100644 --- a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol @@ -7,6 +7,7 @@ import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; // Libraries import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; @@ -95,6 +96,9 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { super.setUp(); + // Skip V1 tests when V2 dispute games are enabled to avoid game type conflicts + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + // Get the actual anchor roots (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); validL2BlockNumber = l2BlockNumber + 1; From c2a4b665b7c993e5e746543744d05c23f812562a Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 13:58:18 -0400 Subject: [PATCH 40/75] fix: skip standard validator until its implemented --- .../test/L1/OPContractsManagerStandardValidator.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 8bbbd9e8c2cd7..7d219ca0de053 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -10,6 +10,7 @@ import { GameTypes, Duration, Claim } from "src/dispute/lib/Types.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import { Features } from "src/libraries/Features.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; @@ -102,6 +103,9 @@ contract OPContractsManagerStandardValidator_TestInit is CommonTest { function setUp() public virtual override { super.setUp(); + // Skip V1 StandardValidator tests when V2 dispute games are enabled + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + // Grab the deploy input for later use. deployInput = deploy.getDeployInput(); From 35ffb7a892baf2534bb134555e071428184a3df2 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 14:22:25 -0400 Subject: [PATCH 41/75] fix: skip addGameType until its implemented --- .../contracts-bedrock/test/L1/OPContractsManager.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 6283e822bb52a..c88f46b61f532 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -436,6 +436,13 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame ); + function setUp() public virtual override { + super.setUp(); + + // Skip AddGameType tests when V2 dispute games are enabled + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + /// @notice Tests that we can add a PermissionedDisputeGame implementation with addGameType. function test_addGameType_permissioned_succeeds() public { // Create the input for the Permissioned game type. From c9aebaee8e718117c74b7b9dc03054e8d9b2b85e Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 14:56:20 -0400 Subject: [PATCH 42/75] fix: skip updatePrestate until its implemented --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index c88f46b61f532..6731300fc12c2 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -795,6 +795,11 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { function setUp() public override { super.setUp(); + + // Skip UpdatePrestate tests when V2 dispute games enabled + // UpdatePrestate feature not yet implemented for V2 + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + prestateUpdater = opcm; } From d2d8781705baa3b52b506718d9d09d848f53b341 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 15:23:02 -0400 Subject: [PATCH 43/75] fix: remove diff in natspec --- .../contracts-bedrock/src/L1/OPContractsManager.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 6992480928344..586a7fc056290 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -719,7 +719,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade - /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. + /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in + /// `_opChainConfigs`'s ProxyAdmin. /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { // Grab the implementations. @@ -887,7 +888,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. - /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. + /// @dev This function is intended to be DELEGATECALLed by the superchainConfig's ProxyAdminOwner. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { // Only upgrade the superchainConfig if the current version is less than the target version. @@ -2010,7 +2011,8 @@ contract OPContractsManager is ISemver { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade - /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. + /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in + /// `_opChainConfigs`'s ProxyAdmin. /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); @@ -2022,7 +2024,8 @@ contract OPContractsManager is ISemver { /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. - /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe. + /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in + /// `_opChainConfigs`'s ProxyAdmin. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); From ebb8185c0751d7ada6ce9ef05a34294ee061a3dd Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 15:27:52 -0400 Subject: [PATCH 44/75] chore: add TODO comments with issue tracking to skips --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 2 ++ .../test/L1/OPContractsManagerStandardValidator.t.sol | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 6731300fc12c2..02c44a7a5e6b1 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -440,6 +440,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { super.setUp(); // Skip AddGameType tests when V2 dispute games are enabled + // TODO(#17260): Remove skip when V2 dispute game support for addGameType implemented skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); } @@ -798,6 +799,7 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { // Skip UpdatePrestate tests when V2 dispute games enabled // UpdatePrestate feature not yet implemented for V2 + // TODO(#17261): Remove skip when V2 dispute game support for updatePrestate implemented skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); prestateUpdater = opcm; diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 7d219ca0de053..c516d46843fb7 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -104,6 +104,7 @@ contract OPContractsManagerStandardValidator_TestInit is CommonTest { super.setUp(); // Skip V1 StandardValidator tests when V2 dispute games are enabled + // TODO(#17267): Remove skip when V2 dispute game support added to the StandardValidator is implemented skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); // Grab the deploy input for later use. From 8bf14ec263038f09f761c087485ebefbb7863831 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 15:57:57 -0400 Subject: [PATCH 45/75] fix: bump semver --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 4d4f5835d4fe4..6d15c3221fd5a 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,8 +20,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0xf414fb79a8941d3ff24f7c6ae65ccee0927c78bd444b20540db9e3376cf2e9e2", - "sourceCodeHash": "0x74a5c3a080db33be6780c9b9bff37c0ce46b4335d6a35148e080a8c2096cd2c8" + "initCodeHash": "0xfea70b2646110201bc6e5cdc33033a764c9bdc1f1c8696c6100f1da5767c3e18", + "sourceCodeHash": "0x49d0020391f7814c2063f33aafc28dfd94cd6e0314e3c05b9ad8fa951d811506" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0xcc5dacb9e1c2b9395aa5f9c300f03c18af1ff5a9efd6a7ce4d5135dfbe7b1e2b", From 997d107179e97a7e6d7e6dbff11cab507bf6aa0e Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 30 Sep 2025 16:32:02 -0400 Subject: [PATCH 46/75] chore: bump semver --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 6d15c3221fd5a..57002bafa774d 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,8 +20,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0xfea70b2646110201bc6e5cdc33033a764c9bdc1f1c8696c6100f1da5767c3e18", - "sourceCodeHash": "0x49d0020391f7814c2063f33aafc28dfd94cd6e0314e3c05b9ad8fa951d811506" + "initCodeHash": "0xda42174d9e9e593fade6a0cc5727e6e4bd024ea1a93031ef50632c3e26bef7d5", + "sourceCodeHash": "0x9428a6fbed82d253e9de7410533f56874745c46c3bea623d09d2c919875449ac" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0xcc5dacb9e1c2b9395aa5f9c300f03c18af1ff5a9efd6a7ce4d5135dfbe7b1e2b", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 586a7fc056290..760ee16a15952 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1872,9 +1872,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 3.6.1 + /// @custom:semver 3.7.0 function version() public pure virtual returns (string memory) { - return "3.6.1"; + return "3.7.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; From d8d2426922cbfca4bf99a5315a9141a2395d10a6 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 2 Oct 2025 14:47:34 -0400 Subject: [PATCH 47/75] Fix silent merge conflicts with develop --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 081ea4336b217..2c5596ed0b05e 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1838,7 +1838,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { /// @notice Test that deploy without v2 flag doesn't set v2 implementations for PERMISSIONED_CANNON function test_deployPermissionedWithoutV2Flag_succeeds() public { // Convert DOI to OPCM input and deploy - IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); + IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); IOPContractsManager.Implementations memory impls = opcm.implementations(); @@ -1897,8 +1897,8 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { ); // Set up deploy input for the v2-enabled OPCM - doi.set(doi.opcm.selector, address(opcmV2)); - IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(doi); + deployOPChainInput.opcm = address(opcmV2); + IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); IOPContractsManager.DeployOutput memory output = opcmV2.deploy(opcmInput); // Verify that the DisputeGameFactory has registered the v2 implementation for PERMISSIONED_CANNON game type From 3301eeafec90430099f839917c2b1abb26d3e0f6 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 10:30:46 -0400 Subject: [PATCH 48/75] Bump semver version --- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 760ee16a15952..da764ff3d6b88 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1872,9 +1872,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 3.7.0 + /// @custom:semver 3.8.0 function version() public pure virtual returns (string memory) { - return "3.7.0"; + return "3.8.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; From 5eb5f3b056ba8e1f689fdcf4efb13015a503223c Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 10:31:32 -0400 Subject: [PATCH 49/75] Run semver-lock --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index f5b520ff3d0ac..4f6e00c353ed7 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,8 +20,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0xda42174d9e9e593fade6a0cc5727e6e4bd024ea1a93031ef50632c3e26bef7d5", - "sourceCodeHash": "0x9428a6fbed82d253e9de7410533f56874745c46c3bea623d09d2c919875449ac" + "initCodeHash": "0x01d2eceec1f7a9f7ca232cdda10da279477c7418545be4834989ab0b5007dd40", + "sourceCodeHash": "0xd226c3b4242b6a4b69ec0554146866dc262110ce3b5ce7e594d5dc0013acb7f2" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x57d6a6729d887ead009d518e8f17fa0d26bfc97b8efe1494ab4ef8dbb000d109", From 06a236df7bda8720b0e300bee60064ef4d42e2ea Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 10:48:59 -0400 Subject: [PATCH 50/75] Reintroduce OPContractsManager_Version_Test --- .../test/L1/OPContractsManager.t.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 2c5596ed0b05e..d927915b4e650 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1933,3 +1933,17 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { assertEq(permissionedProxy.challenger(), opcmInput.roles.challenger, "Challenger should match"); } } + +/// @title OPContractsManager_Version_Test +/// @notice Tests the `version` function of the `OPContractsManager` contract. +contract OPContractsManager_Version_Test is OPContractsManager_TestInit { + OPContractsManager.AddGameInput[] internal gameInput; + + function setUp() public override { + super.setUp(); + } + + function test_semver_works() public view { + assertNotEq(abi.encode(opcm.version()), abi.encode(0)); + } +} From 40f9160c706d11c6b4b0acab71bf94b5bfbaac89 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 10:50:14 -0400 Subject: [PATCH 51/75] Cleanup - revert comment change --- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index da764ff3d6b88..0d2c800471246 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -2024,8 +2024,7 @@ contract OPContractsManager is ISemver { /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. - /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in - /// `_opChainConfigs`'s ProxyAdmin. + /// @dev This function is intended to be DELEGATECALLed by the superchainConfig's ProxyAdminOwner. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); From 9df861a5d8c3f6ee1b98cf2eef6df8e52599b546 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 10:52:38 -0400 Subject: [PATCH 52/75] Remove v2 contracts from DeployOutput --- .../contracts-bedrock/interfaces/L1/IOPContractsManager.sol | 3 --- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 3 --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 4 ---- 3 files changed, 10 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index be3b864b94e88..300f49b9a5008 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -179,9 +179,6 @@ interface IOPContractsManager { IPermissionedDisputeGame permissionedDisputeGame; IDelayedWETH delayedWETHPermissionedGameProxy; IDelayedWETH delayedWETHPermissionlessGameProxy; - // V2 dispute game contracts (deployed when DEPLOY_V2_DISPUTE_GAMES flag is set) - IFaultDisputeGameV2 faultDisputeGameV2; - IPermissionedDisputeGameV2 permissionedDisputeGameV2; } /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 0d2c800471246..2a6b267a99d9c 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1796,9 +1796,6 @@ contract OPContractsManager is ISemver { IPermissionedDisputeGame permissionedDisputeGame; IDelayedWETH delayedWETHPermissionedGameProxy; IDelayedWETH delayedWETHPermissionlessGameProxy; - // V2 dispute game contracts (deployed when DEPLOY_V2_DISPUTE_GAMES flag is set) - IFaultDisputeGameV2 faultDisputeGameV2; - IPermissionedDisputeGameV2 permissionedDisputeGameV2; } /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index d927915b4e650..1af345066132f 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1842,10 +1842,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); IOPContractsManager.Implementations memory impls = opcm.implementations(); - // Check that v2 implementations are not set (since flag is not enabled by default) - assertEq(address(output.permissionedDisputeGameV2), address(0)); - assertEq(address(output.faultDisputeGameV2), address(0)); - // Check that v1 implementation is registered for PERMISSIONED_CANNON address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); assertEq(registeredImpl, address(output.permissionedDisputeGame)); From 61e4338f9b36ec1e1c6b599f2488c6fb43ebc8af Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 6 Oct 2025 17:42:10 -0400 Subject: [PATCH 53/75] Remove unused imports --- .../contracts-bedrock/interfaces/L1/IOPContractsManager.sol | 2 -- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 300f49b9a5008..72fc2a1aaa3eb 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -14,8 +14,6 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 2a6b267a99d9c..d9dccc0ec569a 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -26,8 +26,6 @@ import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; -import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; import { ISuperPermissionedDisputeGame } from "interfaces/dispute/ISuperPermissionedDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; From 7f7c1c7ab43282cb7326bb035e33681790bc631c Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 11:58:10 -0400 Subject: [PATCH 54/75] Update DeployOPChain tests to run across feature flags (in progress) --- .../scripts/deploy/DeployOPChain.s.sol | 34 +++++-- .../test/opcm/DeployOPChain.t.sol | 88 ++++++++++++------- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 77a3d3d4351f8..5a6401c94e13d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; @@ -24,6 +25,7 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IOPContractsManager } from "../../interfaces/L1/IOPContractsManager.sol"; contract DeployOPChain is Script { struct Output { @@ -120,6 +122,13 @@ contract DeployOPChain is Script { checkOutput(_input, output_); } + // -------- Features -------- + + function isDevFeatureV2DisputeGamesEnabled(address opcmAddr) internal view returns (bool) { + IOPContractsManager opcm = IOPContractsManager(opcmAddr); + return DevFeatures.isDevFeatureEnabled(opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + // -------- Validations -------- function checkInput(Types.DeployOPChainInput memory _i) public view { @@ -162,13 +171,19 @@ contract DeployOPChain is Script { address(_o.optimismPortalProxy), address(_o.disputeGameFactoryProxy), address(_o.anchorStateRegistryProxy), - address(_o.permissionedDisputeGame), address(_o.delayedWETHPermissionedGameProxy), address(_o.ethLockboxProxy) ); - // TODO: Eventually switch from Permissioned to Permissionless. Add this address back in. - // address(_o.delayedWETHPermissionlessGameProxy) - // address(_o.faultDisputeGame()), + + if (!isDevFeatureV2DisputeGamesEnabled(_i.opcm)) { + // Only check dispute game contracts if v2 dispute games are not enabled. + // When v2 contracts are enabled, we no longer deploy dispute games per chain + addrs2 = Solarray.extend(addrs2, Solarray.addresses(address(_o.permissionedDisputeGame))); + + // TODO: Eventually switch from Permissioned to Permissionless. Add these addresses back in. + // address(_o.delayedWETHPermissionlessGameProxy) + // address(_o.faultDisputeGame()), + } DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); _assertValidDeploy(_i, _o); @@ -192,10 +207,17 @@ contract DeployOPChain is Script { SuperchainConfig: address(0) }); - ChainAssertions.checkAnchorStateRegistryProxy(_o.anchorStateRegistryProxy, true); + // Check dispute games + address expectedPDGImpl = address(_o.permissionedDisputeGame); + if (isDevFeatureV2DisputeGamesEnabled(_i.opcm)) { + // With v2 game contracts enabled, we use the predeployed pdg implementation + expectedPDGImpl = IOPContractsManager(_i.opcm).implementations().permissionedDisputeGameV2Impl; + } ChainAssertions.checkDisputeGameFactory( - _o.disputeGameFactoryProxy, _i.opChainProxyAdminOwner, address(_o.permissionedDisputeGame), true + _o.disputeGameFactoryProxy, _i.opChainProxyAdminOwner, expectedPDGImpl, true ); + + ChainAssertions.checkAnchorStateRegistryProxy(_o.anchorStateRegistryProxy, true); ChainAssertions.checkL1CrossDomainMessenger(_o.l1CrossDomainMessengerProxy, vm, true); ChainAssertions.checkOptimismPortal2({ _contracts: proxies, diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 4eebfb2863b5a..3033c0254a5b8 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; +import { FeatureFlags } from "test/setup/FeatureFlags.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { DeploySuperchain } from "scripts/deploy/DeploySuperchain.s.sol"; import { DeployImplementations } from "scripts/deploy/DeployImplementations.s.sol"; @@ -11,8 +13,9 @@ import { Types } from "scripts/libraries/Types.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -contract DeployOPChain_TestBase is Test { +contract DeployOPChain_TestBase is Test, FeatureFlags { DeploySuperchain deploySuperchain; DeployImplementations deployImplementations; DeployOPChain deployOPChain; @@ -57,6 +60,7 @@ contract DeployOPChain_TestBase is Test { IOPContractsManager opcm; function setUp() public virtual { + resolveFeaturesFromEnv(); deploySuperchain = new DeploySuperchain(); deployImplementations = new DeployImplementations(); deployOPChain = new DeployOPChain(); @@ -91,7 +95,7 @@ contract DeployOPChain_TestBase is Test { superchainProxyAdmin: dso.superchainProxyAdmin, upgradeController: dso.superchainProxyAdmin.owner(), challenger: challenger, - devFeatureBitmap: bytes32(0) + devFeatureBitmap: devFeatureBitmap }) ); opcm = dio.opcm; @@ -134,25 +138,26 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { // Basic non-zero and code checks are covered inside run->checkOutput. // Additonal targeted assertions added below. - assertEq(address(doo.permissionedDisputeGame.proposer()), proposer, "PDG proposer"); - assertEq(address(doo.permissionedDisputeGame.challenger()), challenger, "PDG challenger"); - assertEq(doo.permissionedDisputeGame.splitDepth(), disputeSplitDepth, "PDG splitDepth"); - assertEq(doo.permissionedDisputeGame.maxGameDepth(), disputeMaxGameDepth, "PDG maxGameDepth"); + IPermissionedDisputeGame pdg = getPermissionedDisputeGame(doo); + assertEq(pdg.splitDepth(), disputeSplitDepth, "PDG splitDepth"); + assertEq(pdg.maxGameDepth(), disputeMaxGameDepth, "PDG maxGameDepth"); + assertEq(Duration.unwrap(pdg.clockExtension()), Duration.unwrap(disputeClockExtension), "PDG clockExtension"); assertEq( - Duration.unwrap(doo.permissionedDisputeGame.clockExtension()), - Duration.unwrap(disputeClockExtension), - "PDG clockExtension" - ); - assertEq( - Duration.unwrap(doo.permissionedDisputeGame.maxClockDuration()), - Duration.unwrap(disputeMaxClockDuration), - "PDG maxClockDuration" - ); - assertEq( - Claim.unwrap(doo.permissionedDisputeGame.absolutePrestate()), - Claim.unwrap(disputeAbsolutePrestate), - "PDG absolutePrestate" + Duration.unwrap(pdg.maxClockDuration()), Duration.unwrap(disputeMaxClockDuration), "PDG maxClockDuration" ); + + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + // For v2 contracts, some immutable args are passed in at game creation time from DGF.gameArgs + assertEq(address(pdg.proposer()), address(0), "PDG proposer"); + assertEq(address(pdg.challenger()), address(0), "PDG challenger"); + assertEq(Claim.unwrap(pdg.absolutePrestate()), bytes32(0), "PDG absolutePrestate"); + } else { + assertEq(address(pdg.proposer()), proposer, "PDG proposer"); + assertEq(address(pdg.challenger()), challenger, "PDG challenger"); + assertEq( + Claim.unwrap(pdg.absolutePrestate()), Claim.unwrap(disputeAbsolutePrestate), "PDG absolutePrestate" + ); + } } function testFuzz_run_memory_succeeds(bytes32 _seed) public { @@ -172,22 +177,45 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { assertEq(doo.disputeGameFactoryProxy.initBonds(GameTypes.CANNON), 0, "2700"); assertEq(doo.disputeGameFactoryProxy.initBonds(GameTypes.PERMISSIONED_CANNON), 0, "2800"); - assertEq(doo.permissionedDisputeGame.l2BlockNumber(), 0, "3000"); - assertEq( - Claim.unwrap(doo.permissionedDisputeGame.absolutePrestate()), - 0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c, - "3100" - ); - assertEq(Duration.unwrap(doo.permissionedDisputeGame.clockExtension()), 10800, "3200"); - assertEq(Duration.unwrap(doo.permissionedDisputeGame.maxClockDuration()), 302400, "3300"); - assertEq(doo.permissionedDisputeGame.splitDepth(), 30, "3400"); - assertEq(doo.permissionedDisputeGame.maxGameDepth(), 73, "3500"); + // Check dispute game deployments + // Validate permissionedDisputeGame (PDG) address + bool isDeployV2Games = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + IOPContractsManager.Implementations memory impls = opcm.implementations(); + address expectedPDGAddress = + isDeployV2Games ? impls.permissionedDisputeGameV2Impl : address(doo.permissionedDisputeGame); + address actualPDGAddress = address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertNotEq(actualPDGAddress, address(0), "PDG address should be non-zero"); + assertEq(actualPDGAddress, expectedPDGAddress, "PDG address should match expected address"); + + // Check PDG getters + IPermissionedDisputeGame pdg = IPermissionedDisputeGame(actualPDGAddress); + bytes32 expectedPrestate = + isDeployV2Games ? bytes32(0) : bytes32(0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c); + assertEq(pdg.l2BlockNumber(), 0, "3000"); + assertEq(Claim.unwrap(pdg.absolutePrestate()), expectedPrestate, "3100"); + assertEq(Duration.unwrap(pdg.clockExtension()), 10800, "3200"); + assertEq(Duration.unwrap(pdg.maxClockDuration()), 302400, "3300"); + assertEq(pdg.splitDepth(), 30, "3400"); + assertEq(pdg.maxGameDepth(), 73, "3500"); } function test_customDisputeGame_customEnabled_succeeds() public { + // For v2 games, these parameters have already been configured at OPCM deploy time + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + deployOPChainInput.allowCustomDisputeParameters = true; deployOPChainInput.disputeSplitDepth = disputeSplitDepth + 1; DeployOPChain.Output memory doo = deployOPChain.run(deployOPChainInput); - assertEq(doo.permissionedDisputeGame.splitDepth(), disputeSplitDepth + 1); + + IPermissionedDisputeGame pdg = getPermissionedDisputeGame(doo); + assertEq(pdg.splitDepth(), disputeSplitDepth + 1); + } + + function getPermissionedDisputeGame(DeployOPChain.Output memory doo) + internal + view + returns (IPermissionedDisputeGame) + { + return IPermissionedDisputeGame(address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON))); } } From f355827060500beaa0e0607acb3b4090b7695a3a Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 13:44:20 -0400 Subject: [PATCH 55/75] Update OPCM tests to work with FeatureFlags options --- .../test/L1/OPContractsManager.t.sol | 157 ++++-------------- 1 file changed, 35 insertions(+), 122 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 1af345066132f..8c7071e6ab8df 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.15; import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; import { VmSafe } from "forge-std/Vm.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; +import { FeatureFlags } from "test/setup/FeatureFlags.sol"; import { DeployOPChain_TestBase } from "test/opcm/DeployOPChain.t.sol"; import { DelegateCaller } from "test/mocks/Callers.sol"; @@ -376,7 +377,7 @@ contract OPContractsManager_TestInit is CommonTest { /// @title OPContractsManager_ChainIdToBatchInboxAddress_Test /// @notice Tests the `chainIdToBatchInboxAddress` function of the `OPContractsManager` contract. /// @dev These tests use the harness which exposes internal functions for testing. -contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test { +contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlags { OPContractsManager_Harness opcmHarness; address challenger = makeAddr("challenger"); @@ -390,8 +391,9 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test { vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); + resolveFeaturesFromEnv(); OPContractsManagerContractsContainer container = - new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, bytes32(0)); + new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, devFeatureBitmap); OPContractsManager.Implementations memory __opcmImplementations = container.implementations(); OPContractsManagerStandardValidator.Implementations memory opcmImplementations; @@ -1744,49 +1746,8 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { }); } - /// @notice Helper function to deploy OPCM with v2 flag enabled - function _deployOPCMWithV2Flag() internal returns (IOPContractsManager) { - // Deploy Superchain contracts first - DeploySuperchain deploySuperchain = new DeploySuperchain(); - DeploySuperchain.Output memory dso = deploySuperchain.run( - DeploySuperchain.Input({ - superchainProxyAdminOwner: makeAddr("superchainProxyAdminOwner"), - protocolVersionsOwner: makeAddr("protocolVersionsOwner"), - guardian: makeAddr("guardian"), - paused: false, - requiredProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(1))), - recommendedProtocolVersion: bytes32(ProtocolVersion.unwrap(ProtocolVersion.wrap(2))) - }) - ); - - // Deploy implementations with v2 flag enabled - DeployImplementations deployImplementations = new DeployImplementations(); - DeployImplementations.Output memory dio = deployImplementations.run( - DeployImplementations.Input({ - withdrawalDelaySeconds: 100, - minProposalSizeBytes: 200, - challengePeriodSeconds: 300, - proofMaturityDelaySeconds: 400, - disputeGameFinalityDelaySeconds: 500, - mipsVersion: StandardConstants.MIPS_VERSION, - faultGameV2MaxGameDepth: 73, - faultGameV2SplitDepth: 30, - faultGameV2ClockExtension: 10800, - faultGameV2MaxClockDuration: 302400, - superchainConfigProxy: dso.superchainConfigProxy, - protocolVersionsProxy: dso.protocolVersionsProxy, - superchainProxyAdmin: dso.superchainProxyAdmin, - upgradeController: dso.superchainProxyAdmin.owner(), - challenger: challenger, - devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here - }) - ); - - return dio.opcm; - } - - /// @notice Helper function to create a permissioned game v2 through the factory - function _createPermissionedGameV2( + /// @notice Helper function to create a permissioned game through the factory + function _createPermissionedGame( IDisputeGameFactory factory, address proposer ) @@ -1835,98 +1796,50 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { opcm.deploy(toOPCMDeployInput(deployOPChainInput)); } - /// @notice Test that deploy without v2 flag doesn't set v2 implementations for PERMISSIONED_CANNON - function test_deployPermissionedWithoutV2Flag_succeeds() public { - // Convert DOI to OPCM input and deploy - IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); - IOPContractsManager.DeployOutput memory output = opcm.deploy(opcmInput); + /// @notice Test that deploy sets the permissioned dispute game implementation + function test_deployPermissioned_succeeds() public { + bool isV2 = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + + // Sanity-check setup is consistent with devFeatures flag IOPContractsManager.Implementations memory impls = opcm.implementations(); + bool expectEmptyV2Implementations = isV2 ? false : true; + assertEq(address(impls.permissionedDisputeGameV2Impl) == address(0), expectEmptyV2Implementations); + assertEq(address(impls.faultDisputeGameV2Impl) == address(0), expectEmptyV2Implementations); - // Check that v1 implementation is registered for PERMISSIONED_CANNON - address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); - assertEq(registeredImpl, address(output.permissionedDisputeGame)); + IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); + IOPContractsManager.DeployOutput memory opcmOutput = opcm.deploy(opcmInput); - address registeredPermissionedImpl = - address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); - assertNotEq( - registeredPermissionedImpl, - address(0), - "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" - ); - assertEq( - registeredPermissionedImpl, address(output.permissionedDisputeGame), "Should be using v2 implementation" - ); + // Verify that the DisputeGameFactory has registered an implementation for the PERMISSIONED_CANNON game type + address expectedPDGAddress = + isV2 ? impls.permissionedDisputeGameV2Impl : address(opcmOutput.permissionedDisputeGame); + address actualPDGAddress = address(opcmOutput.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + assertNotEq(actualPDGAddress, address(0), "DisputeGameFactory should have a registered PERMISSIONED_CANNON"); + assertEq(actualPDGAddress, address(expectedPDGAddress)); // Create a game proxy to test immutable fields - IPermissionedDisputeGame gameV2Proxy = - _createPermissionedGameV2(output.disputeGameFactoryProxy, opcmInput.roles.proposer); + IPermissionedDisputeGame permissionedGame = + _createPermissionedGame(opcmOutput.disputeGameFactoryProxy, opcmInput.roles.proposer); // Verify immutable fields on the game proxy - assertEq(address(gameV2Proxy.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); assertEq( - address(gameV2Proxy.anchorStateRegistry()), address(output.anchorStateRegistryProxy), "ASR should match" - ); - assertEq(address(gameV2Proxy.weth()), address(output.delayedWETHPermissionedGameProxy), "WETH should match"); - assertEq(gameV2Proxy.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); - - // For permissioned game, check proposer and challenger - IPermissionedDisputeGame permissionedProxy = IPermissionedDisputeGame(address(gameV2Proxy)); - assertEq(permissionedProxy.proposer(), opcmInput.roles.proposer, "Proposer should match"); - assertEq(permissionedProxy.challenger(), opcmInput.roles.challenger, "Challenger should match"); - } - - /// @notice Test that deploy with v2 flag would set v2 implementations for PERMISSIONED_CANNON - function test_deployPermissionedWithV2Flag_succeeds() public { - IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); - - assertTrue(opcmV2.isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES), "V2 flag should be enabled"); - - IOPContractsManager.Implementations memory impls = opcmV2.implementations(); - - // Verify that v2 implementations are set (non-zero) - assertTrue( - address(impls.permissionedDisputeGameV2Impl) != address(0), - "PermissionedDisputeGameV2 implementation should be non-zero" - ); - assertTrue( - address(impls.faultDisputeGameV2Impl) != address(0), "FaultDisputeGameV2 implementation should be non-zero" - ); - - // Set up deploy input for the v2-enabled OPCM - deployOPChainInput.opcm = address(opcmV2); - IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); - IOPContractsManager.DeployOutput memory output = opcmV2.deploy(opcmInput); - - // Verify that the DisputeGameFactory has registered the v2 implementation for PERMISSIONED_CANNON game type - address registeredPermissionedImpl = - address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); - assertNotEq( - registeredPermissionedImpl, - address(0), - "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" + permissionedGame.absolutePrestate().raw(), + opcmInput.disputeAbsolutePrestate.raw(), + "Absolute prestate should match input" ); + assertEq(address(permissionedGame.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); assertEq( - registeredPermissionedImpl, - address(impls.permissionedDisputeGameV2Impl), - "Should be using v2 implementation" + address(permissionedGame.anchorStateRegistry()), + address(opcmOutput.anchorStateRegistryProxy), + "ASR should match" ); - - // Create a game proxy to test immutable fields - IPermissionedDisputeGame gameV2Proxy = - _createPermissionedGameV2(output.disputeGameFactoryProxy, opcmInput.roles.proposer); - - // Verify immutable fields on the game proxy - assertEq(address(gameV2Proxy.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); assertEq( - address(gameV2Proxy.anchorStateRegistry()), address(output.anchorStateRegistryProxy), "ASR should match" + address(permissionedGame.weth()), address(opcmOutput.delayedWETHPermissionedGameProxy), "WETH should match" ); - assertEq(address(gameV2Proxy.weth()), address(output.delayedWETHPermissionedGameProxy), "WETH should match"); - assertEq(gameV2Proxy.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); + assertEq(permissionedGame.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); // For permissioned game, check proposer and challenger - IPermissionedDisputeGame permissionedProxy = IPermissionedDisputeGame(address(gameV2Proxy)); - assertEq(permissionedProxy.proposer(), opcmInput.roles.proposer, "Proposer should match"); - assertEq(permissionedProxy.challenger(), opcmInput.roles.challenger, "Challenger should match"); + assertEq(permissionedGame.proposer(), opcmInput.roles.proposer, "Proposer should match"); + assertEq(permissionedGame.challenger(), opcmInput.roles.challenger, "Challenger should match"); } } From 3394856c947d3e1fc9585be06f2e92840397282a Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 14:45:16 -0400 Subject: [PATCH 56/75] Merge PermissionedDisputeGame tests, use feature flags --- .../test/dispute/DisputeGameFactory.t.sol | 29 +- .../dispute/PermissionedDisputeGame.t.sol | 192 +++++++- .../v2/PermissionedDisputeGameV2.t.sol | 417 ------------------ 3 files changed, 193 insertions(+), 445 deletions(-) delete mode 100644 packages/contracts-bedrock/test/dispute/v2/PermissionedDisputeGameV2.t.sol diff --git a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol index 58b4e6e624a22..51dc7912efd9a 100644 --- a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol +++ b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol @@ -11,6 +11,7 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; @@ -82,7 +83,8 @@ contract DisputeGameFactory_TestInit is CommonTest { function _getGameConstructorParams( Claim _absolutePrestate, AlphabetVM _vm, - GameType _gameType + GameType _gameType, + uint256 l2ChainId ) internal view @@ -98,7 +100,7 @@ contract DisputeGameFactory_TestInit is CommonTest { vm: _vm, weth: delayedWeth, anchorStateRegistry: anchorStateRegistry, - l2ChainId: 0 + l2ChainId: l2ChainId }); } @@ -125,7 +127,7 @@ contract DisputeGameFactory_TestInit is CommonTest { view returns (ISuperFaultDisputeGame.GameConstructorParams memory params_) { - bytes memory args = abi.encode(_getGameConstructorParams(_absolutePrestate, _vm, _gameType)); + bytes memory args = abi.encode(_getGameConstructorParams(_absolutePrestate, _vm, _gameType, 0)); params_ = abi.decode(args, (ISuperFaultDisputeGame.GameConstructorParams)); } @@ -206,7 +208,8 @@ contract DisputeGameFactory_TestInit is CommonTest { _name: "FaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( - IFaultDisputeGame.__constructor__, (_getGameConstructorParams(_absolutePrestate, vm_, GameTypes.CANNON)) + IFaultDisputeGame.__constructor__, + (_getGameConstructorParams(_absolutePrestate, vm_, GameTypes.CANNON, 0)) ) ) }); @@ -258,6 +261,21 @@ contract DisputeGameFactory_TestInit is CommonTest { ) internal returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) + { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + return setupPermissionedDisputeGameV2(_absolutePrestate, _proposer, _challenger); + } else { + return setupPermissionedDisputeGameV1(_absolutePrestate, _proposer, _challenger); + } + } + + function setupPermissionedDisputeGameV1( + Claim _absolutePrestate, + address _proposer, + address _challenger + ) + internal + returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) { (vm_, preimageOracle_) = _createVM(_absolutePrestate); gameImpl_ = DeployUtils.create1({ @@ -266,7 +284,7 @@ contract DisputeGameFactory_TestInit is CommonTest { abi.encodeCall( IPermissionedDisputeGame.__constructor__, ( - _getGameConstructorParams(_absolutePrestate, vm_, GameTypes.PERMISSIONED_CANNON), + _getGameConstructorParams(_absolutePrestate, vm_, GameTypes.PERMISSIONED_CANNON, l2ChainId), _proposer, _challenger ) @@ -576,7 +594,6 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni AlphabetVM vm_; IPreimageOracle preimageOracle_; (vm_, preimageOracle_) = _createVM(absolutePrestate); - uint256 l2ChainId = 111; bytes memory args = abi.encodePacked( absolutePrestate, // 32 bytes diff --git a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol index d49459df52ef8..edb19c5479cfe 100644 --- a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol @@ -4,12 +4,14 @@ pragma solidity ^0.8.15; // Testing import { DisputeGameFactory_TestInit } from "test/dispute/DisputeGameFactory.t.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; + // Libraries import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; /// @title PermissionedDisputeGame_TestInit @@ -22,6 +24,9 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { /// @notice Mock challenger key address internal constant CHALLENGER = address(0xfacadec); + /// @dev The initial bond for the game. + uint256 internal initBond; + /// @notice The implementation of the game. IPermissionedDisputeGame internal gameImpl; /// @notice The `Clone` proxy of the game. @@ -61,11 +66,8 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { (address _impl, AlphabetVM _vm,) = setupPermissionedDisputeGame(_absolutePrestate, PROPOSER, CHALLENGER); gameImpl = IPermissionedDisputeGame(_impl); - // Register the game implementation with the factory. - disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); - // Create a new game. - uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); + initBond = disputeGameFactory.initBonds(GAME_TYPE); vm.mockCall( address(anchorStateRegistry), abi.encodeCall(anchorStateRegistry.anchors, (GAME_TYPE)), @@ -73,7 +75,7 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { ); vm.prank(PROPOSER, PROPOSER); gameProxy = IPermissionedDisputeGame( - payable(address(disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, _rootClaim, extraData))) + payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _rootClaim, extraData))) ); // Check immutables @@ -83,8 +85,13 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { assertEq(gameProxy.absolutePrestate().raw(), _absolutePrestate.raw()); assertEq(gameProxy.maxGameDepth(), 2 ** 3); assertEq(gameProxy.splitDepth(), 2 ** 2); + assertEq(gameProxy.clockExtension().raw(), 3 hours); assertEq(gameProxy.maxClockDuration().raw(), 3.5 days); + assertEq(address(gameProxy.weth()), address(delayedWeth)); + assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(address(gameProxy.vm()), address(_vm)); + assertEq(address(gameProxy.gameCreator()), PROPOSER); + assertEq(gameProxy.l2ChainId(), l2ChainId); // Label the proxy vm.label(address(gameProxy), "PermissionedDisputeGame_Clone"); @@ -96,9 +103,6 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { super.setUp(); - // Skip V1 tests when V2 dispute games are enabled to avoid game type conflicts - skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - // Get the actual anchor roots (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); validL2BlockNumber = l2BlockNumber + 1; @@ -128,6 +132,14 @@ contract PermissionedDisputeGame_TestInit is DisputeGameFactory_TestInit { fallback() external payable { } receive() external payable { } + + function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { + uint256 byteCount = src.length < dest.length ? src.length : dest.length; + for (uint256 i = 0; i < byteCount; i++) { + dest[i] = src[i]; + } + return dest; + } } /// @title PermissionedDisputeGame_Version_Test @@ -142,13 +154,59 @@ contract PermissionedDisputeGame_Version_Test is PermissionedDisputeGame_TestIni /// @title PermissionedDisputeGame_Step_Test /// @notice Tests the `step` function of the `PermissionedDisputeGame` contract. contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { - /// @notice Tests that step works properly. - function test_step_succeeds() public { - // Give the test contract some ether + /// @notice Tests that step works properly for the challenger. + function test_step_fromChallenger_succeeds() public { + validateStepForActor(CHALLENGER); + } + + /// @notice Tests that step works properly for the proposer. + function test_step_fromProposer_succeeds() public { + validateStepForActor(PROPOSER); + } + + function validateStepForActor(address actor) internal { + vm.deal(actor, 1_000 ether); + vm.startPrank(actor, actor); + + // Set up and perform the step + setupGameForStep(); + performStep(); + assertEq(gameProxy.claimDataLen(), 9); + + // Resolve the game and check that the expected actor countered the root claim + resolveGame(); + assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + assertEq(gameProxy.resolvedAt().raw(), block.timestamp); + (, address counteredBy,,,,,) = gameProxy.claimData(0); + assertEq(counteredBy, actor); + + vm.stopPrank(); + } + + /// @notice Tests that step reverts for unauthorized addresses. + function test_step_notAuthorized_reverts(address _unauthorized) internal { + vm.assume(_unauthorized != PROPOSER && _unauthorized != CHALLENGER); + vm.deal(_unauthorized, 1_000 ether); vm.deal(CHALLENGER, 1_000 ether); + // Set up for the step using an authorized actor vm.startPrank(CHALLENGER, CHALLENGER); + setupGameForStep(); + vm.stopPrank(); + + // Perform step with the unauthorized actor + vm.startPrank(_unauthorized, _unauthorized); + vm.expectRevert(BadAuth.selector); + performStep(); + + // Game should still be in progress, leaf claim should be missing + assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + assertEq(gameProxy.claimDataLen(), 8); + + vm.stopPrank(); + } + function setupGameForStep() internal { // Make claims all the way down the tree. (,,,, Claim disputed,,) = gameProxy.claimData(0); gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); @@ -167,12 +225,16 @@ contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { (,,,, disputed,,) = gameProxy.claimData(7); gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - // Verify game state before step + // Verify game state and add local data assertEq(uint256(gameProxy.status()), uint256(GameStatus.IN_PROGRESS)); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); + } + + function performStep() internal { gameProxy.step(8, true, absolutePrestateData, hex""); + } + function resolveGame() internal { vm.warp(block.timestamp + gameProxy.maxClockDuration().raw() + 1); gameProxy.resolveClaim(8, 0); gameProxy.resolveClaim(7, 0); @@ -185,11 +247,91 @@ contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { gameProxy.resolveClaim(0, 0); gameProxy.resolve(); + } +} - assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); - assertEq(gameProxy.resolvedAt().raw(), block.timestamp); - (, address counteredBy,,,,,) = gameProxy.claimData(0); - assertEq(counteredBy, CHALLENGER); +/// @title PermissionedDisputeGame_Initialize_Test +/// @notice Tests the initialization of the `PermissionedDisputeGame` contract. +contract PermissionedDisputeGameV2_Initialize_Test is PermissionedDisputeGame_TestInit { + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by extraData of the wrong length + function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { + // The `DisputeGameFactory` will pack the root claim and the extra data into a single + // array, which is enforced to be at least 64 bytes long. + // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the + // contract size limit in this test, as CWIA proxies store the immutable args in their + // bytecode. + // [0 bytes, 31 bytes] u [33 bytes, 23.5 KB] + _extraDataLen = bound(_extraDataLen, 0, 23_500); + if (_extraDataLen == 32) { + _extraDataLen++; + } + bytes memory _extraData = new bytes(_extraDataLen); + + // Assign the first 32 bytes in `extraData` to a valid L2 block number passed the starting + // block. + (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); + assembly { + mstore(add(_extraData, 0x20), add(startingL2Block, 1)) + } + + Claim claim = _dummyClaim(); + vm.prank(PROPOSER, PROPOSER); + vm.expectRevert(IFaultDisputeGame.BadExtraData.selector); + gameProxy = IPermissionedDisputeGame( + payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, _extraData))) + ); + } + + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by additional immutable args data + function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { + skipIfDevFeatureDisabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); + + // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the + // contract size limit in this test, as CWIA proxies store the immutable args in their + // bytecode. + _extraByteCount = bound(_extraByteCount, 1, 23_500); + bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); + // Copy correct args into immutable args + copyBytes(correctArgs, immutableArgs); + + // Set up dispute game implementation with target immutableArgs + setupPermissionedDisputeGameV2(immutableArgs); + + Claim claim = _dummyClaim(); + vm.prank(PROPOSER, PROPOSER); + vm.expectRevert(IFaultDisputeGame.BadExtraData.selector); + gameProxy = IPermissionedDisputeGame( + payable( + address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) + ) + ); + } + + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by missing immutable args data + function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { + skipIfDevFeatureDisabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); + + _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; + bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); + // Copy correct args into immutable args + copyBytes(correctArgs, immutableArgs); + + // Set up dispute game implementation with target immutableArgs + setupPermissionedDisputeGameV2(immutableArgs); + + Claim claim = _dummyClaim(); + vm.prank(PROPOSER, PROPOSER); + vm.expectRevert(IFaultDisputeGame.BadExtraData.selector); + gameProxy = IPermissionedDisputeGame( + payable( + address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) + ) + ); } } @@ -199,9 +341,16 @@ contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { contract PermissionedDisputeGame_Uncategorized_Test is PermissionedDisputeGame_TestInit { /// @notice Tests that the proposer can create a permissioned dispute game. function test_createGame_proposer_succeeds() public { - uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); vm.prank(PROPOSER, PROPOSER); - disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); + disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); + } + + /// @notice Tests that the permissioned game cannot be created by the challenger. + function test_createGame_challenger_reverts() public { + vm.deal(CHALLENGER, initBond); + vm.prank(CHALLENGER, CHALLENGER); + vm.expectRevert(BadAuth.selector); + disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); } /// @notice Tests that the permissioned game cannot be created by any address other than the @@ -209,11 +358,10 @@ contract PermissionedDisputeGame_Uncategorized_Test is PermissionedDisputeGame_T function testFuzz_createGame_notProposer_reverts(address _p) public { vm.assume(_p != PROPOSER); - uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); - vm.deal(_p, bondAmount); + vm.deal(_p, initBond); vm.prank(_p, _p); vm.expectRevert(BadAuth.selector); - disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); + disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); } /// @notice Tests that the challenger can participate in a permissioned dispute game. diff --git a/packages/contracts-bedrock/test/dispute/v2/PermissionedDisputeGameV2.t.sol b/packages/contracts-bedrock/test/dispute/v2/PermissionedDisputeGameV2.t.sol deleted file mode 100644 index 1e621ac80389e..0000000000000 --- a/packages/contracts-bedrock/test/dispute/v2/PermissionedDisputeGameV2.t.sol +++ /dev/null @@ -1,417 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Testing -import { DisputeGameFactory_TestInit } from "test/dispute/DisputeGameFactory.t.sol"; -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; - -// Libraries -import "src/dispute/lib/Types.sol"; -import "src/dispute/lib/Errors.sol"; - -// Interfaces -import { IPermissionedDisputeGameV2 } from "interfaces/dispute/v2/IPermissionedDisputeGameV2.sol"; -import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; - -/// @title PermissionedDisputeGameV2_TestInit -/// @notice Reusable test initialization for `PermissionedDisputeGame` tests. -contract PermissionedDisputeGameV2_TestInit is DisputeGameFactory_TestInit { - /// @notice The type of the game being tested. - GameType internal immutable GAME_TYPE = GameTypes.PERMISSIONED_CANNON; - /// @notice Mock proposer key - address internal constant PROPOSER = address(0xfacade9); - /// @notice Mock challenger key - address internal constant CHALLENGER = address(0xfacadec); - - /// @dev The initial bond for the game. - uint256 internal initBond; - - /// @notice The implementation of the game. - IPermissionedDisputeGameV2 internal gameImpl; - /// @notice The `Clone` proxy of the game. - IPermissionedDisputeGameV2 internal gameProxy; - - /// @notice The extra data passed to the game for initialization. - bytes internal extraData; - - /// @notice The root claim of the game. - Claim internal rootClaim; - /// @notice An arbitrary root claim for testing. - Claim internal arbitaryRootClaim = Claim.wrap(bytes32(uint256(123))); - /// @notice Minimum bond value that covers all possible moves. - uint256 internal constant MIN_BOND = 50 ether; - - /// @notice The preimage of the absolute prestate claim - bytes internal absolutePrestateData; - /// @notice The absolute prestate of the trace. - Claim internal absolutePrestate; - /// @notice A valid l2BlockNumber that comes after the current anchor root block. - uint256 validL2BlockNumber; - - event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); - - function init(Claim _rootClaim, Claim _absolutePrestate, uint256 _l2BlockNumber) public { - // Set the time to a realistic date. - if (!isForkTest()) { - vm.warp(1690906994); - } - - // Fund the proposer on this fork. - vm.deal(PROPOSER, 100 ether); - - // Set the extra data for the game creation - extraData = abi.encode(_l2BlockNumber); - - (address _impl, AlphabetVM _vm,) = setupPermissionedDisputeGameV2(_absolutePrestate, PROPOSER, CHALLENGER); - gameImpl = IPermissionedDisputeGameV2(_impl); - - // Create a new game. - initBond = disputeGameFactory.initBonds(GAME_TYPE); - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.anchors, (GAME_TYPE)), - abi.encode(_rootClaim, 0) - ); - vm.prank(PROPOSER, PROPOSER); - gameProxy = IPermissionedDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _rootClaim, extraData))) - ); - - // Check immutables - assertEq(gameProxy.proposer(), PROPOSER); - assertEq(gameProxy.challenger(), CHALLENGER); - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - assertEq(gameProxy.absolutePrestate().raw(), _absolutePrestate.raw()); - assertEq(gameProxy.maxGameDepth(), 2 ** 3); - assertEq(gameProxy.splitDepth(), 2 ** 2); - assertEq(gameProxy.clockExtension().raw(), 3 hours); - assertEq(gameProxy.maxClockDuration().raw(), 3.5 days); - assertEq(address(gameProxy.weth()), address(delayedWeth)); - assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); - assertEq(address(gameProxy.vm()), address(_vm)); - assertEq(address(gameProxy.gameCreator()), PROPOSER); - assertEq(gameProxy.l2ChainId(), l2ChainId); - - // Label the proxy - vm.label(address(gameProxy), "PermissionedDisputeGame_Clone"); - } - - function setUp() public override { - absolutePrestateData = abi.encode(0); - absolutePrestate = _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData)), VMStatuses.UNFINISHED); - - super.setUp(); - - // Get the actual anchor roots - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2BlockNumber + 1; - rootClaim = Claim.wrap(Hash.unwrap(root)); - init({ _rootClaim: rootClaim, _absolutePrestate: absolutePrestate, _l2BlockNumber: validL2BlockNumber }); - } - - /// @dev Helper to return a pseudo-random claim - function _dummyClaim() internal view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(gasleft()))); - } - - /// @dev Helper to get the required bond for the given claim index. - function _getRequiredBond(uint256 _claimIndex) internal view returns (uint256 bond_) { - (,,,,, Position parent,) = gameProxy.claimData(_claimIndex); - Position pos = parent.move(true); - bond_ = gameProxy.getRequiredBond(pos); - } - - /// @dev Helper to change the VM status byte of a claim. - function _changeClaimStatus(Claim _claim, VMStatus _status) internal pure returns (Claim out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _claim), shl(248, _status)) - } - } - - fallback() external payable { } - - receive() external payable { } - - function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { - uint256 byteCount = src.length < dest.length ? src.length : dest.length; - for (uint256 i = 0; i < byteCount; i++) { - dest[i] = src[i]; - } - return dest; - } -} - -/// @title PermissionedDisputeGameV2_Version_Test -/// @notice Tests the `version` function of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGameV2_Version_Test is PermissionedDisputeGameV2_TestInit { - /// @notice Tests that the game's version function returns a string. - function test_version_works() public view { - assertTrue(bytes(gameProxy.version()).length > 0); - } -} - -/// @title PermissionedDisputeGameV2_Step_Test -/// @notice Tests the `step` function of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGameV2_Step_Test is PermissionedDisputeGameV2_TestInit { - /// @notice Tests that step works properly for the challenger. - function test_step_fromChallenger_succeeds() public { - validateStepForActor(CHALLENGER); - } - - /// @notice Tests that step works properly for the proposer. - function test_step_fromProposer_succeeds() public { - validateStepForActor(PROPOSER); - } - - function validateStepForActor(address actor) internal { - vm.deal(actor, 1_000 ether); - vm.startPrank(actor, actor); - - // Set up and perform the step - setupGameForStep(); - performStep(); - assertEq(gameProxy.claimDataLen(), 9); - - // Resolve the game and check that the expected actor countered the root claim - resolveGame(); - assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); - assertEq(gameProxy.resolvedAt().raw(), block.timestamp); - (, address counteredBy,,,,,) = gameProxy.claimData(0); - assertEq(counteredBy, actor); - - vm.stopPrank(); - } - - /// @notice Tests that step reverts for unauthorized addresses. - function test_step_notAuthorized_reverts(address _unauthorized) internal { - vm.assume(_unauthorized != PROPOSER && _unauthorized != CHALLENGER); - vm.deal(_unauthorized, 1_000 ether); - vm.deal(CHALLENGER, 1_000 ether); - - // Set up for the step using an authorized actor - vm.startPrank(CHALLENGER, CHALLENGER); - setupGameForStep(); - vm.stopPrank(); - - // Perform step with the unauthorized actor - vm.startPrank(_unauthorized, _unauthorized); - vm.expectRevert(BadAuth.selector); - performStep(); - - // Game should still be in progress, leaf claim should be missing - assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); - assertEq(gameProxy.claimDataLen(), 8); - - vm.stopPrank(); - } - - function setupGameForStep() internal { - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - - // Verify game state and add local data - assertEq(uint256(gameProxy.status()), uint256(GameStatus.IN_PROGRESS)); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - } - - function performStep() internal { - gameProxy.step(8, true, absolutePrestateData, hex""); - } - - function resolveGame() internal { - vm.warp(block.timestamp + gameProxy.maxClockDuration().raw() + 1); - gameProxy.resolveClaim(8, 0); - gameProxy.resolveClaim(7, 0); - gameProxy.resolveClaim(6, 0); - gameProxy.resolveClaim(5, 0); - gameProxy.resolveClaim(4, 0); - gameProxy.resolveClaim(3, 0); - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - } -} - -/// @title PermissionedDisputeGame_Initialize_Test -/// @notice Tests the initialization of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGameV2_Initialize_Test is PermissionedDisputeGameV2_TestInit { - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by extraData of the wrong length - function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { - // The `DisputeGameFactory` will pack the root claim and the extra data into a single - // array, which is enforced to be at least 64 bytes long. - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - // [0 bytes, 31 bytes] u [33 bytes, 23.5 KB] - _extraDataLen = bound(_extraDataLen, 0, 23_500); - if (_extraDataLen == 32) { - _extraDataLen++; - } - bytes memory _extraData = new bytes(_extraDataLen); - - // Assign the first 32 bytes in `extraData` to a valid L2 block number passed the starting - // block. - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - assembly { - mstore(add(_extraData, 0x20), add(startingL2Block, 1)) - } - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, _extraData))) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by additional immutable args data - function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { - (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); - - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - _extraByteCount = bound(_extraByteCount, 1, 23_500); - bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupPermissionedDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable( - address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) - ) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by missing immutable args data - function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { - (bytes memory correctArgs,,) = getPermissionedDisputeGameV2ImmutableArgs(absolutePrestate, PROPOSER, CHALLENGER); - - _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; - bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupPermissionedDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.prank(PROPOSER, PROPOSER); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IPermissionedDisputeGameV2( - payable( - address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) - ) - ); - } -} - -/// @title PermissionedDisputeGameV2_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the -/// `PermissionedDisputeGame` contract or are testing multiple functions at once. -contract PermissionedDisputeGameV2_Uncategorized_Test is PermissionedDisputeGameV2_TestInit { - /// @notice Tests that the proposer can create a permissioned dispute game. - function test_createGame_proposer_succeeds() public { - vm.prank(PROPOSER, PROPOSER); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the permissioned game cannot be created by the challenger. - function test_createGame_challenger_reverts() public { - vm.deal(CHALLENGER, initBond); - vm.prank(CHALLENGER, CHALLENGER); - vm.expectRevert(BadAuth.selector); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the permissioned game cannot be created by any address other than the - /// proposer. - function testFuzz_createGame_notProposer_reverts(address _p) public { - vm.assume(_p != PROPOSER); - - vm.deal(_p, initBond); - vm.prank(_p, _p); - vm.expectRevert(BadAuth.selector); - disputeGameFactory.create{ value: initBond }(GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber)); - } - - /// @notice Tests that the challenger can participate in a permissioned dispute game. - function test_participateInGame_challenger_succeeds() public { - vm.startPrank(CHALLENGER, CHALLENGER); - uint256 firstBond = _getRequiredBond(0); - vm.deal(CHALLENGER, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, Claim.wrap(0)); - uint256 secondBond = _getRequiredBond(1); - vm.deal(CHALLENGER, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: secondBond }(disputed, 1, Claim.wrap(0)); - uint256 thirdBond = _getRequiredBond(2); - vm.deal(CHALLENGER, thirdBond); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.move{ value: thirdBond }(disputed, 2, Claim.wrap(0), true); - vm.stopPrank(); - } - - /// @notice Tests that the proposer can participate in a permissioned dispute game. - function test_participateInGame_proposer_succeeds() public { - vm.startPrank(PROPOSER, PROPOSER); - uint256 firstBond = _getRequiredBond(0); - vm.deal(PROPOSER, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, Claim.wrap(0)); - uint256 secondBond = _getRequiredBond(1); - vm.deal(PROPOSER, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: secondBond }(disputed, 1, Claim.wrap(0)); - uint256 thirdBond = _getRequiredBond(2); - vm.deal(PROPOSER, thirdBond); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.move{ value: thirdBond }(disputed, 2, Claim.wrap(0), true); - vm.stopPrank(); - } - - /// @notice Tests that addresses that are not the proposer or challenger cannot participate in - /// a permissioned dispute game. - function test_participateInGame_notAuthorized_reverts(address _p) public { - vm.assume(_p != PROPOSER && _p != CHALLENGER); - - vm.startPrank(_p, _p); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(BadAuth.selector); - gameProxy.attack(disputed, 0, Claim.wrap(0)); - vm.expectRevert(BadAuth.selector); - gameProxy.defend(disputed, 0, Claim.wrap(0)); - vm.expectRevert(BadAuth.selector); - gameProxy.move(disputed, 0, Claim.wrap(0), true); - vm.expectRevert(BadAuth.selector); - gameProxy.step(0, true, absolutePrestateData, hex""); - vm.stopPrank(); - } -} From f6cd910f9d388fa03373e2ff717831cb4f872dbd Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 15:15:53 -0400 Subject: [PATCH 57/75] Merge FaultDisputeGame tests, use feature flag in setup --- .../test/dispute/DisputeGameFactory.t.sol | 18 +- .../test/dispute/FaultDisputeGame.t.sol | 250 +- .../test/dispute/v2/FaultDisputeGameV2.t.sol | 3207 ----------------- 3 files changed, 255 insertions(+), 3220 deletions(-) delete mode 100644 packages/contracts-bedrock/test/dispute/v2/FaultDisputeGameV2.t.sol diff --git a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol index 51dc7912efd9a..403f5b1428895 100644 --- a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol +++ b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol @@ -84,7 +84,7 @@ contract DisputeGameFactory_TestInit is CommonTest { Claim _absolutePrestate, AlphabetVM _vm, GameType _gameType, - uint256 l2ChainId + uint256 _l2ChainId ) internal view @@ -100,7 +100,7 @@ contract DisputeGameFactory_TestInit is CommonTest { vm: _vm, weth: delayedWeth, anchorStateRegistry: anchorStateRegistry, - l2ChainId: l2ChainId + l2ChainId: _l2ChainId }); } @@ -202,6 +202,18 @@ contract DisputeGameFactory_TestInit is CommonTest { function setupFaultDisputeGame(Claim _absolutePrestate) internal returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) + { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + return setupFaultDisputeGameV2(_absolutePrestate); + } else { + return setupFaultDisputeGameV1(_absolutePrestate); + } + } + + /// @notice Sets up a fault game implementation + function setupFaultDisputeGameV1(Claim _absolutePrestate) + internal + returns (address gameImpl_, AlphabetVM vm_, IPreimageOracle preimageOracle_) { (vm_, preimageOracle_) = _createVM(_absolutePrestate); gameImpl_ = DeployUtils.create1({ @@ -209,7 +221,7 @@ contract DisputeGameFactory_TestInit is CommonTest { _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, - (_getGameConstructorParams(_absolutePrestate, vm_, GameTypes.CANNON, 0)) + (_getGameConstructorParams(_absolutePrestate, vm_, GameTypes.CANNON, l2ChainId)) ) ) }); diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index e08a0da2130b6..04138e5efd083 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -28,6 +28,7 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; contract ClaimCreditReenter { @@ -120,6 +121,8 @@ contract BaseFaultDisputeGame_TestInit is DisputeGameFactory_TestInit { assertEq(address(gameProxy.weth()), address(delayedWeth)); assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(address(gameProxy.vm()), address(_vm)); + assertEq(address(gameProxy.gameCreator()), address(this)); + assertEq(gameProxy.l2ChainId(), l2ChainId); // Label the proxy vm.label(address(gameProxy), "FaultDisputeGame_Clone"); @@ -128,6 +131,14 @@ contract BaseFaultDisputeGame_TestInit is DisputeGameFactory_TestInit { fallback() external payable { } receive() external payable { } + + function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { + uint256 byteCount = src.length < dest.length ? src.length : dest.length; + for (uint256 i = 0; i < byteCount; i++) { + dest[i] = src[i]; + } + return dest; + } } /// @title FaultDisputeGame_TestInit @@ -151,9 +162,6 @@ contract FaultDisputeGame_TestInit is BaseFaultDisputeGame_TestInit { super.setUp(); - // Skip V1 tests when V2 dispute games are enabled to avoid game type conflicts - skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - // Get the actual anchor roots (Hash root, uint256 l2Bn) = anchorStateRegistry.getAnchorRoot(); validL2BlockNumber = l2Bn + 1; @@ -228,6 +236,11 @@ contract FaultDisputeGame_Version_Test is FaultDisputeGame_TestInit { /// @title FaultDisputeGame_Constructor_Test /// @notice Tests the constructor of the `FaultDisputeGame` contract. contract FaultDisputeGame_Constructor_Test is FaultDisputeGame_TestInit { + function setUp() public virtual override { + super.setUp(); + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the /// `MAX_GAME_DEPTH` parameter is greater than `LibPosition.MAX_POSITION_BITLEN - 1`. function testFuzz_constructor_maxDepthTooLarge_reverts(uint256 _maxGameDepth) public { @@ -476,6 +489,145 @@ contract FaultDisputeGame_Constructor_Test is FaultDisputeGame_TestInit { } } +/// @title FaultDisputeGame_Constructor_Test +/// @notice Tests the constructor of the `FaultDisputeGame` contract. +contract FaultDisputeGameV2_Constructor_Test is FaultDisputeGame_TestInit { + function setUp() public virtual override { + super.setUp(); + skipIfDevFeatureDisabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + } + + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the + /// `MAX_GAME_DEPTH` parameter is greater than `LibPosition.MAX_POSITION_BITLEN - 1`. + function testFuzz_constructor_maxDepthTooLarge_reverts(uint256 _maxGameDepth) public { + _maxGameDepth = bound(_maxGameDepth, LibPosition.MAX_POSITION_BITLEN, type(uint256).max - 1); + vm.expectRevert(MaxDepthTooLarge.selector); + DeployUtils.create1({ + _name: "FaultDisputeGameV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGameV2.__constructor__, + ( + IFaultDisputeGameV2.GameConstructorParams({ + gameType: GAME_TYPE, + maxGameDepth: _maxGameDepth, + splitDepth: _maxGameDepth + 1, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days) + }) + ) + ) + ) + }); + } + + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` + /// parameter is greater than or equal to the `MAX_GAME_DEPTH` + function testFuzz_constructor_invalidSplitDepth_reverts(uint256 _splitDepth) public { + uint256 maxGameDepth = 2 ** 3; + _splitDepth = bound(_splitDepth, maxGameDepth - 1, type(uint256).max); + vm.expectRevert(InvalidSplitDepth.selector); + DeployUtils.create1({ + _name: "FaultDisputeGameV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGameV2.__constructor__, + ( + IFaultDisputeGameV2.GameConstructorParams({ + gameType: GAME_TYPE, + maxGameDepth: maxGameDepth, + splitDepth: _splitDepth, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days) + }) + ) + ) + ) + }); + } + + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` + /// parameter is less than the minimum split depth (currently 2). + function testFuzz_constructor_lowSplitDepth_reverts(uint256 _splitDepth) public { + uint256 minSplitDepth = 2; + _splitDepth = bound(_splitDepth, 0, minSplitDepth - 1); + vm.expectRevert(InvalidSplitDepth.selector); + DeployUtils.create1({ + _name: "FaultDisputeGameV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGameV2.__constructor__, + ( + IFaultDisputeGameV2.GameConstructorParams({ + gameType: GAME_TYPE, + maxGameDepth: 2 ** 3, + splitDepth: _splitDepth, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days) + }) + ) + ) + ) + }); + } + + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when clock + /// extension * 2 is greater than the max clock duration. + function testFuzz_constructor_clockExtensionTooLong_reverts( + uint64 _maxClockDuration, + uint64 _clockExtension + ) + public + { + // Force the clock extension * 2 to be greater than the max clock duration, but keep things + // within bounds of the uint64 type. + _maxClockDuration = uint64(bound(_maxClockDuration, 0, type(uint64).max / 2 - 1)); + _clockExtension = uint64(bound(_clockExtension, _maxClockDuration / 2 + 1, type(uint64).max / 2)); + + vm.expectRevert(InvalidClockExtension.selector); + DeployUtils.create1({ + _name: "FaultDisputeGameV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGameV2.__constructor__, + ( + IFaultDisputeGameV2.GameConstructorParams({ + gameType: GAME_TYPE, + maxGameDepth: 16, + splitDepth: 8, + clockExtension: Duration.wrap(_clockExtension), + maxClockDuration: Duration.wrap(_maxClockDuration) + }) + ) + ) + ) + }); + } + + /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_gameType` + /// parameter is set to the reserved `type(uint32).max` game type. + function test_constructor_reservedGameType_reverts() public { + vm.expectRevert(ReservedGameType.selector); + DeployUtils.create1({ + _name: "FaultDisputeGameV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGameV2.__constructor__, + ( + IFaultDisputeGameV2.GameConstructorParams({ + gameType: GameType.wrap(type(uint32).max), + maxGameDepth: 16, + splitDepth: 8, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days) + }) + ) + ) + ) + }); + } +} + /// @title FaultDisputeGame_Initialize_Test /// @notice Tests the initialization of the `FaultDisputeGame` contract. contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { @@ -511,9 +663,9 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { assertEq(delayedWeth.balanceOf(address(gameProxy)), _value); } - /// @notice Tests that the game cannot be initialized with extra data of the incorrect length - /// (must be 32 bytes) - function testFuzz_initialize_badExtraData_reverts(uint256 _extraDataLen) public { + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by extraData of the wrong length + function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { // The `DisputeGameFactory` will pack the root claim and the extra data into a single // array, which is enforced to be at least 64 bytes long. // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the @@ -540,6 +692,55 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { ); } + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by additional immutable args data + function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { + skipIfDevFeatureDisabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); + + // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the + // contract size limit in this test, as CWIA proxies store the immutable args in their + // bytecode. + _extraByteCount = bound(_extraByteCount, 1, 23_500); + bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); + // Copy correct args into immutable args + copyBytes(correctArgs, immutableArgs); + + // Set up dispute game implementation with target immutableArgs + setupFaultDisputeGameV2(immutableArgs); + + Claim claim = _dummyClaim(); + vm.expectRevert(IFaultDisputeGame.BadExtraData.selector); + gameProxy = IFaultDisputeGame( + payable( + address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) + ) + ); + } + + /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length + /// caused by missing immutable args data + function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { + skipIfDevFeatureDisabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); + (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); + + _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; + bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); + // Copy correct args into immutable args + copyBytes(correctArgs, immutableArgs); + + // Set up dispute game implementation with target immutableArgs + setupFaultDisputeGameV2(immutableArgs); + + Claim claim = _dummyClaim(); + vm.expectRevert(IFaultDisputeGame.BadExtraData.selector); + gameProxy = IFaultDisputeGame( + payable( + address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) + ) + ); + } + /// @notice Tests that the game is initialized with the correct data. function test_initialize_correctData_succeeds() public view { // Assert that the root claim is initialized correctly. @@ -588,6 +789,35 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { vm.expectRevert(AlreadyInitialized.selector); gameProxy.initialize(); } + + /// @notice Tests that initialization reverts when oracle challenge period is too large. + /// @dev V2 validates oracle challenge period during initialize(), not constructor + function testFuzz_initialize_oracleChallengePeriodTooLarge_reverts(uint256 _challengePeriod) public { + // Bound to values larger than uint64.max + _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); + + // Get the current AlphabetVM from the setup + (, AlphabetVM vm_,) = setupFaultDisputeGameV2(absolutePrestate); + + // Mock the VM's oracle to return invalid challenge period + vm.mockCall( + address(vm_.oracle()), abi.encodeCall(IPreimageOracle.challengePeriod, ()), abi.encode(_challengePeriod) + ); + + // Expect the initialize call to revert with InvalidChallengePeriod + vm.expectRevert(InvalidChallengePeriod.selector); + + // Create game via factory - initialize() is called automatically and should revert + gameProxy = IFaultDisputeGame( + payable( + address( + disputeGameFactory.create{ value: initBond }( + GAME_TYPE, _dummyClaim(), abi.encode(validL2BlockNumber) + ) + ) + ) + ); + } } /// @title FaultDisputeGame_Step_Test @@ -670,7 +900,7 @@ contract FaultDisputeGame_Step_Test is FaultDisputeGame_TestInit { gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); bytes memory claimData7 = abi.encode(7, 7); - Claim postState_ = Claim.wrap(gameImpl.vm().step(claimData7, hex"", bytes32(0))); + Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); (,,,, disputed,,) = gameProxy.claimData(5); gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); @@ -706,7 +936,7 @@ contract FaultDisputeGame_Step_Test is FaultDisputeGame_TestInit { gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, claim5); (,,,, disputed,,) = gameProxy.claimData(6); gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - Claim postState_ = Claim.wrap(gameImpl.vm().step(claimData5, hex"", bytes32(0))); + Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData5, hex"", bytes32(0))); (,,,, disputed,,) = gameProxy.claimData(7); gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, postState_); gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); @@ -734,7 +964,7 @@ contract FaultDisputeGame_Step_Test is FaultDisputeGame_TestInit { gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); bytes memory claimData7 = abi.encode(5, 5); - Claim postState_ = Claim.wrap(gameImpl.vm().step(claimData7, hex"", bytes32(0))); + Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); (,,,, disputed,,) = gameProxy.claimData(5); gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); @@ -770,7 +1000,7 @@ contract FaultDisputeGame_Step_Test is FaultDisputeGame_TestInit { bytes memory claimData7 = abi.encode(5, 5); Claim claim7 = Claim.wrap(keccak256(claimData7)); - Claim postState_ = Claim.wrap(gameImpl.vm().step(claimData7, hex"", bytes32(0))); + Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); (,,,, disputed,,) = gameProxy.claimData(5); gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); diff --git a/packages/contracts-bedrock/test/dispute/v2/FaultDisputeGameV2.t.sol b/packages/contracts-bedrock/test/dispute/v2/FaultDisputeGameV2.t.sol deleted file mode 100644 index feab844563187..0000000000000 --- a/packages/contracts-bedrock/test/dispute/v2/FaultDisputeGameV2.t.sol +++ /dev/null @@ -1,3207 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Testing -import { Vm } from "forge-std/Vm.sol"; -import { DisputeGameFactory_TestInit } from "test/dispute/DisputeGameFactory.t.sol"; -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; -import { stdError } from "forge-std/StdError.sol"; - -// Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -// Contracts -import { DisputeActor, HonestDisputeActor } from "test/actors/FaultDisputeActors.sol"; - -// Libraries -import { Types } from "src/libraries/Types.sol"; -import { Hashing } from "src/libraries/Hashing.sol"; -import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; -import { LibClock } from "src/dispute/lib/LibUDT.sol"; -import { LibPosition } from "src/dispute/lib/LibPosition.sol"; -import "src/dispute/lib/Types.sol"; -import "src/dispute/lib/Errors.sol"; - -// Interfaces -import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; -import { IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; -import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; -import { IFaultDisputeGameV2 } from "interfaces/dispute/v2/IFaultDisputeGameV2.sol"; - -contract ClaimCreditReenter { - Vm internal immutable vm; - IFaultDisputeGameV2 internal immutable GAME; - uint256 public numCalls; - - constructor(IFaultDisputeGameV2 _gameProxy, Vm _vm) { - GAME = _gameProxy; - vm = _vm; - } - - function claimCredit(address _recipient) public { - numCalls += 1; - if (numCalls > 1) { - vm.expectRevert(NoCreditToClaim.selector); - } - GAME.claimCredit(_recipient); - } - - receive() external payable { - if (numCalls == 5) { - return; - } - claimCredit(address(this)); - } -} - -/// @notice Helper to change the VM status byte of a claim. -function _changeClaimStatus(Claim _claim, VMStatus _status) pure returns (Claim out_) { - assembly { - out_ := or(and(not(shl(248, 0xFF)), _claim), shl(248, _status)) - } -} - -/// @title BaseFaultDisputeGameV2_TestInit -/// @notice Base test initializer that can be used by other contracts outside of this test suite. -contract BaseFaultDisputeGameV2_TestInit is DisputeGameFactory_TestInit { - /// @dev The type of the game being tested. - GameType internal immutable GAME_TYPE = GameTypes.CANNON; - - /// @dev The initial bond for the game. - uint256 internal initBond; - - /// @dev The implementation of the game. - IFaultDisputeGameV2 internal gameImpl; - /// @dev The `Clone` proxy of the game. - IFaultDisputeGameV2 internal gameProxy; - - /// @dev The extra data passed to the game for initialization. - bytes internal extraData; - - event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); - event GameClosed(BondDistributionMode bondDistributionMode); - - event ReceiveETH(uint256 amount); - - function init(Claim rootClaim, Claim absolutePrestate, uint256 l2BlockNumber) public { - // Set the time to a realistic date. - if (!isForkTest()) { - vm.warp(1690906994); - } - - // Set the extra data for the game creation - extraData = abi.encode(l2BlockNumber); - - (address _impl, AlphabetVM _vm,) = setupFaultDisputeGameV2(absolutePrestate); - gameImpl = IFaultDisputeGameV2(_impl); - - // Set the init bond for the given game type. - initBond = disputeGameFactory.initBonds(GAME_TYPE); - - // Warp ahead of the game retirement timestamp if needed. - if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { - vm.warp(anchorStateRegistry.retirementTimestamp() + 1); - } - - // Create a new game. - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, rootClaim, extraData))) - ); - - // Check immutables - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - assertEq(gameProxy.absolutePrestate().raw(), absolutePrestate.raw()); - assertEq(gameProxy.maxGameDepth(), 2 ** 3); - assertEq(gameProxy.splitDepth(), 2 ** 2); - assertEq(gameProxy.clockExtension().raw(), 3 hours); - assertEq(gameProxy.maxClockDuration().raw(), 3.5 days); - assertEq(address(gameProxy.weth()), address(delayedWeth)); - assertEq(address(gameProxy.anchorStateRegistry()), address(anchorStateRegistry)); - assertEq(address(gameProxy.vm()), address(_vm)); - assertEq(address(gameProxy.gameCreator()), address(this)); - assertEq(gameProxy.l2ChainId(), l2ChainId); - - // Label the proxy - vm.label(address(gameProxy), "FaultDisputeGame_Clone"); - } - - fallback() external payable { } - - receive() external payable { } - - function copyBytes(bytes memory src, bytes memory dest) internal pure returns (bytes memory) { - uint256 byteCount = src.length < dest.length ? src.length : dest.length; - for (uint256 i = 0; i < byteCount; i++) { - dest[i] = src[i]; - } - return dest; - } -} - -/// @title FaultDisputeGameV2_TestInit -/// @notice Reusable test initialization for `FaultDisputeGame` tests. -contract FaultDisputeGameV2_TestInit is BaseFaultDisputeGameV2_TestInit { - /// @dev The root claim of the game. - Claim internal ROOT_CLAIM; - /// @dev An arbitrary root claim for testing. - Claim internal arbitaryRootClaim = Claim.wrap(bytes32(uint256(123))); - - /// @dev The preimage of the absolute prestate claim - bytes internal absolutePrestateData; - /// @dev The absolute prestate of the trace. - Claim internal absolutePrestate; - /// @dev A valid l2BlockNumber that comes after the current anchor root block. - uint256 internal validL2BlockNumber; - - function setUp() public virtual override { - absolutePrestateData = abi.encode(0); - absolutePrestate = _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData)), VMStatuses.UNFINISHED); - - super.setUp(); - - // Get the actual anchor roots - (Hash root, uint256 l2Bn) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2Bn + 1; - - ROOT_CLAIM = Claim.wrap(Hash.unwrap(root)); - - super.init({ rootClaim: ROOT_CLAIM, absolutePrestate: absolutePrestate, l2BlockNumber: validL2BlockNumber }); - } - - /// @notice Helper to generate a mock RLP encoded header (with only a real block number) & an - /// output root proof. - function _generateOutputRootProof( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - bytes memory _l2BlockNumber - ) - internal - pure - returns (Types.OutputRootProof memory proof_, bytes32 root_, bytes memory rlp_) - { - // L2 Block header - bytes[] memory rawHeaderRLP = new bytes[](9); - rawHeaderRLP[0] = hex"83FACADE"; - rawHeaderRLP[1] = hex"83FACADE"; - rawHeaderRLP[2] = hex"83FACADE"; - rawHeaderRLP[3] = hex"83FACADE"; - rawHeaderRLP[4] = hex"83FACADE"; - rawHeaderRLP[5] = hex"83FACADE"; - rawHeaderRLP[6] = hex"83FACADE"; - rawHeaderRLP[7] = hex"83FACADE"; - rawHeaderRLP[8] = RLPWriter.writeBytes(_l2BlockNumber); - rlp_ = RLPWriter.writeList(rawHeaderRLP); - - // Output root - proof_ = Types.OutputRootProof({ - version: 0, - stateRoot: _storageRoot, - messagePasserStorageRoot: _withdrawalRoot, - latestBlockhash: keccak256(rlp_) - }); - root_ = Hashing.hashOutputRootProof(proof_); - } - - /// @notice Helper to get the required bond for the given claim index. - function _getRequiredBond(uint256 _claimIndex) internal view returns (uint256 bond_) { - (,,,,, Position parent,) = gameProxy.claimData(_claimIndex); - Position pos = parent.move(true); - bond_ = gameProxy.getRequiredBond(pos); - } - - /// @notice Helper to return a pseudo-random claim - function _dummyClaim() internal view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(gasleft()))); - } - - /// @notice Helper to get the localized key for an identifier in the context of the game proxy. - function _getKey(uint256 _ident, bytes32 _localContext) internal view returns (bytes32) { - bytes32 h = keccak256(abi.encode(_ident | (1 << 248), address(gameProxy), _localContext)); - return bytes32((uint256(h) & ~uint256(0xFF << 248)) | (1 << 248)); - } -} - -/// @title FaultDisputeGame_Version_Test -/// @notice Tests the `version` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Version_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's version function returns a string. - function test_version_works() public view { - assertTrue(bytes(gameProxy.version()).length > 0); - } -} - -/// @title FaultDisputeGame_Constructor_Test -/// @notice Tests the constructor of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Constructor_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the - /// `MAX_GAME_DEPTH` parameter is greater than `LibPosition.MAX_POSITION_BITLEN - 1`. - function testFuzz_constructor_maxDepthTooLarge_reverts(uint256 _maxGameDepth) public { - _maxGameDepth = bound(_maxGameDepth, LibPosition.MAX_POSITION_BITLEN, type(uint256).max - 1); - vm.expectRevert(MaxDepthTooLarge.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - ( - IFaultDisputeGameV2.GameConstructorParams({ - gameType: GAME_TYPE, - maxGameDepth: _maxGameDepth, - splitDepth: _maxGameDepth + 1, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - }) - ) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` - /// parameter is greater than or equal to the `MAX_GAME_DEPTH` - function testFuzz_constructor_invalidSplitDepth_reverts(uint256 _splitDepth) public { - uint256 maxGameDepth = 2 ** 3; - _splitDepth = bound(_splitDepth, maxGameDepth - 1, type(uint256).max); - vm.expectRevert(InvalidSplitDepth.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - ( - IFaultDisputeGameV2.GameConstructorParams({ - gameType: GAME_TYPE, - maxGameDepth: maxGameDepth, - splitDepth: _splitDepth, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - }) - ) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` - /// parameter is less than the minimum split depth (currently 2). - function testFuzz_constructor_lowSplitDepth_reverts(uint256 _splitDepth) public { - uint256 minSplitDepth = 2; - _splitDepth = bound(_splitDepth, 0, minSplitDepth - 1); - vm.expectRevert(InvalidSplitDepth.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - ( - IFaultDisputeGameV2.GameConstructorParams({ - gameType: GAME_TYPE, - maxGameDepth: 2 ** 3, - splitDepth: _splitDepth, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - }) - ) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when clock - /// extension * 2 is greater than the max clock duration. - function testFuzz_constructor_clockExtensionTooLong_reverts( - uint64 _maxClockDuration, - uint64 _clockExtension - ) - public - { - // Force the clock extension * 2 to be greater than the max clock duration, but keep things - // within bounds of the uint64 type. - _maxClockDuration = uint64(bound(_maxClockDuration, 0, type(uint64).max / 2 - 1)); - _clockExtension = uint64(bound(_clockExtension, _maxClockDuration / 2 + 1, type(uint64).max / 2)); - - vm.expectRevert(InvalidClockExtension.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - ( - IFaultDisputeGameV2.GameConstructorParams({ - gameType: GAME_TYPE, - maxGameDepth: 16, - splitDepth: 8, - clockExtension: Duration.wrap(_clockExtension), - maxClockDuration: Duration.wrap(_maxClockDuration) - }) - ) - ) - ) - }); - } - - /// @notice Tests that the constructor of the `FaultDisputeGame` reverts when the `_gameType` - /// parameter is set to the reserved `type(uint32).max` game type. - function test_constructor_reservedGameType_reverts() public { - vm.expectRevert(ReservedGameType.selector); - DeployUtils.create1({ - _name: "FaultDisputeGameV2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGameV2.__constructor__, - ( - IFaultDisputeGameV2.GameConstructorParams({ - gameType: GameType.wrap(type(uint32).max), - maxGameDepth: 16, - splitDepth: 8, - clockExtension: Duration.wrap(3 hours), - maxClockDuration: Duration.wrap(3.5 days) - }) - ) - ) - ) - }); - } -} - -/// @title FaultDisputeGame_Initialize_Test -/// @notice Tests the initialization of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Initialize_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game cannot be initialized with an output root that commits to <= - /// the configured starting block number - function testFuzz_initialize_cannotProposeGenesis_reverts(uint256 _blockNumber) public { - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - _blockNumber = bound(_blockNumber, 0, startingL2Block); - - Claim claim = _dummyClaim(); - vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, claim)); - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(_blockNumber)))) - ); - } - - /// @notice Tests that the proxy receives ETH from the dispute game factory. - function test_initialize_receivesETH_succeeds() public { - uint256 _value = disputeGameFactory.initBonds(GAME_TYPE); - vm.deal(address(this), _value); - - assertEq(address(gameProxy).balance, 0); - gameProxy = IFaultDisputeGameV2( - payable( - address( - disputeGameFactory.create{ value: _value }( - GAME_TYPE, arbitaryRootClaim, abi.encode(validL2BlockNumber) - ) - ) - ) - ); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), _value); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by extraData of the wrong length - function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { - // The `DisputeGameFactory` will pack the root claim and the extra data into a single - // array, which is enforced to be at least 64 bytes long. - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - // [0 bytes, 31 bytes] u [33 bytes, 23.5 KB] - _extraDataLen = bound(_extraDataLen, 0, 23_500); - if (_extraDataLen == 32) { - _extraDataLen++; - } - bytes memory _extraData = new bytes(_extraDataLen); - - // Assign the first 32 bytes in `extraData` to a valid L2 block number passed the starting - // block. - (, uint256 startingL2Block) = gameProxy.startingOutputRoot(); - assembly { - mstore(add(_extraData, 0x20), add(startingL2Block, 1)) - } - - Claim claim = _dummyClaim(); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IFaultDisputeGameV2( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, _extraData))) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by additional immutable args data - function test_initialize_extraImmutableArgsBytes_reverts(uint256 _extraByteCount) public { - (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); - - // We bound the upper end to 23.5KB to ensure that the minimal proxy never surpasses the - // contract size limit in this test, as CWIA proxies store the immutable args in their - // bytecode. - _extraByteCount = bound(_extraByteCount, 1, 23_500); - bytes memory immutableArgs = new bytes(_extraByteCount + correctArgs.length); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupFaultDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IFaultDisputeGameV2( - payable( - address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) - ) - ); - } - - /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length - /// caused by missing immutable args data - function test_initialize_missingImmutableArgsBytes_reverts(uint256 _truncatedByteCount) public { - (bytes memory correctArgs,,) = getFaultDisputeGameV2ImmutableArgs(absolutePrestate); - - _truncatedByteCount = (_truncatedByteCount % correctArgs.length) + 1; - bytes memory immutableArgs = new bytes(correctArgs.length - _truncatedByteCount); - // Copy correct args into immutable args - copyBytes(correctArgs, immutableArgs); - - // Set up dispute game implementation with target immutableArgs - setupFaultDisputeGameV2(immutableArgs); - - Claim claim = _dummyClaim(); - vm.expectRevert(IFaultDisputeGameV2.BadExtraData.selector); - gameProxy = IFaultDisputeGameV2( - payable( - address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, claim, abi.encode(validL2BlockNumber))) - ) - ); - } - - /// @notice Tests that the game is initialized with the correct data. - function test_initialize_correctData_succeeds() public view { - // Assert that the root claim is initialized correctly. - ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ) = gameProxy.claimData(0); - assertEq(parentIndex, type(uint32).max); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, initBond); - assertEq(claim.raw(), ROOT_CLAIM.raw()); - assertEq(position.raw(), 1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // Assert that the `createdAt` timestamp is correct. - assertEq(gameProxy.createdAt().raw(), block.timestamp); - - // Assert that the blockhash provided is correct. - assertEq(gameProxy.l1Head().raw(), blockhash(block.number - 1)); - } - - /// @notice Tests that the game cannot be initialized when the anchor root is not found. - function test_initialize_anchorRootNotFound_reverts() public { - // Mock the AnchorStateRegistry to return a zero root. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), - abi.encode(Hash.wrap(bytes32(0)), 0) - ); - - // Creation should fail. - vm.expectRevert(AnchorRootNotFound.selector); - gameProxy = IFaultDisputeGameV2( - payable( - address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _dummyClaim(), new bytes(uint256(32)))) - ) - ); - } - - /// @notice Tests that the game cannot be initialized twice. - function test_initialize_onlyOnce_succeeds() public { - vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(); - } - - /// @notice Tests that initialization reverts when oracle challenge period is too large. - /// @dev V2 validates oracle challenge period during initialize(), not constructor - function testFuzz_initialize_oracleChallengePeriodTooLarge_reverts(uint256 _challengePeriod) public { - // Bound to values larger than uint64.max - _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); - - // Get the current AlphabetVM from the setup - (, AlphabetVM vm_,) = setupFaultDisputeGameV2(absolutePrestate); - - // Mock the VM's oracle to return invalid challenge period - vm.mockCall( - address(vm_.oracle()), abi.encodeCall(IPreimageOracle.challengePeriod, ()), abi.encode(_challengePeriod) - ); - - // Expect the initialize call to revert with InvalidChallengePeriod - vm.expectRevert(InvalidChallengePeriod.selector); - - // Create game via factory - initialize() is called automatically and should revert - gameProxy = IFaultDisputeGameV2( - payable( - address( - disputeGameFactory.create{ value: initBond }( - GAME_TYPE, _dummyClaim(), abi.encode(validL2BlockNumber) - ) - ) - ) - ); - } -} - -/// @title FaultDisputeGame_Step_Test -/// @notice Tests the step functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Step_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that a claim cannot be stepped against twice. - function test_step_duplicateStep_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - vm.expectRevert(DuplicateStep.selector); - gameProxy.step(8, true, absolutePrestateData, hex""); - } - - /// @notice Tests that successfully step with true attacking claim when there is a true defend - /// claim(claim5) in the middle of the dispute game. - function test_stepAttackDummyClaim_defendTrueClaimInTheMiddle_succeeds() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bytes memory claimData5 = abi.encode(5, 5); - Claim claim5 = Claim.wrap(keccak256(claimData5)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, claim5); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, claimData5, hex""); - } - - /// @notice Tests that successfully step with true defend claim when there is a true defend - /// claim(claim7) in the middle of the dispute game. - function test_stepDefendDummyClaim_defendTrueClaimInTheMiddle_succeeds() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(7, 7); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, postState_); - (,,,, disputed,,) = gameProxy.claimData(7); - - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, Claim.wrap(keccak256(claimData7))); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, false, claimData7, hex""); - } - - /// @notice Tests that step reverts with false attacking claim when there is a true defend - /// claim(claim5) in the middle of the dispute game. - function test_stepAttackTrueClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bytes memory claimData5 = abi.encode(5, 5); - Claim claim5 = Claim.wrap(keccak256(claimData5)); - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, claim5); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData5, hex"", bytes32(0))); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, postState_); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, true, claimData5, hex""); - } - - /// @notice Tests that step reverts with false defending claim when there is a true defend - /// claim(postState_) in the middle of the dispute game. - function test_stepDefendDummyClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(5, 5); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - - bytes memory _dummyClaimData = abi.encode(gasleft(), gasleft()); - Claim dummyClaim7 = Claim.wrap(keccak256(_dummyClaimData)); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, dummyClaim7); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, false, _dummyClaimData, hex""); - } - - /// @notice Tests that step reverts with true defending claim when there is a true defend - /// claim(postState_) in the middle of the dispute game. - function test_stepDefendTrueClaim_defendTrueClaimInTheMiddle_reverts() public { - // Give the test contract some ether - vm.deal(address(this), 1000 ether); - - // Make claims all the way down the tree. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - bytes memory claimData7 = abi.encode(5, 5); - Claim claim7 = Claim.wrap(keccak256(claimData7)); - Claim postState_ = Claim.wrap(gameProxy.vm().step(claimData7, hex"", bytes32(0))); - - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, postState_); - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.defend{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, claim7); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - - vm.expectRevert(ValidStep.selector); - gameProxy.step(8, false, claimData7, hex""); - } -} - -/// @title FaultDisputeGame_Move_Test -/// @notice Tests the move functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Move_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that a move while the game status is not `IN_PROGRESS` causes the call to - /// revert with the `GameNotInProgress` error - function test_move_gameNotInProgress_reverts() public { - uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS); - - // Replace the game status in storage. It exists in slot 0 at offset 16. - uint256 slot = uint256(vm.load(address(gameProxy), bytes32(0))); - uint256 offset = 16 << 3; - uint256 mask = 0xFF << offset; - // Replace the byte in the slot value with the challenger wins status. - slot = (slot & ~mask) | (chalWins << offset); - vm.store(address(gameProxy), bytes32(0), bytes32(slot)); - - // Ensure that the game status was properly updated. - GameStatus status = gameProxy.status(); - assertEq(uint256(status), chalWins); - - (,,,, Claim root,,) = gameProxy.claimData(0); - // Attempt to make a move. Should revert. - vm.expectRevert(GameNotInProgress.selector); - gameProxy.attack(root, 0, Claim.wrap(0)); - } - - /// @notice Tests that an attempt to defend the root claim reverts with the - /// `CannotDefendRootClaim` error. - function test_move_defendRoot_reverts() public { - (,,,, Claim root,,) = gameProxy.claimData(0); - vm.expectRevert(CannotDefendRootClaim.selector); - gameProxy.defend(root, 0, _dummyClaim()); - } - - /// @notice Tests that an attempt to move against a claim that does not exist reverts with the - /// `ParentDoesNotExist` error. - function test_move_nonExistentParent_reverts() public { - Claim claim = _dummyClaim(); - - // Expect an out of bounds revert for an attack - vm.expectRevert(stdError.indexOOBError); - gameProxy.attack(_dummyClaim(), 1, claim); - - // Expect an out of bounds revert for a defense - vm.expectRevert(stdError.indexOOBError); - gameProxy.defend(_dummyClaim(), 1, claim); - } - - /// @notice Tests that an attempt to move at the maximum game depth reverts with the - /// `GameDepthExceeded` error. - function test_move_gameDepthExceeded_reverts() public { - Claim claim = _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC); - - uint256 maxDepth = gameProxy.maxGameDepth(); - - for (uint256 i = 0; i <= maxDepth; i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - // At the max game depth, the `_move` function should revert with - // the `GameDepthExceeded` error. - if (i == maxDepth) { - vm.expectRevert(GameDepthExceeded.selector); - gameProxy.attack{ value: 100 ether }(disputed, i, claim); - } else { - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - } - } - - /// @notice Tests that a move made after the clock time has exceeded reverts with the - /// `ClockTimeExceeded` error. - function test_move_clockTimeExceeded_reverts() public { - // Warp ahead past the clock time for the first move (3 1/2 days) - vm.warp(block.timestamp + 3 days + 12 hours + 1); - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(ClockTimeExceeded.selector); - gameProxy.attack{ value: bond }(disputed, 0, _dummyClaim()); - } - - /// @notice Static unit test for the correctness of the chess clock incrementation. - function test_move_clockCorrectness_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - Claim claim = _dummyClaim(); - - vm.warp(block.timestamp + 15); - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - (,,,,,, clock) = gameProxy.claimData(1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(15), Timestamp.wrap(uint64(block.timestamp))).raw()); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, claim); - (,,,,,, clock) = gameProxy.claimData(2); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(10), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // We are at the split depth, so we need to set the status byte of the claim for the next - // move. - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, claim); - (,,,,,, clock) = gameProxy.claimData(3); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(25), Timestamp.wrap(uint64(block.timestamp))).raw()); - - vm.warp(block.timestamp + 10); - bond = _getRequiredBond(3); - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: bond }(disputed, 3, claim); - (,,,,,, clock) = gameProxy.claimData(4); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(20), Timestamp.wrap(uint64(block.timestamp))).raw()); - } - - /// @notice Tests that the standard clock extension is triggered for a move that is not the - /// split depth or the max game depth. - function test_move_standardClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Warp ahead so that the next move will trigger a clock extension. We warp to the very - // first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - clockExtension + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(0); - (,,,, disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - (,,,,,, clock) = gameProxy.claimData(1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension); - - // Warp ahead again so that clock extensions will also trigger for the other team. Here we - // only warp to the clockExtension time because we'll be warping ahead by one second during - // each additional move. - vm.warp(block.timestamp + halfGameDuration - clockExtension); - - // Work our way down to the split depth. - for (uint256 i = 1; i < splitDepth - 2; i++) { - // Warp ahead by one second so that the next move will trigger a clock extension. - vm.warp(block.timestamp + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - (,,,,,, clock) = gameProxy.claimData(i + 1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension); - } - } - - function test_move_splitDepthClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Work our way down to the split depth without moving ahead in time, we don't care about - // the exact clock here, just don't want take the clock below the clock extension time that - // we're trying to test here. - for (uint256 i = 0; i < splitDepth - 2; i++) { - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - } - - // Warp ahead to the very first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - clockExtension * 2 + 1 seconds); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(splitDepth - 2); - (,,,, disputed,,) = gameProxy.claimData(splitDepth - 2); - gameProxy.attack{ value: bond }(disputed, splitDepth - 2, claim); - (,,,,,, clock) = gameProxy.claimData(splitDepth - 1); - - // The clock should have been pushed back to the clock extension time. - assertEq(clock.duration().raw(), halfGameDuration - clockExtension * 2); - } - - function test_move_maxGameDepthClockExtension_succeeds() public { - (,,,,,, Clock clock) = gameProxy.claimData(0); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))).raw()); - - uint256 bond; - Claim disputed; - Claim claim = _dummyClaim(); - uint256 splitDepth = gameProxy.splitDepth(); - uint64 halfGameDuration = gameProxy.maxClockDuration().raw(); - uint64 clockExtension = gameProxy.clockExtension().raw(); - - // Work our way down to the split depth without moving ahead in time, we don't care about - // the exact clock here, just don't want take the clock below the clock extension time that - // we're trying to test here. - for (uint256 i = 0; i < gameProxy.maxGameDepth() - 2; i++) { - bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, claim); - - // Change the claim status when we're crossing the split depth. - if (i == splitDepth - 2) { - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - } - } - - // Warp ahead to the very first timestamp where a clock extension should be triggered. - vm.warp(block.timestamp + halfGameDuration - (clockExtension + gameProxy.vm().oracle().challengePeriod()) + 1); - - // Execute a move that should cause a clock extension. - bond = _getRequiredBond(gameProxy.maxGameDepth() - 2); - (,,,, disputed,,) = gameProxy.claimData(gameProxy.maxGameDepth() - 2); - gameProxy.attack{ value: bond }(disputed, gameProxy.maxGameDepth() - 2, claim); - (,,,,,, clock) = gameProxy.claimData(gameProxy.maxGameDepth() - 1); - - // The clock should have been pushed back to the clock extension time. - assertEq( - clock.duration().raw(), halfGameDuration - (clockExtension + gameProxy.vm().oracle().challengePeriod()) - ); - } - - /// @notice Tests that an identical claim cannot be made twice. The duplicate claim attempt - /// should revert with the `ClaimAlreadyExists` error. - function test_move_duplicateClaim_reverts() public { - Claim claim = _dummyClaim(); - - // Make the first move. This should succeed. - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claim); - - // Attempt to make the same move again. - vm.expectRevert(ClaimAlreadyExists.selector); - gameProxy.attack{ value: bond }(disputed, 0, claim); - } - - /// @notice Static unit test asserting that identical claims at the same position can be made - /// in different subgames. - function test_move_duplicateClaimsDifferentSubgames_succeeds() public { - Claim claimA = _dummyClaim(); - Claim claimB = _dummyClaim(); - - // Make the first moves. This should succeed. - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, claimA); - gameProxy.attack{ value: bond }(disputed, 0, claimB); - - // Perform an attack at the same position with the same claim value in both subgames. - // These both should succeed. - bond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, claimA); - bond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, claimA); - } - - /// @notice Static unit test for the correctness of an opening attack. - function test_move_simpleAttack_succeeds() public { - // Warp ahead 5 seconds. - vm.warp(block.timestamp + 5); - - Claim counter = _dummyClaim(); - - // Perform the attack. - uint256 reqBond = _getRequiredBond(0); - vm.expectEmit(true, true, true, false); - emit Move(0, counter, address(this)); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: reqBond }(disputed, 0, counter); - - // Grab the claim data of the attack. - ( - uint32 parentIndex, - address counteredBy, - address claimant, - uint128 bond, - Claim claim, - Position position, - Clock clock - ) = gameProxy.claimData(1); - - // Assert correctness of the attack claim's data. - assertEq(parentIndex, 0); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, reqBond); - assertEq(claim.raw(), counter.raw()); - assertEq(position.raw(), Position.wrap(1).move(true).raw()); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp))).raw()); - - // Grab the claim data of the parent. - (parentIndex, counteredBy, claimant, bond, claim, position, clock) = gameProxy.claimData(0); - - // Assert correctness of the parent claim's data. - assertEq(parentIndex, type(uint32).max); - assertEq(counteredBy, address(0)); - assertEq(claimant, address(this)); - assertEq(bond, initBond); - assertEq(claim.raw(), ROOT_CLAIM.raw()); - assertEq(position.raw(), 1); - assertEq(clock.raw(), LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp - 5))).raw()); - } - - /// @notice Tests that making a claim at the execution trace bisection root level with an - /// invalid status byte reverts with the `UnexpectedRootClaim` error. - function test_move_incorrectStatusExecRoot_reverts() public { - Claim disputed; - for (uint256 i; i < 4; i++) { - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, _dummyClaim()); - } - - uint256 bond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, bytes32(0))); - gameProxy.attack{ value: bond }(disputed, 4, Claim.wrap(bytes32(0))); - } - - /// @notice Tests that making a claim at the execution trace bisection root level with a valid - /// status byte succeeds. - function test_move_correctStatusExecRoot_succeeds() public { - Claim disputed; - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, _dummyClaim()); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - } - - /// @notice Static unit test asserting that a move reverts when the bonded amount is incorrect. - function test_move_incorrectBondAmount_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.expectRevert(IncorrectBondAmount.selector); - gameProxy.attack{ value: 0 }(disputed, 0, _dummyClaim()); - } - - /// @notice Static unit test asserting that a move reverts when the disputed claim does not - /// match its index. - function test_move_incorrectDisputedIndex_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - uint256 bond = _getRequiredBond(1); - vm.expectRevert(InvalidDisputedClaimIndex.selector); - gameProxy.attack{ value: bond }(disputed, 1, _dummyClaim()); - } -} - -/// @title FaultDisputeGame_AddLocalData_Test -/// @notice Tests the addLocalData functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_AddLocalData_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that adding local data with an out of bounds identifier reverts. - function testFuzz_addLocalData_oob_reverts(uint256 _ident) public { - Claim disputed; - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, _dummyClaim()); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - // [1, 5] are valid local data identifiers. - if (_ident <= 5) _ident = 0; - - vm.expectRevert(InvalidLocalIdent.selector); - gameProxy.addLocalData(_ident, 5, 0); - } - - /// @notice Tests that local data is loaded into the preimage oracle correctly in the subgame - /// that is disputing the transition from `GENESIS -> GENESIS + 1` - function test_addLocalDataGenesisTransition_static_succeeds() public { - IPreimageOracle oracle = IPreimageOracle(address(gameProxy.vm().oracle())); - Claim disputed; - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: lastBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - // Expected start/disputed claims - (Hash root,) = gameProxy.startingOutputRoot(); - bytes32 startingClaim = root.raw(); - bytes32 disputedClaim = bytes32(uint256(3)); - Position disputedPos = LibPosition.wrap(4, 0); - - // Expected local data - bytes32[5] memory data = [ - gameProxy.l1Head().raw(), - startingClaim, - disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) - ]; - - for (uint256 i = 1; i <= 5; i++) { - uint256 expectedLen = i > 3 ? 8 : 32; - bytes32 key = _getKey(i, keccak256(abi.encode(disputedClaim, disputedPos))); - - gameProxy.addLocalData(i, 5, 0); - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - // Account for the length prefix if i > 3 (the data stored at identifiers i <= 3 are - // 32 bytes long, so the expected length is already correct. If i > 3, the data is only - // 8 bytes long, so the length prefix + the data is 16 bytes total.) - assertEq(datLen, expectedLen + (i > 3 ? 8 : 0)); - - gameProxy.addLocalData(i, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, data[i - 1]); - assertEq(datLen, expectedLen); - } - } - - /// @notice Tests that local data is loaded into the preimage oracle correctly. - function test_addLocalDataMiddle_static_succeeds() public { - IPreimageOracle oracle = IPreimageOracle(address(gameProxy.vm().oracle())); - Claim disputed; - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - for (uint256 i; i < 4; i++) { - uint256 bond = _getRequiredBond(i); - (,,,, disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - uint256 lastBond = _getRequiredBond(4); - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.defend{ value: lastBond }(disputed, 4, _changeClaimStatus(ROOT_CLAIM, VMStatuses.VALID)); - - // Expected start/disputed claims - bytes32 startingClaim = bytes32(uint256(3)); - Position startingPos = LibPosition.wrap(4, 0); - bytes32 disputedClaim = bytes32(uint256(2)); - Position disputedPos = LibPosition.wrap(3, 0); - - // Expected local data - bytes32[5] memory data = [ - gameProxy.l1Head().raw(), - startingClaim, - disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) - ]; - - for (uint256 i = 1; i <= 5; i++) { - uint256 expectedLen = i > 3 ? 8 : 32; - bytes32 key = _getKey(i, keccak256(abi.encode(startingClaim, startingPos, disputedClaim, disputedPos))); - - gameProxy.addLocalData(i, 5, 0); - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - // Account for the length prefix if i > 3 (the data stored at identifiers i <= 3 are - // 32 bytes long, so the expected length is already correct. If i > 3, the data is only - // 8 bytes long, so the length prefix + the data is 16 bytes total.) - assertEq(datLen, expectedLen + (i > 3 ? 8 : 0)); - - gameProxy.addLocalData(i, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, data[i - 1]); - assertEq(datLen, expectedLen); - } - } - - /// @notice Tests that the L2 block number claim is favored over the bisected-to block when - /// adding data. - function test_addLocalData_l2BlockNumberExtension_succeeds() public { - // Deploy a new dispute game with a L2 block number claim of 8. This is directly in the - // middle of the leaves in our output bisection test tree, at SPLIT_DEPTH = 2 ** 2 - IFaultDisputeGameV2 game = IFaultDisputeGameV2( - address( - disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(bytes32(uint256(0xFF))), abi.encode(validL2BlockNumber) - ) - ) - ); - - // Get a claim below the split depth so that we can add local data for an execution trace - // subgame. - { - Claim disputed; - Position parent; - Position pos; - - for (uint256 i; i < 4; i++) { - (,,,,, parent,) = game.claimData(i); - pos = parent.move(true); - uint256 bond = game.getRequiredBond(pos); - - (,,,, disputed,,) = game.claimData(i); - if (i == 0) { - game.attack{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } else { - game.defend{ value: bond }(disputed, i, Claim.wrap(bytes32(i))); - } - } - (,,,,, parent,) = game.claimData(4); - pos = parent.move(true); - uint256 lastBond = game.getRequiredBond(pos); - (,,,, disputed,,) = game.claimData(4); - game.defend{ value: lastBond }(disputed, 4, _changeClaimStatus(ROOT_CLAIM, VMStatuses.INVALID)); - } - - // Expected start/disputed claims - bytes32 startingClaim = bytes32(uint256(3)); - Position startingPos = LibPosition.wrap(4, 14); - bytes32 disputedClaim = bytes32(uint256(0xFF)); - Position disputedPos = LibPosition.wrap(0, 0); - - // Expected local data. This should be `l2BlockNumber`, and not the actual bisected-to - // block, as we choose the minimum between the two. - bytes32 expectedNumber = bytes32(validL2BlockNumber << 0xC0); - uint256 expectedLen = 8; - uint256 l2NumberIdent = LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER; - - // Compute the preimage key for the local data - bytes32 localContext = keccak256(abi.encode(startingClaim, startingPos, disputedClaim, disputedPos)); - bytes32 rawKey = keccak256(abi.encode(l2NumberIdent | (1 << 248), address(game), localContext)); - bytes32 key = bytes32((uint256(rawKey) & ~uint256(0xFF << 248)) | (1 << 248)); - - IPreimageOracle oracle = IPreimageOracle(address(game.vm().oracle())); - game.addLocalData(l2NumberIdent, 5, 0); - - (bytes32 dat, uint256 datLen) = oracle.readPreimage(key, 0); - assertEq(dat >> 0xC0, bytes32(expectedLen)); - assertEq(datLen, expectedLen + 8); - - game.addLocalData(l2NumberIdent, 5, 8); - (dat, datLen) = oracle.readPreimage(key, 8); - assertEq(dat, expectedNumber); - assertEq(datLen, expectedLen); - } -} - -/// @title FaultDisputeGame_ChallengeRootL2Block_Test -/// @notice Tests the challengeRootL2Block functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_ChallengeRootL2Block_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root succeeds. - function testFuzz_challengeRootL2Block_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - uint256 wrongL2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(wrongL2BlockNumber) - ); - - // Challenge the L2 block number. - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Ensure that a duplicate challenge reverts. - vm.expectRevert(L2BlockNumberChallenged.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertTrue(fdg.l2BlockNumberChallenged()); - } - - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root succeeds. Also, this claim should always receive the - /// bond when there is another counter that is as far left as possible. - function testFuzz_challengeRootL2Block_receivesBond_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - vm.deal(address(0xb0b), 1 ether); - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - disputeGameFactory.setInitBond(GAME_TYPE, 0.1 ether); - uint256 balanceBefore = address(this).balance; - _l2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = - disputeGameFactory.create{ value: 0.1 ether }(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - // Attack the root as 0xb0b - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = fdg.claimData(0); - vm.prank(address(0xb0b)); - fdg.attack{ value: bond }(disputed, 0, Claim.wrap(0)); - - // Challenge the L2 block number as 0xace. This claim should receive the root claim's bond. - vm.prank(address(0xace)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(1, 0); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - fdg.closeGame(); - - // Claim credit once to trigger unlock period. - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit - vm.expectRevert(NoCreditToClaim.selector); - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Ensure that the party who challenged the L2 block number with the special move received - // the bond. - // - Root claim loses their bond - // - 0xace receives the root claim's bond - // - 0xb0b receives their bond back - assertEq(address(this).balance, balanceBefore - 0.1 ether); - assertEq(address(0xb0b).balance, 1 ether); - assertEq(address(0xace).balance, 0.1 ether); - } - - /// @notice Tests that challenging the root claim's L2 block number by providing the real - /// preimage of the output root never succeeds. - function testFuzz_challengeRootL2Block_rightBlockNumber_reverts( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = - disputeGameFactory.create{ value: initBond }(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); - - // Challenge the L2 block number. - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - vm.expectRevert(BlockNumberMatches.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badProof_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - - vm.expectRevert(InvalidOutputRootProof.selector); - gameProxy.challengeRootL2Block(outputRootProof, hex""); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badHeaderRLP_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - bytes32 outputRoot = Hashing.hashOutputRootProof(outputRootProof); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber) - ); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } - - /// @notice Tests that challenging the root claim's L2 block number with a bad output root - /// proof reverts. - function test_challengeRootL2Block_badHeaderRLPBlockNumberLength_reverts() public { - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot,) = - _generateOutputRootProof(0, 0, new bytes(64)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create{ value: initBond }( - GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber) - ); - IFaultDisputeGameV2 fdg = IFaultDisputeGameV2(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } -} - -/// @title FaultDisputeGame_Resolve_Test -/// @notice Tests the resolve functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_Resolve_Test is FaultDisputeGameV2_TestInit { - /// @notice Static unit test for the correctness an uncontested root resolution. - function test_resolve_rootUncontested_succeeds() public { - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test for the correctness an uncontested root resolution. - function test_resolve_rootUncontestedClockNotExpired_succeeds() public { - vm.warp(block.timestamp + 3 days + 12 hours - 1 seconds); - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test for the correctness of a multi-part resolution of a single claim. - function test_resolve_multiPart_succeeds() public { - vm.deal(address(this), 10_000 ether); - - uint256 bond = _getRequiredBond(0); - for (uint256 i = 0; i < 2048; i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, Claim.wrap(bytes32(i))); - } - - // Warp past the clock period. - vm.warp(block.timestamp + 3 days + 12 hours + 1 seconds); - - // Resolve all children of the root subgame. Every single one of these will be uncontested. - for (uint256 i = 1; i <= 2048; i++) { - gameProxy.resolveClaim(i, 0); - } - - // Resolve the first half of the root claim subgame. - gameProxy.resolveClaim(0, 1024); - - // Fetch the resolution checkpoint for the root subgame and assert correctness. - (bool initCheckpoint, uint32 subgameIndex, Position leftmostPosition, address counteredBy) = - gameProxy.resolutionCheckpoints(0); - assertTrue(initCheckpoint); - assertEq(subgameIndex, 1024); - assertEq(leftmostPosition.raw(), Position.wrap(1).move(true).raw()); - assertEq(counteredBy, address(this)); - - // The root subgame should not be resolved. - assertFalse(gameProxy.resolvedSubgames(0)); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolve(); - - // Resolve the second half of the root claim subgame. - uint256 numToResolve = gameProxy.getNumToResolve(0); - assertEq(numToResolve, 1024); - gameProxy.resolveClaim(0, numToResolve); - - // Fetch the resolution checkpoint for the root subgame and assert correctness. - (initCheckpoint, subgameIndex, leftmostPosition, counteredBy) = gameProxy.resolutionCheckpoints(0); - assertTrue(initCheckpoint); - assertEq(subgameIndex, 2048); - assertEq(leftmostPosition.raw(), Position.wrap(1).move(true).raw()); - assertEq(counteredBy, address(this)); - - // The root subgame should now be resolved - assertTrue(gameProxy.resolvedSubgames(0)); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test asserting that resolve reverts when the absolute root - /// subgame has not been resolved. - function test_resolve_rootUncontestedButUnresolved_reverts() public { - vm.warp(block.timestamp + 3 days + 12 hours); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolve(); - } - - /// @notice Static unit test asserting that resolve reverts when the game state is - /// not in progress. - function test_resolve_notInProgress_reverts() public { - uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS); - - // Replace the game status in storage. It exists in slot 0 at offset 16. - uint256 slot = uint256(vm.load(address(gameProxy), bytes32(0))); - uint256 offset = 16 << 3; - uint256 mask = 0xFF << offset; - // Replace the byte in the slot value with the challenger wins status. - slot = (slot & ~mask) | (chalWins << offset); - - vm.store(address(gameProxy), bytes32(uint256(0)), bytes32(slot)); - vm.expectRevert(GameNotInProgress.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test for the correctness of resolving a single attack game state. - function test_resolve_rootContested_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game with a contested challenge - /// claim. - function test_resolve_challengeContested_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game with multiplayer moves. - function test_resolve_teamDeathmatch_succeeds() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - gameProxy.defend{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(4, 0); - gameProxy.resolveClaim(3, 0); - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - } - - /// @notice Static unit test for the correctness of resolving a game that reaches max game - /// depth. - function test_resolve_stepReached_succeeds() public { - Claim claim = _dummyClaim(); - for (uint256 i; i < gameProxy.splitDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - for (uint256 i = gameProxy.claimDataLen() - 1; i < gameProxy.maxGameDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.warp(block.timestamp + 3 days + 12 hours); - - for (uint256 i = 9; i > 0; i--) { - gameProxy.resolveClaim(i - 1, 0); - } - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve a - /// subgame multiple times - function test_resolve_claimAlreadyResolved_reverts() public { - Claim claim = _dummyClaim(); - uint256 firstBond = _getRequiredBond(0); - vm.deal(address(this), firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - uint256 secondBond = _getRequiredBond(1); - vm.deal(address(this), secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - - vm.expectRevert(ClaimAlreadyResolved.selector); - gameProxy.resolveClaim(1, 0); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve a - /// subgame at max depth - function test_resolve_claimAtMaxDepthAlreadyResolved_reverts() public { - Claim claim = _dummyClaim(); - for (uint256 i; i < gameProxy.splitDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.deal(address(this), 10000 ether); - claim = _changeClaimStatus(claim, VMStatuses.PANIC); - for (uint256 i = gameProxy.claimDataLen() - 1; i < gameProxy.maxGameDepth(); i++) { - (,,,, Claim disputed,,) = gameProxy.claimData(i); - gameProxy.attack{ value: _getRequiredBond(i) }(disputed, i, claim); - } - - vm.warp(block.timestamp + 3 days + 12 hours); - - gameProxy.resolveClaim(8, 0); - - vm.expectRevert(ClaimAlreadyResolved.selector); - gameProxy.resolveClaim(8, 0); - } - - /// @notice Static unit test asserting that resolve reverts when attempting to resolve - /// subgames out of order - function test_resolve_outOfOrderResolution_reverts() public { - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - - vm.warp(block.timestamp + 3 days + 12 hours); - - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - } - - /// @notice Static unit test asserting that resolve pays out bonds on step, output bisection, - /// and execution trace moves. - function test_resolve_bondPayouts_succeeds() public { - // Give the test contract some ether - uint256 bal = 1000 ether; - vm.deal(address(this), bal); - - // Make claims all the way down the tree. - uint256 bond = _getRequiredBond(0); - uint256 totalBonded = bond; - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: bond }(disputed, 0, _dummyClaim()); - bond = _getRequiredBond(1); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: bond }(disputed, 1, _dummyClaim()); - bond = _getRequiredBond(2); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: bond }(disputed, 2, _dummyClaim()); - bond = _getRequiredBond(3); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(3); - gameProxy.attack{ value: bond }(disputed, 3, _dummyClaim()); - bond = _getRequiredBond(4); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: bond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - bond = _getRequiredBond(5); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(5); - gameProxy.attack{ value: bond }(disputed, 5, _dummyClaim()); - bond = _getRequiredBond(6); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: bond }(disputed, 6, _dummyClaim()); - bond = _getRequiredBond(7); - totalBonded += bond; - (,,,, disputed,,) = gameProxy.claimData(7); - gameProxy.attack{ value: bond }(disputed, 7, _dummyClaim()); - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - // Ensure that the step successfully countered the leaf claim. - (, address counteredBy,,,,,) = gameProxy.claimData(8); - assertEq(counteredBy, address(this)); - - // Ensure we bonded the correct amounts - assertEq(address(this).balance, bal - totalBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + totalBonded); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit again to get the bond back. - gameProxy.claimCredit(address(this)); - - // Ensure that bonds were paid out correctly. - assertEq(address(this).balance, bal + initBond); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that resolve pays out bonds on step, output bisection, - /// and execution trace moves with 2 actors and a dishonest root claim. - function test_resolve_bondPayoutsSeveralActors_succeeds() public { - // Give the test contract and bob some ether - // We use the "1000 ether" literal for `bal`, the initial balance, to avoid stack too deep - //uint256 bal = 1000 ether; - address bob = address(0xb0b); - vm.deal(address(this), 1000 ether); - vm.deal(bob, 1000 ether); - - // Make claims all the way down the tree, trading off between bob and the test contract. - uint256 firstBond = _getRequiredBond(0); - uint256 thisBonded = firstBond; - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, _dummyClaim()); - - uint256 secondBond = _getRequiredBond(1); - uint256 bobBonded = secondBond; - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - - uint256 thirdBond = _getRequiredBond(2); - thisBonded += thirdBond; - (,,,, disputed,,) = gameProxy.claimData(2); - gameProxy.attack{ value: thirdBond }(disputed, 2, _dummyClaim()); - - uint256 fourthBond = _getRequiredBond(3); - bobBonded += fourthBond; - (,,,, disputed,,) = gameProxy.claimData(3); - vm.prank(bob); - gameProxy.attack{ value: fourthBond }(disputed, 3, _dummyClaim()); - - uint256 fifthBond = _getRequiredBond(4); - thisBonded += fifthBond; - (,,,, disputed,,) = gameProxy.claimData(4); - gameProxy.attack{ value: fifthBond }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); - - uint256 sixthBond = _getRequiredBond(5); - bobBonded += sixthBond; - (,,,, disputed,,) = gameProxy.claimData(5); - vm.prank(bob); - gameProxy.attack{ value: sixthBond }(disputed, 5, _dummyClaim()); - - uint256 seventhBond = _getRequiredBond(6); - thisBonded += seventhBond; - (,,,, disputed,,) = gameProxy.claimData(6); - gameProxy.attack{ value: seventhBond }(disputed, 6, _dummyClaim()); - - uint256 eighthBond = _getRequiredBond(7); - bobBonded += eighthBond; - (,,,, disputed,,) = gameProxy.claimData(7); - vm.prank(bob); - gameProxy.attack{ value: eighthBond }(disputed, 7, _dummyClaim()); - - gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); - gameProxy.step(8, true, absolutePrestateData, hex""); - - // Ensure that the step successfully countered the leaf claim. - (, address counteredBy,,,,,) = gameProxy.claimData(8); - assertEq(counteredBy, address(this)); - - // Ensure we bonded the correct amounts - assertEq(address(this).balance, 1000 ether - thisBonded); - assertEq(bob.balance, 1000 ether - bobBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + thisBonded + bobBonded); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - - // Resolve the game. - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit again to get the bond back. - gameProxy.claimCredit(address(this)); - - // Bob's claim should revert since it's value is 0 - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(bob); - - // Ensure that bonds were paid out correctly. - assertEq(address(this).balance, 1000 ether + initBond + bobBonded); - assertEq(bob.balance, 1000 ether - bobBonded); - assertEq(address(gameProxy).balance, 0); - assertEq(delayedWeth.balanceOf(address(gameProxy)), 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that resolve pays out bonds on moves to the leftmost - /// actor in subgames containing successful counters. - function test_resolve_leftmostBondPayout_succeeds() public { - uint256 bal = 1000 ether; - address alice = address(0xa11ce); - address bob = address(0xb0b); - address charlie = address(0xc0c); - vm.deal(address(this), bal); - vm.deal(alice, bal); - vm.deal(bob, bal); - vm.deal(charlie, bal); - - // Make claims with bob, charlie and the test contract on defense, and alice as the - // challenger charlie is successfully countered by alice alice is successfully countered by - // both bob and the test contract - uint256 firstBond = _getRequiredBond(0); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, _dummyClaim()); - - uint256 secondBond = _getRequiredBond(1); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.defend{ value: secondBond }(disputed, 1, _dummyClaim()); - vm.prank(charlie); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - gameProxy.attack{ value: secondBond }(disputed, 1, _dummyClaim()); - - uint256 thirdBond = _getRequiredBond(3); - (,,,, disputed,,) = gameProxy.claimData(3); - vm.prank(alice); - gameProxy.attack{ value: thirdBond }(disputed, 3, _dummyClaim()); - - // Resolve all claims - vm.warp(block.timestamp + 3 days + 12 hours); - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - gameProxy.claimCredit(charlie); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // All of these claims should work. - gameProxy.claimCredit(address(this)); - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Charlie's claim should revert since it's value is 0 - vm.expectRevert(NoCreditToClaim.selector); - gameProxy.claimCredit(charlie); - - // Ensure that bonds were paid out correctly. - uint256 aliceLosses = firstBond; - uint256 charlieLosses = secondBond; - assertEq(address(this).balance, bal + aliceLosses + initBond, "incorrect this balance"); - assertEq(alice.balance, bal - aliceLosses + charlieLosses, "incorrect alice balance"); - assertEq(bob.balance, bal, "incorrect bob balance"); - assertEq(charlie.balance, bal - charlieLosses, "incorrect charlie balance"); - assertEq(address(gameProxy).balance, 0); - - // Ensure that the init bond for the game is 0, in case we change it in the test suite in - // the future. - assertEq(disputeGameFactory.initBonds(GAME_TYPE), initBond); - } - - /// @notice Static unit test asserting that the anchor state updates when the game resolves in - /// favor of the defender and the anchor state is older than the game state. - function test_resolve_validNewerStateUpdatesAnchor_succeeds() public { - // Confirm that the anchor state is older than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber < gameProxy.l2BlockNumber()); - - // Resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is now the same as the game state. - (root, l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); - assertEq(root.raw(), gameProxy.rootClaim().raw()); - } - - /// @notice Static unit test asserting that the anchor state does not change when the game - /// resolves in favor of the defender but the game state is not newer than the anchor - /// state. - function test_resolve_validOlderStateSameAnchor_succeeds() public { - // Mock the game block to be older than the game state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(0)); - - // Confirm that the anchor state is newer than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber >= gameProxy.l2SequenceNumber()); - - // Resolve the game. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(0)); - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is the same as the initial anchor state. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); - } - - /// @notice Static unit test asserting that the anchor state does not change when the game - /// resolves in favor of the challenger, even if the game state is newer than the - /// anchor state. - function test_resolve_invalidStateSameAnchor_succeeds() public { - // Confirm that the anchor state is older than the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assert(l2BlockNumber < gameProxy.l2BlockNumber()); - - // Challenge the claim and resolve it. - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Confirm that the anchor state is the same as the initial anchor state. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); - } -} - -/// @title FaultDisputeGame_GameType_Test -/// @notice Tests the `gameType` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_GameType_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's type is set correctly. - function test_gameType_succeeds() public view { - assertEq(gameProxy.gameType().raw(), GAME_TYPE.raw()); - } -} - -/// @title FaultDisputeGame_RootClaim_Test -/// @notice Tests the `rootClaim` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_RootClaim_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's root claim is set correctly. - function test_rootClaim_succeeds() public view { - assertEq(gameProxy.rootClaim().raw(), ROOT_CLAIM.raw()); - } -} - -/// @title FaultDisputeGame_ExtraData_Test -/// @notice Tests the `extraData` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_ExtraData_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's extra data is set correctly. - function test_extraData_succeeds() public view { - assertEq(gameProxy.extraData(), extraData); - } -} - -/// @title FaultDisputeGame_GameData_Test -/// @notice Tests the `gameData` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_GameData_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's data is set correctly. - function test_gameData_succeeds() public view { - (GameType gameType, Claim rootClaim, bytes memory _extraData) = gameProxy.gameData(); - - assertEq(gameType.raw(), GAME_TYPE.raw()); - assertEq(rootClaim.raw(), ROOT_CLAIM.raw()); - assertEq(_extraData, extraData); - } -} - -/// @title FaultDisputeGame_GetRequiredBond_Test -/// @notice Tests the `getRequiredBond` function of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_GetRequiredBond_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the bond during the bisection game depths is correct. - function test_getRequiredBond_succeeds() public view { - for (uint8 i = 0; i < uint8(gameProxy.splitDepth()); i++) { - Position pos = LibPosition.wrap(i, 0); - uint256 bond = gameProxy.getRequiredBond(pos); - - // Reasonable approximation for a max depth of 8. - uint256 expected = 0.08 ether; - for (uint64 j = 0; j < i; j++) { - expected = expected * 22876; - expected = expected / 10000; - } - - assertApproxEqAbs(bond, expected, 0.01 ether); - } - } - - /// @notice Tests that the bond at a depth greater than the maximum game depth reverts. - function test_getRequiredBond_outOfBounds_reverts() public { - Position pos = LibPosition.wrap(uint8(gameProxy.maxGameDepth() + 1), 0); - vm.expectRevert(GameDepthExceeded.selector); - gameProxy.getRequiredBond(pos); - } -} - -/// @title FaultDisputeGame_ClaimCredit_Test -/// @notice Tests the claimCredit functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_ClaimCredit_Test is FaultDisputeGameV2_TestInit { - function test_claimCredit_refundMode_succeeds() public { - // Set up actors. - address alice = address(0xa11ce); - address bob = address(0xb0b); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - - // Bond the first claim. - uint256 firstBond = _getRequiredBond(0); - vm.deal(alice, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - - // Bond the second claim. - uint256 secondBond = _getRequiredBond(1); - vm.deal(bob, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve the game. - // Second claim wins, so bob should get alice's credit. - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Mock that the game proxy is not proper, trigger refund mode. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.isGameProper, (gameProxy)), - abi.encode(false) - ); - - // Close the game. - gameProxy.closeGame(); - - // Assert bond distribution mode is refund mode. - assertTrue(gameProxy.bondDistributionMode() == BondDistributionMode.REFUND); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Grab balances before claim. - uint256 aliceBalanceBefore = alice.balance; - uint256 bobBalanceBefore = bob.balance; - - // Claim credit again to get the bond back. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Should have original balance again. - assertEq(alice.balance, aliceBalanceBefore + firstBond); - assertEq(bob.balance, bobBalanceBefore + secondBond); - } - - /// @notice Tests that claimCredit reverts if the game is paused. - function test_claimCredit_gamePaused_reverts() public { - // Pause the system with the Superchain-wide identifier (address(0)). - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); - - // Attempting to claim credit should now revert. - vm.expectRevert(GamePaused.selector); - gameProxy.claimCredit(address(0)); - } - - /// @notice Static unit test asserting that credit may not be drained past allowance through - /// reentrancy. - function test_claimCredit_claimAlreadyResolved_reverts() public { - ClaimCreditReenter reenter = new ClaimCreditReenter(gameProxy, vm); - vm.startPrank(address(reenter)); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - uint256 firstBond = _getRequiredBond(0); - vm.deal(address(reenter), firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - uint256 secondBond = _getRequiredBond(1); - vm.deal(address(reenter), secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - uint256 reenterBond = firstBond + secondBond; - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Ensure that we bonded all the test contract's ETH - assertEq(address(reenter).balance, 0); - // Ensure the game proxy has 1 ether in it. - assertEq(address(gameProxy).balance, 1 ether); - // Ensure the game has a balance of reenterBond in the delayedWeth contract. - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond + reenterBond); - - // Resolve the claim at index 2 first so that index 1 can be resolved. - gameProxy.resolveClaim(2, 0); - - // Resolve the claim at index 1 and claim the reenter contract's credit. - gameProxy.resolveClaim(1, 0); - - // Ensure that the game registered the `reenter` contract's credit. - assertEq(gameProxy.credit(address(reenter)), reenterBond); - - // Resolve the root claim. - gameProxy.resolveClaim(0, 0); - - // Resolve the game. - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(address(reenter)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Initiate the reentrant credit claim. - reenter.claimCredit(address(reenter)); - - // The reenter contract should have performed 2 calls to `claimCredit`. - // Once all the credit is claimed, all subsequent calls will revert since there is 0 credit - // left to claim. - // The claimant must only have received the amount bonded for the gindex 1 subgame. - // The root claim bond and the unregistered ETH should still exist in the game proxy. - assertEq(reenter.numCalls(), 2); - assertEq(address(reenter).balance, reenterBond); - assertEq(address(gameProxy).balance, 1 ether); - assertEq(delayedWeth.balanceOf(address(gameProxy)), initBond); - - vm.stopPrank(); - } - - /// @notice Tests that claimCredit reverts when recipient can't receive value. - function test_claimCredit_recipientCantReceiveValue_reverts() public { - // Set up actors. - address alice = address(0xa11ce); - address bob = address(0xb0b); - - // Give the game proxy 1 extra ether, unregistered. - vm.deal(address(gameProxy), 1 ether); - - // Perform a bonded move. - Claim claim = _dummyClaim(); - - // Bond the first claim. - uint256 firstBond = _getRequiredBond(0); - vm.deal(alice, firstBond); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - vm.prank(alice); - gameProxy.attack{ value: firstBond }(disputed, 0, claim); - - // Bond the second claim. - uint256 secondBond = _getRequiredBond(1); - vm.deal(bob, secondBond); - (,,,, disputed,,) = gameProxy.claimData(1); - vm.prank(bob); - gameProxy.attack{ value: secondBond }(disputed, 1, claim); - - // Warp past the finalization period - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve the game. - // Second claim wins, so bob should get alice's credit. - gameProxy.resolveClaim(2, 0); - gameProxy.resolveClaim(1, 0); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - gameProxy.closeGame(); - - // Claim credit once to trigger unlock period. - gameProxy.claimCredit(alice); - gameProxy.claimCredit(bob); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Make bob not be able to receive value by setting his contract code to something without - // `receive` - vm.etch(address(bob), address(L1Token).code); - - vm.expectRevert(BondTransferFailed.selector); - gameProxy.claimCredit(address(bob)); - } -} - -/// @title FaultDisputeGame_CloseGame_Test -/// @notice Tests the closeGame functionality of the `FaultDisputeGame` contract. -contract FaultDisputeGameV2_CloseGame_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that closeGame reverts if the game is not resolved - function test_closeGame_gameNotResolved_reverts() public { - vm.expectRevert(GameNotResolved.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame reverts if the game is paused - function test_closeGame_gamePaused_reverts() public { - // Pause the system with the Superchain-wide identifier (address(0)). - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); - - // Attempting to close the game should now revert. - vm.expectRevert(GamePaused.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame reverts if the game is not finalized - function test_closeGame_gameNotFinalized_reverts() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Don't wait the finalization delay - vm.expectRevert(GameNotFinalized.selector); - gameProxy.closeGame(); - } - - /// @notice Tests that closeGame succeeds for a proper game (normal distribution) - function test_closeGame_properGame_succeeds() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game and verify normal distribution mode - vm.expectEmit(true, true, true, true); - emit GameClosed(BondDistributionMode.NORMAL); - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - // Check that the anchor state was set correctly. - assertEq(address(gameProxy.anchorStateRegistry().anchorGame()), address(gameProxy)); - } - - /// @notice Tests that closeGame succeeds for an improper game (refund mode) - function test_closeGame_improperGame_succeeds() public { - // Resolve the game - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Mock the anchor registry to return improper game - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.isGameProper, (IDisputeGame(address(gameProxy)))), - abi.encode(false, "") - ); - - // Close the game and verify refund mode - vm.expectEmit(true, true, true, true); - emit GameClosed(BondDistributionMode.REFUND); - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.REFUND)); - } - - /// @notice Tests that multiple calls to closeGame succeed after initial distribution mode is - /// set - function test_closeGame_multipleCallsAfterSet_succeeds() public { - // Resolve and close the game first - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // First close sets the mode - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - // Subsequent closes should succeed without changing the mode - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - - gameProxy.closeGame(); - assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); - } - - /// @notice Tests that closeGame called with any amount of gas either reverts (with OOG) or - /// updates the anchor state. This is specifically to verify that the try/catch inside - /// closeGame can't be called with just enough gas to OOG when calling the - /// AnchorStateRegistry but successfully execute the remainder of the function. - /// @param _gas Amount of gas to provide to closeGame. - function testFuzz_closeGame_canUpdateAnchorStateAndDoes_succeeds(uint256 _gas) public { - // Resolve and close the game first - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Since providing *too* much gas isn't the issue here, bounding it to half the block gas - // limit is sufficient. We want to know that either (1) the function reverts or (2) the - // anchor state gets updated. If the function doesn't revert and the anchor state isn't - // updated then we have a problem. - _gas = bound(_gas, 0, block.gaslimit / 2); - - // The anchor state should not be the game proxy. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) != address(gameProxy)); - - // Try closing the game. - try gameProxy.closeGame{ gas: _gas }() { - // If we got here, the function didn't revert, so the anchor state should have updated. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) == address(gameProxy)); - } catch { - // Ok, function reverted. - } - } -} - -/// @title FaultDisputeGame_GetChallengerDuration_Test -/// @notice Tests the getChallengerDuration functionality and related resolution tests. -contract FaultDisputeGameV2_GetChallengerDuration_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that if the game is not in progress, querying of `getChallengerDuration` - /// reverts - function test_getChallengerDuration_gameNotInProgress_reverts() public { - // resolve the game - vm.warp(block.timestamp + gameProxy.maxClockDuration().raw()); - - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - vm.expectRevert(GameNotInProgress.selector); - gameProxy.getChallengerDuration(1); - } - - /// @notice Static unit test asserting that resolveClaim isn't possible if there's time left - /// for a counter. - function test_resolution_lastSecondDisputes_succeeds() public { - // The honest proposer created an honest root claim during setup - node 0 - - // Defender's turn - vm.warp(block.timestamp + 3.5 days - 1 seconds); - (,,,, Claim disputed,,) = gameProxy.claimData(0); - gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days - 1 seconds); - assertEq(gameProxy.getChallengerDuration(1).raw(), 0); - - // Advance time by 1 second, so that the root claim challenger clock is expired. - vm.warp(block.timestamp + 1 seconds); - // Attempt a second attack against the root claim. This should revert since the challenger - // clock is expired. - uint256 expectedBond = _getRequiredBond(0); - vm.expectRevert(ClockTimeExceeded.selector); - gameProxy.attack{ value: expectedBond }(disputed, 0, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 1 seconds); - - // Should not be able to resolve the root claim or second counter yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // Warp to the last second of the root claim defender clock. - vm.warp(block.timestamp + 3.5 days - 2 seconds); - // Attack the challenge to the root claim. This should succeed, since the defender clock is - // not expired. - (,,,, disputed,,) = gameProxy.claimData(1); - gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days - 1 seconds); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days - gameProxy.clockExtension().raw()); - - // Should not be able to resolve any claims yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(2, 0); - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - vm.warp(block.timestamp + gameProxy.clockExtension().raw() - 1 seconds); - - // Should not be able to resolve any claims yet. - vm.expectRevert(ClockNotExpired.selector); - gameProxy.resolveClaim(2, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days - 1 seconds); - - // Warp past the challenge period for the root claim defender. Defending the root claim - // should now revert. - vm.warp(block.timestamp + 1 seconds); - expectedBond = _getRequiredBond(1); - vm.expectRevert(ClockTimeExceeded.selector); // no further move can be made - gameProxy.attack{ value: expectedBond }(disputed, 1, _dummyClaim()); - expectedBond = _getRequiredBond(2); - (,,,, disputed,,) = gameProxy.claimData(2); - vm.expectRevert(ClockTimeExceeded.selector); // no further move can be made - gameProxy.attack{ value: expectedBond }(disputed, 2, _dummyClaim()); - // Chess clock time accumulated: - assertEq(gameProxy.getChallengerDuration(0).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(1).raw(), 3.5 days); - assertEq(gameProxy.getChallengerDuration(2).raw(), 3.5 days); - - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(1, 0); - vm.expectRevert(OutOfOrderResolution.selector); - gameProxy.resolveClaim(0, 0); - - // All clocks are expired. Resolve the game. - gameProxy.resolveClaim(2, 0); // Node 2 is resolved as UNCOUNTERED by default since it has no children - gameProxy.resolveClaim(1, 0); // Node 1 is resolved as COUNTERED since it has an UNCOUNTERED child - gameProxy.resolveClaim(0, 0); // Node 0 is resolved as UNCOUNTERED since it has no UNCOUNTERED children - - // Defender wins game since the root claim is uncountered - assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); - } -} - -/// @title FaultDisputeGameV2_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `FaultDisputeGame` -/// contract or are testing multiple functions at once. -contract FaultDisputeGameV2_Uncategorized_Test is FaultDisputeGameV2_TestInit { - /// @notice Tests that the game's starting timestamp is set correctly. - function test_createdAt_succeeds() public view { - assertEq(gameProxy.createdAt().raw(), block.timestamp); - } - - /// @notice Tests that startingOutputRoot and it's getters are set correctly. - function test_startingOutputRootGetters_succeeds() public view { - (Hash root, uint256 l2BlockNumber) = gameProxy.startingOutputRoot(); - (Hash anchorRoot, uint256 anchorRootBlockNumber) = anchorStateRegistry.anchors(GAME_TYPE); - - assertEq(gameProxy.startingBlockNumber(), l2BlockNumber); - assertEq(gameProxy.startingBlockNumber(), anchorRootBlockNumber); - assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(root)); - assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(anchorRoot)); - } - - /// @notice Tests that the user cannot control the first 4 bytes of the CWIA data, disallowing - /// them to control the entrypoint when no calldata is provided to a call. - function test_cwiaCalldata_userCannotControlSelector_succeeds() public { - // Construct the expected CWIA data that the proxy will pass to the implementation, - // alongside any extra calldata passed by the user. - Hash l1Head = gameProxy.l1Head(); - bytes memory cwiaData = abi.encodePacked(address(this), gameProxy.rootClaim(), l1Head, gameProxy.extraData()); - - // We expect a `ReceiveETH` event to be emitted when 0 bytes of calldata are sent; The - // fallback is always reached *within the minimal proxy* in `LibClone`'s version of - // `clones-with-immutable-args` - vm.expectEmit(false, false, false, true); - emit ReceiveETH(0); - // We expect no delegatecall to the implementation contract if 0 bytes are sent. Assert - // that this happens 0 times. - vm.expectCall(address(gameImpl), cwiaData, 0); - (bool successA,) = address(gameProxy).call(hex""); - assertTrue(successA); - - // When calldata is forwarded, we do expect a delegatecall to the implementation. - bytes memory data = abi.encodePacked(gameProxy.l1Head.selector); - vm.expectCall(address(gameImpl), abi.encodePacked(data, cwiaData), 1); - (bool successB, bytes memory returnData) = address(gameProxy).call(data); - assertTrue(successB); - assertEq(returnData, abi.encode(l1Head)); - } -} - -contract FaultDispute_1v1_Actors_Test is FaultDisputeGameV2_TestInit { - /// @notice The honest actor - DisputeActor internal honest; - /// @notice The dishonest actor - DisputeActor internal dishonest; - - function setUp() public override { - // Setup the `FaultDisputeGame` - super.setUp(); - } - - /// @notice Fuzz test for a 1v1 output bisection dispute. - /// @notice The alphabet game has a constant status byte, and is not safe from someone being - /// dishonest in output bisection and then posting a correct execution trace bisection - /// root claim. This test does not cover this case (i.e. root claim of output bisection - /// is dishonest, root claim of execution trace bisection is made by the dishonest - /// actor but is honest, honest actor cannot attack it without risk of losing). - function testFuzz_outputBisection1v1honestRoot_succeeds(uint8 _divergeOutput, uint8 _divergeStep) public { - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - uint256 divergeAtOutput = bound(_divergeOutput, 0, 15); - uint256 divergeAtStep = bound(_divergeStep, 0, 7); - uint256 divergeStepOffset = (divergeAtOutput << 4) + divergeAtStep; - - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i >= divergeAtOutput ? 0xFF : i + 1; - } - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i >= divergeStepOffset ? bytes1(uint8(0xFF)) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRootGenesisAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all set bits. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = bytes1(0xFF); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootGenesisAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of all set bits. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = bytes1(0xFF); - } - - // Run the actor test - _actorTest({ - _rootClaim: 17, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRoot_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all zeros. - bytes memory dishonestTrace = new bytes(256); - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRoot_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of all zeros. - bytes memory dishonestTrace = new bytes(256); - - // Run the actor test - _actorTest({ - _rootClaim: 17, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1correctRootHalfWay_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 4) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootHalfWay_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 4) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1correctAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace correct is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > 127 ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace correct is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > 127 ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRootFinalInstruction_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, and correct all the way up to the final instruction - // of the exec subgame. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 7) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1dishonestRootFinalInstruction_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, - // consisting of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace is half correct, and correct all the way up to the final instruction - // of the exec subgame. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > (127 + 7) ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 0xFF, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.CHALLENGER_WINS - }); - } - - //////////////////////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////////////////////// - - /// @notice Helper to run a 1v1 actor test - function _actorTest( - uint256 _rootClaim, - uint256 _absolutePrestateData, - bytes memory _honestTrace, - uint256[] memory _honestL2Outputs, - bytes memory _dishonestTrace, - uint256[] memory _dishonestL2Outputs, - GameStatus _expectedStatus - ) - internal - { - if (isForkTest()) { - // Mock the call anchorStateRegistry.getAnchorRoot() to return 0 as the block number - (Hash root,) = anchorStateRegistry.getAnchorRoot(); - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), - abi.encode(root, 0) - ); - } - - // Setup the environment - bytes memory absolutePrestateData = - _setup({ _absolutePrestateData: _absolutePrestateData, _rootClaim: _rootClaim }); - - // Create actors - _createActors({ - _honestTrace: _honestTrace, - _honestPreStateData: absolutePrestateData, - _honestL2Outputs: _honestL2Outputs, - _dishonestTrace: _dishonestTrace, - _dishonestPreStateData: absolutePrestateData, - _dishonestL2Outputs: _dishonestL2Outputs - }); - - // Exhaust all moves from both actors - _exhaustMoves(); - - // Resolve the game and assert that the defender won - _warpAndResolve(); - assertEq(uint8(gameProxy.status()), uint8(_expectedStatus)); - } - - /// @notice Helper to setup the 1v1 test - function _setup( - uint256 _absolutePrestateData, - uint256 _rootClaim - ) - internal - returns (bytes memory absolutePrestateData_) - { - absolutePrestateData_ = abi.encode(_absolutePrestateData); - Claim absolutePrestateExec = - _changeClaimStatus(Claim.wrap(keccak256(absolutePrestateData_)), VMStatuses.UNFINISHED); - Claim rootClaim = Claim.wrap(bytes32(uint256(_rootClaim))); - super.init({ rootClaim: rootClaim, absolutePrestate: absolutePrestateExec, l2BlockNumber: _rootClaim }); - } - - /// @notice Helper to create actors for the 1v1 dispute. - function _createActors( - bytes memory _honestTrace, - bytes memory _honestPreStateData, - uint256[] memory _honestL2Outputs, - bytes memory _dishonestTrace, - bytes memory _dishonestPreStateData, - uint256[] memory _dishonestL2Outputs - ) - internal - { - honest = new HonestDisputeActor({ - _gameProxy: IFaultDisputeGame(address(gameProxy)), - _l2Outputs: _honestL2Outputs, - _trace: _honestTrace, - _preStateData: _honestPreStateData - }); - dishonest = new HonestDisputeActor({ - _gameProxy: IFaultDisputeGame(address(gameProxy)), - _l2Outputs: _dishonestL2Outputs, - _trace: _dishonestTrace, - _preStateData: _dishonestPreStateData - }); - - vm.deal(address(honest), 100 ether); - vm.deal(address(dishonest), 100 ether); - vm.label(address(honest), "HonestActor"); - vm.label(address(dishonest), "DishonestActor"); - } - - /// @notice Helper to exhaust all moves from both actors. - function _exhaustMoves() internal { - while (true) { - // Allow the dishonest actor to make their moves, and then the honest actor. - (uint256 numMovesA,) = dishonest.move(); - (uint256 numMovesB, bool success) = honest.move(); - - require(success, "FaultDispute_1v1_Actors_Test: Honest actor's moves should always be successful"); - - // If both actors have run out of moves, we're done. - if (numMovesA == 0 && numMovesB == 0) break; - } - } - - /// @notice Helper to warp past the chess clock and resolve all claims within the dispute game. - function _warpAndResolve() internal { - // Warp past the chess clock - vm.warp(block.timestamp + 3 days + 12 hours); - - // Resolve all claims in reverse order. We allow `resolveClaim` calls to fail due to the - // check that prevents claims with no subgames attached from being passed to - // `resolveClaim`. There's also a check in `resolve` to ensure all children have been - // resolved before global resolution, which catches any unresolved subgames here. - for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { - (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); - assertTrue(success); - } - gameProxy.resolve(); - } -} From 6032f6dd2dddd7af20926d7bb574505d95db026d Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 15:28:00 -0400 Subject: [PATCH 58/75] Remove redundant test --- .../test/scripts/VerifyOPCM.t.sol | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index a2714ffd00218..31dac9fa35d47 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -479,22 +479,3 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { harness.validateAllGettersAccounted(); } } - -/// @title VerifyOPCM_Run_V2DisputeGamesEnabled_Test -/// @notice Tests the `run` function with V2 dispute games enabled. -contract VerifyOPCM_Run_V2DisputeGamesEnabled_Test is VerifyOPCM_TestInit { - function setUp() public override { - setDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - super.setUp(); - setupEnvVars(); - } - - /// @notice Tests that the script succeeds when V2 dispute games are enabled. - function test_run_succeeds() public { - // Coverage changes bytecode and causes failures, skip. - skipIfCoverage(); - - // Run the script with V2-enabled OPCM. - harness.run(address(opcm), true); - } -} From 4fe9e44a3a7ff48d680dc7ae57aa9fddee460190 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 15:48:22 -0400 Subject: [PATCH 59/75] Fix test name --- .../test/dispute/PermissionedDisputeGame.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol index edb19c5479cfe..4fcf6f5b99bda 100644 --- a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol @@ -252,7 +252,7 @@ contract PermissionedDisputeGame_Step_Test is PermissionedDisputeGame_TestInit { /// @title PermissionedDisputeGame_Initialize_Test /// @notice Tests the initialization of the `PermissionedDisputeGame` contract. -contract PermissionedDisputeGameV2_Initialize_Test is PermissionedDisputeGame_TestInit { +contract PermissionedDisputeGame_Initialize_Test is PermissionedDisputeGame_TestInit { /// @notice Tests that the game cannot be initialized with incorrect CWIA calldata length /// caused by extraData of the wrong length function test_initialize_wrongExtradataLength_reverts(uint256 _extraDataLen) public { From c7ab68280b0be82be1f22be005926fa2a73fa285 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 16:09:51 -0400 Subject: [PATCH 60/75] Fix anchorRootNotFound test - pass in extra data --- .../contracts-bedrock/test/dispute/FaultDisputeGame.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index 04138e5efd083..a8a4b99e479d4 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -780,7 +780,9 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { // Creation should fail. vm.expectRevert(AnchorRootNotFound.selector); gameProxy = IFaultDisputeGame( - payable(address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _dummyClaim(), hex""))) + payable( + address(disputeGameFactory.create{ value: initBond }(GAME_TYPE, _dummyClaim(), new bytes(uint256(32)))) + ) ); } From d5385ce279e35b8b63b4b1b5ffd31b734293f7c8 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 17:48:07 -0400 Subject: [PATCH 61/75] Extend deployment test to check more fields --- .../test/L1/OPContractsManager.t.sol | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 8c7071e6ab8df..3b12ce11f3a8d 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1749,7 +1749,9 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { /// @notice Helper function to create a permissioned game through the factory function _createPermissionedGame( IDisputeGameFactory factory, - address proposer + address proposer, + Claim claim, + uint256 l2BlockNumber ) internal returns (IPermissionedDisputeGame) @@ -1765,9 +1767,8 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // We use vm.startPrank to set both msg.sender and tx.origin to the proposer vm.startPrank(proposer, proposer); - IDisputeGame gameProxy = factory.create{ value: initBond }( - GameTypes.PERMISSIONED_CANNON, Claim.wrap(bytes32(uint256(1))), abi.encode(bytes32(uint256(2))) - ); + IDisputeGame gameProxy = + factory.create{ value: initBond }(GameTypes.PERMISSIONED_CANNON, claim, abi.encode(bytes32(l2BlockNumber))); vm.stopPrank(); @@ -1817,29 +1818,38 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { assertEq(actualPDGAddress, address(expectedPDGAddress)); // Create a game proxy to test immutable fields - IPermissionedDisputeGame permissionedGame = - _createPermissionedGame(opcmOutput.disputeGameFactoryProxy, opcmInput.roles.proposer); + Claim claim = Claim.wrap(bytes32(uint256(9876))); + uint256 l2BlockNumber = uint256(123); + IPermissionedDisputeGame pdg = + _createPermissionedGame(opcmOutput.disputeGameFactoryProxy, opcmInput.roles.proposer, claim, l2BlockNumber); // Verify immutable fields on the game proxy + // Constructor args + assertEq(pdg.gameType().raw(), GameTypes.PERMISSIONED_CANNON.raw(), "Game type should match"); + assertEq(pdg.clockExtension().raw(), opcmInput.disputeClockExtension.raw(), "Clock extension should match"); assertEq( - permissionedGame.absolutePrestate().raw(), + pdg.maxClockDuration().raw(), opcmInput.disputeMaxClockDuration.raw(), "Max clock duration should match" + ); + assertEq(pdg.splitDepth(), opcmInput.disputeSplitDepth, "Split depth should match"); + assertEq(pdg.maxGameDepth(), opcmInput.disputeMaxGameDepth, "Max game depth should match"); + // Clone-with-immutable-args + assertEq(pdg.gameCreator(), opcmInput.roles.proposer, "Game creator should match"); + assertEq(pdg.rootClaim().raw(), claim.raw(), "Claim should match"); + assertEq(pdg.l1Head().raw(), blockhash(block.number - 1), "L1 head should match"); + assertEq(pdg.l2BlockNumber(), l2BlockNumber, "L2 Block number should match"); + assertEq( + pdg.absolutePrestate().raw(), opcmInput.disputeAbsolutePrestate.raw(), "Absolute prestate should match input" ); - assertEq(address(permissionedGame.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); - assertEq( - address(permissionedGame.anchorStateRegistry()), - address(opcmOutput.anchorStateRegistryProxy), - "ASR should match" - ); - assertEq( - address(permissionedGame.weth()), address(opcmOutput.delayedWETHPermissionedGameProxy), "WETH should match" - ); - assertEq(permissionedGame.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); + assertEq(address(pdg.vm()), address(impls.mipsImpl), "VM should match MIPS implementation"); + assertEq(address(pdg.anchorStateRegistry()), address(opcmOutput.anchorStateRegistryProxy), "ASR should match"); + assertEq(address(pdg.weth()), address(opcmOutput.delayedWETHPermissionedGameProxy), "WETH should match"); + assertEq(pdg.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); // For permissioned game, check proposer and challenger - assertEq(permissionedGame.proposer(), opcmInput.roles.proposer, "Proposer should match"); - assertEq(permissionedGame.challenger(), opcmInput.roles.challenger, "Challenger should match"); + assertEq(pdg.proposer(), opcmInput.roles.proposer, "Proposer should match"); + assertEq(pdg.challenger(), opcmInput.roles.challenger, "Challenger should match"); } } From 7184c8bbf41a1113a01e2ee9f44b625dda1d7b3e Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 18:03:53 -0400 Subject: [PATCH 62/75] Add guards around new setImplementation call --- .../src/L1/OPContractsManager.sol | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index d9dccc0ec569a..87d1ed39c8e33 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -394,7 +394,15 @@ abstract contract OPContractsManagerBase { /// @param _dgf The dispute game factory /// @param _gameType The game type /// @param _newGame The new game implementation - /// @param _gameArgs Optional game arguments. If empty, calls without gameArgs + function setDGFImplementation(IDisputeGameFactory _dgf, GameType _gameType, IDisputeGame _newGame) internal { + _dgf.setImplementation(_gameType, _newGame); + } + + /// @notice Sets a game implementation on the dispute game factory + /// @param _dgf The dispute game factory + /// @param _gameType The game type + /// @param _newGame The new game implementation + /// @param _gameArgs Game arguments for this game type function setDGFImplementation( IDisputeGameFactory _dgf, GameType _gameType, @@ -403,11 +411,10 @@ abstract contract OPContractsManagerBase { ) internal { - if (_gameArgs.length > 0) { - _dgf.setImplementation(_gameType, _newGame, _gameArgs); - } else { - _dgf.setImplementation(_gameType, _newGame); + if (!isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + revert OPContractsManager.InvalidDevFeatureAccess(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); } + _dgf.setImplementation(_gameType, _newGame, _gameArgs); } } @@ -597,9 +604,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // As a last step, register the new game type with the DisputeGameFactory. If the game // type already exists, then its implementation will be overwritten. - setDGFImplementation( - dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame)), bytes("") - ); + setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame))); dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond); // Emit event for the newly added game type with the new and old implementations. @@ -986,7 +991,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { IDisputeGameFactory dgf = IDisputeGameFactory(_opChainConfig.systemConfigProxy.disputeGameFactory()); // Set the new implementation. - setDGFImplementation(dgf, _gameType, IDisputeGame(newGame), bytes("")); + setDGFImplementation(dgf, _gameType, IDisputeGame(newGame)); } } @@ -1215,8 +1220,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { setDGFImplementation( output.disputeGameFactoryProxy, GameTypes.PERMISSIONED_CANNON, - IDisputeGame(address(output.permissionedDisputeGame)), - bytes("") + IDisputeGame(address(output.permissionedDisputeGame)) ); } @@ -1939,6 +1943,9 @@ contract OPContractsManager is ISemver { /// @notice Thrown when the prestate of a permissioned disputed game is 0. error PrestateRequired(); + /// @notice Thrown if logic gated by a dev feature flag is incorrectly accessed. + error InvalidDevFeatureAccess(bytes32 devFeature); + // -------- Methods -------- constructor( From f1f963f095eddd6a59671432fb1a2f3e0e45789a Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 18:20:17 -0400 Subject: [PATCH 63/75] Add new error to OPCM interface --- .../contracts-bedrock/interfaces/L1/IOPContractsManager.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 72fc2a1aaa3eb..59f674f7635f4 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -299,6 +299,8 @@ interface IOPContractsManager { error PrestateRequired(); + error InvalidDevFeatureAccess(bytes32 devFeature); + // -------- Methods -------- function __constructor__( From 756ce0389bf5885afe53db3a3370602f981a3527 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 7 Oct 2025 18:26:54 -0400 Subject: [PATCH 64/75] Run semver-lock --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 366cbca9da7c8..7efe5d2a8c10b 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,8 +20,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x9f9a3738b05cae6597ea9a5c5747f7dbd3a5328b05a319955054fbd8b1aaa791", - "sourceCodeHash": "0x154c764083f353e2a56337c0dd5cbcd6f2e12c21966cd0580c7a0f96c4e147dd" + "initCodeHash": "0x42721744f90fa46ee680fecc69da2e5caf7fdd8093c2a7f3b33958e574a15579", + "sourceCodeHash": "0x3eab23f3f034eec77afb620a122e51fded9214b5ed6a4c5663e0174714ae0f5e" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x57d6a6729d887ead009d518e8f17fa0d26bfc97b8efe1494ab4ef8dbb000d109", From 9be001b0222006a115398d5d9ea5f912664d4bcc Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 10:50:24 -0400 Subject: [PATCH 65/75] Prefix var with underscore --- packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 5a6401c94e13d..7bf5c07456d97 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -124,8 +124,8 @@ contract DeployOPChain is Script { // -------- Features -------- - function isDevFeatureV2DisputeGamesEnabled(address opcmAddr) internal view returns (bool) { - IOPContractsManager opcm = IOPContractsManager(opcmAddr); + function isDevFeatureV2DisputeGamesEnabled(address _opcmAddr) internal view returns (bool) { + IOPContractsManager opcm = IOPContractsManager(_opcmAddr); return DevFeatures.isDevFeatureEnabled(opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES); } From 9d1323f215802c67465b789e61125768a92ee7d4 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 10:53:47 -0400 Subject: [PATCH 66/75] Remove unused imports --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 6c7ce22f6012b..54be261991428 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -16,10 +16,6 @@ import { VerifyOPCM } from "scripts/deploy/VerifyOPCM.s.sol"; import { DeployOPChain } from "scripts/deploy/DeployOPChain.s.sol"; import { Config } from "scripts/libraries/Config.sol"; import { Types } from "scripts/libraries/Types.sol"; -import { DeployImplementations } from "scripts/deploy/DeployImplementations.s.sol"; -import { DeploySuperchain } from "scripts/deploy/DeploySuperchain.s.sol"; -import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; -import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; // Libraries import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; From 0409e70744a4e869e9ad6841bffe2f3a02c4437d Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 10:59:35 -0400 Subject: [PATCH 67/75] Regenerate snapshots --- .../snapshots/abi/OPContractsManager.json | 21 ++++++++++--------- .../abi/OPContractsManagerDeployer.json | 21 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 370093a786748..5372533426d4e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -427,16 +427,6 @@ "internalType": "contract IDelayedWETH", "name": "delayedWETHPermissionlessGameProxy", "type": "address" - }, - { - "internalType": "contract IFaultDisputeGameV2", - "name": "faultDisputeGameV2", - "type": "address" - }, - { - "internalType": "contract IPermissionedDisputeGameV2", - "name": "permissionedDisputeGameV2", - "type": "address" } ], "internalType": "struct OPContractsManager.DeployOutput", @@ -1011,6 +1001,17 @@ "name": "InvalidChainId", "type": "error" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "devFeature", + "type": "bytes32" + } + ], + "name": "InvalidDevFeatureAccess", + "type": "error" + }, { "inputs": [], "name": "InvalidGameConfigs", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index 28e694d5e4f5f..08b6612c99af7 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -330,16 +330,6 @@ "internalType": "contract IDelayedWETH", "name": "delayedWETHPermissionlessGameProxy", "type": "address" - }, - { - "internalType": "contract IFaultDisputeGameV2", - "name": "faultDisputeGameV2", - "type": "address" - }, - { - "internalType": "contract IPermissionedDisputeGameV2", - "name": "permissionedDisputeGameV2", - "type": "address" } ], "internalType": "struct OPContractsManager.DeployOutput", @@ -538,6 +528,17 @@ "name": "InvalidChainId", "type": "error" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "devFeature", + "type": "bytes32" + } + ], + "name": "InvalidDevFeatureAccess", + "type": "error" + }, { "inputs": [ { From 97e67751032eb24644ef9f7b6d1a8807dd5bcb9b Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 13:27:05 -0400 Subject: [PATCH 68/75] Pass opcm as an argument to _verifyOpcmContractRef --- .../scripts/deploy/VerifyOPCM.s.sol | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index a580bffda5c0c..2966cf5345a26 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -107,10 +107,7 @@ contract VerifyOPCM is Script { /// @notice Setup flag. bool internal ready; - /// @notice The OPCM address being verified, stored to access during contract verification. - address internal currentOpcmAddress; /// @notice Populates override mappings. - function setUp() public { // Overrides for situations where field names do not cleanly map to contract names. fieldNameOverrides["optimismPortalImpl"] = "OptimismPortal2"; @@ -182,9 +179,18 @@ contract VerifyOPCM is Script { /// @param _name Name of the contract to verify. /// @param _addr Address of the contract to verify. /// @param _skipConstructorVerification Whether to skip constructor verification. - function runSingle(string memory _name, address _addr, bool _skipConstructorVerification) public { + function runSingle( + IOPContractsManager _opcm, + string memory _name, + address _addr, + bool _skipConstructorVerification + ) + public + { _verifyOpcmContractRef( - OpcmContractRef({ field: _name, name: _name, addr: _addr, blueprint: false }), _skipConstructorVerification + _opcm, + OpcmContractRef({ field: _name, name: _name, addr: _addr, blueprint: false }), + _skipConstructorVerification ); } @@ -204,9 +210,6 @@ contract VerifyOPCM is Script { console.log(" Do NOT do this in production"); } - // Store OPCM address for use in verification functions - currentOpcmAddress = _opcmAddress; - // Fetch Implementations & Blueprints from OPCM IOPContractsManager opcm = IOPContractsManager(_opcmAddress); @@ -222,7 +225,7 @@ contract VerifyOPCM is Script { // Verify each reference. bool success = true; for (uint256 i = 0; i < refs.length; i++) { - success = _verifyOpcmContractRef(refs[i], _skipConstructorVerification) && success; + success = _verifyOpcmContractRef(opcm, refs[i], _skipConstructorVerification) && success; } // Final Result @@ -394,6 +397,7 @@ contract VerifyOPCM is Script { /// @param _skipConstructorVerification Whether to skip constructor verification. /// @return True if the contract reference is verified, false otherwise. function _verifyOpcmContractRef( + IOPContractsManager _opcm, OpcmContractRef memory _target, bool _skipConstructorVerification ) @@ -404,9 +408,7 @@ contract VerifyOPCM is Script { console.log(string.concat("Checking Contract: ", _target.field)); // Check if this is a V2 dispute game that should be skipped if (_isV2DisputeGameImplementation(_target.name)) { - IOPContractsManager opcm = IOPContractsManager(currentOpcmAddress); - - if (!_isV2DisputeGamesEnabled(opcm)) { + if (!_isV2DisputeGamesEnabled(_opcm)) { if (_target.addr == address(0)) { console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); console.log(string.concat(" Contract: ", _target.name)); From 94ef1c6421e000e98835324c725fe8828cc9bcdc Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 15:43:18 -0400 Subject: [PATCH 69/75] Reorganize verifyOPCM v2 skip logic --- .../scripts/deploy/VerifyOPCM.s.sol | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 2966cf5345a26..1eeb0dd005dff 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -404,33 +404,32 @@ contract VerifyOPCM is Script { internal returns (bool) { + bool success = true; + console.log(); console.log(string.concat("Checking Contract: ", _target.field)); + console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); + console.log(string.concat(" Contract: ", _target.name)); + console.log(string.concat(" Address: ", vm.toString(_target.addr))); + + // Build the expected path to the artifact file. + string memory artifactPath = _buildArtifactPath(_target.name); + console.log(string.concat(" Expected Runtime Artifact: ", artifactPath)); + // Check if this is a V2 dispute game that should be skipped if (_isV2DisputeGameImplementation(_target.name)) { if (!_isV2DisputeGamesEnabled(_opcm)) { if (_target.addr == address(0)) { - console.log(" [SKIP] V2 dispute game not deployed (feature disabled)"); - console.log(string.concat(" Contract: ", _target.name)); + console.log("[SKIP] V2 dispute game not deployed (feature disabled)"); return true; // Consider this "verified" when feature is off } else { - console.log(" [FAIL] V2 dispute game deployed but feature disabled"); - console.log(string.concat(" Contract: ", _target.name)); - console.log(string.concat(" Address: ", vm.toString(_target.addr))); - return false; + console.log("[FAIL] ERROR: V2 dispute game deployed but feature disabled"); + success = false; } } // If feature is enabled, continue with normal verification } - console.log(string.concat(" Type: ", _target.blueprint ? "Blueprint" : "Implementation")); - console.log(string.concat(" Contract: ", _target.name)); - console.log(string.concat(" Address: ", vm.toString(_target.addr))); - - // Build the expected path to the artifact file. - string memory artifactPath = _buildArtifactPath(_target.name); - console.log(string.concat(" Expected Runtime Artifact: ", artifactPath)); - // Load artifact information (bytecode, immutable refs) for detailed comparison ArtifactInfo memory artifact = _loadArtifactInfo(artifactPath); @@ -472,7 +471,7 @@ contract VerifyOPCM is Script { } // Perform detailed bytecode comparison. - bool success = _compareBytecode(actualCode, expectedCode, _target.name, artifact, !_target.blueprint); + success = _compareBytecode(actualCode, expectedCode, _target.name, artifact, !_target.blueprint) && success; // If requested and this is not a blueprint, we also need to check the creation code. if (!_target.blueprint && !_skipConstructorVerification) { From ad552a0ca56a2eb52dc9454b9d505b7eb9a6ef28 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 9 Oct 2025 18:40:55 -0400 Subject: [PATCH 70/75] Add TODO to fix acceptance test --- op-acceptance-tests/tests/base/disputegame_v2/init_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/op-acceptance-tests/tests/base/disputegame_v2/init_test.go b/op-acceptance-tests/tests/base/disputegame_v2/init_test.go index 7f14f8776a715..c777264489c80 100644 --- a/op-acceptance-tests/tests/base/disputegame_v2/init_test.go +++ b/op-acceptance-tests/tests/base/disputegame_v2/init_test.go @@ -7,5 +7,7 @@ import ( ) func TestMain(m *testing.M) { - presets.DoMain(m, presets.WithMinimal(), presets.WithDisputeGameV2()) + // TODO(#17810): Use the new v2 dispute game flag via presets.WithDisputeGameV2() + //presets.DoMain(m, presets.WithMinimal(), presets.WithDisputeGameV2()) + presets.DoMain(m, presets.WithMinimal()) } From 26ce73550987ae3e590b787e488138969f35b823 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 10 Oct 2025 10:34:02 -0400 Subject: [PATCH 71/75] Add function documentation for the new opcm param --- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 1eeb0dd005dff..e46256cda65d6 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -176,6 +176,7 @@ contract VerifyOPCM is Script { } /// @notice Entry point for the script when trying to verify a single contract by name. + /// @param _opcm The opcm that contains the target contract being verified. /// @param _name Name of the contract to verify. /// @param _addr Address of the contract to verify. /// @param _skipConstructorVerification Whether to skip constructor verification. @@ -393,6 +394,7 @@ contract VerifyOPCM is Script { } /// @notice Verifies a single OPCM contract reference (implementation or bytecode). + /// @param _opcm The opcm that contains the target contract reference. /// @param _target The target contract reference to verify. /// @param _skipConstructorVerification Whether to skip constructor verification. /// @return True if the contract reference is verified, false otherwise. From d68a798bbbc99973cc49c054d5014402b1e79aed Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 10 Oct 2025 10:53:02 -0400 Subject: [PATCH 72/75] Cleanup: add test assertion messages --- .../test/L1/OPContractsManager.t.sol | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 54be261991428..0a7b8305b3d46 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -1966,19 +1966,25 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // Sanity-check setup is consistent with devFeatures flag IOPContractsManager.Implementations memory impls = opcm.implementations(); - bool expectEmptyV2Implementations = isV2 ? false : true; - assertEq(address(impls.permissionedDisputeGameV2Impl) == address(0), expectEmptyV2Implementations); - assertEq(address(impls.faultDisputeGameV2Impl) == address(0), expectEmptyV2Implementations); + address pdgImpl = address(impls.permissionedDisputeGameV2Impl); + address fdgImpl = address(impls.faultDisputeGameV2Impl); + if (isV2) { + assertFalse(pdgImpl == address(0), "PDG implementation address should be non-zero"); + assertFalse(fdgImpl == address(0), "FDG implementation address should be non-zero"); + } else { + assertTrue(pdgImpl == address(0), "PDG implementation address should be zero"); + assertTrue(fdgImpl == address(0), "FDG implementation address should be zero"); + } + // Run OPCM.deploy IOPContractsManager.DeployInput memory opcmInput = toOPCMDeployInput(deployOPChainInput); IOPContractsManager.DeployOutput memory opcmOutput = opcm.deploy(opcmInput); // Verify that the DisputeGameFactory has registered an implementation for the PERMISSIONED_CANNON game type - address expectedPDGAddress = - isV2 ? impls.permissionedDisputeGameV2Impl : address(opcmOutput.permissionedDisputeGame); + address expectedPDGAddress = isV2 ? pdgImpl : address(opcmOutput.permissionedDisputeGame); address actualPDGAddress = address(opcmOutput.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); assertNotEq(actualPDGAddress, address(0), "DisputeGameFactory should have a registered PERMISSIONED_CANNON"); - assertEq(actualPDGAddress, address(expectedPDGAddress)); + assertEq(actualPDGAddress, address(expectedPDGAddress), "PDG address should match"); // Create a game proxy to test immutable fields Claim claim = Claim.wrap(bytes32(uint256(9876))); @@ -2009,7 +2015,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { assertEq(address(pdg.anchorStateRegistry()), address(opcmOutput.anchorStateRegistryProxy), "ASR should match"); assertEq(address(pdg.weth()), address(opcmOutput.delayedWETHPermissionedGameProxy), "WETH should match"); assertEq(pdg.l2ChainId(), opcmInput.l2ChainId, "L2 chain ID should match"); - // For permissioned game, check proposer and challenger assertEq(pdg.proposer(), opcmInput.roles.proposer, "Proposer should match"); assertEq(pdg.challenger(), opcmInput.roles.challenger, "Challenger should match"); From 63409d3254a7d1a7803e7e842cd1969b03f432ad Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 10 Oct 2025 11:33:10 -0400 Subject: [PATCH 73/75] Remove opcm param from runSingle script --- .../scripts/deploy/VerifyOPCM.s.sol | 16 ++++----- .../test/scripts/VerifyOPCM.t.sol | 33 +++++++++++++++---- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index e46256cda65d6..3c29bfe7d3b72 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -176,20 +176,16 @@ contract VerifyOPCM is Script { } /// @notice Entry point for the script when trying to verify a single contract by name. - /// @param _opcm The opcm that contains the target contract being verified. /// @param _name Name of the contract to verify. /// @param _addr Address of the contract to verify. /// @param _skipConstructorVerification Whether to skip constructor verification. - function runSingle( - IOPContractsManager _opcm, - string memory _name, - address _addr, - bool _skipConstructorVerification - ) - public - { + function runSingle(string memory _name, address _addr, bool _skipConstructorVerification) public { + // This function is used as part of the release checklist to verify new contracts. + // Rather than requiring an opcm input parameter, just pass in an empty reference + // as we really only need this for features that are in development. + IOPContractsManager emptyOpcm = IOPContractsManager(address(0)); _verifyOpcmContractRef( - _opcm, + emptyOpcm, OpcmContractRef({ field: _name, name: _name, addr: _addr, blueprint: false }), _skipConstructorVerification ); diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index 0ef74f1f7dc3a..4090ab61fdd21 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -88,6 +88,27 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { harness.run(address(opcm), true); } + /// @notice Tests that the runSingle script succeeds when run against production contracts. + function test_runSingle_succeeds() public { + VerifyOPCM.OpcmContractRef[][2] memory refsByType; + refsByType[0] = harness.getOpcmContractRefs(opcm, "implementations", false); + refsByType[1] = harness.getOpcmContractRefs(opcm, "blueprints", true); + + for (uint8 i = 0; i < refsByType.length; i++) { + for (uint256 j = 0; j < refsByType[i].length; j++) { + VerifyOPCM.OpcmContractRef memory ref = refsByType[i][j]; + + // TODO(#17262): Remove these skips once these contracts are no longer behind a feature flag + // This script doesn't work for features that are in-development, so skip for now + if (_isDisputeGameV2ContractRef(ref)) { + continue; + } + + harness.runSingle(ref.name, ref.addr, true); + } + } + } + function test_run_bitmapNotEmptyOnMainnet_reverts(bytes32 _devFeatureBitmap) public { // Coverage changes bytecode and causes failures, skip. skipIfCoverage(); @@ -132,9 +153,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = - LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); - if (isV2DisputeGame && !v2FeatureEnabled) { + if (_isDisputeGameV2ContractRef(ref) && !v2FeatureEnabled) { continue; } @@ -203,9 +222,7 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { VerifyOPCM.OpcmContractRef memory ref = refs[randomImplIndex]; // Skip V2 dispute games when feature disabled - bool isV2DisputeGame = - LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); - if (isV2DisputeGame && !v2FeatureEnabled) { + if (_isDisputeGameV2ContractRef(ref) && !v2FeatureEnabled) { continue; } @@ -355,6 +372,10 @@ contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { assertGt(componentsWithContainerTested, 0, "Should have tested at least one component"); } + function _isDisputeGameV2ContractRef(VerifyOPCM.OpcmContractRef memory ref) internal pure returns (bool) { + return LibString.eq(ref.name, "FaultDisputeGameV2") || LibString.eq(ref.name, "PermissionedDisputeGameV2"); + } + /// @notice Utility function to mock the first OPCM component's contractsContainer address. /// @param _propRefs Array of property references to search through. /// @param _mockAddress The address to mock the contractsContainer call to return. From 463a7d11cbdadfa425974ef2f706af1c07fe4dca Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 10 Oct 2025 12:00:45 -0400 Subject: [PATCH 74/75] Tweak method documentation --- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 3c29bfe7d3b72..b8dc9a685b42f 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -390,7 +390,7 @@ contract VerifyOPCM is Script { } /// @notice Verifies a single OPCM contract reference (implementation or bytecode). - /// @param _opcm The opcm that contains the target contract reference. + /// @param _opcm The OPCM contract that contains the target contract reference. /// @param _target The target contract reference to verify. /// @param _skipConstructorVerification Whether to skip constructor verification. /// @return True if the contract reference is verified, false otherwise. From 81cbdd2db5d3c7b9c43ab28d1776150c434e8a40 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 10 Oct 2025 12:01:15 -0400 Subject: [PATCH 75/75] Cleanup dead code --- packages/contracts-bedrock/test/L1/OPContractsManager.t.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 0a7b8305b3d46..babd8742b5a3e 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -2024,12 +2024,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { /// @title OPContractsManager_Version_Test /// @notice Tests the `version` function of the `OPContractsManager` contract. contract OPContractsManager_Version_Test is OPContractsManager_TestInit { - OPContractsManager.AddGameInput[] internal gameInput; - - function setUp() public override { - super.setUp(); - } - function test_semver_works() public view { assertNotEq(abi.encode(opcm.version()), abi.encode(0)); }