From 60acf7dbeb128199c30c28a2e8972787519b330a Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 19:42:17 +0200 Subject: [PATCH 1/8] move `decodeMethodParams` and `decodeMethodReturn` to web3-eth-abi --- .../web3-eth-abi/src/api/functions_api.ts | 58 ++++++++++++++++++- .../src/contract-deployer-method-class.ts | 3 +- packages/web3-eth-contract/src/contract.ts | 11 ++-- packages/web3-eth-contract/src/encoding.ts | 48 ++------------- 4 files changed, 67 insertions(+), 53 deletions(-) diff --git a/packages/web3-eth-abi/src/api/functions_api.ts b/packages/web3-eth-abi/src/api/functions_api.ts index 7504f352275..9807da95cfe 100644 --- a/packages/web3-eth-abi/src/api/functions_api.ts +++ b/packages/web3-eth-abi/src/api/functions_api.ts @@ -19,11 +19,11 @@ along with web3.js. If not, see . * * @module ABI */ -import { AbiError } from 'web3-errors'; +import { AbiError, Web3ContractError } from 'web3-errors'; import { sha3Raw } from 'web3-utils'; -import { AbiFunctionFragment } from 'web3-types'; +import { AbiConstructorFragment, AbiFunctionFragment, DecodedParams, HexString } from 'web3-types'; import { isAbiFunctionFragment, jsonInterfaceMethodToString } from '../utils.js'; -import { encodeParameters } from './parameters_api.js'; +import { decodeParameters, encodeParameters } from './parameters_api.js'; /** * Encodes the function name to its ABI representation, which are the first 4 bytes of the sha3 of the function name including types. @@ -143,3 +143,55 @@ export const encodeFunctionCall = ( params ?? [], ).replace('0x', '')}`; }; + +export const decodeMethodParams = ( + functionsAbis: AbiFunctionFragment | AbiConstructorFragment, + data: HexString, + methodSignatureProvided = true, +): DecodedParams & { __method__: string } => { + const value = + methodSignatureProvided && data && data.length >= 10 && data.startsWith('0x') + ? data.slice(10) + : data; + if (!functionsAbis.inputs) { + if (value !== '') { + throw new Web3ContractError('No inputs found in the ABI'); + } else { + return { + __length__: 0, + __method__: jsonInterfaceMethodToString(functionsAbis), + }; + } + } + const result = decodeParameters([...functionsAbis.inputs], value); + return { + ...result, + __method__: jsonInterfaceMethodToString(functionsAbis), + }; +}; + +export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexString) => { + // If it was constructor then we need to return contract address + if (abi.type === 'constructor') { + return returnValues; + } + + if (!returnValues) { + // Using "null" value intentionally to match legacy behavior + // eslint-disable-next-line no-null/no-null + return null; + } + + const value = returnValues.length >= 2 ? returnValues.slice(2) : returnValues; + if (!abi.outputs) { + // eslint-disable-next-line no-null/no-null + return null; + } + const result = decodeParameters([...abi.outputs], value); + + if (result.__length__ === 1) { + return result[0]; + } + + return result; +}; diff --git a/packages/web3-eth-contract/src/contract-deployer-method-class.ts b/packages/web3-eth-contract/src/contract-deployer-method-class.ts index 33018541390..b7c14f3f1d9 100644 --- a/packages/web3-eth-contract/src/contract-deployer-method-class.ts +++ b/packages/web3-eth-contract/src/contract-deployer-method-class.ts @@ -17,6 +17,7 @@ along with web3.js. If not, see . import { Web3ContractError } from 'web3-errors'; import { sendTransaction, SendTransactionEvents, SendTransactionOptions } from 'web3-eth'; +import { decodeMethodParams } from 'web3-eth-abi'; import { AbiConstructorFragment, AbiFunctionFragment, @@ -34,7 +35,7 @@ import { import { format } from 'web3-utils'; import { isNullish } from 'web3-validator'; import { Web3PromiEvent } from 'web3-core'; -import { decodeMethodParams, encodeMethodABI } from './encoding.js'; +import { encodeMethodABI } from './encoding.js'; import { NonPayableTxOptions, PayableTxOptions } from './types.js'; import { getSendTxParams } from './utils.js'; // eslint-disable-next-line import/no-cycle diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index a99dc27628e..83876aa600f 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -42,6 +42,8 @@ import { TransactionMiddleware, } from 'web3-eth'; import { + decodeMethodReturn, + decodeMethodParams, encodeEventSignature, encodeFunctionSignature, decodeContractErrorData, @@ -99,12 +101,7 @@ import { ValidationSchemaInput, Web3ValidatorError, } from 'web3-validator'; -import { - decodeMethodReturn, - decodeMethodParams, - encodeEventABI, - encodeMethodABI, -} from './encoding.js'; +import { encodeEventABI, encodeMethodABI } from './encoding.js'; import { ContractLogsSubscription } from './contract_log_subscription.js'; import { ContractEventOptions, @@ -1026,7 +1023,7 @@ export class Contract `The ABI for the provided method signature ${methodSignature} was not found.`, ); } - return { ...decodeMethodParams(abi, data), __method__: jsonInterfaceMethodToString(abi) }; + return decodeMethodParams(abi, data); } private _parseAndSetJsonInterface( diff --git a/packages/web3-eth-contract/src/encoding.ts b/packages/web3-eth-contract/src/encoding.ts index 48531277518..e701744c88f 100644 --- a/packages/web3-eth-contract/src/encoding.ts +++ b/packages/web3-eth-contract/src/encoding.ts @@ -30,7 +30,8 @@ import { } from 'web3-types'; import { - decodeParameters, + decodeMethodParams as decodeMethodParamsFromEthAbi, + decodeMethodReturn as decodeMethodReturnFromEthAbi, encodeEventSignature, encodeFunctionSignature, encodeParameter, @@ -153,44 +154,7 @@ export const encodeMethodABI = ( return `${encodeFunctionSignature(abi)}${params}`; }; -export const decodeMethodParams = ( - abi: AbiFunctionFragment | AbiConstructorFragment, - data: HexString, - methodSignatureProvided = true, -) => { - const value = - methodSignatureProvided && data && data.length >= 10 && data.startsWith('0x') - ? data.slice(10) - : data; - if (!abi.inputs) { - throw new Web3ContractError('No inputs found in the ABI'); - } - const result = decodeParameters([...abi.inputs], value); - return result; -}; - -export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexString) => { - // If it was constructor then we need to return contract address - if (abi.type === 'constructor') { - return returnValues; - } - - if (!returnValues) { - // Using "null" value intentionally to match legacy behavior - // eslint-disable-next-line no-null/no-null - return null; - } - - const value = returnValues.length >= 2 ? returnValues.slice(2) : returnValues; - if (!abi.outputs) { - // eslint-disable-next-line no-null/no-null - return null; - } - const result = decodeParameters([...abi.outputs], value); - - if (result.__length__ === 1) { - return result[0]; - } - - return result; -}; +/** @deprecated import from ''web3-eth-abi' instead. */ +export const decodeMethodParams = decodeMethodParamsFromEthAbi; +/** @deprecated import from ''web3-eth-abi' instead. */ +export const decodeMethodReturn = decodeMethodReturnFromEthAbi; From f1ee17d95c9111a956e1900fbecd525130deac17 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:38:16 +0200 Subject: [PATCH 2/8] add unit tests --- .../unit/decodeMethodParamsAndReturn.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts diff --git a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts new file mode 100644 index 00000000000..a57bca8e4ea --- /dev/null +++ b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts @@ -0,0 +1,116 @@ +/* +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 { decodeMethodParams, decodeMethodReturn } from '../../src'; + +describe('decodeMethodParams and decodeMethodReturn tests should pass', () => { + it('decodeMethodParams should decode single-value data of a method', async () => { + const result = + '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + const params = decodeMethodParams( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + outputs: [ + { internalType: 'bool', name: '', type: 'bool' }, + { internalType: 'string', name: '', type: 'string' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + ); + + expect(params).toMatchObject({ + __method__: 'setGreeting(string)', + __length__: 1, + '0': 'Hello', + _greeting: 'Hello', + }); + }); + + it('decodeMethodParams should decode multi-value data of a method', async () => { + const result = + '0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000'; + + const params = decodeMethodParams( + { + inputs: [ + { internalType: 'string', name: '_greeting', type: 'string' }, + { internalType: 'string', name: '_second_greeting', type: 'string' }, + ], + name: 'setGreeting', + outputs: [ + { internalType: 'bool', name: '', type: 'bool' }, + { internalType: 'string', name: '', type: 'string' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + ); + + expect(params).toEqual({ + '0': 'Hello', + '1': 'Another Greeting', + __length__: 2, + __method__: 'setGreeting(string,string)', + _greeting: 'Hello', + _second_greeting: 'Another Greeting', + }); + }); + + it('decodeMethodReturn should decode single-value data of a method', async () => { + const result = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + const params = decodeMethodReturn( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + ); + + expect(params).toBe('Hello'); + }); + + it('decodeMethodReturn should decode multi-value data of a method', async () => { + const result = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + const params = decodeMethodReturn( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + outputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'bool', name: '', type: 'bool' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + ); + + expect(params).toEqual({ '0': 'Hello', '1': true, __length__: 2 }); + }); +}); From 247f00c5d044b52de1690eec1544a8bcfd9ecae2 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:21:51 +0200 Subject: [PATCH 3/8] renaming, enhancing and documenting --- .../web3-eth-abi/src/api/functions_api.ts | 46 ++++++++++++------- .../unit/decodeMethodParamsAndReturn.test.ts | 20 ++++---- .../src/contract-deployer-method-class.ts | 4 +- packages/web3-eth-contract/src/contract.ts | 10 ++-- packages/web3-eth-contract/src/encoding.ts | 12 ++--- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/packages/web3-eth-abi/src/api/functions_api.ts b/packages/web3-eth-abi/src/api/functions_api.ts index 9807da95cfe..b7b7757d5d5 100644 --- a/packages/web3-eth-abi/src/api/functions_api.ts +++ b/packages/web3-eth-abi/src/api/functions_api.ts @@ -144,8 +144,16 @@ export const encodeFunctionCall = ( ).replace('0x', '')}`; }; -export const decodeMethodParams = ( - functionsAbis: AbiFunctionFragment | AbiConstructorFragment, +/** + * Decodes a function call data using its `JSON interface` object. + * The JSON interface spec documentation https://docs.soliditylang.org/en/latest/abi-spec.html#json + * @param functionsAbi - The `JSON interface` object of the function. + * @param data - The data to decode + * @param methodSignatureProvided - (Optional) if `false` do not remove the first 4 bytes that would rather contain the function signature. + * @returns - The data decoded according to the passed ABI. + */ +export const decodeFunctionCall = ( + functionsAbi: AbiFunctionFragment | AbiConstructorFragment, data: HexString, methodSignatureProvided = true, ): DecodedParams & { __method__: string } => { @@ -153,26 +161,30 @@ export const decodeMethodParams = ( methodSignatureProvided && data && data.length >= 10 && data.startsWith('0x') ? data.slice(10) : data; - if (!functionsAbis.inputs) { - if (value !== '') { - throw new Web3ContractError('No inputs found in the ABI'); - } else { - return { - __length__: 0, - __method__: jsonInterfaceMethodToString(functionsAbis), - }; - } + if (!functionsAbi.inputs) { + throw new Web3ContractError('No inputs found in the ABI'); } - const result = decodeParameters([...functionsAbis.inputs], value); + const result = decodeParameters([...functionsAbi.inputs], value); return { ...result, - __method__: jsonInterfaceMethodToString(functionsAbis), + __method__: jsonInterfaceMethodToString(functionsAbi), }; }; -export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexString) => { +/** + * Decodes a function call data using its `JSON interface` object. + * The JSON interface spec documentation https://docs.soliditylang.org/en/latest/abi-spec.html#json + * @returns - The ABI encoded function call, which, means the function signature and the parameters passed. + * @param functionsAbi - The `JSON interface` object of the function. + * @param returnValues - The data (the function-returned-values) to decoded + * @returns - The function-returned-values decoded according to the passed ABI. + */ +export const decodeFunctionReturn = ( + functionsAbi: AbiFunctionFragment, + returnValues?: HexString, +) => { // If it was constructor then we need to return contract address - if (abi.type === 'constructor') { + if (functionsAbi.type === 'constructor') { return returnValues; } @@ -183,11 +195,11 @@ export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexS } const value = returnValues.length >= 2 ? returnValues.slice(2) : returnValues; - if (!abi.outputs) { + if (!functionsAbi.outputs) { // eslint-disable-next-line no-null/no-null return null; } - const result = decodeParameters([...abi.outputs], value); + const result = decodeParameters([...functionsAbi.outputs], value); if (result.__length__ === 1) { return result[0]; diff --git a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts index a57bca8e4ea..9bc824578fa 100644 --- a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts @@ -15,14 +15,14 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { decodeMethodParams, decodeMethodReturn } from '../../src'; +import { decodeFunctionCall, decodeFunctionReturn } from '../../src'; -describe('decodeMethodParams and decodeMethodReturn tests should pass', () => { - it('decodeMethodParams should decode single-value data of a method', async () => { +describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => { + it('decodeFunctionCall should decode single-value data of a method', async () => { const result = '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - const params = decodeMethodParams( + const params = decodeFunctionCall( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], name: 'setGreeting', @@ -44,11 +44,11 @@ describe('decodeMethodParams and decodeMethodReturn tests should pass', () => { }); }); - it('decodeMethodParams should decode multi-value data of a method', async () => { + it('decodeFunctionCall should decode multi-value data of a method', async () => { const result = '0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000'; - const params = decodeMethodParams( + const params = decodeFunctionCall( { inputs: [ { internalType: 'string', name: '_greeting', type: 'string' }, @@ -75,11 +75,11 @@ describe('decodeMethodParams and decodeMethodReturn tests should pass', () => { }); }); - it('decodeMethodReturn should decode single-value data of a method', async () => { + it('decodeFunctionReturn should decode single-value data of a method', async () => { const result = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - const params = decodeMethodReturn( + const params = decodeFunctionReturn( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], name: 'setGreeting', @@ -93,11 +93,11 @@ describe('decodeMethodParams and decodeMethodReturn tests should pass', () => { expect(params).toBe('Hello'); }); - it('decodeMethodReturn should decode multi-value data of a method', async () => { + it('decodeFunctionReturn should decode multi-value data of a method', async () => { const result = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - const params = decodeMethodReturn( + const params = decodeFunctionReturn( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], name: 'setGreeting', diff --git a/packages/web3-eth-contract/src/contract-deployer-method-class.ts b/packages/web3-eth-contract/src/contract-deployer-method-class.ts index b7c14f3f1d9..08d04b16c8f 100644 --- a/packages/web3-eth-contract/src/contract-deployer-method-class.ts +++ b/packages/web3-eth-contract/src/contract-deployer-method-class.ts @@ -17,7 +17,7 @@ along with web3.js. If not, see . import { Web3ContractError } from 'web3-errors'; import { sendTransaction, SendTransactionEvents, SendTransactionOptions } from 'web3-eth'; -import { decodeMethodParams } from 'web3-eth-abi'; +import { decodeFunctionCall } from 'web3-eth-abi'; import { AbiConstructorFragment, AbiFunctionFragment, @@ -210,7 +210,7 @@ export class DeployerMethodClass { public decodeData(data: HexString) { return { - ...decodeMethodParams( + ...decodeFunctionCall( this.constructorAbi, data.replace(this.deployData as string, ''), false, diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 83876aa600f..60602497e65 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -42,8 +42,8 @@ import { TransactionMiddleware, } from 'web3-eth'; import { - decodeMethodReturn, - decodeMethodParams, + decodeFunctionCall, + decodeFunctionReturn, encodeEventSignature, encodeFunctionSignature, decodeContractErrorData, @@ -1023,7 +1023,7 @@ export class Contract `The ABI for the provided method signature ${methodSignature} was not found.`, ); } - return decodeMethodParams(abi, data); + return decodeFunctionCall(abi, data); } private _parseAndSetJsonInterface( @@ -1248,7 +1248,7 @@ export class Contract }), encodeABI: () => encodeMethodABI(methodAbi, abiParams), - decodeData: (data: HexString) => decodeMethodParams(methodAbi, data), + decodeData: (data: HexString) => decodeFunctionCall(methodAbi, data), createAccessList: async ( options?: PayableCallOptions | NonPayableCallOptions, @@ -1302,7 +1302,7 @@ export class Contract block, this.defaultReturnFormat as typeof DEFAULT_RETURN_FORMAT, ); - return decodeMethodReturn(abi, result); + return decodeFunctionReturn(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 diff --git a/packages/web3-eth-contract/src/encoding.ts b/packages/web3-eth-contract/src/encoding.ts index e701744c88f..2b347269d36 100644 --- a/packages/web3-eth-contract/src/encoding.ts +++ b/packages/web3-eth-contract/src/encoding.ts @@ -30,8 +30,8 @@ import { } from 'web3-types'; import { - decodeMethodParams as decodeMethodParamsFromEthAbi, - decodeMethodReturn as decodeMethodReturnFromEthAbi, + decodeFunctionCall, + decodeFunctionReturn, encodeEventSignature, encodeFunctionSignature, encodeParameter, @@ -154,7 +154,7 @@ export const encodeMethodABI = ( return `${encodeFunctionSignature(abi)}${params}`; }; -/** @deprecated import from ''web3-eth-abi' instead. */ -export const decodeMethodParams = decodeMethodParamsFromEthAbi; -/** @deprecated import from ''web3-eth-abi' instead. */ -export const decodeMethodReturn = decodeMethodReturnFromEthAbi; +/** @deprecated import `decodeFunctionCall` from ''web3-eth-abi' instead. */ +export const decodeMethodParams = decodeFunctionCall; +/** @deprecated import `decodeFunctionReturn` from ''web3-eth-abi' instead. */ +export const decodeMethodReturn = decodeFunctionReturn; From cc7a7276786ec61e1706402b662d0a312b2694a7 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:22:17 +0200 Subject: [PATCH 4/8] update CHANGELOG.md --- packages/web3-eth-abi/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/web3-eth-abi/CHANGELOG.md b/packages/web3-eth-abi/CHANGELOG.md index 9cb5bd2d7b9..7a6951386d9 100644 --- a/packages/web3-eth-abi/CHANGELOG.md +++ b/packages/web3-eth-abi/CHANGELOG.md @@ -195,3 +195,7 @@ Documentation: - `decodeLog` , `decodeParametersWith` , `decodeParameters` and `decodeParameters` now accepts first immutable param as well (#7288) ## [Unreleased] + +### Added + +- added `decodeFunctionCall` and `decodeFunctionReturn`. (#7345) From 501ee4d54219f7e2a84dceb3b6f52211aa6a8369 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:22:27 +0200 Subject: [PATCH 5/8] add 2 tests --- .../unit/decodeMethodParamsAndReturn.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts index 9bc824578fa..e0530fe703f 100644 --- a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts @@ -44,6 +44,55 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => }); }); + it('decodeFunctionCall should decode data of a method without removing the method signature (if intended)', async () => { + const result = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + const params = decodeFunctionCall( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + outputs: [ + { internalType: 'bool', name: '', type: 'bool' }, + { internalType: 'string', name: '', type: 'string' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + false, + ); + + expect(params).toMatchObject({ + __method__: 'setGreeting(string)', + __length__: 1, + '0': 'Hello', + _greeting: 'Hello', + }); + }); + + it('decodeFunctionCall should throw if no inputs at the ABI', async () => { + const result = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + expect(() => + decodeFunctionCall( + { + name: 'setGreeting', + // no `inputs` provided! + outputs: [ + { internalType: 'bool', name: '', type: 'bool' }, + { internalType: 'string', name: '', type: 'string' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + result, + false, + ), + ).toThrow('No inputs found in the ABI'); + }); + it('decodeFunctionCall should decode multi-value data of a method', async () => { const result = '0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000'; From b0dd7c50dca6ca1250d35b0b663c6b318f354724 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:39:11 +0200 Subject: [PATCH 6/8] add a test --- .../web3-eth-abi/src/api/functions_api.ts | 2 +- .../unit/decodeMethodParamsAndReturn.test.ts | 40 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/web3-eth-abi/src/api/functions_api.ts b/packages/web3-eth-abi/src/api/functions_api.ts index b7b7757d5d5..0ff43b35ad4 100644 --- a/packages/web3-eth-abi/src/api/functions_api.ts +++ b/packages/web3-eth-abi/src/api/functions_api.ts @@ -183,7 +183,7 @@ export const decodeFunctionReturn = ( functionsAbi: AbiFunctionFragment, returnValues?: HexString, ) => { - // If it was constructor then we need to return contract address + // If it is a constructor there is nothing to decode! if (functionsAbi.type === 'constructor') { return returnValues; } diff --git a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts index e0530fe703f..8b245a3b8b1 100644 --- a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts @@ -15,11 +15,12 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { AbiFunctionFragment } from 'web3-types'; import { decodeFunctionCall, decodeFunctionReturn } from '../../src'; describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => { it('decodeFunctionCall should decode single-value data of a method', async () => { - const result = + const data = '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; const params = decodeFunctionCall( @@ -33,7 +34,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, ); expect(params).toMatchObject({ @@ -45,7 +46,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => }); it('decodeFunctionCall should decode data of a method without removing the method signature (if intended)', async () => { - const result = + const data = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; const params = decodeFunctionCall( @@ -59,7 +60,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, false, ); @@ -72,7 +73,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => }); it('decodeFunctionCall should throw if no inputs at the ABI', async () => { - const result = + const data = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; expect(() => @@ -87,14 +88,14 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, false, ), ).toThrow('No inputs found in the ABI'); }); it('decodeFunctionCall should decode multi-value data of a method', async () => { - const result = + const data = '0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000'; const params = decodeFunctionCall( @@ -111,7 +112,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, ); expect(params).toEqual({ @@ -125,7 +126,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => }); it('decodeFunctionReturn should decode single-value data of a method', async () => { - const result = + const data = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; const params = decodeFunctionReturn( @@ -136,14 +137,14 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, ); expect(params).toBe('Hello'); }); it('decodeFunctionReturn should decode multi-value data of a method', async () => { - const result = + const data = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; const params = decodeFunctionReturn( @@ -157,9 +158,24 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => stateMutability: 'nonpayable', type: 'function', }, - result, + data, ); expect(params).toEqual({ '0': 'Hello', '1': true, __length__: 2 }); }); + + it('decodeFunctionReturn should decode nothing if it is called on a constructor', async () => { + const result = 'anything passed should be returned as-is'; + + const params = decodeFunctionReturn( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + } as unknown as AbiFunctionFragment, + result, + ); + + expect(params).toEqual(result); + }); }); From 27efc5af1f88af3abf4515c83ba7aeba9e083bab Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:19:02 +0200 Subject: [PATCH 7/8] add 2 tests --- .../unit/decodeMethodParamsAndReturn.test.ts | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts index 8b245a3b8b1..acf96c2aa41 100644 --- a/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts @@ -129,7 +129,7 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => const data = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - const params = decodeFunctionReturn( + const decodedResult = decodeFunctionReturn( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], name: 'setGreeting', @@ -140,14 +140,14 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => data, ); - expect(params).toBe('Hello'); + expect(decodedResult).toBe('Hello'); }); it('decodeFunctionReturn should decode multi-value data of a method', async () => { const data = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - const params = decodeFunctionReturn( + const decodedResult = decodeFunctionReturn( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], name: 'setGreeting', @@ -161,21 +161,57 @@ describe('decodeFunctionCall and decodeFunctionReturn tests should pass', () => data, ); - expect(params).toEqual({ '0': 'Hello', '1': true, __length__: 2 }); + expect(decodedResult).toEqual({ '0': 'Hello', '1': true, __length__: 2 }); }); it('decodeFunctionReturn should decode nothing if it is called on a constructor', async () => { - const result = 'anything passed should be returned as-is'; + const data = 'anything passed should be returned as-is'; - const params = decodeFunctionReturn( + const decodedResult = decodeFunctionReturn( { inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], stateMutability: 'nonpayable', type: 'constructor', } as unknown as AbiFunctionFragment, - result, + data, + ); + + expect(decodedResult).toEqual(data); + }); + + it('decodeFunctionReturn should return `null` if no values passed', async () => { + const data = ''; + + const decodedResult = decodeFunctionReturn( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + outputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'bool', name: '', type: 'bool' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + data, + ); + + expect(decodedResult).toBeNull(); + }); + + it('decodeFunctionReturn should return `null` if no function output provided', async () => { + const data = '0x000000'; + + const decodedResult = decodeFunctionReturn( + { + inputs: [{ internalType: 'string', name: '_greeting', type: 'string' }], + name: 'setGreeting', + stateMutability: 'nonpayable', + type: 'function', + }, + data, ); - expect(params).toEqual(result); + expect(decodedResult).toBeNull(); }); }); From 4a0e890b10621e9e1f5f8c116e4e8d7ab113d77d Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:38:43 +0200 Subject: [PATCH 8/8] add examples in functions docs --- .../web3-eth-abi/src/api/functions_api.ts | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/web3-eth-abi/src/api/functions_api.ts b/packages/web3-eth-abi/src/api/functions_api.ts index 0ff43b35ad4..8518deeab24 100644 --- a/packages/web3-eth-abi/src/api/functions_api.ts +++ b/packages/web3-eth-abi/src/api/functions_api.ts @@ -151,6 +151,37 @@ export const encodeFunctionCall = ( * @param data - The data to decode * @param methodSignatureProvided - (Optional) if `false` do not remove the first 4 bytes that would rather contain the function signature. * @returns - The data decoded according to the passed ABI. + * @example + * ```ts + * const data = + * '0xa413686200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000'; + * const params = decodeFunctionCall( + * { + * inputs: [ + * { internalType: 'string', name: '_greeting', type: 'string' }, + * { internalType: 'string', name: '_second_greeting', type: 'string' }, + * ], + * name: 'setGreeting', + * outputs: [ + * { internalType: 'bool', name: '', type: 'bool' }, + * { internalType: 'string', name: '', type: 'string' }, + * ], + * stateMutability: 'nonpayable', + * type: 'function', + * }, + * data, + * ); + + * console.log(params); + * > { + * > '0': 'Hello', + * > '1': 'Another Greeting', + * > __length__: 2, + * > __method__: 'setGreeting(string,string)', + * > _greeting: 'Hello', + * > _second_greeting: 'Another Greeting', + * > } + * ``` */ export const decodeFunctionCall = ( functionsAbi: AbiFunctionFragment | AbiConstructorFragment, @@ -177,7 +208,51 @@ export const decodeFunctionCall = ( * @returns - The ABI encoded function call, which, means the function signature and the parameters passed. * @param functionsAbi - The `JSON interface` object of the function. * @param returnValues - The data (the function-returned-values) to decoded - * @returns - The function-returned-values decoded according to the passed ABI. + * @returns - The function-returned-values decoded according to the passed ABI. If there are multiple values, it returns them as an object as the example below. But if it is a single value, it returns it only for simplicity. + * @example + * ```ts + * // decode a multi-value data of a method + * const data = + * '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + * const decodedResult = decodeFunctionReturn( + * { + * inputs: [ + * { internalType: 'string', name: '_greeting', type: 'string' } + * ], + * name: 'setGreeting', + * outputs: [ + * { internalType: 'string', name: '', type: 'string' }, + * { internalType: 'bool', name: '', type: 'bool' }, + * ], + * stateMutability: 'nonpayable', + * type: 'function', + * }, + * data, + * ); + + * console.log(decodedResult); + * > { '0': 'Hello', '1': true, __length__: 2 } + * + * + * // decode a single-value data of a method + * const data = + * '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + * const decodedResult = decodeFunctionReturn( + * { + * inputs: [ + * { internalType: 'string', name: '_greeting', type: 'string' } + * ], + * name: 'setGreeting', + * outputs: [{ internalType: 'string', name: '', type: 'string' }], + * stateMutability: 'nonpayable', + * type: 'function', + * }, + * data, + * ); + + * console.log(decodedResult); + * > 'Hello' + * ``` */ export const decodeFunctionReturn = ( functionsAbi: AbiFunctionFragment,