Skip to content

Commit

Permalink
test(e2e): add system test on withdrawal after MPT transition
Browse files Browse the repository at this point in the history
  • Loading branch information
sm-stack committed Dec 13, 2024
1 parent 939d473 commit 5415712
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 13 deletions.
177 changes: 177 additions & 0 deletions op-e2e/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package op_e2e

import (
"context"
"encoding/json"
"fmt"
"math"
"math/big"
Expand All @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -1344,6 +1347,180 @@ func TestWithdrawals(t *testing.T) {
require.Equal(t, withdrawAmount, diff)
}

// [Kroma: START]
// 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)

genesisBlock := hexutil.Uint64(0)
ecotoneTimeOffset := hexutil.Uint64(2)
mptTimeOffset := hexutil.Uint64(4)

cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 2 // 2s finalization period
cfg.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneTimeOffset
cfg.DeployConfig.L2GenesisKromaMPTTimeOffset = &mptTimeOffset
cfg.DeployConfig.L1BlockTime = 3
// set the L2 block time to 2 seconds to enforce the MPT transition at the second block
cfg.DeployConfig.L2BlockTime = 2

// Setup historical rpc node. Note that the port should be set as separate for each tests.
historicalRpcPort := 8055
cfg.Nodes["historical"] = &rollupNode.Config{
Driver: driver.Config{
VerifierConfDepth: 0,
SequencerConfDepth: 0,
SequencerEnabled: false,
},
RPC: rollupNode.RPCConfig{
ListenAddr: "127.0.0.1",
ListenPort: 0,
EnableAdmin: true,
},
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
mptMigrationNextBlock := uint64(mptTimeOffset)/cfg.DeployConfig.L2BlockTime + 1
_, err = geth.WaitForBlock(big.NewInt(int64(mptMigrationNextBlock)), 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)
}

// [Kroma: END]

type stateGetterAdapter struct {
ctx context.Context
t *testing.T
Expand Down
17 changes: 4 additions & 13 deletions op-e2e/tx_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)

Expand All @@ -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")
Expand Down

0 comments on commit 5415712

Please sign in to comment.