Commit 50564d43 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

op-deployer: Support forking live chains (#12918)

parent ae78b73d
......@@ -53,15 +53,7 @@ func (c *CheatCodesPrecompile) CreateSelectFork_84d52b7a(urlOrAlias string, txHa
// createSelectFork implements vm.createSelectFork:
// https://book.getfoundry.sh/cheatcodes/create-select-fork
func (c *CheatCodesPrecompile) createSelectFork(opts ...ForkOption) (*big.Int, error) {
src, err := c.h.onFork(opts...)
if err != nil {
return nil, fmt.Errorf("failed to setup fork source: %w", err)
}
id, err := c.h.state.CreateSelectFork(src)
if err != nil {
return nil, fmt.Errorf("failed to create-select fork: %w", err)
}
return id.U256().ToBig(), nil
return c.h.CreateSelectFork(opts...)
}
// ActiveFork implements vm.activeFork:
......
......@@ -53,7 +53,9 @@ func NewForkDB(source ForkSource) *ForkDB {
// fakeRoot is just a marker; every account we load into the fork-db has this storage-root.
// When opening a storage-trie, we sanity-check we have this root, or an empty trie.
// And then just return the same global trie view for storage reads/writes.
var fakeRoot = common.Hash{0: 42}
// It needs to be set to EmptyRootHash to avoid contract collision errors when
// deploying contracts, since Geth checks the storage root prior to deployment.
var fakeRoot = types.EmptyRootHash
func (f *ForkDB) OpenTrie(root common.Hash) (state.Trie, error) {
if f.active.stateRoot != root {
......
......@@ -54,8 +54,6 @@ func NewForkableState(base VMStateDB) *ForkableState {
addresses.DefaultSenderAddr: ForkID{},
addresses.VMAddr: ForkID{},
addresses.ConsoleAddr: ForkID{},
addresses.ScriptDeployer: ForkID{},
addresses.ForgeDeployer: ForkID{},
},
fallback: base,
idCounter: 0,
......@@ -194,11 +192,27 @@ func (fst *ForkableState) MakePersistent(addr common.Address) {
fst.persistent[addr] = fst.activeFork
}
// MakeExcluded excludes an account from forking. This is useful for things like scripts, which
// should always use the fallback state.
func (fst *ForkableState) MakeExcluded(addr common.Address) {
fst.persistent[addr] = ForkID{}
}
// RevokePersistent is like vm.revokePersistent, it undoes a previous vm.makePersistent.
func (fst *ForkableState) RevokePersistent(addr common.Address) {
delete(fst.persistent, addr)
}
// RevokeExcluded undoes MakeExcluded. It will panic if the account was marked as
// persistent in a different fork.
func (fst *ForkableState) RevokeExcluded(addr common.Address) {
forkID, ok := fst.persistent[addr]
if ok && forkID != (ForkID{}) {
panic(fmt.Sprintf("cannot revoke excluded account %s since it was made persistent in fork %q", addr, forkID))
}
delete(fst.persistent, addr)
}
// IsPersistent is like vm.isPersistent, it checks if an account persists across forks.
func (fst *ForkableState) IsPersistent(addr common.Address) bool {
_, ok := fst.persistent[addr]
......
......@@ -9,7 +9,6 @@ import (
"math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/accounts/abi"
......@@ -850,3 +849,15 @@ func (h *Host) RememberOnLabel(label, srcFile, contract string) error {
})
return nil
}
func (h *Host) CreateSelectFork(opts ...ForkOption) (*big.Int, error) {
src, err := h.onFork(opts...)
if err != nil {
return nil, fmt.Errorf("failed to setup fork source: %w", err)
}
id, err := h.state.CreateSelectFork(src)
if err != nil {
return nil, fmt.Errorf("failed to create-select fork: %w", err)
}
return id.U256().ToBig(), nil
}
......@@ -282,8 +282,8 @@ func TestForkingScript(t *testing.T) {
addr, err := h.LoadContract("ScriptExample.s.sol", "ForkTester")
require.NoError(t, err)
h.AllowCheatcodes(addr)
// Make this script persistent so it doesn't call the fork RPC.
h.state.MakePersistent(addr)
// Make this script excluded so it doesn't call the fork RPC.
h.state.MakeExcluded(addr)
t.Logf("allowing %s to access cheatcodes", addr)
input := bytes4("run()")
......
......@@ -34,7 +34,7 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun
addr := crypto.CreateAddress(deployer, deployNonce)
h.Label(addr, contract)
h.AllowCheatcodes(addr) // before constructor execution, give our script cheatcode access
h.state.MakePersistent(addr) // scripts are persistent across forks
h.state.MakeExcluded(addr) // scripts are persistent across forks
// init bindings (with ABI check)
bindings, err := MakeBindings[B](h.ScriptBackendFn(addr), func(abiDef string) bool {
......
......@@ -7,11 +7,15 @@ import (
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-chain-ops/script/forking"
"github.com/ethereum/go-ethereum/rpc"
"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"
......@@ -104,50 +108,43 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
return fmt.Errorf("failed to read state: %w", err)
}
var l1Client *ethclient.Client
var deployer common.Address
var bcaster broadcaster.Broadcaster
var startingNonce uint64
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
if err := cfg.CheckLive(); err != nil {
return fmt.Errorf("invalid config for apply: %w", err)
}
l1Client, err = ethclient.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
if err := ApplyPipeline(ctx, ApplyPipelineOpts{
L1RPCUrl: cfg.L1RPCUrl,
DeployerPrivateKey: cfg.privateKeyECDSA,
Intent: intent,
State: st,
Logger: cfg.Logger,
StateWriter: pipeline.WorkdirStateWriter(cfg.Workdir),
}); err != nil {
return err
}
chainID, err := l1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to get chain ID: %w", err)
}
return nil
}
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
deployer = crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
type pipelineStage struct {
name string
apply func() error
}
bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: cfg.Logger,
ChainID: new(big.Int).SetUint64(intent.L1ChainID),
Client: l1Client,
Signer: signer,
From: deployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
type ApplyPipelineOpts struct {
L1RPCUrl string
DeployerPrivateKey *ecdsa.PrivateKey
Intent *state.Intent
State *state.State
Logger log.Logger
StateWriter pipeline.StateWriter
}
startingNonce, err = l1Client.NonceAt(ctx, deployer, nil)
if err != nil {
return fmt.Errorf("failed to get starting nonce: %w", err)
}
} else {
deployer = common.Address{0x01}
bcaster = broadcaster.NoopBroadcaster()
}
func ApplyPipeline(
ctx context.Context,
opts ApplyPipelineOpts,
) error {
intent := opts.Intent
st := opts.State
progressor := func(curr, total int64) {
cfg.Logger.Info("artifacts download progress", "current", curr, "total", total)
opts.Logger.Info("artifacts download progress", "current", curr, "total", total)
}
l1ArtifactsFS, cleanupL1, err := artifacts.Download(ctx, intent.L1ContractsLocator, progressor)
......@@ -156,7 +153,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
}
defer func() {
if err := cleanupL1(); err != nil {
cfg.Logger.Warn("failed to clean up L1 artifacts", "err", err)
opts.Logger.Warn("failed to clean up L1 artifacts", "err", err)
}
}()
......@@ -166,7 +163,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
}
defer func() {
if err := cleanupL2(); err != nil {
cfg.Logger.Warn("failed to clean up L2 artifacts", "err", err)
opts.Logger.Warn("failed to clean up L2 artifacts", "err", err)
}
}()
......@@ -175,52 +172,101 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
L2: l2ArtifactsFS,
}
l1Host, err := env.DefaultScriptHost(bcaster, cfg.Logger, deployer, bundle.L1, startingNonce)
var deployer common.Address
var bcaster broadcaster.Broadcaster
var l1Client *ethclient.Client
var l1Host *script.Host
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
l1RPC, err := rpc.Dial(opts.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
l1Client = ethclient.NewClient(l1RPC)
chainID, err := l1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to get chain ID: %w", err)
}
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(opts.DeployerPrivateKey, chainID))
deployer = crypto.PubkeyToAddress(opts.DeployerPrivateKey.PublicKey)
bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: opts.Logger,
ChainID: new(big.Int).SetUint64(intent.L1ChainID),
Client: l1Client,
Signer: signer,
From: deployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
l1Host, err = env.DefaultScriptHost(
bcaster,
opts.Logger,
deployer,
bundle.L1,
script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) {
src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, l1RPC, *cfg.BlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to create RPC fork source: %w", err)
}
return forking.Cache(src), nil
}),
)
if err != nil {
return fmt.Errorf("failed to create L1 script host: %w", err)
}
env := &pipeline.Env{
StateWriter: pipeline.WorkdirStateWriter(cfg.Workdir),
latest, err := l1Client.HeaderByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to get latest block: %w", err)
}
if _, err := l1Host.CreateSelectFork(
script.ForkWithURLOrAlias("main"),
script.ForkWithBlockNumberU256(latest.Number),
); err != nil {
return fmt.Errorf("failed to select fork: %w", err)
}
} else {
deployer = common.Address{0x01}
bcaster = broadcaster.NoopBroadcaster()
l1Host, err = env.DefaultScriptHost(
bcaster,
opts.Logger,
deployer,
bundle.L1,
)
if err != nil {
return fmt.Errorf("failed to create L1 script host: %w", err)
}
}
pEnv := &pipeline.Env{
StateWriter: opts.StateWriter,
L1ScriptHost: l1Host,
L1Client: l1Client,
Logger: cfg.Logger,
Logger: opts.Logger,
Broadcaster: bcaster,
Deployer: deployer,
}
if err := ApplyPipeline(ctx, env, bundle, intent, st); err != nil {
return err
}
return nil
}
type pipelineStage struct {
name string
apply func() error
}
func ApplyPipeline(
ctx context.Context,
env *pipeline.Env,
bundle pipeline.ArtifactsBundle,
intent *state.Intent,
st *state.State,
) error {
pline := []pipelineStage{
{"init", func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.InitLiveStrategy(ctx, env, intent, st)
return pipeline.InitLiveStrategy(ctx, pEnv, intent, st)
} else {
return pipeline.InitGenesisStrategy(env, intent, st)
return pipeline.InitGenesisStrategy(pEnv, intent, st)
}
}},
{"deploy-superchain", func() error {
return pipeline.DeploySuperchain(env, intent, st)
return pipeline.DeploySuperchain(pEnv, intent, st)
}},
{"deploy-implementations", func() error {
return pipeline.DeployImplementations(env, intent, st)
return pipeline.DeployImplementations(pEnv, intent, st)
}},
}
......@@ -230,21 +276,17 @@ func ApplyPipeline(
pline = append(pline, pipelineStage{
fmt.Sprintf("deploy-opchain-%s", chainID.Hex()),
func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.DeployOPChainLiveStrategy(ctx, env, bundle, intent, st, chainID)
} else {
return pipeline.DeployOPChainGenesisStrategy(env, intent, st, chainID)
}
return pipeline.DeployOPChain(pEnv, intent, st, chainID)
},
}, pipelineStage{
fmt.Sprintf("deploy-alt-da-%s", chainID.Hex()),
func() error {
return pipeline.DeployAltDA(env, intent, st, chainID)
return pipeline.DeployAltDA(pEnv, intent, st, chainID)
},
}, pipelineStage{
fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()),
func() error {
return pipeline.GenerateL2Genesis(env, intent, bundle, st, chainID)
return pipeline.GenerateL2Genesis(pEnv, intent, bundle, st, chainID)
},
})
}
......@@ -257,9 +299,9 @@ func ApplyPipeline(
fmt.Sprintf("set-start-block-%s", chainID.Hex()),
func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.SetStartBlockLiveStrategy(ctx, env, st, chainID)
return pipeline.SetStartBlockLiveStrategy(ctx, pEnv, st, chainID)
} else {
return pipeline.SetStartBlockGenesisStrategy(env, st, chainID)
return pipeline.SetStartBlockGenesisStrategy(pEnv, st, chainID)
}
},
})
......@@ -271,23 +313,27 @@ func ApplyPipeline(
if err := stage.apply(); err != nil {
return fmt.Errorf("error in pipeline stage apply: %w", err)
}
dump, err := env.L1ScriptHost.StateDump()
if intent.DeploymentStrategy == state.DeploymentStrategyGenesis {
dump, err := pEnv.L1ScriptHost.StateDump()
if err != nil {
return fmt.Errorf("failed to dump state: %w", err)
}
st.L1StateDump = &state.GzipData[foundry.ForgeAllocs]{
Data: dump,
}
if _, err := env.Broadcaster.Broadcast(ctx); err != nil {
}
if _, err := pEnv.Broadcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast stage %s: %w", stage.name, err)
}
if err := env.StateWriter.WriteState(st); err != nil {
if err := pEnv.StateWriter.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
}
st.AppliedIntent = intent
if err := env.StateWriter.WriteState(st); err != nil {
if err := pEnv.StateWriter.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
......
......@@ -159,11 +159,11 @@ func DelayedWETH(ctx context.Context, cfg DelayedWETHConfig) error {
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
host.SetNonce(chainDeployer, nonce)
var release string
if cfg.ArtifactsLocator.IsTag() {
......
......@@ -162,11 +162,11 @@ func DisputeGame(ctx context.Context, cfg DisputeGameConfig) error {
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
host.SetNonce(chainDeployer, nonce)
var release string
if cfg.ArtifactsLocator.IsTag() {
......
......@@ -157,11 +157,11 @@ func MIPS(ctx context.Context, cfg MIPSConfig) error {
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
host.SetNonce(chainDeployer, nonce)
var release string
if cfg.ArtifactsLocator.IsTag() {
......
......@@ -193,11 +193,11 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
host.SetNonce(chainDeployer, nonce)
var release string
if cfg.ArtifactsLocator.IsTag() {
......
......@@ -72,7 +72,6 @@ func L2SemversCLI(cliCtx *cli.Context) error {
l,
common.Address{19: 0x01},
artifactsFS,
0,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
......
......@@ -3,6 +3,7 @@ package integration_test
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log/slog"
......@@ -17,19 +18,13 @@ import (
"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"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil"
"github.com/ethereum-optimism/optimism/op-service/testutils/anvil"
"github.com/ethereum/go-ethereum/crypto"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
......@@ -37,7 +32,6 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils/kurtosisutil"
......@@ -106,36 +100,25 @@ func TestEndToEndApply(t *testing.T) {
require.NoError(t, err)
pk, err := dk.Secret(depKey)
require.NoError(t, err)
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(pk, l1ChainID))
l2ChainID1 := uint256.NewInt(1)
l2ChainID2 := uint256.NewInt(2)
deployerAddr, err := dk.Address(depKey)
require.NoError(t, err)
loc, _ := testutil.LocalArtifacts(t)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: log.NewLogger(log.DiscardHandler()),
ChainID: l1ChainID,
Client: l1Client,
Signer: signer,
From: deployerAddr,
})
require.NoError(t, err)
env, bundle, _ := createEnv(t, lgr, l1Client, bcaster, deployerAddr)
intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc)
cg := ethClientCodeGetter(ctx, l1Client)
t.Run("initial chain", func(t *testing.T) {
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
deployer.ApplyPipelineOpts{
L1RPCUrl: rpcURL,
DeployerPrivateKey: pk,
Intent: intent,
State: st,
Logger: lgr,
StateWriter: pipeline.NoopStateWriter(),
},
))
validateSuperchainDeployment(t, st, cg)
......@@ -145,15 +128,18 @@ 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, lgr, l1Client, bcaster, deployerAddr)
intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainID, l2ChainID2))
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
deployer.ApplyPipelineOpts{
L1RPCUrl: rpcURL,
DeployerPrivateKey: pk,
Intent: intent,
State: st,
Logger: lgr,
StateWriter: pipeline.NoopStateWriter(),
},
))
validateOPChainDeployment(t, cg, st, intent)
......@@ -191,24 +177,11 @@ func TestApplyExistingOPCM(t *testing.T) {
dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic)
require.NoError(t, err)
// index 0 from Anvil's test set
priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
pk, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
require.NoError(t, err)
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(priv, l1ChainID))
deployerAddr := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
l2ChainID := uint256.NewInt(1)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: l1ChainID,
Client: l1Client,
Signer: signer,
From: deployerAddr,
})
require.NoError(t, err)
env, bundle, _ := createEnv(t, lgr, l1Client, bcaster, deployerAddr)
intent, st := newIntent(
t,
l1ChainID,
......@@ -217,13 +190,20 @@ func TestApplyExistingOPCM(t *testing.T) {
artifacts.DefaultL1ContractsLocator,
artifacts.DefaultL2ContractsLocator,
)
// Define a new create2 salt to avoid contract address collisions
_, err = rand.Read(st.Create2Salt[:])
require.NoError(t, err)
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
deployer.ApplyPipelineOpts{
L1RPCUrl: runner.RPCUrl(),
DeployerPrivateKey: pk,
Intent: intent,
State: st,
Logger: lgr,
StateWriter: pipeline.NoopStateWriter(),
},
))
validateOPChainDeployment(t, ethClientCodeGetter(ctx, l1Client), st, intent)
......@@ -236,18 +216,12 @@ func TestL2BlockTimeOverride(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
env, bundle, intent, st := setupGenesisChain(t)
opts, intent, st := setupGenesisChain(t)
intent.GlobalDeployOverrides = map[string]interface{}{
"l2BlockTime": float64(3),
}
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
))
require.NoError(t, deployer.ApplyPipeline(ctx, opts))
cfg, err := state.CombineDeployConfig(intent, intent.Chains[0], st, st.Chains[0])
require.NoError(t, err)
......@@ -260,16 +234,9 @@ func TestApplyGenesisStrategy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
env, bundle, intent, st := setupGenesisChain(t)
intent.DeploymentStrategy = state.DeploymentStrategyGenesis
opts, intent, st := setupGenesisChain(t)
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
))
require.NoError(t, deployer.ApplyPipeline(ctx, opts))
cg := stateDumpCodeGetter(st)
validateSuperchainDeployment(t, st, cg)
......@@ -287,7 +254,7 @@ func TestProofParamOverrides(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
env, bundle, intent, st := setupGenesisChain(t)
opts, intent, st := setupGenesisChain(t)
intent.GlobalDeployOverrides = map[string]any{
"withdrawalDelaySeconds": standard.WithdrawalDelaySeconds + 1,
"minProposalSizeBytes": standard.MinProposalSizeBytes + 1,
......@@ -304,13 +271,7 @@ func TestProofParamOverrides(t *testing.T) {
"dangerouslyAllowCustomDisputeParameters": true,
}
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
))
require.NoError(t, deployer.ApplyPipeline(ctx, opts))
allocs := st.L1StateDump.Data.Accounts
chainState := st.Chains[0]
......@@ -390,16 +351,10 @@ func TestInteropDeployment(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
env, bundle, intent, st := setupGenesisChain(t)
opts, intent, st := setupGenesisChain(t)
intent.UseInterop = true
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
))
require.NoError(t, deployer.ApplyPipeline(ctx, opts))
chainState := st.Chains[0]
depManagerSlot := common.HexToHash("0x1708e077affb93e89be2665fb0fb72581be66f84dc00d25fed755ae911905b1c")
......@@ -414,7 +369,7 @@ func TestAltDADeployment(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
env, bundle, intent, st := setupGenesisChain(t)
opts, intent, st := setupGenesisChain(t)
altDACfg := genesis.AltDADeployConfig{
UseAltDA: true,
DACommitmentType: altda.KeccakCommitmentString,
......@@ -425,13 +380,7 @@ func TestAltDADeployment(t *testing.T) {
}
intent.Chains[0].DangerousAltDAConfig = altDACfg
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
))
require.NoError(t, deployer.ApplyPipeline(ctx, opts))
chainState := st.Chains[0]
require.NotEmpty(t, chainState.DataAvailabilityChallengeProxyAddress)
......@@ -450,23 +399,9 @@ func TestAltDADeployment(t *testing.T) {
func TestInvalidL2Genesis(t *testing.T) {
op_e2e.InitParallel(t)
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)
require.NoError(t, err)
l2ChainID1 := uint256.NewInt(1)
deployerAddr, err := dk.Address(depKey)
require.NoError(t, err)
loc, _ := testutil.LocalArtifacts(t)
// these tests were generated by grepping all usages of the deploy
// config in L2Genesis.s.sol.
tests := []struct {
......@@ -512,26 +447,18 @@ func TestInvalidL2Genesis(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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))
opts, intent, _ := setupGenesisChain(t)
intent.DeploymentStrategy = state.DeploymentStrategyGenesis
intent.GlobalDeployOverrides = tt.overrides
err := deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
)
err := deployer.ApplyPipeline(ctx, opts)
require.Error(t, err)
require.ErrorContains(t, err, "failed to combine L2 init config")
})
}
}
func setupGenesisChain(t *testing.T) (*pipeline.Env, pipeline.ArtifactsBundle, *state.Intent, *state.State) {
func setupGenesisChain(t *testing.T) (deployer.ApplyPipelineOpts, *state.Intent, *state.State) {
lgr := testlog.Logger(t, slog.LevelDebug)
depKey := new(deployerKey)
......@@ -541,51 +468,24 @@ func setupGenesisChain(t *testing.T) (*pipeline.Env, pipeline.ArtifactsBundle, *
l2ChainID1 := uint256.NewInt(1)
deployerAddr, err := dk.Address(depKey)
priv, err := dk.Secret(depKey)
require.NoError(t, err)
loc, _ := testutil.LocalArtifacts(t)
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 createEnv(
t *testing.T,
lgr log.Logger,
l1Client *ethclient.Client,
bcaster broadcaster.Broadcaster,
deployerAddr common.Address,
) (*pipeline.Env, pipeline.ArtifactsBundle, *script.Host) {
_, artifactsFS := testutil.LocalArtifacts(t)
host, err := env.DefaultScriptHost(
bcaster,
lgr,
deployerAddr,
artifactsFS,
0,
)
require.NoError(t, err)
env := &pipeline.Env{
StateWriter: pipeline.NoopStateWriter(),
L1ScriptHost: host,
L1Client: l1Client,
Broadcaster: bcaster,
Deployer: deployerAddr,
opts := deployer.ApplyPipelineOpts{
DeployerPrivateKey: priv,
Intent: intent,
State: st,
Logger: lgr,
StateWriter: pipeline.NoopStateWriter(),
}
bundle := pipeline.ArtifactsBundle{
L1: artifactsFS,
L2: artifactsFS,
}
return env, bundle, host
return opts, intent, st
}
func addrFor(t *testing.T, dk *devkeys.MnemonicDevKeys, key devkeys.Key) common.Address {
......
......@@ -21,7 +21,6 @@ func TestDeployAltDA(t *testing.T) {
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
0,
)
require.NoError(t, err)
......
......@@ -22,7 +22,6 @@ func TestDeployDelayedWETH(t *testing.T) {
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
0,
)
require.NoError(t, err)
......
[
{
"type": "function",
"name": "decodeOutput",
"inputs": [],
"outputs": [
{
"name": "output",
"indexed": false,
"type": "tuple",
"components": [
{
"name": "opChainProxyAdmin",
"type": "address"
},
{
"name": "addressManager",
"type": "address"
},
{
"name": "l1ERC721BridgeProxy",
"type": "address"
},
{
"name": "systemConfigProxy",
"type": "address"
},
{
"name": "optimismMintableERC20FactoryProxy",
"type": "address"
},
{
"name": "l1StandardBridgeProxy",
"type": "address"
},
{
"name": "l1CrossDomainMessengerProxy",
"type": "address"
},
{
"name": "optimismPortalProxy",
"type": "address"
},
{
"name": "disputeGameFactoryProxy",
"type": "address"
},
{
"name": "anchorStateRegistryProxy",
"type": "address"
},
{
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"name": "faultDisputeGame",
"type": "address",
"internalType": "contract FaultDisputeGame"
},
{
"name": "permissionedDisputeGame",
"type": "address"
},
{
"name": "delayedWETHPermissionedGameProxy",
"type": "address"
},
{
"name": "delayedWETHPermissionlessGameProxy",
"type": "address"
}
]
}
]
}
]
\ No newline at end of file
......@@ -21,7 +21,6 @@ func TestDeployDisputeGame(t *testing.T) {
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
0,
)
require.NoError(t, err)
......
......@@ -20,7 +20,6 @@ func TestDeployMIPS(t *testing.T) {
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
0,
)
require.NoError(t, err)
......
package opcm
import (
"context"
_ "embed"
"fmt"
"math/big"
"strings"
_ "embed"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/holiman/uint256"
)
// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0,
......@@ -25,48 +15,6 @@ var PermissionedGameStartingAnchorRoots = []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
// opcmRolesBase is an internal struct used to pass the roles to OPCM. See opcmDeployInputV160 for more info.
type opcmRolesBase struct {
OpChainProxyAdminOwner common.Address
SystemConfigOwner common.Address
Batcher common.Address
UnsafeBlockSigner common.Address
Proposer common.Address
Challenger common.Address
}
type opcmDeployInputBase struct {
BasefeeScalar uint32
BlobBasefeeScalar uint32
L2ChainId *big.Int
StartingAnchorRoots []byte
SaltMixer string
GasLimit uint64
DisputeGameType uint32
DisputeAbsolutePrestate common.Hash
DisputeMaxGameDepth *big.Int
DisputeSplitDepth *big.Int
DisputeClockExtension uint64
DisputeMaxClockDuration uint64
}
// opcmDeployInputV160 is the input struct for the deploy method of the OPStackManager contract. We
// define a separate struct here to match what the OPSM contract expects.
type opcmDeployInputV160 struct {
opcmDeployInputBase
Roles opcmRolesBase
}
type opcmRolesIsthmus struct {
opcmRolesBase
FeeAdmin common.Address
}
type opcmDeployInputIsthmus struct {
opcmDeployInputBase
Roles opcmRolesIsthmus
}
type DeployOPChainInputV160 struct {
OpChainProxyAdminOwner common.Address
SystemConfigOwner common.Address
......@@ -99,49 +47,11 @@ func (input *DeployOPChainInputV160) StartingAnchorRoots() []byte {
return PermissionedGameStartingAnchorRoots
}
func DeployOPChainInputV160DeployCalldata(input DeployOPChainInputV160) any {
return opcmDeployInputV160{
Roles: opcmRolesBase{
OpChainProxyAdminOwner: input.OpChainProxyAdminOwner,
SystemConfigOwner: input.SystemConfigOwner,
Batcher: input.Batcher,
UnsafeBlockSigner: input.UnsafeBlockSigner,
Proposer: input.Proposer,
Challenger: input.Challenger,
},
opcmDeployInputBase: opcmDeployInputBase{
BasefeeScalar: input.BasefeeScalar,
BlobBasefeeScalar: input.BlobBaseFeeScalar,
L2ChainId: input.L2ChainId,
StartingAnchorRoots: input.StartingAnchorRoots(),
SaltMixer: input.SaltMixer,
GasLimit: input.GasLimit,
DisputeGameType: input.DisputeGameType,
DisputeAbsolutePrestate: input.DisputeAbsolutePrestate,
DisputeMaxGameDepth: new(big.Int).SetUint64(input.DisputeMaxGameDepth),
DisputeSplitDepth: new(big.Int).SetUint64(input.DisputeSplitDepth),
DisputeClockExtension: input.DisputeClockExtension,
DisputeMaxClockDuration: input.DisputeMaxClockDuration,
},
}
}
type DeployOPChainInputIsthmus struct {
DeployOPChainInputV160
SystemConfigFeeAdmin common.Address
}
func DeployOPChainInputIsthmusDeployCalldata(input DeployOPChainInputIsthmus) any {
v160Data := DeployOPChainInputV160DeployCalldata(input.DeployOPChainInputV160).(opcmDeployInputV160)
return opcmDeployInputIsthmus{
Roles: opcmRolesIsthmus{
opcmRolesBase: v160Data.Roles,
FeeAdmin: input.SystemConfigFeeAdmin,
},
opcmDeployInputBase: v160Data.opcmDeployInputBase,
}
}
type DeployOPChainOutput struct {
OpChainProxyAdmin common.Address
AddressManager common.Address
......@@ -210,116 +120,58 @@ func deployOPChain[T any](host *script.Host, input T) (DeployOPChainOutput, erro
return dco, nil
}
// decodeOutputABIJSONV160 defines an ABI for a fake method called "decodeOutput" that returns the
// DeployOutput struct. This allows the code in the deployer to decode directly into a struct
// using Geth's ABI library.
//
//go:embed deployOutput_v160.json
var decodeOutputABIJSONV160 string
var decodeOutputABIV160 abi.ABI
func DeployOPChainRawV160(
ctx context.Context,
l1 *ethclient.Client,
bcast broadcaster.Broadcaster,
deployer common.Address,
artifacts foundry.StatDirFs,
input DeployOPChainInputV160,
) (DeployOPChainOutput, error) {
return deployOPChainRaw(ctx, l1, bcast, deployer, artifacts, input.OpcmProxy, DeployOPChainInputV160DeployCalldata(input))
type ReadImplementationAddressesInput struct {
DeployOPChainOutput
OpcmProxy common.Address
Release string
}
func DeployOPChainRawIsthmus(
ctx context.Context,
l1 *ethclient.Client,
bcast broadcaster.Broadcaster,
deployer common.Address,
artifacts foundry.StatDirFs,
input DeployOPChainInputIsthmus,
) (DeployOPChainOutput, error) {
return deployOPChainRaw(ctx, l1, bcast, deployer, artifacts, input.OpcmProxy, DeployOPChainInputIsthmusDeployCalldata(input))
type ReadImplementationAddressesOutput struct {
DelayedWETH common.Address
OptimismPortal common.Address
SystemConfig common.Address
L1CrossDomainMessenger common.Address
L1ERC721Bridge common.Address
L1StandardBridge common.Address
OptimismMintableERC20Factory common.Address
DisputeGameFactory common.Address
MipsSingleton common.Address
PreimageOracleSingleton common.Address
}
// DeployOPChainRaw deploys an OP Chain using a raw call to a pre-deployed OPSM contract.
func deployOPChainRaw(
ctx context.Context,
l1 *ethclient.Client,
bcast broadcaster.Broadcaster,
deployer common.Address,
artifacts foundry.StatDirFs,
opcmProxyAddress common.Address,
input any,
) (DeployOPChainOutput, error) {
var out DeployOPChainOutput
type ReadImplementationAddressesScript struct {
Run func(input, output common.Address) error
}
artifactsFS := &foundry.ArtifactsFS{FS: artifacts}
opcmArtifacts, err := artifactsFS.ReadArtifact("OPContractsManager.sol", "OPContractsManager")
if err != nil {
return out, fmt.Errorf("failed to read OPStackManager artifact: %w", err)
}
func ReadImplementationAddresses(host *script.Host, input ReadImplementationAddressesInput) (ReadImplementationAddressesOutput, error) {
var rio ReadImplementationAddressesOutput
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()
opcmABI := opcmArtifacts.ABI
calldata, err := opcmABI.Pack("deploy", input)
cleanupInput, err := script.WithPrecompileAtAddress[*ReadImplementationAddressesInput](host, inputAddr, &input)
if err != nil {
return out, fmt.Errorf("failed to pack deploy input: %w", err)
return rio, fmt.Errorf("failed to insert ReadImplementationAddressesInput precompile: %w", err)
}
defer cleanupInput()
host.Label(inputAddr, "ReadImplementationAddressesInput")
nonce, err := l1.NonceAt(ctx, deployer, nil)
cleanupOutput, err := script.WithPrecompileAtAddress[*ReadImplementationAddressesOutput](host, outputAddr, &rio,
script.WithFieldSetter[*ReadImplementationAddressesOutput])
if err != nil {
return out, fmt.Errorf("failed to read nonce: %w", err)
return rio, fmt.Errorf("failed to insert ReadImplementationAddressesOutput precompile: %w", err)
}
defer cleanupOutput()
host.Label(outputAddr, "ReadImplementationAddressesOutput")
bcast.Hook(script.Broadcast{
From: deployer,
To: opcmProxyAddress,
Input: calldata,
Value: (*hexutil.U256)(uint256.NewInt(0)),
// use hardcoded 19MM gas for now since this is roughly what we've seen this deployment cost.
GasUsed: 19_000_000,
Type: script.BroadcastCall,
Nonce: nonce,
})
results, err := bcast.Broadcast(ctx)
deployScript, cleanupDeploy, err := script.WithScript[ReadImplementationAddressesScript](host, "ReadImplementationAddresses.s.sol", "ReadImplementationAddresses")
if err != nil {
return out, fmt.Errorf("failed to broadcast OP chain deployment: %w", err)
}
deployedEvent := opcmABI.Events["Deployed"]
res := results[0]
for _, log := range res.Receipt.Logs {
if log.Topics[0] != deployedEvent.ID {
continue
}
type EventData struct {
DeployOutput []byte
}
var data EventData
if err := opcmABI.UnpackIntoInterface(&data, "Deployed", log.Data); err != nil {
return out, fmt.Errorf("failed to unpack Deployed event: %w", err)
}
type OutputData struct {
Output DeployOPChainOutput
}
var outData OutputData
if err := decodeOutputABIV160.UnpackIntoInterface(&outData, "decodeOutput", data.DeployOutput); err != nil {
return out, fmt.Errorf("failed to unpack DeployOutput: %w", err)
return rio, fmt.Errorf("failed to load ReadImplementationAddresses script: %w", err)
}
defer cleanupDeploy()
return outData.Output, nil
if err := deployScript.Run(inputAddr, outputAddr); err != nil {
return rio, fmt.Errorf("failed to run ReadImplementationAddresses script: %w", err)
}
return out, fmt.Errorf("failed to find Deployed event")
}
func init() {
var err error
decodeOutputABIV160, err = abi.JSON(strings.NewReader(decodeOutputABIJSONV160))
if err != nil {
panic(fmt.Sprintf("failed to parse decodeOutput ABI: %v", err))
}
return rio, nil
}
......@@ -23,7 +23,6 @@ type Env struct {
L1ScriptHost *script.Host
L1Client *ethclient.Client
Broadcaster broadcaster.Broadcaster
Host *script.Host
Deployer common.Address
Logger log.Logger
}
......
......@@ -58,8 +58,6 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro
return fmt.Errorf("error merging proof params from overrides: %w", err)
}
env.L1ScriptHost.ImportState(st.L1StateDump.Data)
dio, err := opcm.DeployImplementations(
env.L1ScriptHost,
opcm.DeployImplementationsInput{
......
......@@ -3,7 +3,7 @@ package pipeline
import (
"fmt"
env2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
"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"
......@@ -13,8 +13,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
func GenerateL2Genesis(env *Env, intent *state.Intent, bundle ArtifactsBundle, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "generate-l2-genesis")
func GenerateL2Genesis(pEnv *Env, intent *state.Intent, bundle ArtifactsBundle, st *state.State, chainID common.Hash) error {
lgr := pEnv.Logger.New("stage", "generate-l2-genesis")
thisIntent, err := intent.Chain(chainID)
if err != nil {
......@@ -38,12 +38,11 @@ func GenerateL2Genesis(env *Env, intent *state.Intent, bundle ArtifactsBundle, s
return fmt.Errorf("failed to combine L2 init config: %w", err)
}
host, err := env2.DefaultScriptHost(
host, err := env.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
env.Logger,
env.Deployer,
pEnv.Logger,
pEnv.Deployer,
bundle.L2,
0,
)
if err != nil {
return fmt.Errorf("failed to create L2 script host: %w", err)
......@@ -60,7 +59,7 @@ func GenerateL2Genesis(env *Env, intent *state.Intent, bundle ArtifactsBundle, s
return fmt.Errorf("failed to call L2Genesis script: %w", err)
}
host.Wipe(env.Deployer)
host.Wipe(pEnv.Deployer)
dump, err := host.StateDump()
if err != nil {
......
package pipeline
import (
"context"
"encoding/hex"
"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"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func DeployOPChainLiveStrategy(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain", "strategy", "live")
func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain")
if !shouldDeployOPChain(st, chainID) {
lgr.Info("opchain deployment not needed")
......@@ -31,6 +24,7 @@ func DeployOPChainLiveStrategy(ctx context.Context, env *Env, bundle ArtifactsBu
return fmt.Errorf("failed to get chain intent: %w", err)
}
var opcmAddr common.Address
var deployFunc func() (opcm.DeployOPChainOutput, error)
switch intent.L1ContractsLocator.Tag {
case standard.ContractsV160Tag, standard.ContractsV170Beta1L2Tag:
......@@ -40,13 +34,8 @@ func DeployOPChainLiveStrategy(ctx context.Context, env *Env, bundle ArtifactsBu
return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err)
}
return opcm.DeployOPChainRawV160(ctx,
env.L1Client,
env.Broadcaster,
env.Deployer,
bundle.L1,
input,
)
opcmAddr = input.OpcmProxy
return opcm.DeployOPChainV160(env.L1ScriptHost, input)
}
default:
deployFunc = func() (opcm.DeployOPChainOutput, error) {
......@@ -55,165 +44,47 @@ func DeployOPChainLiveStrategy(ctx context.Context, env *Env, bundle ArtifactsBu
return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err)
}
return opcm.DeployOPChainRawIsthmus(ctx,
env.L1Client,
env.Broadcaster,
env.Deployer,
bundle.L1,
input,
)
opcmAddr = input.OpcmProxy
return opcm.DeployOPChainIsthmus(env.L1ScriptHost, input)
}
}
var dco opcm.DeployOPChainOutput
lgr.Info("deploying OP chain using existing OPCM", "id", chainID.Hex(), "opcmAddress", st.ImplementationsDeployment.OpcmProxyAddress.Hex())
lgr.Info("deploying OP chain using local allocs", "id", chainID.Hex())
dco, err = deployFunc()
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
}
st.Chains = append(st.Chains, makeChainState(chainID, dco))
opcmProxyAddress := st.ImplementationsDeployment.OpcmProxyAddress
err = conditionallySetImplementationAddresses(ctx, env.L1Client, intent, st, dco, opcmProxyAddress)
if err != nil {
return fmt.Errorf("failed to set implementation addresses: %w", err)
}
return nil
}
// Only try to set the implementation addresses if we reused existing implementations from a release tag.
// The reason why these addresses could be empty is because only DeployOPChain.s.sol is invoked as part of the pipeline.
func conditionallySetImplementationAddresses(ctx context.Context, client *ethclient.Client, intent *state.Intent, st *state.State, dco opcm.DeployOPChainOutput, opcmProxyAddress common.Address) error {
if !intent.L1ContractsLocator.IsTag() {
return nil
}
block, err := client.BlockByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to get latest block by number: %w", err)
}
currentBlockHash := block.Hash()
errCh := make(chan error, 8)
setImplementationAddressTasks := []func(){
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.DelayedWETHPermissionedGameProxy, currentBlockHash, &st.ImplementationsDeployment.DelayedWETHImplAddress)
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.OptimismPortalProxy, currentBlockHash, &st.ImplementationsDeployment.OptimismPortalImplAddress)
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.SystemConfigProxy, currentBlockHash, &st.ImplementationsDeployment.SystemConfigImplAddress)
},
func() {
setRDPImplementationAddress(ctx, client, errCh, dco.AddressManager, &st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress, "OVM_L1CrossDomainMessenger")
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.L1ERC721BridgeProxy, currentBlockHash, &st.ImplementationsDeployment.L1ERC721BridgeImplAddress)
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.L1StandardBridgeProxy, currentBlockHash, &st.ImplementationsDeployment.L1StandardBridgeImplAddress)
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.OptimismMintableERC20FactoryProxy, currentBlockHash, &st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress)
},
func() {
setEIP1967ImplementationAddress(ctx, client, errCh, dco.DisputeGameFactoryProxy, currentBlockHash, &st.ImplementationsDeployment.DisputeGameFactoryImplAddress)
},
func() {
setMipsSingletonAddress(ctx, client, intent.L1ContractsLocator, errCh, opcmProxyAddress, &st.ImplementationsDeployment.MipsSingletonAddress)
setPreimageOracleAddress(ctx, client, errCh, st.ImplementationsDeployment.MipsSingletonAddress, &st.ImplementationsDeployment.PreimageOracleSingletonAddress)
},
}
for _, task := range setImplementationAddressTasks {
go task()
}
var lastTaskErr error
for i := 0; i < len(setImplementationAddressTasks); i++ {
taskErr := <-errCh
if taskErr != nil {
lastTaskErr = taskErr
}
}
if lastTaskErr != nil {
return fmt.Errorf("failed to set implementation addresses: %w", lastTaskErr)
}
return nil
}
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
}
opcmContract := opcm.NewContract(opcmProxyAddress, client)
mipsSingletonAddress, err := opcmContract.GetOPCMImplementationAddress(ctx, l1ArtifactsLocator.Tag, "MIPS")
if err == nil {
*singletonAddress = mipsSingletonAddress
}
errCh <- err
}
func setPreimageOracleAddress(ctx context.Context, client *ethclient.Client, errCh chan error, mipsSingletonAddress common.Address, preimageOracleAddress *common.Address) {
opcmContract := opcm.NewContract(mipsSingletonAddress, client)
preimageOracle, err := opcmContract.GenericAddressGetter(ctx, "oracle")
if err == nil {
*preimageOracleAddress = preimageOracle
}
errCh <- err
}
func DeployOPChainGenesisStrategy(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain", "strategy", "genesis")
if !shouldDeployOPChain(st, chainID) {
lgr.Info("opchain deployment not needed")
return nil
}
thisIntent, err := intent.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain intent: %w", err)
}
var deployFunc func() (opcm.DeployOPChainOutput, error)
switch intent.L1ContractsLocator.Tag {
case standard.ContractsV160Tag, standard.ContractsV170Beta1L2Tag:
deployFunc = func() (opcm.DeployOPChainOutput, error) {
input, err := makeDCIV160(intent, thisIntent, chainID, st)
if err != nil {
return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err)
}
return opcm.DeployOPChainV160(env.L1ScriptHost, input)
}
default:
deployFunc = func() (opcm.DeployOPChainOutput, error) {
input, err := makeDCIIsthmus(intent, thisIntent, chainID, st)
if err != nil {
return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err)
var release string
if intent.L1ContractsLocator.IsTag() {
release = intent.L1ContractsLocator.Tag
} else {
release = "dev"
}
return opcm.DeployOPChainIsthmus(env.L1ScriptHost, input)
}
readInput := opcm.ReadImplementationAddressesInput{
DeployOPChainOutput: dco,
OpcmProxy: opcmAddr,
Release: release,
}
env.L1ScriptHost.ImportState(st.L1StateDump.Data)
var dco opcm.DeployOPChainOutput
lgr.Info("deploying OP chain using local allocs", "id", chainID.Hex())
dco, err = deployFunc()
impls, err := opcm.ReadImplementationAddresses(env.L1ScriptHost, readInput)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
return fmt.Errorf("failed to read implementation addresses: %w", err)
}
st.Chains = append(st.Chains, makeChainState(chainID, dco))
st.ImplementationsDeployment.DelayedWETHImplAddress = impls.DelayedWETH
st.ImplementationsDeployment.OptimismPortalImplAddress = impls.OptimismPortal
st.ImplementationsDeployment.SystemConfigImplAddress = impls.SystemConfig
st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress = impls.L1CrossDomainMessenger
st.ImplementationsDeployment.L1ERC721BridgeImplAddress = impls.L1ERC721Bridge
st.ImplementationsDeployment.L1StandardBridgeImplAddress = impls.L1StandardBridge
st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress = impls.OptimismMintableERC20Factory
st.ImplementationsDeployment.DisputeGameFactoryImplAddress = impls.DisputeGameFactory
st.ImplementationsDeployment.MipsSingletonAddress = impls.MipsSingleton
st.ImplementationsDeployment.PreimageOracleSingletonAddress = impls.PreimageOracleSingleton
return nil
}
......@@ -301,33 +172,6 @@ func makeChainState(chainID common.Hash, dco opcm.DeployOPChainOutput) *state.Ch
}
}
func setRDPImplementationAddress(ctx context.Context, client *ethclient.Client, errCh chan error, addressManager common.Address, implAddress *common.Address, getNameArg string) {
if *implAddress != (common.Address{}) {
errCh <- nil
return
}
addressManagerContract := opcm.NewContract(addressManager, client)
address, err := addressManagerContract.GetAddressByNameViaAddressManager(ctx, getNameArg)
if err == nil {
*implAddress = address
}
errCh <- err
}
func setEIP1967ImplementationAddress(ctx context.Context, client *ethclient.Client, errCh chan error, proxy common.Address, currentBlockHash common.Hash, implAddress *common.Address) {
if *implAddress != (common.Address{}) {
errCh <- nil
return
}
storageValue, err := client.StorageAtHash(ctx, proxy, genesis.ImplementationSlot, currentBlockHash)
if err == nil {
*implAddress = common.HexToAddress(hex.EncodeToString(storageValue))
}
errCh <- err
}
func shouldDeployOPChain(st *state.State, chainID common.Hash) bool {
for _, chain := range st.Chains {
if chain.ID == chainID {
......
......@@ -172,7 +172,7 @@ func SystemOwnerAddrFor(chainID uint64) (common.Address, error) {
func ArtifactsURLForTag(tag string) (*url.URL, error) {
switch tag {
case "op-contracts/v1.6.0":
return url.Parse(standardArtifactsURL("ee07c78c3d8d4cd8f7a933c050f5afeebaa281b57b226cc6f092b19de2a8d61f"))
return url.Parse(standardArtifactsURL("3a27c6dc0cb61b36feaac26def98c64b4a48ec8f5c5ba6965e8ae3157606043c"))
case "op-contracts/v1.7.0-beta.1+l2-contracts":
return url.Parse(standardArtifactsURL("b0fb1f6f674519d637cff39a22187a5993d7f81a6d7b7be6507a0b50a5e38597"))
default:
......
......@@ -16,7 +16,7 @@ func DefaultScriptHost(
lgr log.Logger,
deployer common.Address,
artifacts foundry.StatDirFs,
startingNonce uint64,
additionalOpts ...script.HostOption,
) (*script.Host, error) {
scriptCtx := script.DefaultContext
scriptCtx.Sender = deployer
......@@ -26,16 +26,16 @@ func DefaultScriptHost(
&foundry.ArtifactsFS{FS: artifacts},
nil,
scriptCtx,
append([]script.HostOption{
script.WithBroadcastHook(bcaster.Hook),
script.WithIsolatedBroadcasts(),
script.WithCreate2Deployer(),
}, additionalOpts...)...,
)
if err := h.EnableCheats(); err != nil {
return nil, fmt.Errorf("failed to enable cheats: %w", err)
}
h.SetNonce(deployer, startingNonce)
return h, nil
}
......@@ -235,7 +235,7 @@ contract DeployOPChainOutput is BaseDeployIO {
IDelayedWETH internal _delayedWETHPermissionedGameProxy;
IDelayedWETH internal _delayedWETHPermissionlessGameProxy;
function set(bytes4 _sel, address _addr) public {
function set(bytes4 _sel, address _addr) public virtual {
require(_addr != address(0), "DeployOPChainOutput: cannot set zero address");
// forgefmt: disable-start
if (_sel == this.opChainProxyAdmin.selector) _opChainProxyAdmin = IProxyAdmin(_addr) ;
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol";
import { IProxy } from "src/universal/interfaces/IProxy.sol";
import { Script } from "forge-std/Script.sol";
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
import { DeployOPChainOutput } from "scripts/deploy/DeployOPChain.s.sol";
import { IMIPS } from "src/cannon/interfaces/IMIPS.sol";
import { OPContractsManager } from "src/L1/OPContractsManager.sol";
import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol";
import { IStaticL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol";
contract ReadImplementationAddressesInput is DeployOPChainOutput {
OPContractsManager internal _opcmProxy;
string internal _release;
function set(bytes4 _sel, address _addr) public override {
require(_addr != address(0), "ReadImplementationAddressesInput: cannot set zero address");
if (_sel == this.opcmProxy.selector) _opcmProxy = OPContractsManager(_addr);
else if (_sel == this.addressManager.selector) _addressManager = IAddressManager(_addr);
else super.set(_sel, _addr);
}
function set(bytes4 _sel, string memory _val) public {
if (_sel == this.release.selector) _release = _val;
else revert("ReadImplementationAddressesInput: unknown selector");
}
function opcmProxy() public view returns (OPContractsManager) {
DeployUtils.assertValidContractAddress(address(_opcmProxy));
return _opcmProxy;
}
function release() public view returns (string memory) {
require(bytes(_release).length != 0, "ReadImplementationAddressesInput: release not set");
return _release;
}
}
contract ReadImplementationAddressesOutput is BaseDeployIO {
address internal _delayedWETH;
address internal _optimismPortal;
address internal _systemConfig;
address internal _l1CrossDomainMessenger;
address internal _l1ERC721Bridge;
address internal _l1StandardBridge;
address internal _optimismMintableERC20Factory;
address internal _disputeGameFactory;
address internal _mipsSingleton;
address internal _preimageOracleSingleton;
function set(bytes4 _sel, address _addr) public {
require(_addr != address(0), "ReadImplementationAddressesOutput: cannot set zero address");
if (_sel == this.delayedWETH.selector) _delayedWETH = _addr;
else if (_sel == this.optimismPortal.selector) _optimismPortal = _addr;
else if (_sel == this.systemConfig.selector) _systemConfig = _addr;
else if (_sel == this.l1CrossDomainMessenger.selector) _l1CrossDomainMessenger = _addr;
else if (_sel == this.l1ERC721Bridge.selector) _l1ERC721Bridge = _addr;
else if (_sel == this.l1StandardBridge.selector) _l1StandardBridge = _addr;
else if (_sel == this.optimismMintableERC20Factory.selector) _optimismMintableERC20Factory = _addr;
else if (_sel == this.disputeGameFactory.selector) _disputeGameFactory = _addr;
else if (_sel == this.mipsSingleton.selector) _mipsSingleton = _addr;
else if (_sel == this.preimageOracleSingleton.selector) _preimageOracleSingleton = _addr;
else revert("ReadImplementationAddressesOutput: unknown selector");
}
function delayedWETH() public view returns (address) {
require(_delayedWETH != address(0), "ReadImplementationAddressesOutput: delayedWETH not set");
return _delayedWETH;
}
function optimismPortal() public view returns (address) {
require(_optimismPortal != address(0), "ReadImplementationAddressesOutput: optimismPortal not set");
return _optimismPortal;
}
function systemConfig() public view returns (address) {
require(_systemConfig != address(0), "ReadImplementationAddressesOutput: systemConfig not set");
return _systemConfig;
}
function l1CrossDomainMessenger() public view returns (address) {
require(
_l1CrossDomainMessenger != address(0), "ReadImplementationAddressesOutput: l1CrossDomainMessenger not set"
);
return _l1CrossDomainMessenger;
}
function l1ERC721Bridge() public view returns (address) {
require(_l1ERC721Bridge != address(0), "ReadImplementationAddressesOutput: l1ERC721Bridge not set");
return _l1ERC721Bridge;
}
function l1StandardBridge() public view returns (address) {
require(_l1StandardBridge != address(0), "ReadImplementationAddressesOutput: l1StandardBridge not set");
return _l1StandardBridge;
}
function optimismMintableERC20Factory() public view returns (address) {
require(
_optimismMintableERC20Factory != address(0),
"ReadImplementationAddressesOutput: optimismMintableERC20Factory not set"
);
return _optimismMintableERC20Factory;
}
function disputeGameFactory() public view returns (address) {
require(_disputeGameFactory != address(0), "ReadImplementationAddressesOutput: disputeGameFactory not set");
return _disputeGameFactory;
}
function mipsSingleton() public view returns (address) {
require(_mipsSingleton != address(0), "ReadImplementationAddressesOutput: mipsSingleton not set");
return _mipsSingleton;
}
function preimageOracleSingleton() public view returns (address) {
require(
_preimageOracleSingleton != address(0), "ReadImplementationAddressesOutput: preimageOracleSingleton not set"
);
return _preimageOracleSingleton;
}
}
contract ReadImplementationAddresses is Script {
function run(ReadImplementationAddressesInput _rii, ReadImplementationAddressesOutput _rio) public {
address[6] memory eip1967Proxies = [
address(_rii.delayedWETHPermissionedGameProxy()),
address(_rii.optimismPortalProxy()),
address(_rii.systemConfigProxy()),
address(_rii.l1ERC721BridgeProxy()),
address(_rii.optimismMintableERC20FactoryProxy()),
address(_rii.disputeGameFactoryProxy())
];
bytes4[6] memory sels = [
_rio.delayedWETH.selector,
_rio.optimismPortal.selector,
_rio.systemConfig.selector,
_rio.l1ERC721Bridge.selector,
_rio.optimismMintableERC20Factory.selector,
_rio.disputeGameFactory.selector
];
for (uint256 i = 0; i < eip1967Proxies.length; i++) {
IProxy proxy = IProxy(payable(eip1967Proxies[i]));
vm.prank(address(0));
_rio.set(sels[i], proxy.implementation());
}
vm.prank(address(0));
address l1SBImpl = IStaticL1ChugSplashProxy(address(_rii.l1StandardBridgeProxy())).getImplementation();
vm.prank(address(0));
_rio.set(_rio.l1StandardBridge.selector, l1SBImpl);
(address mipsLogic,) = _rii.opcmProxy().implementations(_rii.release(), "MIPS");
_rio.set(_rio.mipsSingleton.selector, mipsLogic);
IAddressManager am = _rii.addressManager();
_rio.set(_rio.l1CrossDomainMessenger.selector, am.getAddress("OVM_L1CrossDomainMessenger"));
address preimageOracle = address(IMIPS(mipsLogic).oracle());
_rio.set(_rio.preimageOracleSingleton.selector, preimageOracle);
}
}
......@@ -44,7 +44,11 @@ else
tar="tar"
fi
"$tar" -czf "$archive_name" artifacts forge-artifacts cache
rm -f COMMIT
commit=$(git rev-parse HEAD)
echo "$commit" > COMMIT
"$tar" -czf "$archive_name" artifacts forge-artifacts cache COMMIT
du -sh "$archive_name" | awk '{$1=$1};1' # trim leading whitespace
echoerr "> Done."
......@@ -53,3 +57,4 @@ gcloud storage cp "$archive_name" "gs://$DEPLOY_BUCKET/$archive_name"
echoerr "> Done."
rm "$archive_name"
rm COMMIT
\ No newline at end of file
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