From 7a6e49200949325f69a05b3bf465ce64eb41bb8d Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:09:32 +0200 Subject: [PATCH] Handle common cases for smart contract errors according to EIP 838 (#7155) * handle smart contracts errors `Error(string)` and `Panic(uint256)` * add tests * update CHANGELOG.md --- packages/web3-eth-abi/CHANGELOG.md | 5 ++- .../src/decode_contract_error_data.ts | 30 ++++++++++++++++ packages/web3-eth-abi/test/fixtures/data.ts | 34 +++++++++++++++++++ .../test/unit/decodeContractErrorData.test.ts | 8 ++++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/web3-eth-abi/CHANGELOG.md b/packages/web3-eth-abi/CHANGELOG.md index 6d04047ddef..1bb5910ba18 100644 --- a/packages/web3-eth-abi/CHANGELOG.md +++ b/packages/web3-eth-abi/CHANGELOG.md @@ -182,4 +182,7 @@ Documentation: - fix encodedata in EIP-712 (#7095) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Added +- Handle common cases for smart contract errors according to EIP 838: `0x4e487b71` which is the ‘selector’ for `Panic(uint256)` and `0x08c379a0` is the ‘selector’ of `Error(string)`. (7155) diff --git a/packages/web3-eth-abi/src/decode_contract_error_data.ts b/packages/web3-eth-abi/src/decode_contract_error_data.ts index 98eb02d4fd8..11566b79645 100644 --- a/packages/web3-eth-abi/src/decode_contract_error_data.ts +++ b/packages/web3-eth-abi/src/decode_contract_error_data.ts @@ -39,6 +39,36 @@ export const decodeContractErrorData = ( errorSignature = jsonInterfaceMethodToString(errorAbi); // decode abi.inputs according to EIP-838 errorArgs = decodeParameters([...errorAbi.inputs], error.data.substring(10)); + } else if (error.data.startsWith('0x08c379a0')) { + // If ABI was not provided, check for the 2 famous errors: 'Error(string)' or 'Panic(uint256)' + + errorName = 'Error'; + errorSignature = 'Error(string)'; + // decode abi.inputs according to EIP-838 + errorArgs = decodeParameters( + [ + { + name: 'message', + type: 'string', + }, + ], + error.data.substring(10), + ); + } else if (error.data.startsWith('0x4e487b71')) { + errorName = 'Panic'; + errorSignature = 'Panic(uint256)'; + // decode abi.inputs according to EIP-838 + errorArgs = decodeParameters( + [ + { + name: 'code', + type: 'uint256', + }, + ], + error.data.substring(10), + ); + } else { + console.error('No matching error abi found for error data', error.data); } } catch (err) { console.error(err); diff --git a/packages/web3-eth-abi/test/fixtures/data.ts b/packages/web3-eth-abi/test/fixtures/data.ts index 0fe90638f95..f8990a22e86 100644 --- a/packages/web3-eth-abi/test/fixtures/data.ts +++ b/packages/web3-eth-abi/test/fixtures/data.ts @@ -974,6 +974,40 @@ export const validDecodeContractErrorData: { input: any[]; output: any; }[] = [ + { + input: [ + [], + { + code: 12, + message: 'message', + data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320612063616c6c207265766572740000000000000000000000', + }, + ], + output: { + errorName: 'Error', + errorSignature: 'Error(string)', + errorArgs: { + message: 'This is a call revert', + }, + }, + }, + { + input: [ + [], + { + code: 12, + message: 'message', + data: '0x4e487b71000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000', + }, + ], + output: { + errorName: 'Panic', + errorSignature: 'Panic(uint256)', + errorArgs: { + code: 42, + }, + }, + }, { input: [ [ diff --git a/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts b/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts index 3f6a624ef22..9e99add02f3 100644 --- a/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts @@ -32,7 +32,13 @@ describe('decodeContractErrorData', () => { expect(err.errorName).toEqual(output.errorName); expect(err.errorSignature).toEqual(output.errorSignature); expect(err.errorArgs?.message).toEqual(output.errorArgs?.message); - expect(Number(err.errorArgs?.code)).toEqual(output.errorArgs?.code); + + // This ensures they are equal if one was provided + // It also skips if both are not provided + if (err.errorArgs?.code || output.errorArgs?.code) { + // eslint-disable-next-line jest/no-conditional-expect + expect(Number(err.errorArgs?.code)).toEqual(output.errorArgs?.code); + } expect(err.cause?.code).toEqual(output.cause?.code); }, );