Skip to content

Commit

Permalink
feat: encodeFunctionParams (#18)
Browse files Browse the repository at this point in the history
* wip

* feat: encodeFunctionParams

* format

* fix: types
  • Loading branch information
jxom committed Jan 26, 2023
1 parent 4426649 commit bb9e88a
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/rare-cats-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `encodeFunctionParams`.
145 changes: 144 additions & 1 deletion site/docs/contract/encodeFunctionParams.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,146 @@
# encodeFunctionParams

TODO
Encodes the function name and parameters into an ABI encoded value (4byte selector & arguments).

## Install

```ts
import { encodeFunctionParams } from 'viem'
```

## Usage

Below is a very basic example of how to encode a function to calldata.

::: code-group

```ts [example.ts]
import { encodeFunctionParams } from 'viem'

const data = encodeFunctionParams({
abi: wagmiAbi,
functionName: 'totalSupply'
})
```

```ts
export const wagmiAbi = [
...
{
inputs: [],
name: "totalSupply",
outputs: [{ name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
...
] as const;
```

```ts [client.ts]
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
```

:::

### Passing Arguments

If your function requires argument(s), you can pass them through with the `args` attribute.

TypeScript types for `args` will be inferred from the function name & ABI, to guard you from inserting the wrong values.

For example, the `balanceOf` function name below requires an **address** argument, and it is typed as `["0x${string}"]`.

::: code-group

```ts {8} [example.ts]
import { encodeFunctionParams } from 'viem'
import { publicClient } from './client'
import { wagmiAbi } from './abi'

const data = encodeFunctionParams({
abi: wagmiAbi,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC']
})
```

```ts [abi.ts]
export const wagmiAbi = [
...
{
inputs: [{ name: "owner", type: "address" }],
name: "balanceOf",
outputs: [{ name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
...
] as const;
```

```ts [client.ts]
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
```

:::

## Return Value

`Hex`

ABI encoded data (4byte function selector & arguments).

## Parameters

### abi

- **Type:** [`Abi`](/docs/glossary/types#TODO)

The contract's ABI.

```ts
const data = encodeFunctionParams({
abi: wagmiAbi, // [!code focus]
functionName: 'totalSupply',
})
```

### functionName

- **Type:** `string`

The function to encode from the ABI.

```ts
const data = encodeFunctionParams({
abi: wagmiAbi,
functionName: 'totalSupply', // [!code focus]
})
```

### args (optional)

- **Type:** Inferred from ABI.

Arguments to pass to function call.

```ts
const data = encodeFunctionParams({
abi: wagmiAbi,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] // [!code focus]
})
```
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test('exports actions', () => {
"dropTransaction": [Function],
"encodeAbi": [Function],
"encodeBytes": [Function],
"encodeFunctionParams": [Function],
"encodeHex": [Function],
"encodeRlp": [Function],
"estimateGas": [Function],
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ export {
decodeRlp,
encodeAbi,
encodeBytes,
encodeFunctionParams,
encodeHex,
encodeRlp,
getAddress,
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type {
} from './rpc'

export type {
ExtractArgsFromAbi,
ExtractArgsFromEventDefinition,
ExtractArgsFromFunctionDefinition,
} from './solidity'
Expand Down
37 changes: 36 additions & 1 deletion src/types/solidity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import type { AbiType, AbiTypeToPrimitiveType } from 'abitype'
import type {
Abi,
AbiFunction,
AbiType,
AbiTypeToPrimitiveType,
AbiParametersToPrimitiveTypes,
ExtractAbiFunction,
} from 'abitype'

import type { Trim } from './utils'

//////////////////////////////////////////////////////////////////////
// ABIs

export type ExtractArgsFromAbi<
TAbi extends Abi | readonly unknown[],
TFunctionName extends string,
TAbiFunction extends AbiFunction & { type: 'function' } = TAbi extends Abi
? ExtractAbiFunction<TAbi, TFunctionName>
: AbiFunction & { type: 'function' },
TArgs = AbiParametersToPrimitiveTypes<TAbiFunction['inputs']>,
FailedToParseArgs =
| ([TArgs] extends [never] ? true : false)
| (readonly unknown[] extends TArgs ? true : false),
> = true extends FailedToParseArgs
? {
/**
* Arguments to pass contract method
*
* Use a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) on {@link abi} for type inference.
*/
args?: readonly unknown[]
}
: TArgs extends readonly []
? { args?: never }
: {
/** Arguments to pass contract method */ args: TArgs
}

//////////////////////////////////////////////////////////////////////
// Event/Function Definitions

Expand Down
9 changes: 9 additions & 0 deletions src/utils/abi/encodeAbi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import {
} from './encodeAbi'

describe('static', () => {
test('blank', () => {
expect(
encodeAbi({
params: [],
values: [],
}),
).toBe(undefined)
})

test('uint', () => {
expect(
encodeAbi({
Expand Down
4 changes: 3 additions & 1 deletion src/utils/abi/encodeAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export function encodeAbi<TParams extends readonly AbiParameter[]>({
})
// Prepare the parameters to determine dynamic types to encode.
const preparedParams = prepareParams({ params, values })
return encodeParams(preparedParams)
const data = encodeParams(preparedParams)
if (data.length === 0) return undefined
return data
}

/////////////////////////////////////////////////////////////////
Expand Down
140 changes: 140 additions & 0 deletions src/utils/abi/encodeFunctionParams.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { expect, test } from 'vitest'

import { encodeFunctionParams } from './encodeFunctionParams'

test('foo()', () => {
expect(
encodeFunctionParams({
abi: [
{
inputs: [],
name: 'foo',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'foo',
}),
).toEqual('0xc2985578')
expect(
encodeFunctionParams({
// @ts-expect-error
abi: [
{
name: 'foo',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'foo',
}),
).toEqual('0xc2985578')
})

test('bar(uint256)', () => {
expect(
encodeFunctionParams({
abi: [
{
inputs: [
{
internalType: 'uint256',
name: 'a',
type: 'uint256',
},
],
name: 'bar',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'bar',
args: [1n],
}),
).toEqual(
'0x0423a1320000000000000000000000000000000000000000000000000000000000000001',
)
})

test('getVoter((uint256,bool,address,uint256))', () => {
expect(
encodeFunctionParams({
abi: [
{
inputs: [
{
components: [
{
internalType: 'uint256',
name: 'weight',
type: 'uint256',
},
{
internalType: 'bool',
name: 'voted',
type: 'bool',
},
{
internalType: 'address',
name: 'delegate',
type: 'address',
},
{
internalType: 'uint256',
name: 'vote',
type: 'uint256',
},
],
internalType: 'struct Ballot.Voter',
name: 'voter',
type: 'tuple',
},
],
name: 'getVoter',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'getVoter',
args: [
{
delegate: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
vote: 41n,
voted: true,
weight: 69420n,
},
],
}),
).toEqual(
'0xf37414670000000000000000000000000000000000000000000000000000000000010f2c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac0000000000000000000000000000000000000000000000000000000000000029',
)
})

test("errors: function doesn't exist", () => {
expect(() =>
encodeFunctionParams({
abi: [
{
inputs: [],
name: 'foo',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
// @ts-expect-error
functionName: 'bar',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Function \\"bar\\" not found on ABI.
Make sure you are using the correct ABI and that the function exists on it.
Docs: https://viem.sh/docs/contract/encodeFunctionParams
Version: viem@1.0.2"
`)
})
Loading

3 comments on commit bb9e88a

@vercel
Copy link

@vercel vercel bot commented on bb9e88a Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-benchmark – ./playgrounds/benchmark

viem-benchmark-git-main-wagmi-dev.vercel.app
viem-benchmark-wagmi-dev.vercel.app
viem-benchmark.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bb9e88a Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-site – ./site

viem-site-wagmi-dev.vercel.app
viem-site.vercel.app
viem-site-git-main-wagmi-dev.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bb9e88a Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-playground – ./playgrounds/dev

viem-playground.vercel.app
viem-playground-git-main-wagmi-dev.vercel.app
viem-playground-wagmi-dev.vercel.app

Please sign in to comment.