Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make decodeFunctionCall and decodeFunctionReturn available at web3-eth-abi #7345

Merged
merged 9 commits into from
Oct 22, 2024
58 changes: 55 additions & 3 deletions packages/web3-eth-abi/src/api/functions_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
*
* @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.
Expand Down Expand Up @@ -143,3 +143,55 @@
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;

Check warning on line 155 in packages/web3-eth-abi/src/api/functions_api.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/api/functions_api.ts#L155

Added line #L155 was not covered by tests
if (!functionsAbis.inputs) {
if (value !== '') {
throw new Web3ContractError('No inputs found in the ABI');
} else {
return {

Check warning on line 160 in packages/web3-eth-abi/src/api/functions_api.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/api/functions_api.ts#L157-L160

Added lines #L157 - L160 were not covered by tests
__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;

Check warning on line 176 in packages/web3-eth-abi/src/api/functions_api.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/api/functions_api.ts#L176

Added line #L176 was not covered by tests
}

if (!returnValues) {
// Using "null" value intentionally to match legacy behavior
// eslint-disable-next-line no-null/no-null
return null;

Check warning on line 182 in packages/web3-eth-abi/src/api/functions_api.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/api/functions_api.ts#L182

Added line #L182 was not covered by tests
Muhammad-Altabba marked this conversation as resolved.
Show resolved Hide resolved
}

const value = returnValues.length >= 2 ? returnValues.slice(2) : returnValues;
if (!abi.outputs) {
// eslint-disable-next-line no-null/no-null
return null;

Check warning on line 188 in packages/web3-eth-abi/src/api/functions_api.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/api/functions_api.ts#L188

Added line #L188 was not covered by tests
}
const result = decodeParameters([...abi.outputs], value);

if (result.__length__ === 1) {
return result[0];
}

return result;
};
116 changes: 116 additions & 0 deletions packages/web3-eth-abi/test/unit/decodeMethodParamsAndReturn.test.ts
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { Web3ContractError } from 'web3-errors';
import { sendTransaction, SendTransactionEvents, SendTransactionOptions } from 'web3-eth';
import { decodeMethodParams } from 'web3-eth-abi';
import {
AbiConstructorFragment,
AbiFunctionFragment,
Expand All @@ -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
Expand Down
11 changes: 4 additions & 7 deletions packages/web3-eth-contract/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
TransactionMiddleware,
} from 'web3-eth';
import {
decodeMethodReturn,
decodeMethodParams,
encodeEventSignature,
encodeFunctionSignature,
decodeContractErrorData,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1026,7 +1023,7 @@ export class Contract<Abi extends ContractAbi>
`The ABI for the provided method signature ${methodSignature} was not found.`,
);
}
return { ...decodeMethodParams(abi, data), __method__: jsonInterfaceMethodToString(abi) };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the property __method__ is now returned by decodeMethodParams.

return decodeMethodParams(abi, data);
}

private _parseAndSetJsonInterface(
Expand Down
48 changes: 6 additions & 42 deletions packages/web3-eth-contract/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
} from 'web3-types';

import {
decodeParameters,
decodeMethodParams as decodeMethodParamsFromEthAbi,
decodeMethodReturn as decodeMethodReturnFromEthAbi,
encodeEventSignature,
encodeFunctionSignature,
encodeParameter,
Expand Down Expand Up @@ -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;
Loading