Skip to content

Commit

Permalink
feat: prepareEncodeFunctionData (#1981)
Browse files Browse the repository at this point in the history
* feat: `prepareEncodeFunctionData`

* chore: format

* chore: exports
  • Loading branch information
jxom committed Mar 20, 2024
1 parent 73f80fd commit 86b75c4
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/lemon-windows-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `prepareEncodeFunctionData`.
20 changes: 20 additions & 0 deletions site/pages/docs/contract/encodeFunctionData.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ const data = encodeFunctionData({
})
```

### Preparation (Performance Optimization)

If you are calling the same function multiple times, you can prepare the function selector once and reuse it.

```ts
import { prepareEncodeFunctionData, encodeFunctionData } from 'viem'

const transfer = prepareEncodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
})

for (const address of addresses) {
const data = encodeFunctionData({
...transfer,
args: [address, 69420n],
})
}
```

## Return Value

[`Hex`](/docs/glossary/types#hex)
Expand Down
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ test('exports', () => {
"encodeErrorResult",
"encodeEventTopics",
"encodeFunctionData",
"prepareEncodeFunctionData",
"encodeFunctionResult",
"parseEventLogs",
"defineTransaction",
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,12 @@ export {
type EncodeFunctionDataReturnType,
encodeFunctionData,
} from './utils/abi/encodeFunctionData.js'
export {
type PrepareEncodeFunctionDataErrorType,
type PrepareEncodeFunctionDataParameters,
type PrepareEncodeFunctionDataReturnType,
prepareEncodeFunctionData,
} from './utils/abi/prepareEncodeFunctionData.js'
export {
type EncodeFunctionResultErrorType,
type EncodeFunctionResultParameters,
Expand Down
6 changes: 5 additions & 1 deletion src/utils/abi/encodeFunctionData.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expectTypeOf, test } from 'vitest'

import { type Abi, parseAbi } from 'abitype'
import { wagmiContractConfig } from '~test/src/abis.js'
import type { Hex } from '../../types/misc.js'
import {
type EncodeFunctionDataParameters,
encodeFunctionData,
Expand Down Expand Up @@ -123,7 +124,9 @@ test('single abi function, functionName not required', () => {
})

type Result = EncodeFunctionDataParameters<typeof abi>
expectTypeOf<Result['functionName']>().toEqualTypeOf<'approve' | undefined>()
expectTypeOf<Result['functionName']>().toEqualTypeOf<
'approve' | Hex | undefined
>()
})

test('multiple abi functions, functionName required', () => {
Expand All @@ -149,6 +152,7 @@ test('multiple abi functions, functionName required', () => {
| 'tokenURI'
| 'totalSupply'
| 'transferFrom'
| Hex
>()
})

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

import { erc20Abi } from 'abitype/abis'
import { encodeFunctionData } from './encodeFunctionData.js'
import { prepareEncodeFunctionData } from './prepareEncodeFunctionData.js'

test('foo()', () => {
expect(
Expand Down Expand Up @@ -131,6 +133,34 @@ test('inferred functionName', () => {
)
})

test('selector as functionName', () => {
const data = encodeFunctionData({
abi: erc20Abi,
functionName: '0xa9059cbb',
args: ['0x0000000000000000000000000000000000000000', 69420n],
})
expect(data).toMatchInlineSnapshot(
`"0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010f2c"`,
)
})

test('prepared', () => {
const transfer = prepareEncodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
})
const data_prepared = encodeFunctionData({
...transfer,
args: ['0x0000000000000000000000000000000000000000', 69420n],
})
const data = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: ['0x0000000000000000000000000000000000000000', 69420n],
})
expect(data_prepared).toEqual(data)
})

test("errors: function doesn't exist", () => {
expect(() =>
encodeFunctionData({
Expand Down
50 changes: 20 additions & 30 deletions src/utils/abi/encodeFunctionData.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import type { Abi, AbiStateMutability, ExtractAbiFunctions } from 'abitype'

import {
AbiFunctionNotFoundError,
type AbiFunctionNotFoundErrorType,
} from '../../errors/abi.js'
import type { AbiFunctionNotFoundErrorType } from '../../errors/abi.js'
import type {
ContractFunctionArgs,
ContractFunctionName,
} from '../../types/contract.js'
import { type ConcatHexErrorType, concatHex } from '../data/concat.js'
import {
type ToFunctionSelectorErrorType,
toFunctionSelector,
} from '../hash/toFunctionSelector.js'
import type { ToFunctionSelectorErrorType } from '../hash/toFunctionSelector.js'

import type { ErrorType } from '../../errors/utils.js'
import type { Hex } from '../../types/misc.js'
Expand All @@ -21,15 +15,15 @@ import {
type EncodeAbiParametersErrorType,
encodeAbiParameters,
} from './encodeAbiParameters.js'
import { type FormatAbiItemErrorType, formatAbiItem } from './formatAbiItem.js'
import { type GetAbiItemErrorType, getAbiItem } from './getAbiItem.js'

const docsPath = '/docs/contract/encodeFunctionData'
import type { FormatAbiItemErrorType } from './formatAbiItem.js'
import type { GetAbiItemErrorType } from './getAbiItem.js'
import { prepareEncodeFunctionData } from './prepareEncodeFunctionData.js'

export type EncodeFunctionDataParameters<
abi extends Abi | readonly unknown[] = Abi,
functionName extends
| ContractFunctionName<abi>
| Hex
| undefined = ContractFunctionName<abi>,
///
hasFunctions = abi extends Abi
Expand All @@ -52,9 +46,9 @@ export type EncodeFunctionDataParameters<
} & UnionEvaluate<
IsNarrowable<abi, Abi> extends true
? abi['length'] extends 1
? { functionName?: functionName | allFunctionNames | undefined }
: { functionName: functionName | allFunctionNames }
: { functionName?: functionName | allFunctionNames | undefined }
? { functionName?: functionName | allFunctionNames | Hex | undefined }
: { functionName: functionName | allFunctionNames | Hex }
: { functionName?: functionName | allFunctionNames | Hex | undefined }
> &
UnionEvaluate<
readonly [] extends allArgs
Expand All @@ -80,24 +74,20 @@ export function encodeFunctionData<
>(
parameters: EncodeFunctionDataParameters<abi, functionName>,
): EncodeFunctionDataReturnType {
const { abi, args, functionName } = parameters as EncodeFunctionDataParameters
const { args } = parameters as EncodeFunctionDataParameters

let abiItem = abi[0]
if (functionName) {
const item = getAbiItem({
abi,
args,
name: functionName,
})
if (!item) throw new AbiFunctionNotFoundError(functionName, { docsPath })
abiItem = item
}
const { abi, functionName } = (() => {
if (
parameters.abi.length === 1 &&
parameters.functionName?.startsWith('0x')
)
return parameters as { abi: Abi; functionName: Hex }
return prepareEncodeFunctionData(parameters)
})()

if (abiItem.type !== 'function')
throw new AbiFunctionNotFoundError(undefined, { docsPath })
const abiItem = abi[0]
const signature = functionName

const definition = formatAbiItem(abiItem)
const signature = toFunctionSelector(definition)
const data =
'inputs' in abiItem && abiItem.inputs
? encodeAbiParameters(abiItem.inputs, args ?? [])
Expand Down
138 changes: 138 additions & 0 deletions src/utils/abi/prepareEncodeFunctionData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { erc20Abi } from 'abitype/abis'
import { expect, test } from 'vitest'
import { prepareEncodeFunctionData } from './prepareEncodeFunctionData.js'

test('default', () => {
const prepared = prepareEncodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
})
expect(prepared).toMatchInlineSnapshot(`
{
"abi": [
{
"inputs": [
{
"name": "recipient",
"type": "address",
},
{
"name": "amount",
"type": "uint256",
},
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool",
},
],
"stateMutability": "nonpayable",
"type": "function",
},
],
"functionName": "0xa9059cbb",
}
`)
})

test('inferred functionName', () => {
expect(
prepareEncodeFunctionData({
abi: [
{
inputs: [
{
name: 'a',
type: 'uint256',
},
],
name: 'bar',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
],
args: [1n],
}),
).toMatchInlineSnapshot(`
{
"abi": [
{
"inputs": [
{
"name": "a",
"type": "uint256",
},
],
"name": "bar",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
},
],
"functionName": "0x0423a132",
}
`)
})

test("errors: function doesn't exist", () => {
expect(() =>
prepareEncodeFunctionData({
abi: [
{
inputs: [],
name: 'foo',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
],
// @ts-expect-error
functionName: 'bar',
}),
).toThrowErrorMatchingInlineSnapshot(`
[AbiFunctionNotFoundError: 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/encodeFunctionData
Version: viem@1.0.2]
`)

expect(() =>
prepareEncodeFunctionData({
abi: erc20Abi,
// @ts-expect-error
functionName: 'bar',
}),
).toThrowErrorMatchingInlineSnapshot(`
[AbiFunctionNotFoundError: 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/encodeFunctionData
Version: viem@1.0.2]
`)
})

test('errors: abi item not a function', () => {
expect(() =>
// @ts-expect-error abi has no functions
prepareEncodeFunctionData({
abi: [
{
inputs: [],
name: 'Foo',
outputs: [],
type: 'error',
},
],
}),
).toThrowErrorMatchingInlineSnapshot(`
[AbiFunctionNotFoundError: Function 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/encodeFunctionData
Version: viem@1.0.2]
`)
})
Loading

0 comments on commit 86b75c4

Please sign in to comment.