From 4867818f1f2e1329fb4cb44fcaff626966e9b3d7 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 19 Apr 2024 12:39:21 +0200 Subject: [PATCH 01/10] feat: support empty `()` type in typegen --- .../src/abi/types/EmptyType.test.ts | 20 +++++++++++++ .../abi-typegen/src/abi/types/EmptyType.ts | 28 +++++++++++++++++++ .../src/abi/types/EnumType.test.ts | 4 +-- .../abi-typegen/src/abi/types/EnumType.ts | 6 ++-- .../src/utils/shouldSkipAbiType.ts | 2 +- .../src/utils/shouldSkipType.test.ts | 1 - .../src/utils/supportedTypes.test.ts | 2 +- .../abi-typegen/src/utils/supportedTypes.ts | 2 ++ 8 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 packages/abi-typegen/src/abi/types/EmptyType.test.ts create mode 100644 packages/abi-typegen/src/abi/types/EmptyType.ts diff --git a/packages/abi-typegen/src/abi/types/EmptyType.test.ts b/packages/abi-typegen/src/abi/types/EmptyType.test.ts new file mode 100644 index 0000000000..dcf80963be --- /dev/null +++ b/packages/abi-typegen/src/abi/types/EmptyType.test.ts @@ -0,0 +1,20 @@ +import { EmptyType } from './EmptyType'; + +/** + * @group node + */ +describe('EmptyType.ts', () => { + test('should properly parse type attributes', () => { + const emptyType = new EmptyType({ + rawAbiType: { + components: null, + typeParameters: null, + typeId: 0, + type: EmptyType.swayType, + }, + }); + + expect(emptyType.attributes.inputLabel).toEqual('undefined'); + expect(emptyType.attributes.outputLabel).toEqual('undefined'); + }); +}); diff --git a/packages/abi-typegen/src/abi/types/EmptyType.ts b/packages/abi-typegen/src/abi/types/EmptyType.ts new file mode 100644 index 0000000000..7ab179c71c --- /dev/null +++ b/packages/abi-typegen/src/abi/types/EmptyType.ts @@ -0,0 +1,28 @@ +import type { IRawAbiTypeRoot } from '../..'; +import type { IType } from '../../types/interfaces/IType'; + +import { AType } from './AType'; + +export class EmptyType extends AType implements IType { + public static swayType = '()'; + + public name = 'empty'; + + static MATCH_REGEX: RegExp = /^\(\)$/m; + + constructor(params: { rawAbiType: IRawAbiTypeRoot }) { + super(params); + this.attributes = { + inputLabel: `undefined`, + outputLabel: `undefined`, + }; + } + + static isSuitableFor(params: { type: string }) { + return EmptyType.MATCH_REGEX.test(params.type); + } + + public parseComponentsAttributes(_params: { types: IType[] }) { + return this.attributes; + } +} diff --git a/packages/abi-typegen/src/abi/types/EnumType.test.ts b/packages/abi-typegen/src/abi/types/EnumType.test.ts index bbe407502b..3208e700c6 100644 --- a/packages/abi-typegen/src/abi/types/EnumType.test.ts +++ b/packages/abi-typegen/src/abi/types/EnumType.test.ts @@ -23,9 +23,7 @@ describe('EnumType.ts', () => { abiContents: { types: rawTypes }, } = getTypegenForcProject(project); - const types = rawTypes - .filter((t) => t.type !== '()') - .map((rawAbiType: IRawAbiTypeRoot) => makeType({ rawAbiType })); + const types = rawTypes.map((rawAbiType: IRawAbiTypeRoot) => makeType({ rawAbiType })); return { types }; } diff --git a/packages/abi-typegen/src/abi/types/EnumType.ts b/packages/abi-typegen/src/abi/types/EnumType.ts index f5014edf21..c2b97761f4 100644 --- a/packages/abi-typegen/src/abi/types/EnumType.ts +++ b/packages/abi-typegen/src/abi/types/EnumType.ts @@ -43,10 +43,10 @@ export class EnumType extends AType implements IType { public getNativeEnum(params: { types: IType[] }) { const { types } = params; - const typeHash: { [key: number]: IType['rawAbiType'] } = types.reduce( + const typeHash: { [key: number]: IType['rawAbiType']['type'] } = types.reduce( (hash, row) => ({ ...hash, - [row.rawAbiType.typeId]: row, + [row.rawAbiType.typeId]: row.rawAbiType.type, }), {} ); @@ -56,7 +56,7 @@ export class EnumType extends AType implements IType { // `components` array guaranteed to always exist for structs/enums const enumComponents = components as IRawAbiTypeComponent[]; - if (!enumComponents.every(({ type }) => !typeHash[type])) { + if (!enumComponents.every(({ type }) => typeHash[type] === '()')) { return undefined; } diff --git a/packages/abi-typegen/src/utils/shouldSkipAbiType.ts b/packages/abi-typegen/src/utils/shouldSkipAbiType.ts index 8a0ce77b00..575fb98e26 100644 --- a/packages/abi-typegen/src/utils/shouldSkipAbiType.ts +++ b/packages/abi-typegen/src/utils/shouldSkipAbiType.ts @@ -1,5 +1,5 @@ export function shouldSkipAbiType(params: { type: string }) { - const ignoreList = ['()', 'struct RawVec']; + const ignoreList = ['struct RawVec']; const shouldSkip = ignoreList.indexOf(params.type) >= 0; return shouldSkip; } diff --git a/packages/abi-typegen/src/utils/shouldSkipType.test.ts b/packages/abi-typegen/src/utils/shouldSkipType.test.ts index 6faa48fe65..68d1ce5800 100644 --- a/packages/abi-typegen/src/utils/shouldSkipType.test.ts +++ b/packages/abi-typegen/src/utils/shouldSkipType.test.ts @@ -6,7 +6,6 @@ import { supportedTypes } from './supportedTypes'; */ describe('types.ts', () => { test('should always skip these types', () => { - expect(shouldSkipAbiType({ type: '()' })).toEqual(true); expect(shouldSkipAbiType({ type: 'struct RawVec' })).toEqual(true); }); diff --git a/packages/abi-typegen/src/utils/supportedTypes.test.ts b/packages/abi-typegen/src/utils/supportedTypes.test.ts index 32b0303a43..ea5df9ddda 100644 --- a/packages/abi-typegen/src/utils/supportedTypes.test.ts +++ b/packages/abi-typegen/src/utils/supportedTypes.test.ts @@ -5,6 +5,6 @@ import { supportedTypes } from './supportedTypes'; */ describe('supportedTypes.ts', () => { test('should export all supported types', () => { - expect(supportedTypes.length).toEqual(22); + expect(supportedTypes.length).toEqual(23); }); }); diff --git a/packages/abi-typegen/src/utils/supportedTypes.ts b/packages/abi-typegen/src/utils/supportedTypes.ts index 86b44710bf..24d2985ec7 100644 --- a/packages/abi-typegen/src/utils/supportedTypes.ts +++ b/packages/abi-typegen/src/utils/supportedTypes.ts @@ -3,6 +3,7 @@ import { B256Type } from '../abi/types/B256Type'; import { B512Type } from '../abi/types/B512Type'; import { BoolType } from '../abi/types/BoolType'; import { BytesType } from '../abi/types/BytesType'; +import { EmptyType } from '../abi/types/EmptyType'; import { EnumType } from '../abi/types/EnumType'; import { EvmAddressType } from '../abi/types/EvmAddressType'; import { GenericType } from '../abi/types/GenericType'; @@ -22,6 +23,7 @@ import { U8Type } from '../abi/types/U8Type'; import { VectorType } from '../abi/types/VectorType'; export const supportedTypes = [ + EmptyType, ArrayType, B256Type, B512Type, From 6c41a7d717a0f409a4f4542c786146160591a043 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 19 Apr 2024 12:45:52 +0200 Subject: [PATCH 02/10] chore: changeset --- .changeset/polite-rabbits-care.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/polite-rabbits-care.md diff --git a/.changeset/polite-rabbits-care.md b/.changeset/polite-rabbits-care.md new file mode 100644 index 0000000000..9d02e5246f --- /dev/null +++ b/.changeset/polite-rabbits-care.md @@ -0,0 +1,5 @@ +--- +"@fuel-ts/abi-typegen": patch +--- + +feat: support `()` type in typegen From 19296ca82a5b396cc09846231d0ff325a2548ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Fri, 19 Apr 2024 12:55:37 +0200 Subject: [PATCH 03/10] Update packages/abi-typegen/src/abi/types/EnumType.ts Co-authored-by: Peter Smith --- packages/abi-typegen/src/abi/types/EnumType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/abi-typegen/src/abi/types/EnumType.ts b/packages/abi-typegen/src/abi/types/EnumType.ts index c2b97761f4..cab3f803a8 100644 --- a/packages/abi-typegen/src/abi/types/EnumType.ts +++ b/packages/abi-typegen/src/abi/types/EnumType.ts @@ -56,7 +56,7 @@ export class EnumType extends AType implements IType { // `components` array guaranteed to always exist for structs/enums const enumComponents = components as IRawAbiTypeComponent[]; - if (!enumComponents.every(({ type }) => typeHash[type] === '()')) { + if (!enumComponents.every(({ type }) => typeHash[type] === EmptyType.swayType)) { return undefined; } From 8d1c79453f4fe0719d28fe89e5177f73996f3f59 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 19 Apr 2024 13:34:50 +0200 Subject: [PATCH 04/10] fix: missing import --- packages/abi-typegen/src/abi/types/EnumType.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/abi-typegen/src/abi/types/EnumType.ts b/packages/abi-typegen/src/abi/types/EnumType.ts index cab3f803a8..20dae73837 100644 --- a/packages/abi-typegen/src/abi/types/EnumType.ts +++ b/packages/abi-typegen/src/abi/types/EnumType.ts @@ -5,6 +5,7 @@ import { extractStructName } from '../../utils/extractStructName'; import { findType } from '../../utils/findType'; import { AType } from './AType'; +import { EmptyType } from './EmptyType'; export class EnumType extends AType implements IType { public static swayType = 'enum MyEnumName'; From 03113796fab175cf995dffe98dc554a5e984562d Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 19 Apr 2024 13:40:14 +0200 Subject: [PATCH 05/10] test: verify that `()` type is generated properly --- .../abi-typegen/test/fixtures/forc-projects/full/src/main.sw | 4 ++++ packages/abi-typegen/test/fixtures/templates/contract/dts.hbs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw index 5d419b9da2..0d24cd1fa3 100644 --- a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw +++ b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw @@ -25,6 +25,7 @@ struct StructWithSingleOption { } abi MyContract { + fn types_empty(x: ()) -> (); fn types_u8(x: u8) -> u8; fn types_u16(x: u16) -> u16; fn types_u32(x: u32) -> u32; @@ -51,6 +52,9 @@ abi MyContract { } impl MyContract for Contract { + fn types_empty(x: ()) -> () { + x + } fn types_u8(x: u8) -> u8 { 255 } diff --git a/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs b/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs index 6774eb07bb..d413fde979 100644 --- a/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs +++ b/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs @@ -46,6 +46,7 @@ interface MyContractAbiInterface extends Interface { types_b512: FunctionFragment; types_bool: FunctionFragment; types_bytes: FunctionFragment; + types_empty: FunctionFragment; types_enum: FunctionFragment; types_evm_address: FunctionFragment; types_option: FunctionFragment; @@ -71,6 +72,7 @@ interface MyContractAbiInterface extends Interface { encodeFunctionData(functionFragment: 'types_b512', values: [string]): Uint8Array; encodeFunctionData(functionFragment: 'types_bool', values: [boolean]): Uint8Array; encodeFunctionData(functionFragment: 'types_bytes', values: [Bytes]): Uint8Array; + encodeFunctionData(functionFragment: 'types_empty', values: [undefined]): Uint8Array; encodeFunctionData(functionFragment: 'types_enum', values: [MyEnumInput]): Uint8Array; encodeFunctionData(functionFragment: 'types_evm_address', values: [EvmAddress]): Uint8Array; encodeFunctionData(functionFragment: 'types_option', values: [Option]): Uint8Array; @@ -95,6 +97,7 @@ interface MyContractAbiInterface extends Interface { decodeFunctionData(functionFragment: 'types_b512', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_bool', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_bytes', data: BytesLike): DecodedValue; + decodeFunctionData(functionFragment: 'types_empty', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_enum', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_evm_address', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_option', data: BytesLike): DecodedValue; @@ -123,6 +126,7 @@ export class MyContractAbi extends Contract { types_b512: InvokeFunction<[x: string], string>; types_bool: InvokeFunction<[x: boolean], boolean>; types_bytes: InvokeFunction<[x: Bytes], Bytes>; + types_empty: InvokeFunction<[x: undefined], undefined>; types_enum: InvokeFunction<[x: MyEnumInput], MyEnumOutput>; types_evm_address: InvokeFunction<[x: EvmAddress], EvmAddress>; types_option: InvokeFunction<[x: Option], Option>; From 65c66617a6d0c2ed1231d19bdd362da23d97a308 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 22 Apr 2024 12:47:19 +0200 Subject: [PATCH 06/10] feat: ignore empty function inputs --- .../abi-typegen/src/abi/functions/Function.ts | 50 +++++++++++-------- .../src/abi/types/EmptyType.test.ts | 4 +- .../abi-typegen/src/abi/types/EmptyType.ts | 8 ++- .../src/abi/types/OptionType.test.ts | 4 +- .../src/utils/parseTypeArguments.test.ts | 2 +- .../src/utils/parseTypeArguments.ts | 11 +--- .../fixtures/forc-projects/full/src/main.sw | 13 +++++ .../test/fixtures/templates/contract/dts.hbs | 16 +++++- .../fuel-gauge/src/call-test-contract.test.ts | 22 ++++++-- 9 files changed, 86 insertions(+), 44 deletions(-) diff --git a/packages/abi-typegen/src/abi/functions/Function.ts b/packages/abi-typegen/src/abi/functions/Function.ts index f6ad8e8792..e479d31ff1 100644 --- a/packages/abi-typegen/src/abi/functions/Function.ts +++ b/packages/abi-typegen/src/abi/functions/Function.ts @@ -3,6 +3,7 @@ import { TargetEnum } from '../../types/enums/TargetEnum'; import type { IType } from '../../types/interfaces/IType'; import { findType } from '../../utils/findType'; import { parseTypeArguments } from '../../utils/parseTypeArguments'; +import { EmptyType } from '../types/EmptyType'; export class Function implements IFunction { public name: string; @@ -26,33 +27,38 @@ export class Function implements IFunction { const { types } = this; // loop through all inputs - const inputs = this.rawAbiFunction.inputs.map((input) => { - const { name, type: typeId, typeArguments } = input; + const inputs = this.rawAbiFunction.inputs + .filter((input) => { + const type = findType({ types, typeId: input.type }); + return type.rawAbiType.type !== EmptyType.swayType; + }) + .map((input) => { + const { name, type: typeId, typeArguments } = input; - const type = findType({ types, typeId }); + const type = findType({ types, typeId }); - let typeDecl: string; + let typeDecl: string; - if (typeArguments) { - // recursively process child `typeArguments` - typeDecl = parseTypeArguments({ - types, - target: TargetEnum.INPUT, - parentTypeId: typeId, - typeArguments, - }); - } else { - // or just collect type declaration - typeDecl = type.attributes.inputLabel; - } + if (typeArguments) { + // recursively process child `typeArguments` + typeDecl = parseTypeArguments({ + types, + target: TargetEnum.INPUT, + parentTypeId: typeId, + typeArguments, + }); + } else { + // or just collect type declaration + typeDecl = type.attributes.inputLabel; + } - // assemble it in `[key: string]: ` fashion - if (shouldPrefixParams) { - return `${name}: ${typeDecl}`; - } + // assemble it in `[key: string]: ` fashion + if (shouldPrefixParams) { + return `${name}: ${typeDecl}`; + } - return typeDecl; - }); + return typeDecl; + }); return inputs.join(', '); } diff --git a/packages/abi-typegen/src/abi/types/EmptyType.test.ts b/packages/abi-typegen/src/abi/types/EmptyType.test.ts index dcf80963be..8b2a745d4b 100644 --- a/packages/abi-typegen/src/abi/types/EmptyType.test.ts +++ b/packages/abi-typegen/src/abi/types/EmptyType.test.ts @@ -14,7 +14,7 @@ describe('EmptyType.ts', () => { }, }); - expect(emptyType.attributes.inputLabel).toEqual('undefined'); - expect(emptyType.attributes.outputLabel).toEqual('undefined'); + expect(emptyType.attributes.inputLabel).toEqual('never'); + expect(emptyType.attributes.outputLabel).toEqual('void'); }); }); diff --git a/packages/abi-typegen/src/abi/types/EmptyType.ts b/packages/abi-typegen/src/abi/types/EmptyType.ts index 7ab179c71c..8771c19ac6 100644 --- a/packages/abi-typegen/src/abi/types/EmptyType.ts +++ b/packages/abi-typegen/src/abi/types/EmptyType.ts @@ -13,8 +13,12 @@ export class EmptyType extends AType implements IType { constructor(params: { rawAbiType: IRawAbiTypeRoot }) { super(params); this.attributes = { - inputLabel: `undefined`, - outputLabel: `undefined`, + /** + * This type is always ignored as function's input + * and it's an error in the typegen if it makes its way into a function's inputs list + */ + inputLabel: `never`, + outputLabel: `void`, }; } diff --git a/packages/abi-typegen/src/abi/types/OptionType.test.ts b/packages/abi-typegen/src/abi/types/OptionType.test.ts index 2e8d80f7b3..f85d78a822 100644 --- a/packages/abi-typegen/src/abi/types/OptionType.test.ts +++ b/packages/abi-typegen/src/abi/types/OptionType.test.ts @@ -20,9 +20,7 @@ describe('OptionType.ts', () => { const project = getTypegenForcProject(AbiTypegenProjectsEnum.OPTION_SIMPLE); const rawTypes = project.abiContents.types; - const types = rawTypes - .filter((t) => t.type !== '()') - .map((rawAbiType: IRawAbiTypeRoot) => makeType({ rawAbiType })); + const types = rawTypes.map((rawAbiType: IRawAbiTypeRoot) => makeType({ rawAbiType })); return { types }; } diff --git a/packages/abi-typegen/src/utils/parseTypeArguments.test.ts b/packages/abi-typegen/src/utils/parseTypeArguments.test.ts index 397e624733..994c98ed92 100644 --- a/packages/abi-typegen/src/utils/parseTypeArguments.test.ts +++ b/packages/abi-typegen/src/utils/parseTypeArguments.test.ts @@ -108,7 +108,7 @@ describe('parseTypeArguments.ts', () => { test('should fallback to void for null outputs', () => { const project = getTypegenForcProject(AbiTypegenProjectsEnum.FN_VOID); - const types = bundleTypes([]); + const types = bundleTypes(project.abiContents.types); const typeArguments = [project.abiContents.functions[0].output]; // should fallback to void because `typeArguments.type` will be 0, and non-existent diff --git a/packages/abi-typegen/src/utils/parseTypeArguments.ts b/packages/abi-typegen/src/utils/parseTypeArguments.ts index d64bfc4c02..f7e9820f6c 100644 --- a/packages/abi-typegen/src/utils/parseTypeArguments.ts +++ b/packages/abi-typegen/src/utils/parseTypeArguments.ts @@ -29,17 +29,10 @@ export function parseTypeArguments(params: { // loop through all `typeArgument` items typeArguments.forEach((typeArgument) => { - let currentLabel: string; - const currentTypeId = typeArgument.type; - try { - const currentType = findType({ types, typeId: currentTypeId }); - currentLabel = currentType.attributes[attributeKey]; - } catch (_err) { - // used for functions without output - currentLabel = 'void'; - } + const currentType = findType({ types, typeId: currentTypeId }); + const currentLabel = currentType.attributes[attributeKey]; if (typeArgument.typeArguments) { // recursively process nested `typeArguments` diff --git a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw index 0d24cd1fa3..cd20dd93a6 100644 --- a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw +++ b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw @@ -26,6 +26,9 @@ struct StructWithSingleOption { abi MyContract { fn types_empty(x: ()) -> (); + fn types_empty_then_value(x: (), y: u8) -> (); + fn types_value_then_empty(x: u8, y: ()) -> (); + fn types_value_then_empty_then_value(x: u8, y: (), z: u8) -> (); fn types_u8(x: u8) -> u8; fn types_u16(x: u16) -> u16; fn types_u32(x: u32) -> u32; @@ -55,6 +58,16 @@ impl MyContract for Contract { fn types_empty(x: ()) -> () { x } + fn types_empty_then_value(x: (), y: u8) -> () { + () + } + fn types_value_then_empty(x: u8, y: ()) -> () { + () + } + fn types_value_then_empty_then_value(x: u8, y: (), z: u8) -> () { + () + } + fn types_u8(x: u8) -> u8 { 255 } diff --git a/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs b/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs index d413fde979..ae5efb6440 100644 --- a/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs +++ b/packages/abi-typegen/test/fixtures/templates/contract/dts.hbs @@ -47,6 +47,7 @@ interface MyContractAbiInterface extends Interface { types_bool: FunctionFragment; types_bytes: FunctionFragment; types_empty: FunctionFragment; + types_empty_then_value: FunctionFragment; types_enum: FunctionFragment; types_evm_address: FunctionFragment; types_option: FunctionFragment; @@ -61,6 +62,8 @@ interface MyContractAbiInterface extends Interface { types_u32: FunctionFragment; types_u64: FunctionFragment; types_u8: FunctionFragment; + types_value_then_empty: FunctionFragment; + types_value_then_empty_then_value: FunctionFragment; types_vector_geo: FunctionFragment; types_vector_option: FunctionFragment; types_vector_u8: FunctionFragment; @@ -72,7 +75,8 @@ interface MyContractAbiInterface extends Interface { encodeFunctionData(functionFragment: 'types_b512', values: [string]): Uint8Array; encodeFunctionData(functionFragment: 'types_bool', values: [boolean]): Uint8Array; encodeFunctionData(functionFragment: 'types_bytes', values: [Bytes]): Uint8Array; - encodeFunctionData(functionFragment: 'types_empty', values: [undefined]): Uint8Array; + encodeFunctionData(functionFragment: 'types_empty', values: []): Uint8Array; + encodeFunctionData(functionFragment: 'types_empty_then_value', values: [BigNumberish]): Uint8Array; encodeFunctionData(functionFragment: 'types_enum', values: [MyEnumInput]): Uint8Array; encodeFunctionData(functionFragment: 'types_evm_address', values: [EvmAddress]): Uint8Array; encodeFunctionData(functionFragment: 'types_option', values: [Option]): Uint8Array; @@ -87,6 +91,8 @@ interface MyContractAbiInterface extends Interface { encodeFunctionData(functionFragment: 'types_u32', values: [BigNumberish]): Uint8Array; encodeFunctionData(functionFragment: 'types_u64', values: [BigNumberish]): Uint8Array; encodeFunctionData(functionFragment: 'types_u8', values: [BigNumberish]): Uint8Array; + encodeFunctionData(functionFragment: 'types_value_then_empty', values: [BigNumberish]): Uint8Array; + encodeFunctionData(functionFragment: 'types_value_then_empty_then_value', values: [BigNumberish, BigNumberish]): Uint8Array; encodeFunctionData(functionFragment: 'types_vector_geo', values: [Vec]): Uint8Array; encodeFunctionData(functionFragment: 'types_vector_option', values: [Vec]): Uint8Array; encodeFunctionData(functionFragment: 'types_vector_u8', values: [Vec]): Uint8Array; @@ -98,6 +104,7 @@ interface MyContractAbiInterface extends Interface { decodeFunctionData(functionFragment: 'types_bool', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_bytes', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_empty', data: BytesLike): DecodedValue; + decodeFunctionData(functionFragment: 'types_empty_then_value', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_enum', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_evm_address', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_option', data: BytesLike): DecodedValue; @@ -112,6 +119,8 @@ interface MyContractAbiInterface extends Interface { decodeFunctionData(functionFragment: 'types_u32', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_u64', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_u8', data: BytesLike): DecodedValue; + decodeFunctionData(functionFragment: 'types_value_then_empty', data: BytesLike): DecodedValue; + decodeFunctionData(functionFragment: 'types_value_then_empty_then_value', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_vector_geo', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_vector_option', data: BytesLike): DecodedValue; decodeFunctionData(functionFragment: 'types_vector_u8', data: BytesLike): DecodedValue; @@ -126,7 +135,8 @@ export class MyContractAbi extends Contract { types_b512: InvokeFunction<[x: string], string>; types_bool: InvokeFunction<[x: boolean], boolean>; types_bytes: InvokeFunction<[x: Bytes], Bytes>; - types_empty: InvokeFunction<[x: undefined], undefined>; + types_empty: InvokeFunction<[], void>; + types_empty_then_value: InvokeFunction<[y: BigNumberish], void>; types_enum: InvokeFunction<[x: MyEnumInput], MyEnumOutput>; types_evm_address: InvokeFunction<[x: EvmAddress], EvmAddress>; types_option: InvokeFunction<[x: Option], Option>; @@ -141,6 +151,8 @@ export class MyContractAbi extends Contract { types_u32: InvokeFunction<[x: BigNumberish], number>; types_u64: InvokeFunction<[x: BigNumberish], BN>; types_u8: InvokeFunction<[x: BigNumberish], number>; + types_value_then_empty: InvokeFunction<[x: BigNumberish], void>; + types_value_then_empty_then_value: InvokeFunction<[x: BigNumberish, z: BigNumberish], void>; types_vector_geo: InvokeFunction<[x: Vec], Vec>; types_vector_option: InvokeFunction<[x: Vec], Vec>; types_vector_u8: InvokeFunction<[x: Vec], Vec>; diff --git a/packages/fuel-gauge/src/call-test-contract.test.ts b/packages/fuel-gauge/src/call-test-contract.test.ts index f85c0c0b8a..7659d9c218 100644 --- a/packages/fuel-gauge/src/call-test-contract.test.ts +++ b/packages/fuel-gauge/src/call-test-contract.test.ts @@ -1,4 +1,5 @@ import { ASSET_A } from '@fuel-ts/utils/test-utils'; +import type { BigNumberish, Contract } from 'fuels'; import { BN, bn, toHex, BaseAssetId } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -23,7 +24,7 @@ const U64_MAX = bn(2).pow(64).sub(1); describe('CallTestContract', () => { it.each([0, 1337, U64_MAX.sub(1)])('can call a contract with u64 (%p)', async (num) => { const contract = await setupContract(); - const { value } = await contract.functions.foo(num).call(); + const { value } = await contract.functions.foo(num).call(); expect(value.toHex()).toEqual(bn(num).add(1).toHex()); }); @@ -49,6 +50,17 @@ describe('CallTestContract', () => { const { value: value1 } = await contract.functions.foobar().call(); expect(value1.toHex()).toEqual(toHex(63)); + + const { value: value2 } = await contract.functions.foobar2(35).call(); + expect(value2.toHex()).toEqual(toHex(63)); + + // @ts-expect-error asd + const { value: value3 } = await contract.functions.foobar3(35).call(); + expect(value3.toHex()).toEqual(toHex(63)); + + // @ts-expect-error asd + const { value: value4 } = await contract.functions.foobar4(35, 35).call(); + expect(value4.toHex()).toEqual(toHex(63)); }); it('function with empty return should resolve undefined', async () => { @@ -124,10 +136,14 @@ describe('CallTestContract', () => { expected: '0x0000000000000000000000000000000000000000000000000000000000000001', }, ], - ])( + ] as Array< + [keyof CallTestContractAbi['functions'], { values: unknown[]; expected: BigNumberish }] + >)( `Test call with multiple arguments and different types -> %s`, async (method, { values, expected }) => { - const contract = await setupContract(); + // Type cast to Contract because of the dynamic nature of the test + // But the function names are type-constrained to correct Contract's type + const contract = (await setupContract()) as Contract; const { value } = await contract.functions[method](...values).call(); From cf9b651b916fd4fd7bb68d0c122a0cd4f6c41316 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 22 Apr 2024 13:28:09 +0200 Subject: [PATCH 07/10] test: added tests covering various empty param use cases --- .../fuel-gauge/src/call-test-contract.test.ts | 35 ++++++++----------- .../call-test-contract/src/main.sw | 20 +++++++---- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/fuel-gauge/src/call-test-contract.test.ts b/packages/fuel-gauge/src/call-test-contract.test.ts index 7659d9c218..193ca1a2ed 100644 --- a/packages/fuel-gauge/src/call-test-contract.test.ts +++ b/packages/fuel-gauge/src/call-test-contract.test.ts @@ -1,5 +1,5 @@ import { ASSET_A } from '@fuel-ts/utils/test-utils'; -import type { BigNumberish, Contract } from 'fuels'; +import type { Contract } from 'fuels'; import { BN, bn, toHex, BaseAssetId } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -45,22 +45,19 @@ describe('CallTestContract', () => { it('can call a function with empty arguments', async () => { const contract = await setupContract(); - const { value: value0 } = await contract.functions.barfoo(0).call(); - expect(value0.toHex()).toEqual(toHex(63)); + const { value: empty } = await contract.functions.empty().call(); + expect(empty.toHex()).toEqual(toHex(63)); - const { value: value1 } = await contract.functions.foobar().call(); - expect(value1.toHex()).toEqual(toHex(63)); + const { value: emptyThenValue } = await contract.functions.empty_then_value(35).call(); + expect(emptyThenValue.toHex()).toEqual(toHex(63)); - const { value: value2 } = await contract.functions.foobar2(35).call(); - expect(value2.toHex()).toEqual(toHex(63)); + const { value: valueThenEmpty } = await contract.functions.value_then_empty(35).call(); + expect(valueThenEmpty.toHex()).toEqual(toHex(63)); - // @ts-expect-error asd - const { value: value3 } = await contract.functions.foobar3(35).call(); - expect(value3.toHex()).toEqual(toHex(63)); - - // @ts-expect-error asd - const { value: value4 } = await contract.functions.foobar4(35, 35).call(); - expect(value4.toHex()).toEqual(toHex(63)); + const { value: valueThenEmptyThenValue } = await contract.functions + .value_then_empty_then_value(35, 35) + .call(); + expect(valueThenEmptyThenValue.toHex()).toEqual(toHex(63)); }); it('function with empty return should resolve undefined', async () => { @@ -73,7 +70,7 @@ describe('CallTestContract', () => { it.each([ [ - 'foobar_no_params', + 'no_params', { values: [], expected: bn(50), @@ -136,9 +133,7 @@ describe('CallTestContract', () => { expected: '0x0000000000000000000000000000000000000000000000000000000000000001', }, ], - ] as Array< - [keyof CallTestContractAbi['functions'], { values: unknown[]; expected: BigNumberish }] - >)( + ])( `Test call with multiple arguments and different types -> %s`, async (method, { values, expected }) => { // Type cast to Contract because of the dynamic nature of the test @@ -230,7 +225,7 @@ describe('CallTestContract', () => { it('Calling a simple contract function does only one dry run', async () => { const contract = await setupContract(); const dryRunSpy = vi.spyOn(contract.provider.operations, 'dryRun'); - await contract.functions.foobar_no_params().call(); + await contract.functions.no_params().call(); expect(dryRunSpy).toHaveBeenCalledOnce(); }); @@ -238,7 +233,7 @@ describe('CallTestContract', () => { const contract = await setupContract(); const dryRunSpy = vi.spyOn(contract.provider.operations, 'dryRun'); - await contract.functions.foobar_no_params().simulate(); + await contract.functions.no_params().simulate(); expect(dryRunSpy).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw index 75757f20da..0ae04aff10 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw @@ -40,9 +40,11 @@ abi TestContract { fn foo(value: u64) -> u64; fn call_external_foo(param: u64, contract_id: b256) -> u64; fn boo(value: TestStruct) -> TestStruct; - fn barfoo(value: u64) -> u64; - fn foobar(value: ()) -> u64; - fn foobar_no_params() -> u64; + fn empty(empty: ()) -> u64; + fn empty_then_value(empty: (), value: u8) -> u64; + fn value_then_empty(value: u8, empty: ()) -> u64; + fn value_then_empty_then_value(value: u8, empty: (), value2: u8) -> u64; + fn no_params() -> u64; fn sum(a: u64, b: u64) -> u64; fn sum_test(a: u64, test: SumStruct) -> u64; fn sum_single(test: SumStruct) -> u64; @@ -90,13 +92,19 @@ impl TestContract for Contract { b: value.b + 1, } } - fn barfoo(value: u64) -> u64 { + fn empty(value: ()) -> u64 { 63 } - fn foobar(value: ()) -> u64 { + fn empty_then_value(empty: (), value: u8) -> u64 { 63 } - fn foobar_no_params() -> u64 { + fn value_then_empty(value: u8, empty: ()) -> u64 { + 63 + } + fn value_then_empty_then_value(value: u8, empty: (), value2: u8) -> u64 { + 63 + } + fn no_params() -> u64 { 50 } fn sum(a: u64, b: u64) -> u64 { From 1d9b7d45ae823724a91d0e6fb77c99fb05a94a6b Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 22 Apr 2024 13:51:34 +0200 Subject: [PATCH 08/10] fix: rename to correct fn name --- packages/fuel-gauge/src/fee.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fuel-gauge/src/fee.test.ts b/packages/fuel-gauge/src/fee.test.ts index c8cc08b8df..f413b4cb4d 100644 --- a/packages/fuel-gauge/src/fee.test.ts +++ b/packages/fuel-gauge/src/fee.test.ts @@ -233,7 +233,7 @@ describe('Fee', () => { .multiCall([ contract.functions.sum_multparams(1, 2, 3, 4, 5), contract.functions.return_void(), - contract.functions.foobar(), + contract.functions.empty(), contract.functions.return_bytes(), ]) .txParams({ From ed6b1553e7f3d446ea72bd688bdc28f2cc8f6bd4 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 22 Apr 2024 15:17:04 +0200 Subject: [PATCH 09/10] fix: better explanation --- packages/abi-typegen/src/abi/types/EmptyType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/abi-typegen/src/abi/types/EmptyType.ts b/packages/abi-typegen/src/abi/types/EmptyType.ts index 8771c19ac6..96a672169c 100644 --- a/packages/abi-typegen/src/abi/types/EmptyType.ts +++ b/packages/abi-typegen/src/abi/types/EmptyType.ts @@ -14,8 +14,8 @@ export class EmptyType extends AType implements IType { super(params); this.attributes = { /** - * This type is always ignored as function's input - * and it's an error in the typegen if it makes its way into a function's inputs list + * The empty type is always ignored in function inputs + * and it's an bug in the typegen if it makes its way into a function's inputs list */ inputLabel: `never`, outputLabel: `void`, From 0a7cf759cac2d2e934be714654852e7037b8c700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Tue, 23 Apr 2024 16:39:03 +0200 Subject: [PATCH 10/10] Update packages/abi-typegen/src/abi/types/EmptyType.ts Co-authored-by: Anderson Arboleya --- packages/abi-typegen/src/abi/types/EmptyType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/abi-typegen/src/abi/types/EmptyType.ts b/packages/abi-typegen/src/abi/types/EmptyType.ts index 96a672169c..29ef9a97b5 100644 --- a/packages/abi-typegen/src/abi/types/EmptyType.ts +++ b/packages/abi-typegen/src/abi/types/EmptyType.ts @@ -14,8 +14,8 @@ export class EmptyType extends AType implements IType { super(params); this.attributes = { /** - * The empty type is always ignored in function inputs - * and it's an bug in the typegen if it makes its way into a function's inputs list + * The empty type is always ignored in function inputs. If it makes + * its way into a function's inputs list, it's a bug in the typegen. */ inputLabel: `never`, outputLabel: `void`,