Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: improved ABI section #186

Merged
merged 16 commits into from
Sep 6, 2024
4 changes: 0 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@ updates:
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "monthly"
1,637 changes: 755 additions & 882 deletions docs/package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"build": "next build"
},
"dependencies": {
"next": "^14.2.5",
"nextra": "3.0.0-alpha.32",
"nextra-theme-docs": "3.0.0-alpha.32",
"next": "^14.2.8",
"nextra": "3.0.0-alpha.36",
"nextra-theme-docs": "3.0.0-alpha.36",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10"
}
}
184 changes: 169 additions & 15 deletions docs/pages/helper-abi.mdx
Original file line number Diff line number Diff line change
@@ -1,25 +1,179 @@
# ABI Bindings

ABI bindings in `w3` are specified for individual functions using Solidity syntax and are usable for any contract that supports that function.
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.

**Example:** ABI binding for the ERC20 `balanceOf` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-BalanceOf))
* **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.

```go
funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")

## 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.

### Syntax

Function signatures are defined using Solidity syntax. Arguments and returns can optionally be named. While naming is optional, it is recommended for more complex functions or tuple variables. Alias types, such as `uint` for `uint256`, are supported.

#### Example: ERC20 `balanceOf`

ABI binding for the ERC20 `balanceOf` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-BalanceOf)):

```solidity filename="Solidity"
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
// ...
}
```

```go filename="Go"
var funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")
// or
var funcBalanceOf = w3.MustNewFunc("balanceOf(address who)", "uint256 amount")
```

#### Example: QuoterV2 `quoteExactInputSingle`

ABI binding for the Uniswap QuoterV2 `quoteExactInputSingle` function with a tuple parameter (Solidity `struct`):

```solidity filename="Solidity"
interface QuoterV2 {

struct QuoteExactInputSingleParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
uint24 fee;
uint160 sqrtPriceLimitX96;
}

function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
external
returns (
uint256 amountOut,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
);
// ...
}
```

```go filename="Go"
type QuoteExactInputSingleParams struct {
TokenIn common.Address
TokenOut common.Address
AmountIn *big.Int
Fee *big.Int
SqrtPriceLimitX96 *big.Int
}

var funcQuoteExactInputSingle = w3.MustNewFunc(
`quoteExactInputSingle((
address tokenIn,
address tokenOut,
uint256 amountIn,
uint24 fee,
uint160 sqrtPriceLimitX96
) params)`,
`uint256 amountOut,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate`,
)
```

### Tuples (Solidity `struct`'s)

Tuple types need to be embedded in parentheses, with comma-separated fields. Fields must be named, so they can be mapped to the fields of a Go struct.

To map a tuple type to a Go struct, the struct must be defined manually with each tuple field being mapped to a Go struct field. Field names need to match, but Go field names must always start with an uppercase letter. E.g. the tuple field `address tokenIn{:solidity}` must be matched to the Go struct field `TokenIn common.Address{:go}`.

<Callout type="info">
See [Type Mappings](#type-mappings) for more information on how to map primitive Solidity types to Go types and vice versa.
</Callout>

### `EncodeArgs`

The `EncodeArgs` method of a `Func` ABI encodes a Solidity function call. Each argument of the Solidity function must be matched by a corresponding Go value.

### `DecodeArgs` and `DecodeReturns`

The `DecodeArgs` and `DecodeReturns` methods of a `Func` ABI decode the arguments and returns of a Solidity function call. The Go values that should hold the decoded data must be defined beforehand, and passed as pointers to the decode methods. Values that should not be decoded can be passed as `nil`. Tailing `nil` values can be omitted.

#### Example: Uniswap Pair `getReserves`

ABI decode the output of the Uniswap Pair `getReserves` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Func.DecodeReturns-GetReserves)):

```go filename="Go"
var (
funcGetReserves = w3.MustNewFunc("getReserves()", "uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast")
output []byte = w3.B("0x00…")
)

