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

Allow the deploy function to accept parameters even when no ABI was provided to the Contract #6635

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions packages/web3-eth-abi/src/api/parameters_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,10 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
import { AbiError } from 'web3-errors';
import { AbiInput, HexString } from 'web3-types';
import { decodeParameters as decodeParametersInternal } from '../coders/decode.js';
import { encodeParameters as encodeParametersInternal } from '../coders/encode.js';
import { encodeParameters } from '../coders/encode.js';

export { encodeParameters, inferTypesAndEncodeParameters } from '../coders/encode.js';

/**
* Encodes a parameter based on its type to its ABI representation.
* @param abi - An array of {@link AbiInput}. See [Solidity's documentation](https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#json) for more details.
* @param params - The actual parameters to encode.
* @returns - The ABI encoded parameters
* @example
* ```ts
* const res = web3.eth.abi.encodeParameters(
* ["uint256", "string"],
* ["2345675643", "Hello!%"]
* );
*
* console.log(res);
* > 0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000
* ```
*/
export const encodeParameters = (abi: ReadonlyArray<AbiInput>, params: unknown[]): string =>
encodeParametersInternal(abi, params);

/**
* Encodes a parameter based on its type to its ABI representation.
Expand Down
86 changes: 83 additions & 3 deletions packages/web3-eth-abi/src/coders/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,100 @@
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { AbiError } from 'web3-errors';
import { AbiInput } from 'web3-types';
import { AbiInput, AbiParameter } from 'web3-types';
import { toHex } from 'web3-utils';
import { utils } from 'web3-validator';
import { encodeTuple } from './base/index.js';
import { toAbiParams } from './utils.js';

/**
* @param params - The params to infer the ABI from
* @returns The inferred ABI
* @example
* ```
* inferParamsAbi([1, -1, 'hello', '0x1234', ])
* ```
* > [{ type: 'int256' }, { type: 'uint256' }, { type: 'string' }, { type: 'bytes' }]
* ```
*/
function inferParamsAbi(params: unknown[]): ReadonlyArray<AbiParameter> {
const abi: AbiParameter[] = [];
params.forEach(param => {
if (Array.isArray(param)) {
const inferredParams = inferParamsAbi(param);
abi.push({
type: 'tuple',
components: inferredParams,
name: '',
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
} as AbiParameter);
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
abi.push({ type: toHex(param as any, true) } as AbiParameter);
}
});
return abi;
}

