diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index 95db427d8efb..f6eb38d11e08 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -7,6 +7,10 @@ import ( "math/big" "strings" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" "github.com/ethereum/go-ethereum/common" @@ -146,7 +150,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { cfg.Logger.Info("artifacts download progress", "current", curr, "total", total) } - l1ArtifactsFS, cleanupL1, err := pipeline.DownloadArtifacts(ctx, intent.L1ContractsLocator, progressor) + l1ArtifactsFS, cleanupL1, err := artifacts.Download(ctx, intent.L1ContractsLocator, progressor) if err != nil { return fmt.Errorf("failed to download L1 artifacts: %w", err) } @@ -156,7 +160,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { } }() - l2ArtifactsFS, cleanupL2, err := pipeline.DownloadArtifacts(ctx, intent.L2ContractsLocator, progressor) + l2ArtifactsFS, cleanupL2, err := artifacts.Download(ctx, intent.L2ContractsLocator, progressor) if err != nil { return fmt.Errorf("failed to download L2 artifacts: %w", err) } @@ -171,7 +175,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { L2: l2ArtifactsFS, } - l1Host, err := pipeline.DefaultScriptHost(bcaster, cfg.Logger, deployer, bundle.L1, startingNonce) + l1Host, err := env.DefaultScriptHost(bcaster, cfg.Logger, deployer, bundle.L1, startingNonce) if err != nil { return fmt.Errorf("failed to create L1 script host: %w", err) } diff --git a/op-deployer/pkg/deployer/pipeline/downloader.go b/op-deployer/pkg/deployer/artifacts/downloader.go similarity index 94% rename from op-deployer/pkg/deployer/pipeline/downloader.go rename to op-deployer/pkg/deployer/artifacts/downloader.go index ffafd6096869..1303adbe86aa 100644 --- a/op-deployer/pkg/deployer/pipeline/downloader.go +++ b/op-deployer/pkg/deployer/artifacts/downloader.go @@ -1,4 +1,4 @@ -package pipeline +package artifacts import ( "archive/tar" @@ -19,8 +19,6 @@ import ( "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" ) @@ -40,7 +38,7 @@ func LogProgressor(lgr log.Logger) DownloadProgressor { } } -func DownloadArtifacts(ctx context.Context, loc *opcm.ArtifactsLocator, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) { +func Download(ctx context.Context, loc *Locator, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) { var u *url.URL var err error if loc.IsTag() { diff --git a/op-deployer/pkg/deployer/pipeline/downloader_test.go b/op-deployer/pkg/deployer/artifacts/downloader_test.go similarity index 82% rename from op-deployer/pkg/deployer/pipeline/downloader_test.go rename to op-deployer/pkg/deployer/artifacts/downloader_test.go index 36183fe5afa5..e66b41f96a81 100644 --- a/op-deployer/pkg/deployer/pipeline/downloader_test.go +++ b/op-deployer/pkg/deployer/artifacts/downloader_test.go @@ -1,4 +1,4 @@ -package pipeline +package artifacts import ( "context" @@ -9,8 +9,6 @@ import ( "os" "testing" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" - "github.com/stretchr/testify/require" ) @@ -29,11 +27,11 @@ func TestDownloadArtifacts(t *testing.T) { ctx := context.Background() artifactsURL, err := url.Parse(ts.URL) require.NoError(t, err) - loc := &opcm.ArtifactsLocator{ + loc := &Locator{ URL: artifactsURL, } - fs, cleanup, err := DownloadArtifacts(ctx, loc, nil) + fs, cleanup, err := Download(ctx, loc, nil) require.NoError(t, err) require.NotNil(t, fs) defer func() { diff --git a/op-deployer/pkg/deployer/opcm/artifacts_locator.go b/op-deployer/pkg/deployer/artifacts/locator.go similarity index 66% rename from op-deployer/pkg/deployer/opcm/artifacts_locator.go rename to op-deployer/pkg/deployer/artifacts/locator.go index 02a2b60b59fb..160e8790420b 100644 --- a/op-deployer/pkg/deployer/opcm/artifacts_locator.go +++ b/op-deployer/pkg/deployer/artifacts/locator.go @@ -1,4 +1,4 @@ -package opcm +package artifacts import ( "fmt" @@ -8,7 +8,7 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" ) -type schemeUnmarshaler func(string) (*ArtifactsLocator, error) +type schemeUnmarshaler func(string) (*Locator, error) var schemeUnmarshalerDispatch = map[string]schemeUnmarshaler{ "tag": unmarshalTag, @@ -16,20 +16,20 @@ var schemeUnmarshalerDispatch = map[string]schemeUnmarshaler{ "https": unmarshalURL, } -var DefaultL1ContractsLocator = &ArtifactsLocator{ +var DefaultL1ContractsLocator = &Locator{ Tag: standard.DefaultL1ContractsTag, } -var DefaultL2ContractsLocator = &ArtifactsLocator{ +var DefaultL2ContractsLocator = &Locator{ Tag: standard.DefaultL2ContractsTag, } -type ArtifactsLocator struct { +type Locator struct { URL *url.URL Tag string } -func (a *ArtifactsLocator) UnmarshalText(text []byte) error { +func (a *Locator) UnmarshalText(text []byte) error { str := string(text) for scheme, unmarshaler := range schemeUnmarshalerDispatch { @@ -49,7 +49,7 @@ func (a *ArtifactsLocator) UnmarshalText(text []byte) error { return fmt.Errorf("unsupported scheme") } -func (a *ArtifactsLocator) MarshalText() ([]byte, error) { +func (a *Locator) MarshalText() ([]byte, error) { if a.URL != nil { return []byte(a.URL.String()), nil } @@ -61,11 +61,11 @@ func (a *ArtifactsLocator) MarshalText() ([]byte, error) { return nil, fmt.Errorf("no URL, path or tag set") } -func (a *ArtifactsLocator) IsTag() bool { +func (a *Locator) IsTag() bool { return a.Tag != "" } -func unmarshalTag(tag string) (*ArtifactsLocator, error) { +func unmarshalTag(tag string) (*Locator, error) { tag = strings.TrimPrefix(tag, "tag://") if !strings.HasPrefix(tag, "op-contracts/") { return nil, fmt.Errorf("invalid tag: %s", tag) @@ -75,14 +75,14 @@ func unmarshalTag(tag string) (*ArtifactsLocator, error) { return nil, err } - return &ArtifactsLocator{Tag: tag}, nil + return &Locator{Tag: tag}, nil } -func unmarshalURL(text string) (*ArtifactsLocator, error) { +func unmarshalURL(text string) (*Locator, error) { u, err := url.Parse(text) if err != nil { return nil, err } - return &ArtifactsLocator{URL: u}, nil + return &Locator{URL: u}, nil } diff --git a/op-deployer/pkg/deployer/opcm/artifacts_locator_test.go b/op-deployer/pkg/deployer/artifacts/locator_test.go similarity index 88% rename from op-deployer/pkg/deployer/opcm/artifacts_locator_test.go rename to op-deployer/pkg/deployer/artifacts/locator_test.go index 8771fad32ec9..678e2c252b32 100644 --- a/op-deployer/pkg/deployer/opcm/artifacts_locator_test.go +++ b/op-deployer/pkg/deployer/artifacts/locator_test.go @@ -1,4 +1,4 @@ -package opcm +package artifacts import ( "net/url" @@ -7,17 +7,17 @@ import ( "github.com/stretchr/testify/require" ) -func TestArtifactsLocator_Marshaling(t *testing.T) { +func TestLocator_Marshaling(t *testing.T) { tests := []struct { name string in string - out *ArtifactsLocator + out *Locator err bool }{ { name: "valid tag", in: "tag://op-contracts/v1.6.0", - out: &ArtifactsLocator{ + out: &Locator{ Tag: "op-contracts/v1.6.0", }, err: false, @@ -37,7 +37,7 @@ func TestArtifactsLocator_Marshaling(t *testing.T) { { name: "valid HTTPS URL", in: "https://example.com", - out: &ArtifactsLocator{ + out: &Locator{ URL: parseUrl(t, "https://example.com"), }, err: false, @@ -45,7 +45,7 @@ func TestArtifactsLocator_Marshaling(t *testing.T) { { name: "valid file URL", in: "file:///tmp/artifacts", - out: &ArtifactsLocator{ + out: &Locator{ URL: parseUrl(t, "file:///tmp/artifacts"), }, err: false, @@ -71,7 +71,7 @@ func TestArtifactsLocator_Marshaling(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var a ArtifactsLocator + var a Locator err := a.UnmarshalText([]byte(tt.in)) if tt.err { require.Error(t, err) diff --git a/op-deployer/pkg/deployer/pipeline/testdata/artifacts.tar.gz b/op-deployer/pkg/deployer/artifacts/testdata/artifacts.tar.gz similarity index 100% rename from op-deployer/pkg/deployer/pipeline/testdata/artifacts.tar.gz rename to op-deployer/pkg/deployer/artifacts/testdata/artifacts.tar.gz diff --git a/op-deployer/pkg/deployer/bootstrap/delayed_weth.go b/op-deployer/pkg/deployer/bootstrap/delayed_weth.go new file mode 100644 index 000000000000..45952d19e955 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/delayed_weth.go @@ -0,0 +1,204 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "strings" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "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/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type DelayedWETHConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts2.Locator + + privateKeyECDSA *ecdsa.PrivateKey +} + +func (c *DelayedWETHConfig) 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") + } + + return nil +} + +func DelayedWETHCLI(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(artifacts2.Locator) + if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { + return fmt.Errorf("failed to parse artifacts URL: %w", err) + } + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return DelayedWETH(ctx, DelayedWETHConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + }) +} + +func DelayedWETH(ctx context.Context, cfg DelayedWETHConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for DelayedWETH: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := artifacts2.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) + } + chainIDU64 := chainID.Uint64() + + superCfg, err := standard.SuperchainFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain config: %w", err) + } + standardVersionsTOML, err := standard.L1VersionsDataFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting standard versions TOML: %w", err) + } + proxyAdmin, err := standard.ManagerOwnerAddrFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain proxy admin: %w", err) + } + delayedWethOwner, err := standard.SystemOwnerAddrFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain system owner: %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) + } + + nonce, err := l1Client.NonceAt(ctx, chainDeployer, nil) + if err != nil { + return fmt.Errorf("failed to get starting nonce: %w", err) + } + + host, err := env.DefaultScriptHost( + bcaster, + lgr, + chainDeployer, + artifactsFS, + nonce, + ) + if err != nil { + return fmt.Errorf("failed to create script host: %w", err) + } + + var release string + if cfg.ArtifactsLocator.IsTag() { + release = cfg.ArtifactsLocator.Tag + } else { + release = "dev" + } + + lgr.Info("deploying DelayedWETH", "release", release) + + superchainConfigAddr := common.Address(*superCfg.Config.SuperchainConfigAddr) + + dwo, err := opcm.DeployDelayedWETH( + host, + opcm.DeployDelayedWETHInput{ + Release: release, + StandardVersionsToml: standardVersionsTOML, + ProxyAdmin: proxyAdmin, + SuperchainConfigProxy: superchainConfigAddr, + DelayedWethOwner: delayedWethOwner, + DelayedWethDelay: big.NewInt(604800), + }, + ) + if err != nil { + return fmt.Errorf("error deploying implementations: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed DelayedWETH") + + if err := jsonutil.WriteJSON(dwo, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index d0c9eb5e2ce2..6fd598780df5 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -73,6 +73,12 @@ var OPCMFlags = []cli.Flag{ MIPSVersionFlag, } +var DelayedWETHFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ArtifactsLocatorFlag, +} + var Commands = []*cli.Command{ { Name: "opcm", @@ -80,4 +86,10 @@ var Commands = []*cli.Command{ Flags: cliapp.ProtectFlags(OPCMFlags), Action: OPCMCLI, }, + { + Name: "delayedweth", + Usage: "Bootstrap an instance of DelayedWETH.", + Flags: cliapp.ProtectFlags(DelayedWETHFlags), + Action: DelayedWETHCLI, + }, } diff --git a/op-deployer/pkg/deployer/bootstrap/opcm.go b/op-deployer/pkg/deployer/bootstrap/opcm.go index 4b792ef92160..d584d6975f41 100644 --- a/op-deployer/pkg/deployer/bootstrap/opcm.go +++ b/op-deployer/pkg/deployer/bootstrap/opcm.go @@ -8,6 +8,10 @@ import ( "math/big" "strings" + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" @@ -35,7 +39,7 @@ type OPCMConfig struct { L1RPCUrl string PrivateKey string Logger log.Logger - ArtifactsLocator *opcm.ArtifactsLocator + ArtifactsLocator *artifacts2.Locator privateKeyECDSA *ecdsa.PrivateKey } @@ -98,7 +102,7 @@ func OPCMCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) - artifactsLocator := new(opcm.ArtifactsLocator) + artifactsLocator := new(artifacts2.Locator) if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { return fmt.Errorf("failed to parse artifacts URL: %w", err) } @@ -131,7 +135,7 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error { lgr.Info("artifacts download progress", "current", curr, "total", total) } - artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, cfg.ArtifactsLocator, progressor) + artifactsFS, cleanup, err := artifacts2.Download(ctx, cfg.ArtifactsLocator, progressor) if err != nil { return fmt.Errorf("failed to download artifacts: %w", err) } @@ -184,7 +188,7 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error { return fmt.Errorf("failed to get starting nonce: %w", err) } - host, err := pipeline.DefaultScriptHost( + host, err := env.DefaultScriptHost( bcaster, lgr, chainDeployer, diff --git a/op-deployer/pkg/deployer/init.go b/op-deployer/pkg/deployer/init.go index 6dd26222fb82..3a8ae27d2ae7 100644 --- a/op-deployer/pkg/deployer/init.go +++ b/op-deployer/pkg/deployer/init.go @@ -7,7 +7,7 @@ import ( "path" "strings" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" @@ -90,8 +90,8 @@ func Init(cfg InitConfig) error { DeploymentStrategy: cfg.DeploymentStrategy, L1ChainID: cfg.L1ChainID, FundDevAccounts: true, - L1ContractsLocator: opcm.DefaultL1ContractsLocator, - L2ContractsLocator: opcm.DefaultL2ContractsLocator, + L1ContractsLocator: artifacts.DefaultL1ContractsLocator, + L2ContractsLocator: artifacts.DefaultL2ContractsLocator, } l1ChainIDBig := intent.L1ChainIDBig() diff --git a/op-deployer/pkg/deployer/inspect/semvers.go b/op-deployer/pkg/deployer/inspect/semvers.go index f88f2c01476a..7c8e4ce9f3dd 100644 --- a/op-deployer/pkg/deployer/inspect/semvers.go +++ b/op-deployer/pkg/deployer/inspect/semvers.go @@ -8,6 +8,10 @@ import ( "regexp" "time" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" @@ -53,7 +57,7 @@ func L2SemversCLI(cliCtx *cli.Context) error { return fmt.Errorf("chain state does not have allocs") } - artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, intent.L2ContractsLocator, pipeline.LogProgressor(l)) + artifactsFS, cleanup, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.LogProgressor(l)) if err != nil { return fmt.Errorf("failed to download L2 artifacts: %w", err) } @@ -63,7 +67,7 @@ func L2SemversCLI(cliCtx *cli.Context) error { } }() - host, err := pipeline.DefaultScriptHost( + host, err := env.DefaultScriptHost( broadcaster.NoopBroadcaster(), l, common.Address{19: 0x01}, diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index af240aeffa73..e0b34dc8ea6a 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -7,14 +7,16 @@ import ( "fmt" "log/slog" "math/big" - "net/url" "os" - "path" - "runtime" "testing" "time" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "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-deployer/pkg/deployer/broadcaster" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-service/testutils/anvil" @@ -22,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" @@ -109,7 +110,7 @@ func TestEndToEndApply(t *testing.T) { deployerAddr, err := dk.Address(depKey) require.NoError(t, err) - loc := localArtifactsLocator(t) + loc, _ := testutil.LocalArtifacts(t) bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ Logger: log.NewLogger(log.DiscardHandler()), @@ -120,7 +121,7 @@ func TestEndToEndApply(t *testing.T) { }) require.NoError(t, err) - env, bundle, _ := createEnv(t, ctx, lgr, l1Client, bcaster, deployerAddr) + env, bundle, _ := createEnv(t, lgr, l1Client, bcaster, deployerAddr) intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) cg := ethClientCodeGetter(ctx, l1Client) @@ -140,7 +141,7 @@ func TestEndToEndApply(t *testing.T) { t.Run("subsequent chain", func(t *testing.T) { // create a new environment with wiped state to ensure we can continue using the // state from the previous deployment - env, bundle, _ = createEnv(t, ctx, lgr, l1Client, bcaster, deployerAddr) + env, bundle, _ = createEnv(t, lgr, l1Client, bcaster, deployerAddr) intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainID, l2ChainID2)) require.NoError(t, deployer.ApplyPipeline( @@ -202,15 +203,15 @@ func TestApplyExistingOPCM(t *testing.T) { }) require.NoError(t, err) - env, bundle, _ := createEnv(t, ctx, lgr, l1Client, bcaster, deployerAddr) + env, bundle, _ := createEnv(t, lgr, l1Client, bcaster, deployerAddr) intent, st := newIntent( t, l1ChainID, dk, l2ChainID, - opcm.DefaultL1ContractsLocator, - opcm.DefaultL2ContractsLocator, + artifacts.DefaultL1ContractsLocator, + artifacts.DefaultL2ContractsLocator, ) require.NoError(t, deployer.ApplyPipeline( @@ -421,7 +422,7 @@ func TestInvalidL2Genesis(t *testing.T) { deployerAddr, err := dk.Address(depKey) require.NoError(t, err) - loc := localArtifactsLocator(t) + loc, _ := testutil.LocalArtifacts(t) // these tests were generated by grepping all usages of the deploy // config in L2Genesis.s.sol. @@ -468,7 +469,7 @@ func TestInvalidL2Genesis(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - env, bundle, _ := createEnv(t, ctx, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr) + env, bundle, _ := createEnv(t, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr) intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainID, l2ChainID1)) intent.DeploymentStrategy = state.DeploymentStrategyGenesis @@ -490,9 +491,6 @@ func TestInvalidL2Genesis(t *testing.T) { func setupGenesisChain(t *testing.T) (*pipeline.Env, pipeline.ArtifactsBundle, *state.Intent, *state.State) { lgr := testlog.Logger(t, slog.LevelDebug) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - depKey := new(deployerKey) l1ChainID := big.NewInt(77799777) dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) @@ -503,53 +501,25 @@ func setupGenesisChain(t *testing.T) (*pipeline.Env, pipeline.ArtifactsBundle, * deployerAddr, err := dk.Address(depKey) require.NoError(t, err) - loc := localArtifactsLocator(t) + loc, _ := testutil.LocalArtifacts(t) - env, bundle, _ := createEnv(t, ctx, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr) + env, bundle, _ := createEnv(t, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr) intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainID, l2ChainID1)) intent.DeploymentStrategy = state.DeploymentStrategyGenesis return env, bundle, intent, st } -func localArtifactsLocator(t *testing.T) *opcm.ArtifactsLocator { - _, testFilename, _, ok := runtime.Caller(0) - require.Truef(t, ok, "failed to get test filename") - monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..", "..") - artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") - artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) - require.NoError(t, err) - loc := &opcm.ArtifactsLocator{ - URL: artifactsURL, - } - return loc -} - func createEnv( t *testing.T, - ctx context.Context, lgr log.Logger, l1Client *ethclient.Client, bcaster broadcaster.Broadcaster, deployerAddr common.Address, ) (*pipeline.Env, pipeline.ArtifactsBundle, *script.Host) { - _, testFilename, _, ok := runtime.Caller(0) - require.Truef(t, ok, "failed to get test filename") - monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..", "..") - artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") - artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) - require.NoError(t, err) - artifactsLocator := &opcm.ArtifactsLocator{ - URL: artifactsURL, - } - - artifactsFS, cleanupArtifacts, err := pipeline.DownloadArtifacts(ctx, artifactsLocator, pipeline.NoopDownloadProgressor) - require.NoError(t, err) - defer func() { - require.NoError(t, cleanupArtifacts()) - }() + _, artifactsFS := testutil.LocalArtifacts(t) - host, err := pipeline.DefaultScriptHost( + host, err := env.DefaultScriptHost( bcaster, lgr, deployerAddr, @@ -586,8 +556,8 @@ func newIntent( l1ChainID *big.Int, dk *devkeys.MnemonicDevKeys, l2ChainID *uint256.Int, - l1Loc *opcm.ArtifactsLocator, - l2Loc *opcm.ArtifactsLocator, + l1Loc *artifacts.Locator, + l2Loc *artifacts.Locator, ) (*state.Intent, *state.State) { intent := &state.Intent{ DeploymentStrategy: state.DeploymentStrategyLive, diff --git a/op-deployer/pkg/deployer/opcm/delayed_weth.go b/op-deployer/pkg/deployer/opcm/delayed_weth.go new file mode 100644 index 000000000000..d94e25cb9092 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/delayed_weth.go @@ -0,0 +1,71 @@ +package opcm + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployDelayedWETHInput struct { + Release string + StandardVersionsToml string + ProxyAdmin common.Address + SuperchainConfigProxy common.Address + DelayedWethOwner common.Address + DelayedWethDelay *big.Int +} + +func (input *DeployDelayedWETHInput) InputSet() bool { + return true +} + +type DeployDelayedWETHOutput struct { + DelayedWethImpl common.Address + DelayedWethProxy common.Address +} + +func (output *DeployDelayedWETHOutput) CheckOutput(input common.Address) error { + return nil +} + +type DeployDelayedWETHScript struct { + Run func(input, output common.Address) error +} + +func DeployDelayedWETH( + host *script.Host, + input DeployDelayedWETHInput, +) (DeployDelayedWETHOutput, error) { + var output DeployDelayedWETHOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*DeployDelayedWETHInput](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert DeployDelayedWETHInput precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployDelayedWETHOutput](host, outputAddr, &output, + script.WithFieldSetter[*DeployDelayedWETHOutput]) + if err != nil { + return output, fmt.Errorf("failed to insert DeployDelayedWETHOutput precompile: %w", err) + } + defer cleanupOutput() + + implContract := "DeployDelayedWETH" + deployScript, cleanupDeploy, err := script.WithScript[DeployDelayedWETHScript](host, "DeployDelayedWETH.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 +} diff --git a/op-deployer/pkg/deployer/opcm/delayed_weth_test.go b/op-deployer/pkg/deployer/opcm/delayed_weth_test.go new file mode 100644 index 000000000000..98ca7ab6996b --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/delayed_weth_test.go @@ -0,0 +1,46 @@ +package opcm + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "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 TestDeployDelayedWETH(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + 0, + ) + require.NoError(t, err) + + standardVersionsTOML, err := standard.L1VersionsDataFor(11155111) + require.NoError(t, err) + + input := DeployDelayedWETHInput{ + Release: "dev", + StandardVersionsToml: standardVersionsTOML, + ProxyAdmin: common.Address{'P'}, + SuperchainConfigProxy: common.Address{'S'}, + DelayedWethOwner: common.Address{'O'}, + DelayedWethDelay: big.NewInt(100), + } + + output, err := DeployDelayedWETH(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.DelayedWethImpl) + require.NotEmpty(t, output.DelayedWethProxy) +} diff --git a/op-deployer/pkg/deployer/pipeline/l2genesis.go b/op-deployer/pkg/deployer/pipeline/l2genesis.go index 4fa8755df6ee..a5dabefd8e84 100644 --- a/op-deployer/pkg/deployer/pipeline/l2genesis.go +++ b/op-deployer/pkg/deployer/pipeline/l2genesis.go @@ -3,6 +3,8 @@ package pipeline import ( "fmt" + env2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" @@ -36,7 +38,7 @@ func GenerateL2Genesis(env *Env, intent *state.Intent, bundle ArtifactsBundle, s return fmt.Errorf("failed to combine L2 init config: %w", err) } - host, err := DefaultScriptHost( + host, err := env2.DefaultScriptHost( broadcaster.NoopBroadcaster(), env.Logger, env.Deployer, diff --git a/op-deployer/pkg/deployer/pipeline/opchain.go b/op-deployer/pkg/deployer/pipeline/opchain.go index 66699348709d..59e5d214cf4f 100644 --- a/op-deployer/pkg/deployer/pipeline/opchain.go +++ b/op-deployer/pkg/deployer/pipeline/opchain.go @@ -6,6 +6,8 @@ import ( "errors" "fmt" + "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-service/jsonutil" @@ -144,7 +146,7 @@ func conditionallySetImplementationAddresses(ctx context.Context, client *ethcli return nil } -func setMipsSingletonAddress(ctx context.Context, client *ethclient.Client, l1ArtifactsLocator *opcm.ArtifactsLocator, errCh chan error, opcmProxyAddress common.Address, singletonAddress *common.Address) { +func setMipsSingletonAddress(ctx context.Context, client *ethclient.Client, l1ArtifactsLocator *artifacts.Locator, errCh chan error, opcmProxyAddress common.Address, singletonAddress *common.Address) { if !l1ArtifactsLocator.IsTag() { errCh <- errors.New("L1 contracts locator is not a tag, cannot set MIPS singleton address") return diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 46fb812b33c5..78144ca1a7bf 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -156,6 +156,19 @@ func ManagerOwnerAddrFor(chainID uint64) (common.Address, error) { } } +func SystemOwnerAddrFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + // Set to owner of superchain proxy admin + return common.HexToAddress("0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A"), nil + case 11155111: + // Set to development multisig + return common.HexToAddress("0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + func ArtifactsURLForTag(tag string) (*url.URL, error) { switch tag { case "op-contracts/v1.6.0": diff --git a/op-deployer/pkg/deployer/state/intent.go b/op-deployer/pkg/deployer/state/intent.go index cbabe37fc8ef..681d166bc142 100644 --- a/op-deployer/pkg/deployer/state/intent.go +++ b/op-deployer/pkg/deployer/state/intent.go @@ -4,9 +4,9 @@ import ( "fmt" "math/big" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" - "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-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" @@ -42,9 +42,9 @@ type Intent struct { UseInterop bool `json:"useInterop" toml:"useInterop"` - L1ContractsLocator *opcm.ArtifactsLocator `json:"l1ContractsLocator" toml:"l1ContractsLocator"` + L1ContractsLocator *artifacts.Locator `json:"l1ContractsLocator" toml:"l1ContractsLocator"` - L2ContractsLocator *opcm.ArtifactsLocator `json:"l2ContractsLocator" toml:"l2ContractsLocator"` + L2ContractsLocator *artifacts.Locator `json:"l2ContractsLocator" toml:"l2ContractsLocator"` Chains []*ChainIntent `json:"chains" toml:"chains"` @@ -65,11 +65,11 @@ func (c *Intent) Check() error { } if c.L1ContractsLocator == nil { - c.L1ContractsLocator = opcm.DefaultL1ContractsLocator + c.L1ContractsLocator = artifacts.DefaultL1ContractsLocator } if c.L2ContractsLocator == nil { - c.L2ContractsLocator = opcm.DefaultL2ContractsLocator + c.L2ContractsLocator = artifacts.DefaultL2ContractsLocator } var err error diff --git a/op-deployer/pkg/deployer/testutil/env.go b/op-deployer/pkg/deployer/testutil/env.go new file mode 100644 index 000000000000..6b289c4503b7 --- /dev/null +++ b/op-deployer/pkg/deployer/testutil/env.go @@ -0,0 +1,37 @@ +package testutil + +import ( + "context" + "fmt" + "net/url" + "path" + "runtime" + "testing" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/stretchr/testify/require" +) + +func LocalArtifacts(t *testing.T) (*artifacts2.Locator, foundry.StatDirFs) { + _, testFilename, _, ok := runtime.Caller(0) + require.Truef(t, ok, "failed to get test filename") + monorepoDir, err := op_service.FindMonorepoRoot(testFilename) + require.NoError(t, err) + artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") + artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) + require.NoError(t, err) + loc := &artifacts2.Locator{ + URL: artifactsURL, + } + + artifactsFS, cleanupArtifacts, err := artifacts2.Download(context.Background(), loc, artifacts2.NoopDownloadProgressor) + require.NoError(t, err) + t.Cleanup(func() { + _ = cleanupArtifacts() + }) + + return loc, artifactsFS +} diff --git a/op-deployer/pkg/deployer/pipeline/host.go b/op-deployer/pkg/env/host.go similarity index 98% rename from op-deployer/pkg/deployer/pipeline/host.go rename to op-deployer/pkg/env/host.go index df8e6c9c789b..04a0c4a172d6 100644 --- a/op-deployer/pkg/deployer/pipeline/host.go +++ b/op-deployer/pkg/env/host.go @@ -1,4 +1,4 @@ -package pipeline +package env import ( "fmt" diff --git a/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol new file mode 100644 index 000000000000..979869cb3ad0 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Libraries +import { LibString } from "@solady/utils/LibString.sol"; + +// Interfaces +import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; +import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; + +/// @title DeployDelayedWETH +contract DeployDelayedWETHInput is BaseDeployIO { + /// Required inputs. + string internal _release; + string internal _standardVersionsToml; + address public _proxyAdmin; + ISuperchainConfig public _superchainConfigProxy; + address public _delayedWethOwner; + uint256 public _delayedWethDelay; + + function set(bytes4 _sel, uint256 _value) public { + if (_sel == this.delayedWethDelay.selector) { + require(_value != 0, "DeployDelayedWETH: delayedWethDelay cannot be zero"); + _delayedWethDelay = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function set(bytes4 _sel, address _value) public { + if (_sel == this.proxyAdmin.selector) { + require(_value != address(0), "DeployDelayedWETH: proxyAdmin cannot be zero address"); + _proxyAdmin = _value; + } else if (_sel == this.superchainConfigProxy.selector) { + require(_value != address(0), "DeployDelayedWETH: superchainConfigProxy cannot be zero address"); + _superchainConfigProxy = ISuperchainConfig(_value); + } else if (_sel == this.delayedWethOwner.selector) { + require(_value != address(0), "DeployDelayedWETH: delayedWethOwner cannot be zero address"); + _delayedWethOwner = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function set(bytes4 _sel, string memory _value) public { + if (_sel == this.release.selector) { + require(!LibString.eq(_value, ""), "DeployDelayedWETH: release cannot be empty"); + _release = _value; + } else if (_sel == this.standardVersionsToml.selector) { + require(!LibString.eq(_value, ""), "DeployDelayedWETH: standardVersionsToml cannot be empty"); + _standardVersionsToml = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function release() public view returns (string memory) { + require(!LibString.eq(_release, ""), "DeployDelayedWETH: release not set"); + return _release; + } + + function standardVersionsToml() public view returns (string memory) { + require(!LibString.eq(_standardVersionsToml, ""), "DeployDelayedWETH: standardVersionsToml not set"); + return _standardVersionsToml; + } + + function proxyAdmin() public view returns (address) { + require(_proxyAdmin != address(0), "DeployDelayedWETH: proxyAdmin not set"); + return _proxyAdmin; + } + + function superchainConfigProxy() public view returns (ISuperchainConfig) { + require(address(_superchainConfigProxy) != address(0), "DeployDisputeGame: superchainConfigProxy not set"); + return _superchainConfigProxy; + } + + function delayedWethOwner() public view returns (address) { + require(_delayedWethOwner != address(0), "DeployDelayedWETH: delayedWethOwner not set"); + return _delayedWethOwner; + } + + function delayedWethDelay() public view returns (uint256) { + require(_delayedWethDelay != 0, "DeployDelayedWETH: delayedWethDelay not set"); + return _delayedWethDelay; + } +} + +/// @title DeployDelayedWETHOutput +contract DeployDelayedWETHOutput is BaseDeployIO { + IDelayedWETH internal _delayedWethImpl; + IDelayedWETH internal _delayedWethProxy; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.delayedWethImpl.selector) { + require(_value != address(0), "DeployDelayedWETHOutput: delayedWethImpl cannot be zero address"); + _delayedWethImpl = IDelayedWETH(payable(_value)); + } else if (_sel == this.delayedWethProxy.selector) { + require(_value != address(0), "DeployDelayedWETHOutput: delayedWethProxy cannot be zero address"); + _delayedWethProxy = IDelayedWETH(payable(_value)); + } else { + revert("DeployDelayedWETHOutput: unknown selector"); + } + } + + function checkOutput(DeployDelayedWETHInput _dwi) public { + DeployUtils.assertValidContractAddress(address(_delayedWethImpl)); + DeployUtils.assertValidContractAddress(address(_delayedWethProxy)); + assertValidDeploy(_dwi); + } + + function delayedWethImpl() public view returns (IDelayedWETH) { + DeployUtils.assertValidContractAddress(address(_delayedWethImpl)); + return _delayedWethImpl; + } + + function delayedWethProxy() public view returns (IDelayedWETH) { + DeployUtils.assertValidContractAddress(address(_delayedWethProxy)); + return _delayedWethProxy; + } + + function assertValidDeploy(DeployDelayedWETHInput _dwi) public { + assertValidDelayedWethImpl(_dwi); + assertValidDelayedWethProxy(_dwi); + } + + function assertValidDelayedWethImpl(DeployDelayedWETHInput _dwi) internal { + IProxy proxy = IProxy(payable(address(delayedWethProxy()))); + vm.prank(address(0)); + address impl = proxy.implementation(); + require(impl == address(delayedWethImpl()), "DWI-10"); + DeployUtils.assertInitialized({ _contractAddress: address(delayedWethImpl()), _slot: 0, _offset: 0 }); + require(delayedWethImpl().owner() == address(0), "DWI-20"); + require(delayedWethImpl().delay() == _dwi.delayedWethDelay(), "DWI-30"); + require(address(delayedWethImpl().config()) == address(0), "DWI-30"); + } + + function assertValidDelayedWethProxy(DeployDelayedWETHInput _dwi) internal { + // Check as proxy. + IProxy proxy = IProxy(payable(address(delayedWethProxy()))); + vm.prank(address(0)); + address admin = proxy.admin(); + require(admin == _dwi.proxyAdmin(), "DWP-10"); + + // Check as implementation. + DeployUtils.assertInitialized({ _contractAddress: address(delayedWethProxy()), _slot: 0, _offset: 0 }); + require(delayedWethProxy().owner() == _dwi.delayedWethOwner(), "DWP-20"); + require(delayedWethProxy().delay() == _dwi.delayedWethDelay(), "DWP-30"); + require(delayedWethProxy().config() == _dwi.superchainConfigProxy(), "DWP-40"); + } +} + +/// @title DeployDelayedWETH +contract DeployDelayedWETH is Script { + function run(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) public { + deployDelayedWethProxy(_dwi, _dwo); + _dwo.checkOutput(_dwi); + } + + function deployDelayedWethImpl(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal { + string memory release = _dwi.release(); + string memory stdVerToml = _dwi.standardVersionsToml(); + string memory contractName = "delayed_weth"; + IDelayedWETH impl; + + address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); + if (existingImplementation != address(0)) { + impl = IDelayedWETH(payable(existingImplementation)); + } else if (isDevelopRelease(release)) { + vm.broadcast(msg.sender); + impl = IDelayedWETH( + DeployUtils.create1({ + _name: "DelayedWETH", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IDelayedWETH.__constructor__, (_dwi.delayedWethDelay())) + ) + }) + ); + } else { + revert(string.concat("DeployDelayedWETH: failed to deploy release ", release)); + } + + vm.label(address(impl), "DelayedWETHImpl"); + _dwo.set(_dwo.delayedWethImpl.selector, address(impl)); + } + + function deployDelayedWethProxy(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal { + vm.broadcast(msg.sender); + IProxy proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) + }) + ); + + deployDelayedWethImpl(_dwi, _dwo); + IDelayedWETH impl = _dwo.delayedWethImpl(); + + vm.startBroadcast(msg.sender); + proxy.upgradeToAndCall( + address(impl), abi.encodeCall(impl.initialize, (_dwi.delayedWethOwner(), _dwi.superchainConfigProxy())) + ); + proxy.changeAdmin(_dwi.proxyAdmin()); + vm.stopBroadcast(); + + vm.label(address(proxy), "DelayedWETHProxy"); + _dwo.set(_dwo.delayedWethProxy.selector, address(proxy)); + } + + // Zero address is returned if the address is not found in '_standardVersionsToml'. + function getReleaseAddress( + string memory _version, + string memory _contractName, + string memory _standardVersionsToml + ) + internal + pure + returns (address addr_) + { + string memory baseKey = string.concat('.releases["', _version, '"].', _contractName); + string memory implAddressKey = string.concat(baseKey, ".implementation_address"); + string memory addressKey = string.concat(baseKey, ".address"); + try vm.parseTomlAddress(_standardVersionsToml, implAddressKey) returns (address parsedAddr_) { + addr_ = parsedAddr_; + } catch { + try vm.parseTomlAddress(_standardVersionsToml, addressKey) returns (address parsedAddr_) { + addr_ = parsedAddr_; + } catch { + addr_ = address(0); + } + } + } + + // A release is considered a 'develop' release if it does not start with 'op-contracts'. + function isDevelopRelease(string memory _release) internal pure returns (bool) { + return !LibString.startsWith(_release, "op-contracts"); + } +}