Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 176 additions & 39 deletions op-deployer/pkg/deployer/integration_test/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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'},
Expand All @@ -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")
})
})
})
}

Expand Down
16 changes: 16 additions & 0 deletions op-deployer/pkg/deployer/upgrade/embedded/testdata/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"prank": "0x1Eb2fFc903729a0F03966B917003800b145F56E2",
"opcm": "0xaf334f4537e87f5155d135392ff6d52f1866465e",
"upgradeInput": {
"systemConfig": "0x034edD2A225f7f429A63E0f1D2084B9E0A93b538",
"disputeGameConfigs": [
{
"enabled": true,
"initBond": "0x0",
"gameType": 0,
"gameArgs": "0x"
}
],
"extraInstructions": []
}
}
87 changes: 81 additions & 6 deletions op-deployer/pkg/deployer/upgrade/embedded/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{}
Expand Down
Loading