From 7afbbd8fe927035d6970dd4be859d605b8886617 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:46:19 +0200 Subject: [PATCH] feat(op-deployer): bootstrap, genesis and genesis override tests --- .../cli/bootstrap/bootstrap_test.go | 162 ++++++++++++++++++ .../cli/genesis/genesis_test.go | 79 +++++++++ .../cli/overrides/overrides_test.go | 84 +++++++++ .../pkg/deployer/integration_test/cli/test.go | 51 ++++++ 4 files changed, 376 insertions(+) create mode 100644 op-deployer/pkg/deployer/integration_test/cli/bootstrap/bootstrap_test.go create mode 100644 op-deployer/pkg/deployer/integration_test/cli/genesis/genesis_test.go create mode 100644 op-deployer/pkg/deployer/integration_test/cli/overrides/overrides_test.go create mode 100644 op-deployer/pkg/deployer/integration_test/cli/test.go diff --git a/op-deployer/pkg/deployer/integration_test/cli/bootstrap/bootstrap_test.go b/op-deployer/pkg/deployer/integration_test/cli/bootstrap/bootstrap_test.go new file mode 100644 index 0000000000000..4b594543fae0c --- /dev/null +++ b/op-deployer/pkg/deployer/integration_test/cli/bootstrap/bootstrap_test.go @@ -0,0 +1,162 @@ +package bootstrap + +import ( + "context" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/bootstrap" + test "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/integration_test/cli" + "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" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// TestEndToEndBootstrapApply tests that a system can be fully bootstrapped and applied, both from +// local artifacts and the default tagged artifacts. The tagged artifacts test only runs on proposal +// or backports branches, since those are the only branches with an SLA to support tagged artifacts. +func TestEndToEndBootstrapApply(t *testing.T) { + require := require.New(t) + + lgr, l1RPC, l1Client, pkHex, pk, dk, l1ChainID := test.SetupAnvilTest(t) + l2ChainID := uint256.NewInt(1) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + superchainPAO := common.Address{'S', 'P', 'A', 'O'} + + apply := func(t *testing.T, loc *artifacts.Locator) { + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + bstrap, err := bootstrap.Superchain(ctx, bootstrap.SuperchainConfig{ + L1RPCUrl: l1RPC, + PrivateKey: pkHex, + Logger: lgr, + ArtifactsLocator: loc, + CacheDir: testCacheDir, + SuperchainProxyAdminOwner: superchainPAO, + ProtocolVersionsOwner: common.Address{'P', 'V', 'O'}, + Guardian: common.Address{'G'}, + Paused: false, + RecommendedProtocolVersion: params.ProtocolVersion{0x01, 0x02, 0x03, 0x04}, + RequiredProtocolVersion: params.ProtocolVersion{0x01, 0x02, 0x03, 0x04}, + }) + require.NoError(err) + + impls, err := bootstrap.Implementations(ctx, bootstrap.ImplementationsConfig{ + L1RPCUrl: l1RPC, + PrivateKey: pkHex, + ArtifactsLocator: loc, + MIPSVersion: int(standard.MIPSVersion), + WithdrawalDelaySeconds: standard.WithdrawalDelaySeconds, + MinProposalSizeBytes: standard.MinProposalSizeBytes, + ChallengePeriodSeconds: standard.ChallengePeriodSeconds, + ProofMaturityDelaySeconds: standard.ProofMaturityDelaySeconds, + DisputeGameFinalityDelaySeconds: standard.DisputeGameFinalityDelaySeconds, + DevFeatureBitmap: common.Hash{}, + SuperchainConfigProxy: bstrap.SuperchainConfigProxy, + ProtocolVersionsProxy: bstrap.ProtocolVersionsProxy, + UpgradeController: superchainPAO, + SuperchainProxyAdmin: bstrap.SuperchainProxyAdmin, + CacheDir: testCacheDir, + Logger: lgr, + Challenger: common.Address{'C'}, + }) + require.NoError(err) + + intent, st := test.NewIntent(t, l1ChainID, dk, l2ChainID, loc, loc) + intent.SuperchainRoles = nil + intent.OPCMAddress = &impls.Opcm + + require.NoError(deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + DeploymentTarget: deployer.DeploymentTargetLive, + L1RPCUrl: l1RPC, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, + }, + )) + + cg := test.EthClientCodeGetter(ctx, l1Client) + validateSuperchainDeployment(t, st, cg, true) + validateOPChainDeployment(t, cg, st) + } + + t.Run("default tagged artifacts", func(t *testing.T) { + apply(t, artifacts.DefaultL1ContractsLocator) + }) + + t.Run("local artifacts", func(t *testing.T) { + loc, _ := testutil.LocalArtifacts(t) + apply(t, loc) + }) +} + +// Validate that the superchain addresses are always set, even in subsequent deployments +// that pull from an existing OPCM deployment. +func validateSuperchainDeployment(t *testing.T, st *state.State, cg test.CodeGetter, includeSuperchainImpls bool) { + type addrTuple struct { + name string + addr common.Address + } + addrs := []addrTuple{ + {"SuperchainProxyAdminImpl", st.SuperchainDeployment.SuperchainProxyAdminImpl}, + {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxy}, + {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxy}, + {"OpcmImpl", st.ImplementationsDeployment.OpcmImpl}, + {"PreimageOracleImpl", st.ImplementationsDeployment.PreimageOracleImpl}, + {"MipsImpl", st.ImplementationsDeployment.MipsImpl}, + } + + if includeSuperchainImpls { + addrs = append(addrs, addrTuple{"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImpl}) + addrs = append(addrs, addrTuple{"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImpl}) + } + + for _, addr := range addrs { + t.Run(addr.name, func(t *testing.T) { + code := cg(t, addr.addr) + require.NotEmpty(t, code, "contract %s at %s has no code", addr.name, addr.addr) + }) + } +} + +// Validate that the implementation addresses are always set, even in subsequent deployments +// that pull from an existing OPCM deployment. +func validateOPChainDeployment(t *testing.T, cg test.CodeGetter, st *state.State) { + type addrTuple struct { + name string + addr common.Address + } + implAddrs := []addrTuple{ + {"DelayedWethImpl", st.ImplementationsDeployment.DelayedWethImpl}, + {"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImpl}, + {"OptimismPortalInteropImpl", st.ImplementationsDeployment.OptimismPortalInteropImpl}, + {"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImpl}, + {"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImpl}, + {"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1Erc721BridgeImpl}, + {"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImpl}, + {"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableErc20FactoryImpl}, + {"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImpl}, + {"MipsImpl", st.ImplementationsDeployment.MipsImpl}, + {"PreimageOracleImpl", st.ImplementationsDeployment.PreimageOracleImpl}, + } + + for _, addr := range implAddrs { + require.NotEmpty(t, addr.addr, "%s should be set", addr.name) + code := cg(t, addr.addr) + require.NotEmpty(t, code, "contract %s at %s has no code", addr.name, addr.addr) + } +} diff --git a/op-deployer/pkg/deployer/integration_test/cli/genesis/genesis_test.go b/op-deployer/pkg/deployer/integration_test/cli/genesis/genesis_test.go new file mode 100644 index 0000000000000..bc72ae58ebd2b --- /dev/null +++ b/op-deployer/pkg/deployer/integration_test/cli/genesis/genesis_test.go @@ -0,0 +1,79 @@ +package genesis + +import ( + "context" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + test "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/integration_test/cli" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// TestApplyGenesisStrategy tests genesis deployment with custom L1 parameters +func TestApplyGenesisStrategy(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + lgr, l1RPC, _, _, pk, dk, l1ChainID := test.SetupAnvilTest(t) + l2ChainID := uint256.NewInt(1) + + pragueOffset := uint64(2000) + l1GenesisParams := &state.L1DevGenesisParams{ + BlockParams: state.L1DevGenesisBlockParams{ + Timestamp: 1000, + GasLimit: 42_000_000, + ExcessBlobGas: 9000, + }, + PragueTimeOffset: &pragueOffset, + } + + deployChain := func(l1DevGenesisParams *state.L1DevGenesisParams) *state.State { + intent, st := test.NewIntent(t, l1ChainID, dk, l2ChainID, artifacts.DefaultL1ContractsLocator, artifacts.DefaultL2ContractsLocator) + intent.L1DevGenesisParams = l1DevGenesisParams + + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + + // Deploy using genesis strategy + require.NoError(deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + DeploymentTarget: deployer.DeploymentTargetGenesis, + L1RPCUrl: l1RPC, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, + }, + )) + + return st + } + + t.Run("defaults", func(t *testing.T) { + st := deployChain(nil) + require.Greater(st.Chains[0].StartBlock.Time, l1GenesisParams.BlockParams.Timestamp) + require.Nil(st.L1DevGenesis.Config.PragueTime) + }) + + t.Run("custom", func(t *testing.T) { + st := deployChain(l1GenesisParams) + require.EqualValues(l1GenesisParams.BlockParams.Timestamp, st.Chains[0].StartBlock.Time) + require.EqualValues(l1GenesisParams.BlockParams.Timestamp, st.L1DevGenesis.Timestamp) + + require.EqualValues(l1GenesisParams.BlockParams.GasLimit, st.L1DevGenesis.GasLimit) + require.NotNil(st.L1DevGenesis.ExcessBlobGas) + require.EqualValues(l1GenesisParams.BlockParams.ExcessBlobGas, *st.L1DevGenesis.ExcessBlobGas) + require.NotNil(st.L1DevGenesis.Config.PragueTime) + expectedPragueTimestamp := l1GenesisParams.BlockParams.Timestamp + *l1GenesisParams.PragueTimeOffset + require.EqualValues(expectedPragueTimestamp, *st.L1DevGenesis.Config.PragueTime) + }) +} diff --git a/op-deployer/pkg/deployer/integration_test/cli/overrides/overrides_test.go b/op-deployer/pkg/deployer/integration_test/cli/overrides/overrides_test.go new file mode 100644 index 0000000000000..01e8311c983d5 --- /dev/null +++ b/op-deployer/pkg/deployer/integration_test/cli/overrides/overrides_test.go @@ -0,0 +1,84 @@ +package overrides + +import ( + "context" + "strings" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + test "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/integration_test/cli" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// TestGlobalOverrides tests that global deployment overrides are properly applied +func TestGlobalOverrides(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + lgr, l1RPC, _, _, pk, dk, l1ChainID := test.SetupAnvilTest(t) + l2ChainID := uint256.NewInt(1) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + + intent, st := test.NewIntent(t, l1ChainID, dk, l2ChainID, artifacts.DefaultL1ContractsLocator, artifacts.DefaultL2ContractsLocator) + + expectedBaseFeeVaultRecipient := common.HexToAddress("0x0000000000000000000000000000000000000001") + expectedL1FeeVaultRecipient := common.HexToAddress("0x0000000000000000000000000000000000000002") + expectedSequencerFeeVaultRecipient := common.HexToAddress("0x0000000000000000000000000000000000000003") + expectedBaseFeeVaultMinimumWithdrawalAmount := strings.ToLower("0x1BC16D674EC80000") + expectedBaseFeeVaultWithdrawalNetwork := genesis.FromUint8(0) + expectedEnableGovernance := false + expectedGasPriceOracleBaseFeeScalar := uint32(1300) + expectedEIP1559Denominator := uint64(500) + expectedUseFaultProofs := false + + intent.GlobalDeployOverrides = map[string]interface{}{ + "l2BlockTime": float64(3), + "baseFeeVaultRecipient": expectedBaseFeeVaultRecipient, + "l1FeeVaultRecipient": expectedL1FeeVaultRecipient, + "sequencerFeeVaultRecipient": expectedSequencerFeeVaultRecipient, + "baseFeeVaultMinimumWithdrawalAmount": expectedBaseFeeVaultMinimumWithdrawalAmount, + "baseFeeVaultWithdrawalNetwork": expectedBaseFeeVaultWithdrawalNetwork, + "enableGovernance": expectedEnableGovernance, + "gasPriceOracleBaseFeeScalar": expectedGasPriceOracleBaseFeeScalar, + "eip1559Denominator": expectedEIP1559Denominator, + "useFaultProofs": expectedUseFaultProofs, + } + + err := deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + DeploymentTarget: deployer.DeploymentTargetGenesis, + L1RPCUrl: l1RPC, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, + }, + ) + require.NoError(err) + + // Verify that overrides were applied correctly + cfg, err := state.CombineDeployConfig(intent, intent.Chains[0], st, st.Chains[0]) + require.NoError(err) + require.Equal(uint64(3), cfg.L2InitializationConfig.L2CoreDeployConfig.L2BlockTime, "L2 block time should be 3 seconds") + require.Equal(expectedBaseFeeVaultRecipient, cfg.L2InitializationConfig.L2VaultsDeployConfig.BaseFeeVaultRecipient, "Base Fee Vault Recipient should be the expected address") + require.Equal(expectedL1FeeVaultRecipient, cfg.L2InitializationConfig.L2VaultsDeployConfig.L1FeeVaultRecipient, "L1 Fee Vault Recipient should be the expected address") + require.Equal(expectedSequencerFeeVaultRecipient, cfg.L2InitializationConfig.L2VaultsDeployConfig.SequencerFeeVaultRecipient, "Sequencer Fee Vault Recipient should be the expected address") + require.Equal(expectedBaseFeeVaultMinimumWithdrawalAmount, strings.ToLower(cfg.L2InitializationConfig.L2VaultsDeployConfig.BaseFeeVaultMinimumWithdrawalAmount.String()), "Base Fee Vault Minimum Withdrawal Amount should be the expected value") + require.Equal(expectedBaseFeeVaultWithdrawalNetwork, cfg.L2InitializationConfig.L2VaultsDeployConfig.BaseFeeVaultWithdrawalNetwork, "Base Fee Vault Withdrawal Network should be the expected value") + require.Equal(expectedEnableGovernance, cfg.L2InitializationConfig.GovernanceDeployConfig.EnableGovernance, "Governance should be disabled") + require.Equal(expectedGasPriceOracleBaseFeeScalar, cfg.L2InitializationConfig.GasPriceOracleDeployConfig.GasPriceOracleBaseFeeScalar, "Gas Price Oracle Base Fee Scalar should be the expected value") + require.Equal(expectedEIP1559Denominator, cfg.L2InitializationConfig.EIP1559DeployConfig.EIP1559Denominator, "EIP-1559 Denominator should be the expected value") + require.Equal(expectedUseFaultProofs, cfg.UseFaultProofs, "Fault proofs should not be enabled") +} diff --git a/op-deployer/pkg/deployer/integration_test/cli/test.go b/op-deployer/pkg/deployer/integration_test/cli/test.go new file mode 100644 index 0000000000000..ff17d181801c3 --- /dev/null +++ b/op-deployer/pkg/deployer/integration_test/cli/test.go @@ -0,0 +1,51 @@ +package cli + +import ( + "context" + "crypto/ecdsa" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/integration_test/shared" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils/devnet" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// SetupAnvilTest sets up an Anvil test environment and returns test utilities +func SetupAnvilTest(t *testing.T) (log.Logger, string, *ethclient.Client, string, *ecdsa.PrivateKey, *devkeys.MnemonicDevKeys, *uint256.Int) { + lgr := testlog.Logger(t, log.LevelInfo) + l1RPC, l1Client := devnet.DefaultAnvilRPC(t, lgr) + pkHex, pk, dk := shared.DefaultPrivkey(t) + l1ChainID := uint256.NewInt(devnet.DefaultChainID) + + return lgr, l1RPC, l1Client, pkHex, pk, dk, l1ChainID +} + +// NewIntent creates a new intent for testing +func NewIntent(t *testing.T, l1ChainID *uint256.Int, dk *devkeys.MnemonicDevKeys, l2ChainID *uint256.Int, l1Loc *artifacts.Locator, l2Loc *artifacts.Locator) (*state.Intent, *state.State) { + return shared.NewIntent(t, (*uint256.Int)(l1ChainID).ToBig(), dk, l2ChainID, l1Loc, l2Loc, 30000000) +} + +// CodeGetter provides a way to get code at an address for validation +type CodeGetter func(t *testing.T, addr common.Address) []byte + +// EthClientCodeGetter returns a function that gets code from an Ethereum client +func EthClientCodeGetter(ctx context.Context, client *ethclient.Client) CodeGetter { + return func(t *testing.T, addr common.Address) []byte { + code, err := client.CodeAt(ctx, addr, nil) + require.NoError(t, err) + return code + } +} + +// NewChainIntent creates a new chain intent for testing +func NewChainIntent(t *testing.T, dk *devkeys.MnemonicDevKeys, l1ChainID *uint256.Int, l2ChainID *uint256.Int) *state.ChainIntent { + return shared.NewChainIntent(t, dk, l1ChainID.ToBig(), l2ChainID, 30000000) +}