Sitelet https://github.com/ethereum-optimism/optimism/commit/2eb0352fe59f9896cf7c0fe37f65eb7f4325f6cc
Skip to content

Commit 2eb0352

Browse files
authored
ctb/op-deployer: Add bootstrap command for validator contracts (#14391)
* ctb/op-deployer: Add bootstrap command for validator contracts * compiler version * remove unused import * fix semgrep
1 parent a953852 commit 2eb0352

9 files changed

Lines changed: 825 additions & 25 deletions

File tree

‎op-chain-ops/script/script.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ func (h *Host) unwindCallstack(depth int) {
567567
"from", bcast.From,
568568
"to", bcast.To,
569569
"input", bcast.Input,
570+
"input_len", len(bcast.Input),
570571
"value", bcast.Value,
571572
"type", bcast.Type,
572573
)

‎op-deployer/pkg/deployer/bootstrap/flags.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ var (
138138
Usage: "If true, deploy Interop implementations.",
139139
EnvVars: deployer.PrefixEnvVar("USE_INTEROP"),
140140
}
141+
ConfigFileFlag = &cli.StringFlag{
142+
Name: "config",
143+
Usage: "Path to a JSON file",
144+
EnvVars: deployer.PrefixEnvVar("CONFIG"),
145+
}
141146
)
142147

