Skip to content

Commit

Permalink
Port deployers, add end-to-end contract deployments
Browse files Browse the repository at this point in the history
This PR ports over the deployers in `interopgen` into `op-deployer`, and updates `op-deployer` to support end-to end contract deployments for both the Superchain and individual OP Chains.

This PR includes a couple of bugfixes for things I discovered along the way:

1. The script host is updated to bump the nonce of the address calling the CREATE2 deployer when broadcasting. This fixes a chain/simulation mismatch that blocked contracts from being deployed.
2. The DeployImplementations contract used a fixed CREATE2 salt, which caused problems on repeated deployments. I updated the contract to pull the nonce from the environment as we do elsewhere.

Builds on #11910.
  • Loading branch information
mslipper committed Sep 16, 2024
1 parent 044c8b3 commit 4f77209
Show file tree
Hide file tree
Showing 19 changed files with 621 additions and 235 deletions.
21 changes: 17 additions & 4 deletions op-chain-ops/deployer/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,32 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
return nil
}

type pipelineStage struct {
name string
stage pipeline.Stage
}

func ApplyPipeline(
ctx context.Context,
env *pipeline.Env,
intent *state.Intent,
st *state.State,
) error {
pline := []struct {
name string
stage pipeline.Stage
}{
pline := []pipelineStage{
{"init", pipeline.Init},
{"deploy-superchain", pipeline.DeploySuperchain},
{"deploy-implementations", pipeline.DeployImplementations},
}

for _, chain := range intent.Chains {
pline = append(pline, pipelineStage{
fmt.Sprintf("deploy-opchain-%s", chain.ID.Hex()),
func(ctx context.Context, env *pipeline.Env, intent *state.Intent, st *state.State) error {
return pipeline.DeployOPChain(ctx, env, intent, st, chain.ID)
},
})
}

for _, stage := range pline {
if err := stage.stage(ctx, env, intent, st); err != nil {
return fmt.Errorf("error in pipeline stage: %w", err)
Expand Down
94 changes: 84 additions & 10 deletions op-chain-ops/deployer/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"path"
"testing"

"github.com/holiman/uint256"

"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/devkeys"
Expand All @@ -27,6 +29,15 @@ participants:
cl_type: lighthouse
network_params:
prefunded_accounts: '{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "1000000ETH" } }'
additional_preloaded_contracts: '{
"0x4e59b44847b379578588920cA78FbF26c0B4956C": {
balance: "0ETH",
code: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3",
storage: {},
nonce: 0,
secretKey: "0x"
}
}'
network_id: "77799777"
seconds_per_slot: 3
`
Expand All @@ -42,7 +53,7 @@ func (d *deployerKey) String() string {
}

func TestEndToEndApply(t *testing.T) {
kurtosisutil.Test(t)
//kurtosisutil.Test(t)

lgr := testlog.Logger(t, slog.LevelInfo)

Expand Down Expand Up @@ -77,6 +88,8 @@ func TestEndToEndApply(t *testing.T) {
require.NoError(t, err)
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(pk, l1ChainID))

id := uint256.NewInt(1)

addrFor := func(key devkeys.Key) common.Address {
addr, err := dk.Address(key)
require.NoError(t, err)
Expand All @@ -99,6 +112,20 @@ func TestEndToEndApply(t *testing.T) {
UseFaultProofs: true,
FundDevAccounts: true,
ContractArtifactsURL: (*state.ArtifactsURL)(artifactsURL),
Chains: []state.ChainIntent{
{
ID: id.Bytes32(),
Roles: state.ChainRoles{
ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)),
SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l1ChainID)),
GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)),
UnsafeBlockSigner: addrFor(devkeys.SequencerP2PRole.Key(l1ChainID)),
Batcher: addrFor(devkeys.BatcherRole.Key(l1ChainID)),
Proposer: addrFor(devkeys.ProposerRole.Key(l1ChainID)),
Challenger: addrFor(devkeys.ChallengerRole.Key(l1ChainID)),
},
},
},
}
st := &state.State{
Version: 1,
Expand All @@ -111,16 +138,63 @@ func TestEndToEndApply(t *testing.T) {
st,
))

addrs := []common.Address{
st.SuperchainDeployment.ProxyAdminAddress,
st.SuperchainDeployment.SuperchainConfigProxyAddress,
st.SuperchainDeployment.SuperchainConfigImplAddress,
st.SuperchainDeployment.ProtocolVersionsProxyAddress,
st.SuperchainDeployment.ProtocolVersionsImplAddress,
addrs := []struct {
name string
addr common.Address
}{
{"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress},
{"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress},
{"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress},
{"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress},
{"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress},
{"Opsm", st.ImplementationsDeployment.OpsmAddress},
{"DelayedWETHImpl", st.ImplementationsDeployment.DelayedWETHImplAddress},
{"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImplAddress},
{"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress},
{"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress},
{"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImplAddress},
{"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress},
{"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1ERC721BridgeImplAddress},
{"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImplAddress},
{"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress},
{"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImplAddress},
}
for _, addr := range addrs {
code, err := l1Client.CodeAt(ctx, addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code)
t.Run(addr.name, func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr)
})
}

for _, chainState := range st.Chains {
chainAddrs := []struct {
name string
addr common.Address
}{
{"ProxyAdminAddress", chainState.ProxyAdminAddress},
{"AddressManagerAddress", chainState.AddressManagerAddress},
{"L1ERC721BridgeProxyAddress", chainState.L1ERC721BridgeProxyAddress},
{"SystemConfigProxyAddress", chainState.SystemConfigProxyAddress},
{"OptimismMintableERC20FactoryProxyAddress", chainState.OptimismMintableERC20FactoryProxyAddress},
{"L1StandardBridgeProxyAddress", chainState.L1StandardBridgeProxyAddress},
{"L1CrossDomainMessengerProxyAddress", chainState.L1CrossDomainMessengerProxyAddress},
{"OptimismPortalProxyAddress", chainState.OptimismPortalProxyAddress},
{"DisputeGameFactoryProxyAddress", chainState.DisputeGameFactoryProxyAddress},
{"DisputeGameFactoryImplAddress", chainState.DisputeGameFactoryImplAddress},
{"AnchorStateRegistryProxyAddress", chainState.AnchorStateRegistryProxyAddress},
{"AnchorStateRegistryImplAddress", chainState.AnchorStateRegistryImplAddress},
{"FaultDisputeGameAddress", chainState.FaultDisputeGameAddress},
{"PermissionedDisputeGameAddress", chainState.PermissionedDisputeGameAddress},
{"DelayedWETHPermissionedGameProxyAddress", chainState.DelayedWETHPermissionedGameProxyAddress},
{"DelayedWETHPermissionlessGameProxyAddress", chainState.DelayedWETHPermissionlessGameProxyAddress},
}
for _, addr := range chainAddrs {
t.Run(fmt.Sprintf("chain %s - %s", chainState.ID, addr.name), func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID)
})
}
}
}
20 changes: 20 additions & 0 deletions op-chain-ops/deployer/broadcaster/discard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package broadcaster

import (
"context"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)

type discardBroadcaster struct {
}

func DiscardBroadcaster() Broadcaster {
return &discardBroadcaster{}
}

func (d *discardBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, error) {
return nil, nil
}

func (d *discardBroadcaster) Hook(bcast script.Broadcast) {}
56 changes: 41 additions & 15 deletions op-chain-ops/deployer/broadcaster/keyed.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import (
)

const (
GasPadFactor = 1.5
GasPadFactor = 2.0
)

type KeyedBroadcaster struct {
lgr log.Logger
mgr txmgr.TxManager
bcasts []script.Broadcast
client *ethclient.Client
}

type KeyedBroadcasterOpts struct {
Expand Down Expand Up @@ -84,8 +85,9 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) {
}

return &KeyedBroadcaster{
lgr: cfg.Logger,
mgr: mgr,
lgr: cfg.Logger,
mgr: mgr,
client: cfg.Client,
}, nil
}

Expand All @@ -98,16 +100,21 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
futures := make([]<-chan txmgr.SendResponse, len(t.bcasts))
ids := make([]common.Hash, len(t.bcasts))

latestBlock, err := t.client.BlockByNumber(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed to get latest block: %w", err)
}

for i, bcast := range t.bcasts {
futures[i], ids[i] = t.broadcast(ctx, bcast)
futures[i], ids[i] = t.broadcast(ctx, bcast, latestBlock.GasLimit())
t.lgr.Info(
"transaction broadcasted",
"id", ids[i],
"nonce", bcast.Nonce,
)
}

var err *multierror.Error
var txErr *multierror.Error
var completed int
for i, fut := range futures {
bcastRes := <-fut
Expand All @@ -122,7 +129,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er

if bcastRes.Receipt.Status == 0 {
failErr := fmt.Errorf("transaction failed: %s", outRes.Receipt.TxHash.String())
err = multierror.Append(err, failErr)
txErr = multierror.Append(txErr, failErr)
outRes.Err = failErr
t.lgr.Error(
"transaction failed on chain",
Expand All @@ -144,7 +151,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
)
}
} else {
err = multierror.Append(err, bcastRes.Err)
txErr = multierror.Append(txErr, bcastRes.Err)
outRes.Err = bcastRes.Err
t.lgr.Error(
"transaction failed",
Expand All @@ -157,12 +164,13 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er

results = append(results, outRes)
}
return results, err.ErrorOrNil()
return results, txErr.ErrorOrNil()
}

func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast) (<-chan txmgr.SendResponse, common.Hash) {
id := bcast.ID()
func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast, blockGasLimit uint64) (<-chan txmgr.SendResponse, common.Hash) {
ch := make(chan txmgr.SendResponse, 1)

id := bcast.ID()
value := ((*uint256.Int)(bcast.Value)).ToBig()
var candidate txmgr.TxCandidate
switch bcast.Type {
Expand All @@ -172,27 +180,45 @@ func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast
TxData: bcast.Input,
To: to,
Value: value,
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, false),
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, false, blockGasLimit),
}
case script.BroadcastCreate:
candidate = txmgr.TxCandidate{
TxData: bcast.Input,
To: nil,
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true),
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true, blockGasLimit),
}
case script.BroadcastCreate2:
txData := make([]byte, len(bcast.Salt)+len(bcast.Input))
copy(txData, bcast.Salt[:])
copy(txData[len(bcast.Salt):], bcast.Input)

candidate = txmgr.TxCandidate{
TxData: txData,
To: &script.DeterministicDeployerAddress,
Value: value,
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true, blockGasLimit),
}
}

ch := make(chan txmgr.SendResponse, 1)
t.mgr.SendAsync(ctx, candidate, ch)
return ch, id
}

func padGasLimit(data []byte, gasUsed uint64, creation bool) uint64 {
// padGasLimit calculates the gas limit for a transaction based on the intrinsic gas and the gas used by
// the underlying call. Values are multiplied by a pad factor to account for any discrepancies. The output
// is clamped to the block gas limit since Geth will reject transactions that exceed it before letting them
// into the mempool.
func padGasLimit(data []byte, gasUsed uint64, creation bool, blockGasLimit uint64) uint64 {
intrinsicGas, err := core.IntrinsicGas(data, nil, creation, true, true, false)
// This method never errors - we should look into it if it does.
if err != nil {
panic(err)
}

return uint64(float64(intrinsicGas+gasUsed) * GasPadFactor)
limit := uint64(float64(intrinsicGas+gasUsed) * GasPadFactor)
if limit > blockGasLimit {
return blockGasLimit
}
return limit
}
Loading

0 comments on commit 4f77209

Please sign in to comment.