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 10 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
6 changes: 4 additions & 2 deletions packages/web3-eth-abi/src/api/parameters_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ import { encodeParameters as encodeParametersInternal } from '../coders/encode.j
* > 0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000
* ```
*/
export const encodeParameters = (abi: ReadonlyArray<AbiInput>, params: unknown[]): string =>
encodeParametersInternal(abi, params);
export const encodeParameters = (
abi: ReadonlyArray<AbiInput> | 'infer-types',
Muhammad-Altabba marked this conversation as resolved.
Show resolved Hide resolved
params: unknown[],
): string => encodeParametersInternal(abi, params);

/**
* Encodes a parameter based on its type to its ABI representation.
Expand Down
56 changes: 52 additions & 4 deletions packages/web3-eth-abi/src/coders/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,66 @@
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';

export function encodeParameters(abi: ReadonlyArray<AbiInput>, params: unknown[]): string {
if (abi.length !== params.length) {
/**
* @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;
}

export function encodeParameters(
abi: ReadonlyArray<AbiInput> | 'infer-types',
params: unknown[],
): string {
if (abi !== 'infer-types' && abi?.length !== params.length) {
throw new AbiError('Invalid number of values received for given ABI', {
expected: abi.length,
expected: abi?.length,
received: params.length,
});
}

if (abi === 'infer-types') {
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 72 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#L72

Added line #L72 was not covered by tests
params,
});
}
}

const abiParams = toAbiParams(abi);
return utils.uint8ArrayToHexString(
encodeTuple({ type: 'tuple', name: '', components: abiParams }, params).encoded,
Expand Down
11 changes: 11 additions & 0 deletions packages/web3-eth-abi/test/unit/encodeDecodeParams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = encodeParameters('infer-types', [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)
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 @@ -734,7 +734,6 @@ export class Contract<Abi extends ContractAbi>
if (!abi) {
abi = {
type: 'constructor',
inputs: [],
stateMutability: '',
} as AbiConstructorFragment;
}
Expand Down
11 changes: 6 additions & 5 deletions packages/web3-eth-contract/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,17 @@ 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',
'',
);
const params = encodeParameters(
// eslint-disable-next-line no-nested-ternary
abi.inputs ? (Array.isArray(abi.inputs) ? abi.inputs : []) : 'infer-types',
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