Skip to content

Commit e9b80ee

Browse files
authored
Add op-deployer proof-of-concept (ethereum-optimism#11804)
This PR adds a proof-of-concept for `op-deployer`, a CLI tool that allows declarative management of live OP Stack chains. This POC supports initializing the declarative chain config (called an "intent") and deploying the Superchain smart contracts using the OP Stack Manager. An example intent for a Sepolia chain looks like this: ```toml l1ChainID = 11155111 useFaultProofs = true useAltDA = false fundDevAccounts = true contractArtifactsURL = "file:///Users/matthewslipper/dev/optimism/packages/contracts-bedrock/forge-artifacts" [superchainRoles] proxyAdminOwner = "0xb9cdf788704088a4c0191d045c151fcbe2db14a4" protocolVersionsOwner = "0xb910764be39c84d572ff17713c615b5bfd7df650" guardian = "0x8c7e4a51acb17719d225bd17598b8a94b46c8767" ``` When deployed, it produces a state file that looks like this: ```json { "version": 1, "appliedIntent": { "l1ChainID": 11155111, "superchainRoles": { "proxyAdminOwner": "0xb9cdf788704088a4c0191d045c151fcbe2db14a4", "protocolVersionsOwner": "0xb910764be39c84d572ff17713c615b5bfd7df650", "guardian": "0x8c7e4a51acb17719d225bd17598b8a94b46c8767" }, "useFaultProofs": true, "useAltDA": false, "fundDevAccounts": true, "contractArtifactsURL": "file:///Users/matthewslipper/dev/optimism/packages/contracts-bedrock/forge-artifacts", "chains": null }, "superchainDeployment": { "proxyAdminAddress": "0x54a6088c04a7782e69b5031579a1973a9e3c1a8c", "superchainConfigProxyAddress": "0xc969afc4799a9350f9f05b60748bc62f2829b03a", "superchainConfigImplAddress": "0x08426b74350e7cba5b52be4909c542d28b6b3962", "protocolVersionsProxyAddress": "0x212a023892803c7570eb317c77672c8391bf3dde", "protocolVersionsImplAddress": "0x2633ac74edb7ae1f1b5656e042285015f9ee477d" } } ``` To use `op-deployer`, run `op-deployer init --dev --l1-chain-id <chain-id>`. This will initialize a deployment intent using the development keys in the repo. Then, run `op-deployer apply --l1-rpc-url <l1-rpc> --private-key <deployer-private-key>` to apply the deployment. - The contracts deployment is performed by the local Go/Forge tooling. - Upgrades of the contracts (i.e. modifying them after deploying the contracts afresh) is not currently supported. This will be supported in the future. - The rest of the pipeline (i.e., deploying L2s and generating genesis files) is not included in this PR to keep it smaller and allow us to get buy-in on the fundamental concepts behind `op-deployer` before further implementation.
1 parent fe4890f commit e9b80ee

File tree

21 files changed

+1234
-17
lines changed

21 files changed

+1234
-17
lines changed

op-chain-ops/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
bin
2+
intent.toml
3+
state.json

op-chain-ops/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ receipt-reference-builder:
1212
test:
1313
go test ./...
1414

15+
op-deployer:
16+
go build -o ./bin/op-deployer ./cmd/op-deployer/main.go
17+
1518
fuzz:
1619
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzEncodeDecodeWithdrawal ./crossdomain
1720
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzEncodeDecodeLegacyWithdrawal ./crossdomain
1821
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzAliasing ./crossdomain
1922
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzVersionedNonce ./crossdomain
2023

21-
.PHONY: test fuzz
24+
.PHONY: test fuzz op-deployer
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
8+
"github.com/ethereum-optimism/optimism/op-service/cliapp"
9+
"github.com/urfave/cli/v2"
10+
)
11+
12+
func main() {
13+
app := cli.NewApp()
14+
app.Name = "op-deployer"
15+
app.Usage = "Tool to configure and deploy OP Chains."
16+
app.Flags = cliapp.ProtectFlags(deployer.GlobalFlags)
17+
app.Commands = []*cli.Command{
18+
{
19+
Name: "init",
20+
Usage: "initializes a chain intent and state file",
21+
Flags: cliapp.ProtectFlags(deployer.InitFlags),
22+
Action: deployer.InitCLI(),
23+
},
24+
{
25+
Name: "apply",
26+
Usage: "applies a chain intent to the chain",
27+
Flags: cliapp.ProtectFlags(deployer.ApplyFlags),
28+
Action: deployer.ApplyCLI(),
29+
},
30+
}
31+
app.Writer = os.Stdout
32+
app.ErrWriter = os.Stderr
33+
err := app.Run(os.Args)
34+
if err != nil {
35+
_, _ = fmt.Fprintf(os.Stderr, "Application failed: %v\n", err)
36+
os.Exit(1)
37+
}
38+
}

