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 }