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)'); + }); + }); +});