143148
var ImplementationsFlags = []cli.Flag{
@@ -179,6 +184,14 @@ var SuperchainFlags = []cli.Flag{
179184
RecommendedProtocolVersionFlag,
180185
}
181186

187+
var ValidatorFlags = []cli.Flag{
188+
deployer.L1RPCURLFlag,
189+
deployer.PrivateKeyFlag,
190+
OutfileFlag,
191+
ArtifactsLocatorFlag,
192+
ConfigFileFlag,
193+
}
194+
182195
var Commands = []*cli.Command{
183196
{
184197
Name: "implementations",
@@ -199,4 +212,10 @@ var Commands = []*cli.Command{
199212
Flags: cliapp.ProtectFlags(SuperchainFlags),
200213
Action: SuperchainCLI,
201214
},
215+
{
216+
Name: "validator",
217+
Usage: "Bootstrap the StandardValidator contracts",
218+
Flags: cliapp.ProtectFlags(ValidatorFlags),
219+
Action: ValidatorCLI,
220+
},
202221
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package bootstrap
2+
3+
import (
4+
"context"
5+
"crypto/ecdsa"
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"strings"
10+
11+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
12+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
13+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
14+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
15+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
16+
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
17+
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
18+
"github.com/ethereum-optimism/optimism/op-service/ioutil"
19+
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
20+
oplog "github.com/ethereum-optimism/optimism/op-service/log"
21+
"github.com/ethereum/go-ethereum/common"
22+
"github.com/ethereum/go-ethereum/crypto"
23+
"github.com/ethereum/go-ethereum/ethclient"
24+
"github.com/ethereum/go-ethereum/log"
25+
"github.com/ethereum/go-ethereum/rpc"
26+
"github.com/urfave/cli/v2"
27+
)
28+
29+
type ValidatorConfig struct {
30+
L1RPCUrl string
31+
PrivateKey string
32+
Logger log.Logger
33+
ArtifactsLocator *artifacts.Locator
34+
Input ValidatorInput
35+
36+
privateKeyECDSA *ecdsa.PrivateKey
37+
}
38+
39+
type ValidatorInput struct {
40+
Release string `json:"release"`
41+
SuperchainConfig common.Address `json:"superchainConfig"`
42+
ProtocolVersions common.Address `json:"protocolVersions"`
43+
L1PAOMultisig common.Address `json:"l1PAOMultisig"`
44+
MIPS common.Address `json:"mips" evm:"mips"`
45+
Challenger common.Address `json:"challenger"`
46+
SuperchainConfigImpl common.Address `json:"superchainConfigImpl"`
47+
ProtocolVersionsImpl common.Address `json:"protocolVersionsImpl"`
48+
L1ERC721BridgeImpl common.Address `json:"l1ERC721BridgeImpl"`
49+
OptimismPortalImpl common.Address `json:"optimismPortalImpl"`
50+
SystemConfigImpl common.Address `json:"systemConfigImpl"`
51+
OptimismMintableERC20FactoryImpl common.Address `json:"optimismMintableERC20FactoryImpl"`
52+
L1CrossDomainMessengerImpl common.Address `json:"l1CrossDomainMessengerImpl"`
53+
L1StandardBridgeImpl common.Address `json:"l1StandardBridgeImpl"`
54+
DisputeGameFactoryImpl common.Address `json:"disputeGameFactoryImpl"`
55+
AnchorStateRegistryImpl common.Address `json:"anchorStateRegistryImpl"`
56+
DelayedWETHImpl common.Address `json:"delayedWETHImpl"`
57+
MIPSImpl common.Address `json:"mipsImpl" evm:"mipsImpl"`
58+
}
59+
60+
type ValidatorOutput struct {
61+
Validator common.Address `json:"validator"`
62+
}
63+
64+
func (c *ValidatorConfig) Check() error {
65+
if c.L1RPCUrl == "" {
66+
return fmt.Errorf("l1RPCUrl must be specified")
67+
}
68+
69+
if c.PrivateKey == "" {
70+
return fmt.Errorf("private key must be specified")
71+
}
72+
73+
privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x"))
74+
if err != nil {
75+
return fmt.Errorf("failed to parse private key: %w", err)
76+
}
77+
c.privateKeyECDSA = privECDSA
78+
79+
if c.Logger == nil {
80+
return fmt.Errorf("logger must be specified")
81+
}
82+
83+
if c.ArtifactsLocator == nil {
84+
return fmt.Errorf("artifacts locator must be specified")
85+
}
86+
87+
return nil
88+
}
89+
90+
func ValidatorCLI(cliCtx *cli.Context) error {
91+
logCfg := oplog.ReadCLIConfig(cliCtx)
92+
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
93+
oplog.SetGlobalLogHandler(l.Handler())
94+
95+
l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName)
96+
privateKey := cliCtx.String(deployer.PrivateKeyFlagName)
97+
outfile := cliCtx.String(OutfileFlagName)
98+
artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName)
99+
configFile := cliCtx.String(ConfigFileFlag.Name)
100+
101+
artifactsLocator := new(artifacts.Locator)
102+
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
103+
return fmt.Errorf("failed to parse artifacts URL: %w", err)
104+
}
105+
106+
// Read the input configuration from file
107+
configData, err := os.ReadFile(configFile)
108+
if err != nil {
109+
return fmt.Errorf("failed to read config file: %w", err)
110+
}
111+
112+
var input ValidatorInput
113+
if err := json.Unmarshal(configData, &input); err != nil {
114+
return fmt.Errorf("failed to parse config file: %w", err)
115+
}
116+
117+
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
118+
119+
dvo, err := Validator(ctx, ValidatorConfig{
120+
L1RPCUrl: l1RPCUrl,
121+
PrivateKey: privateKey,
122+
Logger: l,
123+
ArtifactsLocator: artifactsLocator,
124+
Input: input,
125+
})
126+
if err != nil {
127+
return fmt.Errorf("failed to deploy Validator: %w", err)
128+
}
129+
130+
if err := jsonutil.WriteJSON(dvo, ioutil.ToStdOutOrFileOrNoop(outfile, 0o755)); err != nil {
131+
return fmt.Errorf("failed to write output: %w", err)
132+
}
133+
return nil
134+
}
135+
136+
func Validator(ctx context.Context, cfg ValidatorConfig) (ValidatorOutput, error) {
137+
var output ValidatorOutput
138+
if err := cfg.Check(); err != nil {
139+
return output, fmt.Errorf("invalid config for Validator: %w", err)
140+
}
141+
142+
lgr := cfg.Logger
143+
144+
artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor())
145+
if err != nil {
146+
return output, fmt.Errorf("failed to download artifacts: %w", err)
147+
}
148+
149+
l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
150+
if err != nil {
151+
return output, fmt.Errorf("failed to connect to L1 RPC: %w", err)
152+
}
153+
154+
chainID, err := l1Client.ChainID(ctx)
155+
if err != nil {
156+
return output, fmt.Errorf("failed to get chain ID: %w", err)
157+
}
158+
159+
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
160+
chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
161+
162+
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
163+
Logger: lgr,
164+
ChainID: chainID,
165+
Client: l1Client,
166+
Signer: signer,
167+
From: chainDeployer,
168+
})
169+
if err != nil {
170+
return output, fmt.Errorf("failed to create broadcaster: %w", err)
171+
}
172+
173+
l1RPC, err := rpc.Dial(cfg.L1RPCUrl)
174+
if err != nil {
175+
return output, fmt.Errorf("failed to connect to L1 RPC: %w", err)
176+
}
177+
178+
l1Host, err := env.DefaultForkedScriptHost(
179+
ctx,
180+
bcaster,
181+
lgr,
182+
chainDeployer,
183+
artifactsFS,
184+
l1RPC,
185+
)
186+
if err != nil {
187+
return output, fmt.Errorf("failed to create script host: %w", err)
188+
}
189+
190+
// Execute the deployment script
191+
output, err = opcm.RunScriptSingle[ValidatorInput, ValidatorOutput](
192+
l1Host,
193+
cfg.Input,
194+
"DeployStandardValidator.s.sol",
195+
"DeployStandardValidator",
196+
)
197+
if err != nil {
198+
return output, fmt.Errorf("error deploying validator: %w", err)
199+
}
200+
201+
// Broadcast the transactions
202+
if _, err := bcaster.Broadcast(ctx); err != nil {
203+
return output, fmt.Errorf("failed to broadcast: %w", err)
204+
}
205+
206+
lgr.Info("deployed validator contracts")
207+
208+
return output, nil
209+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package bootstrap
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
13+
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil"
14+
"github.com/ethereum-optimism/optimism/op-service/testlog"
15+
"github.com/ethereum-optimism/optimism/op-service/testutils/devnet"
16+
"github.com/ethereum/go-ethereum/common"
17+
"github.com/ethereum/go-ethereum/ethclient"
18+
"github.com/stretchr/testify/require"
19+
)
20+
21+
func TestValidator(t *testing.T) {
22+
for _, network := range networks {
23+
for _, release := range []string{"v1.8.0", "v2.0.0"} {
24+
t.Run(fmt.Sprintf("%s/%s", network, release), func(t *testing.T) {
25+
envVar := strings.ToUpper(network) + "_RPC_URL"
26+
rpcURL := os.Getenv(envVar)
27+
require.NotEmpty(t, rpcURL, "must specify RPC url via %s env var", envVar)
28+
loc, _ := testutil.LocalArtifacts(t)
29+
testValidator(t, rpcURL, loc, release)
30+
})
31+
}
32+
}
33+
}
34+
35+
func testValidator(t *testing.T, forkRPCURL string, loc *artifacts.Locator, release string) {
36+
t.Parallel()
37+
38+
if forkRPCURL == "" {
39+
t.Skip("forkRPCURL not set")
40+
}
41+
42+
lgr := testlog.Logger(t, slog.LevelDebug)
43+
44+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
45+
defer cancel()
46+
47+
forkedL1, stopL1, err := devnet.NewForked(lgr, forkRPCURL)
48+
require.NoError(t, err)
49+
t.Cleanup(func() {
50+
require.NoError(t, stopL1())
51+
})
52+
l1RPC := forkedL1.RPCUrl()
53+
54+
// Create the validator input configuration
55+
input := ValidatorInput{
56+
Release: release,
57+
SuperchainConfig: common.Address{'S'},
58+
ProtocolVersions: common.Address{'P'},
59+
L1PAOMultisig: common.Address{'M'},
60+
MIPS: common.Address{'I'},
61+
Challenger: common.Address{'C'},
62+
SuperchainConfigImpl: common.Address{'1'},
63+
ProtocolVersionsImpl: common.Address{'2'},
64+
L1ERC721BridgeImpl: common.Address{'3'},
65+
OptimismPortalImpl: common.Address{'4'},
66+
SystemConfigImpl: common.Address{'5'},
67+
OptimismMintableERC20FactoryImpl: common.Address{'6'},
68+
L1CrossDomainMessengerImpl: common.Address{'7'},
69+
L1StandardBridgeImpl: common.Address{'8'},
70+
DisputeGameFactoryImpl: common.Address{'9'},
71+
AnchorStateRegistryImpl: common.Address{'A'},
72+
DelayedWETHImpl: common.Address{'B'},
73+
MIPSImpl: common.Address{'D'},
74+
}
75+
76+
out, err := Validator(ctx, ValidatorConfig{
77+
L1RPCUrl: l1RPC,
78+
PrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
79+
ArtifactsLocator: loc,
80+
Logger: lgr,
81+
Input: input,
82+
})
83+
require.NoError(t, err)
84+
85+
client, err := ethclient.Dial(l1RPC)
86+
require.NoError(t, err)
87+
88+
addr := out.Validator
89+
require.NotEmpty(t, addr)
90+
91+
code, err := client.CodeAt(ctx, addr, nil)
92+
require.NoError(t, err)
93+
require.NotEmpty(t, code)
94+
}

‎packages/contracts-bedrock/foundry.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ compilation_restrictions = [
2525
{ paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 5000 },
2626
{ paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 5000 },
2727
{ paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 },
28+
{ paths = "src/L1/StandardValidator.sol", optimizer_runs = 5000 },
2829
]
2930

3031
extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout']
@@ -136,6 +137,7 @@ compilation_restrictions = [
136137
{ paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 0 },
137138
{ paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 0 },
138139
{ paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 },
140+
{ paths = "src/L1/StandardValidator.sol", optimizer_runs = 0 },
139141
]
140142

141143
################################################################

0 commit comments

Comments
 (0)