diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index 5a513822ef9ab..2f5ca270376d8 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -452,6 +452,22 @@ "name": "IdentityPrecompileCallFailed", "type": "error" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "devFeature", + "type": "bytes32" + } + ], + "name": "InvalidDevFeatureAccess", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameArgsLength", + "type": "error" + }, { "inputs": [], "name": "InvalidGameConfigs", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index b695111229bb5..a9d5a0fe78c63 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": "0x0ef3fcb6fee1f73c95a48269bba8c83ee463a6f116f36f8d88edb247f72e8a05", - "sourceCodeHash": "0x16a845ddb5ee469e81f3d817ee9f6d6ff5697ace0bc399bd1e0f26e05546f781" + "initCodeHash": "0x02af0627bee699be308b7d83ceb41655c38ae6856ed3842246917ae696475d64", + "sourceCodeHash": "0x76f8991025c5346053444d50c4c0b63faa1739deebdbad99360fe37ca068ed15" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x2eaa345ba05582c67b40a1eb7ec9d54823aa08468e697e2d6c04bb74cc574abc", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index a0bc0789df196..d4bd71355a3de 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -39,6 +39,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 { ISystemConfig } from "../../interfaces/L1/ISystemConfig.sol"; contract OPContractsManagerContractsContainer { /// @notice Addresses of the Blueprint contracts. @@ -571,18 +572,20 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { outputs[i].delayedWETH = gameConfig.delayedWETH; } - // Determine the contract name and blueprints for the game type. - string memory gameContractName; - address blueprint1; - address blueprint2; - uint256 gameL2ChainId; + // Grab the DisputeGameFactory and AnchorStateRegistry for the chain. + IDisputeGameFactory dgf = getDisputeGameFactory(gameConfig.systemConfig); - // Separate context to avoid stack too deep. - { - // Grab the blueprints once since we'll need it multiple times below. - OPContractsManager.Blueprints memory bps = getBlueprints(); + // Grab the existing game implementation from the DisputeGameFactory. + IFaultDisputeGame existingGame = + IFaultDisputeGame(address(getGameImplementation(dgf, gameConfig.disputeGameType))); - // Determine the contract name and blueprints for the game type. + // Super games don't support V2 contracts yet so fallback to the V1 contracts for those game types. + if ( + isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES) + && gameConfig.disputeGameType.raw() != GameTypes.SUPER_PERMISSIONED_CANNON.raw() + && gameConfig.disputeGameType.raw() != GameTypes.SUPER_CANNON.raw() + && gameConfig.disputeGameType.raw() != GameTypes.SUPER_CANNON_KONA.raw() + ) { if ( gameConfig.disputeGameType.raw() == GameTypes.CANNON.raw() || ( @@ -590,92 +593,138 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { && gameConfig.disputeGameType.raw() == GameTypes.CANNON_KONA.raw() ) ) { - gameContractName = "FaultDisputeGame"; - blueprint1 = bps.permissionlessDisputeGame1; - blueprint2 = bps.permissionlessDisputeGame2; - gameL2ChainId = l2ChainId; + address impl = implementations().faultDisputeGameV2Impl; + bytes memory gameArgs = abi.encodePacked( + gameConfig.disputeAbsolutePrestate, // 32 bytes + gameConfig.vm, // 20 bytes + address(getAnchorStateRegistry(ISystemConfig(gameConfig.systemConfig))), // 20 + // bytes + address(outputs[i].delayedWETH), // 20 bytes + l2ChainId // 32 bytes + ); + setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(impl), gameArgs); + outputs[i].faultDisputeGame = IFaultDisputeGame(impl); } else if (gameConfig.disputeGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { - gameContractName = "PermissionedDisputeGame"; - blueprint1 = bps.permissionedDisputeGame1; - blueprint2 = bps.permissionedDisputeGame2; - gameL2ChainId = l2ChainId; - } else if ( - gameConfig.disputeGameType.raw() == GameTypes.SUPER_CANNON.raw() - || ( - isDevFeatureEnabled(DevFeatures.CANNON_KONA) - && gameConfig.disputeGameType.raw() == GameTypes.SUPER_CANNON_KONA.raw() - ) - ) { - gameContractName = "SuperFaultDisputeGame"; - blueprint1 = bps.superPermissionlessDisputeGame1; - blueprint2 = bps.superPermissionlessDisputeGame2; - gameL2ChainId = 0; - } else if (gameConfig.disputeGameType.raw() == GameTypes.SUPER_PERMISSIONED_CANNON.raw()) { - gameContractName = "SuperPermissionedDisputeGame"; - blueprint1 = bps.superPermissionedDisputeGame1; - blueprint2 = bps.superPermissionedDisputeGame2; - gameL2ChainId = 0; + address impl = implementations().permissionedDisputeGameV2Impl; + bytes memory gameArgs = abi.encodePacked( + gameConfig.disputeAbsolutePrestate, // 32 bytes + gameConfig.vm, // 20 bytes + address(getAnchorStateRegistry(ISystemConfig(gameConfig.systemConfig))), // 20 bytes + address(outputs[i].delayedWETH), // 20 bytes + l2ChainId, // 32 bytes + getProposer(dgf, IPermissionedDisputeGame(address(existingGame)), gameConfig.disputeGameType), // 20 + // bytes + getChallenger(dgf, IPermissionedDisputeGame(address(existingGame)), gameConfig.disputeGameType) // 20 + // bytes + ); + setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(impl), gameArgs); + outputs[i].faultDisputeGame = IFaultDisputeGame(payable(impl)); } else { revert OPContractsManagerGameTypeAdder_UnsupportedGameType(); } - } - - // Grab the DisputeGameFactory and AnchorStateRegistry for the chain. - IDisputeGameFactory dgf = getDisputeGameFactory(gameConfig.systemConfig); + } else { + // Determine the contract name and blueprints for the game type. + string memory gameContractName; + address blueprint1; + address blueprint2; + uint256 gameL2ChainId; + + // Separate context to avoid stack too deep. + { + // Grab the blueprints once since we'll need it multiple times below. + OPContractsManager.Blueprints memory bps = getBlueprints(); + + // Determine the contract name and blueprints for the game type. + if ( + gameConfig.disputeGameType.raw() == GameTypes.CANNON.raw() + || ( + isDevFeatureEnabled(DevFeatures.CANNON_KONA) + && gameConfig.disputeGameType.raw() == GameTypes.CANNON_KONA.raw() + ) + ) { + gameContractName = "FaultDisputeGame"; + blueprint1 = bps.permissionlessDisputeGame1; + blueprint2 = bps.permissionlessDisputeGame2; + gameL2ChainId = l2ChainId; + } else if (gameConfig.disputeGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + gameContractName = "PermissionedDisputeGame"; + blueprint1 = bps.permissionedDisputeGame1; + blueprint2 = bps.permissionedDisputeGame2; + gameL2ChainId = l2ChainId; + } else if ( + gameConfig.disputeGameType.raw() == GameTypes.SUPER_CANNON.raw() + || ( + isDevFeatureEnabled(DevFeatures.CANNON_KONA) + && gameConfig.disputeGameType.raw() == GameTypes.SUPER_CANNON_KONA.raw() + ) + ) { + gameContractName = "SuperFaultDisputeGame"; + blueprint1 = bps.superPermissionlessDisputeGame1; + blueprint2 = bps.superPermissionlessDisputeGame2; + gameL2ChainId = 0; + } else if (gameConfig.disputeGameType.raw() == GameTypes.SUPER_PERMISSIONED_CANNON.raw()) { + gameContractName = "SuperPermissionedDisputeGame"; + blueprint1 = bps.superPermissionedDisputeGame1; + blueprint2 = bps.superPermissionedDisputeGame2; + gameL2ChainId = 0; + } else { + revert OPContractsManagerGameTypeAdder_UnsupportedGameType(); + } + } - // Grab the existing game implementation from the DisputeGameFactory. - IFaultDisputeGame existingGame = - IFaultDisputeGame(address(getGameImplementation(dgf, gameConfig.disputeGameType))); + // Encode the constructor data for the game type. + bytes memory constructorData; + if (gameConfig.permissioned) { + constructorData = encodePermissionedFDGConstructor( + IFaultDisputeGame.GameConstructorParams( + gameConfig.disputeGameType, + gameConfig.disputeAbsolutePrestate, + gameConfig.disputeMaxGameDepth, + gameConfig.disputeSplitDepth, + gameConfig.disputeClockExtension, + gameConfig.disputeMaxClockDuration, + gameConfig.vm, + outputs[i].delayedWETH, + getAnchorStateRegistry(gameConfig.systemConfig), + gameL2ChainId + ), + getProposerV1(IPermissionedDisputeGame(address(existingGame))), + getChallengerV1(IPermissionedDisputeGame(address(existingGame))) + ); + } else { + constructorData = encodePermissionlessFDGConstructor( + IFaultDisputeGame.GameConstructorParams( + gameConfig.disputeGameType, + gameConfig.disputeAbsolutePrestate, + gameConfig.disputeMaxGameDepth, + gameConfig.disputeSplitDepth, + gameConfig.disputeClockExtension, + gameConfig.disputeMaxClockDuration, + gameConfig.vm, + outputs[i].delayedWETH, + getAnchorStateRegistry(gameConfig.systemConfig), + gameL2ChainId + ) + ); + } - // Encode the constructor data for the game type. - bytes memory constructorData; - if (gameConfig.permissioned) { - constructorData = encodePermissionedFDGConstructor( - IFaultDisputeGame.GameConstructorParams( - gameConfig.disputeGameType, - gameConfig.disputeAbsolutePrestate, - gameConfig.disputeMaxGameDepth, - gameConfig.disputeSplitDepth, - gameConfig.disputeClockExtension, - gameConfig.disputeMaxClockDuration, - gameConfig.vm, - outputs[i].delayedWETH, - getAnchorStateRegistry(gameConfig.systemConfig), - gameL2ChainId - ), - getProposerV1(IPermissionedDisputeGame(address(existingGame))), - getChallengerV1(IPermissionedDisputeGame(address(existingGame))) - ); - } else { - constructorData = encodePermissionlessFDGConstructor( - IFaultDisputeGame.GameConstructorParams( - gameConfig.disputeGameType, - gameConfig.disputeAbsolutePrestate, - gameConfig.disputeMaxGameDepth, - gameConfig.disputeSplitDepth, - gameConfig.disputeClockExtension, - gameConfig.disputeMaxClockDuration, - gameConfig.vm, - outputs[i].delayedWETH, - getAnchorStateRegistry(gameConfig.systemConfig), - gameL2ChainId + // Deploy the new game type. + 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. + setDGFImplementation( + dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame)) + ); + } - // 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))); dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond); // Emit event for the newly added game type with the new and old implementations. @@ -2089,9 +2138,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 4.4.0 + /// @custom:semver 4.5.0 function version() public pure virtual returns (string memory) { - return "4.4.0"; + return "4.5.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 5f5080991fc6d..0755fce84178d 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -14,10 +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 { DeployOPChain } from "scripts/deploy/DeployOPChain.s.sol"; -import { Config } from "scripts/libraries/Config.sol"; -import { Types } from "scripts/libraries/Types.sol"; // Libraries +import { Config } from "scripts/libraries/Config.sol"; +import { Types } from "scripts/libraries/Types.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; import { Proposal, GameTypes } from "src/dispute/lib/Types.sol"; @@ -47,6 +47,7 @@ import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; import { ISuperPermissionedDisputeGame } from "interfaces/dispute/ISuperPermissionedDisputeGame.sol"; +import { IFaultDisputeGame } from "../../interfaces/dispute/IFaultDisputeGame.sol"; // Contracts import { @@ -58,6 +59,10 @@ import { OPContractsManagerInteropMigrator, OPContractsManagerStandardValidator } from "src/L1/OPContractsManager.sol"; +import { DisputeGames } from "../setup/DisputeGames.sol"; +import { IPermissionedDisputeGame } from "../../interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IProxy } from "../../interfaces/universal/IProxy.sol"; +import { IDelayedWETH } from "../../interfaces/dispute/IDelayedWETH.sol"; /// @title OPContractsManager_Harness /// @notice Exposes internal functions for testing. @@ -414,19 +419,29 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { /// @title OPContractsManager_TestInit /// @notice Reusable test initialization for `OPContractsManager` tests. -abstract contract OPContractsManager_TestInit is CommonTest { +abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { event GameTypeAdded( uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame ); + address proposer; + address challenger; + + uint256 chain1L2ChainId; + uint256 chain2L2ChainId; + IOPContractsManager.DeployOutput internal chainDeployOutput1; IOPContractsManager.DeployOutput internal chainDeployOutput2; function setUp() public virtual override { super.setUp(); + proposer = address(this); + challenger = address(this); + chain1L2ChainId = 100; + chain2L2ChainId = 101; - chainDeployOutput1 = createChainContracts(100); - chainDeployOutput2 = createChainContracts(101); + chainDeployOutput1 = createChainContracts(chain1L2ChainId); + chainDeployOutput2 = createChainContracts(chain2L2ChainId); vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); @@ -449,8 +464,8 @@ abstract contract OPContractsManager_TestInit is CommonTest { systemConfigOwner: address(this), batcher: address(this), unsafeBlockSigner: address(this), - proposer: address(this), - challenger: address(this) + proposer: proposer, + challenger: challenger }), basefeeScalar: 1, blobBasefeeScalar: 1, @@ -482,9 +497,7 @@ abstract contract OPContractsManager_TestInit is CommonTest { IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); inputs[0] = input; - uint256 l2ChainId = IFaultDisputeGame( - address(IDisputeGameFactory(input.systemConfig.disputeGameFactory()).gameImpls(GameType.wrap(1))) - ).l2ChainId(); + uint256 l2ChainId = input.systemConfig.l2ChainId(); // Expect the GameTypeAdded event to be emitted. vm.expectEmit(true, true, true, false, address(this)); @@ -578,14 +591,6 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlag /// @title OPContractsManager_AddGameType_Test /// @notice Tests the `addGameType` function of the `OPContractsManager` contract. contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { - function setUp() public virtual override { - 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); - } - /// @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. @@ -593,18 +598,40 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); - assertValidGameType(input, output); + IFaultDisputeGame newFDG = assertValidGameType(input, output); // Check the values on the new game type. - IPermissionedDisputeGame newPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); - IPermissionedDisputeGame oldPDG = chainDeployOutput1.permissionedDisputeGame; + IPermissionedDisputeGame newPDG = IPermissionedDisputeGame(address(newFDG)); // Check the proposer and challenger values. - assertEq(newPDG.proposer(), oldPDG.proposer(), "proposer mismatch"); - assertEq(newPDG.challenger(), oldPDG.challenger(), "challenger mismatch"); + assertEq(newPDG.proposer(), proposer, "proposer mismatch"); + assertEq(newPDG.challenger(), challenger, "challenger mismatch"); // L2 chain ID call should not revert because this is not a Super game. - assertNotEq(newPDG.l2ChainId(), 0, "l2ChainId should not be zero"); + assertEq(newPDG.l2ChainId(), chain1L2ChainId, "l2ChainId should be set correctly"); + + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + // Get the v2 implementation address from OPCM + IOPContractsManager.Implementations memory impls = opcm.implementations(); + + // Verify v2 implementation is registered in DisputeGameFactory + address registeredImpl = + address(chainDeployOutput1.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(output.faultDisputeGame), + address(impls.permissionedDisputeGameV2Impl), + "addGameType should return v2 PermissionedDisputeGame implementation" + ); + } } /// @notice Tests that we can add a FaultDisputeGame implementation with addGameType. @@ -614,17 +641,40 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); - assertValidGameType(input, output); + IFaultDisputeGame newGame = assertValidGameType(input, output); // Check the values on the new game type. - IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); + IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(newGame)); // 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"); + assertEq(notPDG.l2ChainId(), chain1L2ChainId, "l2ChainId should be set correctly"); + + // Verify v2 implementation is registered in DisputeGameFactory + address registeredImpl = address(chainDeployOutput1.disputeGameFactoryProxy.gameImpls(input.disputeGameType)); + assertNotEq(registeredImpl, address(0), "Implementation should have been set"); + + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + // Get the v2 implementation address from OPCM + IOPContractsManager.Implementations memory impls = opcm.implementations(); + + // Verify implementation address matches permissionedDisputeGameV2Impl + 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(output.faultDisputeGame), + address(impls.faultDisputeGameV2Impl), + "addGameType should return v2 FaultDisputeGame implementation" + ); + } } /// @notice Tests that we can add a SuperPermissionedDisputeGame implementation with addGameType. @@ -650,17 +700,28 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { abi.encodeCall(IDisputeGame.gameType, ()), abi.encode(GameTypes.SUPER_PERMISSIONED_CANNON) ); + // Mock the proposer and challenger calls to behave like SuperPermissionedDisputeGame + // When V2 contracts are used the permissioned game may be the V2 contract and not have proposer and challenger + // in the implementation contract. + vm.mockCall( + address(chainDeployOutput1.permissionedDisputeGame), + abi.encodeCall(IPermissionedDisputeGame.proposer, ()), + abi.encode(proposer) + ); + vm.mockCall( + address(chainDeployOutput1.permissionedDisputeGame), + abi.encodeCall(IPermissionedDisputeGame.challenger, ()), + abi.encode(challenger) + ); // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); vm.clearMockedCalls(); - assertValidGameType(input, output); - + IFaultDisputeGame newGame = assertValidGameType(input, output); // Check the values on the new game type. - IPermissionedDisputeGame newPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); - IPermissionedDisputeGame oldPDG = chainDeployOutput1.permissionedDisputeGame; - assertEq(newPDG.proposer(), oldPDG.proposer(), "proposer mismatch"); - assertEq(newPDG.challenger(), oldPDG.challenger(), "challenger mismatch"); + IPermissionedDisputeGame newPDG = IPermissionedDisputeGame(address(newGame)); + assertEq(newPDG.proposer(), proposer, "proposer mismatch"); + assertEq(newPDG.challenger(), challenger, "challenger mismatch"); // Super games don't have the l2ChainId function. vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args @@ -725,16 +786,15 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { function test_addGameType_reusedDelayedWETH_succeeds() public { IDelayedWETH delayedWETH = IDelayedWETH( - payable( - address( - DeployUtils.create1({ - _name: "DelayedWETH", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IDelayedWETH.__constructor__, (1))) - }) - ) - ) + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(this)))) + }) + ); + IProxy(payable(address(delayedWETH))).upgradeToAndCall( + address(opcm.implementations().delayedWETHImpl), + abi.encodeCall(IDelayedWETH.initialize, (chainDeployOutput1.systemConfigProxy)) ); - vm.etch(address(delayedWETH), hex"01"); IOPContractsManager.AddGameInput memory input = newGameInputFactory(GameTypes.CANNON); input.delayedWETH = delayedWETH; IOPContractsManager.AddGameOutput memory output = addGameType(input); @@ -790,48 +850,50 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { IOPContractsManager.AddGameOutput memory ago ) internal - view + returns (IFaultDisputeGame) { - // Check the config for the game itself - assertEq(ago.faultDisputeGame.gameType().raw(), agi.disputeGameType.raw(), "gameType mismatch"); - assertEq( - ago.faultDisputeGame.absolutePrestate().raw(), - agi.disputeAbsolutePrestate.raw(), - "absolutePrestate mismatch" - ); - assertEq(ago.faultDisputeGame.maxGameDepth(), agi.disputeMaxGameDepth, "maxGameDepth mismatch"); - assertEq(ago.faultDisputeGame.splitDepth(), agi.disputeSplitDepth, "splitDepth mismatch"); - assertEq( - ago.faultDisputeGame.clockExtension().raw(), agi.disputeClockExtension.raw(), "clockExtension mismatch" + // Create a game so we can assert on game args which aren't baked into the implementation contract + Claim claim = Claim.wrap(bytes32(uint256(9876))); + uint256 l2SequenceNumber = uint256(123); + IFaultDisputeGame game = IFaultDisputeGame( + payable( + createGame( + chainDeployOutput1.disputeGameFactoryProxy, agi.disputeGameType, proposer, claim, l2SequenceNumber + ) + ) ); + + // Verify immutable fields on the game proxy + assertEq(game.gameType().raw(), agi.disputeGameType.raw(), "Game type should match"); + assertEq(game.clockExtension().raw(), agi.disputeClockExtension.raw(), "Clock extension should match"); + assertEq(game.maxClockDuration().raw(), agi.disputeMaxClockDuration.raw(), "Max clock duration should match"); + assertEq(game.splitDepth(), agi.disputeSplitDepth, "Split depth should match"); + assertEq(game.maxGameDepth(), agi.disputeMaxGameDepth, "Max game depth should match"); + assertEq(game.gameCreator(), proposer, "Game creator should match"); + assertEq(game.rootClaim().raw(), claim.raw(), "Claim should match"); + assertEq(game.l1Head().raw(), blockhash(block.number - 1), "L1 head should match"); + assertEq(game.l2SequenceNumber(), l2SequenceNumber, "L2 sequence number should match"); assertEq( - ago.faultDisputeGame.maxClockDuration().raw(), - agi.disputeMaxClockDuration.raw(), - "maxClockDuration mismatch" + game.absolutePrestate().raw(), agi.disputeAbsolutePrestate.raw(), "Absolute prestate should match input" ); - assertEq(address(ago.faultDisputeGame.vm()), address(agi.vm), "vm address mismatch"); - assertEq(address(ago.faultDisputeGame.weth()), address(ago.delayedWETH), "delayedWETH address mismatch"); + assertEq(address(game.vm()), address(agi.vm), "VM should match MIPS implementation"); assertEq( - address(ago.faultDisputeGame.anchorStateRegistry()), + address(game.anchorStateRegistry()), address(chainDeployOutput1.anchorStateRegistryProxy), - "ASR address mismatch" + "ASR should match" ); + assertEq(address(game.weth()), address(ago.delayedWETH), "WETH should match"); // Check the DGF - assertEq( - chainDeployOutput1.disputeGameFactoryProxy.gameImpls(agi.disputeGameType).gameType().raw(), - agi.disputeGameType.raw(), - "gameType mismatch" - ); assertEq( address(chainDeployOutput1.disputeGameFactoryProxy.gameImpls(agi.disputeGameType)), address(ago.faultDisputeGame), "gameImpl address mismatch" ); - assertEq(address(ago.faultDisputeGame.weth()), address(ago.delayedWETH), "weth address mismatch"); assertEq( chainDeployOutput1.disputeGameFactoryProxy.initBonds(agi.disputeGameType), agi.initialBond, "bond mismatch" ); + return game; } /// @notice Tests that addGameType will revert if the game type is cannon-kona and the dev feature is not enabled @@ -842,10 +904,10 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { // Run the addGameType call. IOPContractsManager.AddGameOutput memory output = addGameType(input); - assertValidGameType(input, output); + IFaultDisputeGame game = assertValidGameType(input, output); // Check the values on the new game type. - IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(output.faultDisputeGame)); + IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(game)); // Proposer call should revert because this is a permissionless game. vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args @@ -1589,7 +1651,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); } @@ -1992,11 +2054,9 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { /// 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_Deploy_Test is DeployOPChain_TestBase, DisputeGames { using stdStorage for StdStorage; - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - // This helper function is used to convert the input struct type defined in DeployOPChain.s.sol // to the input struct type defined in OPContractsManager.sol. function toOPCMDeployInput(Types.DeployOPChainInput memory _doi) @@ -2028,35 +2088,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { }); } - /// @notice Helper function to create a permissioned game through the factory - function _createPermissionedGame( - IDisputeGameFactory factory, - address proposer, - Claim claim, - uint256 l2BlockNumber - ) - internal - returns (IPermissionedDisputeGame) - { - // 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, abi.encode(bytes32(l2BlockNumber))); - - vm.stopPrank(); - - return IPermissionedDisputeGame(address(gameProxy)); - } - function test_deploy_l2ChainIdEqualsZero_reverts() public { IOPContractsManager.DeployInput memory input = toOPCMDeployInput(deployOPChainInput); input.l2ChainId = 0; @@ -2108,8 +2139,17 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { // Create a game proxy to test immutable fields Claim claim = Claim.wrap(bytes32(uint256(9876))); uint256 l2BlockNumber = uint256(123); - IPermissionedDisputeGame pdg = - _createPermissionedGame(opcmOutput.disputeGameFactoryProxy, opcmInput.roles.proposer, claim, l2BlockNumber); + IPermissionedDisputeGame pdg = IPermissionedDisputeGame( + payable( + createGame( + opcmOutput.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + opcmInput.roles.proposer, + claim, + l2BlockNumber + ) + ) + ); // Verify immutable fields on the game proxy // Constructor args diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 4c6c7667066bc..d8e48aadeaad1 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -59,6 +59,8 @@ contract DeployOPChain_TestBase is Test, FeatureFlags { Duration disputeMaxClockDuration = Duration.wrap(3.5 days); IOPContractsManager opcm; + event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); + function setUp() public virtual { resolveFeaturesFromEnv(); deploySuperchain = new DeploySuperchain(); diff --git a/packages/contracts-bedrock/test/setup/DisputeGames.sol b/packages/contracts-bedrock/test/setup/DisputeGames.sol new file mode 100644 index 0000000000000..778a5c4559155 --- /dev/null +++ b/packages/contracts-bedrock/test/setup/DisputeGames.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { Vm } from "forge-std/Vm.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +// Libraries +import { GameType, Claim } from "src/dispute/lib/LibUDT.sol"; + +// Interfaces +import "../../interfaces/dispute/IDisputeGame.sol"; +import "../../interfaces/dispute/IDisputeGameFactory.sol"; + +contract DisputeGames { + /// @notice The address of the foundry Vm contract. + Vm private constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + /// @notice Helper function to create a permissioned game through the factory + function createGame( + IDisputeGameFactory _factory, + GameType _gameType, + address _proposer, + Claim _claim, + uint256 _l2BlockNumber + ) + internal + returns (address) + { + // Check if there's an init bond required for the game type + uint256 initBond = _factory.initBonds(_gameType); + console.log("Init bond", initBond); + + // 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 }(_gameType, _claim, abi.encode(bytes32(_l2BlockNumber))); + + vm.stopPrank(); + + return address(gameProxy); + } +}