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

     1  package w3vm_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum/common"
    11  	"github.com/ethereum/go-ethereum/core/tracing"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	gethVm "github.com/ethereum/go-ethereum/core/vm"
    14  	"github.com/ethereum/go-ethereum/eth/tracers/logger"
    15  	"github.com/ethereum/go-ethereum/params"
    16  	"github.com/lmittmann/w3"
    17  	"github.com/lmittmann/w3/module/eth"
    18  	"github.com/lmittmann/w3/w3types"
    19  	"github.com/lmittmann/w3/w3vm"
    20  	"github.com/lmittmann/w3/w3vm/hooks"
    21  )
    22  
    23  var (
    24  	addrA = common.Address{0x0a}
    25  	addrB = common.Address{0x0b}
    26  
    27  	client = w3.MustDial("https://ethereum-rpc.publicnode.com")
    28  )
    29  
    30  // Execute an Ether transfer.
    31  func ExampleVM_simpleTransfer() {
    32  	vm, _ := w3vm.New(
    33  		w3vm.WithState(w3types.State{
    34  			addrA: {Balance: w3.I("100 ether")},
    35  		}),
    36  	)
    37  
    38  	// Print balances
    39  	balA, _ := vm.Balance(addrA)
    40  	balB, _ := vm.Balance(addrB)
    41  	fmt.Printf("Before transfer:\nA: %s ETH, B: %s ETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
    42  
    43  	// Transfer 10 ETH from A to B
    44  	vm.Apply(&w3types.Message{
    45  		From:  addrA,
    46  		To:    &addrB,
    47  		Value: w3.I("10 ether"),
    48  	})
    49  
    50  	// Print balances
    51  	balA, _ = vm.Balance(addrA)
    52  	balB, _ = vm.Balance(addrB)
    53  	fmt.Printf("After transfer:\nA: %s ETH, B: %s ETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
    54  	// Output:
    55  	// Before transfer:
    56  	// A: 100 ETH, B: 0 ETH
    57  	// After transfer:
    58  	// A: 90 ETH, B: 10 ETH
    59  }
    60  
    61  // Execute an ERC20 token transfer with faked token balance (Wrapped Ether).
    62  func ExampleVM_fakeTokenBalance() {
    63  	vm, err := w3vm.New(
    64  		w3vm.WithFork(client, nil),
    65  		w3vm.WithNoBaseFee(),
    66  		w3vm.WithState(w3types.State{
    67  			addrWETH: {Storage: w3types.Storage{
    68  				w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
    69  			}},
    70  		}),
    71  	)
    72  	if err != nil {
    73  		// ...
    74  	}
    75  
    76  	// Print WETH balance
    77  	var balA, balB *big.Int
    78  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balA); err != nil {
    79  		// ...
    80  	}
    81  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrB).Returns(&balB); err != nil {
    82  		// ...
    83  	}
    84  	fmt.Printf("Before transfer:\nA: %s WETH, B: %s WETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
    85  
    86  	// Transfer 10 WETH from A to B
    87  	if _, err := vm.Apply(&w3types.Message{
    88  		From: addrA,
    89  		To:   &addrWETH,
    90  		Func: funcTransfer,
    91  		Args: []any{addrB, w3.I("10 ether")},
    92  	}); err != nil {
    93  		// ...
    94  	}
    95  
    96  	// Print WETH balance
    97  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balA); err != nil {
    98  		// ...
    99  	}
   100  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrB).Returns(&balB); err != nil {
   101  		// ...
   102  	}
   103  	fmt.Printf("After transfer:\nA: %s WETH, B: %s WETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
   104  	// Output:
   105  	// Before transfer:
   106  	// A: 100 WETH, B: 0 WETH
   107  	// After transfer:
   108  	// A: 90 WETH, B: 10 WETH
   109  }
   110  
   111  // Execute an ERC20 balanceOf call with raw a [w3types.Message] using the
   112  // messages Func and Args helper.
   113  func ExampleVM_call() {
   114  	vm, err := w3vm.New(
   115  		w3vm.WithFork(client, nil),
   116  		w3vm.WithState(w3types.State{
   117  			addrWETH: {Storage: w3types.Storage{
   118  				w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
   119  			}},
   120  		}),
   121  	)
   122  	if err != nil {
   123  		// ...
   124  	}
   125  
   126  	receipt, err := vm.Call(&w3types.Message{
   127  		To:   &addrWETH,
   128  		Func: funcBalanceOf,
   129  		Args: []any{addrA},
   130  	})
   131  	if err != nil {
   132  		// ...
   133  	}
   134  
   135  	var balance *big.Int
   136  	if err := receipt.DecodeReturns(&balance); err != nil {
   137  		// ...
   138  	}
   139  	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
   140  	// Output:
   141  	// Balance: 100 WETH
   142  }
   143  
   144  // Execute an ERC20 balanceOf call using the [VM.CallFunc] helper.
   145  func ExampleVM_callFunc() {
   146  	vm, err := w3vm.New(
   147  		w3vm.WithFork(client, nil),
   148  		w3vm.WithState(w3types.State{
   149  			addrWETH: {Storage: w3types.Storage{
   150  				w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
   151  			}},
   152  		}),
   153  	)
   154  	if err != nil {
   155  		// ...
   156  	}
   157  
   158  	var balance *big.Int
   159  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balance); err != nil {
   160  		// ...
   161  	}
   162  	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
   163  	// Output:
   164  	// Balance: 100 WETH
   165  }
   166  
   167  // Execute an Uniswap V3 swap.
   168  func ExampleVM_uniswapV3Swap() {
   169  	var (
   170  		addrRouter = w3.A("0xE592427A0AEce92De3Edee1F18E0157C05861564")
   171  		addrUNI    = w3.A("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984")
   172  
   173  		funcExactInput = w3.MustNewFunc(`exactInput(
   174  			(
   175  				bytes path,
   176  				address recipient,
   177  				uint256 deadline,
   178  				uint256 amountIn,
   179  				uint256 amountOutMinimum
   180  			) params
   181  		)`, "uint256 amountOut")
   182  	)
   183  
   184  	// mapping for the exactInput-function params-tuple
   185  	type ExactInputParams struct {
   186  		Path             []byte
   187  		Recipient        common.Address
   188  		Deadline         *big.Int
   189  		AmountIn         *big.Int
   190  		AmountOutMinimum *big.Int
   191  	}
   192  
   193  	encodePath := func(tokenA, tokenB common.Address, fee uint32) []byte {
   194  		path := make([]byte, 43)
   195  		copy(path, tokenA[:])
   196  		path[20], path[21], path[22] = byte(fee>>16), byte(fee>>8), byte(fee)
   197  		copy(path[23:], tokenB[:])
   198  		return path
   199  	}
   200  
   201  	// 1. Create a VM that forks the Mainnet state from the latest block,
   202  	// disables the base fee, and has a fake WETH balance and approval for the router
   203  	vm, err := w3vm.New(
   204  		w3vm.WithFork(client, big.NewInt(20_000_000)),
   205  		w3vm.WithNoBaseFee(),
   206  		w3vm.WithState(w3types.State{
   207  			addrWETH: {Storage: w3types.Storage{
   208  				w3vm.WETHBalanceSlot(addrA):               common.BigToHash(w3.I("1 ether")),
   209  				w3vm.WETHAllowanceSlot(addrA, addrRouter): common.BigToHash(w3.I("1 ether")),
   210  			}},
   211  		}),
   212  	)
   213  	if err != nil {
   214  		// ...
   215  	}
   216  
   217  	// 2. Simulate a Uniswap v3 swap
   218  	receipt, err := vm.Apply(&w3types.Message{
   219  		From: addrA,
   220  		To:   &addrRouter,
   221  		Func: funcExactInput,
   222  		Args: []any{&ExactInputParams{
   223  			Path:             encodePath(addrWETH, addrUNI, 500),
   224  			Recipient:        addrA,
   225  			Deadline:         big.NewInt(time.Now().Unix()),
   226  			AmountIn:         w3.I("1 ether"),
   227  			AmountOutMinimum: w3.Big0,
   228  		}},
   229  	})
   230  	if err != nil {
   231  		// ...
   232  	}
   233  
   234  	// 3. Decode output amount
   235  	var amountOut *big.Int
   236  	if err := receipt.DecodeReturns(&amountOut); err != nil {
   237  		// ...
   238  	}
   239  
   240  	fmt.Printf("AmountOut: %s UNI\n", w3.FromWei(amountOut, 18))
   241  	// Output:
   242  	// AmountOut: 278.327327986946583271 UNI
   243  }
   244  
   245  // Execute a message sent from the zero address.
   246  // The [w3types.Message] sender can be freely chosen, making it possible to
   247  // execute a message from any address.
   248  func ExampleVM_prankZeroAddress() {
   249  	vm, err := w3vm.New(
   250  		w3vm.WithFork(client, big.NewInt(20_000_000)),
   251  		w3vm.WithNoBaseFee(),
   252  	)
   253  	if err != nil {
   254  		// ...
   255  	}
   256  
   257  	_, err = vm.Apply(&w3types.Message{
   258  		From:  w3.Addr0,
   259  		To:    &addrA,
   260  		Value: w3.I("1.234 ether"),
   261  	})
   262  	if err != nil {
   263  		// ...
   264  	}
   265  
   266  	balance, err := vm.Balance(addrA)
   267  	if err != nil {
   268  		// ...
   269  	}
   270  
   271  	fmt.Printf("Received %s ETH from zero address\n", w3.FromWei(balance, 18))
   272  	// Output:
   273  	// Received 1.234 ETH from zero address
   274  }
   275  
   276  // Trace calls (and opcodes) of a transaction.
   277  func ExampleVM_traceCalls() {
   278  	txHash := w3.H("0xc0679fedfe8d7c376d599cbab03de7b527347a3d135d7d8d698047f34a6611f8")
   279  
   280  	var (
   281  		tx      *types.Transaction
   282  		receipt *types.Receipt
   283  	)
   284  	if err := client.Call(
   285  		eth.Tx(txHash).Returns(&tx),
   286  		eth.TxReceipt(txHash).Returns(&receipt),
   287  	); err != nil {
   288  		// ...
   289  	}
   290  
   291  	vm, err := w3vm.New(
   292  		w3vm.WithFork(client, receipt.BlockNumber),
   293  	)
   294  	if err != nil {
   295  		// ...
   296  	}
   297  
   298  	callTracer := hooks.NewCallTracer(os.Stdout, &hooks.CallTracerOptions{
   299  		ShowStaticcall: true,
   300  		DecodeABI:      true,
   301  	})
   302  	vm.ApplyTx(tx, callTracer)
   303  }
   304  
   305  // Trace a message execution to obtain the access list.
   306  func ExampleVM_traceAccessList() {
   307  	txHash := w3.H("0xbb4b3fc2b746877dce70862850602f1d19bd890ab4db47e6b7ee1da1fe578a0d")
   308  
   309  	var (
   310  		tx      *types.Transaction
   311  		receipt *types.Receipt
   312  	)
   313  	if err := client.Call(
   314  		eth.Tx(txHash).Returns(&tx),
   315  		eth.TxReceipt(txHash).Returns(&receipt),
   316  	); err != nil {
   317  		// ...
   318  	}
   319  
   320  	var header *types.Header
   321  	if err := client.Call(eth.HeaderByNumber(receipt.BlockNumber).Returns(&header)); err != nil {
   322  		// ...
   323  	}
   324  
   325  	vm, err := w3vm.New(
   326  		w3vm.WithFork(client, receipt.BlockNumber),
   327  	)
   328  	if err != nil {
   329  		// ...
   330  	}
   331  
   332  	// setup access list tracer
   333  	signer := types.MakeSigner(params.MainnetChainConfig, header.Number, header.Time)
   334  	from, _ := signer.Sender(tx)
   335  	addressesToExclude := map[common.Address]struct{}{from: {}, *tx.To(): {}}
   336  	activePrecompiles := gethVm.ActivePrecompiles(params.MainnetChainConfig.Rules(header.Number, header.Difficulty.Sign() == 0, header.Time))
   337  	for _, addr := range activePrecompiles {
   338  		addressesToExclude[addr] = struct{}{}
   339  	}
   340  
   341  	accessListTracer := logger.NewAccessListTracer(nil, addressesToExclude)
   342  	if _, err := vm.ApplyTx(tx, accessListTracer.Hooks()); err != nil {
   343  		// ...
   344  	}
   345  	fmt.Println("Access List:", accessListTracer.AccessList())
   346  }
   347  
   348  // Trace the execution of all op's in a block.
   349  func ExampleVM_traceBlock() {
   350  	blockNumber := big.NewInt(20_000_000)
   351  
   352  	var block *types.Block
   353  	if err := client.Call(eth.BlockByNumber(blockNumber).Returns(&block)); err != nil {
   354  		// ...
   355  	}
   356  
   357  	vm, err := w3vm.New(
   358  		w3vm.WithFork(client, blockNumber),
   359  	)
   360  	if err != nil {
   361  		// ...
   362  	}
   363  
   364  	var opCount [256]uint64
   365  	tracer := &tracing.Hooks{
   366  		OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
   367  			opCount[op]++
   368  		},
   369  	}
   370  
   371  	for _, tx := range block.Transactions() {
   372  		vm.ApplyTx(tx, tracer)
   373  	}
   374  
   375  	for op, count := range opCount {
   376  		if count > 0 {
   377  			fmt.Printf("0x%02x %-14s %d\n", op, gethVm.OpCode(op), count)
   378  		}
   379  	}
   380  }
   381  
   382  func TestWETHDeposit(t *testing.T) {
   383  	// setup VM
   384  	vm, _ := w3vm.New(
   385  		w3vm.WithState(w3types.State{
   386  			addrWETH: {Code: codeWETH},
   387  			addrA:    {Balance: w3.I("1 ether")},
   388  		}),
   389  	)
   390  
   391  	// pre check
   392  	var wethBalanceBefore *big.Int
   393  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&wethBalanceBefore); err != nil {
   394  		t.Fatal(err)
   395  	}
   396  	if wethBalanceBefore.Sign() != 0 {
   397  		t.Fatal("Invalid WETH balance: want 0")
   398  	}
   399  
   400  	// deposit (via fallback)
   401  	if _, err := vm.Apply(&w3types.Message{
   402  		From:  addrA,
   403  		To:    &addrWETH,
   404  		Value: w3.I("1 ether"),
   405  	}); err != nil {
   406  		t.Fatalf("Deposit failed: %v", err)
   407  	}
   408  
   409  	// post check
   410  	var wethBalanceAfter *big.Int
   411  	if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&wethBalanceAfter); err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	if w3.I("1 ether").Cmp(wethBalanceAfter) != 0 {
   415  		t.Fatalf("Invalid WETH balance: want 1")
   416  	}
   417  }
   418  
   419  func FuzzWETHDeposit(f *testing.F) {
   420  	f.Add([]byte{1})
   421  	f.Fuzz(func(t *testing.T, amountBytes []byte) {
   422  		if len(amountBytes) > 32 {
   423  			t.Skip()
   424  		}
   425  		amount := new(big.Int).SetBytes(amountBytes)
   426  
   427  		// setup VM
   428  		vm, _ := w3vm.New(
   429  			w3vm.WithState(w3types.State{
   430  				addrWETH: {Code: codeWETH},
   431  				addrA:    {Balance: w3.BigMaxUint256},
   432  			}),
   433  		)
   434  
   435  		// Pre-check WETH balance
   436  		var wethBalanceBefore *big.Int
   437  		if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&wethBalanceBefore); err != nil {
   438  			t.Fatal(err)
   439  		}
   440  
   441  		// Attempt deposit
   442  		vm.Apply(&w3types.Message{
   443  			From:  addrA,
   444  			To:    &addrWETH,
   445  			Value: amount,
   446  		})
   447  
   448  		// Post-check WETH balance
   449  		var wethBalanceAfter *big.Int
   450  		if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&wethBalanceAfter); err != nil {
   451  			t.Fatal(err)
   452  		}
   453  
   454  		// Verify balance increment
   455  		wantBalance := new(big.Int).Add(wethBalanceBefore, amount)
   456  		if wethBalanceAfter.Cmp(wantBalance) != 0 {
   457  			t.Fatalf("Invalid WETH balance: want %s, got %s", wantBalance, wethBalanceAfter)
   458  		}
   459  	})
   460  }
   461  
   462  func ExampleWETHBalanceSlot() {
   463  	addrC0fe := w3.A("0x000000000000000000000000000000000000c0Fe")
   464  	addrWETH := w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   465  	funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")
   466  
   467  	vm, err := w3vm.New(
   468  		w3vm.WithFork(client, nil),
   469  		w3vm.WithState(w3types.State{
   470  			addrWETH: {
   471  				Storage: w3types.Storage{
   472  					w3vm.WETHBalanceSlot(addrC0fe): common.BigToHash(w3.I("100 ether")),
   473  				},
   474  			},
   475  		}),
   476  	)
   477  	if err != nil {
   478  		// ...
   479  	}
   480  
   481  	var balance *big.Int
   482  	err = vm.CallFunc(addrWETH, funcBalanceOf, addrC0fe).Returns(&balance)
   483  	if err != nil {
   484  		// ...
   485  	}
   486  	fmt.Printf("%s: %s WETH", addrC0fe, w3.FromWei(balance, 18))
   487  	// Output:
   488  	// 0x000000000000000000000000000000000000c0Fe: 100 WETH
   489  }