From 89b1f812fa971e23147a0ba991e6458316653902 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Fri, 19 Sep 2025 23:17:33 -0400 Subject: [PATCH 01/17] feat: OPCMv2 --- .circleci/config.yml | 1 + .semgrep/rules/sol-rules.yaml | 2 + op-chain-ops/addresses/contracts.go | 1 + op-chain-ops/interopgen/deployments.go | 2 + .../pkg/deployer/opcm/implementations.go | 2 + .../pkg/deployer/pipeline/implementations.go | 1 + packages/contracts-bedrock/foundry.toml | 7 +- .../interfaces/L1/IOPContractsManager.sol | 71 +- .../IOPContractsManagerContractsContainer.sol | 46 + .../L1/opcm/IOPContractsManagerV2.sol | 175 +++ .../interfaces/universal/IStorageSetter.sol | 22 + .../checks/opcm-upgrade-checks/main.go | 2 +- .../scripts/deploy/ChainAssertions.sol | 5 +- .../scripts/deploy/Deploy.s.sol | 42 +- .../deploy/DeployImplementations.s.sol | 53 +- .../deploy/ReadImplementationAddresses.s.sol | 3 +- .../scripts/deploy/VerifyOPCM.s.sol | 3 +- .../scripts/libraries/Config.sol | 5 + .../scripts/ops/pull-artifacts.sh | 30 +- .../snapshots/abi/OPContractsManager.json | 57 +- .../OPContractsManagerContractsContainer.json | 31 +- .../abi/OPContractsManagerDeployer.json | 13 +- .../abi/OPContractsManagerGameTypeAdder.json | 13 +- .../OPContractsManagerInteropMigrator.json | 13 +- .../abi/OPContractsManagerUpgrader.json | 13 +- .../snapshots/abi/OPContractsManagerV2.json | 743 +++++++++++ .../snapshots/semver-lock.json | 6 +- .../OPContractsManagerContractsContainer.json | 10 +- .../storageLayout/OPContractsManagerV2.json | 1 + .../src/L1/OPContractsManager.sol | 424 ++++-- .../OPContractsManagerContractsContainer.sol | 110 ++ .../src/L1/opcm/OPContractsManagerV2.sol | 1167 +++++++++++++++++ .../src/libraries/DevFeatures.sol | 3 + .../test/L1/OPContractsManager.t.sol | 596 +++++++-- ...OPContractsManagerContractsContainer.t.sol | 29 +- .../OPContractsManagerStandardValidator.t.sol | 114 +- .../test/opcm/DeployOPChain.t.sol | 3 +- .../test/setup/DisputeGames.sol | 19 +- .../test/setup/FeatureFlags.sol | 9 + 39 files changed, 3407 insertions(+), 440 deletions(-) create mode 100644 packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol create mode 100644 packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol create mode 100644 packages/contracts-bedrock/interfaces/universal/IStorageSetter.sol create mode 100644 packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerV2.json create mode 100644 packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol create mode 100644 packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol diff --git a/.circleci/config.yml b/.circleci/config.yml index 67f8d7421cd..12fd931fcd8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2547,6 +2547,7 @@ workflows: - OPTIMISM_PORTAL_INTEROP - CANNON_KONA,DEPLOY_V2_DISPUTE_GAMES - CUSTOM_GAS_TOKEN + - OPCM_V2 context: - circleci-repo-readonly-authenticated-github-token - contracts-bedrock-tests: diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index bdfebd4f2d7..fd3b99d06e9 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -320,6 +320,8 @@ rules: - packages/contracts-bedrock/src exclude: - packages/contracts-bedrock/src/L1/OPContractsManager.sol + - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol + - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol - packages/contracts-bedrock/src/L1/OptimismPortal2.sol - packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol - packages/contracts-bedrock/src/L2/FeeVault.sol diff --git a/op-chain-ops/addresses/contracts.go b/op-chain-ops/addresses/contracts.go index 05021b03bbc..a07f7d656fd 100644 --- a/op-chain-ops/addresses/contracts.go +++ b/op-chain-ops/addresses/contracts.go @@ -49,6 +49,7 @@ type ImplementationsContracts struct { AnchorStateRegistryImpl common.Address FaultDisputeGameV2Impl common.Address PermissionedDisputeGameV2Impl common.Address + StorageSetterImpl common.Address } // OpChainContracts struct contains all the contracts for a specific L2 OpChain diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index ff3d2dbeba1..3d2422f60ed 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -17,6 +17,7 @@ type Implementations struct { OpcmUpgrader common.Address `json:"OPCMUpgrader"` OpcmInteropMigrator common.Address `json:"OPCMInteropMigrator"` OpcmStandardValidator common.Address `json:"OPCMStandardValidator"` + OpcmV2 common.Address `json:"OPCMV2"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` OptimismPortalInteropImpl common.Address `json:"OptimismPortalInteropImpl"` @@ -36,6 +37,7 @@ type Implementations struct { PermissionedDisputeGameV2Impl common.Address `json:"PermissionedDisputeGameV2Impl"` SuperFaultDisputeGameImpl common.Address `json:"SuperFaultDisputeGameImpl"` SuperPermissionedDisputeGameImpl common.Address `json:"SuperPermissionedDisputeGameImpl"` + StorageSetterImpl common.Address `json:"StorageSetterImpl"` } type SuperchainDeployment struct { diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index d39dbe032c0..6339386224f 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -35,6 +35,7 @@ type DeployImplementationsOutput struct { OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` OpcmInteropMigrator common.Address `json:"opcmInteropMigratorAddress"` OpcmStandardValidator common.Address `json:"opcmStandardValidatorAddress"` + OpcmV2 common.Address `json:"opcmV2Address" abi:"opcmV2"` DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` OptimismPortalInteropImpl common.Address `json:"optimismPortalInteropImplAddress"` @@ -54,6 +55,7 @@ type DeployImplementationsOutput struct { PermissionedDisputeGameV2Impl common.Address `json:"permissionedDisputeGameV2ImplAddress"` SuperFaultDisputeGameImpl common.Address `json:"superFaultDisputeGameImplAddress"` SuperPermissionedDisputeGameImpl common.Address `json:"superPermissionedDisputeGameImplAddress"` + StorageSetterImpl common.Address `json:"storageSetterImplAddress"` } type DeployImplementationsScript script.DeployScriptWithOutput[DeployImplementationsInput, DeployImplementationsOutput] diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go index 600090e03b7..138186f905a 100644 --- a/op-deployer/pkg/deployer/pipeline/implementations.go +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -89,6 +89,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro AnchorStateRegistryImpl: dio.AnchorStateRegistryImpl, FaultDisputeGameV2Impl: dio.FaultDisputeGameV2Impl, PermissionedDisputeGameV2Impl: dio.PermissionedDisputeGameV2Impl, + StorageSetterImpl: dio.StorageSetterImpl, } return nil diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index fe3ec52fbf0..cf6865723f1 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -28,8 +28,11 @@ compilation_restrictions = [ { paths = "src/dispute/zk/OPSuccinctFaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, + { paths = "src/L1/opcm/OPContractsManagerV2.sol", optimizer_runs = 5000 }, + { paths = "src/L1/opcm/OPContractsManagerContractsContainer.sol", optimizer_runs = 5000 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, - { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 } + { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 }, + { paths = "src/universal/StorageSetter.sol", optimizer_runs = 5000 } ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -155,6 +158,8 @@ compilation_restrictions = [ { paths = "src/dispute/zk/OPSuccinctFaultDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, + { paths = "src/L1/opcm/OPContractsManagerV2.sol", optimizer_runs = 0 }, + { paths = "src/L1/opcm/OPContractsManagerContractsContainer.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 }, ] diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index f042cffa579..110cb8ba1e3 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -10,36 +10,21 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -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 { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; 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 { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; - -interface IOPContractsManagerContractsContainer { - error OPContractsManagerContractsContainer_DevFeatureInProd(); - - function __constructor__( - IOPContractsManager.Blueprints memory _blueprints, - IOPContractsManager.Implementations memory _implementations, - bytes32 _devFeatureBitmap - ) - external; - - function blueprints() external view returns (IOPContractsManager.Blueprints memory); - function implementations() external view returns (IOPContractsManager.Implementations memory); - function devFeatureBitmap() external view returns (bytes32); - function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); -} +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; interface IOPContractsManagerGameTypeAdder { error OPContractsManagerGameTypeAdder_UnsupportedGameType(); @@ -181,45 +166,6 @@ interface IOPContractsManager { IDelayedWETH delayedWETHPermissionlessGameProxy; } - /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size - /// contracts, to reduce the code size of this factory contract. If it deployed full contracts - /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would - /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to - /// reduce the code size of this contract. - struct Blueprints { - address addressManager; - address proxy; - address proxyAdmin; - address l1ChugSplashProxy; - address resolvedDelegateProxy; - address permissionedDisputeGame1; - address permissionedDisputeGame2; - address permissionlessDisputeGame1; - address permissionlessDisputeGame2; - } - - /// @notice The latest implementation contracts for the OP Stack. - struct Implementations { - address superchainConfigImpl; - address protocolVersionsImpl; - address l1ERC721BridgeImpl; - address optimismPortalImpl; - address optimismPortalInteropImpl; - address ethLockboxImpl; - address systemConfigImpl; - address optimismMintableERC20FactoryImpl; - address l1CrossDomainMessengerImpl; - address l1StandardBridgeImpl; - address disputeGameFactoryImpl; - address anchorStateRegistryImpl; - address delayedWETHImpl; - address mipsImpl; - address faultDisputeGameV2Impl; - address permissionedDisputeGameV2Impl; - address superFaultDisputeGameImpl; - address superPermissionedDisputeGameImpl; - } - /// @notice The input required to identify a chain for upgrading. struct OpChainConfig { ISystemConfig systemConfigProxy; @@ -304,6 +250,10 @@ interface IOPContractsManager { error InvalidDevFeatureAccess(bytes32 devFeature); + error MissingPermissionedDisputeGame(); + + event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); + // -------- Methods -------- function __constructor__( @@ -312,6 +262,7 @@ interface IOPContractsManager { IOPContractsManagerUpgrader _opcmUpgrader, IOPContractsManagerInteropMigrator _opcmInteropMigrator, IOPContractsManagerStandardValidator _opcmStandardValidator, + IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) @@ -381,7 +332,7 @@ interface IOPContractsManager { function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address); /// @notice Returns the blueprint contract addresses. - function blueprints() external view returns (Blueprints memory); + function blueprints() external view returns (IOPContractsManagerContractsContainer.Blueprints memory); function opcmDeployer() external view returns (IOPContractsManagerDeployer); @@ -393,6 +344,8 @@ interface IOPContractsManager { function opcmStandardValidator() external view returns (IOPContractsManagerStandardValidator); + function opcmV2() external view returns (IOPContractsManagerV2); + /// @notice Retrieves the development feature bitmap stored in this OPCM contract /// @return The development feature bitmap. function devFeatureBitmap() external view returns (bytes32); @@ -403,5 +356,5 @@ interface IOPContractsManager { function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); /// @notice Returns the implementation contract addresses. - function implementations() external view returns (Implementations memory); + function implementations() external view returns (IOPContractsManagerContractsContainer.Implementations memory); } diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol new file mode 100644 index 00000000000..be27d1bfdfc --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IOPContractsManagerContractsContainer { + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + address permissionedDisputeGame1; + address permissionedDisputeGame2; + address permissionlessDisputeGame1; + address permissionlessDisputeGame2; + } + + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address optimismPortalInteropImpl; + address ethLockboxImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; + address superFaultDisputeGameImpl; + address superPermissionedDisputeGameImpl; + address storageSetterImpl; + } + + error OPContractsManagerContractsContainer_DevFeatureInProd(); + + function blueprints() external view returns (Blueprints memory); + function implementations() external view returns (Implementations memory); + function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); + function devFeatureBitmap() external view returns (bytes32); + function __constructor__(Blueprints memory _blueprints, Implementations memory _implementations, bytes32 _devFeatureBitmap) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol new file mode 100644 index 00000000000..17f680990a7 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { Claim, GameType, Proposal } from "src/dispute/lib/Types.sol"; + +// Interfaces +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; + +interface IOPContractsManagerV2 { + /// @notice Configuration for the FaultDisputeGame. + struct FaultDisputeGameConfig { + Claim absolutePrestate; + } + + /// @notice Configuration for the PermissionedDisputeGame. + struct PermissionedDisputeGameConfig { + Claim absolutePrestate; + address proposer; + address challenger; + } + + /// @notice Dispute game configuration for a specific game type. + struct DisputeGameConfig { + bool enabled; + uint256 initBond; + GameType gameType; + bytes gameArgs; + } + + /// @notice Contracts that represent the Superchain system. + struct SuperchainContracts { + ISuperchainConfig superchainConfig; + } + + /// @notice Addresses of the deployed and wired contracts for an OP Chain. + struct ChainContracts { + ISystemConfig systemConfig; + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + IL1CrossDomainMessenger l1CrossDomainMessenger; + IL1ERC721Bridge l1ERC721Bridge; + IL1StandardBridge l1StandardBridge; + IOptimismPortal2 optimismPortal; + IETHLockbox ethLockbox; + IOptimismMintableERC20Factory optimismMintableERC20Factory; + IDisputeGameFactory disputeGameFactory; + IAnchorStateRegistry anchorStateRegistry; + IDelayedWETH delayedWETH; + } + + /// @notice Full configuration for deploying a new OP Chain. + struct FullConfig { + string saltMixer; + ISuperchainConfig superchainConfig; + address proxyAdminOwner; + address systemConfigOwner; + address unsafeBlockSigner; + address batcher; + Proposal startingAnchorRoot; + GameType startingRespectedGameType; + uint32 basefeeScalar; + uint32 blobBasefeeScalar; + uint64 gasLimit; + uint256 l2ChainId; + IResourceMetering.ResourceConfig resourceConfig; + DisputeGameConfig[] disputeGameConfigs; + } + + struct ExtraInstruction { + string key; + bytes data; + } + + struct UpgradeInput { + ISystemConfig systemConfig; + DisputeGameConfig[] disputeGameConfigs; + ExtraInstruction[] extraInstructions; + } + + struct SuperchainUpgradeInput { + ISuperchainConfig superchainConfig; + ExtraInstruction[] extraInstructions; + } + + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + } + + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address optimismPortalInteropImpl; + address ethLockboxImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; + address superFaultDisputeGameImpl; + address superPermissionedDisputeGameImpl; + address storageSetterImpl; + } + + event ProxyCreation(string name, address proxy); + + error OPContractsManagerV2_InvalidGameConfigs(); + error OPContractsManagerV2_InvalidUpgradeInput(); + error OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); + error OPContractsManagerV2_UnsupportedGameType(); + error OPContractsManagerV2_ProxyMustLoad(); + error OPContractsManagerV2_DowngradeNotAllowed(); + error OPContractsManagerV2_DevFeatureInProd(); + error OPContractsManagerV2_InvalidUpgradeInstruction(); + error OPContractsManagerV2_ConfigLoadFailed(string _name); + error IdentityPrecompileCallFailed(); + error ReservedBitsSet(); + error BytesArrayTooLong(); + error SemverComp_InvalidSemverParts(); + error UnsupportedERCVersion(uint8 version); + error NotABlueprint(); + error DeploymentFailed(); + error EmptyInitcode(); + error UnexpectedPreambleData(bytes data); + + function __constructor__( + IOPContractsManagerContractsContainer _contractsContainer + ) + external; + + function blueprints() external view returns (IOPContractsManagerContractsContainer.Blueprints memory); + + function implementations() external view returns (IOPContractsManagerContractsContainer.Implementations memory); + + function contractsContainer() external view returns (IOPContractsManagerContractsContainer); + + /// @notice Upgrades Superchain-wide contracts. + function upgradeSuperchain(SuperchainUpgradeInput memory _inp) + external + returns (SuperchainContracts memory); + + /// @notice Deploys and wires a complete OP Chain per the provided configuration. + function deploy(FullConfig memory _cfg) external returns (ChainContracts memory); + + /// @notice Upgrades contracts on an existing OP Chain per the provided input. + function upgrade(UpgradeInput memory _inp) external returns (ChainContracts memory); + + /// @notice Returns whether a development feature is enabled. + function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); +} diff --git a/packages/contracts-bedrock/interfaces/universal/IStorageSetter.sol b/packages/contracts-bedrock/interfaces/universal/IStorageSetter.sol new file mode 100644 index 00000000000..0a7664ea865 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/universal/IStorageSetter.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IStorageSetter { + struct Slot { + bytes32 key; + bytes32 value; + } + + function version() external view returns (string memory); + function setBytes32(bytes32 _slot, bytes32 _value) external; + function setBytes32(Slot[] calldata _slots) external; + function getBytes32(bytes32 _slot) external view returns (bytes32 value_); + function setUint(bytes32 _slot, uint256 _value) external; + function getUint(bytes32 _slot) external view returns (uint256 value_); + function setAddress(bytes32 _slot, address _address) external; + function getAddress(bytes32 _slot) external view returns (address addr_); + function setBool(bytes32 _slot, bool _value) external; + function getBool(bytes32 _slot) external view returns (bool value_); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go b/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go index 1b5c6f29d45..7afe2cf1019 100644 --- a/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go +++ b/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go @@ -36,7 +36,7 @@ func main() { // Process. if _, err := common.ProcessFilesGlob( []string{"forge-artifacts/**/*.json"}, - []string{"forge-artifacts/OPContractsManager.sol/*.json"}, + []string{"forge-artifacts/OPContractsManager.sol/*.json", "forge-artifacts/OPContractsManagerV2.sol/*.json", "forge-artifacts/opcm/OPContractsManagerV2.sol/*.json"}, processFile, ); err != nil { fmt.Printf("error: %v\n", err) diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 3661c7602ff..93a6181372e 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -36,6 +36,7 @@ import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -390,7 +391,7 @@ library ChainAssertions { require(address(_opcm.superchainConfig()) == _proxies.SuperchainConfig, "CHECK-OPCM-19"); // Ensure that the OPCM impls are correctly saved - IOPContractsManager.Implementations memory impls = _opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = _opcm.implementations(); require(impls.l1ERC721BridgeImpl == _impls.L1ERC721Bridge, "CHECK-OPCM-50"); require(impls.optimismPortalImpl == _impls.OptimismPortal, "CHECK-OPCM-60"); require(impls.systemConfigImpl == _impls.SystemConfig, "CHECK-OPCM-70"); @@ -404,7 +405,7 @@ library ChainAssertions { require(impls.protocolVersionsImpl == _impls.ProtocolVersions, "CHECK-OPCM-150"); // Verify that initCode is correctly set into the blueprints - IOPContractsManager.Blueprints memory blueprints = _opcm.blueprints(); + IOPContractsManagerContractsContainer.Blueprints memory blueprints = _opcm.blueprints(); Blueprint.Preamble memory addressManagerPreamble = Blueprint.parseBlueprintPreamble(address(blueprints.addressManager).code); require(keccak256(addressManagerPreamble.initcode) == keccak256(vm.getCode("AddressManager")), "CHECK-OPCM-160"); diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 460d6b90b5c..9dbe43d6c30 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -296,6 +296,9 @@ contract Deploy is Deployer { if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { artifacts.save("PermissionedDisputeGame", address(dio.permissionedDisputeGameV2Impl)); } + if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.OPCM_V2)) { + 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); @@ -360,6 +363,7 @@ contract Deploy is Deployer { // Fault Proof contracts artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy)); @@ -367,24 +371,26 @@ contract Deploy is Deployer { artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); } - // Check if the permissionless game implementation is already set - IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); - address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); - - // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. - // If the following require statement is hit, you can delete the block of code after it. - require( - permissionlessGameImpl == address(0), - "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." - ); - address delayedWETHImpl = artifacts.mustGetAddress("DelayedWETHImpl"); - address delayedWETHPermissionlessGameProxy = - deployERC1967ProxyWithOwner("DelayedWETHProxy", address(deployOutput.opChainProxyAdmin)); - vm.broadcast(address(deployOutput.opChainProxyAdmin)); - IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ - _implementation: delayedWETHImpl, - _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) - }); + if (!DevFeatures.isDevFeatureEnabled(opcm.devFeatureBitmap(), DevFeatures.OPCM_V2)) { + // Check if the permissionless game implementation is already set + IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); + + // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. + // If the following require statement is hit, you can delete the block of code after it. + require( + permissionlessGameImpl == address(0), + "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." + ); + address delayedWETHImpl = artifacts.mustGetAddress("DelayedWETHImpl"); + address delayedWETHPermissionlessGameProxy = + deployERC1967ProxyWithOwner("DelayedWETHProxy", address(deployOutput.opChainProxyAdmin)); + vm.broadcast(address(deployOutput.opChainProxyAdmin)); + IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ + _implementation: delayedWETHImpl, + _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) + }); + } } //////////////////////////////////////////////////////////////// diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 3033e7e8bc4..f19db667858 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -25,10 +25,11 @@ import { IOPContractsManagerGameTypeAdder, IOPContractsManagerDeployer, IOPContractsManagerUpgrader, - IOPContractsManagerContractsContainer, IOPContractsManagerInteropMigrator, IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; @@ -38,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 { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; @@ -74,6 +76,7 @@ contract DeployImplementations is Script { IOPContractsManagerUpgrader opcmUpgrader; IOPContractsManagerInteropMigrator opcmInteropMigrator; IOPContractsManagerStandardValidator opcmStandardValidator; + IOPContractsManagerV2 opcmV2; IDelayedWETH delayedWETHImpl; IOptimismPortal optimismPortalImpl; IOptimismPortalInterop optimismPortalInteropImpl; @@ -93,6 +96,7 @@ contract DeployImplementations is Script { IPermissionedDisputeGameV2 permissionedDisputeGameV2Impl; ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; + IStorageSetter storageSetterImpl; } bytes32 internal _salt = DeployUtils.DEFAULT_SALT; @@ -132,6 +136,7 @@ contract DeployImplementations is Script { deploySuperFaultDisputeGameImpl(_input, output_); deploySuperPermissionedDisputeGameImpl(_input, output_); } + deployStorageSetterImpl(output_); // Deploy the OP Contracts Manager with the new implementations set. deployOPContractsManager(_input, output_); @@ -146,12 +151,13 @@ contract DeployImplementations is Script { function createOPCMContract( Input memory _input, Output memory _output, - IOPContractsManager.Blueprints memory _blueprints + IOPContractsManagerContractsContainer.Blueprints memory _blueprints ) private returns (IOPContractsManager opcm_) { - IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ + IOPContractsManagerContractsContainer.Implementations memory implementations = + IOPContractsManagerContractsContainer.Implementations({ superchainConfigImpl: address(_output.superchainConfigImpl), protocolVersionsImpl: address(_output.protocolVersionsImpl), l1ERC721BridgeImpl: address(_output.l1ERC721BridgeImpl), @@ -169,7 +175,8 @@ contract DeployImplementations is Script { faultDisputeGameV2Impl: address(_output.faultDisputeGameV2Impl), permissionedDisputeGameV2Impl: address(_output.permissionedDisputeGameV2Impl), superFaultDisputeGameImpl: address(_output.superFaultDisputeGameImpl), - superPermissionedDisputeGameImpl: address(_output.superPermissionedDisputeGameImpl) + superPermissionedDisputeGameImpl: address(_output.superPermissionedDisputeGameImpl), + storageSetterImpl: address(_output.storageSetterImpl) }); deployOPCMBPImplsContainer(_input, _output, _blueprints, implementations); @@ -178,6 +185,7 @@ contract DeployImplementations is Script { deployOPCMUpgrader(_output); deployOPCMInteropMigrator(_output); deployOPCMStandardValidator(_input, _output, implementations); + deployOPCMV2(_output); // Semgrep rule will fail because the arguments are encoded inside of a separate function. opcm_ = IOPContractsManager( @@ -215,6 +223,7 @@ contract DeployImplementations is Script { _output.opcmUpgrader, _output.opcmInteropMigrator, _output.opcmStandardValidator, + _output.opcmV2, _input.superchainConfigProxy, _input.protocolVersionsProxy ) @@ -225,7 +234,7 @@ contract DeployImplementations is Script { function deployOPContractsManager(Input memory _input, Output memory _output) private { // First we deploy the blueprints for the singletons deployed by OPCM. // forgefmt: disable-start - IOPContractsManager.Blueprints memory blueprints; + IOPContractsManagerContractsContainer.Blueprints memory blueprints; vm.startBroadcast(msg.sender); address checkAddress; (blueprints.addressManager, checkAddress) = DeployUtils.createDeterministicBlueprint(vm.getCode("AddressManager"), _salt); @@ -572,14 +581,14 @@ contract DeployImplementations is Script { function deployOPCMBPImplsContainer( Input memory _input, Output memory _output, - IOPContractsManager.Blueprints memory _blueprints, - IOPContractsManager.Implementations memory _implementations + IOPContractsManagerContractsContainer.Blueprints memory _blueprints, + IOPContractsManagerContractsContainer.Implementations memory _implementations ) private { IOPContractsManagerContractsContainer impl = IOPContractsManagerContractsContainer( DeployUtils.createDeterministic({ - _name: "OPContractsManager.sol:OPContractsManagerContractsContainer", + _name: "OPContractsManagerContractsContainer.sol:OPContractsManagerContractsContainer", _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManagerContractsContainer.__constructor__, @@ -652,7 +661,7 @@ contract DeployImplementations is Script { function deployOPCMStandardValidator( Input memory _input, Output memory _output, - IOPContractsManager.Implementations memory _implementations + IOPContractsManagerContractsContainer.Implementations memory _implementations ) private { @@ -693,6 +702,32 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deployOPCMV2(Output memory _output) private { + IOPContractsManagerV2 impl = IOPContractsManagerV2( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerV2.sol:OPContractsManagerV2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerV2.__constructor__, (_output.opcmContractsContainer)) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerV2Impl"); + _output.opcmV2 = impl; + } + + function deployStorageSetterImpl(Output memory _output) private { + IStorageSetter impl = IStorageSetter( + DeployUtils.createDeterministic({ + _name: "StorageSetter", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IStorageSetter.__constructor__, ())), + _salt: _salt + }) + ); + vm.label(address(impl), "StorageSetterImpl"); + _output.storageSetterImpl = impl; + } + function assertValidInput(Input memory _input) private pure { if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { // Validate V2 game depth parameters are sensible diff --git a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol index 23deddc11e7..a2350b4fdc3 100644 --- a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol @@ -7,6 +7,7 @@ import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; contract ReadImplementationAddresses is Script { struct Input { @@ -67,7 +68,7 @@ contract ReadImplementationAddresses is Script { output_.opcmInteropMigrator = address(opcm.opcmInteropMigrator()); output_.opcmStandardValidator = address(opcm.opcmStandardValidator()); - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); output_.mipsSingleton = impls.mipsImpl; output_.delayedWETH = impls.delayedWETHImpl; output_.ethLockbox = impls.ethLockboxImpl; diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index 1c6c6237c20..a7e2e69b53b 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -129,6 +129,7 @@ contract VerifyOPCM is Script { fieldNameOverrides["opcmUpgrader"] = "OPContractsManagerUpgrader"; fieldNameOverrides["opcmInteropMigrator"] = "OPContractsManagerInteropMigrator"; fieldNameOverrides["opcmStandardValidator"] = "OPContractsManagerStandardValidator"; + fieldNameOverrides["opcmV2"] = "OPContractsManagerV2"; fieldNameOverrides["contractsContainer"] = "OPContractsManagerContractsContainer"; // Overrides for situations where contracts have differently named source files. @@ -136,7 +137,6 @@ contract VerifyOPCM is Script { sourceNameOverrides["OPContractsManagerDeployer"] = "OPContractsManager"; sourceNameOverrides["OPContractsManagerUpgrader"] = "OPContractsManager"; sourceNameOverrides["OPContractsManagerInteropMigrator"] = "OPContractsManager"; - sourceNameOverrides["OPContractsManagerContractsContainer"] = "OPContractsManager"; // Expected getter functions and their verification methods. // CRITICAL: Any getter in the ABI that's not in this list will cause verification to fail. @@ -156,6 +156,7 @@ contract VerifyOPCM is Script { expectedGetters["opcmInteropMigrator"] = "SKIP"; // Address verified via bytecode comparison expectedGetters["opcmStandardValidator"] = "SKIP"; // Address verified via bytecode comparison expectedGetters["opcmUpgrader"] = "SKIP"; // Address verified via bytecode comparison + expectedGetters["opcmV2"] = "SKIP"; // Address verified via bytecode comparison // Getters that don't need any sort of verification expectedGetters["devFeatureBitmap"] = "SKIP"; diff --git a/packages/contracts-bedrock/scripts/libraries/Config.sol b/packages/contracts-bedrock/scripts/libraries/Config.sol index 4232b2fae93..2c1df2f712c 100644 --- a/packages/contracts-bedrock/scripts/libraries/Config.sol +++ b/packages/contracts-bedrock/scripts/libraries/Config.sol @@ -255,4 +255,9 @@ library Config { function devFeatureCustomGasToken() internal view returns (bool) { return vm.envOr("DEV_FEATURE__CUSTOM_GAS_TOKEN", false); } + + /// @notice Returns true if the development feature opcm_v2 is enabled. + function devFeatureOpcmV2() internal view returns (bool) { + return vm.envOr("DEV_FEATURE__OPCM_V2", false); + } } diff --git a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh index 7f79c46341b..690ee9f9897 100755 --- a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh +++ b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh @@ -77,18 +77,24 @@ if [ "$HAS_ZSTD" = true ]; then exists_zst=$(curl -s -o /dev/null --fail -LI "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" || echo "fail") if [ "$exists_zst" != "fail" ]; then - download_and_extract "$archive_name_zst" - fi - - # Try latest fallback if enabled - if [ "$USE_LATEST_FALLBACK" = true ]; then - echoerr "> Exact checksum not found, trying latest artifacts..." - archive_name_zst="artifacts-v1-latest.tar.zst" - exists_latest_zst=$(curl -s -o /dev/null --fail -LI "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" || echo "fail") - - if [ "$exists_latest_zst" != "fail" ]; then - download_and_extract "$archive_name_zst" - fi + echoerr "> Found .tar.zst artifacts. Downloading..." + curl -o "$archive_name_zst" "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" + echoerr "> Done." + + echoerr "> Cleaning up existing artifacts..." + rm -rf artifacts + rm -rf forge-artifacts + rm -rf cache + echoerr "> Done." + + echoerr "> Extracting existing artifacts..." + zstd -dc "$archive_name_zst" | tar -xf - + echoerr "> Done." + + echoerr "> Cleaning up." + rm "$archive_name_zst" + echoerr "> Done." + exit 0 fi fi diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 33f8880d27e..49f9a79c053 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -26,6 +26,11 @@ "name": "_opcmStandardValidator", "type": "address" }, + { + "internalType": "contract IOPContractsManagerV2", + "name": "_opcmV2", + "type": "address" + }, { "internalType": "contract ISuperchainConfig", "name": "_superchainConfig", @@ -185,7 +190,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -520,9 +525,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } @@ -715,6 +725,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "opcmV2", + "outputs": [ + { + "internalType": "contract IOPContractsManagerV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "protocolVersions", @@ -1055,6 +1078,31 @@ "stateMutability": "pure", "type": "function" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "deployer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "deployOutput", + "type": "bytes" + } + ], + "name": "Deployed", + "type": "event" + }, { "inputs": [ { @@ -1124,6 +1172,11 @@ "name": "LatestReleaseNotSet", "type": "error" }, + { + "inputs": [], + "name": "MissingPermissionedDisputeGame", + "type": "error" + }, { "inputs": [], "name": "OnlyDelegatecall", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json index a3dc49e896f..18e515e8494 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -49,7 +49,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct OPContractsManagerContractsContainer.Blueprints", "name": "_blueprints", "type": "tuple" }, @@ -144,9 +144,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct OPContractsManagerContractsContainer.Implementations", "name": "_implementations", "type": "tuple" }, @@ -159,19 +164,6 @@ "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [], - "name": "_isTestingEnvironment", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "blueprints", @@ -224,7 +216,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct OPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -340,9 +332,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct OPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index a8798bdcb95..1240f6485b7 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -433,9 +433,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index 16debf40454..08885f3b479 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -168,7 +168,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -200,7 +200,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -316,9 +316,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json index 5644c7edb07..3ba286b20ef 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -223,9 +223,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index 2df9bcd824a..cb435769d8b 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManager.Blueprints", + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract OPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -223,9 +223,14 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.Implementations", + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json new file mode 100644 index 00000000000..5e82490e819 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -0,0 +1,743 @@ +[ + { + "inputs": [ + { + "internalType": "contract IOPContractsManagerContractsContainer", + "name": "_contractsContainer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract IOPContractsManagerContractsContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "superchainConfig", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdminOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "unsafeBlockSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "batcher", + "type": "address" + }, + { + "components": [ + { + "internalType": "Hash", + "name": "root", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2SequenceNumber", + "type": "uint256" + } + ], + "internalType": "struct Proposal", + "name": "startingAnchorRoot", + "type": "tuple" + }, + { + "internalType": "GameType", + "name": "startingRespectedGameType", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blobBasefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "gasLimit", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "maxResourceLimit", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "elasticityMultiplier", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "baseFeeMaxChangeDenominator", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "minimumBaseFee", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "systemTxMaxGas", + "type": "uint32" + }, + { + "internalType": "uint128", + "name": "maximumBaseFee", + "type": "uint128" + } + ], + "internalType": "struct IResourceMetering.ResourceConfig", + "name": "resourceConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "initBond", + "type": "uint256" + }, + { + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "gameArgs", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerV2.DisputeGameConfig[]", + "name": "disputeGameConfigs", + "type": "tuple[]" + } + ], + "internalType": "struct OPContractsManagerV2.FullConfig", + "name": "_cfg", + "type": "tuple" + } + ], + "name": "deploy", + "outputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IAddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract IL1CrossDomainMessenger", + "name": "l1CrossDomainMessenger", + "type": "address" + }, + { + "internalType": "contract IL1ERC721Bridge", + "name": "l1ERC721Bridge", + "type": "address" + }, + { + "internalType": "contract IL1StandardBridge", + "name": "l1StandardBridge", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2", + "name": "optimismPortal", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockbox", + "type": "address" + }, + { + "internalType": "contract IOptimismMintableERC20Factory", + "name": "optimismMintableERC20Factory", + "type": "address" + }, + { + "internalType": "contract IDisputeGameFactory", + "name": "disputeGameFactory", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerV2.ChainContracts", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalInteropImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "superFaultDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "superPermissionedDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" + } + ], + "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_feature", + "type": "bytes32" + } + ], + "name": "isDevFeatureEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "initBond", + "type": "uint256" + }, + { + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "gameArgs", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerV2.DisputeGameConfig[]", + "name": "disputeGameConfigs", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", + "name": "extraInstructions", + "type": "tuple[]" + } + ], + "internalType": "struct OPContractsManagerV2.UpgradeInput", + "name": "_inp", + "type": "tuple" + } + ], + "name": "upgrade", + "outputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IAddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract IL1CrossDomainMessenger", + "name": "l1CrossDomainMessenger", + "type": "address" + }, + { + "internalType": "contract IL1ERC721Bridge", + "name": "l1ERC721Bridge", + "type": "address" + }, + { + "internalType": "contract IL1StandardBridge", + "name": "l1StandardBridge", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2", + "name": "optimismPortal", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockbox", + "type": "address" + }, + { + "internalType": "contract IOptimismMintableERC20Factory", + "name": "optimismMintableERC20Factory", + "type": "address" + }, + { + "internalType": "contract IDisputeGameFactory", + "name": "disputeGameFactory", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerV2.ChainContracts", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISuperchainConfig", + "name": "superchainConfig", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", + "name": "extraInstructions", + "type": "tuple[]" + } + ], + "internalType": "struct OPContractsManagerV2.SuperchainUpgradeInput", + "name": "_inp", + "type": "tuple" + } + ], + "name": "upgradeSuperchain", + "outputs": [ + { + "components": [ + { + "internalType": "contract ISuperchainConfig", + "name": "superchainConfig", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerV2.SuperchainContracts", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "ProxyCreation", + "type": "event" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], + "name": "OPContractsManagerV2_ConfigLoadFailed", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_DevFeatureInProd", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_DowngradeNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_InvalidGameConfigs", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_InvalidUpgradeInput", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_InvalidUpgradeInstruction", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_ProxyMustLoad", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_SuperchainConfigNeedsUpgrade", + "type": "error" + }, + { + "inputs": [], + "name": "OPContractsManagerV2_UnsupportedGameType", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [], + "name": "SemverComp_InvalidSemverParts", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 34b04976007..441adeef2c3 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x6fd82aaec43858b34bd093e29bbacb659a95817d506de948aebd3c84e6d2a00a", - "sourceCodeHash": "0x5f63555e12cb8e27ce5c1511e986550bf2f1b9769e314223a7324b8dcfa29523" + "initCodeHash": "0xa46937e5ab1a9647a32b2d6452893384649984744b69747783a76f4898148e5f", + "sourceCodeHash": "0x77994c4adab71b526d17b264731f671c77334e221a46c59d3a6f54cbfb04ca77" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x0c8b15453d0f0bc5d9af07f104505e0bbb2b358f0df418289822fb73a8652b30", @@ -271,4 +271,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} \ No newline at end of file +} diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index 9487d230af8..472149a29cd 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -1,16 +1,16 @@ [ { "bytes": "288", - "label": "blueprint", + "label": "bps", "offset": 0, "slot": "0", - "type": "struct OPContractsManager.Blueprints" + "type": "struct OPContractsManagerContractsContainer.Blueprints" }, { - "bytes": "576", - "label": "implementation", + "bytes": "608", + "label": "impls", "offset": 0, "slot": "9", - "type": "struct OPContractsManager.Implementations" + "type": "struct OPContractsManagerContractsContainer.Implementations" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerV2.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerV2.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 31bfb0989e8..89541c0550a 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -38,104 +38,47 @@ 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. - /// This is internal because if public the autogenerated getter method would return a tuple of - /// addresses, but we want it to return a struct. - OPContractsManager.Blueprints internal blueprint; - - /// @notice Addresses of the latest implementation contracts. - OPContractsManager.Implementations internal implementation; - - /// @notice Bitmap of development features that are enabled. We keep the development feature - /// bitmap here rather than in the actual OPCM because other contracts always get a - /// reference to this but not to the OPCM itself. - bytes32 public immutable devFeatureBitmap; - - /// @notice Thrown when a development feature is enabled in production. - error OPContractsManagerContractsContainer_DevFeatureInProd(); - - /// @param _blueprints The blueprint contract addresses. - /// @param _implementations The implementation contract addresses. - /// @param _devFeatureBitmap The bitmap of development features that are enabled. - constructor( - OPContractsManager.Blueprints memory _blueprints, - OPContractsManager.Implementations memory _implementations, - bytes32 _devFeatureBitmap - ) { - blueprint = _blueprints; - implementation = _implementations; - devFeatureBitmap = _devFeatureBitmap; - - // Development features MUST NOT be enabled on Mainnet. - if (block.chainid == 1 && !_isTestingEnvironment() && uint256(_devFeatureBitmap) != 0) { - revert OPContractsManagerContractsContainer_DevFeatureInProd(); - } - } - - function blueprints() public view returns (OPContractsManager.Blueprints memory) { - return blueprint; - } - - function implementations() public view returns (OPContractsManager.Implementations memory) { - return implementation; - } - - /// @notice Returns the status of a development feature. Note that this function does not check - /// that the input feature represents a single feature and the bitwise AND operation - /// allows for multiple features to be enabled at once. Users should generally check - /// for only a single feature at a time. - /// @param _feature The feature to check. - /// @return True if the feature is enabled, false otherwise. - function isDevFeatureEnabled(bytes32 _feature) public view returns (bool) { - return DevFeatures.isDevFeatureEnabled(devFeatureBitmap, _feature); - } - - /// @notice Returns true if the contract is running in a testing environment. Checks that the - /// code for the address 0xbeefcafe is not zero, which is an address that should never - /// have any code in production environments but can be made to have code in tests. - /// @return True if the contract is running in a testing environment, false otherwise. - function _isTestingEnvironment() public view returns (bool) { - return address(0xbeefcafe).code.length > 0; - } -} +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; abstract contract OPContractsManagerBase { /// @notice Thrown when an invalid game type is used. error OPContractsManager_InvalidGameType(); /// @notice The blueprint contract addresses contract. - OPContractsManagerContractsContainer public immutable contractsContainer; + IOPContractsManagerContractsContainer public immutable contractsContainer; /// @notice The OPContractsManager contract that is currently being used. OPContractsManagerBase internal immutable thisOPCM; /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses - constructor(OPContractsManagerContractsContainer _contractsContainer) { + constructor(IOPContractsManagerContractsContainer _contractsContainer) { contractsContainer = _contractsContainer; thisOPCM = this; } /// @notice Retrieves the implementation addresses stored in this OPCM contract - function getImplementations() internal view returns (OPContractsManager.Implementations memory) { + function getImplementations() + internal + view + returns (IOPContractsManagerContractsContainer.Implementations memory) + { return thisOPCM.implementations(); } /// @notice Retrieves the blueprint addresses stored in this OPCM contract - function getBlueprints() internal view returns (OPContractsManager.Blueprints memory) { + function getBlueprints() internal view returns (IOPContractsManagerContractsContainer.Blueprints memory) { return thisOPCM.blueprints(); } /// @notice Retrieves the implementation addresses stored in this OPCM contract - function implementations() public view returns (OPContractsManager.Implementations memory) { + function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { return contractsContainer.implementations(); } /// @notice Retrieves the blueprint addresses stored in this OPCM contract - function blueprints() public view returns (OPContractsManager.Blueprints memory) { + function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { return contractsContainer.blueprints(); } @@ -353,7 +296,7 @@ abstract contract OPContractsManagerBase { /// @param _output The deployment output containing proxy addresses function _registerPermissionedGameV2( OPContractsManager.DeployInput calldata _input, - OPContractsManager.Implementations memory _implementation, + IOPContractsManagerContractsContainer.Implementations memory _implementation, OPContractsManager.DeployOutput memory _output ) internal @@ -531,7 +474,9 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses - constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } + constructor(IOPContractsManagerContractsContainer _contractsContainer) + OPContractsManagerBase(_contractsContainer) + { } /// @notice Deploys a new dispute game and installs it into the DisputeGameFactory. Inputted /// game configs must be added in ascending GameType order. @@ -663,7 +608,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // Separate context to avoid stack too deep. { // Grab the blueprints once since we'll need it multiple times below. - OPContractsManager.Blueprints memory bps = getBlueprints(); + IOPContractsManagerContractsContainer.Blueprints memory bps = getBlueprints(); // Determine the contract name and blueprints for the game type. if ( @@ -861,7 +806,9 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { error OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate(); /// @param _contractsContainer The OPContractsManagerContractsContainer to use. - constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } + constructor(IOPContractsManagerContractsContainer _contractsContainer) + OPContractsManagerBase(_contractsContainer) + { } /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade @@ -870,7 +817,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { // Grab the implementations. - OPContractsManager.Implementations memory impls = getImplementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); // Loop through each chain and upgrade. for (uint256 i = 0; i < _opChainConfigs.length; i++) { @@ -901,7 +848,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _opChainConfig The configuration of the chain to upgrade. /// @param _l2ChainId The L2 chain ID of the chain to upgrade. function _doChainUpgrade( - OPContractsManager.Implementations memory _impls, + IOPContractsManagerContractsContainer.Implementations memory _impls, OPContractsManager.OpChainConfig memory _opChainConfig, uint256 _l2ChainId ) @@ -1103,7 +1050,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { } // Grab the implementations. - OPContractsManager.Implementations memory impls = getImplementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); // Grab the superchainConfig's proxyAdmin. IProxyAdmin _superchainProxyAdmin = IProxyAdmin(_superchainConfig.proxyAdmin()); @@ -1141,8 +1088,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) internal { - OPContractsManager.Blueprints memory bps = getBlueprints(); - OPContractsManager.Implementations memory impls = getImplementations(); + IOPContractsManagerContractsContainer.Blueprints memory bps = getBlueprints(); + IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); // Get the constructor params for the game IFaultDisputeGame.GameConstructorParams memory params = @@ -1206,7 +1153,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _opChainConfig The OP chain configuration function setNewPermissionedGameImplV2( - OPContractsManager.Implementations memory _impls, + IOPContractsManagerContractsContainer.Implementations memory _impls, uint256 _l2ChainId, IDisputeGame _disputeGame, IDelayedWETH _newDelayedWeth, @@ -1260,7 +1207,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _disputeGameFactory The dispute game factory proxy function setNewPermissionlessGameImplV2( - OPContractsManager.Implementations memory _impls, + IOPContractsManagerContractsContainer.Implementations memory _impls, uint256 _l2ChainId, Claim _newAbsolutePrestate, IDelayedWETH _newDelayedWeth, @@ -1319,7 +1266,9 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { /// @param deployOutput ABI-encoded output of the deployment. event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } + constructor(IOPContractsManagerContractsContainer _contractsContainer) + OPContractsManagerBase(_contractsContainer) + { } /// @notice Deploys a new OP Stack chain. /// @param _input The deploy input parameters for the deployment. @@ -1337,8 +1286,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { { assertValidInputs(_input); OPContractsManager.DeployOutput memory output; - OPContractsManager.Blueprints memory blueprint = getBlueprints(); - OPContractsManager.Implementations memory implementation = getImplementations(); + IOPContractsManagerContractsContainer.Blueprints memory blueprint = getBlueprints(); + IOPContractsManagerContractsContainer.Implementations memory implementation = getImplementations(); // -------- Deploy Chain Singletons -------- @@ -1827,7 +1776,9 @@ contract OPContractsManagerInteropMigrator is OPContractsManagerBase { } /// @param _contractsContainer Container of blueprints and implementations. - constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } + constructor(IOPContractsManagerContractsContainer _contractsContainer) + OPContractsManagerBase(_contractsContainer) + { } /// @notice Migrates one or more OP Stack chains to use the Super Root dispute games and shared /// dispute game contracts. @@ -2161,45 +2112,6 @@ contract OPContractsManager is ISemver { IDelayedWETH delayedWETHPermissionlessGameProxy; } - /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size - /// contracts, to reduce the code size of this factory contract. If it deployed full contracts - /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would - /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to - /// reduce the code size of this contract. - struct Blueprints { - address addressManager; - address proxy; - address proxyAdmin; - address l1ChugSplashProxy; - address resolvedDelegateProxy; - address permissionedDisputeGame1; - address permissionedDisputeGame2; - address permissionlessDisputeGame1; - address permissionlessDisputeGame2; - } - - /// @notice The latest implementation contracts for the OP Stack. - struct Implementations { - address superchainConfigImpl; - address protocolVersionsImpl; - address l1ERC721BridgeImpl; - address optimismPortalImpl; - address optimismPortalInteropImpl; - address ethLockboxImpl; - address systemConfigImpl; - address optimismMintableERC20FactoryImpl; - address l1CrossDomainMessengerImpl; - address l1StandardBridgeImpl; - address disputeGameFactoryImpl; - address anchorStateRegistryImpl; - address delayedWETHImpl; - address mipsImpl; - address faultDisputeGameV2Impl; - address permissionedDisputeGameV2Impl; - address superFaultDisputeGameImpl; - address superPermissionedDisputeGameImpl; - } - /// @notice The input required to identify a chain for upgrading, along with new prestate hashes struct OpChainConfig { ISystemConfig systemConfigProxy; @@ -2236,9 +2148,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 5.7.1 + /// @custom:semver 5.8.0 function version() public pure virtual returns (string memory) { - return "5.7.1"; + return "5.8.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; @@ -2251,6 +2163,8 @@ contract OPContractsManager is ISemver { OPContractsManagerStandardValidator public immutable opcmStandardValidator; + IOPContractsManagerV2 public immutable opcmV2; + /// @notice Address of the SuperchainConfig contract shared by all chains. ISuperchainConfig public immutable superchainConfig; @@ -2305,6 +2219,17 @@ contract OPContractsManager is ISemver { /// @notice Thrown if logic gated by a dev feature flag is incorrectly accessed. error InvalidDevFeatureAccess(bytes32 devFeature); + /// @notice Thrown when the PermissionedDisputeGame is not found. + error MissingPermissionedDisputeGame(); + + // -------- Events -------- + + /// @notice Legacy event, emitted when a new OP Stack chain is deployed. + /// @param l2ChainId Chain ID of the new chain. + /// @param deployer Address that deployed the chain. + /// @param deployOutput ABI-encoded output of the deployment. + event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); + // -------- Methods -------- constructor( @@ -2313,6 +2238,7 @@ contract OPContractsManager is ISemver { OPContractsManagerUpgrader _opcmUpgrader, OPContractsManagerInteropMigrator _opcmInteropMigrator, OPContractsManagerStandardValidator _opcmStandardValidator, + IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) { @@ -2323,11 +2249,13 @@ contract OPContractsManager is ISemver { _opcmDeployer.assertValidContractAddress(address(_opcmUpgrader)); _opcmDeployer.assertValidContractAddress(address(_opcmInteropMigrator)); _opcmDeployer.assertValidContractAddress(address(_opcmStandardValidator)); + _opcmDeployer.assertValidContractAddress(address(_opcmV2)); opcmGameTypeAdder = _opcmGameTypeAdder; opcmDeployer = _opcmDeployer; opcmUpgrader = _opcmUpgrader; opcmInteropMigrator = _opcmInteropMigrator; opcmStandardValidator = _opcmStandardValidator; + opcmV2 = _opcmV2; superchainConfig = _superchainConfig; protocolVersions = _protocolVersions; thisOPCM = this; @@ -2389,7 +2317,21 @@ contract OPContractsManager is ISemver { /// @param _input The deploy input parameters for the deployment. /// @return The deploy output values of the deployment. function deploy(DeployInput calldata _input) external virtual returns (DeployOutput memory) { - return opcmDeployer.deploy(_input, superchainConfig, msg.sender); + // If OPCM_V2 is enabled, use the new deploy function. + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + // Maintain legacy behavior. + if (_input.l2ChainId == block.chainid || _input.l2ChainId == 0) { + revert InvalidChainId(); + } + IOPContractsManagerV2.FullConfig memory cfg = _toFullConfig(_input, superchainConfig); + IOPContractsManagerV2.ChainContracts memory cts = opcmV2.deploy(cfg); + DeployOutput memory output = _toDeployOutput(cts); + // Emit the legacy event. + emit Deployed(_input.l2ChainId, msg.sender, abi.encode(output)); + return output; + } else { + return opcmDeployer.deploy(_input, superchainConfig, msg.sender); + } } /// @notice Upgrades a set of chains to the latest implementation contracts @@ -2400,8 +2342,17 @@ contract OPContractsManager is ISemver { function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs)); - _performDelegateCall(address(opcmUpgrader), data); + // If OPCM_V2 is enabled, use the new upgrade function. + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + for (uint256 i = 0; i < _opChainConfigs.length; i++) { + IOPContractsManagerV2.UpgradeInput memory upgradeInput = _toUpgradeInput(_opChainConfigs[i]); + bytes memory data = abi.encodeCall(IOPContractsManagerV2.upgrade, (upgradeInput)); + _performDelegateCall(address(opcmV2), data); + } + } else { + bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs)); + _performDelegateCall(address(opcmUpgrader), data); + } } /// @notice Upgrades the SuperchainConfig contract. @@ -2411,8 +2362,21 @@ contract OPContractsManager is ISemver { function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgradeSuperchainConfig, (_superchainConfig)); - _performDelegateCall(address(opcmUpgrader), data); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + bytes memory data = abi.encodeCall( + IOPContractsManagerV2.upgradeSuperchain, + ( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: _superchainConfig, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + }) + ) + ); + _performDelegateCall(address(opcmV2), data); + } else { + bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgradeSuperchainConfig, (_superchainConfig)); + _performDelegateCall(address(opcmUpgrader), data); + } } /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs @@ -2454,12 +2418,12 @@ contract OPContractsManager is ISemver { } /// @notice Returns the blueprint contract addresses. - function blueprints() public view returns (Blueprints memory) { + function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { return opcmDeployer.blueprints(); } /// @notice Returns the implementation contract addresses. - function implementations() public view returns (Implementations memory) { + function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { return opcmDeployer.implementations(); } @@ -2479,6 +2443,202 @@ contract OPContractsManager is ISemver { return opcmDeployer.isDevFeatureEnabled(_feature); } + /// @notice Helper that converts the legacy DeployInput into the new FullConfig. + /// @param _input The legacy DeployInput. + /// @param _superchainConfig The SuperchainConfig contract. + /// @return The new FullConfig. + function _toFullConfig( + DeployInput memory _input, + ISuperchainConfig _superchainConfig + ) + internal + pure + returns (IOPContractsManagerV2.FullConfig memory) + { + // Start building the full config. + IOPContractsManagerV2.FullConfig memory cfg; + + // Handle salt mixer. + cfg.saltMixer = _input.saltMixer; + + // Handle system roles. + cfg.proxyAdminOwner = _input.roles.opChainProxyAdminOwner; + cfg.systemConfigOwner = _input.roles.systemConfigOwner; + cfg.unsafeBlockSigner = _input.roles.unsafeBlockSigner; + cfg.batcher = _input.roles.batcher; + + // Handle L2 system configuration. + cfg.basefeeScalar = _input.basefeeScalar; + cfg.blobBasefeeScalar = _input.blobBasefeeScalar; + cfg.gasLimit = _input.gasLimit; + cfg.l2ChainId = _input.l2ChainId; + cfg.resourceConfig = Constants.DEFAULT_RESOURCE_CONFIG(); + + // Handle dispute game configs. + cfg.disputeGameConfigs = new IOPContractsManagerV2.DisputeGameConfig[](3); + cfg.disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: false, // NOTE: We currently disable FDG on first deploy. + initBond: 0, // NOTE: We currently disable FDG on first deploy. + gameType: GameTypes.CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: _input.disputeAbsolutePrestate }) + ) + }); + cfg.disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0, // NOTE: PDG gets a zero init bond for legacy deployments. + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: _input.disputeAbsolutePrestate, + proposer: _input.roles.proposer, + challenger: _input.roles.challenger + }) + ) + }); + cfg.disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: false, // NOTE: We currently disable CKDG on first deploy. + initBond: 0, // NOTE: We currently disable CKDG on first deploy. + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(0)) // NOTE: Incorrect prestate but disabled so doesn't matter. + }) + ) + }); + + // Handle anchor state configuration. + cfg.startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (Proposal)); + cfg.startingRespectedGameType = GameTypes.PERMISSIONED_CANNON; + + // Handle SuperchainConfig. + cfg.superchainConfig = _superchainConfig; + + // Return the full config. + return cfg; + } + + /// @notice Helper that converts the legacy OpChainConfig into the new UpgradeInput. + /// @param _opChainConfig The legacy OpChainConfig. + /// @return The new UpgradeInput. + function _toUpgradeInput(OpChainConfig memory _opChainConfig) + internal + view + returns (IOPContractsManagerV2.UpgradeInput memory) + { + // Ugly, but we need to do it this way. Legacy upgrade function does NOT upgrade all + // available dispute games. If we provided all available games as dispute game configs then + // they'd all get upgraded. When using the legacy upgrade function like this we only want + // to upgrade the games that are configured in the existing system. + + // Get the existing FaultDisputeGame and PermissionedDisputeGame addresses and their + // corresponding init bonds. + IDisputeGameFactory dgf = IDisputeGameFactory(_opChainConfig.systemConfigProxy.disputeGameFactory()); + address fdg = address(dgf.gameImpls(GameTypes.CANNON)); + address pdg = address(dgf.gameImpls(GameTypes.PERMISSIONED_CANNON)); + address ckg = address(dgf.gameImpls(GameTypes.CANNON_KONA)); + uint256 fdgBond = dgf.initBonds(GameTypes.CANNON); + uint256 pdgBond = dgf.initBonds(GameTypes.PERMISSIONED_CANNON); + uint256 ckgBond = dgf.initBonds(GameTypes.CANNON_KONA); + + // We can't support this case for legacy upgrades. + if (pdg == address(0)) { + revert MissingPermissionedDisputeGame(); + } + + // Maintaining legacy behavior for now, take the existing prestate but override if the user + // provides their own. Revert if prestate is still zero after both rules are applied. + Claim cannonPrestate = IPermissionedDisputeGame(pdg).absolutePrestate(); + if (_opChainConfig.cannonPrestate.raw() != bytes32(0)) { + cannonPrestate = _opChainConfig.cannonPrestate; + } + if (cannonPrestate.raw() == bytes32(0)) { + revert PrestateNotSet(); + } + + // Handle Cannon Kona prestate. + Claim cannonKonaPrestate = _opChainConfig.cannonKonaPrestate; + if (ckg != address(0)) { + // If Cannon Kona game exists, use its prestate. + if (cannonKonaPrestate.raw() == bytes32(0)) { + cannonKonaPrestate = IPermissionedDisputeGame(ckg).absolutePrestate(); + } + } + + // Build the dispute game configs. OPCMv2 requires that we account for all available game + // types so that we're being explicit about what we want and what we don't want. Game types + // that aren't enabled technically don't need valid game args but it's easier to just + // provide them in this particular instance. + IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new IOPContractsManagerV2.DisputeGameConfig[](3); + disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: fdg != address(0), + initBond: fdgBond, + gameType: GameTypes.CANNON, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }); + disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, // Guaranteed by the check above. + initBond: pdgBond, + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: IPermissionedDisputeGame(pdg).proposer(), + challenger: IPermissionedDisputeGame(pdg).challenger() + }) + ) + }); + disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + // Consistent with the OPCMv1 path, if the prestate is zero, don't enable the game. + enabled: cannonKonaPrestate.raw() != bytes32(0), + initBond: ckgBond, + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + }); + + IOPContractsManagerV2.ExtraInstruction[] memory extraInstructions = + new IOPContractsManagerV2.ExtraInstruction[](1); + extraInstructions[0] = + IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }); + + // Return the upgrade input. + return IOPContractsManagerV2.UpgradeInput({ + systemConfig: _opChainConfig.systemConfigProxy, + disputeGameConfigs: disputeGameConfigs, + extraInstructions: extraInstructions + }); + } + + /// @notice Helper that converts the new ChainContracts struct into the old DeployOutput. + /// @param _cts The new ChainContracts struct. + /// @return The old DeployOutput. + function _toDeployOutput(IOPContractsManagerV2.ChainContracts memory _cts) + internal + view + returns (DeployOutput memory) + { + return DeployOutput({ + opChainProxyAdmin: _cts.proxyAdmin, + addressManager: _cts.addressManager, + l1ERC721BridgeProxy: _cts.l1ERC721Bridge, + systemConfigProxy: _cts.systemConfig, + optimismMintableERC20FactoryProxy: _cts.optimismMintableERC20Factory, + l1StandardBridgeProxy: _cts.l1StandardBridge, + l1CrossDomainMessengerProxy: _cts.l1CrossDomainMessenger, + ethLockboxProxy: _cts.ethLockbox, + optimismPortalProxy: _cts.optimismPortal, + disputeGameFactoryProxy: _cts.disputeGameFactory, + anchorStateRegistryProxy: _cts.anchorStateRegistry, + faultDisputeGame: IFaultDisputeGame(address(_cts.disputeGameFactory.gameImpls(GameTypes.CANNON))), + permissionedDisputeGame: IPermissionedDisputeGame( + address(_cts.disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ), + delayedWETHPermissionedGameProxy: _cts.delayedWETH, + delayedWETHPermissionlessGameProxy: _cts.delayedWETH + }); + } + /// @notice Helper function to perform a delegatecall to a target contract /// @param _target The target contract address /// @param _data The calldata to send to the target diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol new file mode 100644 index 00000000000..c4e74bd4f65 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import { DevFeatures } from "src/libraries/DevFeatures.sol"; + +/// @title OPContractsManagerContractsContainer +/// @notice Helper contract that sits alongside the OPContractsManagerV2 contract. This contract +/// MUST be used to store the blueprints and implementations for OPCM because the +/// blueprints and implementations can't be stored as immutables and wouldn't be returned +/// correctly when the OPCM is DELEGATECALLed. +contract OPContractsManagerContractsContainer { + /// @notice Addresses of the blueprint contracts. + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + address permissionedDisputeGame1; + address permissionedDisputeGame2; + address permissionlessDisputeGame1; + address permissionlessDisputeGame2; + } + + /// @notice Addresses of the implementation contracts. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address optimismPortalInteropImpl; + address ethLockboxImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; + address superFaultDisputeGameImpl; + address superPermissionedDisputeGameImpl; + address storageSetterImpl; + } + + /// @notice Address of the blueprint contracts. This is internal because if it were public the + /// autogenerated getter method would return a tuple of addresses, but we want it to + /// return a struct. + Blueprints internal bps; + + /// @notice Address of the implementation contracts. This is internal because if it were public + /// the autogenerated getter method would return a tuple of addresses, but we want it to + /// return a struct. + Implementations internal impls; + + /// @notice Bitmap of development features that are enabled. We keep the development feature + /// bitmap here rather than in the actual OPCM because other contracts always get a + /// reference to this but not to the OPCM itself. + bytes32 public immutable devFeatureBitmap; + + /// @notice Thrown when a development feature is enabled in production. + error OPContractsManagerContractsContainer_DevFeatureInProd(); + + /// @param _blueprints The blueprint contract addresses. + /// @param _implementations The implementation contract addresses. + /// @param _devFeatureBitmap The bitmap of development features that are enabled. + constructor(Blueprints memory _blueprints, Implementations memory _implementations, bytes32 _devFeatureBitmap) { + bps = _blueprints; + impls = _implementations; + devFeatureBitmap = _devFeatureBitmap; + + // Development features MUST NOT be enabled on Mainnet. + if (block.chainid == 1 && !_isTestingEnvironment() && uint256(_devFeatureBitmap) != 0) { + revert OPContractsManagerContractsContainer_DevFeatureInProd(); + } + } + + /// @notice Returns the blueprints. + /// @return The blueprints. + function blueprints() public view returns (Blueprints memory) { + return bps; + } + + /// @notice Returns the implementations. + /// @return The implementations. + function implementations() public view returns (Implementations memory) { + return impls; + } + + /// @notice Returns the status of a development feature. Note that this function does not check + /// that the input feature represents a single feature and the bitwise AND operation + /// allows for multiple features to be enabled at once. Users should generally check + /// for only a single feature at a time. + /// @param _feature The feature to check. + /// @return True if the feature is enabled, false otherwise. + function isDevFeatureEnabled(bytes32 _feature) public view returns (bool) { + return DevFeatures.isDevFeatureEnabled(devFeatureBitmap, _feature); + } + + /// @notice Returns true if the contract is running in a testing environment. Checks that the + /// code for the address 0xbeefcafe is not zero, which is an address that should never + /// have any code in production environments but can be made to have code in tests. + /// @return True if the contract is running in a testing environment, false otherwise. + function _isTestingEnvironment() internal view returns (bool) { + return address(0xbeefcafe).code.length > 0; + } +} diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol new file mode 100644 index 00000000000..fa12169a7b0 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -0,0 +1,1167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { LibString } from "@solady/utils/LibString.sol"; +import { Blueprint } from "src/libraries/Blueprint.sol"; +import { Bytes } from "src/libraries/Bytes.sol"; +import { Claim, GameType, GameTypes, Proposal } from "src/dispute/lib/Types.sol"; +import { SemverComp } from "src/libraries/SemverComp.sol"; +import { Features } from "src/libraries/Features.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +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 { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; + +/// @title OPContractsManagerV2 +/// @notice OPContractsManagerV2 is an enhanced version of OPContractsManager. OPContractsManagerV2 +/// provides a simplified, minimized way of handling upgrades and deployments of OP Stack +/// chains. Each official release of the OP Stack contracts is packaged with its own unique +/// instance of OPContractsManagerV2 that handles the state transition for that particular +/// release. +/// @dev When adding a new dispute game type, if your dispute game requires configuration that +/// differs from configuration used by other dispute game types, you will need to add a new +/// configuration struct and then add parsing logic for that struct in the _makeGameArgs +/// function. You will also need to return the correct game implementation in _getGameImpl. +/// @dev When adding a net-new input, simply add the input to FullConfig and add the corresponding +/// logic for loading that input in _loadFullConfig. NOTE that when adding a completely new +/// input, users upgrading an existing chain will need to supply that input in the form of an +/// override as part of the UpgradeInput struct. +/// @dev If you were going to build a V3 of OPCM, you probably want to make this look a lot more +/// like Terraform. The V2 design is trending in the direction of being Terraform-like, but it +/// doesn't quite get there yet in an attempt to be a more incremental improvement over the V1 +/// design. Look at _execute, squint, and imagine that it can output an upgrade plan rather +/// than actually executing the upgrade, and then you'll see how it can be improved. +contract OPContractsManagerV2 { + /// @notice Configuration struct for the FaultDisputeGame. + struct FaultDisputeGameConfig { + Claim absolutePrestate; + } + + /// @notice Configuration struct for the PermissionedDisputeGame. + struct PermissionedDisputeGameConfig { + Claim absolutePrestate; + address proposer; + address challenger; + } + + /// @notice Generic dispute game configuration data. + struct DisputeGameConfig { + bool enabled; + uint256 initBond; + GameType gameType; + bytes gameArgs; + } + + /// @notice Contracts that represent the Superchain system. + struct SuperchainContracts { + ISuperchainConfig superchainConfig; + } + + /// @notice Contracts that represent the full chain system. + struct ChainContracts { + ISystemConfig systemConfig; + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + IL1CrossDomainMessenger l1CrossDomainMessenger; + IL1ERC721Bridge l1ERC721Bridge; + IL1StandardBridge l1StandardBridge; + IOptimismPortal optimismPortal; + IETHLockbox ethLockbox; + IOptimismMintableERC20Factory optimismMintableERC20Factory; + IDisputeGameFactory disputeGameFactory; + IAnchorStateRegistry anchorStateRegistry; + IDelayedWETH delayedWETH; + } + + /// @notice Full chain management configuration. + struct FullConfig { + // Basic deployment configuration. + string saltMixer; + ISuperchainConfig superchainConfig; + // System role configuration. + address proxyAdminOwner; + address systemConfigOwner; + address unsafeBlockSigner; + address batcher; + // Anchor state configuration. + Proposal startingAnchorRoot; + GameType startingRespectedGameType; + // L2 system configuration. + uint32 basefeeScalar; + uint32 blobBasefeeScalar; + uint64 gasLimit; + uint256 l2ChainId; + IResourceMetering.ResourceConfig resourceConfig; + // Dispute game configuration. + DisputeGameConfig[] disputeGameConfigs; + } + + /// @notice Struct that represents an additional instruction for an upgrade. Each upgrade has + /// its own set of extra upgrade instructions that may or may not be required. We use + /// this struct to keep the upgrade interface the same each time. + struct ExtraInstruction { + string key; + bytes data; + } + + /// @notice Partial input required for an upgrade. + struct UpgradeInput { + ISystemConfig systemConfig; + DisputeGameConfig[] disputeGameConfigs; + ExtraInstruction[] extraInstructions; + } + + /// @notice Input for upgrading Superchain contracts. + struct SuperchainUpgradeInput { + ISuperchainConfig superchainConfig; + ExtraInstruction[] extraInstructions; + } + + /// @notice Helper struct for deploying proxies, keeps code cleaner. + struct ProxyDeployArgs { + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + uint256 l2ChainId; + string saltMixer; + } + + /// @notice Emitted when a proxy is created by this contract. + /// @param name The name of the proxy. + /// @param proxy The address of the proxy. + event ProxyCreation(string name, address proxy); + + /// @notice Thrown when the SuperchainConfig needs to be upgraded. + error OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); + + /// @notice Thrown when an unsupported game type is provided. + error OPContractsManagerV2_UnsupportedGameType(); + + /// @notice Thrown when an invalid game config is provided. + error OPContractsManagerV2_InvalidGameConfigs(); + + /// @notice Thrown when an invalid upgrade input is provided. + error OPContractsManagerV2_InvalidUpgradeInput(); + + /// @notice Thrown when a proxy must be loaded but couldn't be. + error OPContractsManagerV2_ProxyMustLoad(); + + /// @notice Thrown when user attempts to downgrade a contract. + error OPContractsManagerV2_DowngradeNotAllowed(); + + /// @notice Thrown when a development feature is enabled in production. + error OPContractsManagerV2_DevFeatureInProd(); + + /// @notice Thrown when an invalid upgrade instruction is provided. + error OPContractsManagerV2_InvalidUpgradeInstruction(); + + /// @notice Thrown when a config load fails. + error OPContractsManagerV2_ConfigLoadFailed(string _name); + + /// @notice Container of blueprint and implementation contract addresses. + IOPContractsManagerContractsContainer public immutable contractsContainer; + + /// @param _contractsContainer The container of blueprint and implementation contract addresses. + constructor(IOPContractsManagerContractsContainer _contractsContainer) { + contractsContainer = _contractsContainer; + } + + /////////////////////////////////////////////////////////////////////////// + // PUBLIC CHAIN MANAGEMENT FUNCTIONS // + /////////////////////////////////////////////////////////////////////////// + + /// @notice Upgrades the Superchain contracts. Currently this is limited to the + /// SuperchainConfig contract, but may eventually expand to include other + /// Superchain-wide contracts. + /// @param _inp The input for the Superchain upgrade. + function upgradeSuperchain(SuperchainUpgradeInput memory _inp) external returns (SuperchainContracts memory) { + // NOTE: Since this function is very minimal and only upgrades the SuperchainConfig + // contract, not bothering to fully follow the pattern of the normal chain upgrade flow. + // If we expand the scope of this function to add other Superchain-wide contracts, we'll + // probably want to start following a similar pattern to the chain upgrade flow. + + // Upgrade the SuperchainConfig if it has changed. + _upgrade( + IProxyAdmin(_inp.superchainConfig.proxyAdmin()), + address(_inp.superchainConfig), + implementations().superchainConfigImpl, + abi.encodeCall(ISuperchainConfig.initialize, (_inp.superchainConfig.guardian())) + ); + + // Return the Superchain contracts. + return SuperchainContracts({ superchainConfig: _inp.superchainConfig }); + } + + /// @notice Deploys a new chain from full config. + /// @param _cfg The full chain deployment configuration. + /// @return The chain contracts. + function deploy(FullConfig memory _cfg) external returns (ChainContracts memory) { + // Deploy is the ONLY place where we allow the "ALL" permission for proxy deployment. + ExtraInstruction[] memory instructions = new ExtraInstruction[](1); + instructions[0] = ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") }); + + // Load the chain contracts. + ChainContracts memory cts = + _loadChainContracts(ISystemConfig(address(0)), _cfg.l2ChainId, _cfg.saltMixer, instructions); + + // Execute the deployment. + return _execute(_cfg, cts, true); + } + + /// @notice Upgrades a chain based on the upgrade input. + /// @param _inp The chain upgrade input. + /// @return The upgraded chain contracts. + function upgrade(UpgradeInput memory _inp) external returns (ChainContracts memory) { + // Assert that the upgrade instructions are valid. + // NOTE for developers: We use the concept of upgrade instructions to help maintain the + // principle that OPCM should be updated at the time that the feature is being developed + // and not again later for "maintenance" work. For example, if you are adding a net-new + // input to the SystemConfig contract, OPCMv1 would require that you also modify the + // UpgradeInput struct to include that input. You would then later need to go back and + // remove the input from the struct in some later upgrade. With OPCMv2, you can simply + // update the _loadFullConfig function to include your new input and have users supply an + // override for that particular upgrade (the upgrade won't work without the override) + // without any need to later come back and remove the input from the struct or ever even + // change the interface of OPCMv2 in the first place. + _assertValidUpgradeInstructions(_inp.extraInstructions); + + // Load the chain contracts. + ChainContracts memory cts = + _loadChainContracts(_inp.systemConfig, _inp.systemConfig.l2ChainId(), "salt mixer", _inp.extraInstructions); + + // Load the full config. + FullConfig memory cfg = _loadFullConfig(_inp, cts); + + // Execute the upgrade. + return _execute(cfg, cts, false); + } + + /////////////////////////////////////////////////////////////////////////// + // INTERNAL CHAIN MANAGEMENT FUNCTIONS // + /////////////////////////////////////////////////////////////////////////// + + /// @notice Asserts that the upgrade instructions array is valid. + /// @param _extraInstructions The extra upgrade instructions for the chain. + function _assertValidUpgradeInstructions(ExtraInstruction[] memory _extraInstructions) internal pure { + for (uint256 i = 0; i < _extraInstructions.length; i++) { + if ( + LibString.eq(_extraInstructions[i].key, "PermittedProxyDeployment") + && LibString.eq(string(_extraInstructions[i].data), "DelayedWETH") + ) { + // Unified DelayedWETH is being deployed for the first time. + // TODO:(#?????): Remove this allowance after unified DelayedWETH is deployed. + } else { + revert OPContractsManagerV2_InvalidUpgradeInstruction(); + } + } + } + + /// @notice Loads (or builds) the chain contracts from whatever exists. + /// @param _systemConfig The SystemConfig contract. + /// @param _l2ChainId The L2 chain ID. + /// @param _saltMixer The salt mixer for creating new proxies if needed. + /// @param _extraInstructions The extra upgrade instructions for the chain. + /// @return The chain contracts. + function _loadChainContracts( + ISystemConfig _systemConfig, + uint256 _l2ChainId, + string memory _saltMixer, + ExtraInstruction[] memory _extraInstructions + ) + internal + returns (ChainContracts memory) + { + // If the systemConfig is not initialized, we assume that the entire chain is new. + bool isInitialDeployment = address(_systemConfig) == address(0); + + // ProxyAdmin, AddressManager, and SystemConfig are the three special cases where we handle + // them differently than everything else because they're fundamental. Without these three + // contracts we can't get anything else. + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + ISystemConfig systemConfig; + if (isInitialDeployment) { + // Deploy the ProxyAdmin. + proxyAdmin = IProxyAdmin( + Blueprint.deployFrom( + blueprints().proxyAdmin, + _computeSalt(_l2ChainId, _saltMixer, "ProxyAdmin"), + abi.encode(address(this)) + ) + ); + + // Deploy the AddressManager. + addressManager = IAddressManager( + Blueprint.deployFrom( + blueprints().addressManager, _computeSalt(_l2ChainId, _saltMixer, "AddressManager"), abi.encode() + ) + ); + + // Set the AddressManager on the ProxyAdmin. + proxyAdmin.setAddressManager(addressManager); + + // Transfer ownership of the AddressManager to the ProxyAdmin. + addressManager.transferOwnership(address(proxyAdmin)); + + // Deploy the SystemConfig. + systemConfig = ISystemConfig( + Blueprint.deployFrom( + blueprints().proxy, + _computeSalt(_l2ChainId, _saltMixer, "SystemConfig"), + abi.encode(address(proxyAdmin)) + ) + ); + } else { + // Special case handling, don't bother with the standard flow. + proxyAdmin = _systemConfig.proxyAdmin(); + addressManager = proxyAdmin.addressManager(); + systemConfig = _systemConfig; + } + + // Set up the deploy args once, keeps the code cleaner. + ProxyDeployArgs memory proxyDeployArgs = ProxyDeployArgs({ + proxyAdmin: proxyAdmin, + addressManager: addressManager, + l2ChainId: _l2ChainId, + saltMixer: _saltMixer + }); + + // Now also load the portal, which contains the last few contract references. We do this + // before we set up the rest of the struct so we can reference it. + IOptimismPortal optimismPortal = IOptimismPortal( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.optimismPortal.selector, + proxyDeployArgs, + "OptimismPortal", + _extraInstructions + ) + ); + + // ETHLockbox is a special case. It's only to be used or deployed if the ETH_LOCKBOX + // feature is enabled. If this is an initial deployment, we'll deploy a proxy for it + // largely because the legacy code expects this proxy to be deployed on initial deployment + // though this doesn't mean we actually have to set it up and initialize it. If this is an + // upgrade, we'll load/deploy the proxy only if the system feature is set. + // NOTE: It's important that we don't try to load the proxy here if we're upgrading a chain + // that doesn't have the feature enabled. Chains that don't have the feature enabled will + // return address(0) for optimismPortal.ethLockbox(). If we try to load the proxy here, we + // will revert because the contract returns the zero address (reverting is the safe thing + // to do, so we want to revert, but that would break the upgrade flow). + IETHLockbox ethLockbox; + if (isInitialDeployment || systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { + ethLockbox = IETHLockbox( + _loadOrDeployProxy( + address(optimismPortal), + optimismPortal.ethLockbox.selector, + proxyDeployArgs, + "ETHLockbox", + _extraInstructions + ) + ); + } + + // For every other contract, we load-or-build the proxy. Each contract has a theoretical + // source where the address would be found. If the address isn't found there, we assume the + // address needs to be constructed. + // NOTE: We call _loadOrDeployProxy for each contract (rather than iterating over some sort + // of array) because (1) it's far easier to implement in Solidity and (2) it makes the code + // easier to understand. + return ChainContracts({ + systemConfig: systemConfig, + proxyAdmin: proxyAdmin, + addressManager: addressManager, + optimismPortal: optimismPortal, + ethLockbox: ethLockbox, + l1CrossDomainMessenger: IL1CrossDomainMessenger( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.l1CrossDomainMessenger.selector, + proxyDeployArgs, + "L1CrossDomainMessenger", + _extraInstructions + ) + ), + l1ERC721Bridge: IL1ERC721Bridge( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.l1ERC721Bridge.selector, + proxyDeployArgs, + "L1ERC721Bridge", + _extraInstructions + ) + ), + l1StandardBridge: IL1StandardBridge( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.l1StandardBridge.selector, + proxyDeployArgs, + "L1StandardBridge", + _extraInstructions + ) + ), + optimismMintableERC20Factory: IOptimismMintableERC20Factory( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.optimismMintableERC20Factory.selector, + proxyDeployArgs, + "OptimismMintableERC20Factory", + _extraInstructions + ) + ), + disputeGameFactory: IDisputeGameFactory( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.disputeGameFactory.selector, + proxyDeployArgs, + "DisputeGameFactory", + _extraInstructions + ) + ), + anchorStateRegistry: IAnchorStateRegistry( + _loadOrDeployProxy( + address(optimismPortal), + optimismPortal.anchorStateRegistry.selector, + proxyDeployArgs, + "AnchorStateRegistry", + _extraInstructions + ) + ), + delayedWETH: IDelayedWETH( + _loadOrDeployProxy( + address(systemConfig), + systemConfig.delayedWETH.selector, + proxyDeployArgs, + "DelayedWETH", + _extraInstructions + ) + ) + }); + } + + /// @notice Loads the full config from the upgrade input. + /// @param _upgradeInput The upgrade input. + /// @param _chainContracts The chain contracts. + /// @return The full config. + function _loadFullConfig( + UpgradeInput memory _upgradeInput, + ChainContracts memory _chainContracts + ) + internal + view + returns (FullConfig memory) + { + // Load the full config. + return FullConfig({ + saltMixer: string(bytes.concat(bytes32(uint256(uint160(address(_chainContracts.systemConfig)))))), + superchainConfig: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.superchainConfig.selector, + "overrides.cfg.superchainConfig", + _upgradeInput.extraInstructions + ), + (ISuperchainConfig) + ), + proxyAdminOwner: abi.decode( + _loadBytes( + address(_chainContracts.optimismPortal), + _chainContracts.optimismPortal.proxyAdminOwner.selector, + "overrides.cfg.proxyAdminOwner", + _upgradeInput.extraInstructions + ), + (address) + ), + systemConfigOwner: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.owner.selector, + "overrides.cfg.systemConfigOwner", + _upgradeInput.extraInstructions + ), + (address) + ), + unsafeBlockSigner: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.unsafeBlockSigner.selector, + "overrides.cfg.unsafeBlockSigner", + _upgradeInput.extraInstructions + ), + (address) + ), + batcher: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.batcherHash.selector, + "overrides.cfg.batcher", + _upgradeInput.extraInstructions + ), + (address) + ), + basefeeScalar: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.basefeeScalar.selector, + "overrides.cfg.basefeeScalar", + _upgradeInput.extraInstructions + ), + (uint32) + ), + blobBasefeeScalar: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.blobbasefeeScalar.selector, + "overrides.cfg.blobBasefeeScalar", + _upgradeInput.extraInstructions + ), + (uint32) + ), + gasLimit: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.gasLimit.selector, + "overrides.cfg.gasLimit", + _upgradeInput.extraInstructions + ), + (uint64) + ), + l2ChainId: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.l2ChainId.selector, + "overrides.cfg.l2ChainId", + _upgradeInput.extraInstructions + ), + (uint256) + ), + resourceConfig: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.resourceConfig.selector, + "overrides.cfg.resourceConfig", + _upgradeInput.extraInstructions + ), + (IResourceMetering.ResourceConfig) + ), + startingAnchorRoot: abi.decode( + _loadBytes( + address(_chainContracts.anchorStateRegistry), + _chainContracts.anchorStateRegistry.getAnchorRoot.selector, + "overrides.cfg.startingAnchorRoot", + _upgradeInput.extraInstructions + ), + (Proposal) + ), + startingRespectedGameType: abi.decode( + _loadBytes( + address(_chainContracts.anchorStateRegistry), + _chainContracts.anchorStateRegistry.respectedGameType.selector, + "overrides.cfg.startingRespectedGameType", + _upgradeInput.extraInstructions + ), + (GameType) + ), + disputeGameConfigs: _upgradeInput.disputeGameConfigs + }); + } + + /// @notice Validates the deployment/upgrade config. + /// @param _cfg The full config. + function _assertValidFullConfig(FullConfig memory _cfg) internal pure { + // Start validating the dispute game configs. Put allowed game types here. + GameType[] memory validGameTypes = new GameType[](3); + validGameTypes[0] = GameTypes.CANNON; + validGameTypes[1] = GameTypes.PERMISSIONED_CANNON; + validGameTypes[2] = GameTypes.CANNON_KONA; + + // We must have a config for each valid game type. + if (_cfg.disputeGameConfigs.length != validGameTypes.length) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } + + // Simplest possible check, iterate over each provided config and confirm that it matches + // the game type array. This places a requirement on the user to order the configs properly + // but that's probably a good thing, keeps the config consistent. + for (uint256 i = 0; i < _cfg.disputeGameConfigs.length; i++) { + if (_cfg.disputeGameConfigs[i].gameType.raw() != validGameTypes[i].raw()) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } + } + + // We currently REQUIRE that the PermissionedDisputeGame is enabled. We may be able to + // remove this check at some point in the future if we stop making this assumption, but for + // now we explicitly assert that it is enabled. + if (!_cfg.disputeGameConfigs[1].enabled) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } + } + + /// @notice Executes the deployment/upgrade action. + /// @param _cfg The full config. + /// @param _cts The chain contracts. + /// @param _isInitialDeployment Whether or not this is an initial deployment. + /// @return The chain contracts. + function _execute( + FullConfig memory _cfg, + ChainContracts memory _cts, + bool _isInitialDeployment + ) + internal + returns (ChainContracts memory) + { + // Validate the config. + _assertValidFullConfig(_cfg); + + // Load the implementations. + IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + + // Make sure the provided SuperchainConfig is up to date. + if (SemverComp.lt(_cfg.superchainConfig.version(), ISuperchainConfig(impls.superchainConfigImpl).version())) { + revert OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); + } + + // Update the SystemConfig. + // SystemConfig initializer is the only one large enough to require a separate function to + // avoid stack-too-deep errors. + _upgrade( + _cts.proxyAdmin, address(_cts.systemConfig), impls.systemConfigImpl, _makeSystemConfigInitArgs(_cfg, _cts) + ); + + // Update the OptimismPortal. + if (isDevFeatureEnabled(DevFeatures.OPTIMISM_PORTAL_INTEROP)) { + _upgrade( + _cts.proxyAdmin, + address(_cts.optimismPortal), + impls.optimismPortalInteropImpl, + abi.encodeCall( + IOptimismPortalInterop.initialize, (_cts.systemConfig, _cts.anchorStateRegistry, _cts.ethLockbox) + ) + ); + } else { + _upgrade( + _cts.proxyAdmin, + address(_cts.optimismPortal), + impls.optimismPortalImpl, + abi.encodeCall(IOptimismPortal.initialize, (_cts.systemConfig, _cts.anchorStateRegistry)) + ); + } + + // NOTE: Same general pattern, we call _upgrade for each contract rather than + // iterating over some sort of array because it's easier to implement and understand. + + // We upgrade/initialize the ETHLockbox if this is an initial deployment or if it's an + // upgrade and the ETH_LOCKBOX feature is enabled. + if (_isInitialDeployment || _cts.systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { + IOptimismPortal[] memory portals = new IOptimismPortal[](1); + portals[0] = _cts.optimismPortal; + _upgrade( + _cts.proxyAdmin, + address(_cts.ethLockbox), + impls.ethLockboxImpl, + abi.encodeCall(IETHLockbox.initialize, (_cts.systemConfig, portals)) + ); + } + + // If interop was requested, also set the ETHLockbox feature and migrate liquidity into the + // ETHLockbox contract. + if (isDevFeatureEnabled(DevFeatures.OPTIMISM_PORTAL_INTEROP)) { + // If we haven't already enabled the ETHLockbox, enable it. + if (!_cts.systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { + _cts.systemConfig.setFeature(Features.ETH_LOCKBOX, true); + } + + // Migrate any ETH into the ETHLockbox. + IOptimismPortalInterop(payable(_cts.optimismPortal)).migrateLiquidity(); + } + + // Update the L1CrossDomainMessenger. + // NOTE: L1CrossDomainMessenger initializer is at slot 0, offset 20. + _upgrade( + _cts.proxyAdmin, + address(_cts.l1CrossDomainMessenger), + impls.l1CrossDomainMessengerImpl, + abi.encodeCall(IL1CrossDomainMessenger.initialize, (_cts.systemConfig, _cts.optimismPortal)), + bytes32(0), + 20 + ); + + // Update the L1StandardBridge. + _upgrade( + _cts.proxyAdmin, + address(_cts.l1StandardBridge), + impls.l1StandardBridgeImpl, + abi.encodeCall(IL1StandardBridge.initialize, (_cts.l1CrossDomainMessenger, _cts.systemConfig)) + ); + + // Update the L1ERC721Bridge. + _upgrade( + _cts.proxyAdmin, + address(_cts.l1ERC721Bridge), + impls.l1ERC721BridgeImpl, + abi.encodeCall(IL1ERC721Bridge.initialize, (_cts.l1CrossDomainMessenger, _cts.systemConfig)) + ); + + // Update the OptimismMintableERC20Factory. + _upgrade( + _cts.proxyAdmin, + address(_cts.optimismMintableERC20Factory), + impls.optimismMintableERC20FactoryImpl, + abi.encodeCall(IOptimismMintableERC20Factory.initialize, (address(_cts.l1StandardBridge))) + ); + + // Update the DisputeGameFactory. + _upgrade( + _cts.proxyAdmin, + address(_cts.disputeGameFactory), + impls.disputeGameFactoryImpl, + abi.encodeCall(IDisputeGameFactory.initialize, (address(this))) + ); + + // Update the DelayedWETH. + _upgrade( + _cts.proxyAdmin, + address(_cts.delayedWETH), + impls.delayedWETHImpl, + abi.encodeCall(IDelayedWETH.initialize, (_cts.systemConfig)) + ); + + // Update the AnchorStateRegistry. + _upgrade( + _cts.proxyAdmin, + address(_cts.anchorStateRegistry), + impls.anchorStateRegistryImpl, + abi.encodeCall( + IAnchorStateRegistry.initialize, + (_cts.systemConfig, _cts.disputeGameFactory, _cfg.startingAnchorRoot, _cfg.startingRespectedGameType) + ) + ); + + // Update the DisputeGame config and implementations. + // NOTE: We assert in _assertValidFullConfig that we have a configuration for all valid game + // types so we can be confident that we're setting/unsetting everything we care about. + for (uint256 i = 0; i < _cfg.disputeGameConfigs.length; i++) { + // Game implementation and arguments default to empty values. If the game is disabled, + // we'll use these empty values to unset the game in the factory. + IDisputeGame gameImpl = IDisputeGame(address(0)); + bytes memory gameArgs = bytes(""); + + // If the game is enabled, grab the implementation and craft the game arguments. + if (_cfg.disputeGameConfigs[i].enabled) { + gameImpl = _getGameImpl(_cfg.disputeGameConfigs[i].gameType); + gameArgs = _makeGameArgs(_cfg, _cts, _cfg.disputeGameConfigs[i]); + } + + // Only actually set the game if the implementation or arguments have changed. + if ( + _cts.disputeGameFactory.gameImpls(_cfg.disputeGameConfigs[i].gameType) != gameImpl + || Bytes.equal(_cts.disputeGameFactory.gameArgs(_cfg.disputeGameConfigs[i].gameType), gameArgs) + ) { + _cts.disputeGameFactory.setImplementation(_cfg.disputeGameConfigs[i].gameType, gameImpl, gameArgs); + } + } + + // If critical transfer is allowed, tranfer ownership of the DisputeGameFactory and + // ProxyAdmin to the PAO. During deployments, this means transferring ownership from the + // OPCM contract to the target PAO. During upgrades, this would theoretically mean + // transferring ownership from the existing PAO to itself, which would be a no-op. In an + // abundance of caution to prevent accidental unexpected transfers of ownership, we use a + // boolean flag to control whether this transfer is allowed which should ONLY be used for + // the initial deployment and no other management/upgrade action. + if (_isInitialDeployment) { + // Transfer ownership of the DisputeGameFactory to the proxyAdminOwner. + _cts.disputeGameFactory.transferOwnership(address(_cfg.proxyAdminOwner)); + + // Transfer ownership of the ProxyAdmin to the proxyAdminOwner. + _cts.proxyAdmin.transferOwnership(_cfg.proxyAdminOwner); + } + + // Return contracts as the execution output. + return _cts; + } + + /// @notice Helper for making the SystemConfig initializer arguments. This is the only + /// initializer that needs a helper function because we get stack-too-deep. + /// @param _cfg The full config. + /// @param _cts The chain contracts. + /// @return The SystemConfig initializer arguments. + function _makeSystemConfigInitArgs( + FullConfig memory _cfg, + ChainContracts memory _cts + ) + internal + pure + returns (bytes memory) + { + // Generate the SystemConfig addresses input. + ISystemConfig.Addresses memory addrs = ISystemConfig.Addresses({ + l1CrossDomainMessenger: address(_cts.l1CrossDomainMessenger), + l1ERC721Bridge: address(_cts.l1ERC721Bridge), + l1StandardBridge: address(_cts.l1StandardBridge), + optimismPortal: address(_cts.optimismPortal), + optimismMintableERC20Factory: address(_cts.optimismMintableERC20Factory), + delayedWETH: address(_cts.delayedWETH) + }); + + // Generate the initializer arguments. + return abi.encodeCall( + ISystemConfig.initialize, + ( + _cfg.systemConfigOwner, + _cfg.basefeeScalar, + _cfg.blobBasefeeScalar, + bytes32(uint256(uint160(_cfg.batcher))), + _cfg.gasLimit, + _cfg.unsafeBlockSigner, + _cfg.resourceConfig, + _chainIdToBatchInboxAddress(_cfg.l2ChainId), + addrs, + _cfg.l2ChainId, + _cfg.superchainConfig + ) + ); + } + + /// @notice Helper for retrieving dispute game implementations. + /// @param _gameType The game type to retrieve the implementation for. + /// @return The dispute game implementation. + function _getGameImpl(GameType _gameType) internal view returns (IDisputeGame) { + IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + if (_gameType.raw() == GameTypes.CANNON.raw()) { + return IDisputeGame(impls.faultDisputeGameV2Impl); + } else if (_gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + return IDisputeGame(impls.permissionedDisputeGameV2Impl); + } else if (_gameType.raw() == GameTypes.CANNON_KONA.raw()) { + return IDisputeGame(impls.faultDisputeGameV2Impl); + } else { + // Since we assert in _assertValidFullConfig that we only have valid configs, this + // should never happen, but we'll be defensive and revert if it does. + revert OPContractsManagerV2_UnsupportedGameType(); + } + } + + /// @notice Helper for creating game constructor arguments. + /// @param _cfg Full chain config. + /// @param _cts Chain contracts. + /// @param _gcfg Configuration for the dispute game to create. + /// @return The game constructor arguments. + function _makeGameArgs( + FullConfig memory _cfg, + ChainContracts memory _cts, + DisputeGameConfig memory _gcfg + ) + internal + view + returns (bytes memory) + { + IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + if (_gcfg.gameType.raw() == GameTypes.CANNON.raw() || _gcfg.gameType.raw() == GameTypes.CANNON_KONA.raw()) { + FaultDisputeGameConfig memory parsedInputArgs = abi.decode(_gcfg.gameArgs, (FaultDisputeGameConfig)); + return abi.encodePacked( + parsedInputArgs.absolutePrestate, + impls.mipsImpl, + address(_cts.anchorStateRegistry), + address(_cts.delayedWETH), + _cfg.l2ChainId + ); + } else if (_gcfg.gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + PermissionedDisputeGameConfig memory parsedInputArgs = + abi.decode(_gcfg.gameArgs, (PermissionedDisputeGameConfig)); + return abi.encodePacked( + parsedInputArgs.absolutePrestate, + impls.mipsImpl, + address(_cts.anchorStateRegistry), + address(_cts.delayedWETH), + _cfg.l2ChainId, + parsedInputArgs.proposer, + parsedInputArgs.challenger + ); + } else { + // Since we assert in _assertValidFullConfig that we only have valid configs, this + // should never happen, but we'll be defensive and revert if it does. + revert OPContractsManagerV2_UnsupportedGameType(); + } + } + + /////////////////////////////////////////////////////////////////////////// + // PUBLIC UTILITY FUNCTIONS // + /////////////////////////////////////////////////////////////////////////// + + /// @notice Returns the blueprint contract addresses. + function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { + return contractsContainer.blueprints(); + } + + /// @notice Returns the implementation contract addresses. + function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { + return contractsContainer.implementations(); + } + + /// @notice Returns the status of a development feature. + /// @param _feature The feature to check. + /// @return True if the feature is enabled, false otherwise. + function isDevFeatureEnabled(bytes32 _feature) public view returns (bool) { + return contractsContainer.isDevFeatureEnabled(_feature); + } + + /////////////////////////////////////////////////////////////////////////// + // INTERNAL UTILITY FUNCTIONS // + /////////////////////////////////////////////////////////////////////////// + + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard + /// configuration's convention. This convention is + /// `versionByte || keccak256(bytes32(chainId))[:19]`, where || denotes concatenation, + /// versionByte is 0x00, and chainId is a uint256. + /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters + /// @param _l2ChainId The L2 chain ID to map to an L1 batch inbox address. + /// @return Chain ID mapped to an L1 batch inbox address. + function _chainIdToBatchInboxAddress(uint256 _l2ChainId) internal pure returns (address) { + bytes1 versionByte = 0x00; + bytes32 hashedChainId = keccak256(bytes.concat(bytes32(_l2ChainId))); + bytes19 first19Bytes = bytes19(hashedChainId); + return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); + } + + /// @notice Computes a unique salt for a contract deployment. + /// @param _l2ChainId The L2 chain ID of the chain being deployed to. + /// @param _saltMixer The salt mixer to use for the deployment. + /// @param _contractName The name of the contract to deploy. + /// @return The computed salt. + function _computeSalt( + uint256 _l2ChainId, + string memory _saltMixer, + string memory _contractName + ) + internal + pure + returns (bytes32) + { + return keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName)); + } + + /// @notice Helper function to check if a given instruction is present in a list of extra + /// upgrade instructions. + /// @param _instructions The list of extra upgrade instructions. + /// @param _instruction The instruction to check for. + /// @return True if the instruction is present, false otherwise. + function _hasInstruction( + ExtraInstruction[] memory _instructions, + ExtraInstruction memory _instruction + ) + internal + pure + returns (bool) + { + for (uint256 i = 0; i < _instructions.length; i++) { + if ( + LibString.eq(_instructions[i].key, _instruction.key) + && LibString.eq(string(_instructions[i].data), string(_instruction.data)) + ) { + return true; + } + } + return false; + } + + /// @notice Helper function to get an instruction by key. + /// @param _instructions The list of extra upgrade instructions. + /// @param _key The key of the instruction to get. + /// @return The instruction, or an empty instruction if the instruction is not found. + function _getInstructionByKey( + ExtraInstruction[] memory _instructions, + string memory _key + ) + internal + pure + returns (ExtraInstruction memory) + { + for (uint256 i = 0; i < _instructions.length; i++) { + if (LibString.eq(_instructions[i].key, _key)) { + return _instructions[i]; + } + } + return ExtraInstruction({ key: "", data: bytes("") }); + } + + /// @notice Helper function to load data from a source contract as bytes. + /// @param _source The source contract to load the data from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _instructions The extra upgrade instructions for the data load. + /// @return Data retrieved from the source contract. + function _loadBytes( + address _source, + bytes4 _selector, + string memory _name, + ExtraInstruction[] memory _instructions + ) + internal + view + returns (bytes memory) + { + // If an override exists for this load, return the override data. + ExtraInstruction memory overrideInstruction = _getInstructionByKey(_instructions, _name); + if (bytes(overrideInstruction.key).length > 0) { + return overrideInstruction.data; + } + + // Otherwise, load the data from the source contract. + (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); + if (!success) { + revert OPContractsManagerV2_ConfigLoadFailed(_name); + } + + // Return the loaded data. + return result; + } + + /// @notice Attempts to load a proxy from a source function where the proxy should be found. If + /// the proxy isn't found at the source, or the call to the source fails, we build a + /// new proxy instead. Calls to source contracts MUST NOT fail under any circumstances + /// other than the function not existing (which can happen in an upgrade scenario). + /// @param _source The source contract to load the proxy from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _args The basic arguments for the proxy deployment. + /// @param _contractName The name of the contract to deploy. + /// @param _instructions The extra upgrade instructions for the proxy deployment. + /// @return The address of the loaded or built proxy. + function _loadOrDeployProxy( + address _source, + bytes4 _selector, + ProxyDeployArgs memory _args, + string memory _contractName, + ExtraInstruction[] memory _instructions + ) + internal + returns (address payable) + { + bool loadCanFail = _hasInstruction( + _instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes(_contractName) }) + ) || _hasInstruction(_instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") })); + + // Try to load the proxy from the source. + (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); + + // If the load succeeded and the result is not a zero address, return the result. + if (success && abi.decode(result, (address)) != address(0)) { + return payable(abi.decode(result, (address))); + } else if (!loadCanFail) { + // Load not permitted to fail but did, revert. + revert OPContractsManagerV2_ProxyMustLoad(); + } + + // We've failed to load, but we allowed that failure. + // Deploy the right proxy depending on the contract name. + address ret; + if (LibString.eq(_contractName, "L1StandardBridge")) { + // L1StandardBridge is a special case ChugSplashProxy (legacy). + ret = Blueprint.deployFrom( + blueprints().l1ChugSplashProxy, + _computeSalt(_args.l2ChainId, _args.saltMixer, "L1StandardBridge"), + abi.encode(_args.proxyAdmin) + ); + + // ChugSplashProxy requires setting the proxy type on the ProxyAdmin. + _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.CHUGSPLASH); + } else if (LibString.eq(_contractName, "L1CrossDomainMessenger")) { + // L1CrossDomainMessenger is a special case ResolvedDelegateProxy (legacy). + string memory l1XdmName = "OVM_L1CrossDomainMessenger"; + ret = Blueprint.deployFrom( + blueprints().resolvedDelegateProxy, + _computeSalt(_args.l2ChainId, _args.saltMixer, "L1CrossDomainMessenger"), + abi.encode(_args.addressManager, l1XdmName) + ); + + // ResolvedDelegateProxy requires setting the proxy type on the ProxyAdmin. + _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.RESOLVED); + _args.proxyAdmin.setImplementationName(ret, l1XdmName); + } else { + // Otherwise this is a normal proxy. + ret = Blueprint.deployFrom( + blueprints().proxy, + _computeSalt(_args.l2ChainId, _args.saltMixer, _contractName), + abi.encode(_args.proxyAdmin) + ); + } + + // Emit the proxy creation event. + emit ProxyCreation(_contractName, ret); + + // Return the final deployment result. + return payable(ret); + } + + /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. + /// @param _proxyAdmin The proxy admin of the contract. + /// @param _target The target of the contract. + /// @param _implementation The implementation of the contract. + /// @param _data The data to call the initializer with. + function _upgrade(IProxyAdmin _proxyAdmin, address _target, address _implementation, bytes memory _data) internal { + _upgrade(_proxyAdmin, _target, _implementation, _data, bytes32(0), 0); + } + + /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. + /// @param _proxyAdmin The proxy admin of the contract. + /// @param _target The target of the contract. + /// @param _implementation The implementation of the contract. + /// @param _data The data to call the initializer with. + /// @param _slot The slot where the initialized value is located. + /// @param _offset The offset of the initializer value in the slot. + function _upgrade( + IProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data, + bytes32 _slot, + uint8 _offset + ) + internal + { + // Check to make sure that we're not downgrading. Downgrades aren't inherently dangerous + // but we also don't test for them so we don't really know if a specific downgrade will be + // dangerous or not. It's easier to just revert instead. + if ( + _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) + && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) + ) { + revert OPContractsManagerV2_DowngradeNotAllowed(); + } + + // Upgrade to StorageSetter. + _proxyAdmin.upgrade(payable(_target), address(implementations().storageSetterImpl)); + + // Otherwise, we need to reset the initialized slot and call the initializer. + // Reset the initialized slot by zeroing the single byte at `_offset` (from the right). + bytes32 current = IStorageSetter(_target).getBytes32(_slot); + uint256 mask = ~(uint256(0xff) << (uint256(_offset) * 8)); + IStorageSetter(_target).setBytes32(_slot, bytes32(uint256(current) & mask)); + + // Upgrade to the implementation and call the initializer. + _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); + } + + /// @notice Returns true if the contract is running in a testing environment. Checks that the + /// code for the address 0xbeefcafe is not zero, which is an address that should never + /// have any code in production environments but can be made to have code in tests. + /// @return True if the contract is running in a testing environment, false otherwise. + function _isTestingEnvironment() internal view returns (bool) { + return address(0xbeefcafe).code.length > 0; + } +} diff --git a/packages/contracts-bedrock/src/libraries/DevFeatures.sol b/packages/contracts-bedrock/src/libraries/DevFeatures.sol index d1a25f41c32..33a372c63b0 100644 --- a/packages/contracts-bedrock/src/libraries/DevFeatures.sol +++ b/packages/contracts-bedrock/src/libraries/DevFeatures.sol @@ -26,6 +26,9 @@ library DevFeatures { bytes32 public constant CUSTOM_GAS_TOKEN = bytes32(0x0000000000000000000000000000000000000000000000000000000000001000); + /// @notice The feature that enables the OPContractsManagerV2 contract. + bytes32 public constant OPCM_V2 = bytes32(0x0000000000000000000000000000000000000000000000000000000000010000); + /// @notice Checks if a feature is enabled in a bitmap. Note that this function does not check /// that the input feature represents a single feature and the bitwise AND operation /// allows for multiple features to be enabled at once. Users should generally check diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 877ab5e9781..efcb411d06c 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -23,6 +23,7 @@ import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; import { Proposal, GameTypes } from "src/dispute/lib/Types.sol"; import { LibGameArgs } from "src/dispute/lib/LibGameArgs.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { LibString } from "@solady/utils/LibString.sol"; // Interfaces import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; @@ -43,6 +44,8 @@ import { IOPContractsManagerUpgrader, IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; @@ -55,10 +58,11 @@ import { OPContractsManagerGameTypeAdder, OPContractsManagerDeployer, OPContractsManagerUpgrader, - OPContractsManagerContractsContainer, OPContractsManagerInteropMigrator, OPContractsManagerStandardValidator } from "src/L1/OPContractsManager.sol"; +import { OPContractsManagerV2 } from "src/L1/opcm/OPContractsManagerV2.sol"; +import { OPContractsManagerContractsContainer } from "src/L1/opcm/OPContractsManagerContractsContainer.sol"; import { DisputeGames } from "../setup/DisputeGames.sol"; import { IPermissionedDisputeGame } from "../../interfaces/dispute/IPermissionedDisputeGame.sol"; import { IProxy } from "../../interfaces/universal/IProxy.sol"; @@ -73,6 +77,7 @@ contract OPContractsManager_Harness is OPContractsManager { OPContractsManagerUpgrader _opcmUpgrader, OPContractsManagerInteropMigrator _opcmInteropMigrator, OPContractsManagerStandardValidator _opcmStandardValidator, + IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) @@ -82,6 +87,7 @@ contract OPContractsManager_Harness is OPContractsManager { _opcmUpgrader, _opcmInteropMigrator, _opcmStandardValidator, + _opcmV2, _superchainConfig, _protocolVersions ) @@ -114,20 +120,16 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { /// @notice Thrown when testing with an unsupported chain ID. error UnsupportedChainId(); - struct PreUpgradeState { - Claim cannonAbsolutePrestate; - Claim permissionedAbsolutePrestate; - IDelayedWETH permissionlessWethProxy; - IDelayedWETH permissionedCannonWethProxy; - } - uint256 l2ChainId; address upgrader; IOPContractsManager.OpChainConfig[] opChainConfigs; Claim cannonPrestate; Claim cannonKonaPrestate; string public opChain = Config.forkOpChain(); - PreUpgradeState preUpgradeState; + IOPContractsManagerV2.UpgradeInput internal v2UpgradeInput; + + /// @notice Special string constant used to indicate that we expect a revert without any data. + bytes public constant EXPECT_REVERT_WITHOUT_DATA = bytes("EXPECT_REVERT_WITHOUT_DATA"); function setUp() public virtual override { super.disableUpgradedFork(); @@ -157,28 +159,50 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { }) ); + // Prepare V2 upgrade input with ordered game configs [CANNON, PERMISSIONED_CANNON]. + address initialChallengerForV2 = permissionedGameChallenger(disputeGameFactory); + address initialProposerForV2 = permissionedGameProposer(disputeGameFactory); + v2UpgradeInput.systemConfig = systemConfig; + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON), + gameType: GameTypes.CANNON, + gameArgs: abi.encode(OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }) + ); + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + OPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: initialProposerForV2, + challenger: initialChallengerForV2 + }) + ) + }) + ); + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), + initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode(OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + }) + ); + + // Allow the DelayedWETH proxy to be (re)deployed during upgrades if it is missing. + v2UpgradeInput.extraInstructions.push( + IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) + ); + // Retrieve the l2ChainId, which was read from the superchain-registry, and saved in // Artifacts encoded as an address. l2ChainId = uint256(uint160(address(artifacts.mustGetAddress("L2ChainId")))); - delayedWETHPermissionedGameProxy = - IDelayedWETH(payable(artifacts.mustGetAddress("PermissionedDelayedWETHProxy"))); - permissionedDisputeGame = IPermissionedDisputeGame(address(artifacts.mustGetAddress("PermissionedDisputeGame"))); - IDisputeGameFactory dgf = IDisputeGameFactory(address(artifacts.mustGetAddress("DisputeGameFactoryProxy"))); - faultDisputeGame = IFaultDisputeGame(address(dgf.gameImpls(GameTypes.CANNON))); - delayedWeth = faultDisputeGame.weth(); - - // grab the pre-upgrade state - preUpgradeState = PreUpgradeState({ - cannonAbsolutePrestate: IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))) - .absolutePrestate(), - permissionedAbsolutePrestate: IPermissionedDisputeGame( - address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) - ).absolutePrestate(), - permissionlessWethProxy: delayedWeth, - permissionedCannonWethProxy: delayedWETHPermissionedGameProxy - }); - // Since this superchainConfig is already at the expected reinitializer version... // We do this to pass the reinitializer check when trying to upgrade the superchainConfig contract. @@ -336,81 +360,147 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { validationOverrides ); } - _runPostUpgradeSmokeTests(_opcm, opChainConfigs[0], initialChallenger, initialProposer); } - /// @notice Runs some smoke tests after an upgrade - function _runPostUpgradeSmokeTests( + /// @notice Helper function that runs an OPCM V2 upgrade, asserts that the upgrade was successful, + /// and runs post-upgrade smoke tests. + /// @param _opcm The OPCM contract to reference for shared components. + /// @param _delegateCaller The address of the delegate caller to use for superchain upgrade. + /// @param _revertBytes The bytes of the revert to expect. + function _runOpcmV2UpgradeAndChecks( IOPContractsManager _opcm, - IOPContractsManager.OpChainConfig memory _opChainConfig, - address _challenger, - address _proposer + address _delegateCaller, + bytes memory _revertBytes ) internal { - address expectedVm = address(_opcm.implementations().mipsImpl); + // Grab some values before we upgrade, to be checked later + address initialChallenger = permissionedGameChallenger(disputeGameFactory); + address initialProposer = permissionedGameProposer(disputeGameFactory); - Claim claim = Claim.wrap(bytes32(uint256(1))); - uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON); - vm.deal(address(_challenger), bondAmount); - (, uint256 rootBlockNumber) = optimismPortal2.anchorStateRegistry().getAnchorRoot(); - uint256 l2BlockNumber = rootBlockNumber + 1; + // Always start by upgrading the SuperchainConfig contract. + // Temporarily replace the superchainPAO with a DelegateCaller. + address superchainPAO = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))).owner(); + bytes memory superchainPAOCode = address(superchainPAO).code; + vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - bool expectCannonKonaGameSet = - isDevFeatureEnabled(DevFeatures.CANNON_KONA) && _opChainConfig.cannonKonaPrestate.raw() != bytes32(0); + // Execute the SuperchainConfig upgrade. + DelegateCaller(superchainPAO).dcForward( + address(_opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); - // Deploy live games and ensure they're configured correctly - GameType[] memory gameTypes = new GameType[](expectCannonKonaGameSet ? 3 : 2); - gameTypes[0] = GameTypes.PERMISSIONED_CANNON; - gameTypes[1] = GameTypes.CANNON; - if (expectCannonKonaGameSet) { - gameTypes[2] = GameTypes.CANNON_KONA; - } - for (uint256 i = 0; i < gameTypes.length; i++) { - GameType gt = gameTypes[i]; + // Reset the superchainPAO to the original code. + vm.etch(superchainPAO, superchainPAOCode); - bytes32 expectedAbsolutePrestate = _opChainConfig.cannonPrestate.raw(); - if (expectedAbsolutePrestate == bytes32(0)) { - expectedAbsolutePrestate = preUpgradeState.permissionedAbsolutePrestate.raw(); - } - if (expectCannonKonaGameSet && gt.raw() == GameTypes.CANNON_KONA.raw()) { - expectedAbsolutePrestate = _opChainConfig.cannonKonaPrestate.raw(); + // Get the OPCM V2 contract. + IOPContractsManagerV2 opcmV2 = IOPContractsManagerV2(address(_opcm.opcmV2())); + + // Temporarily replace the upgrader with a DelegateCaller. + bytes memory delegateCallerCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Expect the revert if one is specified. + if (_revertBytes.length > 0) { + if (keccak256(_revertBytes) == keccak256(EXPECT_REVERT_WITHOUT_DATA)) { + // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(); + } else { + vm.expectRevert(_revertBytes); } - assertEq(bondAmount, disputeGameFactory.initBonds(gt)); + } - vm.prank(_proposer, _proposer); - IPermissionedDisputeGame game = IPermissionedDisputeGame( - address(disputeGameFactory.create{ value: bondAmount }(gt, claim, abi.encode(l2BlockNumber))) - ); - (,,,, Claim rootClaim,,) = game.claimData(0); + // Execute the V2 chain upgrade via delegate caller. + DelegateCaller(_delegateCaller).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgrade, (v2UpgradeInput)) + ); - vm.assertEq(gt.raw(), game.gameType().raw()); - vm.assertEq(expectedAbsolutePrestate, game.absolutePrestate().raw()); - vm.assertEq(address(optimismPortal2.anchorStateRegistry()), address(game.anchorStateRegistry())); - vm.assertEq(l2ChainId, game.l2ChainId()); - vm.assertEq(302400, game.maxClockDuration().raw()); - vm.assertEq(10800, game.clockExtension().raw()); - vm.assertEq(73, game.maxGameDepth()); - vm.assertEq(30, game.splitDepth()); - vm.assertEq(l2BlockNumber, game.l2BlockNumber()); - vm.assertEq(expectedVm, address(game.vm())); - vm.assertEq(_proposer, game.gameCreator()); - vm.assertEq(claim.raw(), rootClaim.raw()); - vm.assertEq(blockhash(block.number - 1), game.l1Head().raw()); + // Return early if a revert was expected. Otherwise we'll get errors below. + if (_revertBytes.length > 0) { + return; + } - if (gt.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { - vm.assertEq(address(preUpgradeState.permissionedCannonWethProxy), address(game.weth())); - vm.assertEq(_challenger, game.challenger()); - vm.assertEq(_proposer, game.proposer()); + // Reset the upgrader to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // Less than 90% of the gas target of 2**24 (EIP-7825) to account for the gas used by using Safe. + uint256 fusakaLimit = 2 ** 24; + VmSafe.Gas memory gas = vm.lastCallGas(); + assertLt(gas.gasTotalUsed, fusakaLimit * 9 / 10, "Upgrade exceeds gas target of 90% of 2**24 (EIP-7825)"); + + // Reset the upgrader to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // We expect there to only be one chain config for these tests, you will have to rework + // this test if you add more. + assertEq(opChainConfigs.length, 1); + + // Coverage changes bytecode, so we get various errors. We can safely ignore the result of + // the standard validator in the coverage case, if the validator is failing in coverage + // then it will also fail in other CI tests (unless it's the expected issues, in which case + // we can safely skip). + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + return; + } + + // Create validationOverrides + IOPContractsManagerStandardValidator.ValidationOverrides memory validationOverrides = + IOPContractsManagerStandardValidator.ValidationOverrides({ + l1PAOMultisig: opChainConfigs[0].systemConfigProxy.proxyAdminOwner(), + challenger: initialChallenger + }); + + // Grab the validator before we do the error assertion because otherwise the assertion will + // try to apply to this function call instead. + IOPContractsManagerStandardValidator validator = _opcm.opcmStandardValidator(); + + // If the absolute prestate is zero, we will always get a PDDG-40,PLDG-40 error here in the + // standard validator. This happens because an absolute prestate of zero means that the + // user is requesting to use the existing prestate. We could avoid the error by grabbing + // the prestate from the actual contracts, but that doesn't actually give us any valuable + // checks. Easier to just expect the error in this case. + // We add the prefix of OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER because we use validationOverrides. + if (opChainConfigs[0].cannonPrestate.raw() == bytes32(0)) { + if ( + opChainConfigs[0].cannonKonaPrestate.raw() == bytes32(0) && isDevFeatureEnabled(DevFeatures.CANNON_KONA) + ) { + vm.expectRevert( + "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,PDDG-40,PLDG-40,CKDG-10" + ); } else { - vm.assertEq(address(preUpgradeState.permissionlessWethProxy), address(game.weth())); + vm.expectRevert( + "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,PDDG-40,PLDG-40" + ); } + } else if ( + opChainConfigs[0].cannonKonaPrestate.raw() == bytes32(0) && isDevFeatureEnabled(DevFeatures.CANNON_KONA) + ) { + vm.expectRevert("OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,CKDG-10"); } - if (!expectCannonKonaGameSet) { - assertEq(address(0), address(disputeGameFactory.gameImpls(GameTypes.CANNON_KONA))); - assertEq(0, disputeGameFactory.initBonds(GameTypes.CANNON_KONA)); - assertEq(0, disputeGameFactory.gameArgs(GameTypes.CANNON_KONA).length); + // Run the StandardValidator checks. + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInputDev({ + sysCfg: opChainConfigs[0].systemConfigProxy, + cannonPrestate: opChainConfigs[0].cannonPrestate.raw(), + cannonKonaPrestate: opChainConfigs[0].cannonKonaPrestate.raw(), + l2ChainID: l2ChainId, + proposer: initialProposer + }), + false, + validationOverrides + ); + } else { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInput({ + sysCfg: opChainConfigs[0].systemConfigProxy, + absolutePrestate: opChainConfigs[0].cannonPrestate.raw(), + l2ChainID: l2ChainId, + proposer: initialProposer + }), + false, + validationOverrides + ); } } @@ -444,6 +534,19 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { function runCurrentUpgrade(address _delegateCaller, bytes memory _revertBytes) public { _runOpcmUpgradeAndChecks(opcm, _delegateCaller, _revertBytes); } + + /// @notice Executes the current V2 upgrade and checks the results. + /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. + function runCurrentUpgradeV2(address _delegateCaller) public { + _runOpcmV2UpgradeAndChecks(opcm, _delegateCaller, bytes("")); + } + + /// @notice Executes the current V2 upgrade and expects reverts. + /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. + /// @param _revertBytes The bytes of the revert to expect. + function runCurrentUpgradeV2(address _delegateCaller, bytes memory _revertBytes) public { + _runOpcmV2UpgradeAndChecks(opcm, _delegateCaller, _revertBytes); + } } /// @title OPContractsManager_TestInit @@ -573,16 +676,17 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlag ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); IProxyAdmin superchainProxyAdmin = IProxyAdmin(makeAddr("superchainProxyAdmin")); - OPContractsManager.Blueprints memory emptyBlueprints; - OPContractsManager.Implementations memory emptyImpls; + OPContractsManagerContractsContainer.Blueprints memory emptyBlueprints; + OPContractsManagerContractsContainer.Implementations memory emptyImpls; vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); resolveFeaturesFromEnv(); - OPContractsManagerContractsContainer container = - new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, devFeatureBitmap); + IOPContractsManagerContractsContainer container = IOPContractsManagerContractsContainer( + address(new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, devFeatureBitmap)) + ); - OPContractsManager.Implementations memory __opcmImplementations = container.implementations(); + IOPContractsManagerContractsContainer.Implementations memory __opcmImplementations = container.implementations(); OPContractsManagerStandardValidator.Implementations memory opcmImplementations; assembly { opcmImplementations := __opcmImplementations @@ -596,6 +700,7 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlag _opcmStandardValidator: new OPContractsManagerStandardValidator( opcmImplementations, superchainConfigProxy, address(superchainProxyAdmin), challenger, 100, bytes32(0) ), + _opcmV2: IOPContractsManagerV2(address(new OPContractsManagerV2(container))), _superchainConfig: superchainConfigProxy, _protocolVersions: protocolVersionsProxy }); @@ -641,7 +746,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { // Get the v2 implementation address from OPCM - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); // Verify v2 implementation is registered in DisputeGameFactory address registeredImpl = @@ -688,7 +793,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { // Get the v2 implementation address from OPCM - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); // Verify implementation address matches permissionedDisputeGameV2Impl assertEq( @@ -1446,6 +1551,16 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { /// @title OPContractsManager_Upgrade_Test /// @notice Tests the `upgrade` function of the `OPContractsManager` contract. contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { + struct PreUpgradeState { + bytes32 absolutePrestate; + address pdgWeth; + address fdgWeth; + address proposer; + address challenger; + } + + bytes32 internal constant INITIALIZED_EVENT_SIG = keccak256("Initialized(uint8)"); + function setUp() public override { super.setUp(); @@ -1459,12 +1574,105 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { return Claim.wrap(gameArgs.absolutePrestate); } - function test_upgradeOPChainOnly_succeeds() public { + function test_upgrade_v1_succeeds() public { // Run the upgrade test and checks runCurrentUpgrade(upgrader); } - function test_verifyOpcmCorrectness_succeeds() public { + function test_upgrade_v1WithPostChecks_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + + // Capture pre-upgrade expectations locally + IOPContractsManager.OpChainConfig memory _opChainConfig = opChainConfigs[0]; + PreUpgradeState memory pre; + pre.proposer = permissionedGameProposer(disputeGameFactory); + pre.challenger = permissionedGameChallenger(disputeGameFactory); + pre.absolutePrestate = IPermissionedDisputeGame( + address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ).absolutePrestate().raw(); + pre.pdgWeth = address( + IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))).weth() + ); + pre.fdgWeth = address(IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))).weth()); + + bool expectCannonKonaGameSet = + isDevFeatureEnabled(DevFeatures.CANNON_KONA) && _opChainConfig.cannonKonaPrestate.raw() != bytes32(0); + + // Run the upgrade test and checks + runCurrentUpgrade(upgrader); + + // Post-upgrade smoke checks (inlined from _runPostUpgradeSmokeTests) + address expectedVm = address(opcm.implementations().mipsImpl); + + Claim claim = Claim.wrap(bytes32(uint256(1))); + uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON); + vm.deal(address(pre.challenger), bondAmount); + (, uint256 rootBlockNumber) = optimismPortal2.anchorStateRegistry().getAnchorRoot(); + uint256 l2BlockNumber = rootBlockNumber + 1; + + // Deploy live games and ensure they're configured correctly + GameType[] memory gameTypes = new GameType[](expectCannonKonaGameSet ? 3 : 2); + gameTypes[0] = GameTypes.PERMISSIONED_CANNON; + gameTypes[1] = GameTypes.CANNON; + if (expectCannonKonaGameSet) { + gameTypes[2] = GameTypes.CANNON_KONA; + } + for (uint256 i = 0; i < gameTypes.length; i++) { + GameType gt = gameTypes[i]; + + bytes32 expectedAbsolutePrestate = _opChainConfig.cannonPrestate.raw(); + if (expectedAbsolutePrestate == bytes32(0)) { + expectedAbsolutePrestate = pre.absolutePrestate; + } + if (expectCannonKonaGameSet && gt.raw() == GameTypes.CANNON_KONA.raw()) { + expectedAbsolutePrestate = _opChainConfig.cannonKonaPrestate.raw(); + } + assertEq(bondAmount, disputeGameFactory.initBonds(gt)); + + vm.prank(pre.proposer, pre.proposer); + IPermissionedDisputeGame game = IPermissionedDisputeGame( + address(disputeGameFactory.create{ value: bondAmount }(gt, claim, abi.encode(l2BlockNumber))) + ); + (,,,, Claim rootClaim,,) = game.claimData(0); + + vm.assertEq(gt.raw(), game.gameType().raw()); + vm.assertEq(expectedAbsolutePrestate, game.absolutePrestate().raw()); + vm.assertEq(address(optimismPortal2.anchorStateRegistry()), address(game.anchorStateRegistry())); + vm.assertEq(l2ChainId, game.l2ChainId()); + vm.assertEq(302400, game.maxClockDuration().raw()); + vm.assertEq(10800, game.clockExtension().raw()); + vm.assertEq(73, game.maxGameDepth()); + vm.assertEq(30, game.splitDepth()); + vm.assertEq(l2BlockNumber, game.l2BlockNumber()); + vm.assertEq(expectedVm, address(game.vm())); + vm.assertEq(pre.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(pre.challenger, game.challenger()); + vm.assertEq(pre.proposer, game.proposer()); + vm.assertEq(pre.pdgWeth, address(game.weth())); + } else { + vm.assertEq(pre.fdgWeth, address(game.weth())); + } + } + + if (!expectCannonKonaGameSet) { + assertEq(address(0), address(disputeGameFactory.gameImpls(GameTypes.CANNON_KONA))); + assertEq(0, disputeGameFactory.initBonds(GameTypes.CANNON_KONA)); + assertEq(0, disputeGameFactory.gameArgs(GameTypes.CANNON_KONA).length); + } + } + + function test_upgrade_v2_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Run the upgrade test and checks + runCurrentUpgradeV2(upgrader); + } + + function test_upgrade_withVerifyOPCM_succeeds() public { skipIfCoverage(); // Coverage changes bytecode and breaks the verification script. // Set up environment variables with the actual OPCM addresses for tests that need themqq @@ -1482,15 +1690,16 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_duplicateL2ChainId_succeeds() public { - // Deploy a new OPChain with the same L2 chain ID as the current OPChain + // Upgrade the current chain. + runCurrentUpgrade(upgrader); + + // Deploy a new chain with the same chain ID as the current chain. + // Should work without any issues because the salt mixer creates different addresses. Deploy deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); IOPContractsManager.DeployInput memory deployInput = deploy.getDeployInput(); deployInput.l2ChainId = l2ChainId; deployInput.saltMixer = "v2.0.0"; opcm.deploy(deployInput); - - // Try to upgrade the current OPChain - runCurrentUpgrade(upgrader); } /// @notice Tests that the absolute prestate can be overridden using the upgrade config. @@ -1653,6 +1862,21 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { opChainConfigs[0].cannonPrestate = Claim.wrap(bytes32(uint256(1))); opChainConfigs[0].cannonKonaPrestate = Claim.wrap(bytes32(0)); + // Same idea, but for V2 upgrades. + v2UpgradeInput.disputeGameConfigs[0].gameArgs = abi.encode( + OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(uint256(1))) }) + ); + v2UpgradeInput.disputeGameConfigs[1].gameArgs = abi.encode( + OPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(uint256(1))), + challenger: address(1), + proposer: address(1) + }) + ); + v2UpgradeInput.disputeGameConfigs[2].gameArgs = abi.encode( + OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(uint256(0))) }) + ); + // Run the upgrade. runCurrentUpgrade(upgrader); @@ -1691,7 +1915,7 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { assertNotEq(superchainProxyAdmin.owner(), delegateCaller); assertNotEq(proxyAdmin.owner(), delegateCaller); - runCurrentUpgrade(delegateCaller, bytes("Ownable: caller is not the owner")); + runCurrentUpgrade(delegateCaller, "Ownable: caller is not the owner"); } /// @notice Tests that upgrade reverts when absolutePrestate is zero and the existing game also @@ -1721,13 +1945,155 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { // Force the SuperchainConfig to return an obviously outdated version. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("0.0.0")); + // Error depends on if V1 or V2 is being used. + // nosemgrep: sol-style-use-abi-encodecall + bytes memory err = isDevFeatureEnabled(DevFeatures.OPCM_V2) + ? abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) + : abi.encodeWithSelector( + IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) + ); + // Try upgrading an OPChain without upgrading its superchainConfig. // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgrade( + runCurrentUpgrade(upgrader, err); + } + + /// @notice Tests that the V2 upgrade function reverts when the user does not provide a game + /// config for each valid game type. + function test_upgrade_missingGameConfigs_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Delete the Permissionless game configuration. + delete v2UpgradeInput.disputeGameConfigs[1]; + + // Expect upgrade to revert. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the user provides the game configs + /// in the wrong order. + function test_upgrade_wrongGameConfigOrder_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Swap the game config order. + IOPContractsManagerV2.DisputeGameConfig memory temp = v2UpgradeInput.disputeGameConfigs[0]; + v2UpgradeInput.disputeGameConfigs[0] = v2UpgradeInput.disputeGameConfigs[1]; + v2UpgradeInput.disputeGameConfigs[1] = temp; + + // Expect upgrade to revert due to invalid game config order. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the user wants to disable the + /// PermissionedDisputeGame. + function test_upgrade_disabledPermissionedGame_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Disable the PermissionedDisputeGame. + v2UpgradeInput.disputeGameConfigs[1].enabled = false; + + // Expect upgrade to revert due to missing game config. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function rejects the ALL sentinel in permitted proxy deployments. + function test_upgrade_allPermittedProxyDeployments_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + delete v2UpgradeInput.extraInstructions; + v2UpgradeInput.extraInstructions.push( + IOPContractsManagerV2.ExtraInstruction({ key: "PermitProxyDeployment", data: abi.encode("ALL") }) + ); + + // Expect upgrade to revert due to invalid upgrade input. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( upgrader, - abi.encodeWithSelector( - IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) - ) + abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts if a permitted proxy deployment is required but missing. + function test_upgrade_missingPermittedProxyDeployment_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + delete v2UpgradeInput.extraInstructions; + + // Simulate a missing DelayedWETH proxy so the upgrade path would need to deploy it. + // nosemgrep: sol-style-use-abi-encodecall + vm.mockCallRevert(address(systemConfig), abi.encodeWithSelector(ISystemConfig.delayedWETH.selector), ""); + + // Expect the upgrade to revert because the DelayedWETH proxy must load but the user did not permit + // redeployment. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns data that isn't an abi-encoded address. + /// @param _len Length of the data to generate. + function testFuzz_upgrade_proxyLoadBadReturn_reverts(uint8 _len) public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Ensure we do not produce a 32-byte payload, which would be interpreted as a valid + // abi-encoded address and could change the revert reason. + vm.assume(_len != 32); + + // Build an arbitrary bytes payload of length `_len`. + bytes memory bad = new bytes(_len); + for (uint256 i = 0; i < bad.length; i++) { + bad[i] = bytes1(uint8(0xAA)); + } + + // Mock the first proxy load source call to succeed but return a payload with a length + // not equal to 32 bytes, triggering OPContractsManagerV2_ProxyLoadMustLoad. + vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bad); + + // Expect a revert without any data (due to abi decoding failure). + runCurrentUpgradeV2(upgrader, EXPECT_REVERT_WITHOUT_DATA); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns the zero address but we asked it to load. + function test_upgrade_proxyMustLoadButZeroAddress_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Mock the first proxy load to succeed and return address(0) with 32 bytes, + // which triggers OPContractsManagerV2_ProxyMustLoad since _mustLoad is true in upgrade. + vm.mockCall( + address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), abi.encode(address(0)) + ); + + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns an error but we asked it to load. + function test_upgrade_proxyMustLoadButReverts_reverts() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Mock the first proxy load source to revert, which with _mustLoad=true triggers + // OPContractsManagerV2_ProxyMustLoad. + // nosemgrep: sol-style-use-abi-encodecall + vm.mockCallRevert(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bytes("")); + + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) ); } } @@ -1744,7 +2110,7 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U /// @notice Tests that the upgradeSuperchainConfig function succeeds when the superchainConfig is at the expected /// version and the delegate caller is the superchainProxyAdmin owner. function test_upgradeSuperchainConfig_succeeds() public { - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); @@ -1794,8 +2160,16 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U // Mock the SuperchainConfig to return a very large version. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("99.99.99")); + // Exact revert message depends on OPCM being enabled. + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + vm.expectRevert(IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector); + } else { + vm.expectRevert( + IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate.selector + ); + } + // Try to upgrade the SuperchainConfig contract again, should fail. - vm.expectRevert(IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate.selector); DelegateCaller(upgrader).dcForward( address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) ); @@ -2385,6 +2759,18 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames vm.expectEmit(true, true, true, false); // TODO precompute the expected `deployOutput`. emit Deployed(deployOPChainInput.l2ChainId, address(this), bytes("")); opcm.deploy(toOPCMDeployInput(deployOPChainInput)); + + // Try to keep the gas usage below 12m gas, under the EIP-7825 gas target of 2**24. We can + // technically go higher but this is a conservative bound, good signal that the gas usage + // is increasing. Only check this for the V2 OPCM since the V1 already exceeds this limit. + // Don't check for coverage or lite profile since they mess with gas usage. + VmSafe.Gas memory gas = vm.lastCallGas(); + if ( + isDevFeatureEnabled(DevFeatures.OPCM_V2) && !LibString.eq(Config.foundryProfile(), "lite") + && !vm.isContext(VmSafe.ForgeContext.Coverage) + ) { + assertLt(gas.gasTotalUsed, 12000000, "Deploy gas usage exceeds 12m gas"); + } } /// @notice Test that deploy sets the permissioned dispute game implementation @@ -2392,7 +2778,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames bool isV2 = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); // Sanity-check setup is consistent with devFeatures flag - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); address pdgImpl = address(impls.permissionedDisputeGameV2Impl); address fdgImpl = address(impls.faultDisputeGameV2Impl); if (isV2) { diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol index 0028b6db813..198150eb6f9 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; // Contracts -import { OPContractsManager, OPContractsManagerContractsContainer } from "src/L1/OPContractsManager.sol"; +import { OPContractsManagerContractsContainer } from "src/L1/opcm/OPContractsManagerContractsContainer.sol"; /// @title OPContractsManagerContractsContainer_Constructor_Test /// @notice Tests the constructor of the `OPContractsManagerContractsContainer` contract. @@ -16,13 +16,14 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan // Etch into the magic testing address. vm.etch(address(0xbeefcafe), hex"01"); - // Convert to proper OPCM type for construction. - OPContractsManager opcm2 = OPContractsManager(address(opcm)); + // Create empty blueprints and implementations. + OPContractsManagerContractsContainer.Blueprints memory blueprints; + OPContractsManagerContractsContainer.Implementations memory implementations; // Should not revert. OPContractsManagerContractsContainer container = new OPContractsManagerContractsContainer({ - _blueprints: opcm2.blueprints(), - _implementations: opcm2.implementations(), + _blueprints: blueprints, + _implementations: implementations, _devFeatureBitmap: _devFeatureBitmap }); @@ -39,15 +40,12 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan // Make sure magic address has no code. vm.etch(address(0xbeefcafe), bytes("")); - // Convert to proper OPCM type for construction. - OPContractsManager opcm2 = OPContractsManager(address(opcm)); - // Set the chain ID to 1. vm.chainId(1); - // Fetch ahead of time to avoid expectRevert applying to these functions by accident. - OPContractsManager.Blueprints memory blueprints = opcm2.blueprints(); - OPContractsManager.Implementations memory implementations = opcm2.implementations(); + // Create empty blueprints and implementations. + OPContractsManagerContractsContainer.Blueprints memory blueprints; + OPContractsManagerContractsContainer.Implementations memory implementations; // Should revert. vm.expectRevert( @@ -71,16 +69,17 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan // Make sure magic address has code. vm.etch(address(0xbeefcafe), hex"01"); - // Convert to proper OPCM type for construction. - OPContractsManager opcm2 = OPContractsManager(address(opcm)); + // Create empty blueprints and implementations. + OPContractsManagerContractsContainer.Blueprints memory blueprints; + OPContractsManagerContractsContainer.Implementations memory implementations; // Set the chain ID to 1. vm.chainId(1); // Should not revert. OPContractsManagerContractsContainer container = new OPContractsManagerContractsContainer({ - _blueprints: opcm2.blueprints(), - _implementations: opcm2.implementations(), + _blueprints: blueprints, + _implementations: implementations, _devFeatureBitmap: _devFeatureBitmap }); diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 295a167c13d..25c66729fe4 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -1280,16 +1280,24 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana function test_validate_delayedWETHInvalidVersion_succeeds() public { vm.mockCall(address(delayedWeth), abi.encodeCall(ISemver.version, ()), abi.encode("0.0.1")); - // One last mess here, during local tests delayedWeth refers to the contract attached to - // the FaultDisputeGame, but during fork tests it refers to the one attached to the - // PermissionedDisputeGame. We'll just branch based on the test type. - if (isForkTest()) { - assertEq("PDDG-DWETH-10", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-10,CKDG-DWETH-10", _validate(true)); + assertEq("PDDG-DWETH-10,PLDG-DWETH-10,CKDG-DWETH-10", _validate(true)); + } else { + assertEq("PDDG-DWETH-10,PLDG-DWETH-10", _validate(true)); + } + } else { + // One last mess here, during local tests delayedWeth refers to the contract attached to + // the FaultDisputeGame, but during fork tests it refers to the one attached to the + // PermissionedDisputeGame. We'll just branch based on the test type. + if (isForkTest()) { + assertEq("PDDG-DWETH-10", _validate(true)); } else { - assertEq("PLDG-DWETH-10", _validate(true)); + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-10,CKDG-DWETH-10", _validate(true)); + } else { + assertEq("PLDG-DWETH-10", _validate(true)); + } } } } @@ -1303,13 +1311,21 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana abi.encode(address(0xbad)) ); - if (isForkTest()) { - assertEq("PDDG-DWETH-20", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); + assertEq("PDDG-DWETH-20,PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); } else { - assertEq("PLDG-DWETH-20", _validate(true)); + assertEq("PDDG-DWETH-20,PLDG-DWETH-20", _validate(true)); + } + } else { + if (isForkTest()) { + assertEq("PDDG-DWETH-20", _validate(true)); + } else { + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); + } else { + assertEq("PLDG-DWETH-20", _validate(true)); + } } } } @@ -1321,13 +1337,21 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana address(delayedWeth), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0xbad)) ); - if (isForkTest()) { - assertEq("PDDG-DWETH-30", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-30,CKDG-DWETH-30", _validate(true)); + assertEq("PDDG-DWETH-30,PLDG-DWETH-30,CKDG-DWETH-30", _validate(true)); + } else { + assertEq("PDDG-DWETH-30,PLDG-DWETH-30", _validate(true)); + } + } else { + if (isForkTest()) { + assertEq("PDDG-DWETH-30", _validate(true)); } else { - assertEq("PLDG-DWETH-30", _validate(true)); + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-30,CKDG-DWETH-30", _validate(true)); + } else { + assertEq("PLDG-DWETH-30", _validate(true)); + } } } } @@ -1337,13 +1361,21 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana function test_validate_delayedWETHInvalidDelay_succeeds() public { vm.mockCall(address(delayedWeth), abi.encodeCall(IDelayedWETH.delay, ()), abi.encode(1000)); - if (isForkTest()) { - assertEq("PDDG-DWETH-40", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-40,CKDG-DWETH-40", _validate(true)); + assertEq("PDDG-DWETH-40,PLDG-DWETH-40,CKDG-DWETH-40", _validate(true)); + } else { + assertEq("PDDG-DWETH-40,PLDG-DWETH-40", _validate(true)); + } + } else { + if (isForkTest()) { + assertEq("PDDG-DWETH-40", _validate(true)); } else { - assertEq("PLDG-DWETH-40", _validate(true)); + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-40,CKDG-DWETH-40", _validate(true)); + } else { + assertEq("PLDG-DWETH-40", _validate(true)); + } } } } @@ -1353,13 +1385,21 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana function test_validate_delayedWETHInvalidSystemConfig_succeeds() public { vm.mockCall(address(delayedWeth), abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(address(0xbad))); - if (isForkTest()) { - assertEq("PDDG-DWETH-50", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-50,CKDG-DWETH-50", _validate(true)); + assertEq("PDDG-DWETH-50,PLDG-DWETH-50,CKDG-DWETH-50", _validate(true)); } else { - assertEq("PLDG-DWETH-50", _validate(true)); + assertEq("PDDG-DWETH-50,PLDG-DWETH-50", _validate(true)); + } + } else { + if (isForkTest()) { + assertEq("PDDG-DWETH-50", _validate(true)); + } else { + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-50,CKDG-DWETH-50", _validate(true)); + } else { + assertEq("PLDG-DWETH-50", _validate(true)); + } } } } @@ -1371,13 +1411,21 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana address(delayedWeth), abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(address(0xbad)) ); - if (isForkTest()) { - assertEq("PDDG-DWETH-60", _validate(true)); - } else { + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PLDG-DWETH-60,CKDG-DWETH-60", _validate(true)); + assertEq("PDDG-DWETH-60,PLDG-DWETH-60,CKDG-DWETH-60", _validate(true)); + } else { + assertEq("PDDG-DWETH-60,PLDG-DWETH-60", _validate(true)); + } + } else { + if (isForkTest()) { + assertEq("PDDG-DWETH-60", _validate(true)); } else { - assertEq("PLDG-DWETH-60", _validate(true)); + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + assertEq("PLDG-DWETH-60,CKDG-DWETH-60", _validate(true)); + } else { + assertEq("PLDG-DWETH-60", _validate(true)); + } } } } diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 3246d9de9b4..a09d277f471 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -15,6 +15,7 @@ import { Types } from "scripts/libraries/Types.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; contract DeployOPChain_TestBase is Test, FeatureFlags { DeploySuperchain deploySuperchain; @@ -194,7 +195,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { // Check dispute game deployments // Validate permissionedDisputeGame (PDG) address bool isDeployV2Games = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - IOPContractsManager.Implementations memory impls = opcm.implementations(); + IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); address expectedPDGAddress = isDeployV2Games ? impls.permissionedDisputeGameV2Impl : address(doo.permissionedDisputeGame); address actualPDGAddress = address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); diff --git a/packages/contracts-bedrock/test/setup/DisputeGames.sol b/packages/contracts-bedrock/test/setup/DisputeGames.sol index 4ec7493c70d..d1cb32e54b2 100644 --- a/packages/contracts-bedrock/test/setup/DisputeGames.sol +++ b/packages/contracts-bedrock/test/setup/DisputeGames.sol @@ -86,7 +86,7 @@ contract DisputeGames is FeatureFlags { revert DisputeGames_UnsupportedGameArg(_gameArg); } - function permissionedGameChallenger(IDisputeGameFactory _dgf) internal returns (address challenger_) { + function permissionedGameChallenger(IDisputeGameFactory _dgf) internal view returns (address challenger_) { GameType gameType = GameTypes.PERMISSIONED_CANNON; (bool gameArgsExist, bytes memory gameArgsData) = _getGameArgs(_dgf, gameType); if (gameArgsExist) { @@ -97,7 +97,7 @@ contract DisputeGames is FeatureFlags { } } - function permissionedGameProposer(IDisputeGameFactory _dgf) internal returns (address proposer_) { + function permissionedGameProposer(IDisputeGameFactory _dgf) internal view returns (address proposer_) { GameType gameType = GameTypes.PERMISSIONED_CANNON; (bool gameArgsExist, bytes memory gameArgsData) = _getGameArgs(_dgf, gameType); if (gameArgsExist) { @@ -183,17 +183,22 @@ contract DisputeGames is FeatureFlags { GameType _gameType ) private + view 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(""); + // Safe from issues with EIP150 since this is only used in the testing environment. + // eip150-safe + try _dgf.gameArgs(_gameType) returns (bytes memory gameArgsRet_) { + gameArgsExist_ = gameArgsRet_.length > 0; + gameArgs_ = gameArgsRet_; + } catch { + gameArgsExist_ = false; + gameArgs_ = bytes(""); + } } function _mockGameArg( diff --git a/packages/contracts-bedrock/test/setup/FeatureFlags.sol b/packages/contracts-bedrock/test/setup/FeatureFlags.sol index 22ba2765a19..2f71edac4f7 100644 --- a/packages/contracts-bedrock/test/setup/FeatureFlags.sol +++ b/packages/contracts-bedrock/test/setup/FeatureFlags.sol @@ -48,6 +48,15 @@ abstract contract FeatureFlags { console.log("Setup: DEV_FEATURE__CUSTOM_GAS_TOKEN is enabled"); devFeatureBitmap |= DevFeatures.CUSTOM_GAS_TOKEN; } + if (Config.devFeatureOpcmV2()) { + // WARNING: OPCMv2 also automatically implies DEPLOY_V2_DISPUTE_GAMES and CANNON_KONA. + console.log("Setup: DEV_FEATURE__OPCM_V2 is enabled"); + console.log("Setup: DEV_FEATURE__DEPLOY_V2_DISPUTE_GAMES is enabled"); + console.log("Setup: DEV_FEATURE__CANNON_KONA is enabled"); + devFeatureBitmap |= DevFeatures.OPCM_V2; + devFeatureBitmap |= DevFeatures.DEPLOY_V2_DISPUTE_GAMES; + devFeatureBitmap |= DevFeatures.CANNON_KONA; + } } /// @notice Enables a feature. From 84b829e53d5955f4a193496a74d92cada5a9483b Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 18 Nov 2025 17:59:58 -0500 Subject: [PATCH 02/17] fix: bug in deploying dispute games --- .../snapshots/abi/OPContractsManagerV2.json | 5 ---- .../src/L1/opcm/OPContractsManagerV2.sol | 23 ++++++++++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index 5e82490e819..0fa407e365d 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -668,11 +668,6 @@ "name": "OPContractsManagerV2_ConfigLoadFailed", "type": "error" }, - { - "inputs": [], - "name": "OPContractsManagerV2_DevFeatureInProd", - "type": "error" - }, { "inputs": [], "name": "OPContractsManagerV2_DowngradeNotAllowed", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index fa12169a7b0..1b80dd48b1f 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; // Libraries import { LibString } from "@solady/utils/LibString.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; -import { Bytes } from "src/libraries/Bytes.sol"; import { Claim, GameType, GameTypes, Proposal } from "src/dispute/lib/Types.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; import { Features } from "src/libraries/Features.sol"; @@ -167,9 +166,6 @@ contract OPContractsManagerV2 { /// @notice Thrown when user attempts to downgrade a contract. error OPContractsManagerV2_DowngradeNotAllowed(); - /// @notice Thrown when a development feature is enabled in production. - error OPContractsManagerV2_DevFeatureInProd(); - /// @notice Thrown when an invalid upgrade instruction is provided. error OPContractsManagerV2_InvalidUpgradeInstruction(); @@ -605,6 +601,11 @@ contract OPContractsManagerV2 { if (_cfg.disputeGameConfigs[i].gameType.raw() != validGameTypes[i].raw()) { revert OPContractsManagerV2_InvalidGameConfigs(); } + + // If the game is disabled, we must have a 0 init bond. + if (!_cfg.disputeGameConfigs[i].enabled && _cfg.disputeGameConfigs[i].initBond != 0) { + revert OPContractsManagerV2_InvalidGameConfigs(); + } } // We currently REQUIRE that the PermissionedDisputeGame is enabled. We may be able to @@ -770,13 +771,13 @@ contract OPContractsManagerV2 { gameArgs = _makeGameArgs(_cfg, _cts, _cfg.disputeGameConfigs[i]); } - // Only actually set the game if the implementation or arguments have changed. - if ( - _cts.disputeGameFactory.gameImpls(_cfg.disputeGameConfigs[i].gameType) != gameImpl - || Bytes.equal(_cts.disputeGameFactory.gameArgs(_cfg.disputeGameConfigs[i].gameType), gameArgs) - ) { - _cts.disputeGameFactory.setImplementation(_cfg.disputeGameConfigs[i].gameType, gameImpl, gameArgs); - } + // Set the game implementation and arguments. + // NOTE: If the game is disabled, we'll set the implementation to address(0) and the + // arguments to bytes(""), disabling the game. + _cts.disputeGameFactory.setImplementation(_cfg.disputeGameConfigs[i].gameType, gameImpl, gameArgs); + _cts.disputeGameFactory.setInitBond( + _cfg.disputeGameConfigs[i].gameType, _cfg.disputeGameConfigs[i].initBond + ); } // If critical transfer is allowed, tranfer ownership of the DisputeGameFactory and From d39bc5210abc46497ec491073cff0a1c96279642 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 00:04:23 -0500 Subject: [PATCH 03/17] fix: no more opcmv1 changes --- packages/contracts-bedrock/foundry.toml | 4 +- .../interfaces/L1/IOPContractsManager.sol | 71 ++- ...r.sol => IOPContractsManagerContainer.sol} | 2 +- .../L1/opcm/IOPContractsManagerV2.sol | 46 +- .../scripts/deploy/ChainAssertions.sol | 5 +- .../scripts/deploy/Deploy.s.sol | 143 ++++-- .../deploy/DeployImplementations.s.sol | 86 +++- .../deploy/ReadImplementationAddresses.s.sol | 3 +- .../snapshots/abi/OPContractsManager.json | 57 +-- .../abi/OPContractsManagerContainer.json | 374 +++++++++++++++ .../OPContractsManagerContractsContainer.json | 31 +- .../abi/OPContractsManagerDeployer.json | 13 +- .../abi/OPContractsManagerGameTypeAdder.json | 13 +- .../OPContractsManagerInteropMigrator.json | 13 +- .../abi/OPContractsManagerUpgrader.json | 13 +- .../snapshots/abi/OPContractsManagerV2.json | 39 +- .../snapshots/semver-lock.json | 4 +- .../OPContractsManagerContainer.json | 16 + .../OPContractsManagerContractsContainer.json | 10 +- .../src/L1/OPContractsManager.sol | 424 ++++++------------ ...er.sol => OPContractsManagerContainer.sol} | 4 +- .../src/L1/opcm/OPContractsManagerV2.sol | 30 +- .../test/L1/OPContractsManager.t.sol | 265 ++++++++--- ...OPContractsManagerContractsContainer.t.sol | 38 +- .../OPContractsManagerStandardValidator.t.sol | 152 +++++-- .../test/opcm/DeployOPChain.t.sol | 3 +- .../test/scripts/VerifyOPCM.t.sol | 1 + .../test/setup/ForkLive.s.sol | 127 +++++- .../contracts-bedrock/test/setup/Setup.sol | 9 +- 29 files changed, 1357 insertions(+), 639 deletions(-) rename packages/contracts-bedrock/interfaces/L1/opcm/{IOPContractsManagerContractsContainer.sol => IOPContractsManagerContainer.sol} (97%) create mode 100644 packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json rename packages/contracts-bedrock/src/L1/opcm/{OPContractsManagerContractsContainer.sol => OPContractsManagerContainer.sol} (98%) diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index cf6865723f1..b27dbe8207b 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -29,7 +29,7 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, { paths = "src/L1/opcm/OPContractsManagerV2.sol", optimizer_runs = 5000 }, - { paths = "src/L1/opcm/OPContractsManagerContractsContainer.sol", optimizer_runs = 5000 }, + { paths = "src/L1/opcm/OPContractsManagerContainer.sol", optimizer_runs = 5000 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 }, { paths = "src/universal/StorageSetter.sol", optimizer_runs = 5000 } @@ -159,7 +159,7 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/opcm/OPContractsManagerV2.sol", optimizer_runs = 0 }, - { paths = "src/L1/opcm/OPContractsManagerContractsContainer.sol", optimizer_runs = 0 }, + { paths = "src/L1/opcm/OPContractsManagerContainer.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 }, ] diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 110cb8ba1e3..f042cffa579 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -10,21 +10,36 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +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 { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; 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 { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; -import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +interface IOPContractsManagerContractsContainer { + error OPContractsManagerContractsContainer_DevFeatureInProd(); + + function __constructor__( + IOPContractsManager.Blueprints memory _blueprints, + IOPContractsManager.Implementations memory _implementations, + bytes32 _devFeatureBitmap + ) + external; + + function blueprints() external view returns (IOPContractsManager.Blueprints memory); + function implementations() external view returns (IOPContractsManager.Implementations memory); + function devFeatureBitmap() external view returns (bytes32); + function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); +} interface IOPContractsManagerGameTypeAdder { error OPContractsManagerGameTypeAdder_UnsupportedGameType(); @@ -166,6 +181,45 @@ interface IOPContractsManager { IDelayedWETH delayedWETHPermissionlessGameProxy; } + /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size + /// contracts, to reduce the code size of this factory contract. If it deployed full contracts + /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would + /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to + /// reduce the code size of this contract. + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + address permissionedDisputeGame1; + address permissionedDisputeGame2; + address permissionlessDisputeGame1; + address permissionlessDisputeGame2; + } + + /// @notice The latest implementation contracts for the OP Stack. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address optimismPortalInteropImpl; + address ethLockboxImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; + address superFaultDisputeGameImpl; + address superPermissionedDisputeGameImpl; + } + /// @notice The input required to identify a chain for upgrading. struct OpChainConfig { ISystemConfig systemConfigProxy; @@ -250,10 +304,6 @@ interface IOPContractsManager { error InvalidDevFeatureAccess(bytes32 devFeature); - error MissingPermissionedDisputeGame(); - - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - // -------- Methods -------- function __constructor__( @@ -262,7 +312,6 @@ interface IOPContractsManager { IOPContractsManagerUpgrader _opcmUpgrader, IOPContractsManagerInteropMigrator _opcmInteropMigrator, IOPContractsManagerStandardValidator _opcmStandardValidator, - IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) @@ -332,7 +381,7 @@ interface IOPContractsManager { function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address); /// @notice Returns the blueprint contract addresses. - function blueprints() external view returns (IOPContractsManagerContractsContainer.Blueprints memory); + function blueprints() external view returns (Blueprints memory); function opcmDeployer() external view returns (IOPContractsManagerDeployer); @@ -344,8 +393,6 @@ interface IOPContractsManager { function opcmStandardValidator() external view returns (IOPContractsManagerStandardValidator); - function opcmV2() external view returns (IOPContractsManagerV2); - /// @notice Retrieves the development feature bitmap stored in this OPCM contract /// @return The development feature bitmap. function devFeatureBitmap() external view returns (bytes32); @@ -356,5 +403,5 @@ interface IOPContractsManager { function isDevFeatureEnabled(bytes32 _feature) external view returns (bool); /// @notice Returns the implementation contract addresses. - function implementations() external view returns (IOPContractsManagerContractsContainer.Implementations memory); + function implementations() external view returns (Implementations memory); } diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol similarity index 97% rename from packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol rename to packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol index be27d1bfdfc..03d11b5304a 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface IOPContractsManagerContractsContainer { +interface IOPContractsManagerContainer { struct Blueprints { address addressManager; address proxy; diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index 17f680990a7..68bb0852ebf 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -19,7 +19,8 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; interface IOPContractsManagerV2 { /// @notice Configuration for the FaultDisputeGame. @@ -97,36 +98,6 @@ interface IOPContractsManagerV2 { ExtraInstruction[] extraInstructions; } - struct Blueprints { - address addressManager; - address proxy; - address proxyAdmin; - address l1ChugSplashProxy; - address resolvedDelegateProxy; - } - - struct Implementations { - address superchainConfigImpl; - address protocolVersionsImpl; - address l1ERC721BridgeImpl; - address optimismPortalImpl; - address optimismPortalInteropImpl; - address ethLockboxImpl; - address systemConfigImpl; - address optimismMintableERC20FactoryImpl; - address l1CrossDomainMessengerImpl; - address l1StandardBridgeImpl; - address disputeGameFactoryImpl; - address anchorStateRegistryImpl; - address delayedWETHImpl; - address mipsImpl; - address faultDisputeGameV2Impl; - address permissionedDisputeGameV2Impl; - address superFaultDisputeGameImpl; - address superPermissionedDisputeGameImpl; - address storageSetterImpl; - } - event ProxyCreation(string name, address proxy); error OPContractsManagerV2_InvalidGameConfigs(); @@ -149,15 +120,20 @@ interface IOPContractsManagerV2 { error UnexpectedPreambleData(bytes data); function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer + IOPContractsManagerContainer _contractsContainer, + IOPContractsManagerStandardValidator _standardValidator ) external; - function blueprints() external view returns (IOPContractsManagerContractsContainer.Blueprints memory); + function blueprints() external view returns (IOPContractsManagerContainer.Blueprints memory); + + function implementations() external view returns (IOPContractsManagerContainer.Implementations memory); + + function contractsContainer() external view returns (IOPContractsManagerContainer); - function implementations() external view returns (IOPContractsManagerContractsContainer.Implementations memory); + function standardValidator() external view returns (IOPContractsManagerStandardValidator); - function contractsContainer() external view returns (IOPContractsManagerContractsContainer); + function version() external view returns (string memory); /// @notice Upgrades Superchain-wide contracts. function upgradeSuperchain(SuperchainUpgradeInput memory _inp) diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 93a6181372e..3661c7602ff 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -36,7 +36,6 @@ import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -391,7 +390,7 @@ library ChainAssertions { require(address(_opcm.superchainConfig()) == _proxies.SuperchainConfig, "CHECK-OPCM-19"); // Ensure that the OPCM impls are correctly saved - IOPContractsManagerContractsContainer.Implementations memory impls = _opcm.implementations(); + IOPContractsManager.Implementations memory impls = _opcm.implementations(); require(impls.l1ERC721BridgeImpl == _impls.L1ERC721Bridge, "CHECK-OPCM-50"); require(impls.optimismPortalImpl == _impls.OptimismPortal, "CHECK-OPCM-60"); require(impls.systemConfigImpl == _impls.SystemConfig, "CHECK-OPCM-70"); @@ -405,7 +404,7 @@ library ChainAssertions { require(impls.protocolVersionsImpl == _impls.ProtocolVersions, "CHECK-OPCM-150"); // Verify that initCode is correctly set into the blueprints - IOPContractsManagerContractsContainer.Blueprints memory blueprints = _opcm.blueprints(); + IOPContractsManager.Blueprints memory blueprints = _opcm.blueprints(); Blueprint.Preamble memory addressManagerPreamble = Blueprint.parseBlueprintPreamble(address(blueprints.addressManager).code); require(keccak256(addressManagerPreamble.initcode) == keccak256(vm.getCode("AddressManager")), "CHECK-OPCM-160"); diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 9dbe43d6c30..e188770533a 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -24,9 +24,11 @@ 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"; +import { Constants } from "src/libraries/Constants.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; @@ -290,7 +292,11 @@ contract Deploy is Deployer { // Save the implementation addresses which are needed outside of this function or script. // When called in a fork test, this will overwrite the existing implementations. artifacts.save("MipsSingleton", address(dio.mipsSingleton)); - artifacts.save("OPContractsManager", address(dio.opcm)); + if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.OPCM_V2)) { + artifacts.save("OPContractsManagerV2", address(dio.opcmV2)); + } else { + 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)) { @@ -340,38 +346,38 @@ contract Deploy is Deployer { function deployOpChain() public { console.log("Deploying OP Chain"); - // Ensure that the requisite contracts are deployed - IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); - - IOPContractsManager.DeployInput memory deployInput = getDeployInput(); - IOPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); - // Store code in the Final system owner address so that it can be used for prank delegatecalls // Store "fe" opcode so that accidental calls to this address revert vm.etch(cfg.finalSystemOwner(), hex"fe"); - // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct - artifacts.save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); - artifacts.save("AddressManager", address(deployOutput.addressManager)); - artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); - artifacts.save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); - artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); - artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); - artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); - artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); - - // Fault Proof contracts - artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); - artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); - 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)); - } + if (!DevFeatures.isDevFeatureEnabled(cfg.devFeatureBitmap(), DevFeatures.OPCM_V2)) { + // Ensure that the requisite contracts are deployed + IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); + + IOPContractsManager.DeployInput memory deployInput = getDeployInput(); + IOPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); + + // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct + artifacts.save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); + artifacts.save("AddressManager", address(deployOutput.addressManager)); + artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); + artifacts.save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); + artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); + artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); + artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); + + // Fault Proof contracts + artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); + artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); + 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)); + } - if (!DevFeatures.isDevFeatureEnabled(opcm.devFeatureBitmap(), DevFeatures.OPCM_V2)) { // Check if the permissionless game implementation is already set IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); @@ -390,6 +396,30 @@ contract Deploy is Deployer { _implementation: delayedWETHImpl, _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) }); + } else { + // Ensure that the requisite contracts are deployed + IOPContractsManagerV2 opcm = IOPContractsManagerV2(artifacts.mustGetAddress("OPContractsManagerV2")); + + IOPContractsManagerV2.FullConfig memory deployInput = getDeployInputV2(); + IOPContractsManagerV2.ChainContracts memory deployOutput = opcm.deploy(deployInput); + + // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct + artifacts.save("ProxyAdmin", address(deployOutput.proxyAdmin)); + artifacts.save("AddressManager", address(deployOutput.addressManager)); + artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721Bridge)); + artifacts.save("SystemConfigProxy", address(deployOutput.systemConfig)); + artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20Factory)); + artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridge)); + artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessenger)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockbox)); + + // Fault Proof contracts + artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactory)); + artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETH)); + artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETH)); + artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistry)); + artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortal)); + artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortal)); } } @@ -451,4 +481,61 @@ contract Deploy is Deployer { useCustomGasToken: cfg.useCustomGasToken() }); } + + function getDeployInputV2() public view returns (IOPContractsManagerV2.FullConfig memory) { + IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new IOPContractsManagerV2.DisputeGameConfig[](3); + disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())) + }) + ) + }); + disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0, + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), + proposer: cfg.l2OutputOracleProposer(), + challenger: cfg.l2OutputOracleChallenger() + }) + ) + }); + disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())) + }) + ) + }); + + return IOPContractsManagerV2.FullConfig({ + saltMixer: "salt mixer", + superchainConfig: ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")), + proxyAdminOwner: cfg.finalSystemOwner(), + systemConfigOwner: cfg.finalSystemOwner(), + unsafeBlockSigner: cfg.p2pSequencerAddress(), + batcher: cfg.batchSenderAddress(), + startingAnchorRoot: Proposal({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2SequenceNumber: uint64(cfg.faultGameGenesisBlock()) + }), + startingRespectedGameType: GameTypes.PERMISSIONED_CANNON, + basefeeScalar: cfg.basefeeScalar(), + blobBasefeeScalar: cfg.blobbasefeeScalar(), + gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), + l2ChainId: cfg.l2ChainID(), + resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), + disputeGameConfigs: disputeGameConfigs + }); + } } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index f19db667858..28c63dd042d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -26,10 +26,11 @@ import { IOPContractsManagerDeployer, IOPContractsManagerUpgrader, IOPContractsManagerInteropMigrator, - IOPContractsManagerStandardValidator + IOPContractsManagerStandardValidator, + IOPContractsManagerContractsContainer } from "interfaces/L1/IOPContractsManager.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; @@ -71,6 +72,7 @@ contract DeployImplementations is Script { struct Output { IOPContractsManager opcm; IOPContractsManagerContractsContainer opcmContractsContainer; + IOPContractsManagerContainer opcmContainer; IOPContractsManagerGameTypeAdder opcmGameTypeAdder; IOPContractsManagerDeployer opcmDeployer; IOPContractsManagerUpgrader opcmUpgrader; @@ -151,13 +153,34 @@ contract DeployImplementations is Script { function createOPCMContract( Input memory _input, Output memory _output, - IOPContractsManagerContractsContainer.Blueprints memory _blueprints + IOPContractsManager.Blueprints memory _blueprints ) private returns (IOPContractsManager opcm_) { - IOPContractsManagerContractsContainer.Implementations memory implementations = - IOPContractsManagerContractsContainer.Implementations({ + IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ + superchainConfigImpl: address(_output.superchainConfigImpl), + protocolVersionsImpl: address(_output.protocolVersionsImpl), + l1ERC721BridgeImpl: address(_output.l1ERC721BridgeImpl), + optimismPortalImpl: address(_output.optimismPortalImpl), + optimismPortalInteropImpl: address(_output.optimismPortalInteropImpl), + ethLockboxImpl: address(_output.ethLockboxImpl), + systemConfigImpl: address(_output.systemConfigImpl), + optimismMintableERC20FactoryImpl: address(_output.optimismMintableERC20FactoryImpl), + l1CrossDomainMessengerImpl: address(_output.l1CrossDomainMessengerImpl), + l1StandardBridgeImpl: address(_output.l1StandardBridgeImpl), + disputeGameFactoryImpl: address(_output.disputeGameFactoryImpl), + anchorStateRegistryImpl: address(_output.anchorStateRegistryImpl), + delayedWETHImpl: address(_output.delayedWETHImpl), + mipsImpl: address(_output.mipsSingleton), + faultDisputeGameV2Impl: address(_output.faultDisputeGameV2Impl), + permissionedDisputeGameV2Impl: address(_output.permissionedDisputeGameV2Impl), + superFaultDisputeGameImpl: address(_output.superFaultDisputeGameImpl), + superPermissionedDisputeGameImpl: address(_output.superPermissionedDisputeGameImpl) + }); + + IOPContractsManagerContainer.Implementations memory implementationsV2 = IOPContractsManagerContainer + .Implementations({ superchainConfigImpl: address(_output.superchainConfigImpl), protocolVersionsImpl: address(_output.protocolVersionsImpl), l1ERC721BridgeImpl: address(_output.l1ERC721BridgeImpl), @@ -179,7 +202,21 @@ contract DeployImplementations is Script { storageSetterImpl: address(_output.storageSetterImpl) }); + // Convert blueprints to V2 blueprints + IOPContractsManagerContainer.Blueprints memory blueprintsV2 = IOPContractsManagerContainer.Blueprints({ + addressManager: _blueprints.addressManager, + proxy: _blueprints.proxy, + proxyAdmin: _blueprints.proxyAdmin, + l1ChugSplashProxy: _blueprints.l1ChugSplashProxy, + resolvedDelegateProxy: _blueprints.resolvedDelegateProxy, + permissionedDisputeGame1: _blueprints.permissionedDisputeGame1, + permissionedDisputeGame2: _blueprints.permissionedDisputeGame2, + permissionlessDisputeGame1: _blueprints.permissionlessDisputeGame1, + permissionlessDisputeGame2: _blueprints.permissionlessDisputeGame2 + }); + deployOPCMBPImplsContainer(_input, _output, _blueprints, implementations); + deployOPCMContainer(_input, _output, blueprintsV2, implementationsV2); deployOPCMGameTypeAdder(_output); deployOPCMDeployer(_input, _output); deployOPCMUpgrader(_output); @@ -223,7 +260,6 @@ contract DeployImplementations is Script { _output.opcmUpgrader, _output.opcmInteropMigrator, _output.opcmStandardValidator, - _output.opcmV2, _input.superchainConfigProxy, _input.protocolVersionsProxy ) @@ -234,7 +270,7 @@ contract DeployImplementations is Script { function deployOPContractsManager(Input memory _input, Output memory _output) private { // First we deploy the blueprints for the singletons deployed by OPCM. // forgefmt: disable-start - IOPContractsManagerContractsContainer.Blueprints memory blueprints; + IOPContractsManager.Blueprints memory blueprints; vm.startBroadcast(msg.sender); address checkAddress; (blueprints.addressManager, checkAddress) = DeployUtils.createDeterministicBlueprint(vm.getCode("AddressManager"), _salt); @@ -581,14 +617,14 @@ contract DeployImplementations is Script { function deployOPCMBPImplsContainer( Input memory _input, Output memory _output, - IOPContractsManagerContractsContainer.Blueprints memory _blueprints, - IOPContractsManagerContractsContainer.Implementations memory _implementations + IOPContractsManager.Blueprints memory _blueprints, + IOPContractsManager.Implementations memory _implementations ) private { IOPContractsManagerContractsContainer impl = IOPContractsManagerContractsContainer( DeployUtils.createDeterministic({ - _name: "OPContractsManagerContractsContainer.sol:OPContractsManagerContractsContainer", + _name: "OPContractsManager.sol:OPContractsManagerContractsContainer", _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManagerContractsContainer.__constructor__, @@ -602,6 +638,30 @@ contract DeployImplementations is Script { _output.opcmContractsContainer = impl; } + function deployOPCMContainer( + Input memory _input, + Output memory _output, + IOPContractsManagerContainer.Blueprints memory _blueprints, + IOPContractsManagerContainer.Implementations memory _implementations + ) + private + { + IOPContractsManagerContainer impl = IOPContractsManagerContainer( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerContainer.sol:OPContractsManagerContainer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IOPContractsManagerContainer.__constructor__, + (_blueprints, _implementations, _input.devFeatureBitmap) + ) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerContainerImpl"); + _output.opcmContainer = impl; + } + function deployOPCMGameTypeAdder(Output memory _output) private { IOPContractsManagerGameTypeAdder impl = IOPContractsManagerGameTypeAdder( DeployUtils.createDeterministic({ @@ -661,7 +721,7 @@ contract DeployImplementations is Script { function deployOPCMStandardValidator( Input memory _input, Output memory _output, - IOPContractsManagerContractsContainer.Implementations memory _implementations + IOPContractsManager.Implementations memory _implementations ) private { @@ -707,7 +767,9 @@ contract DeployImplementations is Script { DeployUtils.createDeterministic({ _name: "OPContractsManagerV2.sol:OPContractsManagerV2", _args: DeployUtils.encodeConstructor( - abi.encodeCall(IOPContractsManagerV2.__constructor__, (_output.opcmContractsContainer)) + abi.encodeCall( + IOPContractsManagerV2.__constructor__, (_output.opcmContainer, _output.opcmStandardValidator) + ) ), _salt: _salt }) diff --git a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol index a2350b4fdc3..23deddc11e7 100644 --- a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol @@ -7,7 +7,6 @@ import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; contract ReadImplementationAddresses is Script { struct Input { @@ -68,7 +67,7 @@ contract ReadImplementationAddresses is Script { output_.opcmInteropMigrator = address(opcm.opcmInteropMigrator()); output_.opcmStandardValidator = address(opcm.opcmStandardValidator()); - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); output_.mipsSingleton = impls.mipsImpl; output_.delayedWETH = impls.delayedWETHImpl; output_.ethLockbox = impls.ethLockboxImpl; diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 49f9a79c053..33f8880d27e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -26,11 +26,6 @@ "name": "_opcmStandardValidator", "type": "address" }, - { - "internalType": "contract IOPContractsManagerV2", - "name": "_opcmV2", - "type": "address" - }, { "internalType": "contract ISuperchainConfig", "name": "_superchainConfig", @@ -190,7 +185,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -525,14 +520,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } @@ -725,19 +715,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "opcmV2", - "outputs": [ - { - "internalType": "contract IOPContractsManagerV2", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "protocolVersions", @@ -1078,31 +1055,6 @@ "stateMutability": "pure", "type": "function" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "deployer", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "deployOutput", - "type": "bytes" - } - ], - "name": "Deployed", - "type": "event" - }, { "inputs": [ { @@ -1172,11 +1124,6 @@ "name": "LatestReleaseNotSet", "type": "error" }, - { - "inputs": [], - "name": "MissingPermissionedDisputeGame", - "type": "error" - }, { "inputs": [], "name": "OnlyDelegatecall", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json new file mode 100644 index 00000000000..29b2d118a3d --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json @@ -0,0 +1,374 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerContainer.Blueprints", + "name": "_blueprints", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalInteropImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "superFaultDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "superPermissionedDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerContainer.Implementations", + "name": "_implementations", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_devFeatureBitmap", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerContainer.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "devFeatureBitmap", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalInteropImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "superFaultDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "superPermissionedDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerContainer.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_feature", + "type": "bytes32" + } + ], + "name": "isDevFeatureEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPContractsManagerContractsContainer_DevFeatureInProd", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json index 18e515e8494..a3dc49e896f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -49,7 +49,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "_blueprints", "type": "tuple" }, @@ -144,14 +144,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct OPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "_implementations", "type": "tuple" }, @@ -164,6 +159,19 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "_isTestingEnvironment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "blueprints", @@ -216,7 +224,7 @@ "type": "address" } ], - "internalType": "struct OPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -332,14 +340,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct OPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index 1240f6485b7..a8798bdcb95 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -433,14 +433,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index 08885f3b479..16debf40454 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -168,7 +168,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -200,7 +200,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -316,14 +316,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json index 3ba286b20ef..5644c7edb07 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInteropMigrator.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -223,14 +223,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index cb435769d8b..2df9bcd824a 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -2,7 +2,7 @@ { "inputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "_contractsContainer", "type": "address" } @@ -75,7 +75,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct OPContractsManager.Blueprints", "name": "", "type": "tuple" } @@ -107,7 +107,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract OPContractsManagerContractsContainer", "name": "", "type": "address" } @@ -223,14 +223,9 @@ "internalType": "address", "name": "superPermissionedDisputeGameImpl", "type": "address" - }, - { - "internalType": "address", - "name": "storageSetterImpl", - "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct OPContractsManager.Implementations", "name": "", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index 0fa407e365d..e35e3b5e0ff 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -2,9 +2,14 @@ { "inputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContainer", "name": "_contractsContainer", "type": "address" + }, + { + "internalType": "contract IOPContractsManagerStandardValidator", + "name": "_standardValidator", + "type": "address" } ], "stateMutability": "nonpayable", @@ -62,7 +67,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Blueprints", + "internalType": "struct IOPContractsManagerContainer.Blueprints", "name": "", "type": "tuple" } @@ -75,7 +80,7 @@ "name": "contractsContainer", "outputs": [ { - "internalType": "contract IOPContractsManagerContractsContainer", + "internalType": "contract IOPContractsManagerContainer", "name": "", "type": "address" } @@ -404,7 +409,7 @@ "type": "address" } ], - "internalType": "struct IOPContractsManagerContractsContainer.Implementations", + "internalType": "struct IOPContractsManagerContainer.Implementations", "name": "", "type": "tuple" } @@ -431,6 +436,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "standardValidator", + "outputs": [ + { + "internalType": "contract IOPContractsManagerStandardValidator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -613,6 +631,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "anonymous": false, "inputs": [ diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 441adeef2c3..0287e55f792 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0xa46937e5ab1a9647a32b2d6452893384649984744b69747783a76f4898148e5f", - "sourceCodeHash": "0x77994c4adab71b526d17b264731f671c77334e221a46c59d3a6f54cbfb04ca77" + "initCodeHash": "0x7d38768f50c33e2029508c887b5bcf80119ac7d7b77b25cfb70a1b96e99f3bf9", + "sourceCodeHash": "0xfa88a187cf10da53170190b77edd2b366501302e9cf1cce06a03a1c7f1eaa1e5" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x0c8b15453d0f0bc5d9af07f104505e0bbb2b358f0df418289822fb73a8652b30", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json new file mode 100644 index 00000000000..368a144ddcb --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContainer.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "288", + "label": "bps", + "offset": 0, + "slot": "0", + "type": "struct OPContractsManagerContainer.Blueprints" + }, + { + "bytes": "608", + "label": "impls", + "offset": 0, + "slot": "9", + "type": "struct OPContractsManagerContainer.Implementations" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index 472149a29cd..9487d230af8 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -1,16 +1,16 @@ [ { "bytes": "288", - "label": "bps", + "label": "blueprint", "offset": 0, "slot": "0", - "type": "struct OPContractsManagerContractsContainer.Blueprints" + "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "608", - "label": "impls", + "bytes": "576", + "label": "implementation", "offset": 0, "slot": "9", - "type": "struct OPContractsManagerContractsContainer.Implementations" + "type": "struct OPContractsManager.Implementations" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 89541c0550a..bca3d8e7596 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -38,47 +38,104 @@ 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 { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; +import { ISystemConfig } from "../../interfaces/L1/ISystemConfig.sol"; + +contract OPContractsManagerContractsContainer { + /// @notice Addresses of the Blueprint contracts. + /// This is internal because if public the autogenerated getter method would return a tuple of + /// addresses, but we want it to return a struct. + OPContractsManager.Blueprints internal blueprint; + + /// @notice Addresses of the latest implementation contracts. + OPContractsManager.Implementations internal implementation; + + /// @notice Bitmap of development features that are enabled. We keep the development feature + /// bitmap here rather than in the actual OPCM because other contracts always get a + /// reference to this but not to the OPCM itself. + bytes32 public immutable devFeatureBitmap; + + /// @notice Thrown when a development feature is enabled in production. + error OPContractsManagerContractsContainer_DevFeatureInProd(); + + /// @param _blueprints The blueprint contract addresses. + /// @param _implementations The implementation contract addresses. + /// @param _devFeatureBitmap The bitmap of development features that are enabled. + constructor( + OPContractsManager.Blueprints memory _blueprints, + OPContractsManager.Implementations memory _implementations, + bytes32 _devFeatureBitmap + ) { + blueprint = _blueprints; + implementation = _implementations; + devFeatureBitmap = _devFeatureBitmap; + + // Development features MUST NOT be enabled on Mainnet. + if (block.chainid == 1 && !_isTestingEnvironment() && uint256(_devFeatureBitmap) != 0) { + revert OPContractsManagerContractsContainer_DevFeatureInProd(); + } + } + + function blueprints() public view returns (OPContractsManager.Blueprints memory) { + return blueprint; + } + + function implementations() public view returns (OPContractsManager.Implementations memory) { + return implementation; + } + + /// @notice Returns the status of a development feature. Note that this function does not check + /// that the input feature represents a single feature and the bitwise AND operation + /// allows for multiple features to be enabled at once. Users should generally check + /// for only a single feature at a time. + /// @param _feature The feature to check. + /// @return True if the feature is enabled, false otherwise. + function isDevFeatureEnabled(bytes32 _feature) public view returns (bool) { + return DevFeatures.isDevFeatureEnabled(devFeatureBitmap, _feature); + } + + /// @notice Returns true if the contract is running in a testing environment. Checks that the + /// code for the address 0xbeefcafe is not zero, which is an address that should never + /// have any code in production environments but can be made to have code in tests. + /// @return True if the contract is running in a testing environment, false otherwise. + function _isTestingEnvironment() public view returns (bool) { + return address(0xbeefcafe).code.length > 0; + } +} abstract contract OPContractsManagerBase { /// @notice Thrown when an invalid game type is used. error OPContractsManager_InvalidGameType(); /// @notice The blueprint contract addresses contract. - IOPContractsManagerContractsContainer public immutable contractsContainer; + OPContractsManagerContractsContainer public immutable contractsContainer; /// @notice The OPContractsManager contract that is currently being used. OPContractsManagerBase internal immutable thisOPCM; /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses - constructor(IOPContractsManagerContractsContainer _contractsContainer) { + constructor(OPContractsManagerContractsContainer _contractsContainer) { contractsContainer = _contractsContainer; thisOPCM = this; } /// @notice Retrieves the implementation addresses stored in this OPCM contract - function getImplementations() - internal - view - returns (IOPContractsManagerContractsContainer.Implementations memory) - { + function getImplementations() internal view returns (OPContractsManager.Implementations memory) { return thisOPCM.implementations(); } /// @notice Retrieves the blueprint addresses stored in this OPCM contract - function getBlueprints() internal view returns (IOPContractsManagerContractsContainer.Blueprints memory) { + function getBlueprints() internal view returns (OPContractsManager.Blueprints memory) { return thisOPCM.blueprints(); } /// @notice Retrieves the implementation addresses stored in this OPCM contract - function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { + function implementations() public view returns (OPContractsManager.Implementations memory) { return contractsContainer.implementations(); } /// @notice Retrieves the blueprint addresses stored in this OPCM contract - function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { + function blueprints() public view returns (OPContractsManager.Blueprints memory) { return contractsContainer.blueprints(); } @@ -296,7 +353,7 @@ abstract contract OPContractsManagerBase { /// @param _output The deployment output containing proxy addresses function _registerPermissionedGameV2( OPContractsManager.DeployInput calldata _input, - IOPContractsManagerContractsContainer.Implementations memory _implementation, + OPContractsManager.Implementations memory _implementation, OPContractsManager.DeployOutput memory _output ) internal @@ -474,9 +531,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses - constructor(IOPContractsManagerContractsContainer _contractsContainer) - OPContractsManagerBase(_contractsContainer) - { } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } /// @notice Deploys a new dispute game and installs it into the DisputeGameFactory. Inputted /// game configs must be added in ascending GameType order. @@ -608,7 +663,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // Separate context to avoid stack too deep. { // Grab the blueprints once since we'll need it multiple times below. - IOPContractsManagerContractsContainer.Blueprints memory bps = getBlueprints(); + OPContractsManager.Blueprints memory bps = getBlueprints(); // Determine the contract name and blueprints for the game type. if ( @@ -806,9 +861,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { error OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate(); /// @param _contractsContainer The OPContractsManagerContractsContainer to use. - constructor(IOPContractsManagerContractsContainer _contractsContainer) - OPContractsManagerBase(_contractsContainer) - { } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade @@ -817,7 +870,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @dev This function requires that each chain's superchainConfig is already upgraded. function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { // Grab the implementations. - IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); + OPContractsManager.Implementations memory impls = getImplementations(); // Loop through each chain and upgrade. for (uint256 i = 0; i < _opChainConfigs.length; i++) { @@ -848,7 +901,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _opChainConfig The configuration of the chain to upgrade. /// @param _l2ChainId The L2 chain ID of the chain to upgrade. function _doChainUpgrade( - IOPContractsManagerContractsContainer.Implementations memory _impls, + OPContractsManager.Implementations memory _impls, OPContractsManager.OpChainConfig memory _opChainConfig, uint256 _l2ChainId ) @@ -1050,7 +1103,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { } // Grab the implementations. - IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); + OPContractsManager.Implementations memory impls = getImplementations(); // Grab the superchainConfig's proxyAdmin. IProxyAdmin _superchainProxyAdmin = IProxyAdmin(_superchainConfig.proxyAdmin()); @@ -1088,8 +1141,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) internal { - IOPContractsManagerContractsContainer.Blueprints memory bps = getBlueprints(); - IOPContractsManagerContractsContainer.Implementations memory impls = getImplementations(); + OPContractsManager.Blueprints memory bps = getBlueprints(); + OPContractsManager.Implementations memory impls = getImplementations(); // Get the constructor params for the game IFaultDisputeGame.GameConstructorParams memory params = @@ -1153,7 +1206,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _opChainConfig The OP chain configuration function setNewPermissionedGameImplV2( - IOPContractsManagerContractsContainer.Implementations memory _impls, + OPContractsManager.Implementations memory _impls, uint256 _l2ChainId, IDisputeGame _disputeGame, IDelayedWETH _newDelayedWeth, @@ -1207,7 +1260,7 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _disputeGameFactory The dispute game factory proxy function setNewPermissionlessGameImplV2( - IOPContractsManagerContractsContainer.Implementations memory _impls, + OPContractsManager.Implementations memory _impls, uint256 _l2ChainId, Claim _newAbsolutePrestate, IDelayedWETH _newDelayedWeth, @@ -1266,9 +1319,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { /// @param deployOutput ABI-encoded output of the deployment. event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - constructor(IOPContractsManagerContractsContainer _contractsContainer) - OPContractsManagerBase(_contractsContainer) - { } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } /// @notice Deploys a new OP Stack chain. /// @param _input The deploy input parameters for the deployment. @@ -1286,8 +1337,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { { assertValidInputs(_input); OPContractsManager.DeployOutput memory output; - IOPContractsManagerContractsContainer.Blueprints memory blueprint = getBlueprints(); - IOPContractsManagerContractsContainer.Implementations memory implementation = getImplementations(); + OPContractsManager.Blueprints memory blueprint = getBlueprints(); + OPContractsManager.Implementations memory implementation = getImplementations(); // -------- Deploy Chain Singletons -------- @@ -1776,9 +1827,7 @@ contract OPContractsManagerInteropMigrator is OPContractsManagerBase { } /// @param _contractsContainer Container of blueprints and implementations. - constructor(IOPContractsManagerContractsContainer _contractsContainer) - OPContractsManagerBase(_contractsContainer) - { } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } /// @notice Migrates one or more OP Stack chains to use the Super Root dispute games and shared /// dispute game contracts. @@ -2112,6 +2161,45 @@ contract OPContractsManager is ISemver { IDelayedWETH delayedWETHPermissionlessGameProxy; } + /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size + /// contracts, to reduce the code size of this factory contract. If it deployed full contracts + /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would + /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to + /// reduce the code size of this contract. + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + address permissionedDisputeGame1; + address permissionedDisputeGame2; + address permissionlessDisputeGame1; + address permissionlessDisputeGame2; + } + + /// @notice The latest implementation contracts for the OP Stack. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address optimismPortalInteropImpl; + address ethLockboxImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + address faultDisputeGameV2Impl; + address permissionedDisputeGameV2Impl; + address superFaultDisputeGameImpl; + address superPermissionedDisputeGameImpl; + } + /// @notice The input required to identify a chain for upgrading, along with new prestate hashes struct OpChainConfig { ISystemConfig systemConfigProxy; @@ -2148,9 +2236,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 5.8.0 + /// @custom:semver 5.7.0 function version() public pure virtual returns (string memory) { - return "5.8.0"; + return "5.7.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; @@ -2163,8 +2251,6 @@ contract OPContractsManager is ISemver { OPContractsManagerStandardValidator public immutable opcmStandardValidator; - IOPContractsManagerV2 public immutable opcmV2; - /// @notice Address of the SuperchainConfig contract shared by all chains. ISuperchainConfig public immutable superchainConfig; @@ -2219,17 +2305,6 @@ contract OPContractsManager is ISemver { /// @notice Thrown if logic gated by a dev feature flag is incorrectly accessed. error InvalidDevFeatureAccess(bytes32 devFeature); - /// @notice Thrown when the PermissionedDisputeGame is not found. - error MissingPermissionedDisputeGame(); - - // -------- Events -------- - - /// @notice Legacy event, emitted when a new OP Stack chain is deployed. - /// @param l2ChainId Chain ID of the new chain. - /// @param deployer Address that deployed the chain. - /// @param deployOutput ABI-encoded output of the deployment. - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - // -------- Methods -------- constructor( @@ -2238,7 +2313,6 @@ contract OPContractsManager is ISemver { OPContractsManagerUpgrader _opcmUpgrader, OPContractsManagerInteropMigrator _opcmInteropMigrator, OPContractsManagerStandardValidator _opcmStandardValidator, - IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) { @@ -2249,13 +2323,11 @@ contract OPContractsManager is ISemver { _opcmDeployer.assertValidContractAddress(address(_opcmUpgrader)); _opcmDeployer.assertValidContractAddress(address(_opcmInteropMigrator)); _opcmDeployer.assertValidContractAddress(address(_opcmStandardValidator)); - _opcmDeployer.assertValidContractAddress(address(_opcmV2)); opcmGameTypeAdder = _opcmGameTypeAdder; opcmDeployer = _opcmDeployer; opcmUpgrader = _opcmUpgrader; opcmInteropMigrator = _opcmInteropMigrator; opcmStandardValidator = _opcmStandardValidator; - opcmV2 = _opcmV2; superchainConfig = _superchainConfig; protocolVersions = _protocolVersions; thisOPCM = this; @@ -2317,21 +2389,7 @@ contract OPContractsManager is ISemver { /// @param _input The deploy input parameters for the deployment. /// @return The deploy output values of the deployment. function deploy(DeployInput calldata _input) external virtual returns (DeployOutput memory) { - // If OPCM_V2 is enabled, use the new deploy function. - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - // Maintain legacy behavior. - if (_input.l2ChainId == block.chainid || _input.l2ChainId == 0) { - revert InvalidChainId(); - } - IOPContractsManagerV2.FullConfig memory cfg = _toFullConfig(_input, superchainConfig); - IOPContractsManagerV2.ChainContracts memory cts = opcmV2.deploy(cfg); - DeployOutput memory output = _toDeployOutput(cts); - // Emit the legacy event. - emit Deployed(_input.l2ChainId, msg.sender, abi.encode(output)); - return output; - } else { - return opcmDeployer.deploy(_input, superchainConfig, msg.sender); - } + return opcmDeployer.deploy(_input, superchainConfig, msg.sender); } /// @notice Upgrades a set of chains to the latest implementation contracts @@ -2342,17 +2400,8 @@ contract OPContractsManager is ISemver { function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - // If OPCM_V2 is enabled, use the new upgrade function. - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - for (uint256 i = 0; i < _opChainConfigs.length; i++) { - IOPContractsManagerV2.UpgradeInput memory upgradeInput = _toUpgradeInput(_opChainConfigs[i]); - bytes memory data = abi.encodeCall(IOPContractsManagerV2.upgrade, (upgradeInput)); - _performDelegateCall(address(opcmV2), data); - } - } else { - bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs)); - _performDelegateCall(address(opcmUpgrader), data); - } + bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs)); + _performDelegateCall(address(opcmUpgrader), data); } /// @notice Upgrades the SuperchainConfig contract. @@ -2362,21 +2411,8 @@ contract OPContractsManager is ISemver { function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - bytes memory data = abi.encodeCall( - IOPContractsManagerV2.upgradeSuperchain, - ( - IOPContractsManagerV2.SuperchainUpgradeInput({ - superchainConfig: _superchainConfig, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) - }) - ) - ); - _performDelegateCall(address(opcmV2), data); - } else { - bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgradeSuperchainConfig, (_superchainConfig)); - _performDelegateCall(address(opcmUpgrader), data); - } + bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgradeSuperchainConfig, (_superchainConfig)); + _performDelegateCall(address(opcmUpgrader), data); } /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs @@ -2418,12 +2454,12 @@ contract OPContractsManager is ISemver { } /// @notice Returns the blueprint contract addresses. - function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { + function blueprints() public view returns (Blueprints memory) { return opcmDeployer.blueprints(); } /// @notice Returns the implementation contract addresses. - function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { + function implementations() public view returns (Implementations memory) { return opcmDeployer.implementations(); } @@ -2443,202 +2479,6 @@ contract OPContractsManager is ISemver { return opcmDeployer.isDevFeatureEnabled(_feature); } - /// @notice Helper that converts the legacy DeployInput into the new FullConfig. - /// @param _input The legacy DeployInput. - /// @param _superchainConfig The SuperchainConfig contract. - /// @return The new FullConfig. - function _toFullConfig( - DeployInput memory _input, - ISuperchainConfig _superchainConfig - ) - internal - pure - returns (IOPContractsManagerV2.FullConfig memory) - { - // Start building the full config. - IOPContractsManagerV2.FullConfig memory cfg; - - // Handle salt mixer. - cfg.saltMixer = _input.saltMixer; - - // Handle system roles. - cfg.proxyAdminOwner = _input.roles.opChainProxyAdminOwner; - cfg.systemConfigOwner = _input.roles.systemConfigOwner; - cfg.unsafeBlockSigner = _input.roles.unsafeBlockSigner; - cfg.batcher = _input.roles.batcher; - - // Handle L2 system configuration. - cfg.basefeeScalar = _input.basefeeScalar; - cfg.blobBasefeeScalar = _input.blobBasefeeScalar; - cfg.gasLimit = _input.gasLimit; - cfg.l2ChainId = _input.l2ChainId; - cfg.resourceConfig = Constants.DEFAULT_RESOURCE_CONFIG(); - - // Handle dispute game configs. - cfg.disputeGameConfigs = new IOPContractsManagerV2.DisputeGameConfig[](3); - cfg.disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: false, // NOTE: We currently disable FDG on first deploy. - initBond: 0, // NOTE: We currently disable FDG on first deploy. - gameType: GameTypes.CANNON, - gameArgs: abi.encode( - IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: _input.disputeAbsolutePrestate }) - ) - }); - cfg.disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, - initBond: 0, // NOTE: PDG gets a zero init bond for legacy deployments. - gameType: GameTypes.PERMISSIONED_CANNON, - gameArgs: abi.encode( - IOPContractsManagerV2.PermissionedDisputeGameConfig({ - absolutePrestate: _input.disputeAbsolutePrestate, - proposer: _input.roles.proposer, - challenger: _input.roles.challenger - }) - ) - }); - cfg.disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: false, // NOTE: We currently disable CKDG on first deploy. - initBond: 0, // NOTE: We currently disable CKDG on first deploy. - gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode( - IOPContractsManagerV2.FaultDisputeGameConfig({ - absolutePrestate: Claim.wrap(bytes32(0)) // NOTE: Incorrect prestate but disabled so doesn't matter. - }) - ) - }); - - // Handle anchor state configuration. - cfg.startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (Proposal)); - cfg.startingRespectedGameType = GameTypes.PERMISSIONED_CANNON; - - // Handle SuperchainConfig. - cfg.superchainConfig = _superchainConfig; - - // Return the full config. - return cfg; - } - - /// @notice Helper that converts the legacy OpChainConfig into the new UpgradeInput. - /// @param _opChainConfig The legacy OpChainConfig. - /// @return The new UpgradeInput. - function _toUpgradeInput(OpChainConfig memory _opChainConfig) - internal - view - returns (IOPContractsManagerV2.UpgradeInput memory) - { - // Ugly, but we need to do it this way. Legacy upgrade function does NOT upgrade all - // available dispute games. If we provided all available games as dispute game configs then - // they'd all get upgraded. When using the legacy upgrade function like this we only want - // to upgrade the games that are configured in the existing system. - - // Get the existing FaultDisputeGame and PermissionedDisputeGame addresses and their - // corresponding init bonds. - IDisputeGameFactory dgf = IDisputeGameFactory(_opChainConfig.systemConfigProxy.disputeGameFactory()); - address fdg = address(dgf.gameImpls(GameTypes.CANNON)); - address pdg = address(dgf.gameImpls(GameTypes.PERMISSIONED_CANNON)); - address ckg = address(dgf.gameImpls(GameTypes.CANNON_KONA)); - uint256 fdgBond = dgf.initBonds(GameTypes.CANNON); - uint256 pdgBond = dgf.initBonds(GameTypes.PERMISSIONED_CANNON); - uint256 ckgBond = dgf.initBonds(GameTypes.CANNON_KONA); - - // We can't support this case for legacy upgrades. - if (pdg == address(0)) { - revert MissingPermissionedDisputeGame(); - } - - // Maintaining legacy behavior for now, take the existing prestate but override if the user - // provides their own. Revert if prestate is still zero after both rules are applied. - Claim cannonPrestate = IPermissionedDisputeGame(pdg).absolutePrestate(); - if (_opChainConfig.cannonPrestate.raw() != bytes32(0)) { - cannonPrestate = _opChainConfig.cannonPrestate; - } - if (cannonPrestate.raw() == bytes32(0)) { - revert PrestateNotSet(); - } - - // Handle Cannon Kona prestate. - Claim cannonKonaPrestate = _opChainConfig.cannonKonaPrestate; - if (ckg != address(0)) { - // If Cannon Kona game exists, use its prestate. - if (cannonKonaPrestate.raw() == bytes32(0)) { - cannonKonaPrestate = IPermissionedDisputeGame(ckg).absolutePrestate(); - } - } - - // Build the dispute game configs. OPCMv2 requires that we account for all available game - // types so that we're being explicit about what we want and what we don't want. Game types - // that aren't enabled technically don't need valid game args but it's easier to just - // provide them in this particular instance. - IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerV2.DisputeGameConfig[](3); - disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: fdg != address(0), - initBond: fdgBond, - gameType: GameTypes.CANNON, - gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) - }); - disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, // Guaranteed by the check above. - initBond: pdgBond, - gameType: GameTypes.PERMISSIONED_CANNON, - gameArgs: abi.encode( - IOPContractsManagerV2.PermissionedDisputeGameConfig({ - absolutePrestate: cannonPrestate, - proposer: IPermissionedDisputeGame(pdg).proposer(), - challenger: IPermissionedDisputeGame(pdg).challenger() - }) - ) - }); - disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ - // Consistent with the OPCMv1 path, if the prestate is zero, don't enable the game. - enabled: cannonKonaPrestate.raw() != bytes32(0), - initBond: ckgBond, - gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) - }); - - IOPContractsManagerV2.ExtraInstruction[] memory extraInstructions = - new IOPContractsManagerV2.ExtraInstruction[](1); - extraInstructions[0] = - IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }); - - // Return the upgrade input. - return IOPContractsManagerV2.UpgradeInput({ - systemConfig: _opChainConfig.systemConfigProxy, - disputeGameConfigs: disputeGameConfigs, - extraInstructions: extraInstructions - }); - } - - /// @notice Helper that converts the new ChainContracts struct into the old DeployOutput. - /// @param _cts The new ChainContracts struct. - /// @return The old DeployOutput. - function _toDeployOutput(IOPContractsManagerV2.ChainContracts memory _cts) - internal - view - returns (DeployOutput memory) - { - return DeployOutput({ - opChainProxyAdmin: _cts.proxyAdmin, - addressManager: _cts.addressManager, - l1ERC721BridgeProxy: _cts.l1ERC721Bridge, - systemConfigProxy: _cts.systemConfig, - optimismMintableERC20FactoryProxy: _cts.optimismMintableERC20Factory, - l1StandardBridgeProxy: _cts.l1StandardBridge, - l1CrossDomainMessengerProxy: _cts.l1CrossDomainMessenger, - ethLockboxProxy: _cts.ethLockbox, - optimismPortalProxy: _cts.optimismPortal, - disputeGameFactoryProxy: _cts.disputeGameFactory, - anchorStateRegistryProxy: _cts.anchorStateRegistry, - faultDisputeGame: IFaultDisputeGame(address(_cts.disputeGameFactory.gameImpls(GameTypes.CANNON))), - permissionedDisputeGame: IPermissionedDisputeGame( - address(_cts.disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) - ), - delayedWETHPermissionedGameProxy: _cts.delayedWETH, - delayedWETHPermissionlessGameProxy: _cts.delayedWETH - }); - } - /// @notice Helper function to perform a delegatecall to a target contract /// @param _target The target contract address /// @param _data The calldata to send to the target diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol similarity index 98% rename from packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol rename to packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol index c4e74bd4f65..97f1cfcd3c7 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol @@ -4,12 +4,12 @@ pragma solidity 0.8.15; // Libraries import { DevFeatures } from "src/libraries/DevFeatures.sol"; -/// @title OPContractsManagerContractsContainer +/// @title OPContractsManagerContainer /// @notice Helper contract that sits alongside the OPContractsManagerV2 contract. This contract /// MUST be used to store the blueprints and implementations for OPCM because the /// blueprints and implementations can't be stored as immutables and wouldn't be returned /// correctly when the OPCM is DELEGATECALLed. -contract OPContractsManagerContractsContainer { +contract OPContractsManagerContainer { /// @notice Addresses of the blueprint contracts. struct Blueprints { address addressManager; diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 1b80dd48b1f..10b7d406fe4 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -28,7 +28,8 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; /// @title OPContractsManagerV2 /// @notice OPContractsManagerV2 is an enhanced version of OPContractsManager. OPContractsManagerV2 @@ -49,7 +50,7 @@ import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPCon /// doesn't quite get there yet in an attempt to be a more incremental improvement over the V1 /// design. Look at _execute, squint, and imagine that it can output an upgrade plan rather /// than actually executing the upgrade, and then you'll see how it can be improved. -contract OPContractsManagerV2 { +contract OPContractsManagerV2 is ISemver { /// @notice Configuration struct for the FaultDisputeGame. struct FaultDisputeGameConfig { Claim absolutePrestate; @@ -173,11 +174,22 @@ contract OPContractsManagerV2 { error OPContractsManagerV2_ConfigLoadFailed(string _name); /// @notice Container of blueprint and implementation contract addresses. - IOPContractsManagerContractsContainer public immutable contractsContainer; + IOPContractsManagerContainer public immutable contractsContainer; + + /// @notice Address of the Standard Validator for this OPCM release. + IOPContractsManagerStandardValidator public immutable standardValidator; + + /// @notice The version of the OPCM contract. + string public constant version = "6.0.0"; /// @param _contractsContainer The container of blueprint and implementation contract addresses. - constructor(IOPContractsManagerContractsContainer _contractsContainer) { + /// @param _standardValidator The standard validator for this OPCM release. + constructor( + IOPContractsManagerContainer _contractsContainer, + IOPContractsManagerStandardValidator _standardValidator + ) { contractsContainer = _contractsContainer; + standardValidator = _standardValidator; } /////////////////////////////////////////////////////////////////////////// @@ -633,7 +645,7 @@ contract OPContractsManagerV2 { _assertValidFullConfig(_cfg); // Load the implementations. - IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + IOPContractsManagerContainer.Implementations memory impls = implementations(); // Make sure the provided SuperchainConfig is up to date. if (SemverComp.lt(_cfg.superchainConfig.version(), ISuperchainConfig(impls.superchainConfigImpl).version())) { @@ -845,7 +857,7 @@ contract OPContractsManagerV2 { /// @param _gameType The game type to retrieve the implementation for. /// @return The dispute game implementation. function _getGameImpl(GameType _gameType) internal view returns (IDisputeGame) { - IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + IOPContractsManagerContainer.Implementations memory impls = implementations(); if (_gameType.raw() == GameTypes.CANNON.raw()) { return IDisputeGame(impls.faultDisputeGameV2Impl); } else if (_gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { @@ -873,7 +885,7 @@ contract OPContractsManagerV2 { view returns (bytes memory) { - IOPContractsManagerContractsContainer.Implementations memory impls = implementations(); + IOPContractsManagerContainer.Implementations memory impls = implementations(); if (_gcfg.gameType.raw() == GameTypes.CANNON.raw() || _gcfg.gameType.raw() == GameTypes.CANNON_KONA.raw()) { FaultDisputeGameConfig memory parsedInputArgs = abi.decode(_gcfg.gameArgs, (FaultDisputeGameConfig)); return abi.encodePacked( @@ -907,12 +919,12 @@ contract OPContractsManagerV2 { /////////////////////////////////////////////////////////////////////////// /// @notice Returns the blueprint contract addresses. - function blueprints() public view returns (IOPContractsManagerContractsContainer.Blueprints memory) { + function blueprints() public view returns (IOPContractsManagerContainer.Blueprints memory) { return contractsContainer.blueprints(); } /// @notice Returns the implementation contract addresses. - function implementations() public view returns (IOPContractsManagerContractsContainer.Implementations memory) { + function implementations() public view returns (IOPContractsManagerContainer.Implementations memory) { return contractsContainer.implementations(); } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index efcb411d06c..34ef980cfb8 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -23,6 +23,7 @@ import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; import { Proposal, GameTypes } from "src/dispute/lib/Types.sol"; import { LibGameArgs } from "src/dispute/lib/LibGameArgs.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Constants } from "src/libraries/Constants.sol"; import { LibString } from "@solady/utils/LibString.sol"; // Interfaces @@ -45,7 +46,6 @@ import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManager.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; @@ -58,11 +58,11 @@ import { OPContractsManagerGameTypeAdder, OPContractsManagerDeployer, OPContractsManagerUpgrader, + OPContractsManagerContractsContainer, OPContractsManagerInteropMigrator, OPContractsManagerStandardValidator } from "src/L1/OPContractsManager.sol"; import { OPContractsManagerV2 } from "src/L1/opcm/OPContractsManagerV2.sol"; -import { OPContractsManagerContractsContainer } from "src/L1/opcm/OPContractsManagerContractsContainer.sol"; import { DisputeGames } from "../setup/DisputeGames.sol"; import { IPermissionedDisputeGame } from "../../interfaces/dispute/IPermissionedDisputeGame.sol"; import { IProxy } from "../../interfaces/universal/IProxy.sol"; @@ -77,7 +77,6 @@ contract OPContractsManager_Harness is OPContractsManager { OPContractsManagerUpgrader _opcmUpgrader, OPContractsManagerInteropMigrator _opcmInteropMigrator, OPContractsManagerStandardValidator _opcmStandardValidator, - IOPContractsManagerV2 _opcmV2, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions ) @@ -87,7 +86,6 @@ contract OPContractsManager_Harness is OPContractsManager { _opcmUpgrader, _opcmInteropMigrator, _opcmStandardValidator, - _opcmV2, _superchainConfig, _protocolVersions ) @@ -368,7 +366,7 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { /// @param _delegateCaller The address of the delegate caller to use for superchain upgrade. /// @param _revertBytes The bytes of the revert to expect. function _runOpcmV2UpgradeAndChecks( - IOPContractsManager _opcm, + IOPContractsManagerV2 _opcm, address _delegateCaller, bytes memory _revertBytes ) @@ -385,16 +383,34 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); // Execute the SuperchainConfig upgrade. - DelegateCaller(superchainPAO).dcForward( - address(_opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); + // nosemgrep: sol-safety-trycatch-eip150 + try DelegateCaller(superchainPAO).dcForward( + address(opcmV2), + abi.encodeCall( + IOPContractsManagerV2.upgradeSuperchain, + ( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: superchainConfig, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + }) + ) + ) + ) { + // Great, the upgrade succeeded. + } catch (bytes memory reason) { + // Only acceptable revert reason is the SuperchainConfig already being up to date. This + // try/catch is better than checking the version via the implementations struct because + // the implementations struct interface can change between OPCM versions which would + // cause the test to break and be a pain to resolve. + assertTrue( + bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, + "Revert reason other than DowngradeNotAllowed" + ); + } // Reset the superchainPAO to the original code. vm.etch(superchainPAO, superchainPAOCode); - // Get the OPCM V2 contract. - IOPContractsManagerV2 opcmV2 = IOPContractsManagerV2(address(_opcm.opcmV2())); - // Temporarily replace the upgrader with a DelegateCaller. bytes memory delegateCallerCode = address(_delegateCaller).code; vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); @@ -451,7 +467,7 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { // Grab the validator before we do the error assertion because otherwise the assertion will // try to apply to this function call instead. - IOPContractsManagerStandardValidator validator = _opcm.opcmStandardValidator(); + IOPContractsManagerStandardValidator validator = _opcm.standardValidator(); // If the absolute prestate is zero, we will always get a PDDG-40,PLDG-40 error here in the // standard validator. This happens because an absolute prestate of zero means that the @@ -538,14 +554,14 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { /// @notice Executes the current V2 upgrade and checks the results. /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. function runCurrentUpgradeV2(address _delegateCaller) public { - _runOpcmV2UpgradeAndChecks(opcm, _delegateCaller, bytes("")); + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, bytes("")); } /// @notice Executes the current V2 upgrade and expects reverts. /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. /// @param _revertBytes The bytes of the revert to expect. function runCurrentUpgradeV2(address _delegateCaller, bytes memory _revertBytes) public { - _runOpcmV2UpgradeAndChecks(opcm, _delegateCaller, _revertBytes); + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes); } } @@ -565,6 +581,9 @@ abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { IOPContractsManager.DeployOutput internal chainDeployOutput1; IOPContractsManager.DeployOutput internal chainDeployOutput2; + IOPContractsManagerV2.ChainContracts internal chainContracts1; + IOPContractsManagerV2.ChainContracts internal chainContracts2; + function setUp() public virtual override { super.setUp(); proposer = address(this); @@ -572,11 +591,17 @@ abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { chain1L2ChainId = 100; chain2L2ChainId = 101; - chainDeployOutput1 = createChainContracts(chain1L2ChainId); - chainDeployOutput2 = createChainContracts(chain2L2ChainId); - - vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); - vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); + if (!isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + chainDeployOutput1 = createChainContracts(chain1L2ChainId); + chainDeployOutput2 = createChainContracts(chain2L2ChainId); + vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); + vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); + } else { + chainContracts1 = createChainContractsV2(chain1L2ChainId); + chainContracts2 = createChainContractsV2(chain2L2ChainId); + vm.deal(address(chainContracts1.ethLockbox), 100 ether); + vm.deal(address(chainContracts2.ethLockbox), 100 ether); + } } /// @notice Sets up the environment variables for the VerifyOPCM test. @@ -623,6 +648,66 @@ abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { ); } + /// @notice Helper function to deploy a new set of L1 contracts via OPCM V2. + /// @param _l2ChainId The L2 chain ID to deploy the contracts for. + /// @return The deployed contracts. + function createChainContractsV2(uint256 _l2ChainId) + internal + returns (IOPContractsManagerV2.ChainContracts memory) + { + IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new IOPContractsManagerV2.DisputeGameConfig[](3); + disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0, + gameType: GameTypes.CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")) }) + ) + }); + disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0, + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), + proposer: address(this), + challenger: address(this) + }) + ) + }); + disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: false, + initBond: 0, + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")) }) + ) + }); + return opcmV2.deploy( + IOPContractsManagerV2.FullConfig({ + saltMixer: "hello", + superchainConfig: superchainConfig, + proxyAdminOwner: address(this), + systemConfigOwner: address(this), + unsafeBlockSigner: address(this), + batcher: address(this), + startingAnchorRoot: Proposal({ + root: Hash.wrap(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef), + l2SequenceNumber: 0 + }), + startingRespectedGameType: GameType.wrap(1), + basefeeScalar: 1, + blobBasefeeScalar: 1, + gasLimit: 30_000_000, + l2ChainId: _l2ChainId, + resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), + disputeGameConfigs: disputeGameConfigs + }) + ); + } + function addGameType(IOPContractsManager.AddGameInput memory input) internal returns (IOPContractsManager.AddGameOutput memory) @@ -676,17 +761,16 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlag ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); IProxyAdmin superchainProxyAdmin = IProxyAdmin(makeAddr("superchainProxyAdmin")); - OPContractsManagerContractsContainer.Blueprints memory emptyBlueprints; - OPContractsManagerContractsContainer.Implementations memory emptyImpls; + OPContractsManager.Blueprints memory emptyBlueprints; + OPContractsManager.Implementations memory emptyImpls; vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); resolveFeaturesFromEnv(); - IOPContractsManagerContractsContainer container = IOPContractsManagerContractsContainer( - address(new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, devFeatureBitmap)) - ); + OPContractsManagerContractsContainer container = + new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls, devFeatureBitmap); - IOPContractsManagerContractsContainer.Implementations memory __opcmImplementations = container.implementations(); + OPContractsManager.Implementations memory __opcmImplementations = container.implementations(); OPContractsManagerStandardValidator.Implementations memory opcmImplementations; assembly { opcmImplementations := __opcmImplementations @@ -700,7 +784,6 @@ contract OPContractsManager_ChainIdToBatchInboxAddress_Test is Test, FeatureFlag _opcmStandardValidator: new OPContractsManagerStandardValidator( opcmImplementations, superchainConfigProxy, address(superchainProxyAdmin), challenger, 100, bytes32(0) ), - _opcmV2: IOPContractsManagerV2(address(new OPContractsManagerV2(container))), _superchainConfig: superchainConfigProxy, _protocolVersions: protocolVersionsProxy }); @@ -725,6 +808,11 @@ 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(); + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + } + /// @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. @@ -746,7 +834,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { // Get the v2 implementation address from OPCM - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); // Verify v2 implementation is registered in DisputeGameFactory address registeredImpl = @@ -793,7 +881,7 @@ contract OPContractsManager_AddGameType_Test is OPContractsManager_TestInit { if (isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { // Get the v2 implementation address from OPCM - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); // Verify implementation address matches permissionedDisputeGameV2Impl assertEq( @@ -1090,6 +1178,7 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { function setUp() public virtual override { super.setUp(); prestateUpdater = opcm; + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); } /// @notice Runs the OPCM updatePrestate function and checks the results. @@ -1575,6 +1664,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_v1_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Run the upgrade test and checks runCurrentUpgrade(upgrader); } @@ -1673,6 +1764,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_withVerifyOPCM_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + skipIfCoverage(); // Coverage changes bytecode and breaks the verification script. // Set up environment variables with the actual OPCM addresses for tests that need themqq @@ -1690,6 +1783,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_duplicateL2ChainId_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Upgrade the current chain. runCurrentUpgrade(upgrader); @@ -1704,6 +1799,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the absolute prestate can be overridden using the upgrade config. function test_upgrade_absolutePrestateOverride_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1753,6 +1850,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the old absolute prestate is used if the upgrade config does not set an /// absolute prestate. function test_upgrade_absolutePrestateNotSet_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1797,6 +1896,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the old absolute prestate is used and cannon kona is updated if the upgrade config does not /// set a cannon prestate. function test_upgrade_cannonPrestateNotSet_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1847,6 +1948,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the cannon absolute prestate is updated even if the cannon kona prestate is not specified function test_upgrade_cannonKonaPrestateNotSet_succeeds() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1903,6 +2006,8 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_notDelegateCalled_reverts() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + vm.prank(upgrader); vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); opcm.upgrade(opChainConfigs); @@ -1915,12 +2020,18 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { assertNotEq(superchainProxyAdmin.owner(), delegateCaller); assertNotEq(proxyAdmin.owner(), delegateCaller); - runCurrentUpgrade(delegateCaller, "Ownable: caller is not the owner"); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + runCurrentUpgradeV2(delegateCaller, "Ownable: caller is not the owner"); + } else { + runCurrentUpgrade(delegateCaller, "Ownable: caller is not the owner"); + } } /// @notice Tests that upgrade reverts when absolutePrestate is zero and the existing game also /// has an absolute prestate of zero. function test_upgrade_absolutePrestateNotSet_reverts() public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Set the config to try to update the absolutePrestate to zero. opChainConfigs[0].cannonPrestate = Claim.wrap(bytes32(0)); @@ -1945,17 +2056,22 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { // Force the SuperchainConfig to return an obviously outdated version. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("0.0.0")); - // Error depends on if V1 or V2 is being used. - // nosemgrep: sol-style-use-abi-encodecall - bytes memory err = isDevFeatureEnabled(DevFeatures.OPCM_V2) - ? abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) - : abi.encodeWithSelector( - IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) - ); - // Try upgrading an OPChain without upgrading its superchainConfig. - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgrade(upgrader, err); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + upgrader, + abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) + ); + } else { + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgrade( + upgrader, + abi.encodeWithSelector( + IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) + ) + ); + } } /// @notice Tests that the V2 upgrade function reverts when the user does not provide a game @@ -2099,9 +2215,16 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_Upgrade_Harness { + /// @notice Input for the upgradeSuperchain function. + IOPContractsManagerV2.SuperchainUpgradeInput internal superchainUpgradeInput; + function setUp() public override { super.setUp(); + // Set the superchain config. + // No extra instructions, so don't set them. + superchainUpgradeInput.superchainConfig = superchainConfig; + // The superchainConfig is already at the expected version so we mock this call here to bypass that check and // get our expected error. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("2.2.0")); @@ -2110,7 +2233,12 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U /// @notice Tests that the upgradeSuperchainConfig function succeeds when the superchainConfig is at the expected /// version and the delegate caller is the superchainProxyAdmin owner. function test_upgradeSuperchainConfig_succeeds() public { - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + address superchainConfigImpl; + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + superchainConfigImpl = opcmV2.implementations().superchainConfigImpl; + } else { + superchainConfigImpl = opcm.implementations().superchainConfigImpl; + } ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); @@ -2118,18 +2246,30 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); vm.expectEmit(address(superchainConfig)); - emit Upgraded(impls.superchainConfigImpl); - DelegateCaller(superchainPAO).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); + emit Upgraded(superchainConfigImpl); + + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + DelegateCaller(superchainPAO).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } else { + DelegateCaller(superchainPAO).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); + } } /// @notice Tests that the upgradeSuperchainConfig function reverts when it is not called via delegatecall. function test_upgradeSuperchainConfig_notDelegateCalled_reverts() public { ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); - vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); - opcm.upgradeSuperchainConfig(superchainConfig); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + vm.expectRevert("Ownable: caller is not the owner"); + opcmV2.upgradeSuperchain(superchainUpgradeInput); + } else { + vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); + opcm.upgradeSuperchainConfig(superchainConfig); + } } /// @notice Tests that the upgradeSuperchainConfig function reverts when the delegate caller is not the @@ -2144,9 +2284,15 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U assertNotEq(proxyAdmin.owner(), delegateCaller); vm.expectRevert("Ownable: caller is not the owner"); - DelegateCaller(delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + DelegateCaller(delegateCaller).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } else { + DelegateCaller(delegateCaller).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); + } } /// @notice Tests that the upgradeSuperchainConfig function reverts when the superchainConfig version is the same or @@ -2170,9 +2316,15 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U } // Try to upgrade the SuperchainConfig contract again, should fail. - DelegateCaller(upgrader).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + DelegateCaller(upgrader).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } else { + DelegateCaller(upgrader).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); + } } } @@ -2189,6 +2341,7 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { function setUp() public override { super.setUp(); skipIfDevFeatureDisabled(DevFeatures.OPTIMISM_PORTAL_INTEROP); + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); } /// @notice Helper function to create the default migration input. @@ -2778,7 +2931,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames bool isV2 = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); // Sanity-check setup is consistent with devFeatures flag - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); address pdgImpl = address(impls.permissionedDisputeGameV2Impl); address fdgImpl = address(impls.faultDisputeGameV2Impl); if (isV2) { @@ -2847,6 +3000,10 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames /// @notice Tests the `version` function of the `OPContractsManager` contract. contract OPContractsManager_Version_Test is OPContractsManager_TestInit { function test_semver_works() public view { - assertNotEq(abi.encode(opcm.version()), abi.encode(0)); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + assertNotEq(abi.encode(opcmV2.version()), abi.encode(0)); + } else { + assertNotEq(abi.encode(opcm.version()), abi.encode(0)); + } } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol index 198150eb6f9..d4c7d8d908e 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol @@ -4,8 +4,11 @@ pragma solidity 0.8.15; // Testing import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; +// Libraries +import { DevFeatures } from "src/libraries/DevFeatures.sol"; + // Contracts -import { OPContractsManagerContractsContainer } from "src/L1/opcm/OPContractsManagerContractsContainer.sol"; +import { OPContractsManager, OPContractsManagerContractsContainer } from "src/L1/OPContractsManager.sol"; /// @title OPContractsManagerContractsContainer_Constructor_Test /// @notice Tests the constructor of the `OPContractsManagerContractsContainer` contract. @@ -13,17 +16,18 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// @notice Tests that the constructor succeeds when the devFeatureBitmap is in dev. /// @param _devFeatureBitmap The devFeatureBitmap to use. function testFuzz_constructor_devBitmapInDev_succeeds(bytes32 _devFeatureBitmap) public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Etch into the magic testing address. vm.etch(address(0xbeefcafe), hex"01"); - // Create empty blueprints and implementations. - OPContractsManagerContractsContainer.Blueprints memory blueprints; - OPContractsManagerContractsContainer.Implementations memory implementations; + // Convert to proper OPCM type for construction. + OPContractsManager opcm2 = OPContractsManager(address(opcm)); // Should not revert. OPContractsManagerContractsContainer container = new OPContractsManagerContractsContainer({ - _blueprints: blueprints, - _implementations: implementations, + _blueprints: opcm2.blueprints(), + _implementations: opcm2.implementations(), _devFeatureBitmap: _devFeatureBitmap }); @@ -34,18 +38,23 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// @notice Tests that the constructor reverts when the devFeatureBitmap is in prod. /// @param _devFeatureBitmap The devFeatureBitmap to use. function testFuzz_constructor_devBitmapInProd_reverts(bytes32 _devFeatureBitmap) public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Anything but zero! _devFeatureBitmap = bytes32(bound(uint256(_devFeatureBitmap), 1, type(uint256).max)); // Make sure magic address has no code. vm.etch(address(0xbeefcafe), bytes("")); + // Convert to proper OPCM type for construction. + OPContractsManager opcm2 = OPContractsManager(address(opcm)); + // Set the chain ID to 1. vm.chainId(1); - // Create empty blueprints and implementations. - OPContractsManagerContractsContainer.Blueprints memory blueprints; - OPContractsManagerContractsContainer.Implementations memory implementations; + // Fetch ahead of time to avoid expectRevert applying to these functions by accident. + OPContractsManager.Blueprints memory blueprints = opcm2.blueprints(); + OPContractsManager.Implementations memory implementations = opcm2.implementations(); // Should revert. vm.expectRevert( @@ -66,20 +75,21 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// address having code. /// @param _devFeatureBitmap The devFeatureBitmap to use. function test_constructor_devBitmapMainnetButTestEnv_succeeds(bytes32 _devFeatureBitmap) public { + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + // Make sure magic address has code. vm.etch(address(0xbeefcafe), hex"01"); - // Create empty blueprints and implementations. - OPContractsManagerContractsContainer.Blueprints memory blueprints; - OPContractsManagerContractsContainer.Implementations memory implementations; + // Convert to proper OPCM type for construction. + OPContractsManager opcm2 = OPContractsManager(address(opcm)); // Set the chain ID to 1. vm.chainId(1); // Should not revert. OPContractsManagerContractsContainer container = new OPContractsManagerContractsContainer({ - _blueprints: blueprints, - _implementations: implementations, + _blueprints: opcm2.blueprints(), + _implementations: opcm2.implementations(), _devFeatureBitmap: _devFeatureBitmap }); diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index 25c66729fe4..bdc855e169d 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; import { StandardConstants } from "scripts/deploy/StandardConstants.sol"; import { DisputeGames } from "../setup/DisputeGames.sol"; +import { DelegateCaller } from "test/mocks/Callers.sol"; // Libraries import { GameType, Hash } from "src/dispute/lib/LibUDT.sol"; @@ -44,6 +45,7 @@ import { IDisputeGameFactory } from "../../interfaces/dispute/IDisputeGameFactor import { DisputeGames } from "../setup/DisputeGames.sol"; import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; import { IDelayedWETH } from "../../interfaces/dispute/IDelayedWETH.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; /// @title BadDisputeGameFactoryReturner /// @notice Used to return a bad DisputeGameFactory address to the OPContractsManagerStandardValidator. Far easier @@ -100,6 +102,9 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di /// @notice The proposer role set on the PermissionedDisputeGame instance. address proposer; + /// @notice The challenger role set on the PermissionedDisputeGame instance. + address challenger; + /// @notice The DisputeGameFactory instance. IDisputeGameFactory dgf; @@ -115,6 +120,9 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di /// @notice The BadDisputeGameFactoryReturner instance. BadDisputeGameFactoryReturner badDisputeGameFactoryReturner; + /// @notice The OPContractsManagerStandardValidator instance. + IOPContractsManagerStandardValidator standardValidator; + /// @notice Sets up the test suite. function setUp() public virtual override { super.setUp(); @@ -131,6 +139,12 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di // Load the PreimageOracle once, we'll need it later. preimageOracle = IPreimageOracle(artifacts.mustGetAddress("PreimageOracle")); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + standardValidator = opcmV2.standardValidator(); + } else { + standardValidator = opcm.opcmStandardValidator(); + } + // Values are slightly different for fork tests vs local tests. Most we can get from // reasonable sources, challenger we need to get from live system because there's no other // consistent way to get it right now. Means we're cheating a tiny bit for the challenger @@ -139,55 +153,111 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di l2ChainId = uint256(uint160(address(artifacts.mustGetAddress("L2ChainId")))); cannonPrestate = Claim.wrap(bytes32(keccak256("cannonPrestate"))); proposer = address(123); + challenger = address(456); vm.mockCall( address(proxyAdmin), abi.encodeCall(IProxyAdmin.getProxyImplementation, (address(l1OptimismMintableERC20Factory))), - abi.encode(opcm.opcmStandardValidator().optimismMintableERC20FactoryImpl()) + abi.encode(standardValidator.optimismMintableERC20FactoryImpl()) ); DisputeGames.mockGameImplChallenger( - disputeGameFactory, GameTypes.PERMISSIONED_CANNON, opcm.opcmStandardValidator().challenger() + disputeGameFactory, GameTypes.PERMISSIONED_CANNON, standardValidator.challenger() ); DisputeGames.mockGameImplProposer(disputeGameFactory, GameTypes.PERMISSIONED_CANNON, proposer); vm.mockCall( address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), - abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + abi.encode(standardValidator.l1PAOMultisig()) ); vm.mockCall( address(delayedWeth), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), - abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + abi.encode(standardValidator.l1PAOMultisig()) ); // Use vm.store so that the .setImplementation call below works. vm.store( address(disputeGameFactory), // this assumes that it is not packed with any other value bytes32(ForgeArtifacts.getSlot("DisputeGameFactory", "_owner").slot), - bytes32(uint256(uint160(opcm.opcmStandardValidator().l1PAOMultisig()))) + bytes32(uint256(uint160(standardValidator.l1PAOMultisig()))) ); } else { l2ChainId = deployInput.l2ChainId; cannonPrestate = deployInput.disputeAbsolutePrestate; proposer = deployInput.roles.proposer; + challenger = deployInput.roles.challenger; } // Deploy the BadDisputeGameFactoryReturner once. badDisputeGameFactoryReturner = new BadDisputeGameFactoryReturner( - opcm.opcmStandardValidator(), disputeGameFactory, IDisputeGameFactory(address(0xbad)) + standardValidator, disputeGameFactory, IDisputeGameFactory(address(0xbad)) ); if (isForkTest()) { // Load the FaultDisputeGame once, we'll need it later. fdgImpl = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); } else { - // Deploy a permissionless FaultDisputeGame. - IOPContractsManager.AddGameOutput memory output = addGameType(GameTypes.CANNON, cannonPrestate); - fdgImpl = output.faultDisputeGame; + if (!isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + // Deploy a permissionless FaultDisputeGame. + IOPContractsManager.AddGameOutput memory output = addGameType(GameTypes.CANNON, cannonPrestate); + fdgImpl = output.faultDisputeGame; - // Deploy cannon-kona - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - addGameType(GameTypes.CANNON_KONA, cannonKonaPrestate); + // Deploy cannon-kona + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + addGameType(GameTypes.CANNON_KONA, cannonKonaPrestate); + } + } else { + // Set the ProxyAdmin owner to be a delegatecaller. + address owner = proxyAdmin.owner(); + vm.etch(owner, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Prepare the upgrade input. + IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new IOPContractsManagerV2.DisputeGameConfig[](3); + disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON), + gameType: GameTypes.CANNON, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }); + disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: proposer, + challenger: challenger + }) + ) + }); + disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), + initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate }) + ) + }); + + // Call upgrade to all games to be enabled. + DelegateCaller(owner).dcForward( + address(opcmV2), + abi.encodeCall( + IOPContractsManagerV2.upgrade, + ( + IOPContractsManagerV2.UpgradeInput({ + systemConfig: systemConfig, + disputeGameConfigs: disputeGameConfigs, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + }) + ) + ) + ); + + // Grab the FaultDisputeGame implementation. + fdgImpl = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); } } } @@ -197,7 +267,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di /// @return The error message(s) from the validate function. function _validate(bool _allowFailure) internal view returns (string memory) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - return opcm.validate( + return standardValidator.validate( IOPContractsManagerStandardValidator.ValidationInputDev({ sysCfg: systemConfig, cannonPrestate: cannonPrestate.raw(), @@ -208,7 +278,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di _allowFailure ); } else { - return opcm.validate( + return standardValidator.validate( IOPContractsManagerStandardValidator.ValidationInput({ sysCfg: systemConfig, absolutePrestate: cannonPrestate.raw(), @@ -232,7 +302,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di returns (string memory) { if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - return opcm.validateWithOverrides( + return standardValidator.validateWithOverrides( IOPContractsManagerStandardValidator.ValidationInputDev({ sysCfg: systemConfig, cannonPrestate: cannonPrestate.raw(), @@ -244,7 +314,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di _overrides ); } else { - return opcm.validateWithOverrides( + return standardValidator.validateWithOverrides( IOPContractsManagerStandardValidator.ValidationInput({ sysCfg: systemConfig, absolutePrestate: cannonPrestate.raw(), @@ -1055,12 +1125,10 @@ contract OPContractsManagerStandardValidator_PermissionedDisputeGame_Test is vm.mockCall( badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), - abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + abi.encode(standardValidator.l1PAOMultisig()) ); vm.mockCall( - badWeth, - abi.encodeCall(IDelayedWETH.delay, ()), - abi.encode(opcm.opcmStandardValidator().withdrawalDelaySeconds()) + badWeth, abi.encodeCall(IDelayedWETH.delay, ()), abi.encode(standardValidator.withdrawalDelaySeconds()) ); vm.mockCall(badWeth, abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(sysCfg)); vm.mockCall(badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); @@ -1642,12 +1710,10 @@ contract OPContractsManagerStandardValidator_FaultDisputeGame_Test is OPContract vm.mockCall( badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), - abi.encode(opcm.opcmStandardValidator().l1PAOMultisig()) + abi.encode(standardValidator.l1PAOMultisig()) ); vm.mockCall( - badWeth, - abi.encodeCall(IDelayedWETH.delay, ()), - abi.encode(opcm.opcmStandardValidator().withdrawalDelaySeconds()) + badWeth, abi.encodeCall(IDelayedWETH.delay, ()), abi.encode(standardValidator.withdrawalDelaySeconds()) ); vm.mockCall(badWeth, abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(sysCfg)); vm.mockCall(badWeth, abi.encodeCall(IProxyAdminOwnedBase.proxyAdmin, ()), abi.encode(proxyAdmin)); @@ -1816,52 +1882,40 @@ contract OPContractsManagerStandardValidator_Versions_Test is OPContractsManager /// strings. function test_versions_succeeds() public view { assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().systemConfigImpl()).version()).length > 0, - "systemConfigVersion empty" + bytes(ISemver(standardValidator.systemConfigImpl()).version()).length > 0, "systemConfigVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().optimismPortalImpl()).version()).length > 0, - "optimismPortalVersion empty" + bytes(ISemver(standardValidator.optimismPortalImpl()).version()).length > 0, "optimismPortalVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().l1CrossDomainMessengerImpl()).version()).length > 0, + bytes(ISemver(standardValidator.l1CrossDomainMessengerImpl()).version()).length > 0, "l1CrossDomainMessengerVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().l1ERC721BridgeImpl()).version()).length > 0, - "l1ERC721BridgeVersion empty" + bytes(ISemver(standardValidator.l1ERC721BridgeImpl()).version()).length > 0, "l1ERC721BridgeVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().l1StandardBridgeImpl()).version()).length > 0, + bytes(ISemver(standardValidator.l1StandardBridgeImpl()).version()).length > 0, "l1StandardBridgeVersion empty" ); - assertTrue(bytes(ISemver(opcm.opcmStandardValidator().mipsImpl()).version()).length > 0, "mipsVersion empty"); + assertTrue(bytes(ISemver(standardValidator.mipsImpl()).version()).length > 0, "mipsVersion empty"); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().optimismMintableERC20FactoryImpl()).version()).length > 0, + bytes(ISemver(standardValidator.optimismMintableERC20FactoryImpl()).version()).length > 0, "optimismMintableERC20FactoryVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().disputeGameFactoryImpl()).version()).length > 0, + bytes(ISemver(standardValidator.disputeGameFactoryImpl()).version()).length > 0, "disputeGameFactoryVersion empty" ); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().anchorStateRegistryImpl()).version()).length > 0, + bytes(ISemver(standardValidator.anchorStateRegistryImpl()).version()).length > 0, "anchorStateRegistryVersion empty" ); + assertTrue(bytes(ISemver(standardValidator.delayedWETHImpl()).version()).length > 0, "delayedWETHVersion empty"); assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().delayedWETHImpl()).version()).length > 0, - "delayedWETHVersion empty" - ); - assertTrue( - bytes(opcm.opcmStandardValidator().permissionedDisputeGameVersion()).length > 0, - "permissionedDisputeGameVersion empty" - ); - assertTrue( - bytes(opcm.opcmStandardValidator().preimageOracleVersion()).length > 0, "preimageOracleVersion empty" - ); - assertTrue( - bytes(ISemver(opcm.opcmStandardValidator().ethLockboxImpl()).version()).length > 0, - "ethLockboxVersion empty" + bytes(standardValidator.permissionedDisputeGameVersion()).length > 0, "permissionedDisputeGameVersion empty" ); + assertTrue(bytes(standardValidator.preimageOracleVersion()).length > 0, "preimageOracleVersion empty"); + assertTrue(bytes(ISemver(standardValidator.ethLockboxImpl()).version()).length > 0, "ethLockboxVersion empty"); } } diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index a09d277f471..3246d9de9b4 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -15,7 +15,6 @@ import { Types } from "scripts/libraries/Types.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IOPContractsManagerContractsContainer } from "interfaces/L1/opcm/IOPContractsManagerContractsContainer.sol"; contract DeployOPChain_TestBase is Test, FeatureFlags { DeploySuperchain deploySuperchain; @@ -195,7 +194,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { // Check dispute game deployments // Validate permissionedDisputeGame (PDG) address bool isDeployV2Games = isDevFeatureEnabled(DevFeatures.DEPLOY_V2_DISPUTE_GAMES); - IOPContractsManagerContractsContainer.Implementations memory impls = opcm.implementations(); + IOPContractsManager.Implementations memory impls = opcm.implementations(); address expectedPDGAddress = isDeployV2Games ? impls.permissionedDisputeGameV2Impl : address(doo.permissionedDisputeGame); address actualPDGAddress = address(doo.disputeGameFactoryProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)); diff --git a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol index a09475447d3..38a50526439 100644 --- a/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol +++ b/packages/contracts-bedrock/test/scripts/VerifyOPCM.t.sol @@ -76,6 +76,7 @@ abstract contract VerifyOPCM_TestInit is OPContractsManager_TestInit { contract VerifyOPCM_Run_Test is VerifyOPCM_TestInit { function setUp() public override { super.setUp(); + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); setupEnvVars(); } diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index 2012966ef37..9742bb6b561 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -7,7 +7,7 @@ import { StdAssertions } from "forge-std/StdAssertions.sol"; // Testing import { stdToml } from "forge-std/StdToml.sol"; import { DelegateCaller } from "test/mocks/Callers.sol"; -import { FeatureFlags } from "test/setup/FeatureFlags.sol"; +import { DisputeGames } from "test/setup/DisputeGames.sol"; // Scripts import { Deployer } from "scripts/deploy/Deployer.sol"; @@ -35,6 +35,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOPContractsManagerUpgrader } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; /// @title ForkLive /// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an @@ -46,7 +47,7 @@ import { IOPContractsManagerUpgrader } from "interfaces/L1/IOPContractsManager.s /// superchain-registry. /// This contract must not have constructor logic because it is set into state using `etch`. -contract ForkLive is Deployer, StdAssertions, FeatureFlags { +contract ForkLive is Deployer, StdAssertions, DisputeGames { using stdToml for string; using LibString for string; @@ -249,10 +250,120 @@ contract ForkLive is Deployer, StdAssertions, FeatureFlags { vm.etch(_delegateCaller, upgraderCode); } + /// @notice Performs a single OPCM V2 upgrade. + /// @param _opcm The OPCM V2 contract to upgrade. + /// @param _delegateCaller The address of the upgrader to use for the upgrade. + function _doUpgradeV2(IOPContractsManagerV2 _opcm, address _delegateCaller) internal { + ISystemConfig systemConfig = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); + + // Turn the SuperchainPAO into a DelegateCaller so we can try to upgrade the + // SuperchainConfig contract. + ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); + IProxyAdmin superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); + address superchainPAO = superchainProxyAdmin.owner(); + bytes memory superchainPAOCode = address(superchainPAO).code; + vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Always try to upgrade the SuperchainConfig. Not always necessary but easier to do it + // every time rather than adding or removing this code for each upgrade. + try DelegateCaller(superchainPAO).dcForward( + address(_opcm), + abi.encodeCall( + IOPContractsManagerV2.upgradeSuperchain, + ( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: superchainConfig, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + }) + ) + ) + ) { + // Great, the upgrade succeeded. + } catch (bytes memory reason) { + // Only acceptable revert reason is the SuperchainConfig already being up to date. + assertTrue( + bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, + "Revert reason other than DowngradeNotAllowed" + ); + } + + // Reset the superchainPAO to the original code. + vm.etch(superchainPAO, superchainPAOCode); + + // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, + // then reset its code to the original code. + bytes memory upgraderCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Grab the existing PermissionedDisputeGame parameters. + IDisputeGameFactory disputeGameFactory = + IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + address challenger = permissionedGameChallenger(disputeGameFactory); + address proposer = permissionedGameProposer(disputeGameFactory); + + // Prepare the upgrade input. + IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new IOPContractsManagerV2.DisputeGameConfig[](3); + disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON), + gameType: GameTypes.CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("cannonPrestate"))) + }) + ) + }); + disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("cannonPrestate"))), + proposer: proposer, + challenger: challenger + }) + ) + }); + disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ + enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), + initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode( + IOPContractsManagerV2.FaultDisputeGameConfig({ + absolutePrestate: Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))) + }) + ) + }); + + // Add extra instructions to allow the DelayedWETH proxy to be deployed. + IOPContractsManagerV2.ExtraInstruction[] memory extraInstructions = + new IOPContractsManagerV2.ExtraInstruction[](1); + extraInstructions[0] = + IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }); + + // Upgrade the chain. + DelegateCaller(_delegateCaller).dcForward( + address(_opcm), + abi.encodeCall( + IOPContractsManagerV2.upgrade, + ( + IOPContractsManagerV2.UpgradeInput({ + systemConfig: systemConfig, + disputeGameConfigs: disputeGameConfigs, + extraInstructions: extraInstructions + }) + ) + ) + ); + + // Reset the upgrader to the original code. + vm.etch(_delegateCaller, upgraderCode); + } + /// @notice Upgrades the contracts using the OPCM. function _upgrade() internal { - IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); - ISystemConfig systemConfig = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); IProxyAdmin proxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(systemConfig))); @@ -269,7 +380,13 @@ contract ForkLive is Deployer, StdAssertions, FeatureFlags { } // Current upgrade. - _doUpgrade(opcm, upgrader); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + IOPContractsManagerV2 opcmV2 = IOPContractsManagerV2(artifacts.mustGetAddress("OPContractsManagerV2")); + _doUpgradeV2(opcmV2, upgrader); + } else { + IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); + _doUpgrade(opcm, upgrader); + } console.log("ForkLive: Saving newly deployed contracts"); diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 3ffc56b064f..8d238e9c523 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -22,6 +22,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { Chains } from "scripts/libraries/Chains.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; @@ -67,6 +68,7 @@ import { INativeAssetLiquidity } from "interfaces/L2/INativeAssetLiquidity.sol"; import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; import { IL1Withdrawer } from "interfaces/L2/IL1Withdrawer.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -124,6 +126,7 @@ abstract contract Setup is FeatureFlags { ISuperchainConfig superchainConfig; IDataAvailabilityChallenge dataAvailabilityChallenge; IOPContractsManager opcm; + IOPContractsManagerV2 opcmV2; IBigStepper mips; // L2 contracts @@ -288,7 +291,11 @@ abstract contract Setup is FeatureFlags { anchorStateRegistry = IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")); disputeGameFactory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); delayedWeth = IDelayedWETH(artifacts.mustGetAddress("DelayedWETHProxy")); - opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); + if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { + opcmV2 = IOPContractsManagerV2(artifacts.mustGetAddress("OPContractsManagerV2")); + } else { + opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); + } proxyAdmin = IProxyAdmin(artifacts.mustGetAddress("ProxyAdmin")); proxyAdminOwner = proxyAdmin.owner(); superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); From a8a165971084ff590bf534343ce3c8c2986faf69 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 10:01:03 -0500 Subject: [PATCH 04/17] fix: bad interface --- .../interfaces/L1/opcm/IOPContractsManagerV2.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index 68bb0852ebf..bec9b4c528f 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -106,7 +106,6 @@ interface IOPContractsManagerV2 { error OPContractsManagerV2_UnsupportedGameType(); error OPContractsManagerV2_ProxyMustLoad(); error OPContractsManagerV2_DowngradeNotAllowed(); - error OPContractsManagerV2_DevFeatureInProd(); error OPContractsManagerV2_InvalidUpgradeInstruction(); error OPContractsManagerV2_ConfigLoadFailed(string _name); error IdentityPrecompileCallFailed(); From fee01a0f5b4bb4d0d56585179f4de14d30f39761 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 10:12:48 -0500 Subject: [PATCH 05/17] fix: further clean diff --- .../scripts/deploy/Deploy.s.sol | 156 +++++++++--------- 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index e188770533a..169318aea0b 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -172,7 +172,11 @@ contract Deploy is Deployer { deployImplementations({ _isInterop: cfg.useInterop() }); // Deploy Current OPChain Contracts - deployOpChain(); + if (!DevFeatures.isDevFeatureEnabled(cfg.devFeatureBitmap(), DevFeatures.OPCM_V2)) { + deployOpChain(); + } else { + deployOpChainV2(); + } // Set the respected game type according to the deploy config vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); @@ -302,9 +306,6 @@ contract Deploy is Deployer { if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.DEPLOY_V2_DISPUTE_GAMES)) { artifacts.save("PermissionedDisputeGame", address(dio.permissionedDisputeGameV2Impl)); } - if (DevFeatures.isDevFeatureEnabled(dio.opcm.devFeatureBitmap(), DevFeatures.OPCM_V2)) { - 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); @@ -346,81 +347,88 @@ contract Deploy is Deployer { function deployOpChain() public { console.log("Deploying OP Chain"); + // Ensure that the requisite contracts are deployed + IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); + + IOPContractsManager.DeployInput memory deployInput = getDeployInput(); + IOPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); + // Store code in the Final system owner address so that it can be used for prank delegatecalls // Store "fe" opcode so that accidental calls to this address revert vm.etch(cfg.finalSystemOwner(), hex"fe"); - if (!DevFeatures.isDevFeatureEnabled(cfg.devFeatureBitmap(), DevFeatures.OPCM_V2)) { - // Ensure that the requisite contracts are deployed - IOPContractsManager opcm = IOPContractsManager(artifacts.mustGetAddress("OPContractsManager")); - - IOPContractsManager.DeployInput memory deployInput = getDeployInput(); - IOPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); - - // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct - artifacts.save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); - artifacts.save("AddressManager", address(deployOutput.addressManager)); - artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); - artifacts.save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); - artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); - artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); - artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); - artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); - - // Fault Proof contracts - artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); - artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); - 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")); - address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); - - // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. - // If the following require statement is hit, you can delete the block of code after it. - require( - permissionlessGameImpl == address(0), - "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." - ); - address delayedWETHImpl = artifacts.mustGetAddress("DelayedWETHImpl"); - address delayedWETHPermissionlessGameProxy = - deployERC1967ProxyWithOwner("DelayedWETHProxy", address(deployOutput.opChainProxyAdmin)); - vm.broadcast(address(deployOutput.opChainProxyAdmin)); - IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ - _implementation: delayedWETHImpl, - _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) - }); - } else { - // Ensure that the requisite contracts are deployed - IOPContractsManagerV2 opcm = IOPContractsManagerV2(artifacts.mustGetAddress("OPContractsManagerV2")); - - IOPContractsManagerV2.FullConfig memory deployInput = getDeployInputV2(); - IOPContractsManagerV2.ChainContracts memory deployOutput = opcm.deploy(deployInput); - - // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct - artifacts.save("ProxyAdmin", address(deployOutput.proxyAdmin)); - artifacts.save("AddressManager", address(deployOutput.addressManager)); - artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721Bridge)); - artifacts.save("SystemConfigProxy", address(deployOutput.systemConfig)); - artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20Factory)); - artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridge)); - artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessenger)); - artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockbox)); - - // Fault Proof contracts - artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactory)); - artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETH)); - artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETH)); - artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistry)); - artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortal)); - artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortal)); + // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct + artifacts.save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); + artifacts.save("AddressManager", address(deployOutput.addressManager)); + artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); + artifacts.save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); + artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); + artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); + artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); + + // Fault Proof contracts + artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); + artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); + 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")); + address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); + + // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. + // If the following require statement is hit, you can delete the block of code after it. + require( + permissionlessGameImpl == address(0), + "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." + ); + address delayedWETHImpl = artifacts.mustGetAddress("DelayedWETHImpl"); + address delayedWETHPermissionlessGameProxy = + deployERC1967ProxyWithOwner("DelayedWETHProxy", address(deployOutput.opChainProxyAdmin)); + vm.broadcast(address(deployOutput.opChainProxyAdmin)); + IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ + _implementation: delayedWETHImpl, + _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) + }); + } + + /// @notice Deploy all of the OP Chain specific contracts using OPCM v2 + function deployOpChainV2() public { + console.log("Deploying OP Chain"); + + // Store code in the Final system owner address so that it can be used for prank delegatecalls + // Store "fe" opcode so that accidental calls to this address revert + vm.etch(cfg.finalSystemOwner(), hex"fe"); + + // Ensure that the requisite contracts are deployed + IOPContractsManagerV2 opcm = IOPContractsManagerV2(artifacts.mustGetAddress("OPContractsManagerV2")); + + IOPContractsManagerV2.FullConfig memory deployInput = getDeployInputV2(); + IOPContractsManagerV2.ChainContracts memory deployOutput = opcm.deploy(deployInput); + + // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct + artifacts.save("ProxyAdmin", address(deployOutput.proxyAdmin)); + artifacts.save("AddressManager", address(deployOutput.addressManager)); + artifacts.save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721Bridge)); + artifacts.save("SystemConfigProxy", address(deployOutput.systemConfig)); + artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20Factory)); + artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridge)); + artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessenger)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockbox)); + + // Fault Proof contracts + artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactory)); + artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETH)); + artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETH)); + artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistry)); + artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortal)); + artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortal)); } //////////////////////////////////////////////////////////////// From 3ed221dc98e94a80d07781054ec6c5b2978d1809 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 10:30:46 -0500 Subject: [PATCH 06/17] fix: reduce diff --- packages/contracts-bedrock/scripts/deploy/Deploy.s.sol | 1 - .../scripts/deploy/DeployImplementations.s.sol | 4 ++-- packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 169318aea0b..9e743b2fa4f 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -370,7 +370,6 @@ contract Deploy is Deployer { // Fault Proof contracts artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - artifacts.save("DelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy)); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 28c63dd042d..765e15569f7 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -25,9 +25,9 @@ import { IOPContractsManagerGameTypeAdder, IOPContractsManagerDeployer, IOPContractsManagerUpgrader, + IOPContractsManagerContractsContainer, IOPContractsManagerInteropMigrator, - IOPContractsManagerStandardValidator, - IOPContractsManagerContractsContainer + IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManager.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; diff --git a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol index a7e2e69b53b..1aa38b32c0d 100644 --- a/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/VerifyOPCM.s.sol @@ -137,6 +137,7 @@ contract VerifyOPCM is Script { sourceNameOverrides["OPContractsManagerDeployer"] = "OPContractsManager"; sourceNameOverrides["OPContractsManagerUpgrader"] = "OPContractsManager"; sourceNameOverrides["OPContractsManagerInteropMigrator"] = "OPContractsManager"; + sourceNameOverrides["OPContractsManagerContractsContainer"] = "OPContractsManager"; // Expected getter functions and their verification methods. // CRITICAL: Any getter in the ABI that's not in this list will cause verification to fail. From 5ebcbce2ae2b68bc3f6446ba15f98ee5dad5a75a Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 10:33:28 -0500 Subject: [PATCH 07/17] fix: undo artifacts diffs --- .../scripts/ops/calculate-checksum.sh | 2 +- .../scripts/ops/pull-artifacts.sh | 30 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh b/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh index 6fde4a357e5..ba4ebfdbc59 100755 --- a/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh +++ b/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh @@ -23,4 +23,4 @@ rm manifest.txt echoerr "> Checksum: $checksum" echoerr "> Done." -echo -n "$checksum" +echo -n "$checksum" \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh index 690ee9f9897..7f79c46341b 100755 --- a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh +++ b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh @@ -77,24 +77,18 @@ if [ "$HAS_ZSTD" = true ]; then exists_zst=$(curl -s -o /dev/null --fail -LI "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" || echo "fail") if [ "$exists_zst" != "fail" ]; then - echoerr "> Found .tar.zst artifacts. Downloading..." - curl -o "$archive_name_zst" "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" - echoerr "> Done." - - echoerr "> Cleaning up existing artifacts..." - rm -rf artifacts - rm -rf forge-artifacts - rm -rf cache - echoerr "> Done." - - echoerr "> Extracting existing artifacts..." - zstd -dc "$archive_name_zst" | tar -xf - - echoerr "> Done." - - echoerr "> Cleaning up." - rm "$archive_name_zst" - echoerr "> Done." - exit 0 + download_and_extract "$archive_name_zst" + fi + + # Try latest fallback if enabled + if [ "$USE_LATEST_FALLBACK" = true ]; then + echoerr "> Exact checksum not found, trying latest artifacts..." + archive_name_zst="artifacts-v1-latest.tar.zst" + exists_latest_zst=$(curl -s -o /dev/null --fail -LI "https://storage.googleapis.com/oplabs-contract-artifacts/$archive_name_zst" || echo "fail") + + if [ "$exists_latest_zst" != "fail" ]; then + download_and_extract "$archive_name_zst" + fi fi fi From 52075432659655d6e4ecbd11ecd98bcda063c360 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 10:36:12 -0500 Subject: [PATCH 08/17] fix: broken go structs --- op-chain-ops/addresses/contracts.go | 2 ++ op-chain-ops/interopgen/deployments.go | 1 + op-deployer/pkg/deployer/opcm/implementations.go | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/op-chain-ops/addresses/contracts.go b/op-chain-ops/addresses/contracts.go index a07f7d656fd..eb9ee2c888d 100644 --- a/op-chain-ops/addresses/contracts.go +++ b/op-chain-ops/addresses/contracts.go @@ -34,6 +34,8 @@ type ImplementationsContracts struct { OpcmUpgraderImpl common.Address OpcmInteropMigratorImpl common.Address OpcmStandardValidatorImpl common.Address + OpcmV2Impl common.Address + OpcmContainerImpl common.Address DelayedWethImpl common.Address OptimismPortalImpl common.Address OptimismPortalInteropImpl common.Address diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 3d2422f60ed..12c9223e429 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -18,6 +18,7 @@ type Implementations struct { OpcmInteropMigrator common.Address `json:"OPCMInteropMigrator"` OpcmStandardValidator common.Address `json:"OPCMStandardValidator"` OpcmV2 common.Address `json:"OPCMV2"` + OpcmContainer common.Address `json:"OPCMContainer"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` OptimismPortalInteropImpl common.Address `json:"OptimismPortalInteropImpl"` diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 6339386224f..12ed97dbb9b 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -35,7 +35,8 @@ type DeployImplementationsOutput struct { OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` OpcmInteropMigrator common.Address `json:"opcmInteropMigratorAddress"` OpcmStandardValidator common.Address `json:"opcmStandardValidatorAddress"` - OpcmV2 common.Address `json:"opcmV2Address" abi:"opcmV2"` + OpcmV2 common.Address `json:"opcmV2Address"` + OpcmContainer common.Address `json:"opcmContainerAddress"` DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` OptimismPortalInteropImpl common.Address `json:"optimismPortalInteropImplAddress"` From 6e8e61ce09df9948e165d2fd4a410fa702734d18 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 12:14:02 -0500 Subject: [PATCH 09/17] fix: opcm output struct ordering --- .../scripts/deploy/DeployImplementations.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 765e15569f7..b5eed69563d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -72,13 +72,13 @@ contract DeployImplementations is Script { struct Output { IOPContractsManager opcm; IOPContractsManagerContractsContainer opcmContractsContainer; - IOPContractsManagerContainer opcmContainer; IOPContractsManagerGameTypeAdder opcmGameTypeAdder; IOPContractsManagerDeployer opcmDeployer; IOPContractsManagerUpgrader opcmUpgrader; IOPContractsManagerInteropMigrator opcmInteropMigrator; IOPContractsManagerStandardValidator opcmStandardValidator; IOPContractsManagerV2 opcmV2; + IOPContractsManagerContainer opcmContainer; // v2 container IDelayedWETH delayedWETHImpl; IOptimismPortal optimismPortalImpl; IOptimismPortalInterop optimismPortalInteropImpl; From e8f3da4ed96978e63e02de26a5c4b7ff40a16af7 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 16:28:13 -0500 Subject: [PATCH 10/17] fix: better tests --- .semgrep/rules/sol-rules.yaml | 2 +- packages/contracts-bedrock/justfile | 2 +- .../scripts/checks/test-validation/main.go | 5 +- .../test/L1/OPContractsManager.t.sol | 780 +++--------------- ...OPContractsManagerContractsContainer.t.sol | 9 - .../test/L1/opcm/OPContractsManagerV2.t.sol | 516 ++++++++++++ .../contracts-bedrock/test/setup/Setup.sol | 8 + 7 files changed, 652 insertions(+), 670 deletions(-) create mode 100644 packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index fd3b99d06e9..35bab3b758c 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -321,7 +321,7 @@ rules: exclude: - packages/contracts-bedrock/src/L1/OPContractsManager.sol - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol - - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContractsContainer.sol + - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol - packages/contracts-bedrock/src/L1/OptimismPortal2.sol - packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol - packages/contracts-bedrock/src/L2/FeeVault.sol diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index fe4e0e0d157..13602ea74b2 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -170,7 +170,7 @@ coverage-lcov-upgrade *ARGS: build-go-ffi # Runs coverage-lcov and coverage-lcov-upgrade and merges their output files info one file coverage-lcov-all *ARGS: just coverage-lcov {{ARGS}} && \ - just coverage-lcov-upgrade --match-contract OPContractsManager_Upgrade_Test {{ARGS}} && \ + just coverage-lcov-upgrade --match-contract {OPContractsManager_Upgrade_Test,OPContractsManagerV2_Upgrade_Test} {{ARGS}} && \ lcov -a lcov.info -a lcov-upgrade.info -o lcov-all.info ######################################################## diff --git a/packages/contracts-bedrock/scripts/checks/test-validation/main.go b/packages/contracts-bedrock/scripts/checks/test-validation/main.go index 36c3e0419e7..a62ebfa0799 100644 --- a/packages/contracts-bedrock/scripts/checks/test-validation/main.go +++ b/packages/contracts-bedrock/scripts/checks/test-validation/main.go @@ -237,6 +237,9 @@ func checkTestStructure(artifact *solc.ForgeArtifact) []error { if len(contractParts) == 2 && contractParts[1] == "TestInit" { // Pattern: _TestInit continue + } else if len(contractParts) == 3 && contractParts[2] == "TestInit" { + // Pattern: __TestInit + continue } else if len(contractParts) == 2 && contractParts[1] == "Harness" { // Pattern: _Harness continue @@ -250,7 +253,7 @@ func checkTestStructure(artifact *solc.ForgeArtifact) []error { errors = append(errors, checkTestMethodName(artifact, contractName, contractParts[1], contractParts[2])...) } else { // Invalid naming pattern - errors = append(errors, fmt.Errorf("contract '%s': invalid naming pattern. Expected patterns: _TestInit, __Test, or _Uncategorized_Test", contractName)) + errors = append(errors, fmt.Errorf("contract '%s': invalid naming pattern. Expected patterns: _TestInit, __TestInit, _Harness, __Test, __Harness, or _Uncategorized_Test", contractName)) } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 34ef980cfb8..b92298af39f 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -23,8 +23,6 @@ import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; import { Proposal, GameTypes } from "src/dispute/lib/Types.sol"; import { LibGameArgs } from "src/dispute/lib/LibGameArgs.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; -import { Constants } from "src/libraries/Constants.sol"; -import { LibString } from "@solady/utils/LibString.sol"; // Interfaces import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; @@ -45,7 +43,6 @@ import { IOPContractsManagerUpgrader, IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManager.sol"; -import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; @@ -62,7 +59,6 @@ import { OPContractsManagerInteropMigrator, OPContractsManagerStandardValidator } from "src/L1/OPContractsManager.sol"; -import { OPContractsManagerV2 } from "src/L1/opcm/OPContractsManagerV2.sol"; import { DisputeGames } from "../setup/DisputeGames.sol"; import { IPermissionedDisputeGame } from "../../interfaces/dispute/IPermissionedDisputeGame.sol"; import { IProxy } from "../../interfaces/universal/IProxy.sol"; @@ -118,16 +114,20 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { /// @notice Thrown when testing with an unsupported chain ID. error UnsupportedChainId(); + struct PreUpgradeState { + Claim cannonAbsolutePrestate; + Claim permissionedAbsolutePrestate; + IDelayedWETH permissionlessWethProxy; + IDelayedWETH permissionedCannonWethProxy; + } + uint256 l2ChainId; address upgrader; IOPContractsManager.OpChainConfig[] opChainConfigs; Claim cannonPrestate; Claim cannonKonaPrestate; string public opChain = Config.forkOpChain(); - IOPContractsManagerV2.UpgradeInput internal v2UpgradeInput; - - /// @notice Special string constant used to indicate that we expect a revert without any data. - bytes public constant EXPECT_REVERT_WITHOUT_DATA = bytes("EXPECT_REVERT_WITHOUT_DATA"); + PreUpgradeState preUpgradeState; function setUp() public virtual override { super.disableUpgradedFork(); @@ -137,6 +137,9 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { vm.skip(true); } + // All V1 upgrade tests can safely be skipped for V2. + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + skipIfOpsRepoTest( "OPContractsManager_Upgrade_Harness: cannot test upgrade on superchain ops repo upgrade tests" ); @@ -157,50 +160,28 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { }) ); - // Prepare V2 upgrade input with ordered game configs [CANNON, PERMISSIONED_CANNON]. - address initialChallengerForV2 = permissionedGameChallenger(disputeGameFactory); - address initialProposerForV2 = permissionedGameProposer(disputeGameFactory); - v2UpgradeInput.systemConfig = systemConfig; - v2UpgradeInput.disputeGameConfigs.push( - IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, - initBond: disputeGameFactory.initBonds(GameTypes.CANNON), - gameType: GameTypes.CANNON, - gameArgs: abi.encode(OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) - }) - ); - v2UpgradeInput.disputeGameConfigs.push( - IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, - initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), - gameType: GameTypes.PERMISSIONED_CANNON, - gameArgs: abi.encode( - OPContractsManagerV2.PermissionedDisputeGameConfig({ - absolutePrestate: cannonPrestate, - proposer: initialProposerForV2, - challenger: initialChallengerForV2 - }) - ) - }) - ); - v2UpgradeInput.disputeGameConfigs.push( - IOPContractsManagerV2.DisputeGameConfig({ - enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), - initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), - gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode(OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) - }) - ); - - // Allow the DelayedWETH proxy to be (re)deployed during upgrades if it is missing. - v2UpgradeInput.extraInstructions.push( - IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) - ); - // Retrieve the l2ChainId, which was read from the superchain-registry, and saved in // Artifacts encoded as an address. l2ChainId = uint256(uint160(address(artifacts.mustGetAddress("L2ChainId")))); + delayedWETHPermissionedGameProxy = + IDelayedWETH(payable(artifacts.mustGetAddress("PermissionedDelayedWETHProxy"))); + permissionedDisputeGame = IPermissionedDisputeGame(address(artifacts.mustGetAddress("PermissionedDisputeGame"))); + IDisputeGameFactory dgf = IDisputeGameFactory(address(artifacts.mustGetAddress("DisputeGameFactoryProxy"))); + faultDisputeGame = IFaultDisputeGame(address(dgf.gameImpls(GameTypes.CANNON))); + delayedWeth = faultDisputeGame.weth(); + + // grab the pre-upgrade state + preUpgradeState = PreUpgradeState({ + cannonAbsolutePrestate: IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))) + .absolutePrestate(), + permissionedAbsolutePrestate: IPermissionedDisputeGame( + address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ).absolutePrestate(), + permissionlessWethProxy: delayedWeth, + permissionedCannonWethProxy: delayedWETHPermissionedGameProxy + }); + // Since this superchainConfig is already at the expected reinitializer version... // We do this to pass the reinitializer check when trying to upgrade the superchainConfig contract. @@ -358,165 +339,81 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { validationOverrides ); } + _runPostUpgradeSmokeTests(_opcm, opChainConfigs[0], initialChallenger, initialProposer); } - /// @notice Helper function that runs an OPCM V2 upgrade, asserts that the upgrade was successful, - /// and runs post-upgrade smoke tests. - /// @param _opcm The OPCM contract to reference for shared components. - /// @param _delegateCaller The address of the delegate caller to use for superchain upgrade. - /// @param _revertBytes The bytes of the revert to expect. - function _runOpcmV2UpgradeAndChecks( - IOPContractsManagerV2 _opcm, - address _delegateCaller, - bytes memory _revertBytes + /// @notice Runs some smoke tests after an upgrade + function _runPostUpgradeSmokeTests( + IOPContractsManager _opcm, + IOPContractsManager.OpChainConfig memory _opChainConfig, + address _challenger, + address _proposer ) 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(); - bytes memory superchainPAOCode = address(superchainPAO).code; - vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - - // Execute the SuperchainConfig upgrade. - // nosemgrep: sol-safety-trycatch-eip150 - try DelegateCaller(superchainPAO).dcForward( - address(opcmV2), - abi.encodeCall( - IOPContractsManagerV2.upgradeSuperchain, - ( - IOPContractsManagerV2.SuperchainUpgradeInput({ - superchainConfig: superchainConfig, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) - }) - ) - ) - ) { - // Great, the upgrade succeeded. - } catch (bytes memory reason) { - // Only acceptable revert reason is the SuperchainConfig already being up to date. This - // try/catch is better than checking the version via the implementations struct because - // the implementations struct interface can change between OPCM versions which would - // cause the test to break and be a pain to resolve. - assertTrue( - bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, - "Revert reason other than DowngradeNotAllowed" - ); - } - - // Reset the superchainPAO to the original code. - vm.etch(superchainPAO, superchainPAOCode); + address expectedVm = address(_opcm.implementations().mipsImpl); - // Temporarily replace the upgrader with a DelegateCaller. - bytes memory delegateCallerCode = address(_delegateCaller).code; - vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - - // Expect the revert if one is specified. - if (_revertBytes.length > 0) { - if (keccak256(_revertBytes) == keccak256(EXPECT_REVERT_WITHOUT_DATA)) { - // nosemgrep: sol-safety-expectrevert-no-args - vm.expectRevert(); - } else { - vm.expectRevert(_revertBytes); - } - } + Claim claim = Claim.wrap(bytes32(uint256(1))); + uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON); + vm.deal(address(_challenger), bondAmount); + (, uint256 rootBlockNumber) = optimismPortal2.anchorStateRegistry().getAnchorRoot(); + uint256 l2BlockNumber = rootBlockNumber + 1; - // Execute the V2 chain upgrade via delegate caller. - DelegateCaller(_delegateCaller).dcForward( - address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgrade, (v2UpgradeInput)) - ); + bool expectCannonKonaGameSet = + isDevFeatureEnabled(DevFeatures.CANNON_KONA) && _opChainConfig.cannonKonaPrestate.raw() != bytes32(0); - // Return early if a revert was expected. Otherwise we'll get errors below. - if (_revertBytes.length > 0) { - return; + // Deploy live games and ensure they're configured correctly + GameType[] memory gameTypes = new GameType[](expectCannonKonaGameSet ? 3 : 2); + gameTypes[0] = GameTypes.PERMISSIONED_CANNON; + gameTypes[1] = GameTypes.CANNON; + if (expectCannonKonaGameSet) { + gameTypes[2] = GameTypes.CANNON_KONA; } + for (uint256 i = 0; i < gameTypes.length; i++) { + GameType gt = gameTypes[i]; - // Reset the upgrader to the original code. - vm.etch(_delegateCaller, delegateCallerCode); - - // Less than 90% of the gas target of 2**24 (EIP-7825) to account for the gas used by using Safe. - uint256 fusakaLimit = 2 ** 24; - VmSafe.Gas memory gas = vm.lastCallGas(); - assertLt(gas.gasTotalUsed, fusakaLimit * 9 / 10, "Upgrade exceeds gas target of 90% of 2**24 (EIP-7825)"); - - // Reset the upgrader to the original code. - vm.etch(_delegateCaller, delegateCallerCode); - - // We expect there to only be one chain config for these tests, you will have to rework - // this test if you add more. - assertEq(opChainConfigs.length, 1); - - // Coverage changes bytecode, so we get various errors. We can safely ignore the result of - // the standard validator in the coverage case, if the validator is failing in coverage - // then it will also fail in other CI tests (unless it's the expected issues, in which case - // we can safely skip). - if (vm.isContext(VmSafe.ForgeContext.Coverage)) { - return; - } + bytes32 expectedAbsolutePrestate = _opChainConfig.cannonPrestate.raw(); + if (expectedAbsolutePrestate == bytes32(0)) { + expectedAbsolutePrestate = preUpgradeState.permissionedAbsolutePrestate.raw(); + } + if (expectCannonKonaGameSet && gt.raw() == GameTypes.CANNON_KONA.raw()) { + expectedAbsolutePrestate = _opChainConfig.cannonKonaPrestate.raw(); + } + assertEq(bondAmount, disputeGameFactory.initBonds(gt)); - // Create validationOverrides - IOPContractsManagerStandardValidator.ValidationOverrides memory validationOverrides = - IOPContractsManagerStandardValidator.ValidationOverrides({ - l1PAOMultisig: opChainConfigs[0].systemConfigProxy.proxyAdminOwner(), - challenger: initialChallenger - }); + vm.prank(_proposer, _proposer); + IPermissionedDisputeGame game = IPermissionedDisputeGame( + address(disputeGameFactory.create{ value: bondAmount }(gt, claim, abi.encode(l2BlockNumber))) + ); + (,,,, Claim rootClaim,,) = game.claimData(0); - // Grab the validator before we do the error assertion because otherwise the assertion will - // try to apply to this function call instead. - IOPContractsManagerStandardValidator validator = _opcm.standardValidator(); + vm.assertEq(gt.raw(), game.gameType().raw()); + vm.assertEq(expectedAbsolutePrestate, game.absolutePrestate().raw()); + vm.assertEq(address(optimismPortal2.anchorStateRegistry()), address(game.anchorStateRegistry())); + vm.assertEq(l2ChainId, game.l2ChainId()); + vm.assertEq(302400, game.maxClockDuration().raw()); + vm.assertEq(10800, game.clockExtension().raw()); + vm.assertEq(73, game.maxGameDepth()); + vm.assertEq(30, game.splitDepth()); + vm.assertEq(l2BlockNumber, game.l2BlockNumber()); + vm.assertEq(expectedVm, address(game.vm())); + vm.assertEq(_proposer, game.gameCreator()); + vm.assertEq(claim.raw(), rootClaim.raw()); + vm.assertEq(blockhash(block.number - 1), game.l1Head().raw()); - // If the absolute prestate is zero, we will always get a PDDG-40,PLDG-40 error here in the - // standard validator. This happens because an absolute prestate of zero means that the - // user is requesting to use the existing prestate. We could avoid the error by grabbing - // the prestate from the actual contracts, but that doesn't actually give us any valuable - // checks. Easier to just expect the error in this case. - // We add the prefix of OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER because we use validationOverrides. - if (opChainConfigs[0].cannonPrestate.raw() == bytes32(0)) { - if ( - opChainConfigs[0].cannonKonaPrestate.raw() == bytes32(0) && isDevFeatureEnabled(DevFeatures.CANNON_KONA) - ) { - vm.expectRevert( - "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,PDDG-40,PLDG-40,CKDG-10" - ); + if (gt.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + vm.assertEq(address(preUpgradeState.permissionedCannonWethProxy), address(game.weth())); + vm.assertEq(_challenger, game.challenger()); + vm.assertEq(_proposer, game.proposer()); } else { - vm.expectRevert( - "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,PDDG-40,PLDG-40" - ); + vm.assertEq(address(preUpgradeState.permissionlessWethProxy), address(game.weth())); } - } else if ( - opChainConfigs[0].cannonKonaPrestate.raw() == bytes32(0) && isDevFeatureEnabled(DevFeatures.CANNON_KONA) - ) { - vm.expectRevert("OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,CKDG-10"); } - // Run the StandardValidator checks. - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - validator.validateWithOverrides( - IOPContractsManagerStandardValidator.ValidationInputDev({ - sysCfg: opChainConfigs[0].systemConfigProxy, - cannonPrestate: opChainConfigs[0].cannonPrestate.raw(), - cannonKonaPrestate: opChainConfigs[0].cannonKonaPrestate.raw(), - l2ChainID: l2ChainId, - proposer: initialProposer - }), - false, - validationOverrides - ); - } else { - validator.validateWithOverrides( - IOPContractsManagerStandardValidator.ValidationInput({ - sysCfg: opChainConfigs[0].systemConfigProxy, - absolutePrestate: opChainConfigs[0].cannonPrestate.raw(), - l2ChainID: l2ChainId, - proposer: initialProposer - }), - false, - validationOverrides - ); + if (!expectCannonKonaGameSet) { + assertEq(address(0), address(disputeGameFactory.gameImpls(GameTypes.CANNON_KONA))); + assertEq(0, disputeGameFactory.initBonds(GameTypes.CANNON_KONA)); + assertEq(0, disputeGameFactory.gameArgs(GameTypes.CANNON_KONA).length); } } @@ -550,19 +447,6 @@ contract OPContractsManager_Upgrade_Harness is CommonTest, DisputeGames { function runCurrentUpgrade(address _delegateCaller, bytes memory _revertBytes) public { _runOpcmUpgradeAndChecks(opcm, _delegateCaller, _revertBytes); } - - /// @notice Executes the current V2 upgrade and checks the results. - /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. - function runCurrentUpgradeV2(address _delegateCaller) public { - _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, bytes("")); - } - - /// @notice Executes the current V2 upgrade and expects reverts. - /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. - /// @param _revertBytes The bytes of the revert to expect. - function runCurrentUpgradeV2(address _delegateCaller, bytes memory _revertBytes) public { - _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes); - } } /// @title OPContractsManager_TestInit @@ -581,27 +465,22 @@ abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { IOPContractsManager.DeployOutput internal chainDeployOutput1; IOPContractsManager.DeployOutput internal chainDeployOutput2; - IOPContractsManagerV2.ChainContracts internal chainContracts1; - IOPContractsManagerV2.ChainContracts internal chainContracts2; - function setUp() public virtual override { super.setUp(); + + // TODO(#18332): Remove this once we support all existing OPCM functions. + skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); + proposer = address(this); challenger = address(this); chain1L2ChainId = 100; chain2L2ChainId = 101; - if (!isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - chainDeployOutput1 = createChainContracts(chain1L2ChainId); - chainDeployOutput2 = createChainContracts(chain2L2ChainId); - vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); - vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); - } else { - chainContracts1 = createChainContractsV2(chain1L2ChainId); - chainContracts2 = createChainContractsV2(chain2L2ChainId); - vm.deal(address(chainContracts1.ethLockbox), 100 ether); - vm.deal(address(chainContracts2.ethLockbox), 100 ether); - } + chainDeployOutput1 = createChainContracts(chain1L2ChainId); + chainDeployOutput2 = createChainContracts(chain2L2ChainId); + + vm.deal(address(chainDeployOutput1.ethLockboxProxy), 100 ether); + vm.deal(address(chainDeployOutput2.ethLockboxProxy), 100 ether); } /// @notice Sets up the environment variables for the VerifyOPCM test. @@ -648,66 +527,6 @@ abstract contract OPContractsManager_TestInit is CommonTest, DisputeGames { ); } - /// @notice Helper function to deploy a new set of L1 contracts via OPCM V2. - /// @param _l2ChainId The L2 chain ID to deploy the contracts for. - /// @return The deployed contracts. - function createChainContractsV2(uint256 _l2ChainId) - internal - returns (IOPContractsManagerV2.ChainContracts memory) - { - IOPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = - new IOPContractsManagerV2.DisputeGameConfig[](3); - disputeGameConfigs[0] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, - initBond: 0, - gameType: GameTypes.CANNON, - gameArgs: abi.encode( - IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")) }) - ) - }); - disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: true, - initBond: 0, - gameType: GameTypes.PERMISSIONED_CANNON, - gameArgs: abi.encode( - IOPContractsManagerV2.PermissionedDisputeGameConfig({ - absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), - proposer: address(this), - challenger: address(this) - }) - ) - }); - disputeGameConfigs[2] = IOPContractsManagerV2.DisputeGameConfig({ - enabled: false, - initBond: 0, - gameType: GameTypes.CANNON_KONA, - gameArgs: abi.encode( - IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")) }) - ) - }); - return opcmV2.deploy( - IOPContractsManagerV2.FullConfig({ - saltMixer: "hello", - superchainConfig: superchainConfig, - proxyAdminOwner: address(this), - systemConfigOwner: address(this), - unsafeBlockSigner: address(this), - batcher: address(this), - startingAnchorRoot: Proposal({ - root: Hash.wrap(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef), - l2SequenceNumber: 0 - }), - startingRespectedGameType: GameType.wrap(1), - basefeeScalar: 1, - blobBasefeeScalar: 1, - gasLimit: 30_000_000, - l2ChainId: _l2ChainId, - resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), - disputeGameConfigs: disputeGameConfigs - }) - ); - } - function addGameType(IOPContractsManager.AddGameInput memory input) internal returns (IOPContractsManager.AddGameOutput memory) @@ -808,11 +627,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(); - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - } - /// @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. @@ -1178,7 +992,6 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { function setUp() public virtual override { super.setUp(); prestateUpdater = opcm; - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); } /// @notice Runs the OPCM updatePrestate function and checks the results. @@ -1640,16 +1453,6 @@ contract OPContractsManager_UpdatePrestate_Test is OPContractsManager_TestInit { /// @title OPContractsManager_Upgrade_Test /// @notice Tests the `upgrade` function of the `OPContractsManager` contract. contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { - struct PreUpgradeState { - bytes32 absolutePrestate; - address pdgWeth; - address fdgWeth; - address proposer; - address challenger; - } - - bytes32 internal constant INITIALIZED_EVENT_SIG = keccak256("Initialized(uint8)"); - function setUp() public override { super.setUp(); @@ -1663,109 +1466,12 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { return Claim.wrap(gameArgs.absolutePrestate); } - function test_upgrade_v1_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - - // Run the upgrade test and checks - runCurrentUpgrade(upgrader); - } - - function test_upgrade_v1WithPostChecks_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - - // Capture pre-upgrade expectations locally - IOPContractsManager.OpChainConfig memory _opChainConfig = opChainConfigs[0]; - PreUpgradeState memory pre; - pre.proposer = permissionedGameProposer(disputeGameFactory); - pre.challenger = permissionedGameChallenger(disputeGameFactory); - pre.absolutePrestate = IPermissionedDisputeGame( - address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) - ).absolutePrestate().raw(); - pre.pdgWeth = address( - IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))).weth() - ); - pre.fdgWeth = address(IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))).weth()); - - bool expectCannonKonaGameSet = - isDevFeatureEnabled(DevFeatures.CANNON_KONA) && _opChainConfig.cannonKonaPrestate.raw() != bytes32(0); - + function test_upgradeOPChainOnly_succeeds() public { // Run the upgrade test and checks runCurrentUpgrade(upgrader); - - // Post-upgrade smoke checks (inlined from _runPostUpgradeSmokeTests) - address expectedVm = address(opcm.implementations().mipsImpl); - - Claim claim = Claim.wrap(bytes32(uint256(1))); - uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON); - vm.deal(address(pre.challenger), bondAmount); - (, uint256 rootBlockNumber) = optimismPortal2.anchorStateRegistry().getAnchorRoot(); - uint256 l2BlockNumber = rootBlockNumber + 1; - - // Deploy live games and ensure they're configured correctly - GameType[] memory gameTypes = new GameType[](expectCannonKonaGameSet ? 3 : 2); - gameTypes[0] = GameTypes.PERMISSIONED_CANNON; - gameTypes[1] = GameTypes.CANNON; - if (expectCannonKonaGameSet) { - gameTypes[2] = GameTypes.CANNON_KONA; - } - for (uint256 i = 0; i < gameTypes.length; i++) { - GameType gt = gameTypes[i]; - - bytes32 expectedAbsolutePrestate = _opChainConfig.cannonPrestate.raw(); - if (expectedAbsolutePrestate == bytes32(0)) { - expectedAbsolutePrestate = pre.absolutePrestate; - } - if (expectCannonKonaGameSet && gt.raw() == GameTypes.CANNON_KONA.raw()) { - expectedAbsolutePrestate = _opChainConfig.cannonKonaPrestate.raw(); - } - assertEq(bondAmount, disputeGameFactory.initBonds(gt)); - - vm.prank(pre.proposer, pre.proposer); - IPermissionedDisputeGame game = IPermissionedDisputeGame( - address(disputeGameFactory.create{ value: bondAmount }(gt, claim, abi.encode(l2BlockNumber))) - ); - (,,,, Claim rootClaim,,) = game.claimData(0); - - vm.assertEq(gt.raw(), game.gameType().raw()); - vm.assertEq(expectedAbsolutePrestate, game.absolutePrestate().raw()); - vm.assertEq(address(optimismPortal2.anchorStateRegistry()), address(game.anchorStateRegistry())); - vm.assertEq(l2ChainId, game.l2ChainId()); - vm.assertEq(302400, game.maxClockDuration().raw()); - vm.assertEq(10800, game.clockExtension().raw()); - vm.assertEq(73, game.maxGameDepth()); - vm.assertEq(30, game.splitDepth()); - vm.assertEq(l2BlockNumber, game.l2BlockNumber()); - vm.assertEq(expectedVm, address(game.vm())); - vm.assertEq(pre.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(pre.challenger, game.challenger()); - vm.assertEq(pre.proposer, game.proposer()); - vm.assertEq(pre.pdgWeth, address(game.weth())); - } else { - vm.assertEq(pre.fdgWeth, address(game.weth())); - } - } - - if (!expectCannonKonaGameSet) { - assertEq(address(0), address(disputeGameFactory.gameImpls(GameTypes.CANNON_KONA))); - assertEq(0, disputeGameFactory.initBonds(GameTypes.CANNON_KONA)); - assertEq(0, disputeGameFactory.gameArgs(GameTypes.CANNON_KONA).length); - } } - function test_upgrade_v2_succeeds() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Run the upgrade test and checks - runCurrentUpgradeV2(upgrader); - } - - function test_upgrade_withVerifyOPCM_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - + function test_verifyOpcmCorrectness_succeeds() public { skipIfCoverage(); // Coverage changes bytecode and breaks the verification script. // Set up environment variables with the actual OPCM addresses for tests that need themqq @@ -1783,24 +1489,19 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_duplicateL2ChainId_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - - // Upgrade the current chain. - runCurrentUpgrade(upgrader); - - // Deploy a new chain with the same chain ID as the current chain. - // Should work without any issues because the salt mixer creates different addresses. + // Deploy a new OPChain with the same L2 chain ID as the current OPChain Deploy deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); IOPContractsManager.DeployInput memory deployInput = deploy.getDeployInput(); deployInput.l2ChainId = l2ChainId; deployInput.saltMixer = "v2.0.0"; opcm.deploy(deployInput); + + // Try to upgrade the current OPChain + runCurrentUpgrade(upgrader); } /// @notice Tests that the absolute prestate can be overridden using the upgrade config. function test_upgrade_absolutePrestateOverride_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1850,8 +1551,6 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the old absolute prestate is used if the upgrade config does not set an /// absolute prestate. function test_upgrade_absolutePrestateNotSet_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1896,8 +1595,6 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the old absolute prestate is used and cannon kona is updated if the upgrade config does not /// set a cannon prestate. function test_upgrade_cannonPrestateNotSet_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1948,8 +1645,6 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { /// @notice Tests that the cannon absolute prestate is updated even if the cannon kona prestate is not specified function test_upgrade_cannonKonaPrestateNotSet_succeeds() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Get the pdg and fdg before the upgrade Claim pdgPrestateBefore = IPermissionedDisputeGame( address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) @@ -1965,21 +1660,6 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { opChainConfigs[0].cannonPrestate = Claim.wrap(bytes32(uint256(1))); opChainConfigs[0].cannonKonaPrestate = Claim.wrap(bytes32(0)); - // Same idea, but for V2 upgrades. - v2UpgradeInput.disputeGameConfigs[0].gameArgs = abi.encode( - OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(uint256(1))) }) - ); - v2UpgradeInput.disputeGameConfigs[1].gameArgs = abi.encode( - OPContractsManagerV2.PermissionedDisputeGameConfig({ - absolutePrestate: Claim.wrap(bytes32(uint256(1))), - challenger: address(1), - proposer: address(1) - }) - ); - v2UpgradeInput.disputeGameConfigs[2].gameArgs = abi.encode( - OPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: Claim.wrap(bytes32(uint256(0))) }) - ); - // Run the upgrade. runCurrentUpgrade(upgrader); @@ -2006,8 +1686,6 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } function test_upgrade_notDelegateCalled_reverts() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - vm.prank(upgrader); vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); opcm.upgrade(opChainConfigs); @@ -2020,18 +1698,12 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { assertNotEq(superchainProxyAdmin.owner(), delegateCaller); assertNotEq(proxyAdmin.owner(), delegateCaller); - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - runCurrentUpgradeV2(delegateCaller, "Ownable: caller is not the owner"); - } else { - runCurrentUpgrade(delegateCaller, "Ownable: caller is not the owner"); - } + runCurrentUpgrade(delegateCaller, bytes("Ownable: caller is not the owner")); } /// @notice Tests that upgrade reverts when absolutePrestate is zero and the existing game also /// has an absolute prestate of zero. function test_upgrade_absolutePrestateNotSet_reverts() public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Set the config to try to update the absolutePrestate to zero. opChainConfigs[0].cannonPrestate = Claim.wrap(bytes32(0)); @@ -2057,174 +1729,20 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("0.0.0")); // Try upgrading an OPChain without upgrading its superchainConfig. - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, - abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) - ); - } else { - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgrade( - upgrader, - abi.encodeWithSelector( - IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) - ) - ); - } - } - - /// @notice Tests that the V2 upgrade function reverts when the user does not provide a game - /// config for each valid game type. - function test_upgrade_missingGameConfigs_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Delete the Permissionless game configuration. - delete v2UpgradeInput.disputeGameConfigs[1]; - - // Expect upgrade to revert. - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) - ); - } - - /// @notice Tests that the V2 upgrade function reverts when the user provides the game configs - /// in the wrong order. - function test_upgrade_wrongGameConfigOrder_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Swap the game config order. - IOPContractsManagerV2.DisputeGameConfig memory temp = v2UpgradeInput.disputeGameConfigs[0]; - v2UpgradeInput.disputeGameConfigs[0] = v2UpgradeInput.disputeGameConfigs[1]; - v2UpgradeInput.disputeGameConfigs[1] = temp; - - // Expect upgrade to revert due to invalid game config order. - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) - ); - } - - /// @notice Tests that the V2 upgrade function reverts when the user wants to disable the - /// PermissionedDisputeGame. - function test_upgrade_disabledPermissionedGame_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Disable the PermissionedDisputeGame. - v2UpgradeInput.disputeGameConfigs[1].enabled = false; - - // Expect upgrade to revert due to missing game config. // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) - ); - } - - /// @notice Tests that the V2 upgrade function rejects the ALL sentinel in permitted proxy deployments. - function test_upgrade_allPermittedProxyDeployments_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - delete v2UpgradeInput.extraInstructions; - v2UpgradeInput.extraInstructions.push( - IOPContractsManagerV2.ExtraInstruction({ key: "PermitProxyDeployment", data: abi.encode("ALL") }) - ); - - // Expect upgrade to revert due to invalid upgrade input. - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( + runCurrentUpgrade( upgrader, - abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector) - ); - } - - /// @notice Tests that the V2 upgrade function reverts if a permitted proxy deployment is required but missing. - function test_upgrade_missingPermittedProxyDeployment_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - delete v2UpgradeInput.extraInstructions; - - // Simulate a missing DelayedWETH proxy so the upgrade path would need to deploy it. - // nosemgrep: sol-style-use-abi-encodecall - vm.mockCallRevert(address(systemConfig), abi.encodeWithSelector(ISystemConfig.delayedWETH.selector), ""); - - // Expect the upgrade to revert because the DelayedWETH proxy must load but the user did not permit - // redeployment. - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) - ); - } - - /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load - /// an existing proxy returns data that isn't an abi-encoded address. - /// @param _len Length of the data to generate. - function testFuzz_upgrade_proxyLoadBadReturn_reverts(uint8 _len) public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Ensure we do not produce a 32-byte payload, which would be interpreted as a valid - // abi-encoded address and could change the revert reason. - vm.assume(_len != 32); - - // Build an arbitrary bytes payload of length `_len`. - bytes memory bad = new bytes(_len); - for (uint256 i = 0; i < bad.length; i++) { - bad[i] = bytes1(uint8(0xAA)); - } - - // Mock the first proxy load source call to succeed but return a payload with a length - // not equal to 32 bytes, triggering OPContractsManagerV2_ProxyLoadMustLoad. - vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bad); - - // Expect a revert without any data (due to abi decoding failure). - runCurrentUpgradeV2(upgrader, EXPECT_REVERT_WITHOUT_DATA); - } - - /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load - /// an existing proxy returns the zero address but we asked it to load. - function test_upgrade_proxyMustLoadButZeroAddress_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Mock the first proxy load to succeed and return address(0) with 32 bytes, - // which triggers OPContractsManagerV2_ProxyMustLoad since _mustLoad is true in upgrade. - vm.mockCall( - address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), abi.encode(address(0)) - ); - - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) - ); - } - - /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load - /// an existing proxy returns an error but we asked it to load. - function test_upgrade_proxyMustLoadButReverts_reverts() public { - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); - - // Mock the first proxy load source to revert, which with _mustLoad=true triggers - // OPContractsManagerV2_ProxyMustLoad. - // nosemgrep: sol-style-use-abi-encodecall - vm.mockCallRevert(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bytes("")); - - // nosemgrep: sol-style-use-abi-encodecall - runCurrentUpgradeV2( - upgrader, abi.encodeWithSelector(OPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + abi.encodeWithSelector( + IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigNeedsUpgrade.selector, (0) + ) ); } } contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_Upgrade_Harness { - /// @notice Input for the upgradeSuperchain function. - IOPContractsManagerV2.SuperchainUpgradeInput internal superchainUpgradeInput; - function setUp() public override { super.setUp(); - // Set the superchain config. - // No extra instructions, so don't set them. - superchainUpgradeInput.superchainConfig = superchainConfig; - // The superchainConfig is already at the expected version so we mock this call here to bypass that check and // get our expected error. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("2.2.0")); @@ -2233,12 +1751,7 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U /// @notice Tests that the upgradeSuperchainConfig function succeeds when the superchainConfig is at the expected /// version and the delegate caller is the superchainProxyAdmin owner. function test_upgradeSuperchainConfig_succeeds() public { - address superchainConfigImpl; - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - superchainConfigImpl = opcmV2.implementations().superchainConfigImpl; - } else { - superchainConfigImpl = opcm.implementations().superchainConfigImpl; - } + IOPContractsManager.Implementations memory impls = opcm.implementations(); ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); @@ -2246,30 +1759,18 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); vm.expectEmit(address(superchainConfig)); - emit Upgraded(superchainConfigImpl); - - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - DelegateCaller(superchainPAO).dcForward( - address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) - ); - } else { - DelegateCaller(superchainPAO).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); - } + emit Upgraded(impls.superchainConfigImpl); + DelegateCaller(superchainPAO).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); } /// @notice Tests that the upgradeSuperchainConfig function reverts when it is not called via delegatecall. function test_upgradeSuperchainConfig_notDelegateCalled_reverts() public { ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - vm.expectRevert("Ownable: caller is not the owner"); - opcmV2.upgradeSuperchain(superchainUpgradeInput); - } else { - vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); - opcm.upgradeSuperchainConfig(superchainConfig); - } + vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); + opcm.upgradeSuperchainConfig(superchainConfig); } /// @notice Tests that the upgradeSuperchainConfig function reverts when the delegate caller is not the @@ -2284,15 +1785,9 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U assertNotEq(proxyAdmin.owner(), delegateCaller); vm.expectRevert("Ownable: caller is not the owner"); - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - DelegateCaller(delegateCaller).dcForward( - address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) - ); - } else { - DelegateCaller(delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); - } + DelegateCaller(delegateCaller).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); } /// @notice Tests that the upgradeSuperchainConfig function reverts when the superchainConfig version is the same or @@ -2306,25 +1801,11 @@ contract OPContractsManager_UpgradeSuperchainConfig_Test is OPContractsManager_U // Mock the SuperchainConfig to return a very large version. vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("99.99.99")); - // Exact revert message depends on OPCM being enabled. - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - vm.expectRevert(IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector); - } else { - vm.expectRevert( - IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate.selector - ); - } - // Try to upgrade the SuperchainConfig contract again, should fail. - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - DelegateCaller(upgrader).dcForward( - address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) - ); - } else { - DelegateCaller(upgrader).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) - ); - } + vm.expectRevert(IOPContractsManagerUpgrader.OPContractsManagerUpgrader_SuperchainConfigAlreadyUpToDate.selector); + DelegateCaller(upgrader).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgradeSuperchainConfig, (superchainConfig)) + ); } } @@ -2341,7 +1822,6 @@ contract OPContractsManager_Migrate_Test is OPContractsManager_TestInit { function setUp() public override { super.setUp(); skipIfDevFeatureDisabled(DevFeatures.OPTIMISM_PORTAL_INTEROP); - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); } /// @notice Helper function to create the default migration input. @@ -2912,18 +2392,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames vm.expectEmit(true, true, true, false); // TODO precompute the expected `deployOutput`. emit Deployed(deployOPChainInput.l2ChainId, address(this), bytes("")); opcm.deploy(toOPCMDeployInput(deployOPChainInput)); - - // Try to keep the gas usage below 12m gas, under the EIP-7825 gas target of 2**24. We can - // technically go higher but this is a conservative bound, good signal that the gas usage - // is increasing. Only check this for the V2 OPCM since the V1 already exceeds this limit. - // Don't check for coverage or lite profile since they mess with gas usage. - VmSafe.Gas memory gas = vm.lastCallGas(); - if ( - isDevFeatureEnabled(DevFeatures.OPCM_V2) && !LibString.eq(Config.foundryProfile(), "lite") - && !vm.isContext(VmSafe.ForgeContext.Coverage) - ) { - assertLt(gas.gasTotalUsed, 12000000, "Deploy gas usage exceeds 12m gas"); - } } /// @notice Test that deploy sets the permissioned dispute game implementation @@ -3000,10 +2468,6 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase, DisputeGames /// @notice Tests the `version` function of the `OPContractsManager` contract. contract OPContractsManager_Version_Test is OPContractsManager_TestInit { function test_semver_works() public view { - if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - assertNotEq(abi.encode(opcmV2.version()), abi.encode(0)); - } else { - assertNotEq(abi.encode(opcm.version()), abi.encode(0)); - } + assertNotEq(abi.encode(opcm.version()), abi.encode(0)); } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol index d4c7d8d908e..0028b6db813 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerContractsContainer.t.sol @@ -4,9 +4,6 @@ pragma solidity 0.8.15; // Testing import { OPContractsManager_TestInit } from "test/L1/OPContractsManager.t.sol"; -// Libraries -import { DevFeatures } from "src/libraries/DevFeatures.sol"; - // Contracts import { OPContractsManager, OPContractsManagerContractsContainer } from "src/L1/OPContractsManager.sol"; @@ -16,8 +13,6 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// @notice Tests that the constructor succeeds when the devFeatureBitmap is in dev. /// @param _devFeatureBitmap The devFeatureBitmap to use. function testFuzz_constructor_devBitmapInDev_succeeds(bytes32 _devFeatureBitmap) public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Etch into the magic testing address. vm.etch(address(0xbeefcafe), hex"01"); @@ -38,8 +33,6 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// @notice Tests that the constructor reverts when the devFeatureBitmap is in prod. /// @param _devFeatureBitmap The devFeatureBitmap to use. function testFuzz_constructor_devBitmapInProd_reverts(bytes32 _devFeatureBitmap) public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Anything but zero! _devFeatureBitmap = bytes32(bound(uint256(_devFeatureBitmap), 1, type(uint256).max)); @@ -75,8 +68,6 @@ contract OPContractsManagerContractsContainer_Constructor_Test is OPContractsMan /// address having code. /// @param _devFeatureBitmap The devFeatureBitmap to use. function test_constructor_devBitmapMainnetButTestEnv_succeeds(bytes32 _devFeatureBitmap) public { - skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); - // Make sure magic address has code. vm.etch(address(0xbeefcafe), hex"01"); diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol new file mode 100644 index 00000000000..27e529cc2e9 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { VmSafe } from "forge-std/Vm.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; +import { DelegateCaller } from "test/mocks/Callers.sol"; +import { DisputeGames } from "test/setup/DisputeGames.sol"; + +// Libraries +import { Config } from "scripts/libraries/Config.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { Claim } from "src/dispute/lib/LibUDT.sol"; +import { GameTypes } from "src/dispute/lib/Types.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; + +// Interfaces +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; + +/// @title OPContractsManagerV2_Upgrade_TestInit +/// @notice Test initialization contract for OPContractsManagerV2 upgrade functions. +contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { + // The Upgraded event emitted by the Proxy contract. + event Upgraded(address indexed implementation); + + /// @notice Chain ID for the L2 chain being upgraded in this test. + uint256 l2ChainId; + + /// @notice Address of the ProxyAdmin owner for the chain being upgraded. + address chainPAO; + + /// @notice Address of the Superchain ProxyAdmin owner. + address superchainPAO; + + /// @notice Fake prestate for Cannon games. + Claim cannonPrestate = Claim.wrap(bytes32(keccak256("cannonPrestate"))); + + /// @notice Fake prestate for Cannon Kona games. + Claim cannonKonaPrestate = Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))); + + /// @notice Name of the chain being forked. + string public opChain = Config.forkOpChain(); + + /// @notice Default v2 upgrade input. + IOPContractsManagerV2.UpgradeInput v2UpgradeInput; + + /// @notice Special string constant used to indicate that we expect a revert without any data. + bytes public constant EXPECT_REVERT_WITHOUT_DATA = bytes("EXPECT_REVERT_WITHOUT_DATA"); + + /// @notice Thrown when trying to run past upgrades on an unsupported chain. + error UnsupportedChainId(); + + /// @notice Sets up the test suite. + function setUp() public virtual override { + super.disableUpgradedFork(); + super.setUp(); + + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + skipIfNotForkTest("OPContractsManagerV2_Upgrade_TestInit: only runs in forked tests"); + skipIfOpsRepoTest("OPContractsManagerV2_Upgrade_TestInit: skipped in superchain-ops"); + + // Turn the chain PAO into a DelegateCaller. + chainPAO = proxyAdmin.owner(); + vm.label(chainPAO, "ProxyAdmin Owner"); + vm.etch(chainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Turn the SuperchainConfig PAO into a DelegateCaller. + superchainPAO = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))).owner(); + vm.label(superchainPAO, "SuperchainConfig ProxyAdmin Owner"); + vm.etch(superchainPAO, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Grab and set the L2 chain ID. + l2ChainId = uint256(uint160(address(artifacts.mustGetAddress("L2ChainId")))); + + // Set up the default v2 upgrade input dispute game configs. + address initialChallengerForV2 = permissionedGameChallenger(disputeGameFactory); + address initialProposerForV2 = permissionedGameProposer(disputeGameFactory); + v2UpgradeInput.systemConfig = systemConfig; + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.CANNON), + gameType: GameTypes.CANNON, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }) + ); + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: disputeGameFactory.initBonds(GameTypes.PERMISSIONED_CANNON), + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: initialProposerForV2, + challenger: initialChallengerForV2 + }) + ) + }) + ); + v2UpgradeInput.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), + initBond: disputeGameFactory.initBonds(GameTypes.CANNON_KONA), + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + }) + ); + + // Allow the DelayedWETH proxy to be (re)deployed during upgrades if it is missing. + v2UpgradeInput.extraInstructions.push( + IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) + ); + } + + /// @notice Helper function that runs an OPCM V2 upgrade, asserts that the upgrade was successful, + /// and runs post-upgrade smoke tests. + /// @param _opcm The OPCM contract to reference for shared components. + /// @param _delegateCaller The address of the delegate caller to use for superchain upgrade. + /// @param _revertBytes The bytes of the revert to expect. + function _runOpcmV2UpgradeAndChecks( + IOPContractsManagerV2 _opcm, + address _delegateCaller, + bytes memory _revertBytes + ) + internal + { + // Grab some values before we upgrade, to be checked later + address initialChallenger = permissionedGameChallenger(disputeGameFactory); + address initialProposer = permissionedGameProposer(disputeGameFactory); + + // Execute the SuperchainConfig upgrade. + // nosemgrep: sol-safety-trycatch-eip150 + try DelegateCaller(superchainPAO).dcForward( + address(opcmV2), + abi.encodeCall( + IOPContractsManagerV2.upgradeSuperchain, + ( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: superchainConfig, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + }) + ) + ) + ) { + // Great, the upgrade succeeded. + } catch (bytes memory reason) { + // Only acceptable revert reason is the SuperchainConfig already being up to date. This + // try/catch is better than checking the version via the implementations struct because + // the implementations struct interface can change between OPCM versions which would + // cause the test to break and be a pain to resolve. + assertTrue( + bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, + "Revert reason other than DowngradeNotAllowed" + ); + } + + // Temporarily replace the chainPAO with a DelegateCaller. + bytes memory delegateCallerCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Expect the revert if one is specified. + if (_revertBytes.length > 0) { + if (keccak256(_revertBytes) == keccak256(EXPECT_REVERT_WITHOUT_DATA)) { + // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(); + } else { + vm.expectRevert(_revertBytes); + } + } + + // Execute the V2 chain upgrade via delegate caller. + DelegateCaller(_delegateCaller).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgrade, (v2UpgradeInput)) + ); + + // Return early if a revert was expected. Otherwise we'll get errors below. + if (_revertBytes.length > 0) { + return; + } + + // Reset the chainPAO to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // Less than 90% of the gas target of 2**24 (EIP-7825) to account for the gas used by using Safe. + uint256 fusakaLimit = 2 ** 24; + VmSafe.Gas memory gas = vm.lastCallGas(); + assertLt(gas.gasTotalUsed, fusakaLimit * 9 / 10, "Upgrade exceeds gas target of 90% of 2**24 (EIP-7825)"); + + // Reset the chainPAO to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // Coverage changes bytecode, so we get various errors. We can safely ignore the result of + // the standard validator in the coverage case, if the validator is failing in coverage + // then it will also fail in other CI tests (unless it's the expected issues, in which case + // we can safely skip). + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + return; + } + + // Create validationOverrides + IOPContractsManagerStandardValidator.ValidationOverrides memory validationOverrides = + IOPContractsManagerStandardValidator.ValidationOverrides({ + l1PAOMultisig: v2UpgradeInput.systemConfig.proxyAdminOwner(), + challenger: initialChallenger + }); + + // Grab the validator before we do the error assertion because otherwise the assertion will + // try to apply to this function call instead. + IOPContractsManagerStandardValidator validator = _opcm.standardValidator(); + + // Run the StandardValidator checks. + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInputDev({ + sysCfg: v2UpgradeInput.systemConfig, + cannonPrestate: cannonPrestate.raw(), + cannonKonaPrestate: cannonKonaPrestate.raw(), + l2ChainID: l2ChainId, + proposer: initialProposer + }), + false, + validationOverrides + ); + } else { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInput({ + sysCfg: v2UpgradeInput.systemConfig, + absolutePrestate: cannonPrestate.raw(), + l2ChainID: l2ChainId, + proposer: initialProposer + }), + false, + validationOverrides + ); + } + } + + /// @notice Executes all past upgrades that have not yet been executed on mainnet as of the + /// current simulation block defined in the justfile for this package. This function + /// might be empty if there are no previous upgrades to execute. You should remove + /// upgrades from this function once they've been executed on mainnet and the + /// simulation block has been bumped beyond the execution block. + /// @param _delegateCaller The address of the delegate caller to use for the upgrade. + function runPastUpgrades(address _delegateCaller) internal view { + // Run past upgrades depending on network. + if (block.chainid == 1) { + // Mainnet + // This is empty because the block number in the justfile is after the most recent upgrade so there are no + // past upgrades to run. + _delegateCaller; + } else { + revert UnsupportedChainId(); + } + } + + /// @notice Executes the current V2 upgrade and checks the results. + /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. + function runCurrentUpgradeV2(address _delegateCaller) public { + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, bytes("")); + } + + /// @notice Executes the current V2 upgrade and expects reverts. + /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. + /// @param _revertBytes The bytes of the revert to expect. + function runCurrentUpgradeV2(address _delegateCaller, bytes memory _revertBytes) public { + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes); + } +} + +/// @title OPContractsManagerV2_Upgrade_Test +/// @notice Tests OPContractsManagerV2.upgrade +contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestInit { + /// @notice Sets up the test. + function setUp() public override { + super.setUp(); + + // Run all past upgrades. + runPastUpgrades(chainPAO); + } + + /// @notice Tests that the upgrade function succeeds when executed normally. + function test_upgrade_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Run the upgrade test and checks + runCurrentUpgradeV2(chainPAO); + } + + /// @notice Tests that the upgrade function reverts if not called by the correct ProxyAdmin + /// owner address. + function test_upgrade_notProxyAdminOwner_reverts() public { + address delegateCaller = makeAddr("delegateCaller"); + vm.etch(delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + assertNotEq(superchainProxyAdmin.owner(), delegateCaller); + assertNotEq(proxyAdmin.owner(), delegateCaller); + + runCurrentUpgradeV2(delegateCaller, "Ownable: caller is not the owner"); + } + + /// @notice Tests that the upgrade function reverts when the superchainConfig is not at the + /// expected target version. + function test_upgrade_superchainConfigNeedsUpgrade_reverts() public { + // Force the SuperchainConfig to return an obviously outdated version. + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("0.0.0")); + + // Try upgrading an OPChain without upgrading its superchainConfig. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, + abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the user does not provide a game + /// config for each valid game type. + function test_upgrade_missingGameConfigs_reverts() public { + // Delete the Permissionless game configuration. + delete v2UpgradeInput.disputeGameConfigs[1]; + + // Expect upgrade to revert. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the user provides the game configs + /// in the wrong order. + function test_upgrade_wrongGameConfigOrder_reverts() public { + // Swap the game config order. + IOPContractsManagerV2.DisputeGameConfig memory temp = v2UpgradeInput.disputeGameConfigs[0]; + v2UpgradeInput.disputeGameConfigs[0] = v2UpgradeInput.disputeGameConfigs[1]; + v2UpgradeInput.disputeGameConfigs[1] = temp; + + // Expect upgrade to revert due to invalid game config order. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the user wants to disable the + /// PermissionedDisputeGame. + function test_upgrade_disabledPermissionedGame_reverts() public { + // Disable the PermissionedDisputeGame. + v2UpgradeInput.disputeGameConfigs[1].enabled = false; + + // Expect upgrade to revert due to missing game config. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that the V2 upgrade function rejects the ALL sentinel in permitted proxy + /// deployments. + function test_upgrade_allPermittedProxyDeployments_reverts() public { + delete v2UpgradeInput.extraInstructions; + v2UpgradeInput.extraInstructions.push( + IOPContractsManagerV2.ExtraInstruction({ key: "PermitProxyDeployment", data: abi.encode("ALL") }) + ); + + // Expect upgrade to revert due to invalid upgrade input. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, + abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts if a permitted proxy deployment is + /// required but missing. + function test_upgrade_missingPermittedProxyDeployment_reverts() public { + delete v2UpgradeInput.extraInstructions; + + // Simulate a missing DelayedWETH proxy so the upgrade path would need to deploy it. + // nosemgrep: sol-style-use-abi-encodecall + vm.mockCallRevert(address(systemConfig), abi.encodeWithSelector(ISystemConfig.delayedWETH.selector), ""); + + // Expect the upgrade to revert because the DelayedWETH proxy must load but the user did not permit + // redeployment. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns data that isn't an abi-encoded address. + /// @param _len Length of the data to generate. + function testFuzz_upgrade_proxyLoadBadReturn_reverts(uint8 _len) public { + // Ensure we do not produce a 32-byte payload, which would be interpreted as a valid + // abi-encoded address and could change the revert reason. + vm.assume(_len != 32); + + // Build an arbitrary bytes payload of length `_len`. + bytes memory bad = new bytes(_len); + for (uint256 i = 0; i < bad.length; i++) { + bad[i] = bytes1(uint8(0xAA)); + } + + // Mock the first proxy load source call to succeed but return a payload with a length + // not equal to 32 bytes, triggering OPContractsManagerV2_ProxyLoadMustLoad. + vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bad); + + // Expect a revert without any data (due to abi decoding failure). + runCurrentUpgradeV2(chainPAO, EXPECT_REVERT_WITHOUT_DATA); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns the zero address but we asked it to load. + function test_upgrade_proxyMustLoadButZeroAddress_reverts() public { + // Mock the first proxy load to succeed and return address(0) with 32 bytes, + // which triggers OPContractsManagerV2_ProxyMustLoad since _mustLoad is true in upgrade. + vm.mockCall( + address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), abi.encode(address(0)) + ); + + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + ); + } + + /// @notice Tests that the V2 upgrade function reverts when the function that attempts to load + /// an existing proxy returns an error but we asked it to load. + function test_upgrade_proxyMustLoadButReverts_reverts() public { + // Mock the first proxy load source to revert, which with _mustLoad=true triggers + // OPContractsManagerV2_ProxyMustLoad. + // nosemgrep: sol-style-use-abi-encodecall + vm.mockCallRevert(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bytes("")); + + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + ); + } +} + +/// @title OPContractsManagerV2_UpgradeSuperchain_Test +/// @notice Tests OPContractsManagerV2.upgradeSuperchain +contract OPContractsManagerV2_UpgradeSuperchain_Test is OPContractsManagerV2_Upgrade_TestInit { + /// @notice Input for the upgradeSuperchain function. + IOPContractsManagerV2.SuperchainUpgradeInput internal superchainUpgradeInput; + + /// @notice Sets up the test. + function setUp() public override { + super.setUp(); + + // Set the superchain config. + // No extra instructions, so don't set them. + superchainUpgradeInput.superchainConfig = superchainConfig; + } + + /// @notice Tests that the upgradeSuperchain function succeeds when the superchainConfig is at + /// the expected version and the delegate caller is the SuperchainConfig PAO. + function test_upgradeSuperchain_succeeds() public { + // Expect the SuperchainConfig to be upgraded. + address superchainConfigImpl = opcmV2.implementations().superchainConfigImpl; + vm.expectEmit(address(superchainConfig)); + emit Upgraded(superchainConfigImpl); + + // Do the upgrade. + DelegateCaller(superchainPAO).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } + + /// @notice Tests that the upgradeSuperchain function reverts when not delegatecalled. + function test_upgradeSuperchain_notDelegateCalled_reverts() public { + vm.expectRevert("Ownable: caller is not the owner"); + opcmV2.upgradeSuperchain(superchainUpgradeInput); + } + + /// @notice Tests that the upgradeSuperchain function reverts when the delegate caller is not + /// the superchainProxyAdmin owner. + function test_upgradeSuperchain_notProxyAdminOwner_reverts() public { + // Make a new address and turn it into a DelegateCaller. + address delegateCaller = makeAddr("delegateCaller"); + vm.etch(delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Sanity check that the address we generated isn't the superchainPAO or chainPAO. + assertNotEq(superchainPAO, delegateCaller); + assertNotEq(chainPAO, delegateCaller); + + // Should revert. + vm.expectRevert("Ownable: caller is not the owner"); + DelegateCaller(delegateCaller).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } + + /// @notice Tests that the upgradeSuperchain function reverts when the superchainConfig version + /// is the same or newer than the target version. + function test_upgradeSuperchain_superchainConfigAlreadyUpToDate_reverts() public { + ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); + + // Set the version of the superchain config to a version that is the target version. + vm.clearMockedCalls(); + + // Mock the SuperchainConfig to return a very large version. + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("99.99.99")); + + // Should revert. + vm.expectRevert(IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector); + DelegateCaller(superchainPAO).dcForward( + address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) + ); + } +} diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 8d238e9c523..542ae8f25d9 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -229,6 +229,14 @@ abstract contract Setup is FeatureFlags { } } + /// @dev Skips tests when not running against forked production network. + function skipIfNotForkTest(string memory message) public { + if (!isForkTest()) { + vm.skip(true); + console.log(string.concat("Skipping non-fork test: ", message)); + } + } + /// @dev Skips tests when running against a forked production network using the superchain ops repo. function skipIfOpsRepoTest(string memory message) public { if (forkLive.useOpsRepo()) { From 1e06f5ed7c53362ca11a200af410f0f50ee3aa1c Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 19 Nov 2025 18:00:17 -0500 Subject: [PATCH 11/17] fix: justfile path --- packages/contracts-bedrock/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 13602ea74b2..25bd737df8c 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -170,7 +170,7 @@ coverage-lcov-upgrade *ARGS: build-go-ffi # Runs coverage-lcov and coverage-lcov-upgrade and merges their output files info one file coverage-lcov-all *ARGS: just coverage-lcov {{ARGS}} && \ - just coverage-lcov-upgrade --match-contract {OPContractsManager_Upgrade_Test,OPContractsManagerV2_Upgrade_Test} {{ARGS}} && \ + just coverage-lcov-upgrade --match-contract "{OPContractsManager_Upgrade_Test,OPContractsManagerV2_Upgrade_Test}" {{ARGS}} && \ lcov -a lcov.info -a lcov-upgrade.info -o lcov-all.info ######################################################## From 6e9e700403d477370b7553c357334acb46f36d13 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Thu, 20 Nov 2025 11:07:40 -0500 Subject: [PATCH 12/17] fix: lcov issue --- packages/contracts-bedrock/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 25bd737df8c..434b768f8e4 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -170,7 +170,7 @@ coverage-lcov-upgrade *ARGS: build-go-ffi # Runs coverage-lcov and coverage-lcov-upgrade and merges their output files info one file coverage-lcov-all *ARGS: just coverage-lcov {{ARGS}} && \ - just coverage-lcov-upgrade --match-contract "{OPContractsManager_Upgrade_Test,OPContractsManagerV2_Upgrade_Test}" {{ARGS}} && \ + just coverage-lcov-upgrade --match-contract "OPContractsManager.*_Upgrade_Test" {{ARGS}} && \ lcov -a lcov.info -a lcov-upgrade.info -o lcov-all.info ######################################################## From f24ef9e16cff505fa64d444b1b855f978922bf7c Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Thu, 20 Nov 2025 11:39:36 -0500 Subject: [PATCH 13/17] fix: pr review comments --- .../L1/opcm/IOPContractsManagerV2.sol | 1 + .../scripts/deploy/Deploy.s.sol | 3 ++- .../snapshots/abi/OPContractsManagerV2.json | 17 +++++++++++++ .../L1/opcm/OPContractsManagerContainer.sol | 3 ++- .../src/L1/opcm/OPContractsManagerV2.sol | 24 ++++++++++++------- .../src/libraries/Constants.sol | 5 ++++ 6 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index bec9b4c528f..e5793a1cba4 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -80,6 +80,7 @@ interface IOPContractsManagerV2 { uint256 l2ChainId; IResourceMetering.ResourceConfig resourceConfig; DisputeGameConfig[] disputeGameConfigs; + ExtraInstruction[] extraInstructions; } struct ExtraInstruction { diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 9e743b2fa4f..3820f1810a5 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -542,7 +542,8 @@ contract Deploy is Deployer { gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), l2ChainId: cfg.l2ChainID(), resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), - disputeGameConfigs: disputeGameConfigs + disputeGameConfigs: disputeGameConfigs, + extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) }); } } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index e35e3b5e0ff..42fd4012b41 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -227,6 +227,23 @@ "internalType": "struct OPContractsManagerV2.DisputeGameConfig[]", "name": "disputeGameConfigs", "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", + "name": "extraInstructions", + "type": "tuple[]" } ], "internalType": "struct OPContractsManagerV2.FullConfig", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol index 97f1cfcd3c7..fc4d18eb151 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Libraries import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Constants } from "src/libraries/Constants.sol"; /// @title OPContractsManagerContainer /// @notice Helper contract that sits alongside the OPContractsManagerV2 contract. This contract @@ -105,6 +106,6 @@ contract OPContractsManagerContainer { /// have any code in production environments but can be made to have code in tests. /// @return True if the contract is running in a testing environment, false otherwise. function _isTestingEnvironment() internal view returns (bool) { - return address(0xbeefcafe).code.length > 0; + return Constants.TESTING_ENVIRONMENT_ADDRESS.code.length > 0; } } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 10b7d406fe4..34fe4a58608 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -92,6 +92,14 @@ contract OPContractsManagerV2 is ISemver { IDelayedWETH delayedWETH; } + /// @notice Struct that represents an additional instruction for an upgrade. Each upgrade has + /// its own set of extra upgrade instructions that may or may not be required. We use + /// this struct to keep the upgrade interface the same each time. + struct ExtraInstruction { + string key; + bytes data; + } + /// @notice Full chain management configuration. struct FullConfig { // Basic deployment configuration. @@ -113,14 +121,8 @@ contract OPContractsManagerV2 is ISemver { IResourceMetering.ResourceConfig resourceConfig; // Dispute game configuration. DisputeGameConfig[] disputeGameConfigs; - } - - /// @notice Struct that represents an additional instruction for an upgrade. Each upgrade has - /// its own set of extra upgrade instructions that may or may not be required. We use - /// this struct to keep the upgrade interface the same each time. - struct ExtraInstruction { - string key; - bytes data; + // Extra deployment instructions. + ExtraInstruction[] extraInstructions; } /// @notice Partial input required for an upgrade. @@ -588,7 +590,8 @@ contract OPContractsManagerV2 is ISemver { ), (GameType) ), - disputeGameConfigs: _upgradeInput.disputeGameConfigs + disputeGameConfigs: _upgradeInput.disputeGameConfigs, + extraInstructions: _upgradeInput.extraInstructions }); } @@ -1150,6 +1153,9 @@ contract OPContractsManagerV2 is ISemver { // Check to make sure that we're not downgrading. Downgrades aren't inherently dangerous // but we also don't test for them so we don't really know if a specific downgrade will be // dangerous or not. It's easier to just revert instead. + // NOTE: We DO allow upgrades to the same version, which makes it possible to use this + // function to both upgrade and then later perform management actions like changing + // the prestate for the fault dispute games. if ( _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) diff --git a/packages/contracts-bedrock/src/libraries/Constants.sol b/packages/contracts-bedrock/src/libraries/Constants.sol index 50ad7166974..012618f6ae9 100644 --- a/packages/contracts-bedrock/src/libraries/Constants.sol +++ b/packages/contracts-bedrock/src/libraries/Constants.sol @@ -42,6 +42,11 @@ library Constants { /// transactions. address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + /// @notice The address used by OPCM to check if the contract is running in a testing + /// environment. This address doesn't have any code on production networks but can be + /// made to have code in tests with cheatcodes. + address internal constant TESTING_ENVIRONMENT_ADDRESS = address(0xbeefcafe); + /// @notice Returns the default values for the ResourceConfig. These are the recommended values /// for a production network. function DEFAULT_RESOURCE_CONFIG() internal pure returns (IResourceMetering.ResourceConfig memory) { From b2343615ab74d23a4ba90a524cc8f7e055501f0d Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Mon, 24 Nov 2025 14:01:27 -0500 Subject: [PATCH 14/17] fix: pr comments --- .../L1/opcm/IOPContractsManagerV2.sol | 5 +- .../scripts/deploy/Deploy.s.sol | 3 +- .../src/L1/opcm/OPContractsManagerV2.sol | 67 ++++++++++--------- .../OPContractsManagerStandardValidator.t.sol | 36 ++-------- .../test/L1/opcm/OPContractsManagerV2.t.sol | 13 +++- 5 files changed, 53 insertions(+), 71 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index e5793a1cba4..48fc1aefa97 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -80,7 +80,6 @@ interface IOPContractsManagerV2 { uint256 l2ChainId; IResourceMetering.ResourceConfig resourceConfig; DisputeGameConfig[] disputeGameConfigs; - ExtraInstruction[] extraInstructions; } struct ExtraInstruction { @@ -105,8 +104,8 @@ interface IOPContractsManagerV2 { error OPContractsManagerV2_InvalidUpgradeInput(); error OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); error OPContractsManagerV2_UnsupportedGameType(); - error OPContractsManagerV2_ProxyMustLoad(); - error OPContractsManagerV2_DowngradeNotAllowed(); + error OPContractsManagerV2_ProxyMustLoad(string _name); + error OPContractsManagerV2_DowngradeNotAllowed(address _contract); error OPContractsManagerV2_InvalidUpgradeInstruction(); error OPContractsManagerV2_ConfigLoadFailed(string _name); error IdentityPrecompileCallFailed(); diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 3820f1810a5..9e743b2fa4f 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -542,8 +542,7 @@ contract Deploy is Deployer { gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), l2ChainId: cfg.l2ChainID(), resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), - disputeGameConfigs: disputeGameConfigs, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + disputeGameConfigs: disputeGameConfigs }); } } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 34fe4a58608..34d49b42497 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -48,8 +48,8 @@ import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContracts /// @dev If you were going to build a V3 of OPCM, you probably want to make this look a lot more /// like Terraform. The V2 design is trending in the direction of being Terraform-like, but it /// doesn't quite get there yet in an attempt to be a more incremental improvement over the V1 -/// design. Look at _execute, squint, and imagine that it can output an upgrade plan rather -/// than actually executing the upgrade, and then you'll see how it can be improved. +/// design. Look at _apply, squint, and imagine that it can output an upgrade plan rather than +/// actually executing the upgrade, and then you'll see how it can be improved. contract OPContractsManagerV2 is ISemver { /// @notice Configuration struct for the FaultDisputeGame. struct FaultDisputeGameConfig { @@ -121,8 +121,6 @@ contract OPContractsManagerV2 is ISemver { IResourceMetering.ResourceConfig resourceConfig; // Dispute game configuration. DisputeGameConfig[] disputeGameConfigs; - // Extra deployment instructions. - ExtraInstruction[] extraInstructions; } /// @notice Partial input required for an upgrade. @@ -164,10 +162,10 @@ contract OPContractsManagerV2 is ISemver { error OPContractsManagerV2_InvalidUpgradeInput(); /// @notice Thrown when a proxy must be loaded but couldn't be. - error OPContractsManagerV2_ProxyMustLoad(); + error OPContractsManagerV2_ProxyMustLoad(string _name); /// @notice Thrown when user attempts to downgrade a contract. - error OPContractsManagerV2_DowngradeNotAllowed(); + error OPContractsManagerV2_DowngradeNotAllowed(address _contract); /// @notice Thrown when an invalid upgrade instruction is provided. error OPContractsManagerV2_InvalidUpgradeInstruction(); @@ -184,6 +182,13 @@ contract OPContractsManagerV2 is ISemver { /// @notice The version of the OPCM contract. string public constant version = "6.0.0"; + /// @notice Special constant key for the PermittedProxyDeployment instruction. + string internal constant PERMITTED_PROXY_DEPLOYMENT_KEY = "PermittedProxyDeployment"; + + /// @notice Special constant value for the PermittedProxyDeployment instruction to permit all + /// contracts to be deployed. Only to be used for deployments. + bytes internal constant PERMIT_ALL_CONTRACTS_INSTRUCTION = bytes("ALL"); + /// @param _contractsContainer The container of blueprint and implementation contract addresses. /// @param _standardValidator The standard validator for this OPCM release. constructor( @@ -226,14 +231,15 @@ contract OPContractsManagerV2 is ISemver { function deploy(FullConfig memory _cfg) external returns (ChainContracts memory) { // Deploy is the ONLY place where we allow the "ALL" permission for proxy deployment. ExtraInstruction[] memory instructions = new ExtraInstruction[](1); - instructions[0] = ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") }); + instructions[0] = + ExtraInstruction({ key: PERMITTED_PROXY_DEPLOYMENT_KEY, data: PERMIT_ALL_CONTRACTS_INSTRUCTION }); // Load the chain contracts. ChainContracts memory cts = _loadChainContracts(ISystemConfig(address(0)), _cfg.l2ChainId, _cfg.saltMixer, instructions); // Execute the deployment. - return _execute(_cfg, cts, true); + return _apply(_cfg, cts, true); } /// @notice Upgrades a chain based on the upgrade input. @@ -261,7 +267,7 @@ contract OPContractsManagerV2 is ISemver { FullConfig memory cfg = _loadFullConfig(_inp, cts); // Execute the upgrade. - return _execute(cfg, cts, false); + return _apply(cfg, cts, false); } /////////////////////////////////////////////////////////////////////////// @@ -273,7 +279,7 @@ contract OPContractsManagerV2 is ISemver { function _assertValidUpgradeInstructions(ExtraInstruction[] memory _extraInstructions) internal pure { for (uint256 i = 0; i < _extraInstructions.length; i++) { if ( - LibString.eq(_extraInstructions[i].key, "PermittedProxyDeployment") + LibString.eq(_extraInstructions[i].key, PERMITTED_PROXY_DEPLOYMENT_KEY) && LibString.eq(string(_extraInstructions[i].data), "DelayedWETH") ) { // Unified DelayedWETH is being deployed for the first time. @@ -340,7 +346,9 @@ contract OPContractsManagerV2 is ISemver { ) ); } else { - // Special case handling, don't bother with the standard flow. + // Load-or-deploy pattern just generally doesn't make a lot of sense here. You could + // theoretically do it, but not worth the complexity. Having this special handling for + // how we load these three contracts is just cleaner/simpler. proxyAdmin = _systemConfig.proxyAdmin(); addressManager = proxyAdmin.addressManager(); systemConfig = _systemConfig; @@ -590,8 +598,7 @@ contract OPContractsManagerV2 is ISemver { ), (GameType) ), - disputeGameConfigs: _upgradeInput.disputeGameConfigs, - extraInstructions: _upgradeInput.extraInstructions + disputeGameConfigs: _upgradeInput.disputeGameConfigs }); } @@ -636,7 +643,7 @@ contract OPContractsManagerV2 is ISemver { /// @param _cts The chain contracts. /// @param _isInitialDeployment Whether or not this is an initial deployment. /// @return The chain contracts. - function _execute( + function _apply( FullConfig memory _cfg, ChainContracts memory _cts, bool _isInitialDeployment @@ -976,21 +983,21 @@ contract OPContractsManagerV2 is ISemver { /// @notice Helper function to check if a given instruction is present in a list of extra /// upgrade instructions. /// @param _instructions The list of extra upgrade instructions. - /// @param _instruction The instruction to check for. + /// @param _key The key of the instruction to check for. + /// @param _data The data of the instruction to check for. /// @return True if the instruction is present, false otherwise. function _hasInstruction( ExtraInstruction[] memory _instructions, - ExtraInstruction memory _instruction + string memory _key, + bytes memory _data ) internal pure returns (bool) { for (uint256 i = 0; i < _instructions.length; i++) { - if ( - LibString.eq(_instructions[i].key, _instruction.key) - && LibString.eq(string(_instructions[i].data), string(_instruction.data)) - ) { + if (LibString.eq(_instructions[i].key, _key) && LibString.eq(string(_instructions[i].data), string(_data))) + { return true; } } @@ -1020,6 +1027,7 @@ contract OPContractsManagerV2 is ISemver { /// @notice Helper function to load data from a source contract as bytes. /// @param _source The source contract to load the data from. /// @param _selector The selector of the function to call on the source contract. + /// @param _name The name of the field to load. /// @param _instructions The extra upgrade instructions for the data load. /// @return Data retrieved from the source contract. function _loadBytes( @@ -1068,9 +1076,10 @@ contract OPContractsManagerV2 is ISemver { internal returns (address payable) { - bool loadCanFail = _hasInstruction( - _instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes(_contractName) }) - ) || _hasInstruction(_instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") })); + // Loads are allowed to fail ONLY if the user explicitly permitted it (or if this is a + // deployment and the "ALL" permission is set). + bool loadCanFail = _hasInstruction(_instructions, PERMITTED_PROXY_DEPLOYMENT_KEY, bytes(_contractName)) + || _hasInstruction(_instructions, PERMITTED_PROXY_DEPLOYMENT_KEY, PERMIT_ALL_CONTRACTS_INSTRUCTION); // Try to load the proxy from the source. (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); @@ -1080,7 +1089,7 @@ contract OPContractsManagerV2 is ISemver { return payable(abi.decode(result, (address))); } else if (!loadCanFail) { // Load not permitted to fail but did, revert. - revert OPContractsManagerV2_ProxyMustLoad(); + revert OPContractsManagerV2_ProxyMustLoad(_contractName); } // We've failed to load, but we allowed that failure. @@ -1160,7 +1169,7 @@ contract OPContractsManagerV2 is ISemver { _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) ) { - revert OPContractsManagerV2_DowngradeNotAllowed(); + revert OPContractsManagerV2_DowngradeNotAllowed(address(_target)); } // Upgrade to StorageSetter. @@ -1175,12 +1184,4 @@ contract OPContractsManagerV2 is ISemver { // Upgrade to the implementation and call the initializer. _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); } - - /// @notice Returns true if the contract is running in a testing environment. Checks that the - /// code for the address 0xbeefcafe is not zero, which is an address that should never - /// have any code in production environments but can be made to have code in tests. - /// @return True if the contract is running in a testing environment, false otherwise. - function _isTestingEnvironment() internal view returns (bool) { - return address(0xbeefcafe).code.length > 0; - } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index bdc855e169d..2224e03070a 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -1349,11 +1349,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana vm.mockCall(address(delayedWeth), abi.encodeCall(ISemver.version, ()), abi.encode("0.0.1")); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-10,PLDG-DWETH-10,CKDG-DWETH-10", _validate(true)); - } else { - assertEq("PDDG-DWETH-10,PLDG-DWETH-10", _validate(true)); - } + assertEq("PDDG-DWETH-10,PLDG-DWETH-10,CKDG-DWETH-10", _validate(true)); } else { // One last mess here, during local tests delayedWeth refers to the contract attached to // the FaultDisputeGame, but during fork tests it refers to the one attached to the @@ -1380,11 +1376,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana ); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-20,PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); - } else { - assertEq("PDDG-DWETH-20,PLDG-DWETH-20", _validate(true)); - } + assertEq("PDDG-DWETH-20,PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); } else { if (isForkTest()) { assertEq("PDDG-DWETH-20", _validate(true)); @@ -1406,11 +1398,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana ); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-30,PLDG-DWETH-30,CKDG-DWETH-30", _validate(true)); - } else { - assertEq("PDDG-DWETH-30,PLDG-DWETH-30", _validate(true)); - } + assertEq("PDDG-DWETH-30,PLDG-DWETH-30,CKDG-DWETH-30", _validate(true)); } else { if (isForkTest()) { assertEq("PDDG-DWETH-30", _validate(true)); @@ -1430,11 +1418,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana vm.mockCall(address(delayedWeth), abi.encodeCall(IDelayedWETH.delay, ()), abi.encode(1000)); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-40,PLDG-DWETH-40,CKDG-DWETH-40", _validate(true)); - } else { - assertEq("PDDG-DWETH-40,PLDG-DWETH-40", _validate(true)); - } + assertEq("PDDG-DWETH-40,PLDG-DWETH-40,CKDG-DWETH-40", _validate(true)); } else { if (isForkTest()) { assertEq("PDDG-DWETH-40", _validate(true)); @@ -1454,11 +1438,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana vm.mockCall(address(delayedWeth), abi.encodeCall(IDelayedWETH.systemConfig, ()), abi.encode(address(0xbad))); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-50,PLDG-DWETH-50,CKDG-DWETH-50", _validate(true)); - } else { - assertEq("PDDG-DWETH-50,PLDG-DWETH-50", _validate(true)); - } + assertEq("PDDG-DWETH-50,PLDG-DWETH-50,CKDG-DWETH-50", _validate(true)); } else { if (isForkTest()) { assertEq("PDDG-DWETH-50", _validate(true)); @@ -1480,11 +1460,7 @@ contract OPContractsManagerStandardValidator_DelayedWETH_Test is OPContractsMana ); if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { - if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { - assertEq("PDDG-DWETH-60,PLDG-DWETH-60,CKDG-DWETH-60", _validate(true)); - } else { - assertEq("PDDG-DWETH-60,PLDG-DWETH-60", _validate(true)); - } + assertEq("PDDG-DWETH-60,PLDG-DWETH-60,CKDG-DWETH-60", _validate(true)); } else { if (isForkTest()) { assertEq("PDDG-DWETH-60", _validate(true)); diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 27e529cc2e9..4da0b280c1b 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -387,7 +387,8 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI // redeployment. // nosemgrep: sol-style-use-abi-encodecall runCurrentUpgradeV2( - chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + chainPAO, + abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "DelayedWETH") ); } @@ -424,7 +425,10 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI // nosemgrep: sol-style-use-abi-encodecall runCurrentUpgradeV2( - chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + chainPAO, + abi.encodeWithSelector( + IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "L1CrossDomainMessenger" + ) ); } @@ -438,7 +442,10 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI // nosemgrep: sol-style-use-abi-encodecall runCurrentUpgradeV2( - chainPAO, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector) + chainPAO, + abi.encodeWithSelector( + IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "L1CrossDomainMessenger" + ) ); } } From 98f4062a39934aaa6b9e3b4e904161c34962ef30 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Mon, 24 Nov 2025 15:21:05 -0500 Subject: [PATCH 15/17] fix: rebase tweaks --- .../snapshots/abi/OPContractsManagerV2.json | 33 ++++++++----------- .../snapshots/semver-lock.json | 6 ++-- .../test/L1/opcm/OPContractsManagerV2.t.sol | 7 +++- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index 42fd4012b41..1f10dbcd2b8 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -227,23 +227,6 @@ "internalType": "struct OPContractsManagerV2.DisputeGameConfig[]", "name": "disputeGameConfigs", "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "string", - "name": "key", - "type": "string" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", - "name": "extraInstructions", - "type": "tuple[]" } ], "internalType": "struct OPContractsManagerV2.FullConfig", @@ -717,7 +700,13 @@ "type": "error" }, { - "inputs": [], + "inputs": [ + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], "name": "OPContractsManagerV2_DowngradeNotAllowed", "type": "error" }, @@ -737,7 +726,13 @@ "type": "error" }, { - "inputs": [], + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], "name": "OPContractsManagerV2_ProxyMustLoad", "type": "error" }, diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 0287e55f792..0308e181e1c 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x7d38768f50c33e2029508c887b5bcf80119ac7d7b77b25cfb70a1b96e99f3bf9", - "sourceCodeHash": "0xfa88a187cf10da53170190b77edd2b366501302e9cf1cce06a03a1c7f1eaa1e5" + "initCodeHash": "0xe0a62b1b453241b9802c08419790da2ea39ada09e35d19cb56bd236c47a76163", + "sourceCodeHash": "0x1ce1a3bd6baa338d16d60e65e1ab2bd39e33283a28002ac456eb52a4c8b3c7e4" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x0c8b15453d0f0bc5d9af07f104505e0bbb2b358f0df418289822fb73a8652b30", @@ -271,4 +271,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 4da0b280c1b..d8dd113f917 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -515,7 +515,12 @@ contract OPContractsManagerV2_UpgradeSuperchain_Test is OPContractsManagerV2_Upg vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("99.99.99")); // Should revert. - vm.expectRevert(IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector); + // nosemgrep: sol-style-use-abi-encodecall + vm.expectRevert( + abi.encodeWithSelector( + IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, address(superchainConfig) + ) + ); DelegateCaller(superchainPAO).dcForward( address(opcmV2), abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (superchainUpgradeInput)) ); From b7c2b852a69e7fdf941a5d6ecf37df2a29bea90c Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Mon, 24 Nov 2025 15:41:20 -0500 Subject: [PATCH 16/17] fix: contract verification failure --- op-deployer/pkg/deployer/verify/artifacts.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 6b1e87cf9bb..ccec8fab7f1 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -43,6 +43,8 @@ var contractNameExceptions = map[string]string{ "OpcmUpgrader": "OPContractsManager.sol/OPContractsManagerUpgrader.json", "OpcmInteropMigrator": "OPContractsManager.sol/OPContractsManagerInteropMigrator.json", "OpcmStandardValidator": "OPContractsManagerStandardValidator.sol/OPContractsManagerStandardValidator.json", + "OpcmV2": "OPContractsManagerV2.sol/OPContractsManagerV2.json", + "OpcmContractsContainerV2": "OPContractsManagerContainer.sol/OPContractsManagerContainer.json", "Mips": "MIPS64.sol/MIPS64.json", "EthLockbox": "ETHLockbox.sol/ETHLockbox.json", } From 8fb6572a2b752763fdee95c30fbda3f25d7d5053 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Mon, 24 Nov 2025 16:37:06 -0500 Subject: [PATCH 17/17] fix: last few ci issues --- op-deployer/pkg/deployer/verify/artifacts.go | 2 +- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index ccec8fab7f1..1f64b0e5ee1 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -44,7 +44,7 @@ var contractNameExceptions = map[string]string{ "OpcmInteropMigrator": "OPContractsManager.sol/OPContractsManagerInteropMigrator.json", "OpcmStandardValidator": "OPContractsManagerStandardValidator.sol/OPContractsManagerStandardValidator.json", "OpcmV2": "OPContractsManagerV2.sol/OPContractsManagerV2.json", - "OpcmContractsContainerV2": "OPContractsManagerContainer.sol/OPContractsManagerContainer.json", + "OpcmContainer": "OPContractsManagerContainer.sol/OPContractsManagerContainer.json", "Mips": "MIPS64.sol/MIPS64.json", "EthLockbox": "ETHLockbox.sol/ETHLockbox.json", } diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 0308e181e1c..34b04976007 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0xe0a62b1b453241b9802c08419790da2ea39ada09e35d19cb56bd236c47a76163", - "sourceCodeHash": "0x1ce1a3bd6baa338d16d60e65e1ab2bd39e33283a28002ac456eb52a4c8b3c7e4" + "initCodeHash": "0x6fd82aaec43858b34bd093e29bbacb659a95817d506de948aebd3c84e6d2a00a", + "sourceCodeHash": "0x5f63555e12cb8e27ce5c1511e986550bf2f1b9769e314223a7324b8dcfa29523" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x0c8b15453d0f0bc5d9af07f104505e0bbb2b358f0df418289822fb73a8652b30", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index bca3d8e7596..31bfb0989e8 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -2236,9 +2236,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 5.7.0 + /// @custom:semver 5.7.1 function version() public pure virtual returns (string memory) { - return "5.7.0"; + return "5.7.1"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder;