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 }