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

op-deployer: Run validations over L2 genesis (#12560)

* op-deployer: Run validations over L2 genesis

Asserts that the L2 genesis is valid using our pre-existing checks prior to running the script.

* add negative test

* fix broken test

* clarify test name
parent 58089858
package integration_test package integration_test
import ( import (
"bytes"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"log/slog" "log/slog"
"math/big" "math/big"
...@@ -9,7 +11,6 @@ import ( ...@@ -9,7 +11,6 @@ import (
"os" "os"
"path" "path"
"runtime" "runtime"
"strings"
"testing" "testing"
"time" "time"
...@@ -107,7 +108,7 @@ func TestEndToEndApply(t *testing.T) { ...@@ -107,7 +108,7 @@ func TestEndToEndApply(t *testing.T) {
deployerAddr, err := dk.Address(depKey) deployerAddr, err := dk.Address(depKey)
require.NoError(t, err) require.NoError(t, err)
loc := localArtifacsLocator(t) loc := localArtifactsLocator(t)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: log.NewLogger(log.DiscardHandler()), Logger: log.NewLogger(log.DiscardHandler()),
...@@ -153,7 +154,7 @@ func TestEndToEndApply(t *testing.T) { ...@@ -153,7 +154,7 @@ func TestEndToEndApply(t *testing.T) {
}) })
} }
func localArtifacsLocator(t *testing.T) *opcm.ArtifactsLocator { func localArtifactsLocator(t *testing.T) *opcm.ArtifactsLocator {
_, testFilename, _, ok := runtime.Caller(0) _, testFilename, _, ok := runtime.Caller(0)
require.Truef(t, ok, "failed to get test filename") require.Truef(t, ok, "failed to get test filename")
monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..", "..") monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..", "..")
...@@ -370,6 +371,7 @@ func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, int ...@@ -370,6 +371,7 @@ func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, int
checkImmutable(t, alloc, predeploys.BaseFeeVaultAddr, chainIntent.BaseFeeVaultRecipient) checkImmutable(t, alloc, predeploys.BaseFeeVaultAddr, chainIntent.BaseFeeVaultRecipient)
checkImmutable(t, alloc, predeploys.L1FeeVaultAddr, chainIntent.L1FeeVaultRecipient) checkImmutable(t, alloc, predeploys.L1FeeVaultAddr, chainIntent.L1FeeVaultRecipient)
checkImmutable(t, alloc, predeploys.SequencerFeeVaultAddr, chainIntent.SequencerFeeVaultRecipient) checkImmutable(t, alloc, predeploys.SequencerFeeVaultAddr, chainIntent.SequencerFeeVaultRecipient)
checkImmutable(t, alloc, predeploys.OptimismMintableERC721FactoryAddr, common.BigToHash(new(big.Int).SetUint64(intent.L1ChainID)))
// ownership slots // ownership slots
var addrAsSlot common.Hash var addrAsSlot common.Hash
...@@ -393,16 +395,19 @@ func getEIP1967ImplementationAddress(t *testing.T, allocations types.GenesisAllo ...@@ -393,16 +395,19 @@ func getEIP1967ImplementationAddress(t *testing.T, allocations types.GenesisAllo
return common.HexToAddress(storageValue.Hex()) return common.HexToAddress(storageValue.Hex())
} }
func checkImmutable(t *testing.T, allocations types.GenesisAlloc, proxyContract common.Address, feeRecipient common.Address) { type bytesMarshaler interface {
Bytes() []byte
}
func checkImmutable(t *testing.T, allocations types.GenesisAlloc, proxyContract common.Address, thing bytesMarshaler) {
implementationAddress := getEIP1967ImplementationAddress(t, allocations, proxyContract) implementationAddress := getEIP1967ImplementationAddress(t, allocations, proxyContract)
account, ok := allocations[implementationAddress] account, ok := allocations[implementationAddress]
require.True(t, ok, "%s not found in allocations", implementationAddress.Hex()) require.True(t, ok, "%s not found in allocations", implementationAddress)
require.NotEmpty(t, account.Code, "%s should have code", implementationAddress.Hex()) require.NotEmpty(t, account.Code, "%s should have code", implementationAddress)
require.Contains( require.True(
t, t,
strings.ToLower(common.Bytes2Hex(account.Code)), bytes.Contains(account.Code, thing.Bytes()),
strings.ToLower(strings.TrimPrefix(feeRecipient.Hex(), "0x")), "%s code should contain %s immutable", implementationAddress, hex.EncodeToString(thing.Bytes()),
"%s code should contain %s immutable", implementationAddress.Hex(), feeRecipient.Hex(),
) )
} }
...@@ -520,7 +525,7 @@ func TestL2BlockTimeOverride(t *testing.T) { ...@@ -520,7 +525,7 @@ func TestL2BlockTimeOverride(t *testing.T) {
deployerAddr, err := dk.Address(depKey) deployerAddr, err := dk.Address(depKey)
require.NoError(t, err) require.NoError(t, err)
loc := localArtifacsLocator(t) loc := localArtifactsLocator(t)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr, Logger: lgr,
...@@ -554,7 +559,11 @@ func TestL2BlockTimeOverride(t *testing.T) { ...@@ -554,7 +559,11 @@ func TestL2BlockTimeOverride(t *testing.T) {
st, st,
)) ))
cfg, err := state.CombineDeployConfig(intent, &state.ChainIntent{}, st, st.Chains[0]) chainIntent, err := intent.Chain(l2ChainID.Bytes32())
require.NoError(t, err)
chainState, err := st.Chain(l2ChainID.Bytes32())
require.NoError(t, err)
cfg, err := state.CombineDeployConfig(intent, chainIntent, st, chainState)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(3), cfg.L2InitializationConfig.L2CoreDeployConfig.L2BlockTime, "L2 block time should be 3 seconds") require.Equal(t, uint64(3), cfg.L2InitializationConfig.L2CoreDeployConfig.L2BlockTime, "L2 block time should be 3 seconds")
...@@ -579,15 +588,7 @@ func TestApplyGenesisStrategy(t *testing.T) { ...@@ -579,15 +588,7 @@ func TestApplyGenesisStrategy(t *testing.T) {
deployerAddr, err := dk.Address(depKey) deployerAddr, err := dk.Address(depKey)
require.NoError(t, err) require.NoError(t, err)
_, testFilename, _, ok := runtime.Caller(0) loc := localArtifactsLocator(t)
require.Truef(t, ok, "failed to get test filename")
monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..", "..")
artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts")
artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir))
require.NoError(t, err)
loc := &opcm.ArtifactsLocator{
URL: artifactsURL,
}
env, bundle, _ := createEnv(t, ctx, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr) env, bundle, _ := createEnv(t, ctx, lgr, nil, broadcaster.NoopBroadcaster(), deployerAddr)
intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc)
...@@ -611,3 +612,87 @@ func TestApplyGenesisStrategy(t *testing.T) { ...@@ -611,3 +612,87 @@ func TestApplyGenesisStrategy(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 := localArtifactsLocator(t)
// these tests were generated by grepping all usages of the deploy
// config in L2Genesis.s.sol.
tests := []struct {
name string
overrides map[string]any
}{
{
name: "L2 proxy admin owner not set",
overrides: map[string]any{
"proxyAdminOwner": nil,
},
},
{
name: "base fee vault recipient not set",
overrides: map[string]any{
"baseFeeVaultRecipient": nil,
},
},
{
name: "l1 fee vault recipient not set",
overrides: map[string]any{
"l1FeeVaultRecipient": nil,
},
},
{
name: "sequencer fee vault recipient not set",
overrides: map[string]any{
"sequencerFeeVaultRecipient": nil,
},
},
{
name: "l1 chain ID not set",
overrides: map[string]any{
"l1ChainID": nil,
},
},
{
name: "l2 chain ID not set",
overrides: map[string]any{
"l2ChainID": nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env, bundle, _ := createEnv(t, ctx, 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
intent.GlobalDeployOverrides = tt.overrides
err := deployer.ApplyPipeline(
ctx,
env,
bundle,
intent,
st,
)
require.Error(t, err)
require.ErrorContains(t, err, "failed to combine L2 init config")
})
}
}
...@@ -5,6 +5,9 @@ import ( ...@@ -5,6 +5,9 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -18,8 +21,16 @@ var ( ...@@ -18,8 +21,16 @@ var (
vaultMinWithdrawalAmount = mustHexBigFromHex("0x8ac7230489e80000") vaultMinWithdrawalAmount = mustHexBigFromHex("0x8ac7230489e80000")
) )
func DefaultDeployConfig(chainIntent *ChainIntent) genesis.DeployConfig { func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, chainState *ChainState) (genesis.DeployConfig, error) {
return genesis.DeployConfig{ cfg := genesis.DeployConfig{
L1DependenciesConfig: genesis.L1DependenciesConfig{
L1StandardBridgeProxy: chainState.L1StandardBridgeProxyAddress,
L1CrossDomainMessengerProxy: chainState.L1CrossDomainMessengerProxyAddress,
L1ERC721BridgeProxy: chainState.L1ERC721BridgeProxyAddress,
SystemConfigProxy: chainState.SystemConfigProxyAddress,
OptimismPortalProxy: chainState.OptimismPortalProxyAddress,
ProtocolVersionsProxy: state.SuperchainDeployment.ProtocolVersionsProxyAddress,
},
L2InitializationConfig: genesis.L2InitializationConfig{ L2InitializationConfig: genesis.L2InitializationConfig{
L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{ L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{
L2GenesisBlockGasLimit: 60_000_000, L2GenesisBlockGasLimit: 60_000_000,
...@@ -61,15 +72,27 @@ func DefaultDeployConfig(chainIntent *ChainIntent) genesis.DeployConfig { ...@@ -61,15 +72,27 @@ func DefaultDeployConfig(chainIntent *ChainIntent) genesis.DeployConfig {
UseInterop: false, UseInterop: false,
}, },
L2CoreDeployConfig: genesis.L2CoreDeployConfig{ L2CoreDeployConfig: genesis.L2CoreDeployConfig{
L1ChainID: intent.L1ChainID,
L2ChainID: chainState.ID.Big().Uint64(),
L2BlockTime: 2, L2BlockTime: 2,
FinalizationPeriodSeconds: 12, FinalizationPeriodSeconds: 12,
MaxSequencerDrift: 600, MaxSequencerDrift: 600,
SequencerWindowSize: 3600, SequencerWindowSize: 3600,
ChannelTimeoutBedrock: 300, ChannelTimeoutBedrock: 300,
SystemConfigStartBlock: 0, SystemConfigStartBlock: 0,
BatchInboxAddress: calculateBatchInboxAddr(chainState.ID),
},
OperatorDeployConfig: genesis.OperatorDeployConfig{
BatchSenderAddress: chainIntent.Roles.Batcher,
P2PSequencerAddress: chainIntent.Roles.UnsafeBlockSigner,
},
OwnershipDeployConfig: genesis.OwnershipDeployConfig{
ProxyAdminOwner: chainIntent.Roles.L2ProxyAdminOwner,
FinalSystemOwner: chainIntent.Roles.L1ProxyAdminOwner,
}, },
}, },
FaultProofDeployConfig: genesis.FaultProofDeployConfig{ FaultProofDeployConfig: genesis.FaultProofDeployConfig{
UseFaultProofs: true,
FaultGameWithdrawalDelay: 604800, FaultGameWithdrawalDelay: 604800,
PreimageOracleMinProposalSize: 126000, PreimageOracleMinProposalSize: 126000,
PreimageOracleChallengePeriod: 86400, PreimageOracleChallengePeriod: 86400,
...@@ -77,12 +100,29 @@ func DefaultDeployConfig(chainIntent *ChainIntent) genesis.DeployConfig { ...@@ -77,12 +100,29 @@ func DefaultDeployConfig(chainIntent *ChainIntent) genesis.DeployConfig {
DisputeGameFinalityDelaySeconds: 302400, DisputeGameFinalityDelaySeconds: 302400,
}, },
} }
}
func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, chainState *ChainState) (genesis.DeployConfig, error) { // The below dummy variables are set in order to allow the deploy
firstChainIntent := intent.Chains[0] // config to pass validation. The validation checks are useful to
cfg := DefaultDeployConfig(firstChainIntent) // ensure that the L2 is properly configured. They are not used by
// the L2 genesis script itself.
num := rpc.LatestBlockNumber
cfg.L1StartingBlockTag = &genesis.MarshalableRPCBlockNumberOrHash{
BlockNumber: &num,
}
cfg.L1BlockTime = 12
dummyAddr := common.Address{19: 0x01}
cfg.SuperchainL1DeployConfig = genesis.SuperchainL1DeployConfig{
SuperchainConfigGuardian: dummyAddr,
}
cfg.OutputOracleDeployConfig = genesis.OutputOracleDeployConfig{
L2OutputOracleSubmissionInterval: 1,
L2OutputOracleStartingTimestamp: 1,
L2OutputOracleProposer: dummyAddr,
L2OutputOracleChallenger: dummyAddr,
}
// End of dummy variables
// Apply overrides after setting the main values.
var err error var err error
if len(intent.GlobalDeployOverrides) > 0 { if len(intent.GlobalDeployOverrides) > 0 {
cfg, err = mergeJSON(cfg, intent.GlobalDeployOverrides) cfg, err = mergeJSON(cfg, intent.GlobalDeployOverrides)
...@@ -99,25 +139,9 @@ func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, ...@@ -99,25 +139,9 @@ func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State,
} }
} }
cfg.L2ChainID = chainState.ID.Big().Uint64() if err := cfg.Check(log.New(log.DiscardHandler())); err != nil {
cfg.L1DependenciesConfig = genesis.L1DependenciesConfig{ return cfg, fmt.Errorf("combined deploy config failed validation: %w", err)
L1StandardBridgeProxy: chainState.L1StandardBridgeProxyAddress,
L1CrossDomainMessengerProxy: chainState.L1CrossDomainMessengerProxyAddress,
L1ERC721BridgeProxy: chainState.L1ERC721BridgeProxyAddress,
SystemConfigProxy: chainState.SystemConfigProxyAddress,
OptimismPortalProxy: chainState.OptimismPortalProxyAddress,
ProtocolVersionsProxy: state.SuperchainDeployment.ProtocolVersionsProxyAddress,
}
cfg.OperatorDeployConfig = genesis.OperatorDeployConfig{
BatchSenderAddress: chainIntent.Roles.Batcher,
P2PSequencerAddress: chainIntent.Roles.UnsafeBlockSigner,
}
cfg.OwnershipDeployConfig = genesis.OwnershipDeployConfig{
ProxyAdminOwner: chainIntent.Roles.L2ProxyAdminOwner,
FinalSystemOwner: chainIntent.Roles.L1ProxyAdminOwner,
} }
cfg.BatchInboxAddress = calculateBatchInboxAddr(chainState.ID)
cfg.L1ChainID = intent.L1ChainID
return cfg, nil return cfg, nil
} }
......
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