Skip to content

[backports] op-deployer: Support custom OPCM #16546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
103 changes: 103 additions & 0 deletions op-deployer/pkg/deployer/opcm/read_superchain_deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package opcm

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/lmittmann/w3"
"github.com/lmittmann/w3/module/eth"
)

type ReadSuperchainDeploymentInput struct {
OPCMAddress common.Address `abi:"opcmAddress"`
}

type ReadSuperchainDeploymentOutput struct {
ProtocolVersionsImpl common.Address
ProtocolVersionsProxy common.Address
SuperchainConfigImpl common.Address
SuperchainConfigProxy common.Address
SuperchainProxyAdmin common.Address

Guardian common.Address
ProtocolVersionsOwner common.Address
SuperchainProxyAdminOwner common.Address
RecommendedProtocolVersion [32]byte
RequiredProtocolVersion [32]byte
}

// Function signatures
var (
// OPContractsManager functions
funcProtocolVersions = w3.MustNewFunc("protocolVersions()", "address")
funcSuperchainConfig = w3.MustNewFunc("superchainConfig()", "address")
funcSuperchainProxyAdmin = w3.MustNewFunc("superchainProxyAdmin()", "address")

// Proxy functions
funcImplementation = w3.MustNewFunc("implementation()", "address")

// Ownable functions
funcOwner = w3.MustNewFunc("owner()", "address")

// SuperchainConfig functions
funcGuardian = w3.MustNewFunc("guardian()", "address")

// ProtocolVersions functions
funcRecommended = w3.MustNewFunc("recommended()", "uint256")
funcRequired = w3.MustNewFunc("required()", "uint256")
)

func ReadSuperchainDeployment(ctx context.Context, rpcClient *rpc.Client, input ReadSuperchainDeploymentInput) (*ReadSuperchainDeploymentOutput, error) {
if input.OPCMAddress == (common.Address{}) {
return nil, fmt.Errorf("ReadSuperchainDeployment: opcmAddress not set")
}

w3Client := w3.NewClient(rpcClient)
defer w3Client.Close()

output := &ReadSuperchainDeploymentOutput{}

// Step 1: Get proxy addresses from OPCM
if err := w3Client.Call(
eth.CallFunc(input.OPCMAddress, funcProtocolVersions).Returns(&output.ProtocolVersionsProxy),
eth.CallFunc(input.OPCMAddress, funcSuperchainConfig).Returns(&output.SuperchainConfigProxy),
eth.CallFunc(input.OPCMAddress, funcSuperchainProxyAdmin).Returns(&output.SuperchainProxyAdmin),
); err != nil {
return nil, fmt.Errorf("failed to get proxy addresses: %w", err)
}

// Step 2: Get implementation addresses from proxies
if err := w3Client.Call(
eth.CallFunc(output.ProtocolVersionsProxy, funcImplementation).Returns(&output.ProtocolVersionsImpl),
eth.CallFunc(output.SuperchainConfigProxy, funcImplementation).Returns(&output.SuperchainConfigImpl),
); err != nil {
return nil, fmt.Errorf("failed to get implementation addresses: %w", err)
}

// Step 3: Get owner/guardian information and protocol versions
var recommendedBig, requiredBig *big.Int
if err := w3Client.Call(
eth.CallFunc(output.SuperchainConfigProxy, funcGuardian).Returns(&output.Guardian),
eth.CallFunc(output.ProtocolVersionsProxy, funcOwner).Returns(&output.ProtocolVersionsOwner),
eth.CallFunc(output.SuperchainProxyAdmin, funcOwner).Returns(&output.SuperchainProxyAdminOwner),
eth.CallFunc(output.ProtocolVersionsProxy, funcRecommended).Returns(&recommendedBig),
eth.CallFunc(output.ProtocolVersionsProxy, funcRequired).Returns(&requiredBig),
); err != nil {
return nil, fmt.Errorf("failed to get owner/guardian info: %w", err)
}

// Convert big.Int to [32]byte
if recommendedBig != nil {
recommendedBytes := recommendedBig.Bytes()
copy(output.RecommendedProtocolVersion[32-len(recommendedBytes):], recommendedBytes)
}
if requiredBig != nil {
requiredBytes := requiredBig.Bytes()
copy(output.RequiredProtocolVersion[32-len(requiredBytes):], requiredBytes)
}

return output, nil
}
2 changes: 1 addition & 1 deletion op-deployer/pkg/deployer/pipeline/implementations.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro
SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress,
ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress,
SuperchainProxyAdmin: st.SuperchainDeployment.ProxyAdminAddress,
UpgradeController: intent.SuperchainRoles.ProxyAdminOwner,
UpgradeController: st.SuperchainRoles.ProxyAdminOwner,
UseInterop: intent.UseInterop,
},
)
Expand Down
69 changes: 41 additions & 28 deletions op-deployer/pkg/deployer/pipeline/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"crypto/rand"
"fmt"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
)