/**
* Encodes a parameter based on its type to its ABI representation.
* @param abi - An array of {@link AbiInput}. See [Solidity's documentation](https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#json) for more details.
* @param params - The actual parameters to encode.
* @returns - The ABI encoded parameters
* @example
* ```ts
* const res = web3.eth.abi.encodeParameters(
* ["uint256", "string"],
* ["2345675643", "Hello!%"]
* );
*
* console.log(res);
* > 0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000
* ```
*/
export function encodeParameters(abi: ReadonlyArray<AbiInput>, params: unknown[]): string {
if (abi.length !== params.length) {
if (abi?.length !== params.length) {
throw new AbiError('Invalid number of values received for given ABI', {
expected: abi.length,
expected: abi?.length,
received: params.length,
});
}

const abiParams = toAbiParams(abi);
return utils.uint8ArrayToHexString(
encodeTuple({ type: 'tuple', name: '', components: abiParams }, params).encoded,
);
}

/**
* Infer a smart contract method parameter type and then encode this parameter.
* @param params - The parameters to encode.
* @returns - The ABI encoded parameters
*
* @remarks
* This method is useful when you don't know the type of the parameters you want to encode. It will infer the type of the parameters and then encode them.
* However, it is not recommended to use this method when you know the type of the parameters you want to encode. In this case, use the {@link encodeParameters} method instead.
* The type inference is not perfect and can lead to unexpected results. Especially when you want to encode an array, uint that is not uint256 or bytes....
* @example
* ```ts
* const res = web3.eth.abi.encodeParameters(
* ["2345675643", "Hello!%"]
* );
*
* console.log(res);
* > 0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000
* ```
*/
export function inferTypesAndEncodeParameters(params: unknown[]): string {
try {
const abiParams = inferParamsAbi(params);
return utils.uint8ArrayToHexString(
encodeTuple({ type: 'tuple', name: '', components: abiParams }, params).encoded,
);
} catch (e) {
// throws If the inferred params type caused an error
throw new AbiError('Could not infer types from given params', {

Check warning on line 110 in packages/web3-eth-abi/src/coders/encode.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-abi/src/coders/encode.ts#L110

Added line #L110 was not covered by tests
params,
});
}
}
13 changes: 12 additions & 1 deletion packages/web3-eth-abi/test/unit/encodeDecodeParams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { AbiInput } from 'web3-types';
import { decodeParameters, encodeParameters } from '../../src';
import { decodeParameters, encodeParameters, inferTypesAndEncodeParameters } from '../../src';
import testsData from '../fixtures/abitestsdata.json';
import { deepEqualTolerateBigInt, removeKey } from './test_utils';

Expand All @@ -26,6 +26,17 @@ describe('encodeParameters decodeParameters tests should pass', () => {
expect(encodedResult).toEqual(encoderTestObj.encoded);
});

it.each(testsData)(`unit test of encodeParameters - $name`, encoderTestObj => {
// skip for types that are not supported by infer-types
// the unsupported types are uint(other than 256), int(other than 256), bytes(that has a number like bytes1 or bytes2), and arrays
if (/((?<!u)int)|((?<!uint\d)uint(?!256))|(bytes\d)|(\[.*?\])/.test(encoderTestObj.type)) {
return;
}

const encodedResult = inferTypesAndEncodeParameters([encoderTestObj.value]);
expect(encodedResult).toEqual(encoderTestObj.encoded);
});

it.each(testsData)('unit test of decodeParameters - $name', decoderTestObj => {
const decodedResult = decodeParameters(
[decoderTestObj.type] as AbiInput[],
Expand Down
4 changes: 4 additions & 0 deletions packages/web3-eth-contract/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ Documentation:

## [Unreleased]

### Changed

- Allow the `deploy` function to accept parameters, even when no ABI was provided to the `Contract`(#6635)

### Fixed

- Fix and error that happen when trying to get past events by calling `contract.getPastEvents` or `contract.events.allEvents()`, if there is no matching events. (#6647)
Expand Down
1 change: 0 additions & 1 deletion packages/web3-eth-contract/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,6 @@ export class Contract<Abi extends ContractAbi>
if (!abi) {
abi = {
type: 'constructor',
inputs: [],
stateMutability: '',
} as AbiConstructorFragment;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/web3-eth-contract/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
encodeFunctionSignature,
encodeParameter,
encodeParameters,
inferTypesAndEncodeParameters,
isAbiConstructorFragment,
jsonInterfaceMethodToString,
} from 'web3-eth-abi';
Expand Down Expand Up @@ -120,16 +121,22 @@ export const encodeMethodABI = (
deployData?: HexString,
) => {
const inputLength = Array.isArray(abi.inputs) ? abi.inputs.length : 0;
if (inputLength !== args.length) {
if (abi.inputs && inputLength !== args.length) {
throw new Web3ContractError(
`The number of arguments is not matching the methods required number. You need to pass ${inputLength} arguments.`,
);
}

const params = encodeParameters(Array.isArray(abi.inputs) ? abi.inputs : [], args).replace(
'0x',
'',
);
let params: string;
if (abi.inputs) {
params = encodeParameters(Array.isArray(abi.inputs) ? abi.inputs : [], args).replace(
'0x',
'',
);
} else {
params = inferTypesAndEncodeParameters(args).replace('0x', '');
}


if (isAbiConstructorFragment(abi)) {
if (!deployData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ describe('contract', () => {

const myContract = contract.deploy({
data: '608060405234801561001057600080fd5b506040516101d93803806101d983398181016040528101906100329190610054565b806000819055505061009e565b60008151905061004e81610087565b92915050565b60006020828403121561006657600080fd5b60006100748482850161003f565b91505092915050565b6000819050919050565b6100908161007d565b811461009b57600080fd5b50565b61012c806100ad6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806323fd0e401460375780636ffd773c146051575b600080fd5b603d6069565b6040516048919060bf565b60405180910390f35b6067600480360381019060639190608c565b606f565b005b60005481565b8060008190555050565b60008135905060868160e2565b92915050565b600060208284031215609d57600080fd5b600060a9848285016079565b91505092915050565b60b98160d8565b82525050565b600060208201905060d2600083018460b2565b92915050565b6000819050919050565b60e98160d8565b811460f357600080fd5b5056fea2646970667358221220d28cf161457f7936995800eb9896635a02a559a0561bff6a09a40bfb81cd056564736f6c63430008000033',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
arguments: [1],
});

Expand Down
33 changes: 33 additions & 0 deletions packages/web3-eth-contract/test/unit/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,39 @@ describe('Contract', () => {
sendTransactionSpy.mockClear();
});

it('should deploy contract with input property with no ABI', async () => {
const input = `${GreeterBytecode}0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b4d79204772656574696e67000000000000000000000000000000000000000000`;
const contract = new Contract([]);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const sendTransactionSpy = jest
.spyOn(eth, 'sendTransaction')
.mockImplementation((_objInstance, tx) => {
expect(tx.to).toBeUndefined();
expect(tx.gas).toStrictEqual(sendOptions.gas);
expect(tx.gasPrice).toBeUndefined();
expect(tx.from).toStrictEqual(sendOptions.from);
expect(tx.input).toStrictEqual(input); // padded data

const newContract = contract.clone();
newContract.options.address = deployedAddr;

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Promise.resolve(newContract) as any;
});

const deployedContract = await contract
.deploy({
input: GreeterBytecode,
arguments: ['My Greeting'],
})
.send(sendOptions);

expect(deployedContract).toBeDefined();
expect(deployedContract.options.address).toStrictEqual(deployedAddr);
sendTransactionSpy.mockClear();
});

it('should deploy contract with data property', async () => {
const data = `${GreeterBytecode}0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b4d79204772656574696e67000000000000000000000000000000000000000000`;
const contract = new Contract(GreeterAbi);
Expand Down
21 changes: 15 additions & 6 deletions packages/web3-types/src/eth_abi_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,21 @@ export type ContractConstructor<Abis extends ContractAbi> = {
};
}['constructor'];

export type ContractConstructorArgs<Abis extends ContractAbi> = {
[Abi in FilterAbis<
Abis,
AbiConstructorFragment & { type: 'constructor' }
> as 'constructor']: ContractMethodInputParameters<Abi['inputs']>;
}['constructor'];
export type ContractConstructorArgs<Abis extends ContractAbi> = FilterAbis<
Abis,
AbiConstructorFragment & {
type: 'constructor';
}
> extends never
? any
: {
[Abi in FilterAbis<
Abis,
AbiConstructorFragment & {
type: 'constructor';
}
> as 'constructor']: ContractMethodInputParameters<Abi['inputs']>;
}['constructor'];

export type ContractMethod<Abi extends AbiFunctionFragment> = {
readonly Abi: Abi;
Expand Down
6 changes: 5 additions & 1 deletion packages/web3-utils/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
isHex,
isHexStrict,
isInt,
isUInt,
isNullish,
utils,
utils as validatorUtils,
Expand Down Expand Up @@ -372,9 +373,12 @@
if (isHexStrict(value)) {
return returnType ? 'bytes' : value;
}
if (isHex(value) && !isInt(value)) {
if (isHex(value) && !isInt(value) && !isUInt(value)) {
return returnType ? 'bytes' : `0x${value}`;
}
if (isHex(value) && !isInt(value) && isUInt(value)) {
return returnType ? 'uint' : numberToHex(value);

Check warning on line 380 in packages/web3-utils/src/converters.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-utils/src/converters.ts#L380

Added line #L380 was not covered by tests
}

if (!Number.isFinite(value)) {
return returnType ? 'string' : utf8ToHex(value);
Expand Down
Loading
Loading