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 }