github.com/lmittmann/w3@v0.20.0/w3vm/bench_test.go (about)

     1  package w3vm_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"testing"
     7  
     8  	"github.com/ethereum/go-ethereum/core/types"
     9  	"github.com/lmittmann/w3"
    10  	"github.com/lmittmann/w3/module/eth"
    11  	"github.com/lmittmann/w3/w3types"
    12  	"github.com/lmittmann/w3/w3vm"
    13  )
    14  
    15  // BenchmarkVM runs [VM.ApplyTx] on the given block range and returns the
    16  // simulated gas per second.
    17  //
    18  // The goal of this benchmark is to compare the VM performance in a real-world
    19  // setting.
    20  //
    21  // The required block pre-state of this benchmark is stored in "testdata/w3vm/".
    22  // If the block range is changed the initial run can be quite slow (a local
    23  // Ethereum node is recommended).
    24  func BenchmarkVM(b *testing.B) {
    25  	const (
    26  		startBlock int64 = 19_000_000
    27  		nBlocks    int   = 10
    28  	)
    29  
    30  	// fetch blocks
    31  	blocks := make([]*types.Block, nBlocks)
    32  	calls := make([]w3types.RPCCaller, nBlocks)
    33  	for i := range blocks {
    34  		number := big.NewInt(startBlock + int64(i))
    35  		calls[i] = eth.BlockByNumber(number).Returns(&blocks[i])
    36  	}
    37  
    38  	if err := testClient.Call(calls...); err != nil {
    39  		b.Fatalf("Failed to fetch blocks: %v", err)
    40  	}
    41  
    42  	// execute blocks once to fetch the required state
    43  	fetchers := make([]w3vm.Fetcher, 0, len(blocks))
    44  	for _, block := range blocks {
    45  		fetcher := w3vm.NewTestingRPCFetcher(b, 1, testClient, new(big.Int).Sub(block.Number(), w3.Big1))
    46  		fetchers = append(fetchers, fetcher)
    47  		vm, err := w3vm.New(
    48  			w3vm.WithFetcher(fetcher),
    49  			w3vm.WithHeader(block.Header()),
    50  		)
    51  		if err != nil {
    52  			b.Fatalf("Failed to build VM for block %s: %v", block.Number(), err)
    53  		}
    54  
    55  		for _, tx := range block.Transactions() {
    56  			vm.ApplyTx(tx)
    57  		}
    58  	}
    59  
    60  	// benchmark
    61  	b.ReportAllocs()
    62  	b.ResetTimer()
    63  	var (
    64  		blockI  int // block index
    65  		txI     int // tx index
    66  		block   = blocks[blockI]
    67  		vm, err = w3vm.New(
    68  			w3vm.WithFetcher(fetchers[blockI]),
    69  			w3vm.WithHeader(block.Header()),
    70  		)
    71  		gasSimulated uint64
    72  	)
    73  	if err != nil {
    74  		b.Fatalf("Failed to build VM for block %s: %v", block.Number(), err)
    75  	}
    76  
    77  	for range b.N {
    78  		if txI >= block.Transactions().Len() {
    79  			blockI = (blockI + 1) % len(blocks)
    80  			txI = 0
    81  			block = blocks[blockI]
    82  			vm, err = w3vm.New(
    83  				w3vm.WithFetcher(fetchers[blockI]),
    84  				w3vm.WithHeader(block.Header()),
    85  			)
    86  			if err != nil {
    87  				b.Fatalf("Failed to build VM for block %s: %v", block.Number(), err)
    88  			}
    89  		}
    90  		tx := block.Transactions()[txI]
    91  
    92  		r, err := vm.ApplyTx(tx)
    93  		if r == nil {
    94  			b.Fatalf("Failed to apply tx %d: %v", txI, err)
    95  		}
    96  		gasSimulated += r.GasUsed
    97  		txI++
    98  	}
    99  
   100  	// report simulated gas per second
   101  	dur := b.Elapsed()
   102  	b.ReportMetric(float64(gasSimulated)/dur.Seconds(), "gas/s")
   103  }
   104  
   105  func BenchmarkVMCall(b *testing.B) {
   106  	var (
   107  		addrQuoter = w3.A("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6")
   108  		addrWETH   = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   109  		addrUSDC   = w3.A("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
   110  
   111  		funcQuote     = w3.MustNewFunc("quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96)", "uint256 amountOut")
   112  		funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")
   113  	)
   114  
   115  	benchmarks := []struct {
   116  		Name string
   117  		Opts []w3vm.Option
   118  		Msg  *w3types.Message
   119  	}{
   120  		{
   121  			Name: "UniswapV3Quote",
   122  			Opts: []w3vm.Option{
   123  				w3vm.WithFork(testClient, big.NewInt(20_000_000)),
   124  				w3vm.WithTB(b),
   125  			},
   126  			Msg: &w3types.Message{
   127  				To:    &addrQuoter,
   128  				Input: mustEncodeArgs(funcQuote, addrWETH, addrUSDC, big.NewInt(500), w3.BigEther, w3.Big0),
   129  			},
   130  		},
   131  		{
   132  			Name: "WethBalanceOf",
   133  			Opts: []w3vm.Option{
   134  				w3vm.WithFork(testClient, big.NewInt(20_000_000)),
   135  				w3vm.WithTB(b),
   136  			},
   137  			Msg: &w3types.Message{
   138  				To:    &addrWETH,
   139  				Input: mustEncodeArgs(funcBalanceOf, addrWETH),
   140  			},
   141  		},
   142  	}
   143  
   144  	for _, bench := range benchmarks {
   145  		b.Run(bench.Name, func(b *testing.B) {
   146  			// setup VM
   147  			vm, err := w3vm.New(bench.Opts...)
   148  			if err != nil {
   149  				b.Fatalf("Failed to build VM: %v", err)
   150  			}
   151  
   152  			b.ReportAllocs()
   153  			b.ResetTimer()
   154  
   155  			var gasSimulated uint64
   156  			for range b.N {
   157  				receipt, err := vm.Call(bench.Msg)
   158  				if err != nil {
   159  					b.Fatal(err)
   160  				}
   161  				gasSimulated += receipt.GasUsed
   162  			}
   163  
   164  			// report simulated gas per second
   165  			dur := b.Elapsed()
   166  			b.ReportMetric(float64(gasSimulated)/dur.Seconds(), "gas/s")
   167  		})
   168  	}
   169  }
   170  
   171  func BenchmarkVMSnapshot(b *testing.B) {
   172  	depositMsg := &w3types.Message{
   173  		From:  addr0,
   174  		To:    &addrWETH,
   175  		Value: w3.I("1 ether"),
   176  	}
   177  
   178  	runs := 2
   179  	b.Run(fmt.Sprintf("re-run %d", runs), func(b *testing.B) {
   180  		for range b.N {
   181  			vm, _ := w3vm.New(
   182  				w3vm.WithState(w3types.State{
   183  					addrWETH: {Code: codeWETH},
   184  					addr0:    {Balance: w3.I("2 ether")},
   185  				}),
   186  			)
   187  
   188  			for i := 0; i < runs; i++ {
   189  				_, err := vm.Apply(depositMsg)
   190  				if err != nil {
   191  					b.Fatalf("Failed to deposit: %v", err)
   192  				}
   193  			}
   194  		}
   195  	})
   196  
   197  	b.Run(fmt.Sprintf("snapshot %d", runs), func(b *testing.B) {
   198  		vm, _ := w3vm.New(
   199  			w3vm.WithState(w3types.State{
   200  				addrWETH: {Code: codeWETH},
   201  				addr0:    {Balance: w3.I("2 ether")},
   202  			}),
   203  		)
   204  
   205  		for i := 0; i < runs-1; i++ {
   206  			_, err := vm.Apply(depositMsg)
   207  			if err != nil {
   208  				b.Fatalf("Failed to deposit: %v", err)
   209  			}
   210  		}
   211  
   212  		snap := vm.Snapshot()
   213  
   214  		for range b.N {
   215  			_, err := vm.Apply(depositMsg)
   216  			if err != nil {
   217  				b.Fatalf("Failed to deposit: %v", err)
   218  			}
   219  
   220  			vm.Rollback(snap.Copy())
   221  		}
   222  	})
   223  }