diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index ec0bc620d7dc0..64884131f5a8f 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -51,6 +51,7 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -169,50 +170,66 @@ func TestEndToEndBootstrapApply(t *testing.T) { func TestEndToEndBootstrapApplyWithUpgrade(t *testing.T) { op_e2e.InitParallel(t) - lgr := testlog.Logger(t, slog.LevelDebug) + tests := []struct { + name string + devFeature common.Hash + }{ + {"default", common.Hash{}}, + {"opcm-v2", deployer.OPCMV2DevFlag}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op_e2e.InitParallel(t) + lgr := testlog.Logger(t, slog.LevelDebug) - forkedL1, stopL1, err := devnet.NewForkedSepolia(lgr) - require.NoError(t, err) - pkHex, _, _ := shared.DefaultPrivkey(t) - t.Cleanup(func() { - require.NoError(t, stopL1()) - }) - loc, afactsFS := testutil.LocalArtifacts(t) - testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + forkedL1, stopL1, err := devnet.NewForkedSepolia(lgr) + require.NoError(t, err) + pkHex, _, _ := shared.DefaultPrivkey(t) + t.Cleanup(func() { + require.NoError(t, stopL1()) + }) + loc, afactsFS := testutil.LocalArtifacts(t) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) - superchain, err := standard.SuperchainFor(11155111) - require.NoError(t, err) + superchain, err := standard.SuperchainFor(11155111) + require.NoError(t, err) - superchainProxyAdmin, err := standard.SuperchainProxyAdminAddrFor(11155111) - require.NoError(t, err) + superchainProxyAdmin, err := standard.SuperchainProxyAdminAddrFor(11155111) + require.NoError(t, err) - superchainProxyAdminOwner, err := standard.L1ProxyAdminOwner(11155111) - require.NoError(t, err) + superchainProxyAdminOwner, err := standard.L1ProxyAdminOwner(11155111) + require.NoError(t, err) - cfg := bootstrap.ImplementationsConfig{ - L1RPCUrl: forkedL1.RPCUrl(), - PrivateKey: pkHex, - ArtifactsLocator: loc, - MIPSVersion: int(standard.MIPSVersion), - WithdrawalDelaySeconds: standard.WithdrawalDelaySeconds, - MinProposalSizeBytes: standard.MinProposalSizeBytes, - ChallengePeriodSeconds: standard.ChallengePeriodSeconds, - ProofMaturityDelaySeconds: standard.ProofMaturityDelaySeconds, - DisputeGameFinalityDelaySeconds: standard.DisputeGameFinalityDelaySeconds, - DevFeatureBitmap: common.Hash{}, - SuperchainConfigProxy: superchain.SuperchainConfigAddr, - ProtocolVersionsProxy: superchain.ProtocolVersionsAddr, - L1ProxyAdminOwner: superchainProxyAdminOwner, - SuperchainProxyAdmin: superchainProxyAdmin, - CacheDir: testCacheDir, - Logger: lgr, - Challenger: common.Address{'C'}, - FaultGameMaxGameDepth: standard.DisputeMaxGameDepth, - FaultGameSplitDepth: standard.DisputeSplitDepth, - FaultGameClockExtension: standard.DisputeClockExtension, - FaultGameMaxClockDuration: standard.DisputeMaxClockDuration, + cfg := bootstrap.ImplementationsConfig{ + L1RPCUrl: forkedL1.RPCUrl(), + PrivateKey: pkHex, + ArtifactsLocator: loc, + MIPSVersion: int(standard.MIPSVersion), + WithdrawalDelaySeconds: standard.WithdrawalDelaySeconds, + MinProposalSizeBytes: standard.MinProposalSizeBytes, + ChallengePeriodSeconds: standard.ChallengePeriodSeconds, + ProofMaturityDelaySeconds: standard.ProofMaturityDelaySeconds, + DisputeGameFinalityDelaySeconds: standard.DisputeGameFinalityDelaySeconds, + DevFeatureBitmap: tt.devFeature, + SuperchainConfigProxy: superchain.SuperchainConfigAddr, + ProtocolVersionsProxy: superchain.ProtocolVersionsAddr, + L1ProxyAdminOwner: superchainProxyAdminOwner, + SuperchainProxyAdmin: superchainProxyAdmin, + CacheDir: testCacheDir, + Logger: lgr, + Challenger: common.Address{'C'}, + FaultGameMaxGameDepth: standard.DisputeMaxGameDepth, + FaultGameSplitDepth: standard.DisputeSplitDepth, + FaultGameClockExtension: standard.DisputeClockExtension, + FaultGameMaxClockDuration: standard.DisputeMaxClockDuration, + } + if deployer.IsDevFeatureEnabled(tt.devFeature, deployer.OPCMV2DevFlag) { + cfg.DevFeatureBitmap = deployer.OPCMV2DevFlag + } + + runEndToEndBootstrapAndApplyUpgradeTest(t, afactsFS, cfg) + }) } - runEndToEndBootstrapAndApplyUpgradeTest(t, afactsFS, cfg) } func TestEndToEndApply(t *testing.T) { @@ -845,10 +862,14 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat // Then run the OPCM upgrade t.Run("upgrade opcm", func(t *testing.T) { + if deployer.IsDevFeatureEnabled(implementationsConfig.DevFeatureBitmap, deployer.OPCMV2DevFlag) { + t.Skip("Skipping OPCM upgrade for OPCM V2") + return + } upgradeConfig := embedded.UpgradeOPChainInput{ Prank: superchainProxyAdminOwner, Opcm: impls.Opcm, - EncodedChainConfigs: []embedded.OPChainConfig{ + ChainConfigs: []embedded.OPChainConfig{ { SystemConfigProxy: common.HexToAddress("034edD2A225f7f429A63E0f1D2084B9E0A93b538"), CannonPrestate: common.Hash{'C', 'A', 'N', 'N', 'O', 'N'}, @@ -862,6 +883,122 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat err = embedded.DefaultUpgrader.Upgrade(host, upgradeConfigBytes) require.NoError(t, err, "OPCM upgrade should succeed") }) + t.Run("upgrade opcm v2", func(t *testing.T) { + if !deployer.IsDevFeatureEnabled(implementationsConfig.DevFeatureBitmap, deployer.OPCMV2DevFlag) { + t.Skip("Skipping OPCM V2 upgrade for non-OPCM V2 dev feature") + return + } + require.NotEqual(t, common.Address{}, impls.OpcmV2, "OpcmV2 address should not be zero") + t.Logf("Using OpcmV2 at address: %s", impls.OpcmV2.Hex()) + t.Logf("Using OpcmUtils at address: %s", impls.OpcmUtils.Hex()) + t.Logf("Using OpcmContainer at address: %s", impls.OpcmContainer.Hex()) + + // Verify OPCM V2 has code deployed + opcmCode, err := versionClient.CodeAt(ctx, impls.OpcmV2, nil) + require.NoError(t, err) + require.NotEmpty(t, opcmCode, "OPCM V2 should have code deployed") + t.Logf("OPCM V2 code size: %d bytes", len(opcmCode)) + + // Verify OpcmUtils has code deployed + utilsCode, err := versionClient.CodeAt(ctx, impls.OpcmUtils, nil) + require.NoError(t, err) + require.NotEmpty(t, utilsCode, "OpcmUtils should have code deployed") + t.Logf("OpcmUtils code size: %d bytes", len(utilsCode)) + + // Verify OpcmContainer has code deployed + containerCode, err := versionClient.CodeAt(ctx, impls.OpcmContainer, nil) + require.NoError(t, err) + require.NotEmpty(t, containerCode, "OpcmContainer should have code deployed") + t.Logf("OpcmContainer code size: %d bytes", len(containerCode)) + + // First, upgrade the superchain with V2 + t.Run("upgrade superchain v2", func(t *testing.T) { + superchainUpgradeConfig := embedded.UpgradeSuperchainConfigInput{ + Prank: superchainProxyAdminOwner, + Opcm: impls.OpcmV2, + SuperchainConfig: implementationsConfig.SuperchainConfigProxy, + ExtraInstructions: []embedded.ExtraInstruction{ + { + Key: "PermittedProxyDeployment", + Data: []byte("DelayedWETH"), + }, + }, + } + err := embedded.UpgradeSuperchainConfig(host, superchainUpgradeConfig) + if err != nil { + t.Logf("Superchain upgrade may have failed (could already be upgraded): %v", err) + } else { + t.Log("Superchain V2 upgrade succeeded") + } + }) + + // Then test upgrade on the V2-deployed chain + t.Run("upgrade chain v2", func(t *testing.T) { + // ABI-encode game args for FaultDisputeGameConfig{absolutePrestate} + bytes32Type, err := abi.NewType("bytes32", "", nil) + require.NoError(t, err) + addressType, err := abi.NewType("address", "", nil) + require.NoError(t, err) + + // FaultDisputeGameConfig just needs absolutePrestate (bytes32) + testPrestate := common.Hash{'P', 'R', 'E', 'S', 'T', 'A', 'T', 'E'} + cannonArgs, err := abi.Arguments{{Type: bytes32Type}}.Pack(testPrestate) + require.NoError(t, err) + + // PermissionedDisputeGameConfig needs absolutePrestate, proposer, challenger + testProposer := common.Address{'P'} + testChallenger := common.Address{'C'} + permissionedArgs, err := abi.Arguments{ + {Type: bytes32Type}, + {Type: addressType}, + {Type: addressType}, + }.Pack(testPrestate, testProposer, testChallenger) + require.NoError(t, err) + + upgradeConfig := embedded.UpgradeOPChainInput{ + Prank: superchainProxyAdminOwner, + Opcm: impls.OpcmV2, + UpgradeInputV2: &embedded.UpgradeInputV2{ + SystemConfig: common.HexToAddress("034edD2A225f7f429A63E0f1D2084B9E0A93b538"), + DisputeGameConfigs: []embedded.DisputeGameConfig{ + { + Enabled: true, + InitBond: big.NewInt(1000000000000000000), + GameType: embedded.GameTypeCannon, + GameArgs: cannonArgs, + }, + { + Enabled: true, + InitBond: big.NewInt(1000000000000000000), + GameType: embedded.GameTypePermissionedCannon, + GameArgs: permissionedArgs, + }, + { + Enabled: false, + InitBond: big.NewInt(0), + GameType: embedded.GameTypeCannonKona, + GameArgs: []byte{}, // Disabled games don't need args + }, + }, + ExtraInstructions: []embedded.ExtraInstruction{ + { + Key: "PermittedProxyDeployment", + Data: []byte("DelayedWETH"), + }, + { + Key: "overrides.cfg.useCustomGasToken", + Data: make([]byte, 32), + }, + }, + }, + } + + upgradeConfigBytes, err := json.Marshal(upgradeConfig) + require.NoError(t, err, "UpgradeOPChainV2Input should marshal to JSON") + err = embedded.DefaultUpgrader.Upgrade(host, upgradeConfigBytes) + require.NoError(t, err, "OPCM V2 chain upgrade should succeed") + }) + }) }) } diff --git a/op-deployer/pkg/deployer/upgrade/embedded/testdata/config.json b/op-deployer/pkg/deployer/upgrade/embedded/testdata/config.json new file mode 100644 index 0000000000000..07e0c2046f106 --- /dev/null +++ b/op-deployer/pkg/deployer/upgrade/embedded/testdata/config.json @@ -0,0 +1,16 @@ +{ + "prank": "0x1Eb2fFc903729a0F03966B917003800b145F56E2", + "opcm": "0xaf334f4537e87f5155d135392ff6d52f1866465e", + "upgradeInput": { + "systemConfig": "0x034edD2A225f7f429A63E0f1D2084B9E0A93b538", + "disputeGameConfigs": [ + { + "enabled": true, + "initBond": "0x0", + "gameType": 0, + "gameArgs": "0x" + } + ], + "extraInstructions": [] + } +} diff --git a/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go b/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go index 9ae6f07c63d47..b36d805e3f220 100644 --- a/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go +++ b/op-deployer/pkg/deployer/upgrade/embedded/upgrade.go @@ -3,6 +3,7 @@ package embedded import ( "encoding/json" "fmt" + "math/big" "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" @@ -11,34 +12,108 @@ import ( "github.com/lmittmann/w3" ) +// ScriptInput represents the input struct that is actually passed to the script. +// It contains the prank, opcm, and upgrade input. +type ScriptInput struct { + Prank common.Address `evm:"prank"` + Opcm common.Address `evm:"opcm"` + UpgradeInput []byte `evm:"upgradeInput"` +} + +// UpgradeOPChainInput represents the struct that is read from the config file. +// It contains both fields for the old and new upgrade input. type UpgradeOPChainInput struct { - Prank common.Address `json:"prank"` - Opcm common.Address `json:"opcm"` - EncodedChainConfigs []OPChainConfig `evm:"-" json:"chainConfigs"` + Prank common.Address `json:"prank"` + Opcm common.Address `json:"opcm"` + ChainConfigs []OPChainConfig `json:"chainConfigs,omitempty"` + UpgradeInputV2 *UpgradeInputV2 `json:"upgradeInput,omitempty"` +} + +// UpgradeInputV2 represents the new upgrade input in OPCM v2. +type UpgradeInputV2 struct { + SystemConfig common.Address `json:"systemConfig"` + DisputeGameConfigs []DisputeGameConfig `json:"disputeGameConfigs"` + ExtraInstructions []ExtraInstruction `json:"extraInstructions"` +} + +// DisputeGameConfig represents the configuration for a dispute game. +type DisputeGameConfig struct { + Enabled bool `json:"enabled"` + InitBond *big.Int `json:"initBond"` + GameType GameType `json:"gameType"` + GameArgs []byte `json:"gameArgs"` } +// ExtraInstruction represents an additional upgrade instruction for the upgrade on OPCM v2. +type ExtraInstruction struct { + Key string `json:"key"` + Data []byte `json:"data"` +} + +// GameType represents the type of dispute game. +type GameType uint32 + +const ( + GameTypeCannon GameType = 0 + GameTypePermissionedCannon GameType = 1 + GameTypeCannonKona GameType = 8 +) + +// OPChainConfig represents the configuration for an OP Chain upgrade on OPCM v1. type OPChainConfig struct { SystemConfigProxy common.Address `json:"systemConfigProxy"` CannonPrestate common.Hash `json:"cannonPrestate"` CannonKonaPrestate common.Hash `json:"cannonKonaPrestate"` } +var upgradeInputEncoder = w3.MustNewFunc("dummy((address systemConfig,(bool enabled,uint256 initBond,uint32 gameType,bytes gameArgs)[] disputeGameConfigs,(string key,bytes data)[] extraInstructions))", + "") + var opChainConfigEncoder = w3.MustNewFunc("dummy((address systemConfigProxy,bytes32 cannonPrestate,bytes32 cannonKonaPrestate)[])", "") -func (u *UpgradeOPChainInput) OpChainConfigs() ([]byte, error) { - data, err := opChainConfigEncoder.EncodeArgs(u.EncodedChainConfigs) +func (u *UpgradeOPChainInput) EncodedOpChainConfigs() ([]byte, error) { + data, err := opChainConfigEncoder.EncodeArgs(u.ChainConfigs) if err != nil { return nil, fmt.Errorf("failed to encode chain configs: %w", err) } return data[4:], nil } +func (u *UpgradeOPChainInput) EncodedUpgradeInputV2() ([]byte, error) { + data, err := upgradeInputEncoder.EncodeArgs(u.UpgradeInputV2) + if err != nil { + return nil, fmt.Errorf("failed to encode upgrade input: %w", err) + } + + return data[4:], nil +} + type UpgradeOPChain struct { Run func(input common.Address) } func Upgrade(host *script.Host, input UpgradeOPChainInput) error { - return opcm.RunScriptVoid(host, input, "UpgradeOPChain.s.sol", "UpgradeOPChain") + // We need to check which of the two versions of the input we are using. + var encodedUpgradeInput []byte + var encodedError error + if input.UpgradeInputV2 == nil && len(input.ChainConfigs) == 0 { + return fmt.Errorf("failed to read either an upgrade input or config array") + } else if input.UpgradeInputV2 != nil { + encodedUpgradeInput, encodedError = input.EncodedUpgradeInputV2() + } else { + encodedUpgradeInput, encodedError = input.EncodedOpChainConfigs() + } + + if encodedError != nil { + return encodedError + } + + scriptInput := ScriptInput{ + Prank: input.Prank, + Opcm: input.Opcm, + UpgradeInput: encodedUpgradeInput, + } + return opcm.RunScriptVoid[ScriptInput](host, scriptInput, "UpgradeOPChain.s.sol", "UpgradeOPChain") } type Upgrader struct{} diff --git a/op-deployer/pkg/deployer/upgrade/embedded/upgrade_superchainconfig.go b/op-deployer/pkg/deployer/upgrade/embedded/upgrade_superchainconfig.go index a1adb77cbb224..976534fe23576 100644 --- a/op-deployer/pkg/deployer/upgrade/embedded/upgrade_superchainconfig.go +++ b/op-deployer/pkg/deployer/upgrade/embedded/upgrade_superchainconfig.go @@ -9,9 +9,10 @@ import ( ) type UpgradeSuperchainConfigInput struct { - Prank common.Address `json:"prank"` - Opcm common.Address `json:"opcm"` - SuperchainConfig common.Address `json:"superchainConfig"` + Prank common.Address `json:"prank"` + Opcm common.Address `json:"opcm"` + SuperchainConfig common.Address `json:"superchainConfig"` + ExtraInstructions []ExtraInstruction `json:"extraInstructions,omitempty"` } type UpgradeSuperchainConfigScript script.DeployScriptWithoutOutput[UpgradeSuperchainConfigInput] diff --git a/op-deployer/pkg/deployer/upgrade/embedded/upgrade_test.go b/op-deployer/pkg/deployer/upgrade/embedded/upgrade_test.go new file mode 100644 index 0000000000000..a6e3e45bf8382 --- /dev/null +++ b/op-deployer/pkg/deployer/upgrade/embedded/upgrade_test.go @@ -0,0 +1,88 @@ +package embedded + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestUpgradeOPChainInput_UpgradeInput(t *testing.T) { + input := &UpgradeOPChainInput{ + Prank: common.Address{0xaa}, + Opcm: common.Address{0xbb}, + UpgradeInputV2: &UpgradeInputV2{ + SystemConfig: common.Address{0x01}, + DisputeGameConfigs: []DisputeGameConfig{ + { + Enabled: true, + InitBond: big.NewInt(1000), + GameType: GameTypeCannon, + GameArgs: []byte{0x01, 0x02, 0x03}, + }, + }, + ExtraInstructions: []ExtraInstruction{ + { + Key: "test-key", + Data: []byte{0x04, 0x05, 0x06}, + }, + }, + }, + } + data, err := input.EncodedUpgradeInputV2() + + require.NoError(t, err) + require.NotEmpty(t, data) + + expected := "0000000000000000000000000000000000000000000000000000000000000020" + // offset to tuple + "0000000000000000000000000100000000000000000000000000000000000000" + // systemConfig + "0000000000000000000000000000000000000000000000000000000000000060" + // offset to disputeGameConfigs + "0000000000000000000000000000000000000000000000000000000000000160" + // offset to extraInstructions + "0000000000000000000000000000000000000000000000000000000000000001" + // disputeGameConfigs.length + "0000000000000000000000000000000000000000000000000000000000000020" + // offset to disputeGameConfigs[0] + "0000000000000000000000000000000000000000000000000000000000000001" + // disputeGameConfigs[0].enabled + "00000000000000000000000000000000000000000000000000000000000003e8" + // disputeGameConfigs[0].initBond (1000) + "0000000000000000000000000000000000000000000000000000000000000000" + // disputeGameConfigs[0].gameType + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to gameArgs + "0000000000000000000000000000000000000000000000000000000000000003" + // gameArgs.length + "0102030000000000000000000000000000000000000000000000000000000000" + // gameArgs data + "0000000000000000000000000000000000000000000000000000000000000001" + // extraInstructions.length + "0000000000000000000000000000000000000000000000000000000000000020" + // offset to extraInstructions[0] + "0000000000000000000000000000000000000000000000000000000000000040" + // offset to key + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to data + "0000000000000000000000000000000000000000000000000000000000000008" + // key.length + "746573742d6b65790000000000000000000000000000000000000000000000" + // "test-key" + "00" + // padding + "0000000000000000000000000000000000000000000000000000000000000003" + // data.length + "0405060000000000000000000000000000000000000000000000000000000000" // data + + require.Equal(t, expected, hex.EncodeToString(data)) +} + +func TestUpgradeOPChainInput_OpChainConfigs(t *testing.T) { + input := &UpgradeOPChainInput{ + Prank: common.Address{0xaa}, + Opcm: common.Address{0xbb}, + ChainConfigs: []OPChainConfig{ + { + SystemConfigProxy: common.Address{0x01}, + CannonPrestate: common.Hash{0xaa}, + CannonKonaPrestate: common.Hash{0xbb}, + }, + }, + } + data, err := input.EncodedOpChainConfigs() + + require.NoError(t, err) + require.NotEmpty(t, data) + + expected := "0000000000000000000000000000000000000000000000000000000000000020" + // offset to array + "0000000000000000000000000000000000000000000000000000000000000001" + // array.length + "0000000000000000000000000100000000000000000000000000000000000000" + // systemConfigProxy + "aa00000000000000000000000000000000000000000000000000000000000000" + // cannonPrestate + "bb00000000000000000000000000000000000000000000000000000000000000" // cannonKonaPrestate + + require.Equal(t, expected, hex.EncodeToString(data)) +} diff --git a/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol index d238b55edc23c..d26fa116559ed 100644 --- a/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol @@ -3,26 +3,47 @@ pragma solidity ^0.8.0; import { Script } from "forge-std/Script.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; +import { OPContractsManagerV2 } from "src/L1/opcm/OPContractsManagerV2.sol"; import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; contract UpgradeOPChainInput is BaseDeployIO { address internal _prank; - OPContractsManager internal _opcm; - bytes _opChainConfigs; + address internal _opcm; + /// @notice The upgrade input is stored as opaque bytes to allow storing both OPCM v1 and v2 upgrade inputs. + bytes _upgradeInput; // Setter for OPContractsManager type function set(bytes4 _sel, address _value) public { require(address(_value) != address(0), "UpgradeOPCMInput: cannot set zero address"); if (_sel == this.prank.selector) _prank = _value; - else if (_sel == this.opcm.selector) _opcm = OPContractsManager(_value); + else if (_sel == this.opcm.selector) _opcm = _value; else revert("UpgradeOPCMInput: unknown selector"); } + /// @notice Sets the upgrade input using the OPContractsManager.OpChainConfig[] type, + /// this is used when upgrading chains using OPCM v1. + /// @param _sel The selector of the field to set. + /// @param _value The value to set. function set(bytes4 _sel, OPContractsManager.OpChainConfig[] memory _value) public { require(_value.length > 0, "UpgradeOPCMInput: cannot set empty array"); - if (_sel == this.opChainConfigs.selector) _opChainConfigs = abi.encode(_value); + if (_sel == this.upgradeInput.selector) _upgradeInput = abi.encode(_value); + else revert("UpgradeOPCMInput: unknown selector"); + } + + /// @notice Sets the upgrade input using the OPContractsManagerV2.UpgradeInput type, + /// this is used when upgrading chains using OPCM v2. + /// Minimal validation is performed, relying on the OPCM v2 contract to perform the proper validation. + /// This is done to avoid duplicating the validation logic in the script. + /// @param _sel The selector of the field to set. + /// @param _value The value to set. + function set(bytes4 _sel, OPContractsManagerV2.UpgradeInput memory _value) public { + require(address(_value.systemConfig) != address(0), "UpgradeOPCMInput: cannot set zero address"); + require(_value.disputeGameConfigs.length > 0, "UpgradeOPCMInput: cannot set empty dispute game configs array"); + + if (_sel == this.upgradeInput.selector) _upgradeInput = abi.encode(_value); else revert("UpgradeOPCMInput: unknown selector"); } @@ -31,45 +52,91 @@ contract UpgradeOPChainInput is BaseDeployIO { return _prank; } - function opcm() public view returns (OPContractsManager) { - require(address(_opcm) != address(0), "UpgradeOPCMInput: not set"); + function opcm() public view returns (address) { + require(_opcm != address(0), "UpgradeOPCMInput: not set"); return _opcm; } - function opChainConfigs() public view returns (bytes memory) { - require(_opChainConfigs.length > 0, "UpgradeOPCMInput: not set"); - return _opChainConfigs; + function upgradeInput() public view returns (bytes memory) { + require(_upgradeInput.length > 0, "UpgradeOPCMInput: not set"); + return _upgradeInput; } } contract UpgradeOPChain is Script { function run(UpgradeOPChainInput _uoci) external { - OPContractsManager opcm = _uoci.opcm(); - OPContractsManager.OpChainConfig[] memory opChainConfigs = - abi.decode(_uoci.opChainConfigs(), (OPContractsManager.OpChainConfig[])); + address opcm = _uoci.opcm(); + + // First, we need to check what version of OPCM is being used. + bool useOPCMv2 = OPContractsManager(opcm).isDevFeatureEnabled(DevFeatures.OPCM_V2); // Etch DummyCaller contract. This contract is used to mimic the contract that is used // as the source of the delegatecall to the OPCM. In practice this will be the governance // 2/2 or similar. address prank = _uoci.prank(); - bytes memory code = vm.getDeployedCode("UpgradeOPChain.s.sol:DummyCaller"); + bytes memory code = _getDummyCallerCode(useOPCMv2); vm.etch(prank, code); vm.store(prank, bytes32(0), bytes32(uint256(uint160(address(opcm))))); vm.label(prank, "DummyCaller"); // Call into the DummyCaller. This will perform the delegatecall under the hood and // return the result. - vm.broadcast(msg.sender); - (bool success,) = DummyCaller(prank).upgrade(opChainConfigs); + (bool success,) = _upgrade(prank, useOPCMv2, _uoci.upgradeInput()); require(success, "UpgradeChain: upgrade failed"); } + + /// @notice Helper function to get the proper dummy caller code based on the OPCM version. + /// @param _useOPCMv2 Whether to use OPCM v2. + /// @return code The code of the dummy caller. + function _getDummyCallerCode(bool _useOPCMv2) internal view returns (bytes memory) { + if (_useOPCMv2) return vm.getDeployedCode("UpgradeOPChain.s.sol:DummyCallerV2"); + else return vm.getDeployedCode("UpgradeOPChain.s.sol:DummyCallerV1"); + } + + /// @notice Helper function to upgrade the OPCM based on the OPCM version. Performs the decoding of the upgrade + /// input and the delegatecall to the OPCM. + /// @param _prank The address of the dummy caller contract. + /// @param _useOPCMv2 Whether to use OPCM v2. + /// @param _upgradeInput The upgrade input. + /// @return success Whether the upgrade succeeded. + /// @return result The result of the upgrade (bool, bytes memory). + function _upgrade( + address _prank, + bool _useOPCMv2, + bytes memory _upgradeInput + ) + internal + returns (bool, bytes memory) + { + vm.broadcast(msg.sender); + if (_useOPCMv2) { + return DummyCallerV2(_prank).upgrade(abi.decode(_upgradeInput, (OPContractsManagerV2.UpgradeInput))); + } else { + return DummyCallerV1(_prank).upgrade(abi.decode(_upgradeInput, (OPContractsManager.OpChainConfig[]))); + } + } +} +/// @title DummyCallerV2 +/// @notice This contract is used to mimic the contract that is used as the source of the delegatecall to the OPCM v2. +/// Uses OPContractsManagerV2.UpgradeInput type for the upgrade input. + +contract DummyCallerV2 { + address internal _opcmAddr; + + function upgrade(OPContractsManagerV2.UpgradeInput memory _upgradeInput) external returns (bool, bytes memory) { + bytes memory data = abi.encodeCall(OPContractsManagerV2.upgrade, _upgradeInput); + (bool success, bytes memory result) = _opcmAddr.delegatecall(data); + return (success, result); + } } +/// @notice This contract is used to mimic the contract that is used as the source of the delegatecall to the OPCM v1. +/// Uses OPContractsManager.OpChainConfig[] type for the upgrade input. -contract DummyCaller { +contract DummyCallerV1 { address internal _opcmAddr; function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external returns (bool, bytes memory) { - bytes memory data = abi.encodeCall(DummyCaller.upgrade, _opChainConfigs); + bytes memory data = abi.encodeCall(OPContractsManager.upgrade, _opChainConfigs); (bool success, bytes memory result) = _opcmAddr.delegatecall(data); return (success, result); } diff --git a/packages/contracts-bedrock/scripts/deploy/UpgradeSuperchainConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/UpgradeSuperchainConfig.s.sol index 0f4d110f81174..ed30491e839e4 100644 --- a/packages/contracts-bedrock/scripts/deploy/UpgradeSuperchainConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/UpgradeSuperchainConfig.s.sol @@ -3,13 +3,17 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; contract UpgradeSuperchainConfig is Script { struct Input { address prank; - IOPContractsManager opcm; + address opcm; ISuperchainConfig superchainConfig; + IOPContractsManagerUtils.ExtraInstruction[] extraInstructions; } /// @notice Delegate calls upgradeSuperchainConfig on the OPCM from the input.prank address. @@ -17,34 +21,64 @@ contract UpgradeSuperchainConfig is Script { // Make sure the input is valid assertValidInput(_input); - IOPContractsManager opcm = _input.opcm; + // Both OPCM v1 and v2 implement the isDevFeatureEnabled function. + bool useOPCMv2 = IOPContractsManager(_input.opcm).isDevFeatureEnabled(DevFeatures.OPCM_V2); + + address opcm = _input.opcm; // Etch DummyCaller contract. This contract is used to mimic the contract that is used // as the source of the delegatecall to the OPCM. In practice this will be the governance // 2/2 or similar. address prank = _input.prank; - bytes memory code = vm.getDeployedCode("UpgradeSuperchainConfig.s.sol:DummyCaller"); + bytes memory code = _getDummyCallerCode(useOPCMv2); vm.etch(prank, code); - vm.store(prank, bytes32(0), bytes32(uint256(uint160(address(opcm))))); + vm.store(prank, bytes32(0), bytes32(uint256(uint160(opcm)))); vm.label(prank, "DummyCaller"); - ISuperchainConfig superchainConfig = _input.superchainConfig; - - // Call into the DummyCaller to perform the delegatecall - vm.broadcast(msg.sender); - - (bool success,) = DummyCaller(prank).upgradeSuperchainConfig(superchainConfig); + (bool success,) = _upgrade(prank, useOPCMv2, _input); require(success, "UpgradeSuperchainConfig: upgradeSuperchainConfig failed"); } /// @notice Asserts that the input is valid. function assertValidInput(Input memory _input) internal pure { + // Note: Intentionally not checking extra instructions for OPCM v2 as they are not required in some upgrades. + // This responsibility is delegated to the OPCM v2 contract. require(_input.prank != address(0), "UpgradeSuperchainConfig: prank not set"); require(address(_input.opcm) != address(0), "UpgradeSuperchainConfig: opcm not set"); require(address(_input.superchainConfig) != address(0), "UpgradeSuperchainConfig: superchainConfig not set"); } + + /// @notice Helper function to get the proper dummy caller code based on the OPCM version. + /// @param _useOPCMv2 Whether to use OPCM v2. + /// @return code The code of the dummy caller. + function _getDummyCallerCode(bool _useOPCMv2) internal view returns (bytes memory) { + if (_useOPCMv2) return vm.getDeployedCode("UpgradeSuperchainConfig.s.sol:DummyCallerV2"); + else return vm.getDeployedCode("UpgradeSuperchainConfig.s.sol:DummyCaller"); + } + + /// @notice Helper function to upgrade the OPCM based on the OPCM version. Performs the decoding of the upgrade + /// input and the delegatecall to the OPCM. + /// @param _prank The address of the dummy caller contract. + /// @param _useOPCMv2 Whether to use OPCM v2. + /// @param _input The input. + /// @return success Whether the upgrade succeeded. + /// @return result The result of the upgrade (bool, bytes memory). + function _upgrade(address _prank, bool _useOPCMv2, Input memory _input) internal returns (bool, bytes memory) { + // Call into the DummyCaller to perform the delegatecall + vm.broadcast(msg.sender); + if (_useOPCMv2) { + return DummyCallerV2(_prank).upgradeSuperchain( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: _input.superchainConfig, + extraInstructions: _input.extraInstructions + }) + ); + } else { + return DummyCaller(_prank).upgradeSuperchainConfig(_input.superchainConfig); + } + } } /// @title DummyCaller @@ -58,3 +92,19 @@ contract DummyCaller { return (success, result); } } +/// @title DummyCallerV2 +/// @notice This contract is used to mimic the contract that is used as the source of the delegatecall to the OPCM v2. +/// Uses IOPContractsManagerV2.SuperchainUpgradeInput type for the upgrade input. + +contract DummyCallerV2 { + address internal _opcmAddr; + + function upgradeSuperchain(IOPContractsManagerV2.SuperchainUpgradeInput memory _superchainUpgradeInput) + external + returns (bool, bytes memory) + { + bytes memory data = abi.encodeCall(IOPContractsManagerV2.upgradeSuperchain, (_superchainUpgradeInput)); + (bool success, bytes memory result) = _opcmAddr.delegatecall(data); + return (success, result); + } +} diff --git a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol index d713e4d826802..35accd9af1cfc 100644 --- a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol @@ -9,11 +9,16 @@ import { UpgradeOPChain, UpgradeOPChainInput } from "scripts/deploy/UpgradeOPCha // Contracts import { OPContractsManager } from "src/L1/OPContractsManager.sol"; +import { OPContractsManagerV2 } from "src/L1/opcm/OPContractsManagerV2.sol"; +import { UpgradeOPChain, UpgradeOPChainInput } from "scripts/deploy/UpgradeOPChain.s.sol"; // Libraries import { Claim } from "src/dispute/lib/Types.sol"; +import { GameType } from "src/dispute/lib/LibUDT.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; contract UpgradeOPChainInput_Test is Test { @@ -31,7 +36,7 @@ contract UpgradeOPChainInput_Test is Test { input.opcm(); vm.expectRevert("UpgradeOPCMInput: not set"); - input.opChainConfigs(); + input.upgradeInput(); } function test_setAddress_succeeds() public { @@ -45,7 +50,7 @@ contract UpgradeOPChainInput_Test is Test { input.set(input.opcm.selector, mockOPCM); assertEq(input.prank(), mockPrank); - assertEq(address(input.opcm()), mockOPCM); + assertEq(input.opcm(), mockOPCM); } function test_setOpChainConfigs_succeeds() public { @@ -76,9 +81,9 @@ contract UpgradeOPChainInput_Test is Test { cannonKonaPrestate: Claim.wrap(bytes32(uint256(3))) }); - input.set(input.opChainConfigs.selector, configs); + input.set(input.upgradeInput.selector, configs); - bytes memory storedConfigs = input.opChainConfigs(); + bytes memory storedConfigs = input.upgradeInput(); assertEq(storedConfigs, abi.encode(configs)); // Additional verification of stored claims if needed @@ -88,6 +93,55 @@ contract UpgradeOPChainInput_Test is Test { assertEq(Claim.unwrap(decodedConfigs[1].cannonPrestate), bytes32(uint256(2))); } + /// @notice Tests that the upgrade input can be set using the OPContractsManagerV2.UpgradeInput type. + function test_setUpgradeInputV2_succeeds() public { + // Create sample UpgradeInputV2 + OPContractsManagerV2.DisputeGameConfig[] memory disputeGameConfigs = + new OPContractsManagerV2.DisputeGameConfig[](1); + disputeGameConfigs[0] = OPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 1000, + gameType: GameType.wrap(1), + gameArgs: abi.encode("test") + }); + + IOPContractsManagerUtils.ExtraInstruction[] memory extraInstructions = + new IOPContractsManagerUtils.ExtraInstruction[](1); + extraInstructions[0] = IOPContractsManagerUtils.ExtraInstruction({ key: "test", data: abi.encode("test") }); + + OPContractsManagerV2.UpgradeInput memory upgradeInput = OPContractsManagerV2.UpgradeInput({ + systemConfig: ISystemConfig(makeAddr("systemConfig")), + disputeGameConfigs: disputeGameConfigs, + extraInstructions: extraInstructions + }); + + input.set(input.upgradeInput.selector, upgradeInput); + + bytes memory storedUpgradeInput = input.upgradeInput(); + assertEq(storedUpgradeInput, abi.encode(upgradeInput)); + + // Additional verification of stored values if needed + OPContractsManagerV2.UpgradeInput memory decodedUpgradeInput = + abi.decode(storedUpgradeInput, (OPContractsManagerV2.UpgradeInput)); + // Check system config matches + assertEq(address(decodedUpgradeInput.systemConfig), address(upgradeInput.systemConfig)); + // Check dispute game configs match + assertEq(decodedUpgradeInput.disputeGameConfigs.length, disputeGameConfigs.length); + assertEq(decodedUpgradeInput.disputeGameConfigs[0].enabled, disputeGameConfigs[0].enabled); + assertEq(decodedUpgradeInput.disputeGameConfigs[0].initBond, disputeGameConfigs[0].initBond); + assertEq( + GameType.unwrap(decodedUpgradeInput.disputeGameConfigs[0].gameType), + GameType.unwrap(disputeGameConfigs[0].gameType) + ); + assertEq( + keccak256(decodedUpgradeInput.disputeGameConfigs[0].gameArgs), keccak256(disputeGameConfigs[0].gameArgs) + ); + // Check extra instructions match + assertEq(decodedUpgradeInput.extraInstructions.length, extraInstructions.length); + assertEq(decodedUpgradeInput.extraInstructions[0].key, extraInstructions[0].key); + assertEq(keccak256(decodedUpgradeInput.extraInstructions[0].data), keccak256(extraInstructions[0].data)); + } + function test_setAddress_withZeroAddress_reverts() public { vm.expectRevert("UpgradeOPCMInput: cannot set zero address"); input.set(input.prank.selector, address(0)); @@ -100,7 +154,7 @@ contract UpgradeOPChainInput_Test is Test { OPContractsManager.OpChainConfig[] memory emptyConfigs = new OPContractsManager.OpChainConfig[](0); vm.expectRevert("UpgradeOPCMInput: cannot set empty array"); - input.set(input.opChainConfigs.selector, emptyConfigs); + input.set(input.upgradeInput.selector, emptyConfigs); } function test_set_withInvalidSelector_reverts() public { @@ -125,11 +179,15 @@ contract UpgradeOPChainInput_Test is Test { } } -contract MockOPCM { +contract MockOPCMV1 { event UpgradeCalled( address indexed sysCfgProxy, bytes32 indexed absolutePrestate, bytes32 indexed cannonKonaPrestate ); + function isDevFeatureEnabled(bytes32 /* _feature */ ) public pure returns (bool) { + return false; + } + function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) public { emit UpgradeCalled( address(_opChainConfigs[0].systemConfigProxy), @@ -139,8 +197,26 @@ contract MockOPCM { } } +contract MockOPCMV2 { + event UpgradeCalled( + address indexed systemConfig, + OPContractsManagerV2.DisputeGameConfig[] indexed disputeGameConfigs, + IOPContractsManagerUtils.ExtraInstruction[] indexed extraInstructions + ); + + function isDevFeatureEnabled(bytes32 _feature) public pure returns (bool) { + return _feature == DevFeatures.OPCM_V2; + } + + function upgrade(OPContractsManagerV2.UpgradeInput memory _upgradeInput) public { + emit UpgradeCalled( + address(_upgradeInput.systemConfig), _upgradeInput.disputeGameConfigs, _upgradeInput.extraInstructions + ); + } +} + contract UpgradeOPChain_Test is Test { - MockOPCM mockOPCM; + MockOPCMV1 mockOPCM; UpgradeOPChainInput uoci; OPContractsManager.OpChainConfig config; UpgradeOPChain upgradeOPChain; @@ -151,7 +227,7 @@ contract UpgradeOPChain_Test is Test { ); function setUp() public virtual { - mockOPCM = new MockOPCM(); + mockOPCM = new MockOPCMV1(); uoci = new UpgradeOPChainInput(); uoci.set(uoci.opcm.selector, address(mockOPCM)); config = OPContractsManager.OpChainConfig({ @@ -161,7 +237,7 @@ contract UpgradeOPChain_Test is Test { }); OPContractsManager.OpChainConfig[] memory configs = new OPContractsManager.OpChainConfig[](1); configs[0] = config; - uoci.set(uoci.opChainConfigs.selector, configs); + uoci.set(uoci.upgradeInput.selector, configs); prank = makeAddr("prank"); uoci.set(uoci.prank.selector, prank); upgradeOPChain = new UpgradeOPChain(); @@ -178,3 +254,44 @@ contract UpgradeOPChain_Test is Test { upgradeOPChain.run(uoci); } } + +contract UpgradeOPChain_TestV2 is Test { + MockOPCMV2 mockOPCM; + UpgradeOPChainInput uoci; + UpgradeOPChain upgradeOPChain; + address prank; + + event UpgradeCalled( + address indexed systemConfig, + OPContractsManagerV2.DisputeGameConfig[] indexed disputeGameConfigs, + IOPContractsManagerUtils.ExtraInstruction[] indexed extraInstructions + ); + + function setUp() public { + mockOPCM = new MockOPCMV2(); + uoci = new UpgradeOPChainInput(); + uoci.set(uoci.opcm.selector, address(mockOPCM)); + + prank = makeAddr("prank"); + uoci.set(uoci.prank.selector, prank); + upgradeOPChain = new UpgradeOPChain(); + } + + function test_upgrade_succeeds() public { + // NOTE: Setting the upgrade input here to avoid `Copying of type struct OPContractsManagerV2.DisputeGameConfig + // memory[] memory to storage not yet supported.` error. + OPContractsManagerV2.UpgradeInput memory upgradeInput = OPContractsManagerV2.UpgradeInput({ + systemConfig: ISystemConfig(makeAddr("systemConfig")), + disputeGameConfigs: new OPContractsManagerV2.DisputeGameConfig[](1), + extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0) + }); + uoci.set(uoci.upgradeInput.selector, upgradeInput); + + // UpgradeCalled should be emitted by the prank since it's a delegate call. + vm.expectEmit(address(prank)); + emit UpgradeCalled( + address(upgradeInput.systemConfig), upgradeInput.disputeGameConfigs, upgradeInput.extraInstructions + ); + upgradeOPChain.run(uoci); + } +} diff --git a/packages/contracts-bedrock/test/opcm/UpgradeSuperchainConfig.t.sol b/packages/contracts-bedrock/test/opcm/UpgradeSuperchainConfig.t.sol index a822997b2e1cd..f9308dd30e2a5 100644 --- a/packages/contracts-bedrock/test/opcm/UpgradeSuperchainConfig.t.sol +++ b/packages/contracts-bedrock/test/opcm/UpgradeSuperchainConfig.t.sol @@ -9,22 +9,43 @@ import { UpgradeSuperchainConfig } from "scripts/deploy/UpgradeSuperchainConfig. // Interfaces import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; -/// @title MockOPCM +import { DevFeatures } from "src/libraries/DevFeatures.sol"; + +/// @title MockOPCMV1 /// @notice This contract is used to mock the OPCM contract and emit an event which we check for in the test. -contract MockOPCM { +contract MockOPCMV1 { event UpgradeCalled(address indexed superchainConfig); + function isDevFeatureEnabled(bytes32 /* _feature */ ) public pure returns (bool) { + return false; + } + function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig) public { emit UpgradeCalled(address(_superchainConfig)); } } +/// @title MockOPCMV2 +/// @notice This contract is used to mock the OPCM v2 contract and emit an event which we check for in the test. +contract MockOPCMV2 { + event UpgradeCalled(IOPContractsManagerV2.SuperchainUpgradeInput indexed superchainUpgradeInput); + + function isDevFeatureEnabled(bytes32 _feature) public pure returns (bool) { + return _feature == DevFeatures.OPCM_V2; + } + + function upgradeSuperchain(IOPContractsManagerV2.SuperchainUpgradeInput memory _superchainUpgradeInput) public { + emit UpgradeCalled(_superchainUpgradeInput); + } +} + /// @title UpgradeSuperchainConfig_Test /// @notice This test is used to test the UpgradeSuperchainConfig script. -contract UpgradeSuperchainConfig_Run_Test is Test { - MockOPCM mockOPCM; +contract UpgradeSuperchainConfigV1_Run_Test is Test { + MockOPCMV1 mockOPCM; UpgradeSuperchainConfig.Input input; UpgradeSuperchainConfig upgradeSuperchainConfig; address prank; @@ -34,9 +55,9 @@ contract UpgradeSuperchainConfig_Run_Test is Test { /// @notice Sets up the test suite. function setUp() public virtual { - mockOPCM = new MockOPCM(); + mockOPCM = new MockOPCMV1(); - input.opcm = IOPContractsManager(address(mockOPCM)); + input.opcm = address(mockOPCM); superchainConfig = ISuperchainConfig(makeAddr("superchainConfig")); prank = makeAddr("prank"); @@ -62,10 +83,10 @@ contract UpgradeSuperchainConfig_Run_Test is Test { upgradeSuperchainConfig.run(input); input.prank = prank; - input.opcm = IOPContractsManager(address(0)); + input.opcm = address(0); vm.expectRevert("UpgradeSuperchainConfig: opcm not set"); upgradeSuperchainConfig.run(input); - input.opcm = IOPContractsManager(address(mockOPCM)); + input.opcm = address(mockOPCM); input.superchainConfig = ISuperchainConfig(address(0)); vm.expectRevert("UpgradeSuperchainConfig: superchainConfig not set"); @@ -73,3 +94,52 @@ contract UpgradeSuperchainConfig_Run_Test is Test { input.superchainConfig = ISuperchainConfig(address(superchainConfig)); } } + +/// @title UpgradeSuperchainConfigV2_Run_Test +/// @notice This test is used to test the UpgradeSuperchainConfig script with OPCM v2. +contract UpgradeSuperchainConfigV2_Run_Test is Test { + MockOPCMV2 mockOPCM; + UpgradeSuperchainConfig upgradeSuperchainConfig; + address prank; + ISuperchainConfig superchainConfig; + + event UpgradeCalled(IOPContractsManagerV2.SuperchainUpgradeInput indexed superchainUpgradeInput); + + /// @notice Sets up the test suite. + function setUp() public { + mockOPCM = new MockOPCMV2(); + + superchainConfig = ISuperchainConfig(makeAddr("superchainConfig")); + prank = makeAddr("prank"); + + upgradeSuperchainConfig = new UpgradeSuperchainConfig(); + } + + /// @notice Tests that the UpgradeSuperchainConfig script succeeds when called with non-zero input values. + function testFuzz_upgrade_succeeds(IOPContractsManagerUtils.ExtraInstruction[] memory extraInstructions) public { + UpgradeSuperchainConfig.Input memory input = _getInput(extraInstructions); + + // UpgradeCalled should be emitted by the prank since it's a delegate call. + vm.expectEmit(address(prank)); + emit UpgradeCalled( + IOPContractsManagerV2.SuperchainUpgradeInput({ + superchainConfig: superchainConfig, + extraInstructions: extraInstructions + }) + ); + upgradeSuperchainConfig.run(input); + } + + function _getInput(IOPContractsManagerUtils.ExtraInstruction[] memory extraInstructions) + internal + view + returns (UpgradeSuperchainConfig.Input memory) + { + return UpgradeSuperchainConfig.Input({ + prank: prank, + opcm: address(mockOPCM), + superchainConfig: superchainConfig, + extraInstructions: extraInstructions + }); + } +}