diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol index 56aa6dace01..6585609f638 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerStandardValidator.sol @@ -27,6 +27,7 @@ interface IOPContractsManagerStandardValidator { ISystemConfig sysCfg; bytes32 absolutePrestate; uint256 l2ChainID; + address proposer; } struct ValidationOverrides { diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index fd91f93a2ea..93edb1d7ac0 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -22,6 +22,7 @@ import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; // Libraries import { Types } from "scripts/libraries/Types.sol"; import { Duration } from "src/dispute/lib/LibUDT.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { GameType, Claim, GameTypes, Proposal, Hash } from "src/dispute/lib/Types.sol"; // Interfaces @@ -292,6 +293,9 @@ contract Deploy is Deployer { artifacts.save("OPContractsManager", address(dio.opcm)); artifacts.save("DelayedWETHImpl", address(dio.delayedWETHImpl)); artifacts.save("PreimageOracle", address(dio.preimageOracleSingleton)); + if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + artifacts.save("PermissionedDisputeGame", address(dio.permissionedDisputeGameV2Impl)); + } // Get a contract set from the implementation addresses which were just deployed. Types.ContractSet memory impls = ChainAssertions.dioToContractSet(dio); @@ -357,9 +361,11 @@ contract Deploy is Deployer { artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); - artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy)); + if (!DevFeatures.isDevFeatureEnabled(opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); + } // Check if the permissionless game implementation is already set IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 26c600b1d62..d7112b92019 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -847,6 +847,11 @@ "internalType": "uint256", "name": "l2ChainID", "type": "uint256" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" } ], "internalType": "struct OPContractsManagerStandardValidator.ValidationInput", @@ -893,6 +898,11 @@ "internalType": "uint256", "name": "l2ChainID", "type": "uint256" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" } ], "internalType": "struct OPContractsManagerStandardValidator.ValidationInput", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json index b09166a2529..3c21aacf89f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerStandardValidator.json @@ -354,6 +354,11 @@ "internalType": "uint256", "name": "l2ChainID", "type": "uint256" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" } ], "internalType": "struct OPContractsManagerStandardValidator.ValidationInput", @@ -400,6 +405,11 @@ "internalType": "uint256", "name": "l2ChainID", "type": "uint256" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" } ], "internalType": "struct OPContractsManagerStandardValidator.ValidationInput", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index d163f859fd4..d673dd3590d 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,12 +20,12 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x1a1de93a1e15a32fd621b572211bd1ceccf82d5d2b936e0aa337ea7593c1ce19", - "sourceCodeHash": "0xbe7ceb48b39fc1d6c311b9d6dd9095ee72fa5cf82f72d27ec4511c97c01b02aa" + "initCodeHash": "0x0fec1db69fa99c31c4b13140bceee87419b8d3342cc952063a7a268242f3a199", + "sourceCodeHash": "0x2b0abd0e179a5449be392b301677554fa1d4f93af1083baa23f43ccf3cac9d02" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { - "initCodeHash": "0x2eaa345ba05582c67b40a1eb7ec9d54823aa08468e697e2d6c04bb74cc574abc", - "sourceCodeHash": "0x30d7e4243a3bab7fea8504be2cd24f2c5ab3335b802d62282a374a458a08feec" + "initCodeHash": "0xe0e6d892d38211dc0165ca00dc1e8aa558eb7a1240560260e23262f066f6be72", + "sourceCodeHash": "0xe40f42a857c9b0905db4c91d61073d9dd572a6bc49921c5ee6e875cf54dc9407" }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { "initCodeHash": "0x5bf576ea7f566e402a997204988471fc9b971410aa9dff8fe810b10baf6b7456", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index f40fd6078ce..fb252ebcfe5 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -2153,9 +2153,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 4.6.0 + /// @custom:semver 5.0.0 function version() public pure virtual returns (string memory) { - return "4.6.0"; + return "5.0.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol index 6acf9f6a1da..12ab3a53d37 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol @@ -40,8 +40,8 @@ import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; /// before and after an upgrade. contract OPContractsManagerStandardValidator is ISemver { /// @notice The semantic version of the OPContractsManagerStandardValidator contract. - /// @custom:semver 1.19.0 - string public constant version = "1.19.0"; + /// @custom:semver 2.0.0 + string public constant version = "2.0.0"; /// @notice The SuperchainConfig contract. ISuperchainConfig public superchainConfig; @@ -118,6 +118,7 @@ contract OPContractsManagerStandardValidator is ISemver { ISystemConfig sysCfg; bytes32 absolutePrestate; uint256 l2ChainID; + address proposer; } /// @notice Struct containing override parameters for the validation process. @@ -143,6 +144,7 @@ contract OPContractsManagerStandardValidator is ISemver { IDelayedWETH weth; uint256 l2ChainId; address challenger; + address proposer; } /// @notice Constructor for the OPContractsManagerStandardValidator contract. @@ -210,7 +212,7 @@ contract OPContractsManagerStandardValidator is ISemver { /// @notice Returns the expected PermissionedDisputeGame version. function permissionedDisputeGameVersion() public view returns (string memory) { if (DevFeatures.isDevFeatureEnabled(devFeatureBitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { - return "2.1.0"; + return "2.2.0"; } else { return "1.8.0"; } @@ -503,48 +505,43 @@ contract OPContractsManagerStandardValidator is ISemver { bytes32 _absolutePrestate, uint256 _l2ChainID, IProxyAdmin _admin, + address _proposer, ValidationOverrides memory _overrides ) internal view returns (string memory) { - IDisputeGameFactory _factory = IDisputeGameFactory(_sysCfg.disputeGameFactory()); - IPermissionedDisputeGame _game = - IPermissionedDisputeGame(address(_factory.gameImpls(GameTypes.PERMISSIONED_CANNON))); - - if (address(_game) == address(0)) { - _errors = internalRequire(false, "PDDG-10", _errors); - // Return early to avoid reverting, since this means that there is no valid game impl - // for this game type. - return _errors; - } - - bytes memory _gameArgs = _factory.gameArgs(GameTypes.PERMISSIONED_CANNON); - bool lenCheckFailed; - (_errors, lenCheckFailed) = assertGameArgsLength(_errors, _gameArgs, true, "PDDG"); - if (lenCheckFailed) { - // bail out immediately to avoid trying to validate an invalid dispute game + GameType gameType = GameTypes.PERMISSIONED_CANNON; + string memory errorPrefix = "PDDG"; + + // Collect game implementation parameters + DisputeGameImplementation memory gameImpl; + bool failedToGetImpl = false; + (gameImpl, _errors, failedToGetImpl) = getGameImplementation(_errors, gameType, _sysCfg, errorPrefix); + if (failedToGetImpl) { + // Return early on failure to avoid trying to validate an invalid dispute game return _errors; } - DisputeGameImplementation memory _gameImpl = _decodeDisputeGameImpl(_game, _gameArgs, true); _errors = assertValidDisputeGame( - _errors, - _sysCfg, - _gameImpl, - _factory, - _absolutePrestate, - _l2ChainID, - _admin, - GameTypes.PERMISSIONED_CANNON, - _overrides, - "PDDG" + DisputeGameValidationArgs({ + errors: _errors, + sysCfg: _sysCfg, + game: gameImpl, + absolutePrestate: _absolutePrestate, + l2ChainID: _l2ChainID, + admin: _admin, + gameType: gameType, + overrides: _overrides, + errorPrefix: errorPrefix + }) ); // Challenger is specific to the PermissionedDisputeGame contract. address _challenger = expectedChallenger(_overrides); - _errors = internalRequire(_gameImpl.challenger == _challenger, "PDDG-130", _errors); + _errors = internalRequire(gameImpl.challenger == _challenger, "PDDG-130", _errors); + _errors = internalRequire(gameImpl.proposer == _proposer, "PDDG-140", _errors); return _errors; } @@ -562,95 +559,132 @@ contract OPContractsManagerStandardValidator is ISemver { view returns (string memory) { + GameType gameType = GameTypes.CANNON; + string memory errorPrefix = "PLDG"; + + // Collect game implementation parameters + DisputeGameImplementation memory gameImpl; + bool failedToGetImpl = false; + (gameImpl, _errors, failedToGetImpl) = getGameImplementation(_errors, gameType, _sysCfg, errorPrefix); + if (failedToGetImpl) { + // Return early on failure to avoid trying to validate an invalid dispute game + return _errors; + } + + _errors = assertValidDisputeGame( + DisputeGameValidationArgs({ + errors: _errors, + sysCfg: _sysCfg, + game: gameImpl, + absolutePrestate: _absolutePrestate, + l2ChainID: _l2ChainID, + admin: _admin, + gameType: gameType, + overrides: _overrides, + errorPrefix: errorPrefix + }) + ); + + return _errors; + } + + function getGameImplementation( + string memory _initialErrors, + GameType _gameType, + ISystemConfig _sysCfg, + string memory _errorPrefix + ) + internal + view + returns (DisputeGameImplementation memory gameImpl_, string memory errors_, bool failed_) + { + errors_ = _initialErrors; + bool isPermissioned = _gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw(); IDisputeGameFactory _factory = IDisputeGameFactory(_sysCfg.disputeGameFactory()); - IPermissionedDisputeGame _game = IPermissionedDisputeGame(address(_factory.gameImpls(GameTypes.CANNON))); + IPermissionedDisputeGame _game = IPermissionedDisputeGame(address(_factory.gameImpls(_gameType))); if (address(_game) == address(0)) { - _errors = internalRequire(false, "PLDG-10", _errors); + errors_ = internalRequire(false, string.concat(_errorPrefix, "-10"), errors_); // Return early to avoid reverting, since this means that there is no valid game impl // for this game type. - return _errors; + failed_ = true; + return (gameImpl_, errors_, failed_); } - bytes memory _gameArgs = _factory.gameArgs(GameTypes.CANNON); + bytes memory _gameArgs = _factory.gameArgs(_gameType); bool lenCheckFailed; - (_errors, lenCheckFailed) = assertGameArgsLength(_errors, _gameArgs, false, "PLDG"); + (errors_, lenCheckFailed) = assertGameArgsLength(errors_, _gameArgs, isPermissioned, _errorPrefix); if (lenCheckFailed) { - // bail out immediately to avoid trying to validate an invalid dispute game - return _errors; + // Return early to avoid decoding invalid game args + failed_ = true; + return (gameImpl_, errors_, failed_); } + gameImpl_ = _decodeDisputeGameImpl(_game, _gameArgs, _gameType); - DisputeGameImplementation memory _gameImpl = _decodeDisputeGameImpl(_game, _gameArgs, false); - _errors = assertValidDisputeGame( - _errors, - _sysCfg, - _gameImpl, - _factory, - _absolutePrestate, - _l2ChainID, - _admin, - GameTypes.CANNON, - _overrides, - "PLDG" - ); + return (gameImpl_, errors_, failed_); + } - return _errors; + struct DisputeGameValidationArgs { + string errors; + ISystemConfig sysCfg; + DisputeGameImplementation game; + bytes32 absolutePrestate; + uint256 l2ChainID; + IProxyAdmin admin; + GameType gameType; + ValidationOverrides overrides; + string errorPrefix; } /// @notice Asserts that a DisputeGame contract is valid. - function assertValidDisputeGame( - string memory _errors, - ISystemConfig _sysCfg, - DisputeGameImplementation memory _game, - IDisputeGameFactory _factory, - bytes32 _absolutePrestate, - uint256 _l2ChainID, - IProxyAdmin _admin, - GameType _gameType, - ValidationOverrides memory _overrides, - string memory _errorPrefix - ) + function assertValidDisputeGame(DisputeGameValidationArgs memory _args) internal view - returns (string memory) + returns (string memory errors_) { - (Hash anchorRoot,) = _game.asr.getAnchorRoot(); - - _errors = internalRequire( - LibString.eq(getVersion(_game.gameAddress), permissionedDisputeGameVersion()), - string.concat(_errorPrefix, "-20"), - _errors + errors_ = _args.errors; + string memory errorPrefix = _args.errorPrefix; + DisputeGameImplementation memory game = _args.game; + (Hash anchorRoot,) = game.asr.getAnchorRoot(); + IDisputeGameFactory dgf = IDisputeGameFactory(_args.sysCfg.disputeGameFactory()); + + errors_ = internalRequire( + LibString.eq(getVersion(game.gameAddress), permissionedDisputeGameVersion()), + string.concat(errorPrefix, "-20"), + errors_ ); - _errors = internalRequire( - GameType.unwrap(_game.gameType) == GameType.unwrap(_gameType), string.concat(_errorPrefix, "-30"), _errors + errors_ = internalRequire( + GameType.unwrap(game.gameType) == GameType.unwrap(_args.gameType), + string.concat(errorPrefix, "-30"), + errors_ ); - _errors = internalRequire( - Claim.unwrap(_game.absolutePrestate) == _absolutePrestate, string.concat(_errorPrefix, "-40"), _errors + errors_ = internalRequire( + Claim.unwrap(game.absolutePrestate) == _args.absolutePrestate, string.concat(errorPrefix, "-40"), errors_ ); - _errors = internalRequire(_game.l2ChainId == _l2ChainID, string.concat(_errorPrefix, "-60"), _errors); - _errors = internalRequire(_game.l2SequenceNumber == 0, string.concat(_errorPrefix, "-70"), _errors); - _errors = - internalRequire(Duration.unwrap(_game.clockExtension) == 10800, string.concat(_errorPrefix, "-80"), _errors); - _errors = internalRequire(_game.splitDepth == 30, string.concat(_errorPrefix, "-90"), _errors); - _errors = internalRequire(_game.maxGameDepth == 73, string.concat(_errorPrefix, "-100"), _errors); - _errors = internalRequire( - Duration.unwrap(_game.maxClockDuration) == 302400, string.concat(_errorPrefix, "-110"), _errors + errors_ = internalRequire(game.l2ChainId == _args.l2ChainID, string.concat(errorPrefix, "-60"), errors_); + errors_ = internalRequire(game.l2SequenceNumber == 0, string.concat(errorPrefix, "-70"), errors_); + errors_ = + internalRequire(Duration.unwrap(game.clockExtension) == 10800, string.concat(errorPrefix, "-80"), errors_); + errors_ = internalRequire(game.splitDepth == 30, string.concat(errorPrefix, "-90"), errors_); + errors_ = internalRequire(game.maxGameDepth == 73, string.concat(errorPrefix, "-100"), errors_); + errors_ = internalRequire( + Duration.unwrap(game.maxClockDuration) == 302400, string.concat(errorPrefix, "-110"), errors_ ); - _errors = internalRequire(Hash.unwrap(anchorRoot) != bytes32(0), string.concat(_errorPrefix, "-120"), _errors); + errors_ = internalRequire(Hash.unwrap(anchorRoot) != bytes32(0), string.concat(errorPrefix, "-120"), errors_); - _errors = assertValidDelayedWETH(_errors, _sysCfg, _game.weth, _admin, _overrides, _errorPrefix); - _errors = assertValidAnchorStateRegistry(_errors, _sysCfg, _factory, _game.asr, _admin, _errorPrefix); + errors_ = assertValidDelayedWETH(errors_, _args.sysCfg, game.weth, _args.admin, _args.overrides, errorPrefix); + errors_ = assertValidAnchorStateRegistry(errors_, _args.sysCfg, dgf, game.asr, _args.admin, errorPrefix); - _errors = assertValidMipsVm(_errors, IMIPS64(address(_game.vm)), _errorPrefix); + errors_ = assertValidMipsVm(errors_, IMIPS64(address(game.vm)), errorPrefix); // Only assert valid preimage oracle if the game VM is valid, since otherwise // the contract is likely to revert. - if (address(_game.vm) == mipsImpl) { - _errors = assertValidPreimageOracle(_errors, _game.vm.oracle(), _errorPrefix); + if (address(game.vm) == mipsImpl) { + errors_ = assertValidPreimageOracle(errors_, game.vm.oracle(), errorPrefix); } - return _errors; + return errors_; } /// @notice Asserts that the DelayedWETH contract is valid. @@ -812,7 +846,13 @@ contract OPContractsManagerStandardValidator is ISemver { _errors = assertValidOptimismPortal(_errors, _input.sysCfg, _input.proxyAdmin); _errors = assertValidDisputeGameFactory(_errors, _input.sysCfg, _input.proxyAdmin, _overrides); _errors = assertValidPermissionedDisputeGame( - _errors, _input.sysCfg, _input.absolutePrestate, _input.l2ChainID, _input.proxyAdmin, _overrides + _errors, + _input.sysCfg, + _input.absolutePrestate, + _input.l2ChainID, + _input.proxyAdmin, + _input.proposer, + _overrides ); _errors = assertValidPermissionlessDisputeGame( _errors, _input.sysCfg, _input.absolutePrestate, _input.l2ChainID, _input.proxyAdmin, _overrides @@ -874,23 +914,27 @@ contract OPContractsManagerStandardValidator is ISemver { function _decodeDisputeGameImpl( IPermissionedDisputeGame _game, bytes memory _gameArgsBytes, - bool _isPermissioned + GameType _gameType ) internal view returns (DisputeGameImplementation memory gameImpl_) { + GameType gameType; LibGameArgs.GameArgs memory gameArgs; if (DevFeatures.isDevFeatureEnabled(devFeatureBitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + gameType = _gameType; gameArgs = LibGameArgs.decode(_gameArgsBytes); } else { + gameType = _game.gameType(); gameArgs.absolutePrestate = Claim.unwrap(_game.absolutePrestate()); gameArgs.vm = address(_game.vm()); gameArgs.anchorStateRegistry = address(_game.anchorStateRegistry()); gameArgs.weth = address(_game.weth()); gameArgs.l2ChainId = _game.l2ChainId(); - if (_isPermissioned) { + if (_gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { gameArgs.challenger = _game.challenger(); + gameArgs.proposer = _game.proposer(); } } @@ -900,14 +944,15 @@ contract OPContractsManagerStandardValidator is ISemver { splitDepth: _game.splitDepth(), maxClockDuration: _game.maxClockDuration(), clockExtension: _game.clockExtension(), - gameType: _game.gameType(), + gameType: gameType, l2SequenceNumber: _game.l2SequenceNumber(), absolutePrestate: Claim.wrap(gameArgs.absolutePrestate), vm: IBigStepper(gameArgs.vm), asr: IAnchorStateRegistry(gameArgs.anchorStateRegistry), weth: IDelayedWETH(payable(gameArgs.weth)), l2ChainId: gameArgs.l2ChainId, - challenger: gameArgs.challenger + challenger: gameArgs.challenger, + proposer: gameArgs.proposer }); } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 3001e600252..4d2f6d71503 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -94,7 +94,7 @@ contract OPContractsManager_Harness is OPContractsManager { /// @title OPContractsManager_Upgrade_Harness /// @notice Exposes internal functions for testing. -contract OPContractsManager_Upgrade_Harness is CommonTest { +contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { // The Upgraded event emitted by the Proxy contract. event Upgraded(address indexed implementation); @@ -202,6 +202,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { ) internal { + // Grab some values before we upgrade, to be checked later + address initialChallenger = permissionedGameChallenger(disputeGameFactory); + address initialProposer = permissionedGameProposer(disputeGameFactory); + // Always start by upgrading the SuperchainConfig contract. // Temporarily replace the superchainPAO with a DelegateCaller. address superchainPAO = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))).owner(); @@ -270,20 +274,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } // Create validationOverrides - address challengerOverride; - if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { - LibGameArgs.GameArgs memory gameArgs = - LibGameArgs.decode(disputeGameFactory.gameArgs(GameTypes.PERMISSIONED_CANNON)); - challengerOverride = gameArgs.challenger; - } else { - challengerOverride = IPermissionedDisputeGame( - address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) - ).challenger(); - } IOPContractsManagerStandardValidator.ValidationOverrides memory validationOverrides = IOPContractsManagerStandardValidator.ValidationOverrides({ l1PAOMultisig: opChainConfigs[0].proxyAdmin.owner(), - challenger: challengerOverride + challenger: initialChallenger }); // Grab the validator before we do the error assertion because otherwise the assertion will @@ -308,35 +302,25 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { proxyAdmin: opChainConfigs[0].proxyAdmin, sysCfg: opChainConfigs[0].systemConfigProxy, absolutePrestate: opChainConfigs[0].absolutePrestate.raw(), - l2ChainID: l2ChainId + l2ChainID: l2ChainId, + proposer: initialProposer }), false, validationOverrides ); - _runPostUpgradeSmokeTests(_opcm, opChainConfigs[0], challengerOverride); - } - - function permissionedGameProposer() internal view returns (address) { - if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { - LibGameArgs.GameArgs memory gameArgs = - LibGameArgs.decode(disputeGameFactory.gameArgs(GameTypes.PERMISSIONED_CANNON)); - return gameArgs.proposer; - } else { - return IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))) - .proposer(); - } + _runPostUpgradeSmokeTests(_opcm, opChainConfigs[0], initialChallenger, initialProposer); } /// @notice Runs some smoke tests after an upgrade function _runPostUpgradeSmokeTests( IOPContractsManager _opcm, IOPContractsManager.OpChainConfig memory _opChainConfig, - address _challenger + address _challenger, + address _proposer ) internal { - address expectedProposer = permissionedGameProposer(); bytes32 expectedAbsolutePrestate = _opChainConfig.absolutePrestate.raw(); if (expectedAbsolutePrestate == bytes32(0)) { expectedAbsolutePrestate = preUpgradeState.permissionedAbsolutePrestate.raw(); @@ -355,7 +339,7 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { gameTypes[1] = GameTypes.CANNON; for (uint256 i = 0; i < gameTypes.length; i++) { GameType gt = gameTypes[i]; - vm.prank(expectedProposer, expectedProposer); + vm.prank(_proposer, _proposer); IPermissionedDisputeGame game = IPermissionedDisputeGame( address(disputeGameFactory.create{ value: bondAmount }(gt, claim, abi.encode(l2BlockNumber))) ); @@ -371,14 +355,14 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.assertEq(30, game.splitDepth()); vm.assertEq(l2BlockNumber, game.l2BlockNumber()); vm.assertEq(expectedVm, address(game.vm())); - vm.assertEq(expectedProposer, game.gameCreator()); + vm.assertEq(_proposer, game.gameCreator()); vm.assertEq(claim.raw(), rootClaim.raw()); vm.assertEq(blockhash(block.number - 1), game.l1Head().raw()); if (gt.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { vm.assertEq(address(preUpgradeState.permissionedCannonWethProxy), address(game.weth())); vm.assertEq(_challenger, game.challenger()); - vm.assertEq(expectedProposer, game.proposer()); + vm.assertEq(_proposer, game.proposer()); } else { vm.assertEq(address(preUpgradeState.permissionlessWethProxy), address(game.weth())); } @@ -958,7 +942,7 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { } /// @notice Returns the game args of a v1 or v2 game. - function _getGameArgs( + function _getParsedGameArgs( IDisputeGameFactory _dgf, GameType _gameType ) @@ -1026,14 +1010,14 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { // Retrieve current game args before updatePrestate IDisputeGameFactory dgf = IDisputeGameFactory(chainDeployOutput1.systemConfigProxy.disputeGameFactory()); - LibGameArgs.GameArgs memory pdgArgsBefore = _getGameArgs(dgf, GameTypes.PERMISSIONED_CANNON); + LibGameArgs.GameArgs memory pdgArgsBefore = _getParsedGameArgs(dgf, GameTypes.PERMISSIONED_CANNON); LibGameArgs.GameArgs memory cannonArgsBefore; LibGameArgs.GameArgs memory cannonKonaArgsBefore; if (expectCannonUpdated) { - cannonArgsBefore = _getGameArgs(dgf, GameTypes.CANNON); + cannonArgsBefore = _getParsedGameArgs(dgf, GameTypes.CANNON); } if (expectCannonKonaUpdated) { - cannonKonaArgsBefore = _getGameArgs(dgf, GameTypes.CANNON_KONA); + cannonKonaArgsBefore = _getParsedGameArgs(dgf, GameTypes.CANNON_KONA); } // Turn the ProxyAdmin owner into a DelegateCaller. @@ -1057,14 +1041,14 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { return; } - LibGameArgs.GameArgs memory pdgArgsAfter = _getGameArgs(dgf, GameTypes.PERMISSIONED_CANNON); + LibGameArgs.GameArgs memory pdgArgsAfter = _getParsedGameArgs(dgf, GameTypes.PERMISSIONED_CANNON); _assertGameArgsEqual(pdgArgsBefore, pdgArgsAfter, true); assertEq(pdgArgsAfter.absolutePrestate, _input.cannonPrestate.raw(), "permissioned game prestate mismatch"); // Ensure that the WETH contracts are not reverting IDelayedWETH(payable(pdgArgsAfter.weth)).balanceOf(address(0)); if (expectCannonUpdated) { - LibGameArgs.GameArgs memory cannonArgsAfter = _getGameArgs(dgf, GameTypes.CANNON); + LibGameArgs.GameArgs memory cannonArgsAfter = _getParsedGameArgs(dgf, GameTypes.CANNON); _assertGameArgsEqual(cannonArgsBefore, cannonArgsAfter, true); assertEq(cannonArgsAfter.absolutePrestate, _input.cannonPrestate.raw(), "cannon game prestate mismatch"); // Ensure that the WETH contracts are not reverting @@ -1074,7 +1058,7 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { } if (expectCannonKonaUpdated) { - LibGameArgs.GameArgs memory cannonKonaArgsAfter = _getGameArgs(dgf, GameTypes.CANNON_KONA); + LibGameArgs.GameArgs memory cannonKonaArgsAfter = _getParsedGameArgs(dgf, GameTypes.CANNON_KONA); _assertGameArgsEqual(cannonKonaArgsBefore, cannonKonaArgsAfter, true); assertEq( cannonKonaArgsAfter.absolutePrestate, diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index f2b1908b485..a4ba64b52ce 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -4,10 +4,11 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; +import { DisputeGames } from "../setup/DisputeGames.sol"; // Libraries +import { GameType, Hash } from "src/dispute/lib/LibUDT.sol"; 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"; @@ -20,6 +21,7 @@ import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisput import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -37,6 +39,11 @@ import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; +import { IBigStepper } from "../../interfaces/dispute/IBigStepper.sol"; +import { IDisputeGameFactory } from "../../interfaces/dispute/IDisputeGameFactory.sol"; +import { DisputeGames } from "../setup/DisputeGames.sol"; +import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; +import { IDelayedWETH } from "../../interfaces/dispute/IDelayedWETH.sol"; /// @title BadDisputeGameFactoryReturner /// @notice Used to return a bad DisputeGameFactory address to the OPContractsManagerStandardValidator. Far easier @@ -77,7 +84,7 @@ contract BadDisputeGameFactoryReturner { /// @title OPContractsManagerStandardValidator_TestInit /// @notice Base contract for `OPContractsManagerStandardValidator` tests, handles common setup. -abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { +abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, DisputeGames { /// @notice Deploy input that was used to deploy the contracts being tested. IOPContractsManager.DeployInput deployInput; @@ -87,6 +94,12 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { /// @notice The absolute prestate, either from config or dummy value if fork test. Claim absolutePrestate; + /// @notice The proposer role set on the PermissionedDisputeGame instance. + address proposer; + + /// @notice The DisputeGameFactory instance. + IDisputeGameFactory dgf; + /// @notice The PermissionedDisputeGame instance. IPermissionedDisputeGame pdg; @@ -103,13 +116,12 @@ abstract 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(); + // Load the dgf + dgf = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + // Load the PermissionedDisputeGame once, we'll need it later. pdg = IPermissionedDisputeGame(artifacts.mustGetAddress("PermissionedDisputeGame")); @@ -123,6 +135,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { if (isForkTest()) { l2ChainId = uint256(uint160(address(artifacts.mustGetAddress("L2ChainId")))); absolutePrestate = Claim.wrap(bytes32(keccak256("absolutePrestate"))); + proposer = address(123); vm.mockCall( address(proxyAdmin), @@ -134,6 +147,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { abi.encodeCall(IPermissionedDisputeGame.challenger, ()), abi.encode(opcm.opcmStandardValidator().challenger()) ); + vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.proposer, ()), abi.encode(proposer)); vm.mockCall( address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), @@ -154,6 +168,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { } else { l2ChainId = deployInput.l2ChainId; absolutePrestate = deployInput.disputeAbsolutePrestate; + proposer = deployInput.roles.proposer; } // Deploy the BadDisputeGameFactoryReturner once. @@ -165,31 +180,9 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { // Load the FaultDisputeGame once, we'll need it later. fdg = IFaultDisputeGame(artifacts.mustGetAddress("FaultDisputeGame")); } else { - // Deploy the FaultDisputeGame. - fdg = IFaultDisputeGame( - DeployUtils.create1({ - _name: "FaultDisputeGame", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGame.__constructor__, - ( - IFaultDisputeGame.GameConstructorParams({ - gameType: GameTypes.CANNON, - absolutePrestate: absolutePrestate, - maxGameDepth: 73, - splitDepth: 30, - clockExtension: Duration.wrap(10800), - maxClockDuration: Duration.wrap(302400), - vm: mips, - weth: delayedWeth, - anchorStateRegistry: anchorStateRegistry, - l2ChainId: l2ChainId - }) - ) - ) - ) - }) - ); + // Deploy a permissionless FaultDisputeGame. + IOPContractsManager.AddGameOutput memory output = addGameType(GameTypes.CANNON); + fdg = output.faultDisputeGame; } // Add the FaultDisputeGame to the DisputeGameFactory. @@ -206,7 +199,8 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { proxyAdmin: proxyAdmin, sysCfg: systemConfig, absolutePrestate: absolutePrestate.raw(), - l2ChainID: l2ChainId + l2ChainID: l2ChainId, + proposer: proposer }), _allowFailure ); @@ -228,7 +222,8 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { proxyAdmin: proxyAdmin, sysCfg: systemConfig, absolutePrestate: absolutePrestate.raw(), - l2ChainID: l2ChainId + l2ChainID: l2ChainId, + proposer: proposer }), _allowFailure, _overrides @@ -245,6 +240,49 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest { challenger: address(0) }); } + + function addGameType(GameType _gameType) internal returns (IOPContractsManager.AddGameOutput memory) { + IOPContractsManager.AddGameInput memory input = newGameInputFactory(_gameType); + return addGameType(input); + } + + function addGameType(IOPContractsManager.AddGameInput memory input) + internal + returns (IOPContractsManager.AddGameOutput memory) + { + IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); + inputs[0] = input; + + address owner = deployInput.roles.opChainProxyAdminOwner; + vm.startPrank(address(owner), address(owner), true); + (bool success, bytes memory rawGameOut) = + address(opcm).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (inputs))); + assertTrue(success, "addGameType failed"); + vm.stopPrank(); + + IOPContractsManager.AddGameOutput[] memory addGameOutAll = + abi.decode(rawGameOut, (IOPContractsManager.AddGameOutput[])); + return addGameOutAll[0]; + } + + function newGameInputFactory(GameType _gameType) internal view returns (IOPContractsManager.AddGameInput memory) { + return IOPContractsManager.AddGameInput({ + saltMixer: "hello", + systemConfig: systemConfig, + proxyAdmin: proxyAdmin, + delayedWETH: delayedWeth, + disputeGameType: _gameType, + disputeAbsolutePrestate: absolutePrestate, + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(10800), + 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() + }); + } } /// @title OPContractsManagerStandardValidator_CoreValidation_Test @@ -297,9 +335,7 @@ contract OPContractsManagerStandardValidator_GeneralOverride_Test is OPContracts abi.encodeCall(IDisputeGameFactory.owner, ()), abi.encode(overrides.l1PAOMultisig) ); - vm.mockCall( - address(pdg), abi.encodeCall(IPermissionedDisputeGame.challenger, ()), abi.encode(overrides.challenger) - ); + mockGameImplChallenger(dgf, GameTypes.PERMISSIONED_CANNON, overrides.challenger); assertEq("OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER", _validateWithOverrides(true, overrides)); } @@ -887,36 +923,97 @@ contract OPContractsManagerStandardValidator_PermissionedDisputeGame_Test is /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame game type is invalid. function test_validate_permissionedDisputeGameInvalidGameType_succeeds() public { + // For v2 game contracts, we don't store the game type anywhere other than the DGF gameImpls and gameArgs maps + // So, there's not really an obvious way to return an invalid gameType + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); vm.mockCall(address(pdg), abi.encodeCall(IDisputeGame.gameType, ()), abi.encode(GameTypes.CANNON)); assertEq("PDDG-30", _validate(true)); } + /// @notice Tests that the validate function successfully returns the right error when the + /// PermissionedDisputeGame game args are invalid. + function test_validate_permissionedDisputeGameInvalidGameArgs_succeeds() public { + bytes memory invalidGameArgs = hex"123456"; + GameType gameType = GameTypes.PERMISSIONED_CANNON; + vm.mockCall(address(dgf), abi.encodeCall(IDisputeGameFactory.gameArgs, (gameType)), abi.encode(invalidGameArgs)); + + assertEq("PDDG-GARGS-10", _validate(true)); + } + /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame absolute prestate is invalid. function test_validate_permissionedDisputeGameInvalidAbsolutePrestate_succeeds() public { - vm.mockCall( - address(pdg), - abi.encodeCall(IPermissionedDisputeGame.absolutePrestate, ()), - abi.encode(bytes32(uint256(0xbad))) - ); + bytes32 badPrestate = bytes32(uint256(0xbadbad)); + mockGameImplPrestate(dgf, GameTypes.PERMISSIONED_CANNON, badPrestate); assertEq("PDDG-40", _validate(true)); } /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame VM address is invalid. function test_validate_permissionedDisputeGameInvalidVM_succeeds() public { - vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.vm, ()), abi.encode(address(0xbad))); - vm.mockCall(address(0xbad), abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + address badVM = address(0xbad); + mockGameImplVM(dgf, GameTypes.PERMISSIONED_CANNON, badVM); + vm.mockCall(badVM, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); vm.mockCall( address(0xbad), abi.encodeCall(IMIPS64.stateVersion, ()), abi.encode(StandardConstants.MIPS_VERSION) ); assertEq("PDDG-VM-10,PDDG-VM-20", _validate(true)); } + /// @notice Tests that the validate function successfully returns the right error when the + /// PermissionedDisputeGame ASR address is invalid. + function test_validate_permissionedDisputeGameInvalidASR_succeeds() public { + address badASR = address(0xbad); + mockGameImplASR(dgf, GameTypes.PERMISSIONED_CANNON, badASR); + + // Mock invalid values + vm.mockCall(badASR, abi.encodeCall(IStaticERC1967Proxy.implementation, ()), abi.encode(address(0xdeadbeef))); + vm.mockCall(badASR, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + + // Mock valid return values + vm.mockCall( + badASR, + abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), + abi.encode(Hash.wrap(bytes32(uint256(0x123))), uint256(123)) + ); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.disputeGameFactory, ()), abi.encode(dgf)); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.systemConfig, ()), abi.encode(sysCfg)); + vm.mockCall(badASR, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.retirementTimestamp, ()), abi.encode(uint64(100))); + + assertEq("PDDG-ANCHORP-10,PDDG-ANCHORP-20", _validate(true)); + } + + /// @notice Tests that the validate function successfully returns the right error when the + /// PermissionedDisputeGame Weth address is invalid. + function test_validate_permissionedDisputeGameInvalidWeth_succeeds() public { + address badWeth = address(0xbad); + mockGameImplWeth(dgf, GameTypes.PERMISSIONED_CANNON, badWeth); + + // Mock invalid values + vm.mockCall(badWeth, abi.encodeCall(IStaticERC1967Proxy.implementation, ()), abi.encode(address(0xdeadbeef))); + vm.mockCall(badWeth, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + + // Mock valid return values + vm.mockCall( + badWeth, + abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), + abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + ); + vm.mockCall( + badWeth, + abi.encodeCall(IDelayedWETH.delay, ()), + abi.encode(opcm.opcmStandardValidator().withdrawalDelaySeconds()) + ); + vm.mockCall(badWeth, abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(sysCfg)); + vm.mockCall(badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); + + assertEq("PDDG-DWETH-10,PDDG-DWETH-20", _validate(true)); + } + /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame VM's state version is invalid. function test_validate_permissionedDisputeGameInvalidVMStateVersion_succeeds() public { - vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.vm, ()), abi.encode(address(mips))); vm.mockCall(address(mips), abi.encodeCall(IMIPS64.stateVersion, ()), abi.encode(6)); assertEq("PDDG-VM-30,PLDG-VM-30", _validate(true)); } @@ -924,7 +1021,8 @@ contract OPContractsManagerStandardValidator_PermissionedDisputeGame_Test is /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame L2 Chain ID is invalid. function test_validate_permissionedDisputeGameInvalidL2ChainId_succeeds() public { - vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.l2ChainId, ()), abi.encode(l2ChainId + 1)); + uint256 badChainId = l2ChainId + 1; + mockGameImplL2ChainId(dgf, GameTypes.PERMISSIONED_CANNON, badChainId); assertEq("PDDG-60", _validate(true)); } @@ -981,16 +1079,20 @@ contract OPContractsManagerStandardValidator_PermissionedDisputeGame_Test is /// @notice Tests that the validate function successfully returns the right error when the /// PermissionedDisputeGame challenger is invalid. function test_validate_permissionedDisputeGameInvalidChallenger_succeeds() public { - vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.challenger, ()), abi.encode(address(0xbad))); + address badChallenger = address(0xbad); + mockGameImplChallenger(dgf, GameTypes.PERMISSIONED_CANNON, badChallenger); assertEq("PDDG-130", _validate(true)); } /// @notice Tests that the validate function successfully returns the right overrides error when the /// PermissionedDisputeGame challenger is overridden but is correct. function test_validate_overridenPermissionedDisputeGameChallenger_succeeds() public { + address challengerOverride = address(0xbad); + + mockGameImplChallenger(dgf, GameTypes.PERMISSIONED_CANNON, challengerOverride); IOPContractsManagerStandardValidator.ValidationOverrides memory overrides = _defaultValidationOverrides(); - overrides.challenger = address(0xbad); - vm.mockCall(address(pdg), abi.encodeCall(IPermissionedDisputeGame.challenger, ()), abi.encode(address(0xbad))); + overrides.challenger = challengerOverride; + assertEq("OVERRIDES-CHALLENGER", _validateWithOverrides(true, overrides)); } @@ -1001,6 +1103,14 @@ contract OPContractsManagerStandardValidator_PermissionedDisputeGame_Test is overrides.challenger = address(0xbad); assertEq("OVERRIDES-CHALLENGER,PDDG-130", _validateWithOverrides(true, overrides)); } + + /// @notice Tests that the validate function successfully returns the right error when the + /// PermissionedDisputeGame proposer is invalid. + function test_validate_permissionedDisputeGameInvalidProposer_succeeds() public { + address badProposer = address(0xbad); + mockGameImplProposer(dgf, GameTypes.PERMISSIONED_CANNON, badProposer); + assertEq("PDDG-140", _validate(true)); + } } /// @title OPContractsManagerStandardValidator_AnchorStateRegistry_Test @@ -1205,36 +1315,97 @@ contract OPContractsManagerStandardValidator_FaultDisputeGame_Test is OPContract /// @notice Tests that the validate function successfully returns the right error when the /// FaultDisputeGame (permissionless) game type is invalid. function test_validate_faultDisputeGameInvalidGameType_succeeds() public { + // For v2 game contracts, we don't store the game type anywhere other than the DGF gameImpls and gameArgs maps + // So, there's not really an obvious way to return an invalid gameType + skipIfDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); vm.mockCall(address(fdg), abi.encodeCall(IDisputeGame.gameType, ()), abi.encode(GameTypes.PERMISSIONED_CANNON)); assertEq("PLDG-30", _validate(true)); } + /// @notice Tests that the validate function successfully returns the right error when the + /// FaultDisputeGame (permissionless) game args are invalid. + function test_validate_faultDisputeGameInvalidGameArgs_succeeds() public { + bytes memory invalidGameArgs = hex"123456"; + GameType gameType = GameTypes.CANNON; + vm.mockCall(address(dgf), abi.encodeCall(IDisputeGameFactory.gameArgs, (gameType)), abi.encode(invalidGameArgs)); + + assertEq("PLDG-GARGS-10", _validate(true)); + } + /// @notice Tests that the validate function successfully returns the right error when the /// FaultDisputeGame (permissionless) absolute prestate is invalid. function test_validate_faultDisputeGameInvalidAbsolutePrestate_succeeds() public { - vm.mockCall( - address(fdg), - abi.encodeCall(IFaultDisputeGame.absolutePrestate, ()), - abi.encode(bytes32(uint256(0xbadbad))) // Different from the expected absolutePrestate - ); + bytes32 badPrestate = bytes32(uint256(0xbadbad)); + mockGameImplPrestate(dgf, GameTypes.CANNON, badPrestate); + assertEq("PLDG-40", _validate(true)); } /// @notice Tests that the validate function successfully returns the right error when the /// FaultDisputeGame (permissionless) VM address is invalid. function test_validate_faultDisputeGameInvalidVM_succeeds() public { - vm.mockCall(address(fdg), abi.encodeCall(IFaultDisputeGame.vm, ()), abi.encode(address(0xbad))); - vm.mockCall(address(0xbad), abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + address badVM = address(0xbad); + mockGameImplVM(dgf, GameTypes.CANNON, badVM); + vm.mockCall(badVM, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + vm.mockCall(badVM, abi.encodeCall(IMIPS64.stateVersion, ()), abi.encode(StandardConstants.MIPS_VERSION)); + + assertEq("PLDG-VM-10,PLDG-VM-20", _validate(true)); + } + + /// @notice Tests that the validate function successfully returns the right error when the + /// FaultDisputeGame (permissionless) ASR address is invalid. + function test_validate_faultDisputeGameInvalidASR_succeeds() public { + address badASR = address(0xbad); + mockGameImplASR(dgf, GameTypes.CANNON, badASR); + + // Mock invalid values + vm.mockCall(badASR, abi.encodeCall(IStaticERC1967Proxy.implementation, ()), abi.encode(address(0xdeadbeef))); + vm.mockCall(badASR, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + + // Mock valid return values vm.mockCall( - address(0xbad), abi.encodeCall(IMIPS64.stateVersion, ()), abi.encode(StandardConstants.MIPS_VERSION) + badASR, + abi.encodeCall(IAnchorStateRegistry.getAnchorRoot, ()), + abi.encode(Hash.wrap(bytes32(uint256(0x123))), uint256(123)) ); - assertEq("PLDG-VM-10,PLDG-VM-20", _validate(true)); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.disputeGameFactory, ()), abi.encode(dgf)); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.systemConfig, ()), abi.encode(sysCfg)); + vm.mockCall(badASR, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); + vm.mockCall(badASR, abi.encodeCall(IAnchorStateRegistry.retirementTimestamp, ()), abi.encode(uint64(100))); + + assertEq("PLDG-ANCHORP-10,PLDG-ANCHORP-20", _validate(true)); + } + + /// @notice Tests that the validate function successfully returns the right error when the + /// FaultDisputeGame (permissionless) Weth address is invalid. + function test_validate_faultDisputeGameInvalidWeth_succeeds() public { + address badWeth = address(0xbad); + mockGameImplWeth(dgf, GameTypes.CANNON, badWeth); + + // Mock invalid values + vm.mockCall(badWeth, abi.encodeCall(IStaticERC1967Proxy.implementation, ()), abi.encode(address(0xdeadbeef))); + vm.mockCall(badWeth, abi.encodeCall(ISemver.version, ()), abi.encode("0.0.0")); + + // Mock valid return values + vm.mockCall( + badWeth, + abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), + abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + ); + vm.mockCall( + badWeth, + abi.encodeCall(IDelayedWETH.delay, ()), + abi.encode(opcm.opcmStandardValidator().withdrawalDelaySeconds()) + ); + vm.mockCall(badWeth, abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(sysCfg)); + vm.mockCall(badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); + + assertEq("PLDG-DWETH-10,PLDG-DWETH-20", _validate(true)); } /// @notice Tests that the validate function successfully returns the right error when the /// FaultDisputeGame (permissionless) VM's state version is invalid. function test_validate_faultDisputeGameInvalidVMStateVersion_succeeds() public { - vm.mockCall(address(fdg), abi.encodeCall(IFaultDisputeGame.vm, ()), abi.encode(address(mips))); vm.mockCall(address(mips), abi.encodeCall(IMIPS64.stateVersion, ()), abi.encode(6)); assertEq("PDDG-VM-30,PLDG-VM-30", _validate(true)); } @@ -1242,7 +1413,9 @@ contract OPContractsManagerStandardValidator_FaultDisputeGame_Test is OPContract /// @notice Tests that the validate function successfully returns the right error when the /// FaultDisputeGame (permissionless) L2 Chain ID is invalid. function test_validate_faultDisputeGameInvalidL2ChainId_succeeds() public { - vm.mockCall(address(fdg), abi.encodeCall(IFaultDisputeGame.l2ChainId, ()), abi.encode(l2ChainId + 1)); + uint256 badChainId = l2ChainId + 1; + mockGameImplL2ChainId(dgf, GameTypes.CANNON, badChainId); + assertEq("PLDG-60", _validate(true)); } diff --git a/packages/contracts-bedrock/test/setup/ByteUtils.sol b/packages/contracts-bedrock/test/setup/ByteUtils.sol new file mode 100644 index 00000000000..5989e82d432 --- /dev/null +++ b/packages/contracts-bedrock/test/setup/ByteUtils.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library ByteUtils { + /// @notice Overwrite bytes in-place at a specific offset. + function overwriteAtOffset(bytes memory _bytes, uint256 _offset, bytes memory _value) internal pure { + for (uint256 i = 0; i < _value.length; i++) { + uint256 dataOffset = _offset + i; + if (dataOffset >= _bytes.length) { + // Stop writing bytes when we get to the end of _bytes + break; + } + _bytes[dataOffset] = _value[i]; + } + } +} diff --git a/packages/contracts-bedrock/test/setup/DisputeGames.sol b/packages/contracts-bedrock/test/setup/DisputeGames.sol index 778a5c45591..bd5dff7bede 100644 --- a/packages/contracts-bedrock/test/setup/DisputeGames.sol +++ b/packages/contracts-bedrock/test/setup/DisputeGames.sol @@ -2,17 +2,26 @@ pragma solidity 0.8.15; // Testing +import { FeatureFlags } from "./FeatureFlags.sol"; +import { ByteUtils } from "./ByteUtils.sol"; 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"; +import { GameTypes } from "src/dispute/lib/Types.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { LibGameArgs } from "src/dispute/lib/LibGameArgs.sol"; // Interfaces import "../../interfaces/dispute/IDisputeGame.sol"; import "../../interfaces/dispute/IDisputeGameFactory.sol"; +import { IFaultDisputeGame } from "../../interfaces/dispute/IFaultDisputeGame.sol"; +import { IPermissionedDisputeGame } from "../../interfaces/dispute/IPermissionedDisputeGame.sol"; + +contract DisputeGames is FeatureFlags { + using ByteUtils for bytes; -contract DisputeGames { /// @notice The address of the foundry Vm contract. Vm private constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -46,4 +55,156 @@ contract DisputeGames { return address(gameProxy); } + + enum GameArg { + PRESTATE, + VM, + ASR, + WETH, + L2_CHAIN_ID, + PROPOSER, + CHALLENGER + } + + /// @notice Thrown when an unsupported game arg is provided + error DisputeGames_UnsupportedGameArg(GameArg gameArg); + + function gameArgsOffset(GameArg _gameArg) internal pure returns (uint256) { + if (_gameArg == GameArg.PRESTATE) return 0; + if (_gameArg == GameArg.VM) return 32; + if (_gameArg == GameArg.ASR) return 52; + if (_gameArg == GameArg.WETH) return 72; + if (_gameArg == GameArg.L2_CHAIN_ID) return 92; + if (_gameArg == GameArg.PROPOSER) return 124; + if (_gameArg == GameArg.CHALLENGER) return 144; + + revert DisputeGames_UnsupportedGameArg(_gameArg); + } + + function permissionedGameChallenger(IDisputeGameFactory _dgf) internal returns (address challenger_) { + GameType gameType = GameTypes.PERMISSIONED_CANNON; + (bool gameArgsExist, bytes memory gameArgsData) = _getGameArgs(_dgf, gameType); + if (gameArgsExist) { + LibGameArgs.GameArgs memory gameArgs = LibGameArgs.decode(gameArgsData); + challenger_ = gameArgs.challenger; + } else { + challenger_ = IPermissionedDisputeGame(address(_dgf.gameImpls(gameType))).challenger(); + } + } + + function permissionedGameProposer(IDisputeGameFactory _dgf) internal returns (address proposer_) { + GameType gameType = GameTypes.PERMISSIONED_CANNON; + (bool gameArgsExist, bytes memory gameArgsData) = _getGameArgs(_dgf, gameType); + if (gameArgsExist) { + LibGameArgs.GameArgs memory gameArgs = LibGameArgs.decode(gameArgsData); + proposer_ = gameArgs.proposer; + } else { + proposer_ = IPermissionedDisputeGame(address(_dgf.gameImpls(gameType))).proposer(); + } + } + + function mockGameImplPrestate(IDisputeGameFactory _dgf, GameType _gameType, bytes32 _prestate) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_prestate); + _mockGameArg(_dgf, _gameType, GameArg.PRESTATE, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IFaultDisputeGame.absolutePrestate, ()), abi.encode(_prestate)); + } + } + + function mockGameImplVM(IDisputeGameFactory _dgf, GameType _gameType, address _vm) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_vm); + _mockGameArg(_dgf, _gameType, GameArg.VM, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IFaultDisputeGame.vm, ()), abi.encode(_vm)); + } + } + + function mockGameImplASR(IDisputeGameFactory _dgf, GameType _gameType, address _asr) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_asr); + _mockGameArg(_dgf, _gameType, GameArg.ASR, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IFaultDisputeGame.anchorStateRegistry, ()), abi.encode(_asr)); + } + } + + function mockGameImplWeth(IDisputeGameFactory _dgf, GameType _gameType, address _weth) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_weth); + _mockGameArg(_dgf, _gameType, GameArg.WETH, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IFaultDisputeGame.weth, ()), abi.encode(_weth)); + } + } + + function mockGameImplL2ChainId(IDisputeGameFactory _dgf, GameType _gameType, uint256 _chainId) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_chainId); + _mockGameArg(_dgf, _gameType, GameArg.L2_CHAIN_ID, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IFaultDisputeGame.l2ChainId, ()), abi.encode(_chainId)); + } + } + + function mockGameImplProposer(IDisputeGameFactory _dgf, GameType _gameType, address _proposer) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_proposer); + _mockGameArg(_dgf, _gameType, GameArg.PROPOSER, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IPermissionedDisputeGame.proposer, ()), abi.encode(_proposer)); + } + } + + function mockGameImplChallenger(IDisputeGameFactory _dgf, GameType _gameType, address _challenger) internal { + if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + bytes memory value = abi.encodePacked(_challenger); + _mockGameArg(_dgf, _gameType, GameArg.CHALLENGER, value); + } else { + address gameAddr = address(_dgf.gameImpls(_gameType)); + vm.mockCall(gameAddr, abi.encodeCall(IPermissionedDisputeGame.challenger, ()), abi.encode(_challenger)); + } + } + + function _getGameArgs( + IDisputeGameFactory _dgf, + GameType _gameType + ) + private + returns (bool gameArgsExist_, bytes memory gameArgs_) + { + if (!isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { + return (false, gameArgs_); + } + + bytes memory gameArgsCallData = abi.encodeCall(IDisputeGameFactory.gameArgs, (_gameType)); + (bool success, bytes memory gameArgs) = address(_dgf).call(gameArgsCallData); + + gameArgsExist_ = success && gameArgs.length > 0; + gameArgs_ = gameArgsExist_ ? gameArgs : bytes(""); + } + + function _mockGameArg( + IDisputeGameFactory _dgf, + GameType _gameType, + GameArg _gameArg, + bytes memory _value + ) + private + { + bytes memory modifiedGameArgs = _dgf.gameArgs(_gameType); + uint256 offset = gameArgsOffset(_gameArg); + modifiedGameArgs.overwriteAtOffset(offset, _value); + + vm.mockCall( + address(_dgf), abi.encodeCall(IDisputeGameFactory.gameArgs, (_gameType)), abi.encode(modifiedGameArgs) + ); + } }