Commit 3f65402a authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

op-deployer: Separate L1 and L2 contract artifacts (#12480)

* op-deployer: Separate L1 and L2 contract artifacts

Adds support for specifying separate artifact URLs for L1 and L2. There are two new fields in the intent - `L1ContractsLocator` and `L2ContractsLocator` - which together replace the `ContractArtifactsURL` and `ContractsRelease` fields. Specifying a file or HTTPS URL for either field will automatically enable dev mode for the deployment. Specifying a `tag://` URL will use the standard deployment for L1 and L2. The default have been set to the following:

- L1: `op-contracts/v1.6.0`
- L2: `op-contracts/v1.7.0-beta.1+l2-contracts`

Fixes https://github.com/ethereum-optimism/platforms-team/issues/337.

* fix test

* fix another test
parent dfdc28be
......@@ -6,8 +6,6 @@ import (
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline"
......@@ -94,6 +92,16 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
deployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
intent, err := pipeline.ReadIntent(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}
st, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read state: %w", err)
}
env := &pipeline.Env{
Workdir: cfg.Workdir,
L1Client: l1Client,
......@@ -102,29 +110,10 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
Deployer: deployer,
}
intent, err := env.ReadIntent()
if err != nil {
return err
}
if err := intent.Check(); err != nil {
return fmt.Errorf("invalid intent: %w", err)
}
st, err := env.ReadState()
if err != nil {
return err
}
if err := ApplyPipeline(ctx, env, intent, st); err != nil {
return err
}
st.AppliedIntent = intent
if err := env.WriteState(st); err != nil {
return err
}
return nil
}
......@@ -143,16 +132,31 @@ func ApplyPipeline(
env.Logger.Info("artifacts download progress", "current", curr, "total", total)
}
artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, intent.ContractArtifactsURL, progressor)
l1ArtifactsFS, cleanupL1, err := pipeline.DownloadArtifacts(ctx, intent.L1ContractsLocator, progressor)
if err != nil {
return fmt.Errorf("failed to download artifacts: %w", err)
return fmt.Errorf("failed to download L1 artifacts: %w", err)
}
defer func() {
if err := cleanup(); err != nil {
env.Logger.Warn("failed to clean up artifacts", "err", err)
if err := cleanupL1(); err != nil {
env.Logger.Warn("failed to clean up L1 artifacts", "err", err)
}
}()
l2ArtifactsFS, cleanupL2, err := pipeline.DownloadArtifacts(ctx, intent.L2ContractsLocator, progressor)
if err != nil {
return fmt.Errorf("failed to download L2 artifacts: %w", err)
}
defer func() {
if err := cleanupL2(); err != nil {
env.Logger.Warn("failed to clean up L2 artifacts", "err", err)
}
}()
bundle := pipeline.ArtifactsBundle{
L1: l1ArtifactsFS,
L2: l2ArtifactsFS,
}
pline := []pipelineStage{
{"init", pipeline.Init},
{"deploy-superchain", pipeline.DeploySuperchain},
......@@ -163,25 +167,28 @@ func ApplyPipeline(
chainID := chain.ID
pline = append(pline, pipelineStage{
fmt.Sprintf("deploy-opchain-%s", chainID.Hex()),
func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error {
return pipeline.DeployOPChain(ctx, env, artifactsFS, intent, st, chainID)
func(ctx context.Context, env *pipeline.Env, bundle pipeline.ArtifactsBundle, intent *state.Intent, st *state.State) error {
return pipeline.DeployOPChain(ctx, env, bundle, intent, st, chainID)
},
}, pipelineStage{
fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()),
func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error {
return pipeline.GenerateL2Genesis(ctx, env, artifactsFS, intent, st, chainID)
func(ctx context.Context, env *pipeline.Env, bundle pipeline.ArtifactsBundle, intent *state.Intent, st *state.State) error {
return pipeline.GenerateL2Genesis(ctx, env, bundle, intent, st, chainID)
},
})
}
for _, stage := range pline {
if err := stage.apply(ctx, env, artifactsFS, intent, st); err != nil {
if err := stage.apply(ctx, env, bundle, intent, st); err != nil {
return fmt.Errorf("error in pipeline stage apply: %w", err)
}
if err := pipeline.WriteState(env.Workdir, st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
}
st.AppliedIntent = intent
if err := env.WriteState(st); err != nil {
if err := pipeline.WriteState(env.Workdir, st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
......
......@@ -11,7 +11,6 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
......@@ -30,7 +29,7 @@ type OPCMConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsURL *state.ArtifactsURL
ArtifactsLocator *opcm.ArtifactsLocator
ContractsRelease string
privateKeyECDSA *ecdsa.PrivateKey
......@@ -55,8 +54,8 @@ func (c *OPCMConfig) Check() error {
return fmt.Errorf("logger must be specified")
}
if c.ArtifactsURL == nil {
return fmt.Errorf("artifacts URL must be specified")
if c.ArtifactsLocator == nil {
return fmt.Errorf("artifacts locator must be specified")
}
if c.ContractsRelease == "" {
......@@ -74,8 +73,8 @@ func OPCMCLI(cliCtx *cli.Context) error {
l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName)
privateKey := cliCtx.String(deployer.PrivateKeyFlagName)
artifactsURLStr := cliCtx.String(ArtifactsURLFlagName)
artifactsURL := new(state.ArtifactsURL)
if err := artifactsURL.UnmarshalText([]byte(artifactsURLStr)); err != nil {
artifactsLocator := new(opcm.ArtifactsLocator)
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
return fmt.Errorf("failed to parse artifacts URL: %w", err)
}
contractsRelease := cliCtx.String(ContractsReleaseFlagName)
......@@ -86,7 +85,7 @@ func OPCMCLI(cliCtx *cli.Context) error {
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsURL: artifactsURL,
ArtifactsLocator: artifactsLocator,
ContractsRelease: contractsRelease,
})
}
......@@ -101,7 +100,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.ArtifactsURL, progressor)
artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, cfg.ArtifactsLocator, progressor)
if err != nil {
return fmt.Errorf("failed to download artifacts: %w", err)
}
......@@ -126,7 +125,7 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
if err != nil {
return fmt.Errorf("error getting superchain config: %w", err)
}
standardVersionsTOML, err := opcm.StandardVersionsFor(chainIDU64)
standardVersionsTOML, err := opcm.StandardL1VersionsDataFor(chainIDU64)
if err != nil {
return fmt.Errorf("error getting standard versions TOML: %w", err)
}
......
......@@ -7,6 +7,8 @@ import (
"path"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
op_service "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
......@@ -15,8 +17,6 @@ import (
"github.com/urfave/cli/v2"
)
var V160ArtifactsURL = state.MustParseArtifactsURL("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-ee07c78c3d8d4cd8f7a933c050f5afeebaa281b57b226cc6f092b19de2a8d61f.tar.gz")
type InitConfig struct {
L1ChainID uint64
Outdir string
......@@ -77,8 +77,8 @@ func Init(cfg InitConfig) error {
intent := &state.Intent{
L1ChainID: cfg.L1ChainID,
FundDevAccounts: true,
ContractsRelease: "op-contracts/v1.6.0",
ContractArtifactsURL: V160ArtifactsURL,
L1ContractsLocator: opcm.DefaultL1ContractsLocator,
L2ContractsLocator: opcm.DefaultL2ContractsLocator,
}
l1ChainIDBig := intent.L1ChainIDBig()
......
......@@ -21,8 +21,7 @@ func GenesisCLI(cliCtx *cli.Context) error {
return err
}
env := &pipeline.Env{Workdir: cfg.Workdir}
globalState, err := env.ReadState()
globalState, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}
......
......@@ -62,8 +62,7 @@ func L1CLI(cliCtx *cli.Context) error {
return err
}
env := &pipeline.Env{Workdir: cfg.Workdir}
globalState, err := env.ReadState()
globalState, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}
......
......@@ -15,8 +15,7 @@ func RollupCLI(cliCtx *cli.Context) error {
return err
}
env := &pipeline.Env{Workdir: cfg.Workdir}
globalState, err := env.ReadState()
globalState, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}
......
......@@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
......@@ -194,6 +196,9 @@ func makeIntent(
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,
}
addrFor := func(key devkeys.Key) common.Address {
addr, err := dk.Address(key)
......@@ -209,8 +214,8 @@ func makeIntent(
Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainID)),
},
FundDevAccounts: true,
ContractArtifactsURL: (*state.ArtifactsURL)(artifactsURL),
ContractsRelease: "dev",
L1ContractsLocator: artifactsLocator,
L2ContractsLocator: artifactsLocator,
Chains: []*state.ChainIntent{
{
ID: l2ChainID.Bytes32(),
......@@ -353,7 +358,8 @@ func TestApplyExistingOPCM(t *testing.T) {
}
intent, st := makeIntent(t, l1ChainID, dk, l2ChainID)
intent.ContractsRelease = "op-contracts/v1.6.0"
intent.L1ContractsLocator = opcm.DefaultL1ContractsLocator
intent.L2ContractsLocator = opcm.DefaultL2ContractsLocator
require.NoError(t, deployer.ApplyPipeline(
ctx,
......
package opcm
import (
"fmt"
"net/url"
"strings"
)
type schemeUnmarshaler func(string) (*ArtifactsLocator, error)
var schemeUnmarshalerDispatch = map[string]schemeUnmarshaler{
"tag": unmarshalTag,
"file": unmarshalURL,
"https": unmarshalURL,
}
type ArtifactsLocator struct {
URL *url.URL
Tag string
}
func (a *ArtifactsLocator) UnmarshalText(text []byte) error {
str := string(text)
for scheme, unmarshaler := range schemeUnmarshalerDispatch {
if !strings.HasPrefix(str, scheme+"://") {
continue
}
loc, err := unmarshaler(str)
if err != nil {
return err
}
*a = *loc
return nil
}
return fmt.Errorf("unsupported scheme")
}
func (a *ArtifactsLocator) MarshalText() ([]byte, error) {
if a.URL != nil {
return []byte(a.URL.String()), nil
}
if a.Tag != "" {
return []byte(a.Tag), nil
}
return nil, fmt.Errorf("no URL, path or tag set")
}
func (a *ArtifactsLocator) IsTag() bool {
return a.Tag != ""
}
func unmarshalTag(tag string) (*ArtifactsLocator, error) {
tag = strings.TrimPrefix(tag, "tag://")
if !strings.HasPrefix(tag, "op-contracts/") {
return nil, fmt.Errorf("invalid tag: %s", tag)
}
if _, err := StandardArtifactsURLForTag(tag); err != nil {
return nil, err
}
return &ArtifactsLocator{Tag: tag}, nil
}
func unmarshalURL(text string) (*ArtifactsLocator, error) {
u, err := url.Parse(text)
if err != nil {
return nil, err
}
return &ArtifactsLocator{URL: u}, nil
}
package opcm
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
)
func TestArtifactsLocator_Marshaling(t *testing.T) {
tests := []struct {
name string
in string
out *ArtifactsLocator
err bool
}{
{
name: "valid tag",
in: "tag://op-contracts/v1.6.0",
out: &ArtifactsLocator{
Tag: "op-contracts/v1.6.0",
},
err: false,
},
{
name: "well-formed but nonexistent tag",
in: "tag://op-contracts/v1.5.0",
out: nil,
err: true,
},
{
name: "mal-formed tag",
in: "tag://honk",
out: nil,
err: true,
},
{
name: "valid HTTPS URL",
in: "https://example.com",
out: &ArtifactsLocator{
URL: parseUrl(t, "https://example.com"),
},
err: false,
},
{
name: "valid file URL",
in: "file:///tmp/artifacts",
out: &ArtifactsLocator{
URL: parseUrl(t, "file:///tmp/artifacts"),
},
err: false,
},
{
name: "empty",
in: "",
out: nil,
err: true,
},
{
name: "no scheme",
in: "example.com",
out: nil,
err: true,
},
{
name: "unsupported scheme",
in: "http://example.com",
out: nil,
err: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var a ArtifactsLocator
err := a.UnmarshalText([]byte(tt.in))
if tt.err {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.out, &a)
})
}
}
func parseUrl(t *testing.T, u string) *url.URL {
parsed, err := url.Parse(u)
require.NoError(t, err)
return parsed
}
......@@ -3,6 +3,7 @@ package opcm
import (
"embed"
"fmt"
"net/url"
"github.com/BurntSushi/toml"
......@@ -16,15 +17,23 @@ var StandardVersionsMainnetData string
//go:embed standard-versions-sepolia.toml
var StandardVersionsSepoliaData string
var StandardVersionsSepolia StandardVersions
var StandardL1VersionsSepolia StandardL1Versions
var StandardVersionsMainnet StandardVersions
var StandardL1VersionsMainnet StandardL1Versions
type StandardVersions struct {
Releases map[string]StandardVersionsReleases `toml:"releases"`
var DefaultL1ContractsLocator = &ArtifactsLocator{
Tag: "op-contracts/v1.6.0",
}
type StandardVersionsReleases struct {
var DefaultL2ContractsLocator = &ArtifactsLocator{
Tag: "op-contracts/v1.7.0-beta.1+l2-contracts",
}
type StandardL1Versions struct {
Releases map[string]StandardL1VersionsReleases `toml:"releases"`
}
type StandardL1VersionsReleases struct {
OptimismPortal StandardVersionRelease `toml:"optimism_portal"`
SystemConfig StandardVersionRelease `toml:"system_config"`
AnchorStateRegistry StandardVersionRelease `toml:"anchor_state_registry"`
......@@ -48,7 +57,7 @@ type StandardVersionRelease struct {
var _ embed.FS
func StandardVersionsFor(chainID uint64) (string, error) {
func StandardL1VersionsDataFor(chainID uint64) (string, error) {
switch chainID {
case 1:
return StandardVersionsMainnetData, nil
......@@ -59,6 +68,17 @@ func StandardVersionsFor(chainID uint64) (string, error) {
}
}
func StandardL1VersionsFor(chainID uint64) (StandardL1Versions, error) {
switch chainID {
case 1:
return StandardL1VersionsMainnet, nil
case 11155111:
return StandardL1VersionsSepolia, nil
default:
return StandardL1Versions{}, fmt.Errorf("unsupported chain ID: %d", chainID)
}
}
func SuperchainFor(chainID uint64) (*superchain.Superchain, error) {
switch chainID {
case 1:
......@@ -93,14 +113,29 @@ func ManagerOwnerAddrFor(chainID uint64) (common.Address, error) {
}
}
func StandardArtifactsURLForTag(tag string) (*url.URL, error) {
switch tag {
case "op-contracts/v1.6.0":
return url.Parse(standardArtifactsURL("ee07c78c3d8d4cd8f7a933c050f5afeebaa281b57b226cc6f092b19de2a8d61f"))
case "op-contracts/v1.7.0-beta.1+l2-contracts":
return url.Parse(standardArtifactsURL("40ca65dc738f0f5fbb05ec9ec953d9be94bc1c02a09fd871a36b152f6b36c1fe"))
default:
return nil, fmt.Errorf("unsupported tag: %s", tag)
}
}
func standardArtifactsURL(checksum string) string {
return fmt.Sprintf("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-%s.tar.gz", checksum)
}
func init() {
StandardVersionsMainnet = StandardVersions{}
if err := toml.Unmarshal([]byte(StandardVersionsMainnetData), &StandardVersionsMainnet); err != nil {
StandardL1VersionsMainnet = StandardL1Versions{}
if err := toml.Unmarshal([]byte(StandardVersionsMainnetData), &StandardL1VersionsMainnet); err != nil {
panic(err)
}
StandardVersionsSepolia = StandardVersions{}
if err := toml.Unmarshal([]byte(StandardVersionsSepoliaData), &StandardVersionsSepolia); err != nil {
StandardL1VersionsSepolia = StandardL1Versions{}
if err := toml.Unmarshal([]byte(StandardVersionsSepoliaData), &StandardL1VersionsSepolia); err != nil {
panic(err)
}
}
......@@ -15,7 +15,8 @@ import (
"strings"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
)
......@@ -27,10 +28,21 @@ type CleanupFunc func() error
var noopCleanup = func() error { return nil }
func DownloadArtifacts(ctx context.Context, artifactsURL *state.ArtifactsURL, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) {
switch artifactsURL.Scheme {
func DownloadArtifacts(ctx context.Context, loc *opcm.ArtifactsLocator, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) {
var u *url.URL
var err error
if loc.IsTag() {
u, err = opcm.StandardArtifactsURLForTag(loc.Tag)
if err != nil {
return nil, nil, fmt.Errorf("failed to get standard artifacts URL for tag %s: %w", loc.Tag, err)
}
} else {
u = loc.URL
}
switch u.Scheme {
case "http", "https":
req, err := http.NewRequestWithContext(ctx, http.MethodGet, (*url.URL)(artifactsURL).String(), nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create request: %w", err)
}
......@@ -73,7 +85,7 @@ func DownloadArtifacts(ctx context.Context, artifactsURL *state.ArtifactsURL, pr
}
return fs.(foundry.StatDirFs), cleanup, nil
case "file":
fs := os.DirFS(artifactsURL.Path)
fs := os.DirFS(u.Path)
return fs.(foundry.StatDirFs), noopCleanup, nil
default:
return nil, nil, ErrUnsupportedArtifactsScheme
......
......@@ -9,7 +9,8 @@ import (
"os"
"testing"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/stretchr/testify/require"
)
......@@ -28,8 +29,11 @@ func TestDownloadArtifacts(t *testing.T) {
ctx := context.Background()
artifactsURL, err := url.Parse(ts.URL)
require.NoError(t, err)
loc := &opcm.ArtifactsLocator{
URL: artifactsURL,
}
fs, cleanup, err := DownloadArtifacts(ctx, (*state.ArtifactsURL)(artifactsURL), nil)
fs, cleanup, err := DownloadArtifacts(ctx, loc, nil)
require.NoError(t, err)
require.NotNil(t, fs)
defer func() {
......
......@@ -23,8 +23,8 @@ type Env struct {
Logger log.Logger
}
func (e *Env) ReadIntent() (*state.Intent, error) {
intentPath := path.Join(e.Workdir, "intent.toml")
func ReadIntent(workdir string) (*state.Intent, error) {
intentPath := path.Join(workdir, "intent.toml")
intent, err := jsonutil.LoadTOML[state.Intent](intentPath)
if err != nil {
return nil, fmt.Errorf("failed to read intent file: %w", err)
......@@ -32,8 +32,8 @@ func (e *Env) ReadIntent() (*state.Intent, error) {
return intent, nil
}
func (e *Env) ReadState() (*state.State, error) {
statePath := path.Join(e.Workdir, "state.json")
func ReadState(workdir string) (*state.State, error) {
statePath := path.Join(workdir, "state.json")
st, err := jsonutil.LoadJSON[state.State](statePath)
if err != nil {
return nil, fmt.Errorf("failed to read state file: %w", err)
......@@ -41,9 +41,14 @@ func (e *Env) ReadState() (*state.State, error) {
return st, nil
}
func (e *Env) WriteState(st *state.State) error {
statePath := path.Join(e.Workdir, "state.json")
func WriteState(workdir string, st *state.State) error {
statePath := path.Join(workdir, "state.json")
return st.WriteToFile(statePath)
}
type Stage func(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, state2 *state.State) error
type ArtifactsBundle struct {
L1 foundry.StatDirFs
L2 foundry.StatDirFs
}
type Stage func(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error
......@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
......@@ -12,7 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)
func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error {
func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "deploy-implementations")
if !shouldDeployImplementations(intent, st) {
......@@ -23,12 +22,16 @@ func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.St
lgr.Info("deploying implementations")
var standardVersionsTOML string
var contractsRelease string
var err error
if strings.HasPrefix(intent.ContractsRelease, "op-contracts") {
standardVersionsTOML, err = opcm.StandardVersionsFor(intent.L1ChainID)
if intent.L1ContractsLocator.IsTag() {
standardVersionsTOML, err = opcm.StandardL1VersionsDataFor(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting standard versions TOML: %w", err)
}
contractsRelease = intent.L1ContractsLocator.Tag
} else {
contractsRelease = "dev"
}
var dump *foundry.ForgeAllocs
......@@ -38,7 +41,7 @@ func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.St
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: artifactsFS,
ArtifactsFS: bundle.L1,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
......@@ -56,7 +59,7 @@ func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.St
ProofMaturityDelaySeconds: big.NewInt(604800),
DisputeGameFinalityDelaySeconds: big.NewInt(302400),
MipsVersion: big.NewInt(1),
Release: intent.ContractsRelease,
Release: contractsRelease,
SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress,
ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress,
OpcmProxyOwner: st.SuperchainDeployment.ProxyAdminAddress,
......@@ -93,9 +96,6 @@ func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.St
DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl,
StateDump: dump,
}
if err := env.WriteState(st); err != nil {
return err
}
return nil
}
......
......@@ -4,11 +4,8 @@ import (
"context"
"crypto/rand"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum/go-ethereum/common"
......@@ -20,7 +17,7 @@ func IsSupportedStateVersion(version int) bool {
return version == 1
}
func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Intent, st *state.State) error {
func Init(ctx context.Context, env *Env, _ ArtifactsBundle, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "init")
lgr.Info("initializing pipeline")
......@@ -36,7 +33,7 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte
}
}
if strings.HasPrefix(intent.ContractsRelease, "op-contracts") {
if intent.L1ContractsLocator.IsTag() {
superCfg, err := opcm.SuperchainFor(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting superchain config: %w", err)
......@@ -64,29 +61,13 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte
}
}
// If the state has never been applied, we don't need to perform
// any additional checks.
if st.AppliedIntent == nil {
return nil
}
// If the state has been applied, we need to check if any immutable
// fields have changed.
if st.AppliedIntent.L1ChainID != intent.L1ChainID {
return immutableErr("L1ChainID", st.AppliedIntent.L1ChainID, intent.L1ChainID)
}
if st.AppliedIntent.FundDevAccounts != intent.FundDevAccounts {
return immutableErr("fundDevAccounts", st.AppliedIntent.FundDevAccounts, intent.FundDevAccounts)
}
l1ChainID, err := env.L1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to get L1 chain ID: %w", err)
}
if l1ChainID.Cmp(intent.L1ChainIDBig()) != 0 {
return fmt.Errorf("L1 chain ID mismatch: got %d, expected %d", l1ChainID, intent.L1ChainID)
return fmt.Errorf("l1 chain ID mismatch: got %d, expected %d", l1ChainID, intent.L1ChainID)
}
deployerCode, err := env.L1Client.CodeAt(ctx, script.DeterministicDeployerAddress, nil)
......@@ -97,6 +78,22 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte
return fmt.Errorf("deterministic deployer is not deployed on this chain - please deploy it first")
}
// If the state has never been applied, we don't need to perform
// any additional checks.
if st.AppliedIntent == nil {
return nil
}
// If the state has been applied, we need to check if any immutable
// fields have changed.
if st.AppliedIntent.L1ChainID != intent.L1ChainID {
return immutableErr("L1ChainID", st.AppliedIntent.L1ChainID, intent.L1ChainID)
}
if st.AppliedIntent.FundDevAccounts != intent.FundDevAccounts {
return immutableErr("fundDevAccounts", st.AppliedIntent.FundDevAccounts, intent.FundDevAccounts)
}
// TODO: validate individual
return nil
......
......@@ -15,7 +15,7 @@ import (
"github.com/ethereum/go-ethereum/common"
)
func GenerateL2Genesis(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State, chainID common.Hash) error {
func GenerateL2Genesis(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "generate-l2-genesis")
lgr.Info("generating L2 genesis", "id", chainID.Hex())
......@@ -41,7 +41,7 @@ func GenerateL2Genesis(ctx context.Context, env *Env, artifactsFS foundry.StatDi
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: artifactsFS,
ArtifactsFS: bundle.L2,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
......@@ -89,9 +89,5 @@ func GenerateL2Genesis(ctx context.Context, env *Env, artifactsFS foundry.StatDi
}
thisChainState.StartBlock = startHeader
if err := env.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
return nil
}
......@@ -9,11 +9,10 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum/go-ethereum/common"
)
func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State, chainID common.Hash) error {
func DeployOPChain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain")
if !shouldDeployOPChain(intent, st, chainID) {
......@@ -66,7 +65,7 @@ func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs,
env.L1Client,
bcaster,
env.Deployer,
artifactsFS,
bundle.L1,
input,
)
if err != nil {
......@@ -91,9 +90,6 @@ func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs,
DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy,
DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy,
})
if err := env.WriteState(st); err != nil {
return err
}
return nil
}
......
......@@ -13,7 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
func DeploySuperchain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error {
func DeploySuperchain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "deploy-superchain")
if !shouldDeploySuperchain(intent, st) {
......@@ -31,7 +31,7 @@ func DeploySuperchain(ctx context.Context, env *Env, artifactsFS foundry.StatDir
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: artifactsFS,
ArtifactsFS: bundle.L1,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
......@@ -71,9 +71,6 @@ func DeploySuperchain(ctx context.Context, env *Env, artifactsFS foundry.StatDir
ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl,
StateDump: dump,
}
if err := env.WriteState(st); err != nil {
return err
}
return nil
}
......
package state
import "net/url"
type ArtifactsURL url.URL
func (a *ArtifactsURL) MarshalText() ([]byte, error) {
return []byte((*url.URL)(a).String()), nil
}
func (a *ArtifactsURL) UnmarshalText(text []byte) error {
u, err := url.Parse(string(text))
if err != nil {
return err
}
*a = ArtifactsURL(*u)
return nil
}
func ParseArtifactsURL(in string) (*ArtifactsURL, error) {
u, err := url.Parse(in)
if err != nil {
return nil, err
}
return (*ArtifactsURL)(u), nil
}
func MustParseArtifactsURL(in string) *ArtifactsURL {
u, err := ParseArtifactsURL(in)
if err != nil {
panic(err)
}
return u
}
......@@ -3,8 +3,8 @@ package state
import (
"fmt"
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"
......@@ -19,9 +19,9 @@ type Intent struct {
FundDevAccounts bool `json:"fundDevAccounts" toml:"fundDevAccounts"`
ContractArtifactsURL *ArtifactsURL `json:"contractArtifactsURL" toml:"contractArtifactsURL"`
L1ContractsLocator *opcm.ArtifactsLocator `json:"l1ContractsLocator" toml:"l1ContractsLocator"`
ContractsRelease string `json:"contractsRelease" toml:"contractsRelease"`
L2ContractsLocator *opcm.ArtifactsLocator `json:"l2ContractsLocator" toml:"l2ContractsLocator"`
Chains []*ChainIntent `json:"chains" toml:"chains"`
......@@ -37,11 +37,31 @@ func (c *Intent) Check() error {
return fmt.Errorf("l1ChainID must be set")
}
if c.ContractsRelease == "dev" {
return c.checkDev()
if c.L1ContractsLocator == nil {
c.L1ContractsLocator = opcm.DefaultL1ContractsLocator
}
return c.checkProd()
if c.L2ContractsLocator == nil {
c.L2ContractsLocator = opcm.DefaultL2ContractsLocator
}
var err error
if c.L1ContractsLocator.IsTag() {
err = c.checkL1Prod()
} else {
err = c.checkL1Dev()
}
if err != nil {
return err
}
if c.L2ContractsLocator.IsTag() {
if err := c.checkL2Prod(); err != nil {
return err
}
}
return nil
}
func (c *Intent) Chain(id common.Hash) (*ChainIntent, error) {
......@@ -58,7 +78,20 @@ func (c *Intent) WriteToFile(path string) error {
return jsonutil.WriteTOML(c, ioutil.ToAtomicFile(path, 0o755))
}
func (c *Intent) checkDev() error {
func (c *Intent) checkL1Prod() error {
versions, err := opcm.StandardL1VersionsFor(c.L1ChainID)
if err != nil {
return err
}
if _, ok := versions.Releases[c.L1ContractsLocator.Tag]; !ok {
return fmt.Errorf("tag '%s' not found in standard versions", c.L1ContractsLocator.Tag)
}
return nil
}
func (c *Intent) checkL1Dev() error {
if c.SuperchainRoles.ProxyAdminOwner == emptyAddress {
return fmt.Errorf("proxyAdminOwner must be set")
}
......@@ -71,19 +104,12 @@ func (c *Intent) checkDev() error {
c.SuperchainRoles.Guardian = c.SuperchainRoles.ProxyAdminOwner
}
if c.ContractArtifactsURL == nil {
return fmt.Errorf("contractArtifactsURL must be set in dev mode")
}
return nil
}
func (c *Intent) checkProd() error {
if !strings.HasPrefix(c.ContractsRelease, "op-contracts/") {
return fmt.Errorf("contractsVersion must be either the literal \"dev\" or start with \"op-contracts/\"")
}
return nil
func (c *Intent) checkL2Prod() error {
_, err := opcm.StandardArtifactsURLForTag(c.L2ContractsLocator.Tag)
return err
}
type SuperchainRoles struct {
......
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