diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index adb75fe8c8d..be3b864b94e 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 @@ -216,6 +221,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 dced3b7d2c3..430c07bc677 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/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 39ae8d5eb02..371eb0118d3 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,13 +107,18 @@ 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"; fieldNameOverrides["optimismPortalInteropImpl"] = "OptimismPortalInterop"; fieldNameOverrides["mipsImpl"] = "MIPS64"; fieldNameOverrides["ethLockboxImpl"] = "ETHLockbox"; + fieldNameOverrides["faultDisputeGameV2Impl"] = "FaultDisputeGameV2"; + fieldNameOverrides["permissionedDisputeGameV2Impl"] = "PermissionedDisputeGameV2"; fieldNameOverrides["permissionlessDisputeGame1"] = "FaultDisputeGame"; fieldNameOverrides["permissionlessDisputeGame2"] = "FaultDisputeGame"; fieldNameOverrides["permissionedDisputeGame1"] = "PermissionedDisputeGame"; @@ -199,6 +205,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); @@ -394,6 +403,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))); @@ -498,6 +526,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/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index f4ef1718aa4..7af882bfa86 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", @@ -530,6 +540,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 c49484fc722..6b4c6228c27 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 7cd7a44502c..28e694d5e4f 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", @@ -428,6 +438,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 80b80512aeb..d9ac76fa834 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 b06cd541bb3..e8d1dec02c2 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 512a83ae75c..a092f317994 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/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 4d4f5835d4f..6d15c3221fd 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", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index d87deb94bc7..21930538698 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", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 424066f1e6f..586a7fc0562 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"; @@ -309,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()); @@ -362,8 +393,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); + } } } @@ -553,7 +599,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. @@ -940,7 +988,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("")); } } @@ -1050,30 +1098,31 @@ 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 + ) ) - ) - ); + ); + } // -------- Set and Initialize Proxy Implementations -------- bytes memory data; @@ -1159,11 +1208,19 @@ 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)) { + // Extracted to helper function to avoid stack too deep error + _registerPermissionedGameV2(_input, implementation, output); + } else { + // Register v1 implementation for PERMISSIONED_CANNON game type + setDGFImplementation( + output.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + IDisputeGame(address(output.permissionedDisputeGame)), + bytes("") + ); + } transferOwnership(address(output.disputeGameFactoryProxy), address(_input.roles.opChainProxyAdminOwner)); @@ -1739,6 +1796,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 @@ -1778,6 +1838,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 @@ -1962,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 DELEGATECALLed by the superchainConfig's ProxyAdminOwner. + /// @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(); diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index eeeff41eaab..447b70dfb85 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"; @@ -25,6 +29,7 @@ import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; @@ -306,11 +311,154 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } } +/// @title OPContractsManager_Init +/// @notice Base contract for OPContractsManager tests that need v2 functionality +contract OPContractsManager_Init is DeployOPChain_TestBase { + IOPContractsManager.DeployOutput internal chainDeployOutput1; + IOPContractsManager.DeployOutput internal chainDeployOutput2; + IDelayedWETH internal standaloneDelayedWETH; + + function setUp() public virtual override { + DeployOPChain_TestBase.setUp(); + + // Initialize DOI with necessary values + // Set the test contract as the owner for delegatecall compatibility + doi.set(doi.opChainProxyAdminOwner.selector, address(this)); + doi.set(doi.systemConfigOwner.selector, systemConfigOwner); + doi.set(doi.batcher.selector, batcher); + doi.set(doi.unsafeBlockSigner.selector, unsafeBlockSigner); + doi.set(doi.proposer.selector, proposer); + doi.set(doi.challenger.selector, challenger); + doi.set(doi.basefeeScalar.selector, basefeeScalar); + doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar); + doi.set(doi.l2ChainId.selector, l2ChainId); + doi.set(doi.opcm.selector, address(opcm)); + doi.set(doi.gasLimit.selector, gasLimit); + doi.set(doi.disputeGameType.selector, disputeGameType); + doi.set(doi.disputeAbsolutePrestate.selector, disputeAbsolutePrestate); + doi.set(doi.disputeMaxGameDepth.selector, disputeMaxGameDepth); + doi.set(doi.disputeSplitDepth.selector, disputeSplitDepth); + doi.set(doi.disputeClockExtension.selector, disputeClockExtension); + doi.set(doi.disputeMaxClockDuration.selector, disputeMaxClockDuration); + + chainDeployOutput1 = createChainContracts(100); + chainDeployOutput2 = createChainContracts(101); + + vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); + vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); + + // Deploy a standalone DelayedWETH for tests to avoid ProxyAdmin ownership issues + standaloneDelayedWETH = deployStandaloneDelayedWETH(); + } + + /// @notice Deploy a standalone DelayedWETH contract for testing + function deployStandaloneDelayedWETH() internal returns (IDelayedWETH) { + // Deploy proxy + address delayedWETHProxy = DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(this)))) + }); + + // Deploy implementation + address delayedWETHImpl = address(opcm.implementations().delayedWETHImpl); + + // Initialize the proxy with the implementation + IProxy(payable(delayedWETHProxy)).upgradeTo(delayedWETHImpl); + IDelayedWETH(payable(delayedWETHProxy)).initialize(chainDeployOutput1.systemConfigProxy); + + return IDelayedWETH(payable(delayedWETHProxy)); + } + + /// @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 Helper function to convert DOI to OPCM deploy input + function toOPCMDeployInput(DeployOPChainInput _doi) + internal + view + virtual + returns (IOPContractsManager.DeployInput memory) + { + return IOPContractsManager.DeployInput({ + roles: IOPContractsManager.Roles({ + opChainProxyAdminOwner: _doi.opChainProxyAdminOwner(), + systemConfigOwner: _doi.systemConfigOwner(), + batcher: _doi.batcher(), + unsafeBlockSigner: _doi.unsafeBlockSigner(), + proposer: _doi.proposer(), + challenger: _doi.challenger() + }), + basefeeScalar: _doi.basefeeScalar(), + blobBasefeeScalar: _doi.blobBaseFeeScalar(), + l2ChainId: _doi.l2ChainId(), + startingAnchorRoot: _doi.startingAnchorRoot(), + saltMixer: _doi.saltMixer(), + gasLimit: _doi.gasLimit(), + disputeGameType: _doi.disputeGameType(), + disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), + disputeMaxGameDepth: _doi.disputeMaxGameDepth(), + disputeSplitDepth: _doi.disputeSplitDepth(), + disputeClockExtension: _doi.disputeClockExtension(), + disputeMaxClockDuration: _doi.disputeMaxClockDuration() + }); + } + + /// @notice Helper function to deploy a new set of L1 contracts via OPCM. + /// @param _l2ChainId The L2 chain ID to deploy the contracts for. + function createChainContracts(uint256 _l2ChainId) internal returns (IOPContractsManager.DeployOutput memory) { + doi.set(doi.l2ChainId.selector, _l2ChainId); + IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); + deployInput.saltMixer = string.concat("test-", vm.toString(_l2ChainId)); + + return opcm.deploy(deployInput); + } +} + /// @title OPContractsManager_TestInit /// @notice Reusable test initialization for `OPContractsManager` tests. contract OPContractsManager_TestInit is CommonTest { IOPContractsManager.DeployOutput internal chainDeployOutput1; IOPContractsManager.DeployOutput internal chainDeployOutput2; + IDelayedWETH internal standaloneDelayedWETH; function setUp() public virtual override { super.setUp(); @@ -320,6 +468,27 @@ contract OPContractsManager_TestInit is CommonTest { vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); + + // Deploy a standalone DelayedWETH for tests to avoid ProxyAdmin ownership issues + standaloneDelayedWETH = deployStandaloneDelayedWETH(); + } + + /// @notice Deploy a standalone DelayedWETH contract for testing + function deployStandaloneDelayedWETH() internal returns (IDelayedWETH) { + // Deploy proxy + address delayedWETHProxy = DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(this)))) + }); + + // Deploy implementation + address delayedWETHImpl = address(opcm.implementations().delayedWETHImpl); + + // Initialize the proxy with the implementation + IProxy(payable(delayedWETHProxy)).upgradeTo(delayedWETHImpl); + IDelayedWETH(payable(delayedWETHProxy)).initialize(chainDeployOutput1.systemConfigProxy); + + return IDelayedWETH(payable(delayedWETHProxy)); } /// @notice Sets up the environment variables for the VerifyOPCM test. @@ -427,7 +596,7 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test { /// @title OPContractsManager_AddGameType_Test /// @notice Tests the `addGameType` function of the `OPContractsManager` contract. -contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { +contract OPContractsManager_AddGameType_Test is OPContractsManager_Init { event GameTypeAdded( uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame ); @@ -435,7 +604,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { /// @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. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.PERMISSIONED_CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(true); // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); @@ -454,9 +623,9 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } /// @notice Tests that we can add a FaultDisputeGame implementation with addGameType. - function test_addGameType_cannon_succeeds() public { + function test_addGameType_permissionless_succeeds() public { // Create the input for the Permissionless game type. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(false); // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); @@ -476,7 +645,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { /// @notice Tests that we can add a SuperPermissionedDisputeGame implementation with addGameType. function test_addGameType_permissionedSuper_succeeds() public { // Create the input for the Super game type. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.SUPER_PERMISSIONED_CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(true); input.disputeGameType = GameTypes.SUPER_PERMISSIONED_CANNON; // Since OPCM will start with the standard Permissioned (non-Super) game type we won't have @@ -515,9 +684,10 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } /// @notice Tests that we can add a SuperFaultDisputeGame implementation with addGameType. - function test_addGameType_superCannon_succeeds() public { + function test_addGameType_permissionlessSuper_succeeds() public { // Create the input for the Super game type. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.SUPER_CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(false); + input.disputeGameType = GameTypes.SUPER_CANNON; // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); @@ -537,31 +707,8 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { /// @notice Tests that addGameType will revert if the game type is not supported. function test_addGameType_unsupportedGameType_reverts() public { - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameType.wrap(2000)); - - // Run the addGameType call, should revert. - IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); - inputs[0] = input; - (bool success,) = address(opcm).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (inputs))); - assertFalse(success, "addGameType should have failed"); - } - - /// @notice Tests that addGameType will revert if the game type is cannon-kona and the dev feature is not enabled - function test_addGameType_cannonKonaGameTypeDisabled_reverts() public { - skipIfDevFeatureEnabled(DevFeatures.CANNON_KONA); - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON_KONA); - - // Run the addGameType call, should revert. - IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); - inputs[0] = input; - (bool success,) = address(opcm).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (inputs))); - assertFalse(success, "addGameType should have failed"); - } - - /// @notice Tests that addGameType will revert if the game type is cannon-kona and the dev feature is not enabled - function test_addGameType_superCannonKonaGameTypeDisabled_reverts() public { - skipIfDevFeatureEnabled(DevFeatures.CANNON_KONA); - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.SUPER_CANNON_KONA); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(false); + input.disputeGameType = GameType.wrap(2000); // Run the addGameType call, should revert. IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); @@ -582,7 +729,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { ) ); vm.etch(address(delayedWETH), hex"01"); - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(false); input.delayedWETH = delayedWETH; IOPContractsManager.AddGameOutput memory output = addGameType(input); assertValidGameType(input, output); @@ -590,8 +737,10 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } function test_addGameType_outOfOrderInputs_reverts() public { - IOPContractsManager.AddGameInput memory input1 = newGameInputFactory(GameType.wrap(2)); - IOPContractsManager.AddGameInput memory input2 = newGameInputFactory(GameType.wrap(1)); + IOPContractsManager.AddGameInput memory input1 = newGameInputFactory(false); + input1.disputeGameType = GameType.wrap(2); + IOPContractsManager.AddGameInput memory input2 = newGameInputFactory(false); + input2.disputeGameType = GameType.wrap(1); IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](2); inputs[0] = input1; inputs[1] = input2; @@ -602,7 +751,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } function test_addGameType_duplicateGameType_reverts() public { - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(false); IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](2); inputs[0] = input; inputs[1] = input; @@ -624,7 +773,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } function test_addGameType_notDelegateCall_reverts() public { - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.PERMISSIONED_CANNON); + IOPContractsManager.AddGameInput memory input = newGameInputFactory(true); IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); inputs[0] = input; @@ -632,6 +781,182 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { opcm.addGameType(inputs); } + /// @notice Test that addGameType with v2 flag uses v2 implementation for PERMISSIONED_CANNON + function test_addGameType_withV2Flag_permissionedCannon_succeeds() public { + // Deploy OPCM with v2 flag enabled + IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); + + // Deploy a chain first with v2-enabled OPCM + IOPContractsManager.DeployInput memory deployInput = IOPContractsManager.DeployInput({ + roles: IOPContractsManager.Roles({ + opChainProxyAdminOwner: makeAddr("opChainProxyAdminOwner"), + systemConfigOwner: makeAddr("systemConfigOwner"), + batcher: makeAddr("batcher"), + unsafeBlockSigner: makeAddr("unsafeBlockSigner"), + proposer: makeAddr("proposer"), + challenger: makeAddr("challenger") + }), + basefeeScalar: 100, + blobBasefeeScalar: 200, + l2ChainId: 10001, + startingAnchorRoot: abi.encode(Proposal({ root: Hash.wrap(bytes32(uint256(0x123))), l2SequenceNumber: 1 })), + saltMixer: "test-salt", + gasLimit: 30_000_000, + disputeGameType: GameTypes.PERMISSIONED_CANNON, + disputeAbsolutePrestate: Claim.wrap(bytes32(uint256(0x456))), + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(uint64(3 hours)), + disputeMaxClockDuration: Duration.wrap(uint64(3.5 days)) + }); + + vm.startPrank(address(this)); + IOPContractsManager.DeployOutput memory output = opcmV2.deploy(deployInput); + vm.stopPrank(); + + // Get the v2 implementation address from OPCM + IOPContractsManager.Implementations memory impls = opcmV2.implementations(); + + // Prepare to add PERMISSIONED_CANNON game type with a different prestate + IOPContractsManager.AddGameInput[] memory gameConfigs = new IOPContractsManager.AddGameInput[](1); + gameConfigs[0] = IOPContractsManager.AddGameInput({ + disputeGameType: GameTypes.PERMISSIONED_CANNON, + systemConfig: output.systemConfigProxy, + proxyAdmin: output.opChainProxyAdmin, + delayedWETH: output.delayedWETHPermissionedGameProxy, // Use existing DelayedWETH to avoid proxy upgrade + disputeAbsolutePrestate: Claim.wrap(bytes32(uint256(0x789))), // Different prestate + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(uint64(3 hours)), + disputeMaxClockDuration: Duration.wrap(uint64(3.5 days)), + initialBond: 1 ether, + vm: IBigStepper(makeAddr("vm")), + permissioned: true, + saltMixer: "salt1" + }); + + // Transfer DisputeGameFactory ownership to test contract temporarily for delegatecall + // This is needed because addGameType uses delegatecall and needs to call setImplementation + vm.prank(makeAddr("opChainProxyAdminOwner")); + output.disputeGameFactoryProxy.transferOwnership(address(this)); + + // Add the game type via delegatecall + (bool success, bytes memory rawGameOut) = + address(opcmV2).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (gameConfigs))); + assertTrue(success, "addGameType failed"); + + IOPContractsManager.AddGameOutput[] memory addGameOutputs = + abi.decode(rawGameOut, (IOPContractsManager.AddGameOutput[])); + + // Transfer DisputeGameFactory ownership back to the original owner + output.disputeGameFactoryProxy.transferOwnership(makeAddr("opChainProxyAdminOwner")); + + // Verify v2 implementation is registered in DisputeGameFactory + address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); + + // Verify implementation address matches permissionedDisputeGameV2Impl + assertEq( + registeredImpl, + address(impls.permissionedDisputeGameV2Impl), + "DisputeGameFactory should have v2 PermissionedDisputeGame implementation registered" + ); + + // Verify that the returned fault dispute game is the v2 implementation + assertEq( + address(addGameOutputs[0].faultDisputeGame), + address(impls.permissionedDisputeGameV2Impl), + "addGameType should return v2 PermissionedDisputeGame implementation" + ); + } + + /// @notice Test that addGameType with v2 flag uses v2 implementation for CANNON (permissionless) + function test_addGameType_withV2Flag_permissionlessCannon_succeeds() public { + // Deploy OPCM with v2 flag enabled + IOPContractsManager opcmV2 = _deployOPCMWithV2Flag(); + + // Deploy a chain first with v2-enabled OPCM + IOPContractsManager.DeployInput memory deployInput = IOPContractsManager.DeployInput({ + roles: IOPContractsManager.Roles({ + opChainProxyAdminOwner: makeAddr("opChainProxyAdminOwner"), + systemConfigOwner: makeAddr("systemConfigOwner"), + batcher: makeAddr("batcher"), + unsafeBlockSigner: makeAddr("unsafeBlockSigner"), + proposer: makeAddr("proposer"), + challenger: makeAddr("challenger") + }), + basefeeScalar: 100, + blobBasefeeScalar: 200, + l2ChainId: 10002, + startingAnchorRoot: abi.encode(Proposal({ root: Hash.wrap(bytes32(uint256(0x123))), l2SequenceNumber: 1 })), + saltMixer: "test-salt-2", + gasLimit: 30_000_000, + disputeGameType: GameTypes.PERMISSIONED_CANNON, + disputeAbsolutePrestate: Claim.wrap(bytes32(uint256(0x456))), + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(uint64(3 hours)), + disputeMaxClockDuration: Duration.wrap(uint64(3.5 days)) + }); + + vm.startPrank(address(this)); + IOPContractsManager.DeployOutput memory output = opcmV2.deploy(deployInput); + vm.stopPrank(); + + // Get the v2 implementation address from OPCM + IOPContractsManager.Implementations memory impls = opcmV2.implementations(); + + // Prepare to add CANNON game type (permissionless) + IOPContractsManager.AddGameInput[] memory gameConfigs = new IOPContractsManager.AddGameInput[](1); + gameConfigs[0] = IOPContractsManager.AddGameInput({ + disputeGameType: GameTypes.CANNON, + systemConfig: output.systemConfigProxy, + proxyAdmin: output.opChainProxyAdmin, + delayedWETH: output.delayedWETHPermissionedGameProxy, // Use existing DelayedWETH to avoid proxy upgrade + disputeAbsolutePrestate: Claim.wrap(bytes32(uint256(0xabc))), // Different prestate + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(uint64(3 hours)), + disputeMaxClockDuration: Duration.wrap(uint64(3.5 days)), + initialBond: 1 ether, + vm: IBigStepper(makeAddr("vm")), + permissioned: false, // Permissionless CANNON + saltMixer: "salt2" + }); + + // Transfer DisputeGameFactory ownership to test contract temporarily for delegatecall + // This is needed because addGameType uses delegatecall and needs to call setImplementation + vm.prank(makeAddr("opChainProxyAdminOwner")); + output.disputeGameFactoryProxy.transferOwnership(address(this)); + + // Add the game type via delegatecall + (bool success, bytes memory rawGameOut) = + address(opcmV2).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (gameConfigs))); + assertTrue(success, "addGameType failed"); + + IOPContractsManager.AddGameOutput[] memory addGameOutputs = + abi.decode(rawGameOut, (IOPContractsManager.AddGameOutput[])); + + // Transfer DisputeGameFactory ownership back to the original owner + output.disputeGameFactoryProxy.transferOwnership(makeAddr("opChainProxyAdminOwner")); + + // Verify v2 implementation is registered in DisputeGameFactory + address registeredImpl = address(output.disputeGameFactoryProxy.gameImpls(GameTypes.CANNON)); + + // Verify implementation address matches faultDisputeGameV2Impl + assertEq( + registeredImpl, + address(impls.faultDisputeGameV2Impl), + "DisputeGameFactory should have v2 FaultDisputeGame implementation registered" + ); + + // Verify that the returned fault dispute game is the v2 implementation + assertEq( + address(addGameOutputs[0].faultDisputeGame), + address(impls.faultDisputeGameV2Impl), + "addGameType should return v2 FaultDisputeGame implementation" + ); + } + function addGameType(IOPContractsManager.AddGameInput memory input) internal returns (IOPContractsManager.AddGameOutput memory) @@ -657,13 +982,13 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { return addGameOutAll[0]; } - function newGameInputFactory(GameType _gameType) internal view returns (IOPContractsManager.AddGameInput memory) { + function newGameInputFactory(bool permissioned) internal view returns (IOPContractsManager.AddGameInput memory) { return IOPContractsManager.AddGameInput({ saltMixer: "hello", systemConfig: chainDeployOutput1.systemConfigProxy, proxyAdmin: chainDeployOutput1.opChainProxyAdmin, - delayedWETH: IDelayedWETH(payable(address(0))), - disputeGameType: _gameType, + delayedWETH: standaloneDelayedWETH, + disputeGameType: GameType.wrap(permissioned ? 1 : 0), disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), disputeMaxGameDepth: 73, disputeSplitDepth: 30, @@ -671,8 +996,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { disputeMaxClockDuration: Duration.wrap(302400), initialBond: 1 ether, vm: IBigStepper(address(opcm.implementations().mipsImpl)), - permissioned: _gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw() - || _gameType.raw() == GameTypes.SUPER_PERMISSIONED_CANNON.raw() + permissioned: permissioned }); } @@ -726,56 +1050,6 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { } } -/// @title OPContractsManager_AddGameTypeCannonKonaEnabled_Test -/// @notice Tests the `addGameType` function of the `OPContractsManager` contract with CANNON_KONA enabled. -contract OPContractsManager_AddGameType_CannonKonaEnabled_Test is OPContractsManager_AddGameType_Test { - function setUp() public override { - setDevFeatureEnabled(DevFeatures.CANNON_KONA); - super.setUp(); - } - - /// @notice Tests that addGameType will revert if the game type is cannon-kona and the dev feature is not enabled - function test_addGameType_cannonKonaGameType_succeeds() public { - // Create the input for the cannon-kona game type. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON_KONA); - - // Run the addGameType call. - IOPContractsManager.AddGameOutput memory output = addGameType(input); - assertValidGameType(input, output); - - // Check the values on the new game type. - IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); - - // Proposer call should revert because this is a permissionless game. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - notPDG.proposer(); - - // L2 chain ID call should not revert because this is not a Super game. - assertNotEq(notPDG.l2ChainId(), 0, "l2ChainId should not be zero"); - } - - /// @notice Tests that addGameType will revert if the game type is cannon-kona and the dev feature is not enabled - function test_addGameType_superCannonKonaGameType_succeeds() public { - // Create the input for the cannon-kona game type. - IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.SUPER_CANNON_KONA); - - // Run the addGameType call. - IOPContractsManager.AddGameOutput memory output = addGameType(input); - assertValidGameType(input, output); - - // Grab the new game type. - IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); - - // Proposer should fail, this is a permissionless game. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - notPDG.proposer(); - - // Super games don't have the l2ChainId function. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - notPDG.l2ChainId(); - } -} - /// @title OPContractsManager_UpdatePrestate_Test /// @notice Tests the `updatePrestate` function of the `OPContractsManager` contract. contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { @@ -1020,7 +1294,7 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { saltMixer: "hello", systemConfig: chainDeployOutput1.systemConfigProxy, proxyAdmin: chainDeployOutput1.opChainProxyAdmin, - delayedWETH: IDelayedWETH(payable(address(0))), + delayedWETH: standaloneDelayedWETH, disputeGameType: GameType.wrap(permissioned ? 1 : 0), disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), disputeMaxGameDepth: 73, @@ -1278,7 +1552,7 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { Claim absolutePrestate2 = Claim.wrap(bytes32(hex"DEAD")); /// @notice Function requires interop portal. - function setUp() public virtual override { + function setUp() public override { super.setUp(); skipIfDevFeatureDisabled(DevFeatures.OPTIMISM_PORTAL_INTEROP); } @@ -1353,11 +1627,6 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { assertEq(address(_disputeGameFactory.gameImpls(GameTypes.SUPER_CANNON)), address(0)); assertEq(address(_disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)), address(0)); assertEq(address(_disputeGameFactory.gameImpls(GameTypes.SUPER_PERMISSIONED_CANNON)), address(0)); - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - // Only explicitly zeroed out if feature is enabled. Otherwise left unchanged (which may still be 0). - assertEq(address(_disputeGameFactory.gameImpls(GameTypes.CANNON_KONA)), address(0)); - assertEq(address(_disputeGameFactory.gameImpls(GameTypes.SUPER_CANNON_KONA)), address(0)); - } } /// @notice Tests that the migration function succeeds when requesting to use the @@ -1650,51 +1919,12 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { } } -/// @title OPContractsManager_Migrate_CannonKonaEnabled_Test -/// @notice Tests the `migrate` function of the `OPContractsManager` contract. -contract OPContractsManager_Migrate_CannonKonaEnabled_Test is OPContractsManager_Migrate_Test { - function setUp() public override { - setDevFeatureEnabled(DevFeatures.CANNON_KONA); - super.setUp(); - } - - function test_migrate_zerosOutCannonKonaGameTypes_succeeds() public { - IOPContractsManagerInteropMigrator.MigrateInput memory input = _getDefaultInput(); - - // Grab the existing DisputeGameFactory for each chain. - IDisputeGameFactory oldDisputeGameFactory1 = - IDisputeGameFactory(payable(chainDeployOutput1.systemConfigProxy.disputeGameFactory())); - IDisputeGameFactory oldDisputeGameFactory2 = - IDisputeGameFactory(payable(chainDeployOutput2.systemConfigProxy.disputeGameFactory())); - // Ensure cannon kona games have implementations - oldDisputeGameFactory1.setImplementation(GameTypes.CANNON_KONA, IDisputeGame(address(1))); - oldDisputeGameFactory2.setImplementation(GameTypes.CANNON_KONA, IDisputeGame(address(1))); - oldDisputeGameFactory1.setImplementation(GameTypes.SUPER_CANNON_KONA, IDisputeGame(address(2))); - oldDisputeGameFactory2.setImplementation(GameTypes.SUPER_CANNON_KONA, IDisputeGame(address(2))); - - // Execute the migration. - _doMigration(input); - - // Assert that the old game implementations are now zeroed out. - _assertOldGamesZeroed(oldDisputeGameFactory1); - _assertOldGamesZeroed(oldDisputeGameFactory2); - } -} - -/// @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_DeployBase is DeployOPChain_TestBase { using stdStorage for StdStorage; 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); @@ -1722,6 +1952,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { function toOPCMDeployInput(DeployOPChainInput _doi) internal view + virtual returns (IOPContractsManager.DeployInput memory) { return IOPContractsManager.DeployInput({ @@ -1748,6 +1979,74 @@ 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(), + proposer: proposer, + challenger: challenger, + devFeatureBitmap: DevFeatures.DEPLOY_V2_DISPUTE_GAMES // Enable v2 flag here + }) + ); + + return dio.opcm; + } +} + +/// @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_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_DeployBase { function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory deployInput = toOPCMDeployInput(doi); deployInput.l2ChainId = 0; @@ -1768,20 +2067,67 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { emit Deployed(doi.l2ChainId(), address(this), bytes("")); opcm.deploy(toOPCMDeployInput(doi)); } -} + /// @notice Test that deploy without v2 flag doesn't set v2 implementations -/// @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 test_deployWithoutV2Flag_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(); - function setUp() public override { - super.setUp(); - prestateUpdater = opcm; + // 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)); } - function test_semver_works() public view { - assertNotEq(abi.encode(prestateUpdater.version()), abi.encode(0)); + /// @notice Test that deploy with v2 flag would set v2 implementations + function test_deployWithV2Flag_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 + 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( + registeredPermissionedImpl, + address(output.permissionedDisputeGameV2), + "DisputeGameFactory should have v2 PermissionedDisputeGame registered for PERMISSIONED_CANNON" + ); } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 8bbbd9e8c2c..c516d46843f 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,10 @@ contract OPContractsManagerStandardValidator_TestInit is CommonTest { function setUp() public virtual override { 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. deployInput = deploy.getDeployInput(); diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index 686b4489f5d..e08a0da2130 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 30c0e547a29..d49459df52e 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; diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index fd64d07b948..a2714ffd002 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,23 @@ 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,12 +192,23 @@ 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 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; @@ -456,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); + } +}