Skip to content

Commit

Permalink
feat: encodeErrorResult
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Jan 30, 2023
1 parent 4699881 commit 9120e26
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-bottles-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `encodeErrorResult`.
2 changes: 1 addition & 1 deletion site/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ export const sidebar: DefaultTheme.Sidebar = {
link: '/docs/contract/encodeDeployData',
},
{
text: 'encodeErrorResult 🚧',
text: 'encodeErrorResult',
link: '/docs/contract/encodeErrorResult',
},
{
Expand Down
31 changes: 31 additions & 0 deletions src/errors/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ export class AbiEncodingLengthMismatchError extends BaseError {
}
}

export class AbiErrorInputsNotFoundError extends BaseError {
name = 'AbiErrorInputsNotFoundError'
constructor(errorName: string) {
super(
[
`Arguments (\`args\`) were provided to "${errorName}", but "${errorName}" on the ABI does not contain any parameters (\`inputs\`).`,
'Cannot encode error result without knowing what the parameter types are.',
'Make sure you are using the correct ABI and that the inputs exist on it.',
].join('\n'),
{
docsPath: '/docs/contract/encodeErrorResult',
},
)
}
}

export class AbiErrorNotFoundError extends BaseError {
name = 'AbiErrorNotFoundError'
constructor(errorName: string) {
super(
[
`Error "${errorName}" not found on ABI.`,
'Make sure you are using the correct ABI and that the error exists on it.',
].join('\n'),
{
docsPath: '/docs/contract/encodeErrorResult',
},
)
}
}

