github.com/lmittmann/w3@v0.20.0/w3vm/vm_test.go (about) 1 package w3vm_test 2 3 import ( 4 "bytes" 5 "cmp" 6 _ "embed" 7 "errors" 8 "fmt" 9 "math" 10 "math/big" 11 "os" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/core" 19 "github.com/ethereum/go-ethereum/core/state" 20 "github.com/ethereum/go-ethereum/core/tracing" 21 "github.com/ethereum/go-ethereum/core/types" 22 "github.com/ethereum/go-ethereum/core/vm" 23 "github.com/ethereum/go-ethereum/crypto" 24 "github.com/ethereum/go-ethereum/params" 25 gocmp "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 "github.com/lmittmann/w3" 28 "github.com/lmittmann/w3/internal" 29 "github.com/lmittmann/w3/module/eth" 30 "github.com/lmittmann/w3/w3types" 31 "github.com/lmittmann/w3/w3vm" 32 "golang.org/x/time/rate" 33 ) 34 35 var ( 36 addr0 = common.Address{0x0} 37 addr1 = common.Address{0x1} 38 addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") 39 40 //go:embed testdata/weth9.bytecode 41 hexCodeWETH string 42 codeWETH = w3.B(strings.TrimSpace(hexCodeWETH)) 43 44 funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256") 45 funcTransfer = w3.MustNewFunc("transfer(address,uint256)", "bool") 46 47 testArchiveRPC = cmp.Or(os.Getenv("RPC_MAINNET"), "https://eth.llamarpc.com") 48 testClient = w3.MustDial(testArchiveRPC, w3.WithRateLimiter( 49 rate.NewLimiter(rate.Every(time.Second/20), 100), 50 func(methods []string) (cost int) { return len(methods) }, 51 )) 52 ) 53 54 func TestVMSetNonce(t *testing.T) { 55 vm, _ := w3vm.New() 56 57 if nonce, _ := vm.Nonce(addr0); nonce != 0 { 58 t.Fatalf("Nonce: want 0, got %d", nonce) 59 } 60 61 want := uint64(42) 62 vm.SetNonce(addr0, want) 63 64 if nonce, _ := vm.Nonce(addr0); want != nonce { 65 t.Fatalf("Nonce: want %d, got %d", want, nonce) 66 } 67 } 68 69 func TestVMSetBalance(t *testing.T) { 70 vm, _ := w3vm.New() 71 72 if balance, _ := vm.Balance(addr0); balance.Sign() != 0 { 73 t.Fatalf("Balance: want 0, got %s", balance) 74 } 75 76 want := w3.I("1 ether") 77 vm.SetBalance(addr0, want) 78 79 if balance, _ := vm.Balance(addr0); want.Cmp(balance) != 0 { 80 t.Fatalf("Balance: want %s ether, got %s ether", w3.FromWei(want, 18), w3.FromWei(balance, 18)) 81 } 82 } 83 84 func TestVMSetCode(t *testing.T) { 85 vm, _ := w3vm.New() 86 87 if code, _ := vm.Code(addr0); len(code) != 0 { 88 t.Fatalf("Code: want empty, got %x", code) 89 } 90 91 want := []byte{0xc0, 0xfe} 92 vm.SetCode(addr0, want) 93 94 if code, _ := vm.Code(addr0); !bytes.Equal(want, code) { 95 t.Fatalf("Code: want %x, got %x", want, code) 96 } 97 } 98 99 func TestVMSetStorage(t *testing.T) { 100 vm, _ := w3vm.New() 101 102 if storage, _ := vm.StorageAt(addr0, common.Hash{}); storage != w3.Hash0 { 103 t.Fatalf("Storage: want empty, got %x", storage) 104 } 105 106 want := common.Hash{0xc0, 0xfe} 107 vm.SetStorageAt(addr0, common.Hash{}, want) 108 109 if storage, _ := vm.StorageAt(addr0, common.Hash{}); want != storage { 110 t.Fatalf("Storage: want %x, got %x", want, storage) 111 } 112 } 113 114 func TestVMApply(t *testing.T) { 115 tests := []struct { 116 PreState w3types.State 117 Message *w3types.Message 118 WantReceipt *w3vm.Receipt 119 WantErr error 120 }{ 121 { 122 Message: &w3types.Message{ 123 From: addr0, 124 To: &addr1, 125 Gas: 21_000, 126 Value: big.NewInt(1), 127 }, 128 WantErr: errors.New("insufficient funds for gas * price + value: address 0x0000000000000000000000000000000000000000 have 0 want 1"), 129 }, 130 { 131 Message: &w3types.Message{ 132 From: addr0, 133 To: &addr1, 134 Gas: 21_000, 135 GasFeeCap: big.NewInt(1), 136 Value: big.NewInt(1), 137 }, 138 WantErr: errors.New("insufficient funds for gas * price + value: address 0x0000000000000000000000000000000000000000 have 0 want 21001"), 139 }, 140 { 141 PreState: w3types.State{ 142 addr0: { 143 Balance: w3.I("1 ether"), 144 }, 145 }, 146 Message: &w3types.Message{ 147 From: addr0, 148 To: &addr1, 149 Gas: 21_000, 150 Value: w3.I("1 ether"), 151 }, 152 WantReceipt: &w3vm.Receipt{ 153 GasUsed: 21_000, 154 MaxGasUsed: 21_000, 155 }, 156 }, 157 { // WETH transfer 158 PreState: w3types.State{ 159 addr0: {Balance: w3.I("1 ether")}, 160 addrWETH: { 161 Code: codeWETH, 162 Storage: w3types.Storage{ 163 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("1 ether")), 164 }, 165 }, 166 }, 167 Message: &w3types.Message{ 168 From: addr0, 169 To: &addrWETH, 170 Input: mustEncodeArgs(funcTransfer, addr1, w3.I("1 ether")), 171 Gas: 100_000, 172 }, 173 WantReceipt: &w3vm.Receipt{ 174 GasUsed: 38_853, 175 MaxGasUsed: 48_566, 176 Logs: []*types.Log{ 177 { 178 Address: addrWETH, 179 Topics: []common.Hash{ 180 w3.H("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), 181 w3.H("0x0000000000000000000000000000000000000000000000000000000000000000"), 182 w3.H("0x0000000000000000000000000100000000000000000000000000000000000000"), 183 }, 184 Data: w3.B("0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"), 185 }, 186 }, 187 Output: w3.B("0x0000000000000000000000000000000000000000000000000000000000000001"), 188 }, 189 }, 190 { // WETH transfer with insufficient balance 191 PreState: w3types.State{ 192 addr0: {Balance: w3.I("1 ether")}, 193 addrWETH: { 194 Code: codeWETH, 195 Storage: w3types.Storage{ 196 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("1 ether")), 197 }, 198 }, 199 }, 200 Message: &w3types.Message{ 201 From: addr0, 202 To: &addrWETH, 203 Input: mustEncodeArgs(funcTransfer, addr1, w3.I("10 ether")), 204 Gas: 100_000, 205 }, 206 WantReceipt: &w3vm.Receipt{ 207 GasUsed: 24_019, 208 MaxGasUsed: 24_019, 209 Err: errors.New("execution reverted"), 210 }, 211 WantErr: errors.New("execution reverted"), 212 }, 213 { // revert with output 214 PreState: w3types.State{ 215 addr0: {Balance: w3.I("1 ether")}, 216 addr1: {Code: w3.B("0x60015ffd")}, // PUSH1 0x1, PUSH0, REVERT 217 }, 218 Message: &w3types.Message{ 219 From: addr0, 220 To: &addr1, 221 }, 222 WantReceipt: &w3vm.Receipt{ 223 GasUsed: 21_008, 224 MaxGasUsed: 21_008, 225 Output: w3.B("0x00"), 226 Err: errors.New("execution reverted"), 227 }, 228 WantErr: errors.New("execution reverted"), 229 }, 230 { // deploy contract for account with nonce == 0 231 Message: &w3types.Message{ 232 From: addr1, 233 Input: w3.B("0x00"), 234 }, 235 WantReceipt: &w3vm.Receipt{ 236 GasUsed: 53_006, 237 MaxGasUsed: 53_006, 238 ContractAddress: ptr(crypto.CreateAddress(addr1, 0)), 239 }, 240 }, 241 { // deploy contract for account with nonce > 0 242 PreState: w3types.State{ 243 addr1: {Nonce: 1}, 244 }, 245 Message: &w3types.Message{ 246 From: addr1, 247 Input: w3.B("0x00"), 248 }, 249 WantReceipt: &w3vm.Receipt{ 250 GasUsed: 53_006, 251 MaxGasUsed: 53_006, 252 ContractAddress: ptr(crypto.CreateAddress(addr1, 1)), 253 }, 254 }, 255 { // EOA with storage 256 PreState: w3types.State{ 257 addr0: { 258 Balance: w3.I("1 ether"), 259 Storage: w3types.Storage{ 260 common.Hash{0x1}: common.Hash{0x2}, 261 }, 262 }, 263 }, 264 Message: &w3types.Message{ 265 From: addr0, 266 To: &addr1, 267 Value: w3.I("1 ether"), 268 }, 269 WantReceipt: &w3vm.Receipt{ 270 GasUsed: 21_000, 271 MaxGasUsed: 21_000, 272 }, 273 }, 274 } 275 276 for i, test := range tests { 277 t.Run(strconv.Itoa(i), func(t *testing.T) { 278 vm, _ := w3vm.New( 279 w3vm.WithState(test.PreState), 280 ) 281 gotReceipt, gotErr := vm.Apply(test.Message) 282 if diff := gocmp.Diff(test.WantErr, gotErr, 283 internal.EquateErrors(), 284 ); diff != "" { 285 t.Fatalf("(-want +got)\n%s", diff) 286 } 287 if diff := gocmp.Diff(test.WantReceipt, gotReceipt, 288 internal.EquateErrors(), 289 cmpopts.IgnoreUnexported(w3vm.Receipt{}), 290 cmpopts.EquateComparable(common.Address{}, common.Hash{}), 291 ); diff != "" { 292 t.Fatalf("(-want +got)\n%s", diff) 293 } 294 }) 295 } 296 } 297 298 func TestVMApply_Hook(t *testing.T) { 299 vm, err := w3vm.New( 300 w3vm.WithNoBaseFee(), 301 w3vm.WithFork(testClient, big.NewInt(20_000_000)), 302 w3vm.WithTB(t), 303 ) 304 if err != nil { 305 t.Fatalf("Failed to create VM: %v", err) 306 } 307 308 // setup hook 309 var hookCount [10]uint 310 hook := &tracing.Hooks{ 311 // vm event hooks 312 OnEnter: func(int, byte, common.Address, common.Address, []byte, uint64, *big.Int) { hookCount[0]++ }, 313 OnExit: func(int, []byte, uint64, error, bool) { hookCount[1]++ }, 314 OnOpcode: func(uint64, byte, uint64, uint64, tracing.OpContext, []byte, int, error) { hookCount[2]++ }, 315 OnFault: func(uint64, byte, uint64, uint64, tracing.OpContext, int, error) { hookCount[3]++ }, 316 OnGasChange: func(uint64, uint64, tracing.GasChangeReason) { hookCount[4]++ }, 317 // state hooks 318 OnBalanceChange: func(common.Address, *big.Int, *big.Int, tracing.BalanceChangeReason) { hookCount[5]++ }, 319 OnNonceChange: func(addr common.Address, prev, new uint64) { hookCount[6]++ }, 320 OnCodeChange: func(common.Address, common.Hash, []byte, common.Hash, []byte) { hookCount[7]++ }, 321 OnStorageChange: func(addr common.Address, slot, prev, new common.Hash) { hookCount[8]++ }, 322 OnLog: func(*types.Log) { hookCount[9]++ }, 323 } 324 325 vm.Apply(&w3types.Message{To: &addrWETH, Value: w3.Big1}, hook) 326 vm.Apply(&w3types.Message{To: nil, Input: w3.B("0xfe")}, hook) // fault 327 vm.Apply(&w3types.Message{To: nil, Input: w3.B("0x5f5ff3")}, hook) // deploy empty contract 328 329 for i, field := range []string{ 330 "OnEnter", "OnExit", "OnOpcode", "OnFault", "OnGasChange", // vm event hooks 331 "OnBalanceChange", "OnNonceChange", "OnCodeChange", "OnStorageChange", "OnLog", // state hooks 332 } { 333 if hookCount[i] > 0 { 334 continue 335 } 336 t.Fatalf("Hook %q was not triggered", field) 337 } 338 } 339 340 func TestVMSnapshot(t *testing.T) { 341 vm, _ := w3vm.New( 342 w3vm.WithState(w3types.State{ 343 addrWETH: {Code: codeWETH}, 344 addr0: {Balance: w3.I("100 ether")}, 345 }), 346 ) 347 348 depositMsg := &w3types.Message{ 349 From: addr0, 350 To: &addrWETH, 351 Value: w3.I("1 ether"), 352 } 353 354 getBalanceOf := func(t *testing.T, token, acc common.Address) *big.Int { 355 t.Helper() 356 357 var balance *big.Int 358 if err := vm.CallFunc(token, funcBalanceOf, acc).Returns(&balance); err != nil { 359 t.Fatalf("Failed to call balanceOf: %v", err) 360 } 361 return balance 362 } 363 364 if got := getBalanceOf(t, addrWETH, addr0); got.Sign() != 0 { 365 t.Fatalf("Balance: want 0 WETH, got %s WETH", w3.FromWei(got, 18)) 366 } 367 368 var snap *state.StateDB 369 for i := range 100 { 370 if i == 42 { 371 snap = vm.Snapshot() 372 } 373 374 if _, err := vm.Apply(depositMsg); err != nil { 375 t.Fatalf("Failed to apply deposit msg: %v", err) 376 } 377 378 want := w3.I(strconv.Itoa(i+1) + " ether") 379 if got := getBalanceOf(t, addrWETH, addr0); want.Cmp(got) != 0 { 380 t.Fatalf("Balance: want %s WETH, got %s WETH", w3.FromWei(want, 18), w3.FromWei(got, 18)) 381 } 382 } 383 384 vm.Rollback(snap) 385 386 want := w3.I("42 ether") 387 if got := getBalanceOf(t, addrWETH, addr0); got.Cmp(want) != 0 { 388 t.Fatalf("Balance: want %s WETH, got %s WETH", w3.FromWei(want, 18), w3.FromWei(got, 18)) 389 } 390 } 391 392 func TestVMSnapshot_Logs(t *testing.T) { 393 var ( 394 preState = w3types.State{ 395 addrWETH: { 396 Code: codeWETH, 397 Storage: w3types.Storage{ 398 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("10 ether")), 399 }, 400 }, 401 } 402 transferMsg = &w3types.Message{ 403 From: addr0, 404 To: &addrWETH, 405 Func: funcTransfer, 406 Args: []any{addr1, w3.I("1 ether")}, 407 } 408 ) 409 410 tests := []struct { 411 Name string 412 F func() (receipt0, receipt1 *w3vm.Receipt, err error) 413 }{ 414 { 415 Name: "rollback_0", 416 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 417 vm, _ := w3vm.New(w3vm.WithState(preState)) 418 419 snap := vm.Snapshot() 420 421 receipt0, err = vm.Apply(transferMsg) 422 if err != nil { 423 return 424 } 425 426 vm.Rollback(snap) 427 428 receipt1, err = vm.Apply(transferMsg) 429 return 430 }, 431 }, 432 { 433 Name: "rollback_1", 434 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 435 vm, _ := w3vm.New(w3vm.WithState(preState)) 436 437 if _, err = vm.Apply(transferMsg); err != nil { 438 return 439 } 440 441 snap := vm.Snapshot() 442 443 receipt0, err = vm.Apply(transferMsg) 444 if err != nil { 445 return 446 } 447 448 vm.Rollback(snap) 449 450 receipt1, err = vm.Apply(transferMsg) 451 return 452 }, 453 }, 454 { 455 Name: "rollback_2", 456 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 457 vm, _ := w3vm.New(w3vm.WithState(preState)) 458 459 receipt0, err = vm.Apply(transferMsg) 460 if err != nil { 461 return 462 } 463 464 snap := vm.Snapshot() 465 vm.Rollback(snap) 466 467 receipt1, err = vm.Apply(transferMsg) 468 return 469 }, 470 }, 471 { 472 Name: "rollback_3", 473 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 474 vm, _ := w3vm.New(w3vm.WithState(preState)) 475 476 if _, err = vm.Apply(transferMsg); err != nil { 477 return 478 } 479 480 snap := vm.Snapshot() 481 receipt0, err = vm.Apply(transferMsg) 482 if err != nil { 483 return 484 } 485 486 vm2, _ := w3vm.New(w3vm.WithState(preState)) 487 vm2.Rollback(snap) 488 489 receipt1, err = vm2.Apply(transferMsg) 490 return 491 }, 492 }, 493 { 494 Name: "new_0", 495 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 496 vm, _ := w3vm.New(w3vm.WithState(preState)) 497 498 snap := vm.Snapshot() 499 500 receipt0, err = vm.Apply(transferMsg) 501 if err != nil { 502 return 503 } 504 505 vm, _ = w3vm.New(w3vm.WithStateDB(snap)) 506 507 receipt1, err = vm.Apply(transferMsg) 508 return 509 }, 510 }, 511 { 512 Name: "new_1", 513 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 514 vm, _ := w3vm.New(w3vm.WithState(preState)) 515 516 if _, err = vm.Apply(transferMsg); err != nil { 517 return 518 } 519 520 snap := vm.Snapshot() 521 522 receipt0, err = vm.Apply(transferMsg) 523 if err != nil { 524 return 525 } 526 527 vm, _ = w3vm.New(w3vm.WithStateDB(snap)) 528 529 receipt1, err = vm.Apply(transferMsg) 530 return 531 }, 532 }, 533 { 534 Name: "new_2", 535 F: func() (receipt0, receipt1 *w3vm.Receipt, err error) { 536 vm, _ := w3vm.New(w3vm.WithState(preState)) 537 538 receipt0, err = vm.Apply(transferMsg) 539 if err != nil { 540 return 541 } 542 543 snap := vm.Snapshot() 544 vm, _ = w3vm.New(w3vm.WithStateDB(snap)) 545 546 receipt1, err = vm.Apply(transferMsg) 547 return 548 }, 549 }, 550 } 551 552 for _, test := range tests { 553 t.Run(test.Name, func(t *testing.T) { 554 receipt0, receipt1, err := test.F() 555 if err != nil { 556 t.Fatal(err) 557 } 558 559 if diff := gocmp.Diff(receipt0.Logs, receipt1.Logs); diff != "" { 560 t.Fatalf("(-want +got)\n%s", diff) 561 } 562 }) 563 } 564 } 565 566 func TestVMCall(t *testing.T) { 567 tests := []struct { 568 PreState w3types.State 569 Message *w3types.Message 570 WantReceipt *w3vm.Receipt 571 WantErr error 572 }{ 573 { 574 PreState: w3types.State{ 575 addrWETH: { 576 Code: codeWETH, 577 Storage: w3types.Storage{ 578 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("1 ether")), 579 }, 580 }, 581 }, 582 Message: &w3types.Message{ 583 From: addr0, 584 To: &addrWETH, 585 Input: mustEncodeArgs(funcBalanceOf, addr0), 586 }, 587 WantReceipt: &w3vm.Receipt{ 588 GasUsed: 23_726, 589 MaxGasUsed: 23_726, 590 Output: w3.B("0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"), 591 }, 592 }, 593 } 594 595 for i, test := range tests { 596 t.Run(strconv.Itoa(i), func(t *testing.T) { 597 vm, _ := w3vm.New( 598 w3vm.WithState(test.PreState), 599 ) 600 gotReceipt, gotErr := vm.Call(test.Message) 601 if diff := gocmp.Diff(test.WantErr, gotErr, 602 internal.EquateErrors(), 603 ); diff != "" { 604 t.Fatalf("(-want +got)\n%s", diff) 605 } 606 if diff := gocmp.Diff(test.WantReceipt, gotReceipt, 607 internal.EquateErrors(), 608 cmpopts.IgnoreUnexported(w3vm.Receipt{}), 609 cmpopts.EquateComparable(common.Address{}, common.Hash{}), 610 ); diff != "" { 611 t.Fatalf("(-want +got)\n%s", diff) 612 } 613 }) 614 } 615 } 616 617 func TestVMCallFunc(t *testing.T) { 618 vm, _ := w3vm.New( 619 w3vm.WithState(w3types.State{ 620 addrWETH: { 621 Code: codeWETH, 622 Storage: w3types.Storage{ 623 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("1 ether")), 624 }, 625 }, 626 }), 627 ) 628 629 var gotBalance *big.Int 630 if err := vm.CallFunc(addrWETH, funcBalanceOf, addr0).Returns(&gotBalance); err != nil { 631 t.Fatalf("Failed to call balanceOf: %v", err) 632 } 633 634 wantBalance := w3.I("1 ether") 635 if wantBalance.Cmp(gotBalance) != 0 { 636 t.Fatalf("Balance: want %s, got %s", wantBalance, gotBalance) 637 } 638 } 639 640 func TestVM_Fetcher(t *testing.T) { 641 f := new(testFetcher) 642 vm, err := w3vm.New( 643 w3vm.WithFetcher(f), 644 ) 645 if err != nil { 646 t.Fatalf("Failed to create VM: %v", err) 647 } 648 649 _, err = vm.Nonce(addr0) 650 want := "fetching failed: failed to fetch nonce of 0x0000000000000000000000000000000000000000" 651 if !errors.Is(err, w3vm.ErrFetch) || want != err.Error() { 652 t.Errorf("Nonce: want %q, got %q", want, err) 653 } 654 655 _, err = vm.Balance(addr0) 656 want = "fetching failed: failed to fetch balance of 0x0000000000000000000000000000000000000000" 657 if !errors.Is(err, w3vm.ErrFetch) || want != err.Error() { 658 t.Errorf("Balance: want %q, got %q", want, err) 659 } 660 661 _, err = vm.Code(addr0) 662 want = "fetching failed: failed to fetch code of 0x0000000000000000000000000000000000000000" 663 if !errors.Is(err, w3vm.ErrFetch) || want != err.Error() { 664 t.Errorf("Code: want %q, got %q", want, err) 665 } 666 667 _, err = vm.StorageAt(addr0, common.Hash{}) 668 want = "fetching failed: failed to fetch storage of 0x0000000000000000000000000000000000000000 at 0x0000000000000000000000000000000000000000000000000000000000000000" 669 if !errors.Is(err, w3vm.ErrFetch) || want != err.Error() { 670 t.Errorf("StorageAt: want %q, got %q", want, err) 671 } 672 } 673 674 func TestVM_BaseFee(t *testing.T) { 675 // contract that returns GASPRICE 676 code := w3.B("3a", "5f", "52", "6020", "5f", "f3") 677 codeAddr := common.Address{0xc0, 0xde} 678 679 preState := w3types.State{ 680 codeAddr: {Code: code}, 681 w3.Addr0: {Balance: w3.I("1000 ether")}, 682 } 683 684 tests := []struct { 685 Name string 686 Msg *w3types.Message 687 Opts []w3vm.Option 688 WantGasPrice *big.Int 689 WantErr error 690 }{ 691 { 692 Name: "BaseFee0_GasPrice", 693 Msg: &w3types.Message{To: &codeAddr, GasPrice: big.NewInt(10)}, 694 Opts: []w3vm.Option{}, 695 WantGasPrice: big.NewInt(10), 696 }, 697 { 698 Name: "BaseFee1_GasPrice", 699 Msg: &w3types.Message{To: &codeAddr, GasPrice: big.NewInt(10)}, 700 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(1)})}, 701 WantGasPrice: big.NewInt(10), 702 }, 703 { 704 Name: "BaseFee100_GasPrice", 705 Msg: &w3types.Message{To: &codeAddr, GasPrice: big.NewInt(10)}, 706 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(100)})}, 707 WantErr: core.ErrFeeCapTooLow, 708 }, 709 { 710 Name: "NoBaseFee100_GasPrice", 711 Msg: &w3types.Message{To: &codeAddr, GasPrice: big.NewInt(10)}, 712 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(100)}), w3vm.WithNoBaseFee()}, 713 WantGasPrice: big.NewInt(0), 714 }, 715 716 { 717 Name: "BaseFee0_GasFeeCap", 718 Msg: &w3types.Message{To: &codeAddr, GasFeeCap: big.NewInt(10)}, 719 Opts: []w3vm.Option{}, 720 WantGasPrice: big.NewInt(0), 721 }, 722 { 723 Name: "BaseFee0_GasFeeCap_GasTipCap", 724 Msg: &w3types.Message{To: &codeAddr, GasFeeCap: big.NewInt(10), GasTipCap: big.NewInt(5)}, 725 Opts: []w3vm.Option{}, 726 WantGasPrice: big.NewInt(5), 727 }, 728 { 729 Name: "BaseFee1_GasFeeCap", 730 Msg: &w3types.Message{To: &codeAddr, GasFeeCap: big.NewInt(10)}, 731 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(1)})}, 732 WantGasPrice: big.NewInt(1), 733 }, 734 { 735 Name: "BaseFee100_GasFeeCap", 736 Msg: &w3types.Message{To: &codeAddr, GasFeeCap: big.NewInt(10)}, 737 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(100)})}, 738 WantErr: core.ErrFeeCapTooLow, 739 }, 740 { 741 Name: "NoBaseFee100_GasFeeCap", 742 Msg: &w3types.Message{To: &codeAddr, GasFeeCap: big.NewInt(10)}, 743 Opts: []w3vm.Option{w3vm.WithHeader(&types.Header{BaseFee: big.NewInt(100)}), w3vm.WithNoBaseFee()}, 744 WantGasPrice: big.NewInt(0), 745 }, 746 } 747 748 for _, test := range tests { 749 t.Run(test.Name, func(t *testing.T) { 750 vm, _ := w3vm.New(append(test.Opts, w3vm.WithState(preState))...) 751 receipt, gotErr := vm.Apply(test.Msg) 752 if !errors.Is(gotErr, test.WantErr) { 753 t.Fatalf("Error: want %v, got %v", test.WantErr, gotErr) 754 } else if receipt == nil { 755 return 756 } 757 if gotGasPrice := new(big.Int).SetBytes(receipt.Output); test.WantGasPrice.Cmp(gotGasPrice) != 0 { 758 t.Fatalf("GasPrice: want %v, got %v", test.WantGasPrice, gotGasPrice) 759 } 760 }) 761 } 762 } 763 764 type testFetcher struct{} 765 766 func (f *testFetcher) Account(addr common.Address) (*types.StateAccount, error) { 767 return nil, fmt.Errorf("%w: failed to fetch account", w3vm.ErrFetch) 768 } 769 770 func (f *testFetcher) Code(codeHash common.Hash) ([]byte, error) { 771 return nil, fmt.Errorf("%w: failed to fetch code hash", w3vm.ErrFetch) 772 } 773 774 func (f *testFetcher) StorageAt(addr common.Address, key common.Hash) (common.Hash, error) { 775 return common.Hash{}, fmt.Errorf("%w: failed to fetch storage", w3vm.ErrFetch) 776 } 777 778 func (f *testFetcher) HeaderHash(blockNumber uint64) (common.Hash, error) { 779 return common.Hash{}, fmt.Errorf("%w: failed to fetch header hash", w3vm.ErrFetch) 780 } 781 782 func TestVMApply_Integration(t *testing.T) { 783 if testing.Short() { 784 t.SkipNow() 785 } 786 787 tests := []struct { 788 Name string 789 Offset int64 // Start block number 790 Size int64 // Number of blocks 791 }{ 792 {Name: "Byzantium", Offset: 4_370_000 - 2, Size: 4}, 793 {Name: "Constantinople&Petersburg", Offset: 7_280_000 - 2, Size: 4}, 794 {Name: "Istanbul", Offset: 9_069_000 - 2, Size: 4}, 795 {Name: "Muir Glacier", Offset: 9_200_000 - 2, Size: 4}, 796 {Name: "Berlin", Offset: 12_244_000 - 2, Size: 4}, 797 {Name: "London", Offset: 12_965_000 - 2, Size: 4}, 798 {Name: "Arrow Glacier", Offset: 13_773_000 - 2, Size: 4}, 799 {Name: "Gray Glacier", Offset: 15_050_000 - 2, Size: 4}, 800 {Name: "Paris", Offset: 15_537_394 - 2, Size: 4}, // The Merge 801 {Name: "Shanghai", Offset: 17_034_870 - 2, Size: 4}, 802 {Name: "Cancun", Offset: 19_426_487 - 2, Size: 4}, 803 } 804 805 for _, test := range tests { 806 t.Run(test.Name, func(t *testing.T) { 807 // execute blocks 808 for i := test.Offset; i < test.Offset+test.Size; i++ { 809 // gather block and receipts 810 blockNumber := big.NewInt(i) 811 812 t.Run(blockNumber.String(), func(t *testing.T) { 813 t.Parallel() 814 815 // fetch block 816 var ( 817 block *types.Block 818 receipts types.Receipts 819 ) 820 if err := testClient.Call( 821 eth.BlockByNumber(blockNumber).Returns(&block), 822 eth.BlockReceipts(blockNumber).Returns(&receipts), 823 ); err != nil { 824 t.Fatalf("Failed to fetch block and receipts: %v", err) 825 } 826 827 // setup vm 828 f := w3vm.NewTestingRPCFetcher(t, 1, testClient, big.NewInt(i-1)) 829 vm, _ := w3vm.New( 830 w3vm.WithFetcher(f), 831 w3vm.WithHeader(block.Header()), 832 ) 833 834 // execute txs 835 for j, tx := range block.Transactions() { 836 wantReceipt := &w3vm.Receipt{ 837 GasUsed: receipts[j].GasUsed, 838 Logs: receipts[j].Logs, 839 } 840 if receipts[j].ContractAddress != addr0 { 841 wantReceipt.ContractAddress = &receipts[j].ContractAddress 842 } 843 if receipts[j].Status == types.ReceiptStatusFailed { 844 wantReceipt.Err = cmpopts.AnyError 845 } 846 847 gotReceipt, err := vm.ApplyTx(tx) 848 if err != nil && gotReceipt == nil { 849 t.Fatalf("Failed to apply tx %d (%s): %v", j, tx.Hash(), err) 850 } 851 if diff := gocmp.Diff(wantReceipt, gotReceipt, 852 cmpopts.EquateEmpty(), 853 cmpopts.EquateErrors(), 854 cmpopts.IgnoreUnexported(w3vm.Receipt{}), 855 cmpopts.IgnoreFields(w3vm.Receipt{}, "MaxGasUsed", "Output"), 856 cmpopts.IgnoreFields(types.Log{}, "BlockHash", "BlockNumber", "BlockTimestamp", "TxHash", "TxIndex", "Index"), 857 cmpopts.EquateComparable(common.Address{}, common.Hash{}), 858 ); diff != "" { 859 t.Fatalf("[%v,%d,%s] (-want +got)\n%s", block.Number(), j, tx.Hash(), diff) 860 } 861 } 862 863 // check coinbase balance at the end of the block 864 if !params.MainnetChainConfig.IsShanghai(block.Number(), block.Time()) { 865 return // only check postmerge blocks for correct coinbase balance 866 } 867 868 var wantCoinbaseBal *big.Int 869 if err := testClient.Call( 870 eth.Balance(block.Coinbase(), block.Number()).Returns(&wantCoinbaseBal), 871 ); err != nil { 872 t.Fatalf("Failed to fetch coinbase balance: %v", err) 873 } 874 875 // actual coinbase balance after all txs were applied 876 gotCoinbaseBal, _ := vm.Balance(block.Coinbase()) 877 if wantCoinbaseBal.Cmp(gotCoinbaseBal) != 0 { 878 t.Fatalf("Coinbase balance: want: %s, got: %s (%s)", 879 w3.FromWei(wantCoinbaseBal, 18), 880 w3.FromWei(gotCoinbaseBal, 18), 881 block.Coinbase(), 882 ) 883 } 884 }) 885 } 886 }) 887 } 888 } 889 890 func mustEncodeArgs(f w3types.Func, args ...any) []byte { 891 input, err := f.EncodeArgs(args...) 892 if err != nil { 893 panic(err) 894 } 895 return input 896 } 897 898 func BenchmarkTransferWETH9(b *testing.B) { 899 addr0 := w3vm.RandA() 900 addr1 := w3vm.RandA() 901 902 // encode input 903 input := mustEncodeArgs(funcTransfer, addr1, w3.I("1 gwei")) 904 905 blockCtx := vm.BlockContext{ 906 CanTransfer: core.CanTransfer, 907 Transfer: core.Transfer, 908 GetHash: func(n uint64) common.Hash { return common.Hash{} }, 909 910 BlockNumber: new(big.Int), 911 Difficulty: new(big.Int), 912 BaseFee: new(big.Int), 913 GasLimit: 30_000_000, 914 } 915 916 b.Run("w3vm", func(b *testing.B) { 917 vm, _ := w3vm.New( 918 w3vm.WithBlockContext(&blockCtx), 919 w3vm.WithChainConfig(params.AllEthashProtocolChanges), 920 w3vm.WithState(w3types.State{ 921 addrWETH: { 922 Code: codeWETH, 923 Storage: w3types.Storage{ 924 w3vm.WETHBalanceSlot(addr0): common.BigToHash(w3.I("1 ether")), 925 }, 926 }, 927 }), 928 ) 929 930 b.ResetTimer() 931 for i := range b.N { 932 _, err := vm.Apply(&w3types.Message{ 933 From: addr0, 934 To: &addrWETH, 935 Gas: 100_000, 936 Nonce: uint64(i), 937 Input: input, 938 }) 939 if err != nil { 940 b.Fatalf("Failed to apply msg: %v", err) 941 } 942 } 943 }) 944 945 b.Run("geth", func(b *testing.B) { 946 stateDB, _ := state.New(common.Hash{}, state.NewDatabaseForTesting()) 947 stateDB.SetCode(addrWETH, codeWETH) 948 stateDB.SetState(addrWETH, w3vm.WETHBalanceSlot(addr0), common.BigToHash(w3.I("1 ether"))) 949 950 b.ResetTimer() 951 for i := range b.N { 952 msg := &core.Message{ 953 To: &addrWETH, 954 From: addr0, 955 Nonce: uint64(i), 956 Value: new(big.Int), 957 GasLimit: 100_000, 958 GasPrice: new(big.Int), 959 GasFeeCap: new(big.Int), 960 GasTipCap: new(big.Int), 961 Data: input, 962 AccessList: nil, 963 SkipNonceChecks: false, 964 SkipFromEOACheck: false, 965 } 966 evm := vm.NewEVM(blockCtx, stateDB, params.AllEthashProtocolChanges, vm.Config{NoBaseFee: true}) 967 gp := new(core.GasPool).AddGas(math.MaxUint64) 968 _, err := core.ApplyMessage(evm, msg, gp) 969 if err != nil { 970 b.Fatalf("Failed to apply msg: %v", err) 971 } 972 stateDB.Finalise(false) 973 } 974 }) 975 } 976 977 func ptr[T any](t T) *T { return &t }