Skip to content

Commit

Permalink
Parse nested error for Smartcontract errors (#6045)
Browse files Browse the repository at this point in the history
* get nested `data` at `Eip838ExecutionError`

* modify unit test for nested error of Eip838ExecutionError

* update CHANGELOG.md

* add unit test for Eip838ExecutionError
  • Loading branch information
Muhammad-Altabba authored May 5, 2023
1 parent d4b4b8b commit 667668a
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 24 deletions.
4 changes: 4 additions & 0 deletions packages/web3-errors/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `gasLimit` is no longer accepted as a parameter for `MissingGasError` and `TransactionGasMismatchError, and is also no longer included in error message (#5915)

## [Unreleased]

### Changed

- Nested Smart Contract error data is extracted at `Eip838ExecutionError` constructor and the nested error is set at `innerError` (#6045)
71 changes: 61 additions & 10 deletions packages/web3-errors/src/errors/contract_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export class ContractInstantiationError extends BaseWeb3Error {
public code = ERR_CONTRACT_INSTANTIATION;
}

export type ProviderErrorData =
| HexString
| { data: HexString }
| { originalError: { data: HexString } };

/**
* 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
Expand All @@ -138,11 +143,35 @@ export class Eip838ExecutionError extends Web3ContractError {
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;
// eslint-disable-next-line no-use-before-define
public innerError: Eip838ExecutionError | undefined;

public constructor(error: JsonRpcError<ProviderErrorData> | Eip838ExecutionError) {
super(error.message || 'Error');

this.name = ('name' in error && error.name) || this.constructor.name;
this.stack = ('stack' in error && error.stack) || undefined;
this.code = error.code;

// get embedded error details got from some providers like MetaMask
// and set this.data from the inner error data for easier read.
// note: the data is a hex string inside either:
// error.data, error.data.data or error.data.originalError.data (https://github.com/web3/web3.js/issues/4454#issuecomment-1485953455)
if (typeof error.data === 'object') {
let originalError: { data: string };
if ('originalError' in error.data) {
originalError = error.data.originalError;
} else {
// Ganache has no `originalError` sub-object unlike others
originalError = error.data;
}
this.data = originalError.data;
this.innerError = new Eip838ExecutionError(
originalError as JsonRpcError<ProviderErrorData>,
);
} else {
this.data = error.data;
}
}

public setDecodedProperties(
Expand All @@ -154,6 +183,32 @@ export class Eip838ExecutionError extends Web3ContractError {
this.errorSignature = errorSignature;
this.errorArgs = errorArgs;
}

public toJSON() {
let json = {
...super.toJSON(),
data: this.data,
} as {
name: string;
code: number;
message: string;
innerError: Error | Error[] | undefined;
data: string;
errorName?: string;
errorSignature?: string;
errorArgs?: { [K in string]: unknown };
};

if (this.errorName) {
json = {
...json,
errorName: this.errorName,
errorSignature: this.errorSignature,
errorArgs: this.errorArgs,
};
}
return json;
}
}

/**
Expand All @@ -166,11 +221,7 @@ export class ContractExecutionError extends Web3ContractError {
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,
);
this.innerError = new Eip838ExecutionError(rpcError as JsonRpcError<ProviderErrorData>);
}
}

Expand Down
20 changes: 20 additions & 0 deletions packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ Object {
}
`;

exports[`errors Eip838ExecutionError should get the data from error.data.data 1`] = `
Object {
"code": undefined,
"data": "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000",
"innerError": [Eip838ExecutionError: Error],
"message": "Error",
"name": "Eip838ExecutionError",
}
`;

exports[`errors Eip838ExecutionError should get the data from error.data.originalError.data 1`] = `
Object {
"code": undefined,
"data": "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000",
"innerError": [Eip838ExecutionError: Error],
"message": "Error",
"name": "Eip838ExecutionError",
}
`;

exports[`errors InvalidConnectionError should have valid json structure 1`] = `
Object {
"code": 501,
Expand Down
24 changes: 24 additions & 0 deletions packages/web3-errors/test/unit/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { JsonRpcError } from 'web3-types';
import * as accountErrors from '../../src/errors/account_errors';
import * as connectionErrors from '../../src/errors/connection_errors';
import * as contractErrors from '../../src/errors/contract_errors';
Expand Down Expand Up @@ -286,6 +287,29 @@ describe('errors', () => {
});
});

describe('Eip838ExecutionError', () => {
it('should get the data from error.data.data', () => {
expect(
new contractErrors.Eip838ExecutionError({
data: {
data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000',
},
} as JsonRpcError<contractErrors.ProviderErrorData>).toJSON(),
).toMatchSnapshot();
});
it('should get the data from error.data.originalError.data', () => {
expect(
new contractErrors.Eip838ExecutionError({
data: {
originalError: {
data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000',
},
},
} as JsonRpcError<contractErrors.ProviderErrorData>).toJSON(),
).toMatchSnapshot();
});
});

describe('ResponseError', () => {
it('should have valid json structure with data', () => {
expect(
Expand Down
8 changes: 4 additions & 4 deletions packages/web3-eth-abi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [4.0.1-alpha.5]

### Changed

- web3.js dependencies (#5757)

## [4.0.1-rc.0]

### Removed
Expand All @@ -100,3 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed `formatDecodedObject` function (#5934)

## [Unreleased]

### Changed

- Nested Smart Contract error data hex string is decoded when the error contains the data as object (when the data hex string is inside data.originalError.data or data.data) (#6045)
70 changes: 70 additions & 0 deletions packages/web3-eth-abi/test/fixtures/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,76 @@ export const validDecodeContractErrorData: {
},
},
},
{
input: [
[
{ inputs: [], name: 'ErrorWithNoParams', type: 'error' },
{
inputs: [
{ name: 'code', type: 'uint256' },
{ name: 'message', type: 'string' },
],
name: 'ErrorWithParams',
type: 'error',
},
],
{
code: 12,
message: 'message',
data: {
code: -32000,
data: '0xc85bda60000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001c5468697320697320616e206572726f72207769746820706172616d7300000000',
},
},
],
output: {
errorName: 'ErrorWithParams',
errorSignature: 'ErrorWithParams(uint256,string)',
errorArgs: {
code: 42,
message: 'This is an error with params',
},
innerError: {
code: -32000,
},
},
},
{
input: [
[
{ inputs: [], name: 'ErrorWithNoParams', type: 'error' },
{
inputs: [
{ name: 'code', type: 'uint256' },
{ name: 'message', type: 'string' },
],
name: 'ErrorWithParams',
type: 'error',
},
],
{
code: 12,
message: 'message',
data: {
originalError: {
code: 3,
data: '0xc85bda60000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001c5468697320697320616e206572726f72207769746820706172616d7300000000',
},
},
},
],
output: {
errorName: 'ErrorWithParams',
errorSignature: 'ErrorWithParams(uint256,string)',
errorArgs: {
code: 42,
message: 'This is an error with params',
},
innerError: {
code: 3,
},
},
},
];

export const invalidDecodeContractErrorData: {
Expand Down
13 changes: 3 additions & 10 deletions packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,15 @@ describe('decodeContractErrorData', () => {
it.each(validDecodeContractErrorData)(
'%#: should pass for valid values: %j',
({ input: [abi, errorData], output }) => {
const err = new Eip838ExecutionError(
errorData.code,
errorData.message,
errorData.data,
);
const err = new Eip838ExecutionError(errorData);

decodeContractErrorData(abi, err);

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);
expect(err.innerError?.code).toEqual(output.innerError?.code);
},
);
});
Expand All @@ -47,11 +44,7 @@ describe('decodeContractErrorData', () => {
({ input: [abi, errorData] }) => {
// mock console.error
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => 'error');
const err = new Eip838ExecutionError(
errorData.code,
errorData.message,
errorData.data,
);
const err = new Eip838ExecutionError(errorData);
decodeContractErrorData(abi, err);
expect(consoleSpy).toHaveBeenCalled();
},
Expand Down

0 comments on commit 667668a

Please sign in to comment.