Skip to content

Commit

Permalink
feat: decodeErrorResult
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Jan 29, 2023
1 parent 7e82537 commit c52ce66
Show file tree
Hide file tree
Showing 15 changed files with 273 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-peas-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `decodeErrorResult`.
8 changes: 4 additions & 4 deletions site/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,8 @@ export const sidebar: DefaultTheme.Sidebar = {
link: '/docs/contract/decodeDeployData',
},
{
text: 'decodeErrorData 🚧',
link: '/docs/contract/decodeErrorData',
text: 'decodeErrorResult',
link: '/docs/contract/decodeErrorResult',
},
{
text: 'decodeEventTopics 🚧',
Expand All @@ -462,8 +462,8 @@ export const sidebar: DefaultTheme.Sidebar = {
link: '/docs/contract/encodeDeployData',
},
{
text: 'encodeErrorData 🚧',
link: '/docs/contract/encodeErrorData',
text: 'encodeErrorResult 🚧',
link: '/docs/contract/encodeErrorResult',
},
{
text: 'encodeEventTopics 🚧',
Expand Down
3 changes: 0 additions & 3 deletions site/docs/contract/decodeErrorData.md

This file was deleted.

85 changes: 85 additions & 0 deletions site/docs/contract/decodeErrorResult.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# decodeErrorResult

Decodes reverted error from a contract function call.

## Install

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

## Usage

::: code-group

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

const value = decodeErrorResult({
abi: wagmiAbi,
data: '0xb758934b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000'
})
// { errorName: 'InvalidTokenError', args: ['sold out'] }
```

```ts [abi.ts]
export const wagmiAbi = [
...
{
inputs: [
{
internalType: "string",
name: "reason",
type: "string"
}
],
name: "InvalidTokenError",
type: "error"
},
...
] 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

The decoded error. Type is inferred from the ABI.

## Parameters

### abi

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

The contract's ABI.

```ts
const value = decodeErrorResult({
abi: wagmiAbi, // [!code focus]
data: '0xb758934b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000'
})
```

### data

- **Type**: `Hex`

The calldata.

```ts
const value = decodeErrorResult({
abi: wagmiAbi,
data: '0xb758934b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000' // [!code focus]
})
```
16 changes: 16 additions & 0 deletions src/errors/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ export class AbiEncodingLengthMismatchError extends BaseError {
}
}

export class AbiErrorSignatureNotFoundError extends BaseError {
name = 'AbiErrorSignatureNotFoundError'
constructor(signature: Hex) {
super(
[
`Encoded error signature "${signature}" not found on ABI.`,
'Make sure you are using the correct ABI and that the error exists on it.',
`You can look up the signature "${signature}" here: https://sig.eth.samczsun.com/.`,
].join('\n'),
{
docsPath: '/docs/contract/decodeErrorResult',
},
)
}
}

export class AbiFunctionNotFoundError extends BaseError {
name = 'AbiFunctionNotFoundError'
constructor(functionName: string) {
Expand Down
1 change: 1 addition & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
AbiDecodingDataSizeInvalidError,
AbiEncodingArrayLengthMismatchError,
AbiEncodingLengthMismatchError,
AbiErrorSignatureNotFoundError,
AbiFunctionNotFoundError,
AbiFunctionOutputsNotFoundError,
AbiFunctionSignatureNotFoundError,
Expand Down
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ test('exports actions', () => {
"custom": [Function],
"decodeAbi": [Function],
"decodeBytes": [Function],
"decodeErrorResult": [Function],
"decodeFunctionData": [Function],
"decodeFunctionResult": [Function],
"decodeHex": [Function],
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export {
bytesToNumber,
decodeAbi,
decodeBytes,
decodeErrorResult,
decodeFunctionData,
decodeFunctionResult,
decodeHex,
Expand Down
130 changes: 130 additions & 0 deletions src/utils/abi/decodeErrorResult.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { expect, test } from 'vitest'

import { decodeErrorResult } from './decodeErrorResult'

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

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

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

test("errors: error doesn't exist", () => {
expect(() =>
decodeErrorResult({
abi: [
{
inputs: [],
name: 'SoldOutError',
type: 'error',
},
],
data: '0xa37414670000000000000000000000000000000000000000000000000000000000010f2c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5cc3c03994db5b0d9a5eedd10cabab0813678ac0000000000000000000000000000000000000000000000000000000000000029',
}),
).toThrowErrorMatchingInlineSnapshot(`
"Encoded error signature \\"0xa3741467\\" not found on ABI.
Make sure you are using the correct ABI and that the error exists on it.
You can look up the signature \\"0xa3741467\\" here: https://sig.eth.samczsun.com/.
Docs: https://viem.sh/docs/contract/decodeErrorResult
Version: [email protected]"
`)
})
24 changes: 24 additions & 0 deletions src/utils/abi/decodeErrorResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Abi } from 'abitype'
import { AbiErrorSignatureNotFoundError } from '../../errors'
import { Hex } from '../../types'
import { slice } from '../data'
import { getFunctionSignature } from '../hash'
import { decodeAbi } from './decodeAbi'
import { getDefinition } from './getDefinition'

export function decodeErrorResult({ abi, data }: { abi: Abi; data: Hex }) {
const signature = slice(data, 0, 4)
const description = abi.find(
(x) => signature === getFunctionSignature(getDefinition(x)),
)
if (!description) throw new AbiErrorSignatureNotFoundError(signature)
return {
errorName: (description as { name: string }).name,
args:
'inputs' in description &&
description.inputs &&
description.inputs.length > 0
? decodeAbi({ data: slice(data, 4), params: description.inputs })
: undefined,
}
}
2 changes: 1 addition & 1 deletion src/utils/abi/decodeFunctionData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Abi } from 'abitype'
import { AbiFunctionSignatureNotFoundError } from '../../errors'

import { AbiFunctionSignatureNotFoundError } from '../../errors'
import { Hex } from '../../types'
import { slice } from '../data'
import { getFunctionSignature } from '../hash'
Expand Down
1 change: 1 addition & 0 deletions src/utils/abi/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ test('exports utils', () => {
expect(utils).toMatchInlineSnapshot(`
{
"decodeAbi": [Function],
"decodeErrorResult": [Function],
"decodeFunctionData": [Function],
"decodeFunctionResult": [Function],
"encodeAbi": [Function],
Expand Down
2 changes: 2 additions & 0 deletions src/utils/abi/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { decodeAbi } from './decodeAbi'

export { decodeErrorResult } from './decodeErrorResult'

export { decodeFunctionData } from './decodeFunctionData'

export { decodeFunctionResult } from './decodeFunctionResult'
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test('exports utils', () => {
"bytesToString": [Function],
"decodeAbi": [Function],
"decodeBytes": [Function],
"decodeErrorResult": [Function],
"decodeFunctionData": [Function],
"decodeFunctionResult": [Function],
"decodeHex": [Function],
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
decodeAbi,
decodeErrorResult,
decodeFunctionData,
decodeFunctionResult,
encodeAbi,
Expand Down

3 comments on commit c52ce66

@vercel
Copy link

@vercel vercel bot commented on c52ce66 Jan 29, 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-wagmi-dev.vercel.app
viem-benchmark-git-main-wagmi-dev.vercel.app
viem-benchmark.vercel.app

@vercel
Copy link

@vercel vercel bot commented on c52ce66 Jan 29, 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 c52ce66 Jan 29, 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

Please sign in to comment.