Commit 63140ba2 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

op-deployer: Bootstrap superchain command (#13294)

* op-deployer: Bootstrap superchain command

Fixes https://github.com/ethereum-optimism/optimism/issues/13265.

* fix test
parent 403ae810
......@@ -35,6 +35,12 @@ const (
DelayedWethProxyFlagName = "delayed-weth-proxy"
DelayedWethImplFlagName = "delayed-weth-impl"
ProxyOwnerFlagName = "proxy-owner"
SuperchainProxyAdminOwnerFlagName = "superchain-proxy-admin-owner"
ProtocolVersionsOwnerFlagName = "protocol-versions-owner"
GuardianFlagName = "guardian"
PausedFlagName = "paused"
RequiredProtocolVersionFlagName = "required-protocol-version"
RecommendedProtocolVersionFlagName = "recommended-protocol-version"
)
var (
......@@ -176,6 +182,39 @@ var (
EnvVars: deployer.PrefixEnvVar("PROXY_OWNER"),
Value: common.Address{}.Hex(),
}
SuperchainProxyAdminOwnerFlag = &cli.StringFlag{
Name: SuperchainProxyAdminOwnerFlagName,
Usage: "Owner address for the superchain proxy admin",
EnvVars: deployer.PrefixEnvVar("SUPERCHAIN_PROXY_ADMIN_OWNER"),
Value: common.Address{}.Hex(),
}
ProtocolVersionsOwnerFlag = &cli.StringFlag{
Name: ProtocolVersionsOwnerFlagName,
Usage: "Owner address for protocol versions",
EnvVars: deployer.PrefixEnvVar("PROTOCOL_VERSIONS_OWNER"),
Value: common.Address{}.Hex(),
}
GuardianFlag = &cli.StringFlag{
Name: GuardianFlagName,
Usage: "Guardian address",
EnvVars: deployer.PrefixEnvVar("GUARDIAN"),
Value: common.Address{}.Hex(),
}
PausedFlag = &cli.BoolFlag{
Name: PausedFlagName,
Usage: "Initial paused state",
EnvVars: deployer.PrefixEnvVar("PAUSED"),
}
RequiredProtocolVersionFlag = &cli.StringFlag{
Name: RequiredProtocolVersionFlagName,
Usage: "Required protocol version (semver)",
EnvVars: deployer.PrefixEnvVar("REQUIRED_PROTOCOL_VERSION"),
}
RecommendedProtocolVersionFlag = &cli.StringFlag{
Name: RecommendedProtocolVersionFlagName,
Usage: "Recommended protocol version (semver)",
EnvVars: deployer.PrefixEnvVar("RECOMMENDED_PROTOCOL_VERSION"),
}
)
var OPCMFlags = []cli.Flag{
......@@ -239,6 +278,18 @@ var ProxyFlags = []cli.Flag{
ProxyOwnerFlag,
}
var SuperchainFlags = []cli.Flag{
deployer.L1RPCURLFlag,
deployer.PrivateKeyFlag,
ArtifactsLocatorFlag,
SuperchainProxyAdminOwnerFlag,
ProtocolVersionsOwnerFlag,
GuardianFlag,
PausedFlag,
RequiredProtocolVersionFlag,
RecommendedProtocolVersionFlag,
}
var Commands = []*cli.Command{
{
Name: "opcm",
......@@ -285,4 +336,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(ProxyFlags),
Action: ProxyCLI,
},
{
Name: "superchain",
Usage: "Bootstrap the Superchain configuration",
Flags: cliapp.ProtectFlags(SuperchainFlags),
Action: SuperchainCLI,
},
}
......@@ -17,7 +17,7 @@ import (
var networks = []string{"mainnet", "sepolia"}
var versions = []string{"v1.8.0-rc.3"}
var versions = []string{"v1.8.0-rc.3", "v1.6.0"}
func TestOPCMLiveChain(t *testing.T) {
for _, network := range networks {
......@@ -27,6 +27,10 @@ func TestOPCMLiveChain(t *testing.T) {
t.Skip("v1.8.0-rc.3 not supported on mainnet yet")
}
if version == "v1.6.0" {
t.Skip("v1.6.0 not supported")
}
envVar := strings.ToUpper(network) + "_RPC_URL"
rpcURL := os.Getenv(envVar)
require.NotEmpty(t, rpcURL, "must specify RPC url via %s env var", envVar)
......
package bootstrap
import (
"context"
"crypto/ecdsa"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
type SuperchainConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsLocator *artifacts.Locator
privateKeyECDSA *ecdsa.PrivateKey
SuperchainProxyAdminOwner common.Address
ProtocolVersionsOwner common.Address
Guardian common.Address
Paused bool
RequiredProtocolVersion params.ProtocolVersion
RecommendedProtocolVersion params.ProtocolVersion
}
func (c *SuperchainConfig) Check() error {
if c.L1RPCUrl == "" {
return fmt.Errorf("l1RPCUrl must be specified")
}
if c.PrivateKey == "" {
return fmt.Errorf("private key must be specified")
}
privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x"))
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
c.privateKeyECDSA = privECDSA
if c.Logger == nil {
return fmt.Errorf("logger must be specified")
}
if c.ArtifactsLocator == nil {
return fmt.Errorf("artifacts locator must be specified")
}
if c.SuperchainProxyAdminOwner == (common.Address{}) {
return fmt.Errorf("superchain proxy admin owner must be specified")
}
if c.ProtocolVersionsOwner == (common.Address{}) {
return fmt.Errorf("protocol versions owner must be specified")
}
if c.Guardian == (common.Address{}) {
return fmt.Errorf("guardian must be specified")
}
return nil
}
func SuperchainCLI(cliCtx *cli.Context) error {
logCfg := oplog.ReadCLIConfig(cliCtx)
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
oplog.SetGlobalLogHandler(l.Handler())
l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName)
privateKey := cliCtx.String(deployer.PrivateKeyFlagName)
artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName)
artifactsLocator := new(artifacts.Locator)
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
return fmt.Errorf("failed to parse artifacts URL: %w", err)
}
superchainProxyAdminOwner := common.HexToAddress(cliCtx.String(SuperchainProxyAdminOwnerFlagName))
protocolVersionsOwner := common.HexToAddress(cliCtx.String(ProtocolVersionsOwnerFlagName))
guardian := common.HexToAddress(cliCtx.String(GuardianFlagName))
paused := cliCtx.Bool(PausedFlagName)
requiredVersionStr := cliCtx.String(RequiredProtocolVersionFlagName)
recommendedVersionStr := cliCtx.String(RecommendedProtocolVersionFlagName)
cfg := SuperchainConfig{
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsLocator: artifactsLocator,
SuperchainProxyAdminOwner: superchainProxyAdminOwner,
ProtocolVersionsOwner: protocolVersionsOwner,
Guardian: guardian,
Paused: paused,
}
if err := cfg.RequiredProtocolVersion.UnmarshalText([]byte(requiredVersionStr)); err != nil {
return fmt.Errorf("failed to parse required protocol version: %w", err)
}
if err := cfg.RecommendedProtocolVersion.UnmarshalText([]byte(recommendedVersionStr)); err != nil {
return fmt.Errorf("failed to parse required protocol version: %w", err)
}
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
dso, err := Superchain(ctx, cfg)
if err != nil {
return fmt.Errorf("failed to deploy superchain: %w", err)
}
if err := jsonutil.WriteJSON(dso, ioutil.ToStdOut()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}
func Superchain(ctx context.Context, cfg SuperchainConfig) (opcm.DeploySuperchainOutput, error) {
var dso opcm.DeploySuperchainOutput
if err := cfg.Check(); err != nil {
return dso, fmt.Errorf("invalid config for Superchain: %w", err)
}
lgr := cfg.Logger
progressor := func(curr, total int64) {
lgr.Info("artifacts download progress", "current", curr, "total", total)
}
artifactsFS, cleanup, err := artifacts.Download(ctx, cfg.ArtifactsLocator, progressor)
if err != nil {
return dso, fmt.Errorf("failed to download artifacts: %w", err)
}
defer func() {
if err := cleanup(); err != nil {
lgr.Warn("failed to clean up artifacts", "err", err)
}
}()
l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
if err != nil {
return dso, fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
chainID, err := l1Client.ChainID(ctx)
if err != nil {
return dso, fmt.Errorf("failed to get chain ID: %w", err)
}
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: chainID,
Client: l1Client,
Signer: signer,
From: chainDeployer,
})
if err != nil {
return dso, fmt.Errorf("failed to create broadcaster: %w", err)
}
l1RPC, err := rpc.Dial(cfg.L1RPCUrl)
if err != nil {
return dso, fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
l1Host, err := env.DefaultForkedScriptHost(
ctx,
bcaster,
lgr,
chainDeployer,
artifactsFS,
l1RPC,
)
if err != nil {
return dso, fmt.Errorf("failed to create script host: %w", err)
}
dso, err = opcm.DeploySuperchain(
l1Host,
opcm.DeploySuperchainInput{
SuperchainProxyAdminOwner: cfg.SuperchainProxyAdminOwner,
ProtocolVersionsOwner: cfg.ProtocolVersionsOwner,
Guardian: cfg.Guardian,
Paused: cfg.Paused,
RequiredProtocolVersion: cfg.RequiredProtocolVersion,
RecommendedProtocolVersion: cfg.RecommendedProtocolVersion,
},
)
if err != nil {
return dso, fmt.Errorf("error deploying superchain: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
return dso, fmt.Errorf("failed to broadcast: %w", err)
}
lgr.Info("deployed superchain configuration")
return dso, nil
}
package bootstrap
import (
"context"
"log/slog"
"os"
"strings"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/retryproxy"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils/anvil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestSuperchain(t *testing.T) {
for _, network := range networks {
for _, version := range versions {
t.Run(network+"-"+version, func(t *testing.T) {
envVar := strings.ToUpper(network) + "_RPC_URL"
rpcURL := os.Getenv(envVar)
require.NotEmpty(t, rpcURL, "must specify RPC url via %s env var", envVar)
testSuperchain(t, rpcURL, version)
})
}
}
}
func testSuperchain(t *testing.T, forkRPCURL string, version string) {
t.Parallel()
if forkRPCURL == "" {
t.Skip("forkRPCURL not set")
}
lgr := testlog.Logger(t, slog.LevelDebug)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
retryProxy := retryproxy.New(lgr, forkRPCURL)
require.NoError(t, retryProxy.Start())
t.Cleanup(func() {
require.NoError(t, retryProxy.Stop())
})
runner, err := anvil.New(
retryProxy.Endpoint(),
lgr,
)
require.NoError(t, err)
require.NoError(t, runner.Start(ctx))
t.Cleanup(func() {
require.NoError(t, runner.Stop())
})
out, err := Superchain(ctx, SuperchainConfig{
L1RPCUrl: runner.RPCUrl(),
PrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
ArtifactsLocator: artifacts.MustNewLocatorFromTag("op-contracts/" + version),
Logger: lgr,
SuperchainProxyAdminOwner: common.Address{'S'},
ProtocolVersionsOwner: common.Address{'P'},
Guardian: common.Address{'G'},
Paused: false,
RequiredProtocolVersion: params.ProtocolVersionV0{Major: 1}.Encode(),
RecommendedProtocolVersion: params.ProtocolVersionV0{Major: 2}.Encode(),
})
require.NoError(t, err)
client, err := ethclient.Dial(runner.RPCUrl())
require.NoError(t, err)
addresses := []common.Address{
out.SuperchainConfigProxy,
out.SuperchainConfigImpl,
out.SuperchainProxyAdmin,
out.ProtocolVersionsImpl,
out.ProtocolVersionsProxy,
}
for _, addr := range addresses {
require.NotEmpty(t, addr)
code, err := client.CodeAt(ctx, addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment