From 66a14421eb3ccb4f372f3d862932ebceb233b018 Mon Sep 17 00:00:00 2001 From: lmittmann <3458786+lmittmann@users.noreply.github.com> Date: Sun, 31 Mar 2024 14:54:47 +0200 Subject: [PATCH] Updated Readme (#126) * updated intro * use non breaking hyphen * updated RPC section * added VM section * changed code ident * added links * updated client example * added VM example * updated spacing * small fix * updated VM example * . * dropped g1.22 function * . * . * tabs to spaces * renamed and linked `NewFunc` example * highlight examples * added UniswapV4Swap ABI-example * ~~s~~ * added "utils" section * abi-section: updated example description * added detailed rpc sections * w3types: added `RPCCaller` example * added content for "Custom RPC Method Bindings"-section * added example code * dropped old text * w3types: improved example doc * w3vm: added `ExampleVM_Call` * added client example * use non breaking spaces in links * small fix * `map[common.Hash]common.Hash` -> `w3types.Storage` * docs update * small css update * dep update --------- Co-authored-by: lmittmann --- README.md | 279 ++++++++++++++++++---------------- client_test.go | 64 +++++++- docs/next.config.js | 5 +- docs/package.json | 14 +- docs/pages/_meta.js | 22 +++ docs/pages/_meta.json | 18 --- docs/pages/abi.mdx | 80 ---------- docs/pages/index.mdx | 297 ++++++++++++++++++++----------------- docs/pages/rpc.mdx | 8 - docs/pages/style.css | 2 +- docs/pages/vm.mdx | 8 - docs/public/favicon.ico | Bin 0 -> 33310 bytes docs/theme.config.jsx | 32 ++-- func_test.go | 46 +++++- w3types/interfaces_test.go | 78 ++++++++++ w3vm/vm_test.go | 112 ++++++++++++++ 16 files changed, 650 insertions(+), 415 deletions(-) create mode 100644 docs/pages/_meta.js delete mode 100644 docs/pages/_meta.json delete mode 100644 docs/pages/abi.mdx delete mode 100644 docs/pages/rpc.mdx delete mode 100644 docs/pages/vm.mdx create mode 100644 docs/public/favicon.ico create mode 100644 w3types/interfaces_test.go diff --git a/README.md b/README.md index 9ba2545..fc68864 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,154 @@ -# w3 +# `w3`: Enhanced Ethereum Integration for Go [![Go Reference](https://pkg.go.dev/badge/github.com/lmittmann/w3.svg)](https://pkg.go.dev/github.com/lmittmann/w3) [![Go Report Card](https://goreportcard.com/badge/github.com/lmittmann/w3)](https://goreportcard.com/report/github.com/lmittmann/w3) [![Coverage Status](https://coveralls.io/repos/github/lmittmann/w3/badge.svg?branch=main)](https://coveralls.io/github/lmittmann/w3?branch=main) [![Latest Release](https://img.shields.io/github/v/release/lmittmann/w3)](https://github.com/lmittmann/w3/releases) +W3 Gopher -W3 Gopher +`w3` is your toolbelt for integrating with Ethereum in Go. Closely linked to `go‑ethereum`, it provides an ergonomic wrapper for working with **RPC**, **ABI's**, and the **EVM**. -Package `w3` implements a blazing fast and modular Ethereum JSON RPC client with -first-class ABI support. - -* **Batch request** support significantly reduces the duration of requests to - both remote and local endpoints. -* **ABI** bindings are specified for individual functions using Solidity syntax. - No need for `abigen` and ABI JSON files. -* **Modular** API allows to create custom RPC method integrations that can be - used alongside the methods implemented by the package. - -`w3` is closely linked to [go-ethereum](https://github.com/ethereum/go-ethereum) -and uses a variety of its types, such as [`common.Address`](https://pkg.go.dev/github.com/ethereum/go-ethereum/common#Address) -or [`types.Transaction`](https://pkg.go.dev/github.com/ethereum/go-ethereum/core/types#Transaction). - -Batch requests with `w3` are up to **85x faster** than sequential requests with -`go-ethereum/ethclient`. - -
-Benchmarks -
-name               ethclient time/op  w3 time/op  delta
-Call_BalanceNonce  78.3ms ± 2%        39.0ms ± 1%  -50.15%  (p=0.000 n=23+22)
-Call_Balance100     3.90s ± 5%         0.05s ± 2%  -98.84%  (p=0.000 n=20+24)
-Call_BalanceOf100   3.99s ± 3%         0.05s ± 2%  -98.73%  (p=0.000 n=22+23)
-Call_Block100       6.89s ± 7%         1.94s ±11%  -71.77%  (p=0.000 n=24+23)
-
-
- -## Install ``` go get github.com/lmittmann/w3 ``` -## Getting Started - -> **Note** -> Check out the [examples](https://github.com/lmittmann/w3/tree/main/examples)! +## At a Glance -Connect to an RPC endpoint via HTTP, WebSocket, or IPC using [`Dial`](https://pkg.go.dev/github.com/lmittmann/w3#Dial) -or [`MustDial`](https://pkg.go.dev/github.com/lmittmann/w3#MustDial). +* Use `w3.Client` to connect to an RPC endpoint. The client features batch request support for up to **80x faster requests** and easy extendibility. [learn more ↗](#rpc-client) +* Use `w3vm.VM` to simulate EVM execution with optional tracing and Mainnet state forking, or test Smart Contracts. [learn more ↗](#vm) +* Use `w3.Func` and `w3.Event` to create ABI bindings from Solidity function and event signatures. [learn more ↗](#abi-bindings) +* Use `w3.A`, `w3.H`, and many other utility functions to parse addresses, hashes, and other common types from strings. [learn more ↗](#utils) -```go -// Connect (or panic on error) -client := w3.MustDial("https://rpc.ankr.com/eth") -defer client.Close() -``` +## Getting Started -## Batch Requests +### RPC Client -Batch request support in the [`Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client) -allows to send multiple RPC requests in a single HTTP request. The speed gains -to remote endpoints are huge. Fetching 100 blocks in a single batch request -with `w3` is ~80x faster compared to sequential requests with `ethclient`. +[`w3.Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client) is a batch request focused RPC client that can be used to connect to an Ethereum node via HTTP, WebSocket, or IPC. Its modular API allows to create custom RPC method integrations that can be used alongside the common methods implemented by this package. -Example: Request the nonce and balance of an address in a single request +**Example:** Batch Request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client)) ```go -var ( - addr = w3.A("0x000000000000000000000000000000000000c0Fe") +// 1. Connect to an RPC endpoint +client, err := w3.Dial("https://rpc.ankr.com/eth") +if err != nil { + // handle error +} +defer client.Close() - nonce uint64 - balance big.Int -) -err := client.Call( - eth.Nonce(addr, nil).Returns(&nonce), - eth.Balance(addr, nil).Returns(&balance), +// 2. Make a batch request +var ( + balance big.Int + nonce uint64 ) +if err := client.Call( + eth.Balance(addr, nil).Returns(&balance), + eth.Nonce(addr, nil).Returns(&nonce), +); err != nil { + // handle error +} ``` +> [!NOTE] +> #### why send batch requests? +> Most of the time you need to call multiple RPC methods to get the data you need. When you make separate requests per RPC call you need a single round trip to the server for each call. This can be slow, especially for remote endpoints. Batching multiple RPC calls into a single request only requires a single round trip, and speeds up RPC calls significantly. -## ABI Bindings +#### Learn More +* List of supported [**RPC methods**](#rpc-methods) +* Learn how to create [**custom RPC method bindings**](#custom-rpc-method-bindings) -ABI bindings in `w3` are specified for individual functions using Solidity -syntax and are usable for any contract that supports that function. +### VM -Example: ABI binding for the ERC20-function `balanceOf` +[`w3vm.VM`](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#VM) is a high-level EVM environment with a simple but powerful API to simulate EVM execution, test Smart Contracts, or trace transactions. It supports Mainnet state forking via RPC and state caching for faster testing. -```go -funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256") -``` - -A [`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to - -* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)), -* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and -* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)). - -### Reading Contracts - -[`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func)'s can be used with -[`eth.CallFunc`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth#CallFunc) -in the client to read contract data. +**Example:** Simulate an Uniswap v3 swap ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM)) ```go -var ( - weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") - dai = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F") - - weth9Balance big.Int - daiBalance big.Int -) - -err := client.Call( - eth.CallFunc(weth9, funcBalanceOf, addr).Returns(&weth9Balance), - eth.CallFunc(dai, funcBalanceOf, addr).Returns(&daiBalance), +// 1. Create a VM that forks the Mainnet state from the latest block, +// disables the base fee, and has a fake WETH balance and approval for the router +vm, err := w3vm.New( + w3vm.WithFork(client, nil), + w3vm.WithNoBaseFee(), + w3vm.WithState(w3types.State{ + addrWETH: {Storage: w3types.Storage{ + w3vm.WETHBalanceSlot(addrEOA): common.BigToHash(w3.I("1 ether")), + w3vm.WETHAllowanceSlot(addrEOA, addrRouter): common.BigToHash(w3.I("1 ether")), + }}, + }), ) +if err != nil { + // handle error +} + +// 2. Simulate a Uniswap v3 swap +receipt, err := vm.Apply(&w3types.Message{ + From: addrEOA, + To: &addrRouter, + Func: funcExactInput, + Args: []any{&ExactInputParams{ + Path: encodePath(addrWETH, 500, addrUNI), + Recipient: addrEOA, + Deadline: big.NewInt(time.Now().Unix()), + AmountIn: w3.I("1 ether"), + AmountOutMinimum: w3.Big0, + }}, +}) +if err != nil { + // handle error +} + +// 3. Decode output amount +var amountOut *big.Int +if err := receipt.DecodeReturns(&amountOut); err != nil { + // handle error +} ``` -### Writing Contracts - -Sending a transaction to a contract requires three steps. - -1. Encode the transaction input data using [`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs). - -```go -var funcTransfer = w3.MustNewFunc("transfer(address,uint256)", "bool") +### ABI Bindings -input, err := funcTransfer.EncodeArgs(w3.A("0x…"), w3.I("1 ether")) -``` +ABI bindings in `w3` are specified for individual functions using Solidity syntax and are usable for any contract that supports that function. -2. Create a signed transaction to the contract using [go-ethereum/types](https://github.com/ethereum/go-ethereum). +**Example:** ABI binding for the ERC20 `balanceOf` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-BalanceOf)) ```go -signer := types.LatestSigner(params.MainnetChainConfig) -tx := types.MustSignNewTx(privKey, signer, &types.DynamicFeeTx{ - To: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - Nonce: 0, - Data: input, - Gas: 75000, - GasFeeCap: w3.I("100 gwei"), - GasTipCap: w3.I("1 gwei"), -}) +funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256") ``` -3. Send the signed transaction. +**Example:** ABI binding for the Uniswap v4 `swap` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-UniswapV4Swap)) ```go -var txHash common.Hash -err := client.Call( - eth.SendTx(tx).Returns(&txHash), -) +funcSwap := w3.MustNewFunc(`swap( + (address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, + (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params, + bytes hookData +)`, "int256 delta") ``` +A [`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to -## Custom RPC Methods - -Custom RPC methods can be called with the `w3` client by creating a -[`w3types.Caller`](https://pkg.go.dev/github.com/lmittmann/w3/w3types#Caller) -implementation. -The `w3/module/eth` package can be used as implementation reference. - +* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)), +* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and +* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)). -## Utils +### Utils -Static addresses, hashes, hex byte slices or `big.Int`'s can be parsed from -strings with the following utility functions. +Static addresses, hashes, bytes or integers can be parsed from (hex-)strings with the following utility functions that panic if the string is not valid. ```go -var ( - addr = w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") - hash = w3.H("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - bytes = w3.B("0x27c5342c") - big = w3.I("12.34 ether") -) +addr := w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") +hash := w3.H("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") +bytes := w3.B("0x27c5342c") +amount := w3.I("12.34 ether") ``` -Note that these functions panic if the string cannot be parsed. Use -[go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common) -to parse strings that may not be valid instead. +Use [go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common) to parse strings that may not be valid instead. ## RPC Methods -List of supported RPC methods. +List of supported RPC methods for [`w3.Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client). ### [`eth`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth) @@ -242,3 +207,55 @@ List of supported RPC methods. | Package | Description | :----------------------------------------------------------------------- | :----------- | [github.com/lmittmann/flashbots](https://github.com/lmittmann/flashbots) | Package `flashbots` implements RPC API bindings for the Flashbots relay and mev-geth. + + +## Custom RPC Method Bindings + +Custom RPC method bindings can be created by implementing the [`w3types.RPCCaller`](https://pkg.go.dev/github.com/lmittmann/w3/w3types#RPCCaller) interface. + +**Example:** RPC binding for the Otterscan `ots_getTransactionBySenderAndNonce` method ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3types#example-RPCCaller-GetTransactionBySenderAndNonce)) + +```go +// TxBySenderAndNonceFactory requests the senders transaction hash by the nonce. +func TxBySenderAndNonceFactory(sender common.Address, nonce uint64) w3types.RPCCallerFactory[common.Hash] { + return &getTransactionBySenderAndNonceFactory{ + sender: sender, + nonce: nonce, + } +} + +// getTransactionBySenderAndNonceFactory implements the w3types.RPCCaller and +// w3types.RPCCallerFactory interfaces. It stores the method parameters and +// the the reference to the return value. +type getTransactionBySenderAndNonceFactory struct { + // params + sender common.Address + nonce uint64 + + // returns + returns *common.Hash +} + +// Returns sets the reference to the return value. +func (f *getTransactionBySenderAndNonceFactory) Returns(txHash *common.Hash) w3types.RPCCaller { + f.returns = txHash + return f +} + +// CreateRequest creates a batch request element for the Otterscan getTransactionBySenderAndNonce method. +func (f *getTransactionBySenderAndNonceFactory) CreateRequest() (rpc.BatchElem, error) { + return rpc.BatchElem{ + Method: "ots_getTransactionBySenderAndNonce", + Args: []any{f.sender, f.nonce}, + Result: f.returns, + }, nil +} + +// HandleResponse handles the response of the Otterscan getTransactionBySenderAndNonce method. +func (f *getTransactionBySenderAndNonceFactory) HandleResponse(elem rpc.BatchElem) error { + if err := elem.Error; err != nil { + return err + } + return nil +} +``` diff --git a/client_test.go b/client_test.go index eabb4df..39be83e 100644 --- a/client_test.go +++ b/client_test.go @@ -14,7 +14,9 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/google/go-cmp/cmp" "github.com/lmittmann/w3" @@ -34,20 +36,32 @@ var ( `< [{"jsonrpc":"2.0","id":1,"result":"0x1"},{"jsonrpc":"2.0","id":2,"result":"0x1"}]` ) -func ExampleDial() { +func ExampleClient() { + addr := w3.A("0x0000000000000000000000000000000000000000") + + // 1. Connect to an RPC endpoint client, err := w3.Dial("https://rpc.ankr.com/eth") if err != nil { - // ... + // handle error } defer client.Close() -} -func ExampleMustDial() { - client := w3.MustDial("https://rpc.ankr.com/eth") - defer client.Close() + // 2. Make a batch request + var ( + balance big.Int + nonce uint64 + ) + if err := client.Call( + eth.Balance(addr, nil).Returns(&balance), + eth.Nonce(addr, nil).Returns(&nonce), + ); err != nil { + // handle error + } + + fmt.Printf("balance: %s\nnonce: %d\n", w3.FromWei(&balance, 18), nonce) } -func ExampleClient_Call() { +func ExampleClient_Call_balanceOf() { // Connect to RPC endpoint (or panic on error) and // close the connection when you are done. client := w3.MustDial("https://rpc.ankr.com/eth") @@ -103,6 +117,42 @@ func ExampleClient_Call_nonceAndBalance() { fmt.Printf("%s: Nonce: %d, Balance: ♦%s\n", addr, nonce, w3.FromWei(&balance, 18)) } +func ExampleClient_Call_sendERC20transferTx() { + client := w3.MustDial("https://rpc.ankr.com/eth") + defer client.Close() + + var ( + weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + receiver = w3.A("0x000000000000000000000000000000000000c0Fe") + eoaPrv, _ = crypto.GenerateKey() + ) + + funcTransfer := w3.MustNewFunc("transfer(address receiver, uint256 amount)", "bool") + input, err := funcTransfer.EncodeArgs(receiver, w3.I("1 ether")) + if err != nil { + fmt.Printf("Failed to encode args: %v\n", err) + return + } + + signer := types.LatestSigner(params.MainnetChainConfig) + var txHash common.Hash + if err := client.Call( + eth.SendTx(types.MustSignNewTx(eoaPrv, signer, &types.DynamicFeeTx{ + Nonce: 0, + To: &weth9, + Data: input, + GasTipCap: w3.I("1 gwei"), + GasFeeCap: w3.I("100 gwei"), + Gas: 100_000, + })).Returns(&txHash), + ); err != nil { + fmt.Printf("Failed to send tx: %v\n", err) + return + } + + fmt.Printf("Sent tx: %s\n", txHash) +} + func TestClientCall(t *testing.T) { tests := []struct { Buf *bytes.Buffer diff --git a/docs/next.config.js b/docs/next.config.js index e6bfeef..5817c7f 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -4,13 +4,14 @@ const withNextra = nextra({ theme: 'nextra-theme-docs', themeConfig: './theme.config.jsx', staticImage: true, - flexsearch: { - codeblocks: false + search: { + codeblocks: true }, defaultShowCopyCode: true }) export default withNextra({ + output: 'export', reactStrictMode: true, images: { unoptimized: true, diff --git a/docs/package.json b/docs/package.json index ea1bc57..ef06d9a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,18 +2,18 @@ "type": "module", "scripts": { "dev": "next dev", - "build": "next build && next export" + "build": "next build" }, "dependencies": { - "next": "^13.4.19", - "nextra": "^2.12.3", - "nextra-theme-docs": "^2.12.3", + "next": "^14.1.4", + "nextra": "3.0.0-alpha.22", + "nextra-theme-docs": "3.0.0-alpha.22", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { - "autoprefixer": "^10.4.15", - "postcss": "^8.4.29", - "tailwindcss": "^3.3.3" + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3" } } diff --git a/docs/pages/_meta.js b/docs/pages/_meta.js new file mode 100644 index 0000000..757dd35 --- /dev/null +++ b/docs/pages/_meta.js @@ -0,0 +1,22 @@ +export default { + index: { + title: 'Introduction', + theme: { + sidebar: false, + toc: true, + breadcrumb: false, + }, + }, + examples: { + title: 'Examples ↗', + type: 'page', + href: '/examples', + newWindow: true + }, + godoc: { + title: 'GoDoc ↗', + type: 'page', + href: 'https://pkg.go.dev/github.com/lmittmann/w3#section-documentation', + newWindow: true + }, +} diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json deleted file mode 100644 index a76b623..0000000 --- a/docs/pages/_meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "examples": { - "title": "Examples ↗", - "type": "page", - "href": "/examples", - "newWindow": true - }, - "godoc": { - "title": "GoDoc ↗", - "type": "page", - "href": "https://pkg.go.dev/github.com/lmittmann/w3#section-documentation", - "newWindow": true - }, - "index": { - "type": "page", - "display": "hidden" - } -} diff --git a/docs/pages/abi.mdx b/docs/pages/abi.mdx deleted file mode 100644 index b8099ca..0000000 --- a/docs/pages/abi.mdx +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: ABI Bindings -draft: true ---- - -# ABI - -ABI bindings allow the encoding and decoding of Smart Contract function calls or the decoding of events. -In `w3` ABI bindings are defined for individual functions or events at runtime using Solidity syntax. - -* **Easy to write:** Creating an ABI binding only requires the Solidity function signature. No need - to firstly generate an ABI json file using `solc` and secondly generate ABI bindings using `abigen`. -* **Flexible:** ABI bindings for a function or event can be used with any Smart Contract. No need to - generate overlapping bindings for multiple Smart Contracts. - - -## Functions - -Function ABI bindings can be defined using -* `func NewFunc(signature, returns string) (*Func, error)`, or -* `func MustNewFunc(signature, returns string) *Func` which panics on error. - -### Examples - -#### ERC20 `transfer` - -The ERC20 `transfer` function can be defined as follows: -```go -funcTransfer := w3.MustNewFunc("transfer(address,uint256)", "bool") -``` - -Alternatively the function arguments and returns can also be named. Note, that Solidity type aliases -are also supported (e.g. `uint` for `uint256`): -```go -funcTransfer := w3.MustNewFunc("transfer(address to, uint amount)", "bool success") -``` - -#### Tuples - -The UniSwap V3 [`IQuoterV2.sol`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/IQuoterV2.sol#L27-L53) -uses a tuple as argument for e.g. `quoteExactInputSingle`, which can be defined as follows: - -```go -funcQuoteExactInputSingle := w3.MustNewFunc( - `quoteExactInputSingle(( - address tokenIn, - address tokenOut, - uint256 amountIn, - uint24 fee, - uint160 sqrtPriceLimitX96 - ) params)`, - `uint256 amountOut, - uint160 sqrtPriceX96After, - uint32 initializedTicksCrossed, - uint256 gasEstimate`, -) -``` - -Note, that the Solidity struct arguments are wrapped in parenthesis and names are mandatory. - -### `EncodeArgs` - -### `DecodeArgs` and `DecodeReturns` - - - - -## Events - -Event ABI bindings can be defined using -* `func NewEvent(signature string) (*Event, error)`, or -* `func MustNewEvent(signature string) *Event` which panics on error. - -### Examples - -#### ERC20 `Transfer` - -```go -evtTransfer := w3.MustNewEvent("Transfer(address indexed from, address indexed to, uint256 value)") -``` diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index a9b6708..b7c4b25 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -1,12 +1,11 @@ --- -title: Introduction -theme: - breadcrumb: false +description: 'w3: Enhanced Ethereum Integration for Go' --- import Image from 'next/image' +import { Callout } from 'nextra/components' -# w3 +# `w3`: Enhanced Ethereum Integration for Go
@@ -23,199 +22,169 @@ import Image from 'next/image'
-Hello +Hello -Package `w3` implements a blazing fast and modular Ethereum JSON RPC client with -first-class ABI support. +`w3` is your toolbelt for integrating with Ethereum in Go. Closely linked to `go‑ethereum`, it provides an ergonomic wrapper for working with **RPC**, **ABI's**, and the **EVM**. -* **Batch request** support significantly reduces the duration of requests to - both remote and local endpoints. -* **ABI** bindings are specified for individual functions using Solidity syntax. - No need for `abigen` and ABI JSON files. -* **Modular** API allows to create custom RPC method integrations that can be - used alongside the methods implemented by the package. - -`w3` is closely linked to [go-ethereum](https://github.com/ethereum/go-ethereum) -and uses a variety of its types, such as [`common.Address`](https://pkg.go.dev/github.com/ethereum/go-ethereum/common#Address) -or [`types.Transaction`](https://pkg.go.dev/github.com/ethereum/go-ethereum/core/types#Transaction). - -Batch requests with `w3` are up to **85x faster** than sequential requests with -`go-ethereum/ethclient`. - -
-Benchmarks -
-name               ethclient time/op  w3 time/op  delta
-Call_BalanceNonce  78.3ms ± 2%        39.0ms ± 1%  -50.15%  (p=0.000 n=23+22)
-Call_Balance100     3.90s ± 5%         0.05s ± 2%  -98.84%  (p=0.000 n=20+24)
-Call_BalanceOf100   3.99s ± 3%         0.05s ± 2%  -98.73%  (p=0.000 n=22+23)
-Call_Block100       6.89s ± 7%         1.94s ±11%  -71.77%  (p=0.000 n=24+23)
-
-
- -## Install ``` go get github.com/lmittmann/w3 ``` -## Getting Started +## At a Glance -> **Note** -> Check out the [examples](examples/)! +* Use `w3.Client` to connect to an RPC endpoint. The client features batch request support for up to **80x faster requests** and easy extendibility. [learn more ↗](#rpc-client) +* Use `w3vm.VM` to simulate EVM execution with optional tracing and Mainnet state forking, or test Smart Contracts. [learn more ↗](#vm) +* Use `w3.Func` and `w3.Event` to create ABI bindings from Solidity function and event signatures. [learn more ↗](#abi-bindings) +* Use `w3.A`, `w3.H`, and many other utility functions to parse addresses, hashes, and other common types from strings. [learn more ↗](#utils) -Connect to an RPC endpoint via HTTP, WebSocket, or IPC using [`Dial`](https://pkg.go.dev/github.com/lmittmann/w3#Dial) -or [`MustDial`](https://pkg.go.dev/github.com/lmittmann/w3#MustDial). - -```go -// Connect (or panic on error) -client := w3.MustDial("https://rpc.ankr.com/eth") -defer client.Close() -``` +## Getting Started -## Batch Requests +### RPC Client -Batch request support in the [`Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client) -allows to send multiple RPC requests in a single HTTP request. The speed gains -to remote endpoints are huge. Fetching 100 blocks in a single batch request -with `w3` is ~80x faster compared to sequential requests with `ethclient`. +[`w3.Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client) is a batch request focused RPC client that can be used to connect to an Ethereum node via HTTP, WebSocket, or IPC. Its modular API allows to create custom RPC method integrations that can be used alongside the common methods implemented by this package. -Example: Request the nonce and balance of an address in a single request +**Example:** Batch Request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client)) ```go -var ( - addr = w3.A("0x000000000000000000000000000000000000c0Fe") +// 1. Connect to an RPC endpoint +client, err := w3.Dial("https://rpc.ankr.com/eth") +if err != nil { + // handle error +} +defer client.Close() - nonce uint64 - balance big.Int -) -err := client.Call( - eth.Nonce(addr, nil).Returns(&nonce), - eth.Balance(addr, nil).Returns(&balance), +// 2. Make a batch request +var ( + balance big.Int + nonce uint64 ) +if err := client.Call( + eth.Balance(addr, nil).Returns(&balance), + eth.Nonce(addr, nil).Returns(&nonce), +); err != nil { + // handle error +} ``` -## ABI Bindings + + #### why send batch requests? + Most of the time you need to call multiple RPC methods to get the data you need. When you make separate requests per RPC call you need a single round trip to the server for each call. This can be slow, especially for remote endpoints. Batching multiple RPC calls into a single request only requires a single round trip, and speeds up RPC calls significantly. + -ABI bindings in `w3` are specified for individual functions using Solidity -syntax and are usable for any contract that supports that function. +#### Learn More +* List of supported [**RPC methods**](#rpc-methods) +* Learn how to create [**custom RPC method bindings**](#custom-rpc-method-bindings) -Example: ABI binding for the ERC20-function `balanceOf` +### VM -```go -funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256") -``` +[`w3vm.VM`](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#VM) is a high-level EVM environment with a simple but powerful API to simulate EVM execution, test Smart Contracts, or trace transactions. It supports Mainnet state forking via RPC and state caching for faster testing. -A [`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to - -* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)), -* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and -* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)). - -### Reading Contracts - -[`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func)'s can be used with -[`eth.CallFunc`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth#CallFunc) -in the client to read contract data. +**Example:** Simulate an Uniswap v3 swap ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM)) ```go -var ( - weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") - dai = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F") - - weth9Balance big.Int - daiBalance big.Int -) - -err := client.Call( - eth.CallFunc(funcBalanceOf, weth9, addr).Returns(&weth9Balance), - eth.CallFunc(funcBalanceOf, dai, addr).Returns(&daiBalance), +// 1. Create a VM that forks the Mainnet state from the latest block, +// disables the base fee, and has a fake WETH balance and approval for the router +vm, err := w3vm.New( + w3vm.WithFork(client, nil), + w3vm.WithNoBaseFee(), + w3vm.WithState(w3types.State{ + addrWETH: {Storage: w3types.Storage{ + w3vm.WETHBalanceSlot(addrEOA): common.BigToHash(w3.I("1 ether")), + w3vm.WETHAllowanceSlot(addrEOA, addrRouter): common.BigToHash(w3.I("1 ether")), + }}, + }), ) +if err != nil { + // handle error +} + +// 2. Simulate a Uniswap v3 swap +receipt, err := vm.Apply(&w3types.Message{ + From: addrEOA, + To: &addrRouter, + Func: funcExactInput, + Args: []any{&ExactInputParams{ + Path: encodePath(addrWETH, 500, addrUNI), + Recipient: addrEOA, + Deadline: big.NewInt(time.Now().Unix()), + AmountIn: w3.I("1 ether"), + AmountOutMinimum: w3.Big0, + }}, +}) +if err != nil { + // handle error +} + +// 3. Decode output amount +var amountOut *big.Int +if err := receipt.DecodeReturns(&amountOut); err != nil { + // handle error +} ``` -### Writing Contracts +### ABI Bindings -Sending a transaction to a contract requires three steps. +ABI bindings in `w3` are specified for individual functions using Solidity syntax and are usable for any contract that supports that function. -1. Encode the transaction input data using [`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs). +**Example:** ABI binding for the ERC20 `balanceOf` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-BalanceOf)) ```go -var funcTransfer = w3.MustNewFunc("transfer(address,uint256)", "bool") - -input, err := funcTransfer.EncodeArgs(w3.A("0x…"), w3.I("1 ether")) -``` - -2. Create a signed transaction to the contract using [go-ethereum/core/types](https://pkg.go.dev/github.com/ethereum/go-ethereum/core/types). - -```go -signer := types.LatestSigner(params.MainnetChainConfig) -tx := types.MustSignNewTx(privKey, signer, &types.DynamicFeeTx{ - To: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - Nonce: 0, - Data: input, - Gas: 75000, - GasFeeCap: w3.I("100 gwei"), - GasTipCap: w3.I("1 gwei"), -}) +funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256") ``` -3. Send the signed transaction. +**Example:** ABI binding for the Uniswap v4 `swap` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-UniswapV4Swap)) ```go -var txHash common.Hash -err := client.Call( - eth.SendTx(tx).Returns(&txHash), -) +funcSwap := w3.MustNewFunc(`swap( + (address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, + (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params, + bytes hookData +)`, "int256 delta") ``` +A [`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to -## Custom RPC Methods - -Custom RPC methods can be called with the `w3` client by creating a -[`core.Caller`](https://pkg.go.dev/github.com/lmittmann/w3/core#Caller) -implementation. -The `w3/module/eth` package can be used as implementation reference. - +* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)), +* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and +* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)). -## Utils +### Utils -Static addresses, hashes, hex byte slices or `big.Int`'s can be parsed from -strings with the following utility functions. +Static addresses, hashes, bytes or integers can be parsed from (hex-)strings with the following utility functions that panic if the string is not valid. ```go -var ( - addr = w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") - hash = w3.H("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - bytes = w3.B("0x27c5342c") - big = w3.I("12.34 ether") -) +addr := w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") +hash := w3.H("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") +bytes := w3.B("0x27c5342c") +amount := w3.I("12.34 ether") ``` -Note that these functions panic if the string cannot be parsed. Use -[go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common) -to parse strings that may not be valid instead. +Use [go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common) to parse strings that may not be valid instead. ## RPC Methods -List of supported RPC methods. +List of supported RPC methods for [`w3.Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client). ### [`eth`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth) | Method | Go Code | :---------------------------------------- | :------- | `eth_blockNumber` | `eth.BlockNumber().Returns(blockNumber *big.Int)` -| `eth_call` | `eth.Call(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(output *[]byte)`
`eth.CallFunc(fn core.Func, contract common.Address, args ...any).Returns(returns ...any)` +| `eth_call` | `eth.Call(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(output *[]byte)`
`eth.CallFunc(contract common.Address, f w3types.Func, args ...any).Returns(returns ...any)` | `eth_chainId` | `eth.ChainID().Returns(chainID *uint64)` | `eth_createAccessList` | `eth.AccessList(msg *w3types.Message, blockNumber *big.Int).Returns(resp *eth.AccessListResponse)` | `eth_estimateGas` | `eth.EstimateGas(msg *w3types.Message, blockNumber *big.Int).Returns(gas *uint64)` | `eth_gasPrice` | `eth.GasPrice().Returns(gasPrice *big.Int)` +| `eth_maxPriorityFeePerGas` | `eth.GasTipCap().Returns(gasTipCap *big.Int)` | `eth_getBalance` | `eth.Balance(addr common.Address, blockNumber *big.Int).Returns(balance *big.Int)` -| `eth_getBlockByHash` | `eth.BlockByHash(hash common.Hash).Returns(block *types.Block)`
`eth.HeaderByHash(hash common.Hash).Returns(header *types.Header)` -| `eth_getBlockByNumber` | `eth.BlockByNumber(number *big.Int).Returns(block *types.Block)`
`eth.HeaderByNumber(number *big.Int).Returns(header *types.Header)` +| `eth_getBlockByHash` | `eth.BlockByHash(hash common.Hash).Returns(block *types.Block)`
`eth.HeaderByHash(hash common.Hash).Returns(header *types.Header)` +| `eth_getBlockByNumber` | `eth.BlockByNumber(number *big.Int).Returns(block *types.Block)`
`eth.HeaderByNumber(number *big.Int).Returns(header *types.Header)` +| `eth_getBlockReceipts` | `eth.BlockReceipts(blockNumber *big.Int).Returns(receipts *types.Receipts)` | `eth_getBlockTransactionCountByHash` | `eth.BlockTxCountByHash(hash common.Hash).Returns(count *uint)` | `eth_getBlockTransactionCountByNumber` | `eth.BlockTxCountByNumber(number *big.Int).Returns(count *uint)` | `eth_getCode` | `eth.Code(addr common.Address, blockNumber *big.Int).Returns(code *[]byte)` @@ -226,7 +195,7 @@ List of supported RPC methods. | `eth_getTransactionByBlockNumberAndIndex` | `eth.TxByBlockNumberAndIndex(blockNumber *big.Int, index uint).Returns(tx *types.Transaction)` | `eth_getTransactionCount` | `eth.Nonce(addr common.Address, blockNumber *big.Int).Returns(nonce *uint)` | `eth_getTransactionReceipt` | `eth.TxReceipt(txHash common.Hash).Returns(receipt *types.Receipt)` -| `eth_sendRawTransaction` | `eth.SendRawTx(rawTx []byte).Returns(hash *common.Hash)`
`eth.SendTx(tx *types.Transaction).Returns(hash *common.Hash)` +| `eth_sendRawTransaction` | `eth.SendRawTx(rawTx []byte).Returns(hash *common.Hash)`
`eth.SendTx(tx *types.Transaction).Returns(hash *common.Hash)` | `eth_getUncleByBlockHashAndIndex` | `eth.UncleByBlockHashAndIndex(hash common.Hash, index uint).Returns(uncle *types.Header)` | `eth_getUncleByBlockNumberAndIndex` | `eth.UncleByBlockNumberAndIndex(number *big.Int, index uint).Returns(uncle *types.Header)` | `eth_getUncleCountByBlockHash` | `eth.UncleCountByBlockHash(hash common.Hash).Returns(count *uint)` @@ -236,8 +205,8 @@ List of supported RPC methods. | Method | Go Code | :----------------------- | :------- -| `debug_traceCall` | `debug.TraceCall(msg *w3types.Message, blockNumber *big.Int, config *debug.TraceConfig).Returns(trace *debug.Trace)`
`debug.CallTraceCall(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(trace *debug.CallTrace)` -| `debug_traceTransaction` | `debug.TraceTx(txHash common.Hash, config *debug.TraceConfig).Returns(trace *debug.Trace)`
`debug.CallTraceTx(txHash common.Hash, overrides w3types.State).Returns(trace *debug.CallTrace)` +| `debug_traceCall` | `debug.TraceCall(msg *w3types.Message, blockNumber *big.Int, config *debug.TraceConfig).Returns(trace *debug.Trace)`
`debug.CallTraceCall(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(trace *debug.CallTrace)` +| `debug_traceTransaction` | `debug.TraceTx(txHash common.Hash, config *debug.TraceConfig).Returns(trace *debug.Trace)`
`debug.CallTraceTx(txHash common.Hash, overrides w3types.State).Returns(trace *debug.CallTrace)` ### [`txpool`](https://pkg.go.dev/github.com/lmittmann/w3/module/txpool) @@ -258,3 +227,55 @@ List of supported RPC methods. | Package | Description | :----------------------------------------------------------------------- | :----------- | [github.com/lmittmann/flashbots](https://github.com/lmittmann/flashbots) | Package `flashbots` implements RPC API bindings for the Flashbots relay and mev-geth. + + +## Custom RPC Method Bindings + +Custom RPC method bindings can be created by implementing the [`w3types.RPCCaller`](https://pkg.go.dev/github.com/lmittmann/w3/w3types#RPCCaller) interface. + +**Example:** RPC binding for the Otterscan `ots_getTransactionBySenderAndNonce` method ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3types#example-RPCCaller-GetTransactionBySenderAndNonce)) + +```go +// TxBySenderAndNonceFactory requests the senders transaction hash by the nonce. +func TxBySenderAndNonceFactory(sender common.Address, nonce uint64) w3types.RPCCallerFactory[common.Hash] { + return &getTransactionBySenderAndNonceFactory{ + sender: sender, + nonce: nonce, + } +} + +// getTransactionBySenderAndNonceFactory implements the w3types.RPCCaller and +// w3types.RPCCallerFactory interfaces. It stores the method parameters and +// the the reference to the return value. +type getTransactionBySenderAndNonceFactory struct { + // params + sender common.Address + nonce uint64 + + // returns + returns *common.Hash +} + +// Returns sets the reference to the return value. +func (f *getTransactionBySenderAndNonceFactory) Returns(txHash *common.Hash) w3types.RPCCaller { + f.returns = txHash + return f +} + +// CreateRequest creates a batch request element for the Otterscan getTransactionBySenderAndNonce method. +func (f *getTransactionBySenderAndNonceFactory) CreateRequest() (rpc.BatchElem, error) { + return rpc.BatchElem{ + Method: "ots_getTransactionBySenderAndNonce", + Args: []any{f.sender, f.nonce}, + Result: f.returns, + }, nil +} + +// HandleResponse handles the response of the Otterscan getTransactionBySenderAndNonce method. +func (f *getTransactionBySenderAndNonceFactory) HandleResponse(elem rpc.BatchElem) error { + if err := elem.Error; err != nil { + return err + } + return nil +} +``` diff --git a/docs/pages/rpc.mdx b/docs/pages/rpc.mdx deleted file mode 100644 index 52fd2d2..0000000 --- a/docs/pages/rpc.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: RPC -draft: true ---- - -# RPC - -Coming soon... diff --git a/docs/pages/style.css b/docs/pages/style.css index 9230c53..0ef07e7 100644 --- a/docs/pages/style.css +++ b/docs/pages/style.css @@ -4,7 +4,7 @@ @layer base { main { - @apply dark:text-neutral-400; + @apply dark:text-neutral-300; } main strong, diff --git a/docs/pages/vm.mdx b/docs/pages/vm.mdx deleted file mode 100644 index 005d22b..0000000 --- a/docs/pages/vm.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: VM -draft: true ---- - -# VM - -Coming soon... diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4f23aa8f3f18d09bcd15afa695c264f879a9803b GIT binary patch literal 33310 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 A^#A|> literal 0 HcmV?d00001 diff --git a/docs/theme.config.jsx b/docs/theme.config.jsx index 8c99004..7e71751 100644 --- a/docs/theme.config.jsx +++ b/docs/theme.config.jsx @@ -1,5 +1,5 @@ -import { useRouter } from 'next/router' import Image from 'next/image' +import { useConfig } from 'nextra-theme-docs' export default { logo: <> @@ -8,12 +8,19 @@ export default { w3 , - banner: { - text: '🚧 This site is currently under construction 🚧' - }, - useNextSeoProps() { - const { pathname } = useRouter() - return { titleTemplate: pathname === '/' ? 'w3' : '%s – w3' } + head: () => { + const { frontMatter } = useConfig() + const title = frontMatter.title ? `${frontMatter.title} – w3` : 'w3' + return ( + <> + {title} + + + + ) }, footer: { component: null, @@ -21,15 +28,12 @@ export default { project: { link: 'https://github.com/lmittmann/w3', }, - editLink: { - text: 'Edit this page on GitHub' - }, feedback: { content: null, }, docsRepositoryBase: 'https://github.com/lmittmann/w3/blob/main/docs/pages', - primaryHue: { - dark: 189, - light: 191 - } + color: { + hue: 189, + saturation: 100, + }, } diff --git a/func_test.go b/func_test.go index 7625746..678c3a4 100644 --- a/func_test.go +++ b/func_test.go @@ -16,7 +16,7 @@ import ( "github.com/lmittmann/w3/w3types" ) -func ExampleNewFunc() { +func ExampleNewFunc_balanceOf() { // ABI binding to the balanceOf function of an ERC20 Token. funcBalanceOf, _ := w3.NewFunc("balanceOf(address)", "uint256") @@ -48,6 +48,50 @@ func ExampleNewFunc() { // balanceOf returns: 49406 } +func ExampleNewFunc_uniswapV4Swap() { + // ABI binding for the Uniswap v4 swap function. + funcSwap, _ := w3.NewFunc(`swap( + (address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, + (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params, + bytes hookData + )`, "int256 delta") + + // ABI binding for the PoolKey struct. + type PoolKey struct { + Currency0 common.Address + Currency1 common.Address + Fee *big.Int + TickSpacing *big.Int + Hooks common.Address + } + + // ABI binding for the SwapParams struct. + type SwapParams struct { + ZeroForOne bool + AmountSpecified *big.Int + SqrtPriceLimitX96 *big.Int + } + + // ABI-encode the functions args. + input, _ := funcSwap.EncodeArgs( + &PoolKey{ + Currency0: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), + Currency1: w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F"), + Fee: big.NewInt(0), + TickSpacing: big.NewInt(0), + }, + &SwapParams{ + ZeroForOne: false, + AmountSpecified: big.NewInt(0), + SqrtPriceLimitX96: big.NewInt(0), + }, + []byte{}, + ) + fmt.Printf("swap input: 0x%x\n", input) + // Output: + // swap input: 0xf3cd914c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000 +} + func TestNewFunc(t *testing.T) { tests := []struct { Signature string diff --git a/w3types/interfaces_test.go b/w3types/interfaces_test.go new file mode 100644 index 0000000..398e784 --- /dev/null +++ b/w3types/interfaces_test.go @@ -0,0 +1,78 @@ +package w3types_test + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/lmittmann/w3" + "github.com/lmittmann/w3/w3types" +) + +// TxBySenderAndNonceFactory requests the senders transaction hash by the nonce. +func TxBySenderAndNonceFactory(sender common.Address, nonce uint64) w3types.RPCCallerFactory[common.Hash] { + return &getTransactionBySenderAndNonceFactory{ + sender: sender, + nonce: nonce, + } +} + +// getTransactionBySenderAndNonceFactory implements the w3types.RPCCaller and +// w3types.RPCCallerFactory interfaces. It stores the method parameters and +// the the reference to the return value. +type getTransactionBySenderAndNonceFactory struct { + // params + sender common.Address + nonce uint64 + + // returns + returns *common.Hash +} + +// Returns sets the reference to the return value. +// +// Return implements the [w3types.RPCCallerFactory] interface. +func (f *getTransactionBySenderAndNonceFactory) Returns(txHash *common.Hash) w3types.RPCCaller { + f.returns = txHash + return f +} + +// CreateRequest creates a batch request element for the Otterscan getTransactionBySenderAndNonce method. +// +// CreateRequest implements the [w3types.RPCCaller] interface. +func (f *getTransactionBySenderAndNonceFactory) CreateRequest() (rpc.BatchElem, error) { + return rpc.BatchElem{ + Method: "ots_getTransactionBySenderAndNonce", + Args: []any{f.sender, f.nonce}, + Result: f.returns, + }, nil +} + +// HandleResponse handles the response of the Otterscan getTransactionBySenderAndNonce method. +// +// HandleResponse implements the [w3types.RPCCaller] interface. +func (f *getTransactionBySenderAndNonceFactory) HandleResponse(elem rpc.BatchElem) error { + if err := elem.Error; err != nil { + return err + } + return nil +} + +func ExampleRPCCaller_getTransactionBySenderAndNonce() { + client := w3.MustDial("https://docs-demo.quiknode.pro") + defer client.Close() + + addr := w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") + + var firstTxHash common.Hash + if err := client.Call( + TxBySenderAndNonceFactory(addr, 0).Returns(&firstTxHash), + ); err != nil { + fmt.Printf("Request failed: %v\n", err) + return + } + + fmt.Printf("First Tx Hash: %s\n", firstTxHash) + // Output: + // First Tx Hash: 0x6ff0860e202c61189cb2a3a38286bffd694acbc50577df6cb5a7ff40e21ea074 +} diff --git a/w3vm/vm_test.go b/w3vm/vm_test.go index d5aec9c..bd9ae55 100644 --- a/w3vm/vm_test.go +++ b/w3vm/vm_test.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -538,3 +539,114 @@ func BenchmarkTransferWETH9(b *testing.B) { } func ptr[T any](t T) *T { return &t } + +func ExampleVM() { + var ( + addrEOA = w3.A("0x000000000000000000000000000000000000c0Fe") + addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + addrUNI = w3.A("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984") + addrRouter = w3.A("0xE592427A0AEce92De3Edee1F18E0157C05861564") + + funcExactInput = w3.MustNewFunc(`exactInput( + ( + bytes path, + address recipient, + uint256 deadline, + uint256 amountIn, + uint256 amountOutMinimum + ) params + )`, "uint256 amountOut") + ) + + type ExactInputParams struct { + Path []byte + Recipient common.Address + Deadline *big.Int + AmountIn *big.Int + AmountOutMinimum *big.Int + } + + encodePath := func(tokenA common.Address, fee uint32, tokenB common.Address) []byte { + path := make([]byte, 43) + copy(path, tokenA[:]) + path[20], path[21], path[22] = byte(fee>>16), byte(fee>>8), byte(fee) + copy(path[23:], tokenB[:]) + return path + } + + client, err := w3.Dial("https://rpc.ankr.com/eth") + if err != nil { + // handle error + } + defer client.Close() + + // 1. Create a VM that forks the Mainnet state from the latest block, + // disables the base fee, and has a fake WETH balance and approval for the router + vm, err := w3vm.New( + w3vm.WithFork(client, nil), + w3vm.WithNoBaseFee(), + w3vm.WithState(w3types.State{ + addrWETH: {Storage: map[common.Hash]common.Hash{ + w3vm.WETHBalanceSlot(addrEOA): common.BigToHash(w3.I("1 ether")), + w3vm.WETHAllowanceSlot(addrEOA, addrRouter): common.BigToHash(w3.I("1 ether")), + }}, + }), + ) + if err != nil { + // handle error + } + + // 2. Simulate a UniSwap v3 swap + receipt, err := vm.Apply(&w3types.Message{ + From: addrEOA, + To: &addrRouter, + Func: funcExactInput, + Args: []any{&ExactInputParams{ + Path: encodePath(addrWETH, 500, addrUNI), + Recipient: addrEOA, + Deadline: big.NewInt(time.Now().Unix()), + AmountIn: w3.I("1 ether"), + AmountOutMinimum: w3.Big0, + }}, + }) + if err != nil { + // handle error + } + + // 3. Decode output amount + var amountOut *big.Int + if err := receipt.DecodeReturns(&amountOut); err != nil { + // handle error + } + + fmt.Printf("amount out: %s UNI\n", w3.FromWei(amountOut, 18)) +} + +func ExampleVM_Call() { + client := w3.MustDial("https://rpc.ankr.com/eth") + defer client.Close() + + addrWETH := w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + addrEOA := w3.A("0x000000000000000000000000000000000000c0Fe") + + vm, err := w3vm.New( + w3vm.WithFork(client, nil), + w3vm.WithState(w3types.State{ + addrWETH: {Storage: map[common.Hash]common.Hash{ + w3vm.WETHBalanceSlot(addrEOA): common.BigToHash(w3.I("1 ether")), + }}, + }), + ) + if err != nil { + // handle error + } + + balanceOf := w3.MustNewFunc("balanceOf(address)", "uint256") + var balance *big.Int + if err := vm.CallFunc(addrWETH, balanceOf, addrEOA).Returns(&balance); err != nil { + // handle error + } + fmt.Printf("%s: Balance: %s WETH\n", addrEOA, w3.FromWei(balance, 18)) + // Output: + // 0x000000000000000000000000000000000000c0Fe: Balance: 1 WETH +}