Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

@bitwiseguy bitwiseguy Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a duplicate of the /integration_test/apply_test.go TestEndToEndBootstrapApply. I think we need to trigger bootstrap via the cli runner instead of via the library function to include cli flag parsing in the test. Otherwise I'm not sure what value this test is adding compared to the existing one

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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
})
}
Original file line number Diff line number Diff line change
@@ -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")
}
51 changes: 51 additions & 0 deletions op-deployer/pkg/deployer/integration_test/cli/test.go
Original file line number Diff line number Diff line change
@@ -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)
}