From 5aa5d8b7dd46cc135d862f9f0f982694c60164d1 Mon Sep 17 00:00:00 2001 From: will-af Date: Wed, 22 Jan 2025 16:59:02 -0600 Subject: [PATCH] Add raw revert data to ContractFunctionRevertedError Exposes otherwise missing raw revert data via a new rawData field on the ContractFunctionRevertedError Fixes #3235 --- .changeset/two-crabs-push.md | 5 ++ src/errors/contract.test.ts | 103 ++++++++++++++++++++--------------- src/errors/contract.ts | 3 + 3 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 .changeset/two-crabs-push.md diff --git a/.changeset/two-crabs-push.md b/.changeset/two-crabs-push.md new file mode 100644 index 0000000000..dee32a5e14 --- /dev/null +++ b/.changeset/two-crabs-push.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added raw revert data field to `ContractFunctionRevertedError` so that raw revert data is always programmatically accessible if provided, regardless of whether the error was successfully decoded via the provided ABI. diff --git a/src/errors/contract.test.ts b/src/errors/contract.test.ts index bab6a47457..9d73d65883 100644 --- a/src/errors/contract.test.ts +++ b/src/errors/contract.test.ts @@ -317,58 +317,67 @@ describe('ContractFunctionExecutionError', () => { describe('ContractFunctionRevertedError', () => { test('default', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - message: 'oh no', - functionName: 'totalSupply', - }), - ).toMatchInlineSnapshot(` + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + message: 'oh no', + functionName: 'totalSupply', + }) + + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "totalSupply" reverted with the following reason: oh no Version: viem@x.y.z] `) + + expect(err.rawData).be.undefined }) test('data: Error(string)', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - data: '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473000000000000000000000000000000000000000000000000000000000000', - functionName: 'totalSupply', - }), - ).toMatchInlineSnapshot(` + const data = + '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473000000000000000000000000000000000000000000000000000000000000' + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + data, + functionName: 'totalSupply', + }) + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "totalSupply" reverted with the following reason: EnumerableSet: index out of bounds Version: viem@x.y.z] `) + + expect(err.rawData).toEqual(data) }) test('data: Panic(uint256)', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - data: '0x4e487b710000000000000000000000000000000000000000000000000000000000000001', - functionName: 'totalSupply', - }), - ).toMatchInlineSnapshot(` + const data = + '0x4e487b710000000000000000000000000000000000000000000000000000000000000001' + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + data, + functionName: 'totalSupply', + }) + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "totalSupply" reverted with the following reason: An \`assert\` condition failed. Version: viem@x.y.z] `) + + expect(err.rawData).toEqual(data) }) test('data: custom error', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - data: '0xdb731cf4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000066275676765720000000000000000000000000000000000000000000000000000', - functionName: 'customComplexError', - }), - ).toMatchInlineSnapshot(` + const data = + '0xdb731cf4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000066275676765720000000000000000000000000000000000000000000000000000' + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + data, + functionName: 'customComplexError', + }) + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "customComplexError" reverted. Error: ComplexError((address sender, uint256 bar), string message, uint256 number) @@ -376,39 +385,47 @@ describe('ContractFunctionRevertedError', () => { Version: viem@x.y.z] `) + + expect(err.rawData).toEqual(data) }) test('data: zero data', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - data: '0x', - functionName: 'customComplexError', - }), - ).toMatchInlineSnapshot(` + const data = '0x' + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + data, + functionName: 'customComplexError', + }) + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "customComplexError" reverted. Version: viem@x.y.z] `) + + expect(err.rawData).toEqual(data) }) test('data: error signature does not exist on ABI', () => { - expect( - new ContractFunctionRevertedError({ - abi: ErrorsExample.abi, - data: '0xdb731cfa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000066275676765720000000000000000000000000000000000000000000000000000', - functionName: 'totalSupply', - }), - ).toMatchInlineSnapshot(` + const data = + '0xdb731cfa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000066275676765720000000000000000000000000000000000000000000000000000' + const err = new ContractFunctionRevertedError({ + abi: ErrorsExample.abi, + data, + functionName: 'totalSupply', + }) + expect(err).toMatchInlineSnapshot(` [ContractFunctionRevertedError: The contract function "totalSupply" reverted with the following signature: 0xdb731cfa Unable to decode signature "0xdb731cfa" as it was not found on the provided ABI. Make sure you are using the correct ABI and that the error exists on it. You can look up the decoded signature here: https://openchain.xyz/signatures?query=0xdb731cfa. - + Raw revert data: "${data}" + Docs: https://viem.sh/docs/contract/decodeErrorResult Version: viem@x.y.z] `) + + expect(err.rawData).toEqual(data) }) }) diff --git a/src/errors/contract.ts b/src/errors/contract.ts index 9ab413ffb1..9b2ba90490 100644 --- a/src/errors/contract.ts +++ b/src/errors/contract.ts @@ -170,6 +170,7 @@ export type ContractFunctionRevertedErrorType = } export class ContractFunctionRevertedError extends BaseError { data?: DecodeErrorResultReturnType | undefined + rawData?: Hex | undefined reason?: string | undefined signature?: Hex | undefined @@ -232,6 +233,7 @@ export class ContractFunctionRevertedError extends BaseError { `Unable to decode signature "${signature}" as it was not found on the provided ABI.`, 'Make sure you are using the correct ABI and that the error exists on it.', `You can look up the decoded signature here: https://openchain.xyz/signatures?query=${signature}.`, + `Raw revert data: "${data}"`, ] } @@ -251,6 +253,7 @@ export class ContractFunctionRevertedError extends BaseError { }, ) + this.rawData = data this.data = decodedData this.reason = reason this.signature = signature