export class AbiErrorSignatureNotFoundError extends BaseError {
name = 'AbiErrorSignatureNotFoundError'
constructor(signature: Hex) {
Expand Down
2 changes: 2 additions & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export {
AbiDecodingDataSizeInvalidError,
AbiEncodingArrayLengthMismatchError,
AbiEncodingLengthMismatchError,
AbiErrorInputsNotFoundError,
AbiErrorNotFoundError,
AbiErrorSignatureNotFoundError,
AbiEventNotFoundError,
AbiFunctionNotFoundError,
Expand Down
6 changes: 6 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ test('exports actions', () => {
"AbiDecodingDataSizeInvalidError": [Function],
"AbiEncodingArrayLengthMismatchError": [Function],
"AbiEncodingLengthMismatchError": [Function],
"AbiErrorInputsNotFoundError": [Function],
"AbiErrorNotFoundError": [Function],
"AbiErrorSignatureNotFoundError": [Function],
"AbiEventNotFoundError": [Function],
"AbiFunctionNotFoundError": [Function],
"AbiFunctionOutputsNotFoundError": [Function],
"AbiFunctionSignatureNotFoundError": [Function],
"BaseError": [Function],
"BlockNotFoundError": [Function],
"DataLengthTooLongError": [Function],
"DataLengthTooShortError": [Function],
"FilterTypeNotSupportedError": [Function],
"HttpRequestError": [Function],
"InternalRpcError": [Function],
"InvalidAbiDecodingTypeError": [Function],
Expand Down Expand Up @@ -79,6 +84,7 @@ test('exports actions', () => {
"encodeAbi": [Function],
"encodeBytes": [Function],
"encodeDeployData": [Function],
"encodeErrorResult": [Function],
"encodeEventTopics": [Function],
"encodeFunctionData": [Function],
"encodeFunctionResult": [Function],
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ export {
AbiDecodingDataSizeInvalidError,
AbiEncodingArrayLengthMismatchError,
AbiEncodingLengthMismatchError,
AbiErrorInputsNotFoundError,
AbiErrorNotFoundError,
AbiErrorSignatureNotFoundError,
AbiEventNotFoundError,
AbiFunctionNotFoundError,
AbiFunctionOutputsNotFoundError,
AbiFunctionSignatureNotFoundError,
Expand All @@ -180,6 +184,7 @@ export {
DataLengthTooLongError,
DataLengthTooShortError,
HttpRequestError,
FilterTypeNotSupportedError,
InternalRpcError,
InvalidAbiDecodingTypeError,
InvalidAbiEncodingTypeError,
Expand Down Expand Up @@ -281,6 +286,7 @@ export {
encodeAbi,
encodeBytes,
encodeDeployData,
encodeErrorResult,
encodeEventTopics,
encodeFunctionData,
encodeFunctionResult,
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type {
ExtractArgsFromEventDefinition,
ExtractArgsFromFunctionDefinition,
ExtractConstructorArgsFromAbi,
ExtractErrorArgsFromAbi,
ExtractResultFromAbi,
} from './solidity'

Expand Down
27 changes: 27 additions & 0 deletions src/types/solidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
AbiParameter,
ExtractAbiEvent,
AbiEvent,
AbiError,
ExtractAbiError,
} from 'abitype'

import type { Trim } from './utils'
Expand Down Expand Up @@ -105,6 +107,31 @@ export type ExtractConstructorArgsFromAbi<
/** Arguments to pass contract method */ args: TArgs
}

export type ExtractErrorArgsFromAbi<
TAbi extends Abi | readonly unknown[],
TErrorName extends string,
TAbiError extends AbiError = TAbi extends Abi
? ExtractAbiError<TAbi, TErrorName>
: AbiError,
TArgs = AbiParametersToPrimitiveTypes<TAbiError['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
}

export type ExtractResultFromAbi<
TAbi extends Abi | readonly unknown[],
TFunctionName extends string,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/abi/encodeAbi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('static', () => {
params: [],
values: [],
}),
).toBe(undefined)
).toBe('0x')
})

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

Expand Down
2 changes: 1 addition & 1 deletion src/utils/abi/encodeDeployData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Abi, AbiParameterToPrimitiveType } from 'abitype'
import { Abi } from 'abitype'

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

import { encodeErrorResult } from './encodeErrorResult'

test('revert SoldOutError()', () => {
expect(
encodeErrorResult({
abi: [
{
inputs: [],
name: 'SoldOutError',
type: 'error',
},
],
errorName: 'SoldOutError',
}),
).toEqual('0x7f6df6bb')
expect(
encodeErrorResult({
abi: [
// @ts-expect-error
{
name: 'SoldOutError',
type: 'error',
},
],
errorName: 'SoldOutError',
}),
).toEqual('0x7f6df6bb')
})

test('revert AccessDeniedError(string)', () => {
expect(
encodeErrorResult({
abi: [
{
inputs: [
{
internalType: 'string',
name: 'a',
type: 'string',
},
],
name: 'AccessDeniedError',
type: 'error',
},
] as const,
errorName: 'AccessDeniedError',
args: ['you do not have access ser'],
}),
).toEqual(
'0x83aa206e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a796f7520646f206e6f7420686176652061636365737320736572000000000000',
)
})

test('revert AccessDeniedError((uint256,bool,address,uint256))', () => {
expect(
encodeErrorResult({
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: 'AccessDeniedError',
type: 'error',
},
],
errorName: 'AccessDeniedError',
args: [
{
delegate: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
vote: 41n,
voted: true,
weight: 69420n,
},
],
}),
).toEqual(
'0x0a1895610000000000000000000000000000000000000000000000000000000000010f2c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac0000000000000000000000000000000000000000000000000000000000000029',
)
})

test("errors: error doesn't exist", () => {
expect(() =>
encodeErrorResult({
abi: [
{
inputs: [],
name: 'SoldOutError',
type: 'error',
},
],
errorName: 'AccessDeniedError',
args: [
{
delegate: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
vote: 41n,
voted: true,
weight: 69420n,
},
],
}),
).toThrowErrorMatchingInlineSnapshot(`
"Error \\"AccessDeniedError\\" not found on ABI.
Make sure you are using the correct ABI and that the error exists on it.
Docs: https://viem.sh/docs/contract/encodeErrorResult
Version: viem@1.0.2"
`)
})

test('errors: no inputs', () => {
expect(() =>
encodeErrorResult({
abi: [
// @ts-expect-error
{
name: 'AccessDeniedError',
type: 'error',
},
],
errorName: 'AccessDeniedError',
args: [
{
delegate: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
vote: 41n,
voted: true,
weight: 69420n,
},
],
}),
).toThrowErrorMatchingInlineSnapshot(`
"Arguments (\`args\`) were provided to \\"AccessDeniedError\\", but \\"AccessDeniedError\\" on the ABI does not contain any parameters (\`inputs\`).
Cannot encode error result without knowing what the parameter types are.
Make sure you are using the correct ABI and that the inputs exist on it.
Docs: https://viem.sh/docs/contract/encodeErrorResult
Version: viem@1.0.2"
`)
expect(() =>
encodeErrorResult({
abi: [
{
// @ts-expect-error
inputs: undefined,
name: 'AccessDeniedError',
type: 'error',
},
],
errorName: 'AccessDeniedError',
args: [
{
delegate: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
vote: 41n,
voted: true,
weight: 69420n,
},
],
}),
).toThrowErrorMatchingInlineSnapshot(`
"Arguments (\`args\`) were provided to \\"AccessDeniedError\\", but \\"AccessDeniedError\\" on the ABI does not contain any parameters (\`inputs\`).
Cannot encode error result without knowing what the parameter types are.
Make sure you are using the correct ABI and that the inputs exist on it.
Docs: https://viem.sh/docs/contract/encodeErrorResult
Version: viem@1.0.2"
`)
})

3 comments on commit 9120e26

@vercel
Copy link

@vercel vercel bot commented on 9120e26 Jan 30, 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 9120e26 Jan 30, 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

@vercel
Copy link

@vercel vercel bot commented on 9120e26 Jan 30, 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-git-main-wagmi-dev.vercel.app
viem-site-wagmi-dev.vercel.app
viem-site.vercel.app

Please sign in to comment.