op-chain-ops/deployer/apply.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package deployer
2+
3+
import (
4+
"context"
5+
"crypto/ecdsa"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline"
10+
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
11+
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
12+
oplog "github.com/ethereum-optimism/optimism/op-service/log"
13+
"github.com/ethereum/go-ethereum/crypto"
14+
"github.com/ethereum/go-ethereum/ethclient"
15+
"github.com/ethereum/go-ethereum/log"
16+
"github.com/urfave/cli/v2"
17+
)
18+
19+
type ApplyConfig struct {
20+
L1RPCUrl string
21+
Workdir string
22+
PrivateKey string
23+
Logger log.Logger
24+
25+
privateKeyECDSA *ecdsa.PrivateKey
26+
}
27+
28+
func (a *ApplyConfig) Check() error {
29+
if a.L1RPCUrl == "" {
30+
return fmt.Errorf("l1RPCUrl must be specified")
31+
}
32+
33+
if a.Workdir == "" {
34+
return fmt.Errorf("workdir must be specified")
35+
}
36+
37+
if a.PrivateKey == "" {
38+
return fmt.Errorf("private key must be specified")
39+
}
40+
41+
privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(a.PrivateKey, "0x"))
42+
if err != nil {
43+
return fmt.Errorf("failed to parse private key: %w", err)
44+
}
45+
a.privateKeyECDSA = privECDSA
46+
47+
if a.Logger == nil {
48+
return fmt.Errorf("logger must be specified")
49+
}
50+
51+
return nil
52+
}
53+
54+
func ApplyCLI() func(cliCtx *cli.Context) error {
55+
return func(cliCtx *cli.Context) error {
56+
logCfg := oplog.ReadCLIConfig(cliCtx)
57+
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
58+
oplog.SetGlobalLogHandler(l.Handler())
59+
60+
l1RPCUrl := cliCtx.String(L1RPCURLFlagName)
61+
workdir := cliCtx.String(WorkdirFlagName)
62+
privateKey := cliCtx.String(PrivateKeyFlagName)
63+
64+
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
65+
66+
return Apply(ctx, ApplyConfig{
67+
L1RPCUrl: l1RPCUrl,
68+
Workdir: workdir,
69+
PrivateKey: privateKey,
70+
Logger: l,
71+
})
72+
}
73+
}
74+
75+
func Apply(ctx context.Context, cfg ApplyConfig) error {
76+
if err := cfg.Check(); err != nil {
77+
return fmt.Errorf("invalid config for apply: %w", err)
78+
}
79+
80+
l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
81+
if err != nil {
82+
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
83+
}
84+
85+
chainID, err := l1Client.ChainID(ctx)
86+
if err != nil {
87+
return fmt.Errorf("failed to get chain ID: %w", err)
88+
}
89+
90+
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
91+
deployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
92+
93+
env := &pipeline.Env{
94+
Workdir: cfg.Workdir,
95+
L1RPCUrl: cfg.L1RPCUrl,
96+
L1Client: l1Client,
97+
Logger: cfg.Logger,
98+
Signer: signer,
99+
Deployer: deployer,
100+
}
101+
102+
intent, err := env.ReadIntent()
103+
if err != nil {
104+
return err
105+
}
106+
107+
if err := intent.Check(); err != nil {
108+
return fmt.Errorf("invalid intent: %w", err)
109+
}
110+
111+
st, err := env.ReadState()
112+
if err != nil {
113+
return err
114+
}
115+
116+
pline := []struct {
117+
name string
118+
stage pipeline.Stage
119+
}{
120+
{"init", pipeline.Init},
121+
{"deploy-superchain", pipeline.DeploySuperchain},
122+
}
123+
for _, stage := range pline {
124+
if err := stage.stage(ctx, env, intent, st); err != nil {
125+
return fmt.Errorf("error in pipeline stage: %w", err)
126+
}
127+
}
128+
129+
st.AppliedIntent = intent
130+
if err := env.WriteState(st); err != nil {
131+
return err
132+
}
133+
134+
return nil
135+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package broadcaster
2+
3+
import (
4+
"context"
5+
6+
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/ethereum/go-ethereum/core/types"
9+
)
10+
11+
type Broadcaster interface {
12+
Broadcast(ctx context.Context) ([]BroadcastResult, error)
13+
Hook(bcast script.Broadcast)
14+
}
15+
16+
type BroadcastResult struct {
17+
Broadcast script.Broadcast `json:"broadcast"`
18+
TxHash common.Hash `json:"txHash"`
19+
Receipt *types.Receipt `json:"receipt"`
20+
Err error `json:"-"`
21+
}

0 commit comments

Comments
 (0)