From c3681775d7fe561d5deecba4c708ac95825858b4 Mon Sep 17 00:00:00 2001 From: sm-stack Date: Thu, 12 Dec 2024 15:52:28 +0900 Subject: [PATCH] test(e2e): add system test on withdrawal after MPT transition --- op-e2e/system_test.go | 164 ++++++++++++++++++++++++++++++++++++++++++ op-e2e/tx_helper.go | 17 ++--- 2 files changed, 168 insertions(+), 13 deletions(-) diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index a25c1053db..05963d56c6 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -2,6 +2,7 @@ package op_e2e import ( "context" + "encoding/json" "fmt" "math" "math/big" @@ -19,6 +20,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/driver" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/oppprof" @@ -30,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -1344,6 +1347,167 @@ func TestWithdrawals(t *testing.T) { require.Equal(t, withdrawAmount, diff) } +// TestKromaMptWithdrawals checks that a deposit and then withdrawal execution succeeds after Kroma MPT upgrade. +// It verifies the balance changes on L1 and L2 and has to include gas fees in the balance checks. +// It does not check that the withdrawal can be executed prior to the end of the finality period. +func TestKromaMptWithdrawals(t *testing.T) { + InitParallel(t) + + cfg := DefaultSystemConfig(t) + cfg.DeployConfig.FinalizationPeriodSeconds = 10 // 10s finalization period + genesisBlock := hexutil.Uint64(0) + ecotoneTimeOffset := hexutil.Uint64(2) + mptTimeOffset := hexutil.Uint64(4) + cfg.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock + cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneTimeOffset + cfg.DeployConfig.L2GenesisKromaMPTTimeOffset = &mptTimeOffset + + // Setup historical rpc node. + historicalRpcPort := 8045 + cfg.Nodes["historical"] = &rollupNode.Config{ + Driver: driver.Config{ + VerifierConfDepth: 0, + SequencerConfDepth: 0, + SequencerEnabled: false, + }, + L1EpochPollInterval: time.Second * 4, + RuntimeConfigReloadInterval: time.Minute * 10, + ConfigPersistence: &rollupNode.DisabledConfigPersistence{}, + Sync: sync.Config{SyncMode: sync.CLSync}, + } + cfg.Loggers["historical"] = testlog.Logger(t, log.LevelInfo).New("role", "historical") + cfg.GethOptions["historical"] = append(cfg.GethOptions["historical"], []geth.GethOption{ + func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error { + nodeCfg.HTTPPort = historicalRpcPort + nodeCfg.HTTPModules = []string{"debug", "eth"} + nodeCfg.HTTPHost = "127.0.0.1" + return nil + }, + }...) + + // Set historical rpc endpoint. + for name := range cfg.Nodes { + name := name + cfg.GethOptions[name] = append(cfg.GethOptions[name], []geth.GethOption{ + func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error { + // Since the migration process requires preimages, enable storing preimage option. + ethCfg.Preimages = true + ethCfg.RollupHistoricalRPC = fmt.Sprintf("http://127.0.0.1:%d", historicalRpcPort) + if name == "historical" { + ethCfg.RollupHistoricalRPC = "" + ethCfg.DisableMPTMigration = true + } + // Deep copy the genesis + dst := &core.Genesis{} + b, _ := json.Marshal(ethCfg.Genesis) + err := json.Unmarshal(b, dst) + if err != nil { + return err + } + ethCfg.Genesis = dst + return nil + }, + }...) + } + + sys, err := cfg.Start(t) + require.Nil(t, err, "Error starting up system") + defer sys.Close() + + l1Client := sys.Clients["l1"] + l2Seq := sys.Clients["sequencer"] + l2Verif := sys.Clients["verifier"] + + // Transactor Account + ethPrivKey := cfg.Secrets.Alice + fromAddr := crypto.PubkeyToAddress(ethPrivKey.PublicKey) + + // Create L1 signer + opts, err := bind.NewKeyedTransactorWithChainID(ethPrivKey, cfg.L1ChainIDBig()) + require.Nil(t, err) + + // Wait for the block after Kroma MPT migration + _, err = geth.WaitForBlock(big.NewInt(3), l2Verif, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + require.NoError(t, err) + + // Start L2 balance + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + startBalanceBeforeDeposit, err := l2Verif.BalanceAt(ctx, fromAddr, nil) + require.Nil(t, err) + + // Send deposit tx + mintAmount := big.NewInt(1_000_000_000_000) + opts.Value = mintAmount + SendDepositTx(t, cfg, l1Client, l2Verif, opts, func(l2Opts *DepositTxOpts) { + l2Opts.Value = common.Big0 + }, true) + + // Confirm L2 balance + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + endBalanceAfterDeposit, err := wait.ForBalanceChange(ctx, l2Verif, fromAddr, startBalanceBeforeDeposit) + require.Nil(t, err) + + diff := new(big.Int) + diff = diff.Sub(endBalanceAfterDeposit, startBalanceBeforeDeposit) + require.Equal(t, mintAmount, diff, "Did not get expected balance change after mint") + + // Start L2 balance for withdrawal + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + startBalanceBeforeWithdrawal, err := l2Seq.BalanceAt(ctx, fromAddr, nil) + require.Nil(t, err) + + withdrawAmount := big.NewInt(500_000_000_000) + tx, receipt := SendWithdrawal(t, cfg, l2Seq, ethPrivKey, func(opts *WithdrawalTxOpts) { + opts.Value = withdrawAmount + opts.VerifyOnClients(l2Verif) + }) + + // Verify L2 balance after withdrawal + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + header, err := l2Verif.HeaderByNumber(ctx, receipt.BlockNumber) + require.Nil(t, err) + + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + endBalanceAfterWithdrawal, err := wait.ForBalanceChange(ctx, l2Seq, fromAddr, startBalanceBeforeWithdrawal) + require.Nil(t, err) + + // Take fee into account + diff = new(big.Int).Sub(startBalanceBeforeWithdrawal, endBalanceAfterWithdrawal) + fees := calcGasFees(receipt.GasUsed, tx.GasTipCap(), tx.GasFeeCap(), header.BaseFee) + fees = fees.Add(fees, receipt.L1Fee) + diff = diff.Sub(diff, fees) + require.Equal(t, withdrawAmount, diff) + + // Take start balance on L1 + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + startBalanceBeforeFinalize, err := l1Client.BalanceAt(ctx, fromAddr, nil) + require.Nil(t, err) + + proveReceipt, finalizeReceipt := ProveAndFinalizeWithdrawal(t, cfg, sys, "verifier", ethPrivKey, receipt) + + // Verify balance after withdrawal + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + endBalanceAfterFinalize, err := wait.ForBalanceChange(ctx, l1Client, fromAddr, startBalanceBeforeFinalize) + require.Nil(t, err) + + // Ensure that withdrawal - gas fees are added to the L1 balance + // Fun fact, the fee is greater than the withdrawal amount + // NOTE: The gas fees include *both* the ProveWithdrawalTransaction and FinalizeWithdrawalTransaction transactions. + diff = new(big.Int).Sub(endBalanceAfterFinalize, startBalanceBeforeFinalize) + proveFee := new(big.Int).Mul(new(big.Int).SetUint64(proveReceipt.GasUsed), proveReceipt.EffectiveGasPrice) + finalizeFee := new(big.Int).Mul(new(big.Int).SetUint64(finalizeReceipt.GasUsed), finalizeReceipt.EffectiveGasPrice) + fees = new(big.Int).Add(proveFee, finalizeFee) + withdrawAmount = withdrawAmount.Sub(withdrawAmount, fees) + require.Equal(t, withdrawAmount, diff) +} + type stateGetterAdapter struct { ctx context.Context t *testing.T diff --git a/op-e2e/tx_helper.go b/op-e2e/tx_helper.go index 8f9f573cdf..60d8738378 100644 --- a/op-e2e/tx_helper.go +++ b/op-e2e/tx_helper.go @@ -3,12 +3,10 @@ package op_e2e import ( "context" "crypto/ecdsa" - "errors" "math/big" "testing" "time" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -26,7 +24,7 @@ import ( // The L2 transaction options can be configured by modifying the DepositTxOps value supplied to applyL2Opts // Will verify that the transaction is included with the expected status on L1 and L2 // Returns the receipt of the L2 transaction -func SendDepositTx(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Client *ethclient.Client, l1Opts *bind.TransactOpts, applyL2Opts DepositTxOptsFn) *types.Receipt { +func SendDepositTx(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Client *ethclient.Client, l1Opts *bind.TransactOpts, applyL2Opts DepositTxOptsFn, isKromaMPT ...bool) *types.Receipt { l2Opts := defaultDepositTxOpts(l1Opts) applyL2Opts(l2Opts) @@ -53,17 +51,10 @@ func SendDepositTx(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l require.NoError(t, err, "Could not reconstruct L2 Deposit") tx = types.NewTx(reconstructedDep) // [Kroma: START] Use KromaDepositTx instead of DepositTx - // If a receipt cannot be found using the DepositTx hash, it is converted to a KromaDepositTx. - _, err = l2Client.TransactionReceipt(ctx, tx.Hash()) - if err != nil { - if errors.Is(err, ethereum.NotFound) { - tx, err = tx.ToKromaDepositTx() - require.NoError(t, err, "failed to convert DepositTx to KromaDepositTx") - } else { - require.Fail(t, "failed to get deposit tx with reconstructed tx hash", err) - } + if len(isKromaMPT) == 0 { + tx, err = tx.ToKromaDepositTx() + require.NoError(t, err, "failed to convert DepositTx to KromaDepositTx") } - require.NoError(t, err) // [Kroma: END] l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus) require.NoError(t, err, "Waiting for deposit tx on L2")