func IsSupportedStateVersion(version int) bool {
Expand All @@ -25,8 +27,7 @@ func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *s
return err
}

opcmAddress, opcmAddrErr := standard.ManagerImplementationAddrFor(intent.L1ChainID, intent.L1ContractsLocator.Tag)
hasPredeployedOPCM := opcmAddrErr == nil
hasPredeployedOPCM := intent.OPCMAddress != nil
isL1Tag := intent.L1ContractsLocator.IsTag()
isL2Tag := intent.L2ContractsLocator.IsTag()

Expand All @@ -38,34 +39,23 @@ func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *s
return fmt.Errorf("unsupported L2 version: %s", intent.L2ContractsLocator.Tag)
}

isStandardIntent := intent.ConfigType == state.IntentTypeStandard ||
intent.ConfigType == state.IntentTypeStandardOverrides
if isL1Tag && hasPredeployedOPCM && isStandardIntent {
stdRoles, err := state.GetStandardSuperchainRoles(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting superchain roles: %w", err)
if hasPredeployedOPCM {
if intent.SuperchainConfigProxy != nil {
return fmt.Errorf("cannot set superchain config proxy for predeployed OPCM")
}

if intent.SuperchainRoles != nil {
return fmt.Errorf("cannot set superchain roles for predeployed OPCM")
}

if *intent.SuperchainRoles == *stdRoles {
superCfg, err := standard.SuperchainFor(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting superchain config: %w", err)
}

proxyAdmin, err := standard.SuperchainProxyAdminAddrFor(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting superchain proxy admin address: %w", err)
}

st.SuperchainDeployment = &state.SuperchainDeployment{
ProxyAdminAddress: proxyAdmin,
ProtocolVersionsProxyAddress: superCfg.ProtocolVersionsAddr,
SuperchainConfigProxyAddress: superCfg.SuperchainConfigAddr,
}

st.ImplementationsDeployment = &state.ImplementationsDeployment{
OpcmAddress: opcmAddress,
}
superDeployment, superRoles, err := PopulateSuperchainState(env.L1Client.Client(), *intent.OPCMAddress)
if err != nil {
return fmt.Errorf("error populating superchain state: %w", err)
}
st.SuperchainDeployment = superDeployment
st.SuperchainRoles = superRoles
st.ImplementationsDeployment = &state.ImplementationsDeployment{
OpcmAddress: *intent.OPCMAddress,
}
}

Expand Down Expand Up @@ -143,3 +133,26 @@ func InitGenesisStrategy(env *Env, intent *state.Intent, st *state.State) error
func immutableErr(field string, was, is any) error {
return fmt.Errorf("%s is immutable: was %v, is %v", field, was, is)
}

func PopulateSuperchainState(rpcClient *rpc.Client, opcmAddr common.Address) (*state.SuperchainDeployment, *state.SuperchainRoles, error) {
out, err := opcm.ReadSuperchainDeployment(context.Background(), rpcClient, opcm.ReadSuperchainDeploymentInput{
OPCMAddress: opcmAddr,
})
if err != nil {
return nil, nil, fmt.Errorf("error reading superchain deployment: %w", err)
}

deployment := &state.SuperchainDeployment{
ProxyAdminAddress: out.SuperchainProxyAdmin,
SuperchainConfigProxyAddress: out.SuperchainConfigProxy,
SuperchainConfigImplAddress: out.SuperchainConfigImpl,
ProtocolVersionsProxyAddress: out.ProtocolVersionsProxy,
ProtocolVersionsImplAddress: out.ProtocolVersionsImpl,
}
roles := &state.SuperchainRoles{
Guardian: out.Guardian,
ProtocolVersionsOwner: out.ProtocolVersionsOwner,
ProxyAdminOwner: out.SuperchainProxyAdminOwner,
}
return deployment, roles, nil
}
79 changes: 73 additions & 6 deletions op-deployer/pkg/deployer/pipeline/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
Expand All @@ -18,6 +24,8 @@ import (
)

func TestInitLiveStrategy_OPCMReuseLogicSepolia(t *testing.T) {
t.Parallel()

rpcURL := os.Getenv("SEPOLIA_RPC_URL")
require.NotEmpty(t, rpcURL, "SEPOLIA_RPC_URL must be set")

Expand All @@ -31,8 +39,9 @@ func TestInitLiveStrategy_OPCMReuseLogicSepolia(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

client, err := ethclient.Dial(retryProxy.Endpoint())
rpcClient, err := rpc.Dial(retryProxy.Endpoint())
require.NoError(t, err)
client := ethclient.NewClient(rpcClient)

l1ChainID := uint64(11155111)
t.Run("untagged L1 locator", func(t *testing.T) {
Expand Down Expand Up @@ -60,24 +69,39 @@ func TestInitLiveStrategy_OPCMReuseLogicSepolia(t *testing.T) {

t.Run("tagged L1 locator with standard intent types and standard roles", func(t *testing.T) {
runTest := func(configType state.IntentType) {
_, afacts := testutil.LocalArtifacts(t)
host, err := env.DefaultForkedScriptHost(
ctx,
broadcaster.NoopBroadcaster(),
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
afacts,
rpcClient,
)
require.NoError(t, err)

stdSuperchainRoles, err := state.GetStandardSuperchainRoles(l1ChainID)
require.NoError(t, err)

opcmAddr, err := standard.OPCMImplAddressFor(l1ChainID, artifacts.DefaultL1ContractsLocator.Tag)
require.NoError(t, err)

intent := &state.Intent{
ConfigType: configType,
L1ChainID: l1ChainID,
L1ContractsLocator: artifacts.DefaultL1ContractsLocator,
L2ContractsLocator: artifacts.DefaultL2ContractsLocator,
SuperchainRoles: stdSuperchainRoles,
OPCMAddress: &opcmAddr,
}
st := &state.State{
Version: 1,
}
require.NoError(t, InitLiveStrategy(
ctx,
&Env{
L1Client: client,
Logger: lgr,
L1Client: client,
Logger: lgr,
L1ScriptHost: host,
},
intent,
st,
Expand All @@ -88,20 +112,22 @@ func TestInitLiveStrategy_OPCMReuseLogicSepolia(t *testing.T) {
require.NoError(t, err)
proxyAdmin, err := standard.SuperchainProxyAdminAddrFor(l1ChainID)
require.NoError(t, err)
opcmAddr, err := standard.ManagerImplementationAddrFor(l1ChainID, intent.L1ContractsLocator.Tag)
require.NoError(t, err)

expDeployment := &state.SuperchainDeployment{
ProxyAdminAddress: proxyAdmin,
ProtocolVersionsProxyAddress: superCfg.ProtocolVersionsAddr,
ProtocolVersionsImplAddress: common.HexToAddress("0x37E15e4d6DFFa9e5E320Ee1eC036922E563CB76C"),
SuperchainConfigProxyAddress: superCfg.SuperchainConfigAddr,
SuperchainConfigImplAddress: common.HexToAddress("0x4da82a327773965b8d4D85Fa3dB8249b387458E7"),
}

// Tagged locator will reuse the existing superchain and OPCM
require.NotNil(t, st.SuperchainDeployment)
require.NotNil(t, st.ImplementationsDeployment)
require.NotNil(t, st.SuperchainRoles)
require.Equal(t, *expDeployment, *st.SuperchainDeployment)
require.Equal(t, opcmAddr, st.ImplementationsDeployment.OpcmAddress)
require.Equal(t, *stdSuperchainRoles, *st.SuperchainRoles)
}

runTest(state.IntentTypeStandard)
Expand Down Expand Up @@ -169,3 +195,44 @@ func TestInitLiveStrategy_OPCMReuseLogicSepolia(t *testing.T) {
require.Nil(t, st.ImplementationsDeployment)
})
}

// TestPopulateSuperchainState validates that the ReadSuperchainDeployment script successfully returns data about the
// given Superchain. For testing purposes, we use a forked script host that points to a pinned block on Sepolia. Pinning
// the block lets us use constant values in the test without worrying about changes on chain. We use values from the SR
// whenever possible, however some (like the Superchain PAO) are not included in the SR and are therefore hardcoded.
func TestPopulateSuperchainState(t *testing.T) {
t.Parallel()

rpcURL := os.Getenv("SEPOLIA_RPC_URL")
require.NotEmpty(t, rpcURL, "SEPOLIA_RPC_URL must be set")

lgr := testlog.Logger(t, slog.LevelInfo)
retryProxy := devnet.NewRetryProxy(lgr, rpcURL)
require.NoError(t, retryProxy.Start())
t.Cleanup(func() {
require.NoError(t, retryProxy.Stop())
})

rpcClient, err := rpc.Dial(retryProxy.Endpoint())
require.NoError(t, err)

l1Versions, err := standard.L1VersionsFor(11155111)
require.NoError(t, err)
superchain, err := standard.SuperchainFor(11155111)
require.NoError(t, err)
opcmAddr := l1Versions["op-contracts/v2.0.0-rc.1"].OPContractsManager.Address
dep, roles, err := PopulateSuperchainState(rpcClient, common.Address(*opcmAddr))
require.NoError(t, err)
require.Equal(t, state.SuperchainDeployment{
ProxyAdminAddress: common.HexToAddress("0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc"),
SuperchainConfigProxyAddress: superchain.SuperchainConfigAddr,
SuperchainConfigImplAddress: common.HexToAddress("0x4da82a327773965b8d4D85Fa3dB8249b387458E7"),
ProtocolVersionsProxyAddress: superchain.ProtocolVersionsAddr,
ProtocolVersionsImplAddress: common.HexToAddress("0x37E15e4d6DFFa9e5E320Ee1eC036922E563CB76C"),
}, *dep)
require.Equal(t, state.SuperchainRoles{
ProxyAdminOwner: common.HexToAddress("0x1Eb2fFc903729a0F03966B917003800b145F56E2"),
ProtocolVersionsOwner: common.HexToAddress("0xfd1D2e729aE8eEe2E146c033bf4400fE75284301"),
Guardian: common.HexToAddress("0x7a50f00e8D05b95F98fE38d8BeE366a7324dCf7E"),
}, *roles)
}
1 change: 1 addition & 0 deletions op-deployer/pkg/deployer/pipeline/superchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func DeploySuperchain(env *Env, intent *state.Intent, st *state.State) error {
ProtocolVersionsProxyAddress: dso.ProtocolVersionsProxy,
ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl,
}
st.SuperchainRoles = intent.SuperchainRoles

return nil
}
Expand Down
16 changes: 12 additions & 4 deletions op-deployer/pkg/deployer/standard/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,24 @@ func SuperchainFor(chainID uint64) (superchain.Superchain, error) {
}
}

func ManagerImplementationAddrFor(chainID uint64, tag string) (common.Address, error) {
func OPCMImplAddressFor(chainID uint64, tag string) (common.Address, error) {
versionsData, err := L1VersionsFor(chainID)
if err != nil {
return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID)
return common.Address{}, fmt.Errorf("unsupported chainID: %d", chainID)
}
versionData, ok := versionsData[validation.Semver(tag)]
if !ok {
return common.Address{}, fmt.Errorf("unsupported tag for chain ID %d: %s", chainID, tag)
return common.Address{}, fmt.Errorf("unsupported tag for chainID %d: %s", chainID, tag)
}
if versionData.OPContractsManager.Address != nil {
// op-contracts/v1.8.0 and earlier use proxied opcm
return common.Address(*versionData.OPContractsManager.Address), nil
}
if versionData.OPContractsManager.ImplementationAddress != nil {
// op-contracts/v2.0.0-rc.1 and later use non-proxied opcm
return common.Address(*versionData.OPContractsManager.ImplementationAddress), nil
}
return common.Address(*versionData.OPContractsManager.Address), nil
return common.Address{}, fmt.Errorf("OPContractsManager address is nil for tag %s", tag)
}

// SuperchainProxyAdminAddrFor returns the address of the Superchain ProxyAdmin for the given chain ID.
Expand Down
Loading