Skip to content

Commit

Permalink
feat(op-deployer): Proxy bootstrap command
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Dec 4, 2024
1 parent 4ce84f1 commit d2e2a5b
Show file tree
Hide file tree
Showing 5 changed files with 399 additions and 0 deletions.
20 changes: 20 additions & 0 deletions op-deployer/pkg/deployer/bootstrap/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
ProposerFlagName = "proposer"
ChallengerFlagName = "challenger"
PreimageOracleFlagName = "preimage-oracle"
ProxyOwnerFlagName = "proxy-owner"
)

var (
Expand Down Expand Up @@ -153,6 +154,12 @@ var (
EnvVars: deployer.PrefixEnvVar("PREIMAGE_ORACLE"),
Value: common.Address{}.Hex(),
}
ProxyOwnerFlag = &cli.StringFlag{
Name: ProxyOwnerFlagName,
Usage: "Proxy owner address.",
EnvVars: deployer.PrefixEnvVar("PROXY_OWNER"),
Value: common.Address{}.Hex(),
}
)

var OPCMFlags = []cli.Flag{
Expand Down Expand Up @@ -205,6 +212,13 @@ var MIPSFlags = append(BaseFPVMFlags, MIPSVersionFlag)

var AsteriscFlags = BaseFPVMFlags

var ProxyFlags = []cli.Flag{
deployer.L1RPCURLFlag,
deployer.PrivateKeyFlag,
ArtifactsLocatorFlag,
ProxyOwnerFlag,
}

var Commands = []*cli.Command{
{
Name: "opcm",
Expand Down Expand Up @@ -236,4 +250,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(AsteriscFlags),
Action: AsteriscCLI,
},
{
Name: "proxy",
Usage: "Bootstrap a ERC-1967 Proxy without an implementation set.",
Flags: cliapp.ProtectFlags(ProxyFlags),
Action: ProxyCLI,
},
}
185 changes: 185 additions & 0 deletions op-deployer/pkg/deployer/bootstrap/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package bootstrap

import (
"context"
"crypto/ecdsa"
"fmt"
"strings"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-chain-ops/script/forking"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum/go-ethereum/common"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
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/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)

type ProxyConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsLocator *artifacts.Locator

privateKeyECDSA *ecdsa.PrivateKey

Owner common.Address
}

func (c *ProxyConfig) 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.Owner == (common.Address{}) {
return fmt.Errorf("proxy owner must be specified")
}

return nil
}

func ProxyCLI(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)
}

owner := common.HexToAddress(cliCtx.String(ProxyOwnerFlagName))

ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)

return Proxy(ctx, ProxyConfig{
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsLocator: artifactsLocator,
Owner: owner,
})
}

func Proxy(ctx context.Context, cfg ProxyConfig) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config for Proxy: %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 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 fmt.Errorf("failed to connect to L1 RPC: %w", err)
}

chainID, err := l1Client.ChainID(ctx)
if err != nil {
return 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 fmt.Errorf("failed to create broadcaster: %w", err)
}

l1RPC, err := rpc.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}

l1Host, err := env.DefaultScriptHost(
bcaster,
lgr,
chainDeployer,
artifactsFS,
script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) {
src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, l1RPC, *cfg.BlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to create RPC fork source: %w", err)
}
return forking.Cache(src), nil
}),
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}

dgo, err := opcm.DeployProxy(
l1Host,
opcm.DeployProxyInput{
Owner: cfg.Owner,
},
)
if err != nil {
return fmt.Errorf("error deploying proxy: %w", err)
}

if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}

lgr.Info("deployed new ERC-1967 proxy")

if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}
67 changes: 67 additions & 0 deletions op-deployer/pkg/deployer/opcm/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package opcm

import (
"fmt"

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

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

type DeployProxyInput struct {
Owner common.Address
}

func (input *DeployProxyInput) InputSet() bool {
return true
}

type DeployProxyOutput struct {
Proxy common.Address
}

func (output *DeployProxyOutput) CheckOutput(input common.Address) error {
if output.Proxy == (common.Address{}) {
return fmt.Errorf("output.Proxy not set")
}
return nil
}

type DeployProxyScript struct {
Run func(input, output common.Address) error
}

func DeployProxy(
host *script.Host,
input DeployProxyInput,
) (DeployProxyOutput, error) {
var output DeployProxyOutput
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()

cleanupInput, err := script.WithPrecompileAtAddress[*DeployProxyInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert DeployProxyInput precompile: %w", err)
}
defer cleanupInput()

cleanupOutput, err := script.WithPrecompileAtAddress[*DeployProxyOutput](host, outputAddr, &output,
script.WithFieldSetter[*DeployProxyOutput])
if err != nil {
return output, fmt.Errorf("failed to insert DeployProxyOutput precompile: %w", err)
}
defer cleanupOutput()

implContract := "DeployProxy"
deployScript, cleanupDeploy, err := script.WithScript[DeployProxyScript](host, "DeployProxy.s.sol", implContract)
if err != nil {
return output, fmt.Errorf("failed to load %s script: %w", implContract, err)
}
defer cleanupDeploy()

if err := deployScript.Run(inputAddr, outputAddr); err != nil {
return output, fmt.Errorf("failed to run %s script: %w", implContract, err)
}

return output, nil
}
34 changes: 34 additions & 0 deletions op-deployer/pkg/deployer/opcm/proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package opcm

import (
"testing"

"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-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

func TestDeployProxy(t *testing.T) {
_, artifacts := testutil.LocalArtifacts(t)

host, err := env.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
)
require.NoError(t, err)

input := DeployProxyInput{
Owner: common.Address{0xab},
}

output, err := DeployProxy(host, input)
require.NoError(t, err)

require.NotEmpty(t, output.Proxy)
}
Loading

0 comments on commit d2e2a5b

Please sign in to comment.