diff --git a/CHANGELOG.md b/CHANGELOG.md
index d63bb804723..fd341cac29c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -736,17 +736,31 @@ should use 4.0.1-alpha.0 for testing.
### Added
+#### web3-core
+
+- If the response error was `execution reverted`, raise `ContractExecutionError` and pass the response error to it in order to be set as `innerError` (this innerError will be decoded at web3-eth-contract if its ABI was provided according to EIP-838). (#5434)
+
#### web3-error
-- Add optional `innerError` property to the abstract class `Web3Error`.
+- Add optional `innerError` property to the abstract class `Web3Error`. This `innerError` could be `Error`, `Error[]` or `undefined`. (#5435) (#5434)
+- The class `Web3ContractError` is moved to this package from `web3-eth-contract`. (#5434)
+
+#### web3-eth-abi
+
+- If an error happens when decoding a value, preserve that exception at `innerError` inside the error class `AbiError`. (#5435)
+- Add basic functionality that is used, by `web3-eth-contract`, when decoding error data according to EIP-838. (#5434)
+
+#### web3-eth-contract
+
+- Decoding error data, using Error ABI if available, according to EIP-838. (#5434)
+- The class `Web3ContractError` is moved from this package to `web3-error`. (#5434)
### Fixed
#### web3-eth-contract
-- According to the latest change in `web3-eth-abi`, the decoded values of the large numbers, returned from function calls or events, are now available as `BigInt`.
+- According to the latest change in `web3-eth-abi`, the decoded values of the large numbers, returned from function calls or events, are now available as `BigInt`. (#5435)
#### web3-eth-abi
-- Return `BigInt` instead of `string` when decoding function parameters for large numbers, such as `uint256`.
-- If an error happens when decoding a value, preserve that exception at `innerError` inside the `AbiError`.
+- Return `BigInt` instead of `string` when decoding function parameters for large numbers, such as `uint256`. (#5435)
diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md
index dc93264a3e0..820e90601d9 100644
--- a/packages/web3-core/CHANGELOG.md
+++ b/packages/web3-core/CHANGELOG.md
@@ -34,3 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- I've improved the security in XY (#1000)
-->
+
+## [Unreleased]
+
+### Added
+
+- If the response error was `execution reverted`, raise `ContractExecutionError` and pass the response error to it in order to be set as `innerError` (this innerError will be decoded at web3-eth-contract if its ABI was provided according to EIP-838). (#5434)
diff --git a/packages/web3-core/src/web3_request_manager.ts b/packages/web3-core/src/web3_request_manager.ts
index 47e943a16c1..eb448c85ebd 100644
--- a/packages/web3-core/src/web3_request_manager.ts
+++ b/packages/web3-core/src/web3_request_manager.ts
@@ -16,7 +16,12 @@ along with web3.js. If not, see .
*/
import { Socket } from 'net';
-import { InvalidResponseError, ProviderError, ResponseError } from 'web3-errors';
+import {
+ ContractExecutionError,
+ InvalidResponseError,
+ ProviderError,
+ ResponseError,
+} from 'web3-errors';
import HttpProvider from 'web3-providers-http';
import IpcProvider from 'web3-providers-ipc';
import WSProvider from 'web3-providers-ws';
@@ -26,6 +31,7 @@ import {
JsonRpcBatchResponse,
JsonRpcPayload,
JsonRpcResponse,
+ JsonRpcResponseWithError,
SupportedProviders,
Web3APIMethod,
Web3APIPayload,
@@ -287,7 +293,16 @@ export class Web3RequestManager<
// This is the majority of the cases so check these first
// A valid JSON-RPC response with error object
if (jsonRpc.isResponseWithError(response)) {
- throw new InvalidResponseError(response);
+ if (
+ (response.error as unknown as { message: string })?.message === 'execution reverted'
+ ) {
+ // This message means that there was an error while executing the code of the smart contract
+ // However, more processing will happen at a higher level to decode the error data,
+ // according to the Error ABI, if it was available as of EIP-838.
+ throw new ContractExecutionError((response as JsonRpcResponseWithError).error);
+ } else {
+ throw new InvalidResponseError(response);
+ }
}
// This is the majority of the cases so check these first
diff --git a/packages/web3-errors/CHANGELOG.md b/packages/web3-errors/CHANGELOG.md
index 58dd54d066b..8d761f4208d 100644
--- a/packages/web3-errors/CHANGELOG.md
+++ b/packages/web3-errors/CHANGELOG.md
@@ -39,4 +39,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-- Add optional `innerError` property to the abstract class `Web3Error`.
+- Add optional `innerError` property to the abstract class `Web3Error`. This `innerError` could be `Error`, `Error[]` or `undefined`. (#5435) (#5434)
+- The class `Web3ContractError` is moved to this package from `web3-eth-contract`. (#5434)
diff --git a/packages/web3-errors/src/error_codes.ts b/packages/web3-errors/src/error_codes.ts
index 1bf85e6988d..f90ec91d915 100644
--- a/packages/web3-errors/src/error_codes.ts
+++ b/packages/web3-errors/src/error_codes.ts
@@ -38,6 +38,7 @@ export const ERR_CONTRACT_MISSING_DEPLOY_DATA = 306;
export const ERR_CONTRACT_MISSING_ADDRESS = 307;
export const ERR_CONTRACT_MISSING_FROM_ADDRESS = 308;
export const ERR_CONTRACT_INSTANTIATION = 309;
+export const ERR_CONTRACT_EXECUTION_REVERTED = 310;
// Transaction error codes
export const ERR_TX = 400;
@@ -75,6 +76,7 @@ export const ERR_TX_LOCAL_WALLET_NOT_AVAILABLE = 429;
export const ERR_TX_NOT_FOUND = 430;
export const ERR_TX_SEND_TIMEOUT = 431;
+
// Connection error codes
export const ERR_CONN = 500;
export const ERR_CONN_INVALID = 501;
diff --git a/packages/web3-errors/src/errors/contract_errors.ts b/packages/web3-errors/src/errors/contract_errors.ts
index 048a5290e91..6134c820bbe 100644
--- a/packages/web3-errors/src/errors/contract_errors.ts
+++ b/packages/web3-errors/src/errors/contract_errors.ts
@@ -17,8 +17,11 @@ along with web3.js. If not, see .
/* eslint-disable max-classes-per-file */
+import { JsonRpcError, TransactionReceipt, HexString } from 'web3-types';
import {
+ ERR_CONTRACT,
ERR_CONTRACT_ABI_MISSING,
+ ERR_CONTRACT_EXECUTION_REVERTED,
ERR_CONTRACT_EVENT_NOT_EXISTS,
ERR_CONTRACT_INSTANTIATION,
ERR_CONTRACT_MISSING_ADDRESS,
@@ -30,6 +33,16 @@ import {
} from '../error_codes';
import { Web3Error } from '../web3_error_base';
+export class Web3ContractError extends Web3Error {
+ public code = ERR_CONTRACT;
+ public receipt?: TransactionReceipt;
+
+ public constructor(message: string, receipt?: TransactionReceipt) {
+ super(message);
+
+ this.receipt = receipt;
+ }
+}
export class ResolverMethodMissingError extends Web3Error {
public code = ERR_CONTRACT_RESOLVER_MISSING;
@@ -111,3 +124,51 @@ export class ContractNoFromAddressDefinedError extends Web3Error {
export class ContractInstantiationError extends Web3Error {
public code = ERR_CONTRACT_INSTANTIATION;
}
+
+/**
+ * This class is expected to be set as an `innerError` inside ContractExecutionError
+ * The properties would be typically decoded from the `data` if it was encoded according to EIP-838
+ */
+export class Eip838ExecutionError extends Web3ContractError {
+ public readonly name: string;
+ public code: number;
+ public data?: HexString;
+ public errorName?: string;
+ public errorSignature?: string;
+ public errorArgs?: { [K in string]: unknown };
+
+ public constructor(code: number, message: string, data?: HexString) {
+ super(message);
+ this.name = this.constructor.name;
+ this.code = code;
+ this.data = data;
+ }
+
+ public setDecodedProperties(
+ errorName: string,
+ errorSignature?: string,
+ errorArgs?: { [K in string]: unknown },
+ ) {
+ this.errorName = errorName;
+ this.errorSignature = errorSignature;
+ this.errorArgs = errorArgs;
+ }
+}
+
+/**
+ * Used when an error is raised while executing a function inside a smart contract.
+ * The data is expected to be encoded according to EIP-848.
+ */
+export class ContractExecutionError extends Web3ContractError {
+ public innerError: Eip838ExecutionError;
+
+ public constructor(rpcError: JsonRpcError) {
+ super('Error happened while trying to execute a function inside a smart contract');
+ this.code = ERR_CONTRACT_EXECUTION_REVERTED;
+ this.innerError = new Eip838ExecutionError(
+ rpcError.code,
+ rpcError.message,
+ rpcError.data as string,
+ );
+ }
+}
diff --git a/packages/web3-errors/src/errors/response_errors.ts b/packages/web3-errors/src/errors/response_errors.ts
index efc461f07f5..930ad296e77 100644
--- a/packages/web3-errors/src/errors/response_errors.ts
+++ b/packages/web3-errors/src/errors/response_errors.ts
@@ -16,7 +16,7 @@ along with web3.js. If not, see .
*/
// eslint-disable-next-line max-classes-per-file
-import { JsonRpcResponse, JsonRpcResponseWithError } from 'web3-types';
+import { JsonRpcError, JsonRpcResponse, JsonRpcResponseWithError } from 'web3-types';
import { Web3Error } from '../web3_error_base';
import { ERR_INVALID_RESPONSE, ERR_RESPONSE } from '../error_codes';
@@ -65,9 +65,15 @@ export class ResponseError extends Web3Error {
export class InvalidResponseError extends ResponseError {
public constructor(result: JsonRpcResponse) {
super(result);
- if (!this.message || this.message === '') {
- this.message = `Invalid JSON RPC response: ${JSON.stringify(result)}`;
- }
this.code = ERR_INVALID_RESPONSE;
+
+ let errorOrErrors: JsonRpcError | JsonRpcError[] | undefined;
+ if (`error` in result) {
+ errorOrErrors = result.error as JsonRpcError;
+ } else if (result instanceof Array) {
+ errorOrErrors = result.map(r => r.error) as JsonRpcError[];
+ }
+
+ this.innerError = errorOrErrors as Error | Error[] | undefined;
}
}
diff --git a/packages/web3-errors/src/web3_error_base.ts b/packages/web3-errors/src/web3_error_base.ts
index eaaf87dc593..1c24d157a00 100644
--- a/packages/web3-errors/src/web3_error_base.ts
+++ b/packages/web3-errors/src/web3_error_base.ts
@@ -23,9 +23,9 @@ export abstract class Web3Error extends Error implements ErrorInterface {
public readonly name: string;
public abstract readonly code: number;
public stack: string | undefined;
- public innerError: Error | undefined;
+ public innerError: Error | Error[] | undefined;
- public constructor(msg?: string, innerError?: Error) {
+ public constructor(msg?: string, innerError?: Error | Error[]) {
super(msg);
this.innerError = innerError;
this.name = this.constructor.name;
diff --git a/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap b/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap
index f5de39c141a..9e6705c7e85 100644
--- a/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap
+++ b/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap
@@ -162,7 +162,14 @@ Object {
"a": "10",
"b": "20",
},
- "innerError": undefined,
+ "innerError": Object {
+ "code": 123,
+ "data": Object {
+ "a": "10",
+ "b": "20",
+ },
+ "message": "error message",
+ },
"message": "Returned error: error message",
"name": "InvalidResponseError",
}
diff --git a/packages/web3-eth-abi/CHANGELOG.md b/packages/web3-eth-abi/CHANGELOG.md
index 7c2c0e3f978..6690ca81543 100644
--- a/packages/web3-eth-abi/CHANGELOG.md
+++ b/packages/web3-eth-abi/CHANGELOG.md
@@ -39,7 +39,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
-#### web3-eth-abi
+### Added
+
+- If an error happens when decoding a value, preserve that exception at `innerError` inside the error class `AbiError`. (#5435)
+- Add basic functionality that is used, by `web3-eth-contract`, when decoding error data according to EIP-838. (#5434)
+
+### Fixed
-- Return `BigInt` instead of `string` when decoding function parameters for large numbers, such as `uint256`.
-- If an error happens when decoding a value, preserve that exception at `innerError` inside the `AbiError`.
+- Return `BigInt` instead of `string` when decoding function parameters for large numbers, such as `uint256`. (#5435)
diff --git a/packages/web3-eth-abi/src/api/errors_api.ts b/packages/web3-eth-abi/src/api/errors_api.ts
new file mode 100644
index 00000000000..038b4cb3249
--- /dev/null
+++ b/packages/web3-eth-abi/src/api/errors_api.ts
@@ -0,0 +1,40 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import { sha3Raw } from 'web3-utils';
+import { AbiError } from 'web3-errors';
+import { AbiErrorFragment } from '../types';
+import { jsonInterfaceMethodToString, isAbiErrorFragment } from '../utils';
+
+/**
+ * Encodes the error name to its ABI signature, which are the sha3 hash of the error name including input types.
+ */
+export const encodeErrorSignature = (functionName: string | AbiErrorFragment): string => {
+ if (typeof functionName !== 'string' && !isAbiErrorFragment(functionName)) {
+ throw new AbiError('Invalid parameter value in encodeErrorSignature');
+ }
+
+ let name: string;
+
+ if (functionName && (typeof functionName === 'function' || typeof functionName === 'object')) {
+ name = jsonInterfaceMethodToString(functionName);
+ } else {
+ name = functionName;
+ }
+
+ return sha3Raw(name);
+};
diff --git a/packages/web3-eth-abi/src/index.ts b/packages/web3-eth-abi/src/index.ts
index 579fd5d156f..7dfb6117061 100644
--- a/packages/web3-eth-abi/src/index.ts
+++ b/packages/web3-eth-abi/src/index.ts
@@ -16,6 +16,7 @@ along with web3.js. If not, see .
*/
export * from './types';
+export * from './api/errors_api';
export * from './api/events_api';
export * from './api/functions_api';
export * from './api/logs_api';
diff --git a/packages/web3-eth-abi/src/types.ts b/packages/web3-eth-abi/src/types.ts
index 89bf69b3964..926fdc66402 100644
--- a/packages/web3-eth-abi/src/types.ts
+++ b/packages/web3-eth-abi/src/types.ts
@@ -88,11 +88,19 @@ export type AbiEventFragment = AbiBaseFragment & {
readonly anonymous?: boolean;
};
+// https://docs.soliditylang.org/en/latest/abi-spec.html#errors
+export type AbiErrorFragment = AbiBaseFragment & {
+ readonly name: string;
+ readonly type: string | 'error';
+ readonly inputs?: ReadonlyArray;
+};
+
// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type AbiFragment =
| AbiConstructorFragment
| AbiFunctionFragment
| AbiEventFragment
+ | AbiErrorFragment
| AbiFallbackFragment;
export type ContractAbi = ReadonlyArray;
diff --git a/packages/web3-eth-abi/src/utils.ts b/packages/web3-eth-abi/src/utils.ts
index 10e0f830895..e94a79ee752 100644
--- a/packages/web3-eth-abi/src/utils.ts
+++ b/packages/web3-eth-abi/src/utils.ts
@@ -34,7 +34,13 @@ export const isAbiFragment = (item: unknown): item is AbiFragment =>
!isNullish(item) &&
typeof item === 'object' &&
!isNullish((item as { type: string }).type) &&
- ['function', 'event', 'constructor'].includes((item as { type: string }).type);
+ ['function', 'event', 'constructor', 'error'].includes((item as { type: string }).type);
+
+export const isAbiErrorFragment = (item: unknown): item is AbiEventFragment =>
+ !isNullish(item) &&
+ typeof item === 'object' &&
+ !isNullish((item as { type: string }).type) &&
+ (item as { type: string }).type === 'error';
export const isAbiEventFragment = (item: unknown): item is AbiEventFragment =>
!isNullish(item) &&
@@ -278,7 +284,7 @@ export const flattenTypes = (
* returns a string
*/
export const jsonInterfaceMethodToString = (json: AbiFragment): string => {
- if (isAbiEventFragment(json) || isAbiFunctionFragment(json)) {
+ if (isAbiErrorFragment(json) || isAbiEventFragment(json) || isAbiFunctionFragment(json)) {
if (json.name?.includes('(')) {
return json.name;
}
diff --git a/packages/web3-eth-contract/CHANGELOG.md b/packages/web3-eth-contract/CHANGELOG.md
index f8e82648fbb..ce845467323 100644
--- a/packages/web3-eth-contract/CHANGELOG.md
+++ b/packages/web3-eth-contract/CHANGELOG.md
@@ -173,6 +173,11 @@ const transactionHash = receipt.transactionHash;
## [Unreleased]
+### Added
+
+- Decoding error data, using Error ABI if available, according to EIP-838. (#5434)
+- The class `Web3ContractError` is moved from this package to `web3-error`. (#5434)
+
### Fixed
-- According to the latest change in `web3-eth-abi`, the decoded values of the large numbers, returned from function calls or events, are now available as `BigInt`.
+- According to the latest change in `web3-eth-abi`, the decoded values of the large numbers, returned from function calls or events, are now available as `BigInt`. (#5435)
diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts
index 72e330f6d9f..999a2c621eb 100644
--- a/packages/web3-eth-contract/src/contract.ts
+++ b/packages/web3-eth-contract/src/contract.ts
@@ -16,7 +16,7 @@ along with web3.js. If not, see .
*/
import { Web3Context, Web3EventEmitter, Web3PromiEvent } from 'web3-core';
-import { SubscriptionError } from 'web3-errors';
+import { ContractExecutionError, SubscriptionError, Web3ContractError } from 'web3-errors';
import {
call,
estimateGas,
@@ -27,6 +27,7 @@ import {
} from 'web3-eth';
import {
AbiConstructorFragment,
+ AbiErrorFragment,
AbiEventFragment,
AbiFragment,
AbiFunctionFragment,
@@ -38,6 +39,7 @@ import {
encodeEventSignature,
encodeFunctionSignature,
FilterAbis,
+ isAbiErrorFragment,
isAbiEventFragment,
isAbiFunctionFragment,
jsonInterfaceMethodToString,
@@ -62,8 +64,13 @@ import {
} from 'web3-utils';
import { isNullish, validator, utils as validatorUtils } from 'web3-validator';
import { ALL_EVENTS_ABI } from './constants';
-import { decodeEventABI, decodeMethodReturn, encodeEventABI, encodeMethodABI } from './encoding';
-import { Web3ContractError } from './errors';
+import {
+ decodeEventABI,
+ decodeMethodReturn,
+ encodeEventABI,
+ encodeMethodABI,
+ decodeErrorData,
+} from './encoding';
import { LogsSubscription } from './log_subscription';
import {
ContractAbiWithSignature,
@@ -865,7 +872,11 @@ export class Contract
let result: ContractAbi = [];
- for (const a of abis) {
+ const functionsAbi = abis.filter(abi => abi.type !== 'error');
+ const errorsAbi = abis.filter(abi =>
+ isAbiErrorFragment(abi),
+ ) as unknown as AbiErrorFragment[];
+ for (const a of functionsAbi) {
const abi: Mutable = {
...a,
signature: '',
@@ -887,13 +898,13 @@ export class Contract
if (methodName in this._functions) {
this._functions[methodName] = {
signature: methodSignature,
- method: this._createContractMethod(abi),
+ method: this._createContractMethod(abi, errorsAbi),
cascadeFunction: this._functions[methodName].method,
};
} else {
this._functions[methodName] = {
signature: methodSignature,
- method: this._createContractMethod(abi),
+ method: this._createContractMethod(abi, errorsAbi),
};
}
@@ -934,7 +945,10 @@ export class Contract
this._jsonInterface = [...result] as unknown as ContractAbiWithSignature;
}
- private _createContractMethod(abi: T): ContractBoundMethod {
+ private _createContractMethod(
+ abi: T,
+ errorsAbis: E[],
+ ): ContractBoundMethod {
return (...params: unknown[]) => {
let abiParams!: Array;
@@ -952,9 +966,9 @@ export class Contract
return {
arguments: params,
call: async (options?: PayableCallOptions, block?: BlockNumberOrTag) =>
- this._contractMethodCall(abi, params, options, block),
+ this._contractMethodCall(abi, params, errorsAbis, options, block),
send: (options?: PayableTxOptions) =>
- this._contractMethodSend(abi, params, options),
+ this._contractMethodSend(abi, params, options), // TODO: refactor to parse errorsAbi
estimateGas: async <
ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT,
>(
@@ -971,9 +985,9 @@ export class Contract
return {
arguments: abiParams,
call: async (options?: NonPayableCallOptions, block?: BlockNumberOrTag) =>
- this._contractMethodCall(abi, params, options, block),
+ this._contractMethodCall(abi, params, errorsAbis, options, block),
send: (options?: NonPayableTxOptions) =>
- this._contractMethodSend(abi, params, options),
+ this._contractMethodSend(abi, params, options), // TODO: refactor to parse errorsAbi
estimateGas: async (
options?: NonPayableCallOptions,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
@@ -986,9 +1000,13 @@ export class Contract
};
}
- private async _contractMethodCall(
+ private async _contractMethodCall<
+ E extends AbiErrorFragment,
+ Options extends PayableCallOptions | NonPayableCallOptions,
+ >(
abi: AbiFunctionFragment,
params: unknown[],
+ errorsAbi: E[],
options?: Options,
block?: BlockNumberOrTag,
) {
@@ -998,8 +1016,16 @@ export class Contract
options,
contractOptions: this.options,
});
-
- return decodeMethodReturn(abi, await call(this, tx, block, DEFAULT_RETURN_FORMAT));
+ try {
+ const result = await call(this, tx, block, DEFAULT_RETURN_FORMAT);
+ return decodeMethodReturn(abi, result);
+ } catch (error: unknown) {
+ if (error instanceof ContractExecutionError) {
+ // this will parse the error data by trying to decode the ABI error inputs according to EIP-838
+ decodeErrorData(errorsAbi, error.innerError);
+ }
+ throw error;
+ }
}
private _contractMethodSend(
diff --git a/packages/web3-eth-contract/src/encoding.ts b/packages/web3-eth-contract/src/encoding.ts
index e59fab2db2a..95fdf61a3dc 100644
--- a/packages/web3-eth-contract/src/encoding.ts
+++ b/packages/web3-eth-contract/src/encoding.ts
@@ -21,10 +21,12 @@ import { LogsInput, BlockNumberOrTag, Filter, HexString, Topic, Numbers } from '
import {
AbiConstructorFragment,
+ AbiErrorFragment,
AbiEventFragment,
AbiFunctionFragment,
decodeLog,
decodeParameters,
+ encodeErrorSignature,
encodeEventSignature,
encodeFunctionSignature,
encodeParameter,
@@ -35,7 +37,7 @@ import {
import { blockSchema, logSchema } from 'web3-eth/dist/schemas';
-import { Web3ContractError } from './errors';
+import { Eip838ExecutionError, Web3ContractError } from 'web3-errors';
// eslint-disable-next-line import/no-cycle
import { ContractAbiWithSignature, ContractOptions, EventLog } from './types';
@@ -233,3 +235,27 @@ export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexS
return result;
};
+
+export const decodeErrorData = (errorsAbi: AbiErrorFragment[], error: Eip838ExecutionError) => {
+ if (error?.data) {
+ let errorName: string | undefined;
+ let errorSignature: string | undefined;
+ let errorArgs: { [K in string]: unknown } | undefined;
+ try {
+ const errorSha = error.data.slice(0, 10);
+ const errorAbi = errorsAbi.find(abi => encodeErrorSignature(abi).startsWith(errorSha));
+
+ if (errorAbi?.inputs) {
+ errorName = errorAbi.name;
+ errorSignature = jsonInterfaceMethodToString(errorAbi);
+ // decode abi.inputs according to EIP-838
+ errorArgs = decodeParameters([...errorAbi.inputs], error.data.substring(10));
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ if (errorName) {
+ error.setDecodedProperties(errorName, errorSignature, errorArgs);
+ }
+ }
+};
diff --git a/packages/web3-eth-contract/src/errors.ts b/packages/web3-eth-contract/src/errors.ts
deleted file mode 100644
index 115e3aabd5e..00000000000
--- a/packages/web3-eth-contract/src/errors.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-This file is part of web3.js.
-
-web3.js is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-web3.js is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with web3.js. If not, see .
-*/
-
-import { TransactionReceipt } from 'web3-types';
-import { ERR_CONTRACT, Web3Error } from 'web3-errors';
-
-export class Web3ContractError extends Web3Error {
- public code = ERR_CONTRACT;
- public receipt?: TransactionReceipt;
-
- public constructor(message: string, receipt?: TransactionReceipt) {
- super(message);
-
- this.receipt = receipt;
- }
-}
diff --git a/packages/web3-eth-contract/src/utils.ts b/packages/web3-eth-contract/src/utils.ts
index ade3d19d700..c570c1b6fc5 100644
--- a/packages/web3-eth-contract/src/utils.ts
+++ b/packages/web3-eth-contract/src/utils.ts
@@ -15,11 +15,11 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import { TransactionWithSenderAPI, TransactionCall, HexString } from 'web3-types';
+import { Web3ContractError } from 'web3-errors';
import { AbiFunctionFragment } from 'web3-eth-abi';
+import { TransactionWithSenderAPI, TransactionCall, HexString } from 'web3-types';
import { isNullish, mergeDeep } from 'web3-utils';
import { encodeMethodABI } from './encoding';
-import { Web3ContractError } from './errors';
import {
NonPayableCallOptions,
PayableCallOptions,
diff --git a/packages/web3-eth-contract/test/integration/contract_methods_errors.test.ts b/packages/web3-eth-contract/test/integration/contract_methods_errors.test.ts
new file mode 100644
index 00000000000..51eb802f41a
--- /dev/null
+++ b/packages/web3-eth-contract/test/integration/contract_methods_errors.test.ts
@@ -0,0 +1,80 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import { ContractExecutionError, ERR_CONTRACT_EXECUTION_REVERTED } from 'web3-errors';
+import { Contract } from '../../src';
+import { createTempAccount } from '../fixtures/system_test_utils';
+
+describe('contract errors', () => {
+ let sendOptions: Record;
+
+ beforeAll(async () => {
+ const acc = await createTempAccount();
+ sendOptions = { from: acc.address };
+ });
+
+ describe('Test EIP-838 Error Codes', () => {
+ const addr = '0xbd0B4B009a76CA97766360F04f75e05A3E449f1E';
+ it('testError1', async () => {
+ const abi = [
+ {
+ inputs: [
+ { internalType: 'address', name: 'addr', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'TestError1',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'bool', name: 'pass', type: 'bool' },
+ { internalType: 'address', name: 'addr', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'testError1',
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
+ stateMutability: 'pure',
+ type: 'function',
+ },
+ ] as const;
+ const contract = new Contract(abi, addr);
+ contract.setProvider('https://ropsten.infura.io/v3/49a0efa3aaee4fd99797bfa94d8ce2f1');
+
+ let error: ContractExecutionError | undefined;
+ try {
+ await contract.methods.testError1(false, addr, 42).call(sendOptions);
+
+ // execution should throw before this line, if not throw here to indicate an issue.
+ } catch (err: any) {
+ error = err;
+ }
+
+ expect(error).toBeDefined();
+
+ expect(error?.code).toEqual(ERR_CONTRACT_EXECUTION_REVERTED);
+ expect(error?.innerError?.code).toBe(3);
+ expect(error?.innerError?.errorArgs && error?.innerError?.errorArgs[0]).toEqual(addr);
+ expect(error?.innerError?.errorArgs?.addr).toEqual(addr);
+ expect(error?.innerError?.errorArgs && error?.innerError?.errorArgs[1]).toEqual(
+ BigInt(42),
+ );
+ expect(error?.innerError?.errorArgs?.value).toEqual(BigInt(42));
+ expect(error?.innerError?.errorName).toBe('TestError1');
+ expect(error?.innerError?.errorSignature).toBe('TestError1(address,uint256)');
+ });
+ });
+});