var (
reserve0, reserve1 *big.Int
blockTimestampLast uint32
)
if err := funcGetReserves.DecodeReturns(output, &reserve0, &reserve1, &blockTimestampLast); err != nil {
// ...
}
```

**Example:** ABI binding for the Uniswap v4 `swap` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-UniswapV4Swap))
In case only the reserves should be decoded, the `blockTimestampLast` can be ignored using `funcGetReserves.DecodeReturns(output, &reserve0, &reserve1, nil){:go}`, which is equivalent to `funcGetReserves.DecodeReturns(output, &reserve0, &reserve1){:go}`.


```go
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")
## Events

Event ABI bindings can be defined using
* `func NewEvent(signature string) (*Event, error)`, or
* `func MustNewEvent(signature string) *Event` which panics on error.

#### Example: ERC20 `Transfer`

ABI binding for the ERC20 `Transfer` event:

```solidity filename="Solidity"
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
// ...
}
```

```go filename="Go"
var evtTransfer = w3.MustNewEvent("Transfer(address indexed from, address indexed to, uint256 value)")
```

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)).
## Type Mappings

| **Solidity Type** | **Go Type** |
|:-----------------------------------------------------|:----------------------------|
| `bool` | `bool` |
| `int8` | `int8` |
| `int16` | `int16` |
| `int32` | `int32` |
| `int64` | `int64` |
| `int24`,`int40`…`int56`,`int72`…`int256`,`int` | `*big.Int` |
| `uint8` | `uint8` |
| `uint16` | `uint16` |
| `uint32` | `uint32` |
| `uint64` | `uint64` |
| `uint24`,`uint40`…`uint56`,`uint72`…`uint256`,`uint` | `*big.Int` |
| `bytes` | `[]byte` |
| `bytes1`…`bytes32` | `[1]byte`…`[32]byte` |
| `address` | `common.Address`/`[20]byte` |
| `bytes32` | `common.Hash`/`[32]byte` |

### Arrays and Slices

Solidity arrays and slices are mapped to Go arrays and slices respectively and vice versa.

| **Solidity Type** | **Go Type** |
|:------------------|:------------|
| `type[n]` | `[n]type` |
| `type[]` | `[]type` |
10 changes: 10 additions & 0 deletions docs/pages/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ a._underline {
@apply no-underline;
}

/* Tables */
tr._border-gray-300 td,
tr._border-gray-300 th {
@apply dark:border-neutral-700;
}

tr._border-gray-300 {
@apply even:bg-neutral-900 !important;
}

/* Customize header */
.nextra-nav-container nav a[target="_blank"]:nth-child(2):after,
.nextra-nav-container nav a[target="_blank"]:nth-child(3):after,
Expand Down
24 changes: 24 additions & 0 deletions func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,30 @@ func ExampleNewFunc_uniswapV4Swap() {
// swap input: 0xf3cd914c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000
}

func ExampleFunc_DecodeReturns_getReserves() {
funcGetReserves := w3.MustNewFunc("getReserves()", "uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast")
output := w3.B(
"0x00000000000000000000000000000000000000000000003635c9adc5dea00000",
"0x0000000000000000000000000000000000000000000000a2a15d09519be00000",
"0x0000000000000000000000000000000000000000000000000000000064373057",
)

var (
reserve0, reserve1 *big.Int
blockTimestampLast uint32
)
if err := funcGetReserves.DecodeReturns(output, &reserve0, &reserve1, &blockTimestampLast); err != nil {
// ...
}
fmt.Println("Reserve0:", reserve0)
fmt.Println("Reserve1:", reserve1)
fmt.Println("BlockTimestampLast:", blockTimestampLast)
// Output:
// Reserve0: 1000000000000000000000
// Reserve1: 3000000000000000000000
// BlockTimestampLast: 1681338455
}

func TestNewFunc(t *testing.T) {
tests := []struct {
Signature string
Expand Down
Loading