From 2a97695b05ac424df97d9b4706a4baed02c83f5e Mon Sep 17 00:00:00 2001 From: Mihailo Date: Mon, 23 Jan 2023 12:23:52 +0100 Subject: [PATCH 1/2] Add struct parsing to @typechain/web3-v1 (#779) --- .changeset/pink-ads-rhyme.md | 5 + .../types/v0.6.4/DataTypesInput.ts | 262 +++++++++++++++--- .../types/v0.6.4/DataTypesPure.ts | 29 +- .../types/v0.6.4/DataTypesView.ts | 29 +- .../types/v0.6.4/Events.ts | 15 +- .../types/v0.8.9/Issue552_Reproduction.ts | 32 ++- .../v0.8.9/KingOfTheHill/KingOfTheHill.ts | 24 +- .../types/v0.8.9/Rarity/Rarity.ts | 20 +- packages/target-web3-v1/src/codegen/events.ts | 4 +- .../target-web3-v1/src/codegen/functions.ts | 30 +- packages/target-web3-v1/src/codegen/index.ts | 10 +- .../target-web3-v1/src/codegen/structs.ts | 48 ++++ packages/target-web3-v1/src/codegen/types.ts | 113 ++++++-- packages/target-web3-v1/src/common.ts | 2 + packages/target-web3-v1/src/index.ts | 2 +- 15 files changed, 503 insertions(+), 122 deletions(-) create mode 100644 .changeset/pink-ads-rhyme.md create mode 100644 packages/target-web3-v1/src/codegen/structs.ts create mode 100644 packages/target-web3-v1/src/common.ts diff --git a/.changeset/pink-ads-rhyme.md b/.changeset/pink-ads-rhyme.md new file mode 100644 index 000000000..135f81340 --- /dev/null +++ b/.changeset/pink-ads-rhyme.md @@ -0,0 +1,5 @@ +--- +'@typechain/web3-v1': minor +--- + +Add struct parsing and typing for web3 library diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts index 19388318b..c14d7edcb 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts @@ -21,6 +21,46 @@ export interface EventOptions { topics?: string[]; } +export declare namespace StructsLib1 { + export type InfoStruct = + | [number | string | BN, number | string | BN] + | { a: number | string | BN; b: number | string | BN }; + + export type InfoStructOutput = [string, string] & { a: string; b: string }; +} + +export declare namespace StructsLib2 { + export type InfoStruct = [string, string] | { a: string; b: string }; + + export type InfoStructOutput = [string, string] & { a: string; b: string }; +} + +export declare namespace DataTypesInput { + export type Struct1Struct = + | [number | string | BN, number | string | BN] + | { uint256_0: number | string | BN; uint256_1: number | string | BN }; + + export type Struct1StructOutput = [string, string] & { + uint256_0: string; + uint256_1: string; + }; + + export type Struct2Struct = + | [number | string | BN, DataTypesInput.Struct1Struct] + | { input1: number | string | BN; input2: DataTypesInput.Struct1Struct }; + + export type Struct2StructOutput = [ + string, + DataTypesInput.Struct1StructOutput + ] & { input1: string; input2: DataTypesInput.Struct1StructOutput }; + + export type Struct3Struct = + | [number | string | BN[]] + | { input1: number | string | BN[] }; + + export type Struct3StructOutput = [string[]] & { input1: string[] }; +} + export interface DataTypesInput extends BaseContract { constructor( jsonInterface: any[], @@ -44,8 +84,20 @@ export interface DataTypesInput extends BaseContract { ): NonPayableTransactionObject; input_fixedarray_array_fixedarray( - input1: (number | string | BN)[][][] - ): NonPayableTransactionObject; + input1: [ + [number | string | BN, number | string | BN, number | string | BN][], + [number | string | BN, number | string | BN, number | string | BN][], + [number | string | BN, number | string | BN, number | string | BN][], + [number | string | BN, number | string | BN, number | string | BN][] + ] + ): NonPayableTransactionObject< + [ + [string, string, string][], + [string, string, string][], + [string, string, string][], + [string, string, string][] + ] + >; input_int256( input1: number | string | BN @@ -56,83 +108,203 @@ export interface DataTypesInput extends BaseContract { ): NonPayableTransactionObject; input_multiple_structs_with_same_name( - info1: [number | string | BN, number | string | BN] - ): NonPayableTransactionObject<[string, string]>; + info1: StructsLib1.InfoStruct + ): NonPayableTransactionObject; input_stat_array( - input1: (number | string | BN)[] - ): NonPayableTransactionObject; + input1: [number | string | BN, number | string | BN, number | string | BN] + ): NonPayableTransactionObject<[string, string, string]>; input_string(input1: string): NonPayableTransactionObject; input_struct( - input1: [number | string | BN, number | string | BN] - ): NonPayableTransactionObject<[string, string]>; + input1: DataTypesInput.Struct1Struct + ): NonPayableTransactionObject; input_struct2( - input1: [ - number | string | BN, - [number | string | BN, number | string | BN] - ] - ): NonPayableTransactionObject<[string, [string, string]]>; + input1: DataTypesInput.Struct2Struct + ): NonPayableTransactionObject; input_struct2_array( - input1: [ - number | string | BN, - [number | string | BN, number | string | BN] - ][] - ): NonPayableTransactionObject<[string, [string, string]][]>; + input1: DataTypesInput.Struct2Struct[] + ): NonPayableTransactionObject; input_struct2_tuple( input: [ - number | string | BN, - [number | string | BN, number | string | BN] - ][] - ): NonPayableTransactionObject<[string, [string, string]][]>; + DataTypesInput.Struct2Struct, + DataTypesInput.Struct2Struct, + DataTypesInput.Struct2Struct + ] + ): NonPayableTransactionObject< + [ + DataTypesInput.Struct2StructOutput, + DataTypesInput.Struct2StructOutput, + DataTypesInput.Struct2StructOutput + ] + >; input_struct3_array( - input1: [(number | string | BN)[]][] - ): NonPayableTransactionObject<[string[]][]>; + input1: DataTypesInput.Struct3Struct[] + ): NonPayableTransactionObject; input_struct_array( - input1: [number | string | BN, number | string | BN][] - ): NonPayableTransactionObject<[string, string][]>; + input1: DataTypesInput.Struct1Struct[] + ): NonPayableTransactionObject; input_struct_array_array( - input1: [number | string | BN, number | string | BN][][] - ): NonPayableTransactionObject<[string, string][][]>; + input1: DataTypesInput.Struct1Struct[][] + ): NonPayableTransactionObject; input_struct_array_array_array( - input1: [number | string | BN, number | string | BN][][][] - ): NonPayableTransactionObject<[string, string][][][]>; + input1: DataTypesInput.Struct1Struct[][][] + ): NonPayableTransactionObject; input_struct_array_fixedarray( - input1: [number | string | BN, number | string | BN][][] - ): NonPayableTransactionObject<[string, string][][]>; + input1: [DataTypesInput.Struct1Struct[], DataTypesInput.Struct1Struct[]] + ): NonPayableTransactionObject< + [ + DataTypesInput.Struct1StructOutput[], + DataTypesInput.Struct1StructOutput[] + ] + >; input_struct_fixedarray_array( - input1: [number | string | BN, number | string | BN][][] - ): NonPayableTransactionObject<[string, string][][]>; + input1: [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ): NonPayableTransactionObject< + [DataTypesInput.Struct1StructOutput, DataTypesInput.Struct1StructOutput][] + >; input_struct_fixedarray_array_fixedarray( - input1: [number | string | BN, number | string | BN][][][] - ): NonPayableTransactionObject<[string, string][][][]>; + input1: [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ] + ): NonPayableTransactionObject< + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][] + ] + >; input_struct_fixedarray_array_fixedarray_array_fixedarray( - input1: [number | string | BN, number | string | BN][][][][][] - ): NonPayableTransactionObject<[string, string][][][][][]>; + input1: [ + [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ][], + [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ][], + [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ][], + [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct][] + ][] + ] + ): NonPayableTransactionObject< + [ + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][] + ][], + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][] + ][], + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][] + ][], + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ][] + ][] + ] + >; input_struct_fixedarray_fixedarray( - input1: [number | string | BN, number | string | BN][][] - ): NonPayableTransactionObject<[string, string][][]>; + input1: [ + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct], + [DataTypesInput.Struct1Struct, DataTypesInput.Struct1Struct] + ] + ): NonPayableTransactionObject< + [ + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ], + [ + DataTypesInput.Struct1StructOutput, + DataTypesInput.Struct1StructOutput + ], + [DataTypesInput.Struct1StructOutput, DataTypesInput.Struct1StructOutput] + ] + >; input_tuple( input1: number | string | BN, input2: number | string | BN - ): NonPayableTransactionObject<{ - 0: string; - 1: string; - }>; + ): NonPayableTransactionObject<[string, string]>; input_uint256( input1: number | string | BN @@ -143,7 +315,7 @@ export interface DataTypesInput extends BaseContract { ): NonPayableTransactionObject; input_uint_array( - input1: (number | string | BN)[] + input1: number | string | BN[] ): NonPayableTransactionObject; }; events: { diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts index f4e3b42a8..06c2c4dfb 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts @@ -21,6 +21,17 @@ export interface EventOptions { topics?: string[]; } +export declare namespace DataTypesPure { + export type Struct1Struct = + | [number | string | BN, number | string | BN] + | { uint256_0: number | string | BN; uint256_1: number | string | BN }; + + export type Struct1StructOutput = [string, string] & { + uint256_0: string; + uint256_1: string; + }; +} + export interface DataTypesPure extends BaseContract { constructor( jsonInterface: any[], @@ -43,23 +54,17 @@ export interface DataTypesPure extends BaseContract { pure_int8(): NonPayableTransactionObject; - pure_named(): NonPayableTransactionObject<{ - uint256_1: string; - uint256_2: string; - 0: string; - 1: string; - }>; + pure_named(): NonPayableTransactionObject< + [string, string] & { uint256_1: string; uint256_2: string } + >; - pure_stat_array(): NonPayableTransactionObject; + pure_stat_array(): NonPayableTransactionObject<[string, string, string]>; pure_string(): NonPayableTransactionObject; - pure_struct(): NonPayableTransactionObject<[string, string]>; + pure_struct(): NonPayableTransactionObject; - pure_tuple(): NonPayableTransactionObject<{ - 0: string; - 1: string; - }>; + pure_tuple(): NonPayableTransactionObject<[string, string]>; pure_uint256(): NonPayableTransactionObject; diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts index cbb6568b8..1e980e8a7 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts @@ -21,6 +21,17 @@ export interface EventOptions { topics?: string[]; } +export declare namespace DataTypesView { + export type Struct1Struct = + | [number | string | BN, number | string | BN] + | { uint256_0: number | string | BN; uint256_1: number | string | BN }; + + export type Struct1StructOutput = [string, string] & { + uint256_0: string; + uint256_1: string; + }; +} + export interface DataTypesView extends BaseContract { constructor( jsonInterface: any[], @@ -43,23 +54,17 @@ export interface DataTypesView extends BaseContract { view_int8(): NonPayableTransactionObject; - view_named(): NonPayableTransactionObject<{ - uint256_1: string; - uint256_2: string; - 0: string; - 1: string; - }>; + view_named(): NonPayableTransactionObject< + [string, string] & { uint256_1: string; uint256_2: string } + >; - view_stat_array(): NonPayableTransactionObject; + view_stat_array(): NonPayableTransactionObject<[string, string, string]>; view_string(): NonPayableTransactionObject; - view_struct(): NonPayableTransactionObject<[string, string]>; + view_struct(): NonPayableTransactionObject; - view_tuple(): NonPayableTransactionObject<{ - 0: string; - 1: string; - }>; + view_tuple(): NonPayableTransactionObject<[string, string]>; view_uint256(): NonPayableTransactionObject; diff --git a/packages/target-web3-v1-test/types/v0.6.4/Events.ts b/packages/target-web3-v1-test/types/v0.6.4/Events.ts index df8955d9d..5d4cee71d 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/Events.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/Events.ts @@ -21,6 +21,17 @@ export interface EventOptions { topics?: string[]; } +export declare namespace Events { + export type EventDataStruct = + | [number | string | BN, string] + | { index: number | string | BN; name: string }; + + export type EventDataStructOutput = [string, string] & { + index: string; + name: string; + }; +} + export type AnonEvent1 = ContractEventLog<{ value1: string; 0: string; @@ -45,8 +56,8 @@ export type Event3_uint256 = ContractEventLog<{ 0: string; }>; export type Event4 = ContractEventLog<{ - data: [string, string]; - 0: [string, string]; + data: Events.EventDataStructOutput; + 0: Events.EventDataStructOutput; }>; export type NoArgsEvent = ContractEventLog<{}>; export type UpdateFrequencySet = ContractEventLog<{ diff --git a/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts b/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts index 8d779ffed..3fafc05ad 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts @@ -21,6 +21,34 @@ export interface EventOptions { topics?: string[]; } +export declare namespace Issue552_Observer { + export type ObservationStruct = + | [number | string | BN, number | string | BN] + | { val: number | string | BN; blockTimestamp: number | string | BN }; + + export type ObservationStructOutput = [string, string] & { + val: string; + blockTimestamp: string; + }; +} + +export declare namespace Issue552_Reproduction { + export type ObservationParamsStruct = + | [Issue552_Observer.ObservationStruct[], number | string | BN] + | { + observations: Issue552_Observer.ObservationStruct[]; + index: number | string | BN; + }; + + export type ObservationParamsStructOutput = [ + Issue552_Observer.ObservationStructOutput[], + string + ] & { + observations: Issue552_Observer.ObservationStructOutput[]; + index: string; + }; +} + export interface Issue552_Reproduction extends BaseContract { constructor( jsonInterface: any[], @@ -31,9 +59,9 @@ export interface Issue552_Reproduction extends BaseContract { methods: { bars( arg0: number | string | BN - ): NonPayableTransactionObject<[[string, string][], string]>; + ): NonPayableTransactionObject; - input(values: (number | string | BN)[]): NonPayableTransactionObject; + input(values: number | string | BN[]): NonPayableTransactionObject; makeObservation( barId: number | string | BN, diff --git a/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts b/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts index 7e2451c9f..787f262bd 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts @@ -21,9 +21,20 @@ export interface EventOptions { topics?: string[]; } +export declare namespace KingOfTheHill { + export type BidStruct = + | [string, number | string | BN] + | { bidder: string; value: number | string | BN }; + + export type BidStructOutput = [string, string] & { + bidder: string; + value: string; + }; +} + export type HighestBidIncreased = ContractEventLog<{ - bid: [string, string]; - 0: [string, string]; + bid: KingOfTheHill.BidStructOutput; + 0: KingOfTheHill.BidStructOutput; }>; export interface KingOfTheHill extends BaseContract { @@ -36,12 +47,9 @@ export interface KingOfTheHill extends BaseContract { methods: { bid(): PayableTransactionObject; - highestBid(): NonPayableTransactionObject<{ - bidder: string; - value: string; - 0: string; - 1: string; - }>; + highestBid(): NonPayableTransactionObject< + [string, string] & { bidder: string; value: string } + >; withdraw(): NonPayableTransactionObject; }; diff --git a/packages/target-web3-v1-test/types/v0.8.9/Rarity/Rarity.ts b/packages/target-web3-v1-test/types/v0.8.9/Rarity/Rarity.ts index aad19ac85..bd048ef32 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/Rarity/Rarity.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/Rarity/Rarity.ts @@ -133,16 +133,16 @@ export interface Rarity extends BaseContract { summon(_class: number | string | BN): NonPayableTransactionObject; - summoner(_summoner: number | string | BN): NonPayableTransactionObject<{ - _xp: string; - _log: string; - _class: string; - _level: string; - 0: string; - 1: string; - 2: string; - 3: string; - }>; + summoner( + _summoner: number | string | BN + ): NonPayableTransactionObject< + [string, string, string, string] & { + _xp: string; + _log: string; + _class: string; + _level: string; + } + >; tokenByIndex( index: number | string | BN diff --git a/packages/target-web3-v1/src/codegen/events.ts b/packages/target-web3-v1/src/codegen/events.ts index 952b2b455..cb0bbfd42 100644 --- a/packages/target-web3-v1/src/codegen/events.ts +++ b/packages/target-web3-v1/src/codegen/events.ts @@ -73,7 +73,7 @@ function codegenEventDeclaration(event: EventDeclaration, overloadedName?: strin function codegenOutputTypesForEvents(outputs: EventArgDeclaration[]): string { return `{ - ${outputs.map((t) => t.name && `${t.name}: ${codegenOutputType(t.type)}, `).join('')} - ${outputs.map((t, i) => `${i}: ${codegenOutputType(t.type)}`).join(', ')} + ${outputs.map((t) => t.name && `${t.name}: ${codegenOutputType({ useStructs: true }, t.type)}, `).join('')} + ${outputs.map((t, i) => `${i}: ${codegenOutputType({ useStructs: true }, t.type)}`).join(', ')} }` } diff --git a/packages/target-web3-v1/src/codegen/functions.ts b/packages/target-web3-v1/src/codegen/functions.ts index b3c28a22e..6f16f3f5c 100644 --- a/packages/target-web3-v1/src/codegen/functions.ts +++ b/packages/target-web3-v1/src/codegen/functions.ts @@ -1,29 +1,43 @@ import { values } from 'lodash' import { Dictionary } from 'ts-essentials' -import { FunctionDeclaration, FunctionDocumentation, getSignatureForFn } from 'typechain' +import { CodegenConfig, FunctionDeclaration, FunctionDocumentation, getSignatureForFn } from 'typechain' import { codegenInputTypes, codegenOutputTypes } from './types' -export function codegenForFunctions(fns: Dictionary): string { +interface GenerateFunctionOptions { + returnResultObject?: boolean + isStaticCall?: boolean + overrideOutput?: string + codegenConfig: CodegenConfig +} + +export function codegenForFunctions(fns: Dictionary, options: GenerateFunctionOptions): string { return values(fns) .map((fns) => { if (fns.length === 1) { - return codegenForSingleFunction(fns[0]) + return codegenForSingleFunction(fns[0], options) } else { - return codegenForOverloadedFunctions(fns) + return codegenForOverloadedFunctions(fns, options) } }) .join('\n') } -function codegenForOverloadedFunctions(fns: FunctionDeclaration[]): string { - return fns.map((f) => codegenForSingleFunction(f, `"${getSignatureForFn(f)}"`)).join('\n') +function codegenForOverloadedFunctions(fns: FunctionDeclaration[], options: GenerateFunctionOptions): string { + return fns.map((f) => codegenForSingleFunction(f, options, `"${getSignatureForFn(f)}"`)).join('\n') } -function codegenForSingleFunction(fn: FunctionDeclaration, overloadedName?: string): string { +function codegenForSingleFunction( + fn: FunctionDeclaration, + options: GenerateFunctionOptions, + overloadedName?: string, +): string { return ` ${generateFunctionDocumentation(fn.documentation)} - ${overloadedName ?? fn.name}(${codegenInputTypes(fn.inputs)}): ${getTransactionObject(fn)}<${codegenOutputTypes( + ${overloadedName ?? fn.name}(${codegenInputTypes({ useStructs: true }, fn.inputs)}): ${getTransactionObject( + fn, + )}<${codegenOutputTypes( + { returnResultObject: !!options.returnResultObject, useStructs: true}, fn.outputs, )}>; ` diff --git a/packages/target-web3-v1/src/codegen/index.ts b/packages/target-web3-v1/src/codegen/index.ts index d2106b717..605fcfef6 100644 --- a/packages/target-web3-v1/src/codegen/index.ts +++ b/packages/target-web3-v1/src/codegen/index.ts @@ -1,9 +1,11 @@ -import { Contract } from 'typechain' +import { values } from 'lodash' +import { CodegenConfig, Contract } from 'typechain' import { codegenForEvents, codegenForEventsDeclarations, codegenForEventsOnceFns } from './events' import { codegenForFunctions } from './functions' +import { generateStructTypes } from './structs' -export function codegen(contract: Contract) { +export function codegen(contract: Contract, codegenConfig: CodegenConfig) { const typesPath = contract.path.length ? `${new Array(contract.path.length).fill('..').join('/')}/types` : './types' const template = ` @@ -19,13 +21,15 @@ export function codegen(contract: Contract) { topics?: string[]; } + ${generateStructTypes(values(contract.structs).map((v) => v[0]))} + ${codegenForEventsDeclarations(contract.events)} export interface ${contract.name} extends BaseContract { constructor(jsonInterface: any[], address?: string, options?: ContractOptions): ${contract.name}; clone(): ${contract.name}; methods: { - ${codegenForFunctions(contract.functions)} + ${codegenForFunctions(contract.functions, { codegenConfig })} }; events: { ${codegenForEvents(contract.events)} diff --git a/packages/target-web3-v1/src/codegen/structs.ts b/packages/target-web3-v1/src/codegen/structs.ts new file mode 100644 index 000000000..0b1426ad3 --- /dev/null +++ b/packages/target-web3-v1/src/codegen/structs.ts @@ -0,0 +1,48 @@ +import { groupBy } from 'lodash' +import { StructName, StructType } from 'typechain' + +import { STRUCT_INPUT_POSTFIX, STRUCT_OUTPUT_POSTFIX } from '../common' +import { codegenInputType, codegenOutputType } from './types' + +export function generateStructTypes(structs: StructType[]) { + const namedStructs = structs.filter((s): s is StructWithName => !!s.structName) + const namespaces = groupBy(namedStructs, (s) => s.structName.namespace) + + const exports: string[] = [] + + if ('undefined' in namespaces) { + exports.push(namespaces['undefined'].map((s) => generateExports(s)).join('\n')) + delete namespaces['undefined'] + } + + for (const namespace of Object.keys(namespaces)) { + exports.push(`\nexport declare namespace ${namespace} { + ${namespaces[namespace].map((s) => generateExports(s)).join('\n')} + }`) + } + + return exports.join('\n') +} + +function generateExports(struct: StructWithName): string { + const { identifier } = struct.structName + + const inputName = `${identifier}${STRUCT_INPUT_POSTFIX}` + const outputName = `${identifier}${STRUCT_OUTPUT_POSTFIX}` + const outputNameArray = `${outputName}Array`; + const outputNameObject = `${outputName}Struct`; + const inputType = codegenInputType({ useStructs: false }, struct) + const outputType = codegenOutputType({ useStructs: false }, struct) + + const [outputTypeArray,outputTypeObject] = outputType.split('&'); + + return ` + export type ${inputName} = ${inputType} + + export type ${outputNameArray} = ${outputTypeArray} + export type ${outputNameObject} = ${outputTypeObject} + export type ${outputName} = ${outputNameArray} & ${outputNameObject} + ` +} + +type StructWithName = StructType & { structName: StructName } diff --git a/packages/target-web3-v1/src/codegen/types.ts b/packages/target-web3-v1/src/codegen/types.ts index 7f72068c9..075498a92 100644 --- a/packages/target-web3-v1/src/codegen/types.ts +++ b/packages/target-web3-v1/src/codegen/types.ts @@ -1,26 +1,32 @@ +import { compact } from 'lodash' import { AbiOutputParameter, AbiParameter, EvmOutputType, EvmType, TupleType } from 'typechain' -export function codegenInputTypes(input: AbiParameter[]): string { +import { STRUCT_INPUT_POSTFIX, STRUCT_OUTPUT_POSTFIX } from '../common' + +interface GenerateTypeOptions { + returnResultObject?: boolean + useStructs?: boolean // uses struct type for first depth, if false then generates first depth tuple types +} + +export function codegenInputTypes(options: GenerateTypeOptions, input: Array): string { if (input.length === 0) { return '' } return ( - input.map((input, index) => `${input.name || `arg${index}`}: ${codegenInputType(input.type)}`).join(', ') + ', ' + input.map((input, index) => `${input.name || `arg${index}`}: ${codegenInputType(options, input.type)}`).join(', ') + + ', ' ) } -export function codegenOutputTypes(outputs: AbiOutputParameter[]): string { - if (outputs.length === 1) { - return codegenOutputType(outputs[0].type) +export function codegenOutputTypes(options: GenerateTypeOptions, outputs: Array): string { + if (!options.returnResultObject && outputs.length === 1) { + return codegenOutputType(options, outputs[0].type) } else { - return `{ - ${outputs.map((t) => t.name && `${t.name}: ${codegenOutputType(t.type)}, `).join('')} - ${outputs.map((t, i) => `${i}: ${codegenOutputType(t.type)}`).join(', ')} - }` + return codegenOutputComplexType(outputs, options) } } -export function codegenInputType(evmType: EvmType): string { +export function codegenInputType(options: GenerateTypeOptions, evmType: EvmType): string { switch (evmType.type) { case 'integer': case 'uinteger': @@ -31,19 +37,22 @@ export function codegenInputType(evmType: EvmType): string { case 'dynamic-bytes': return 'string | number[]' case 'array': - return `(${codegenInputType(evmType.itemType)})[]` + return codegenArrayOrTupleType(codegenInputType(options, evmType.itemType), evmType.size) case 'boolean': return 'boolean' case 'string': return 'string' case 'tuple': - return codegenTupleType(evmType, codegenInputType) + if (evmType.structName && options.useStructs) { + return evmType.structName.toString() + STRUCT_INPUT_POSTFIX + } + return codegenInputComplexType(evmType.components, { ...options, useStructs: true }) case 'unknown': return 'any' } } -export function codegenOutputType(evmType: EvmOutputType): string { +export function codegenOutputType(options: GenerateTypeOptions, evmType: EvmOutputType): string { switch (evmType.type) { case 'integer': return 'string' @@ -57,18 +66,88 @@ export function codegenOutputType(evmType: EvmOutputType): string { case 'dynamic-bytes': return 'string' case 'array': - return `(${codegenOutputType(evmType.itemType)})[]` + return codegenArrayOrTupleType(codegenOutputType(options, evmType.itemType), evmType.size) case 'boolean': return 'boolean' case 'string': return 'string' case 'tuple': - return codegenTupleType(evmType, codegenOutputType) + if (evmType.structName && options.useStructs) { + return evmType.structName.toString() + STRUCT_OUTPUT_POSTFIX + } + return codegenOutputComplexType(evmType.components, { ...options, useStructs: true }) case 'unknown': return 'any' } } -export function codegenTupleType(tuple: TupleType, generator: (evmType: EvmType) => string) { - return '[' + tuple.components.map((component) => generator(component.type)).join(', ') + ']' +export function codegenArrayOrTupleType(item: string, length?: number) { + if (length !== undefined && length < 6) { + return `[${Array(length).fill(item).join(', ')}]` + } else { + return `${item}[]` + } +} + +export function codegenObjectTypeLiteral(tuple: TupleType, generator: (evmType: EvmType) => string) { + return '{' + tuple.components.map((component) => `${component.name}: ${generator(component.type)}`).join(', ') + '}' +} + +/** + * Always return an array type; if there are named outputs, merge them to that type + * this generates slightly better typings fixing: https://github.com/ethereum-ts/TypeChain/issues/232 + **/ +export function codegenOutputComplexType(components: AbiOutputParameter[], options: GenerateTypeOptions) { + const existingOutputComponents = compact([ + codegenOutputComplexTypeAsArray(components, options), + codegenOutputComplexTypesAsObject(components, options), + ]) + return existingOutputComponents.join(' & ') +} + +export function codegenInputComplexType(components: AbiParameter[], options: GenerateTypeOptions) { + const existingOutputComponents = compact([ + codegenInputComplexTypeAsArray(components, options), + codegenInputComplexTypesAsObject(components, options), + ]) + return existingOutputComponents.join(' | ') +} +export function codegenOutputComplexTypeAsArray( + components: AbiOutputParameter[], + options: GenerateTypeOptions, +): string { + return `[${components.map((t) => codegenOutputType(options, t.type)).join(', ')}]` +} + +export function codegenOutputComplexTypesAsObject( + components: AbiOutputParameter[], + options: GenerateTypeOptions, +): string | undefined { + let namedElementsCode + const namedElements = components.filter((e) => !!e.name) + if (namedElements.length > 0) { + namedElementsCode = + '{' + namedElements.map((t) => `${t.name}: ${codegenOutputType(options, t.type)}`).join(', ') + ' }' + + } + + return namedElementsCode +} + +export function codegenInputComplexTypeAsArray(components: AbiParameter[], options: GenerateTypeOptions): string { + return `[${components.map((t) => codegenInputType(options, t.type)).join(', ')}]` +} + +export function codegenInputComplexTypesAsObject( + components: AbiParameter[], + options: GenerateTypeOptions, +): string | undefined { + let namedElementsCode + const namedElements = components.filter((e) => !!e.name) + if (namedElements.length > 0) { + namedElementsCode = + '{' + namedElements.map((t) => `${t.name}: ${codegenInputType(options, t.type)}`).join(', ') + ' }' + } + + return namedElementsCode } diff --git a/packages/target-web3-v1/src/common.ts b/packages/target-web3-v1/src/common.ts new file mode 100644 index 000000000..ee731d5b0 --- /dev/null +++ b/packages/target-web3-v1/src/common.ts @@ -0,0 +1,2 @@ +export const STRUCT_INPUT_POSTFIX = 'Struct' +export const STRUCT_OUTPUT_POSTFIX = 'StructOutput' diff --git a/packages/target-web3-v1/src/index.ts b/packages/target-web3-v1/src/index.ts index 28795ecc2..1183f27e5 100644 --- a/packages/target-web3-v1/src/index.ts +++ b/packages/target-web3-v1/src/index.ts @@ -47,7 +47,7 @@ export default class Web3V1 extends TypeChainTarget { return { path: join(this.outDirAbs, ...contract.path, `${contract.name}.ts`), - contents: codegen(contract), + contents: codegen(contract, this.cfg.flags), } } From a2e307c6e4460e43062bb1b0d705cdf2347619c1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kaczor Date: Mon, 23 Jan 2023 12:36:33 +0100 Subject: [PATCH 2/2] Fix formatting, update saved files --- .../types/v0.6.4/DataTypesInput.ts | 28 +++++++++++++++---- .../types/v0.6.4/DataTypesPure.ts | 5 +++- .../types/v0.6.4/DataTypesView.ts | 5 +++- .../types/v0.6.4/Events.ts | 8 +++--- .../types/v0.8.9/Issue552_Reproduction.ts | 12 ++++++-- .../v0.8.9/KingOfTheHill/KingOfTheHill.ts | 7 ++--- .../target-web3-v1/src/codegen/functions.ts | 5 +--- .../target-web3-v1/src/codegen/structs.ts | 6 ++-- packages/target-web3-v1/src/codegen/types.ts | 1 - 9 files changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts index c14d7edcb..95dc7bd45 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesInput.ts @@ -26,13 +26,17 @@ export declare namespace StructsLib1 { | [number | string | BN, number | string | BN] | { a: number | string | BN; b: number | string | BN }; - export type InfoStructOutput = [string, string] & { a: string; b: string }; + export type InfoStructOutputArray = [string, string]; + export type InfoStructOutputStruct = { a: string; b: string }; + export type InfoStructOutput = InfoStructOutputArray & InfoStructOutputStruct; } export declare namespace StructsLib2 { export type InfoStruct = [string, string] | { a: string; b: string }; - export type InfoStructOutput = [string, string] & { a: string; b: string }; + export type InfoStructOutputArray = [string, string]; + export type InfoStructOutputStruct = { a: string; b: string }; + export type InfoStructOutput = InfoStructOutputArray & InfoStructOutputStruct; } export declare namespace DataTypesInput { @@ -40,25 +44,37 @@ export declare namespace DataTypesInput { | [number | string | BN, number | string | BN] | { uint256_0: number | string | BN; uint256_1: number | string | BN }; - export type Struct1StructOutput = [string, string] & { + export type Struct1StructOutputArray = [string, string]; + export type Struct1StructOutputStruct = { uint256_0: string; uint256_1: string; }; + export type Struct1StructOutput = Struct1StructOutputArray & + Struct1StructOutputStruct; export type Struct2Struct = | [number | string | BN, DataTypesInput.Struct1Struct] | { input1: number | string | BN; input2: DataTypesInput.Struct1Struct }; - export type Struct2StructOutput = [ + export type Struct2StructOutputArray = [ string, DataTypesInput.Struct1StructOutput - ] & { input1: string; input2: DataTypesInput.Struct1StructOutput }; + ]; + export type Struct2StructOutputStruct = { + input1: string; + input2: DataTypesInput.Struct1StructOutput; + }; + export type Struct2StructOutput = Struct2StructOutputArray & + Struct2StructOutputStruct; export type Struct3Struct = | [number | string | BN[]] | { input1: number | string | BN[] }; - export type Struct3StructOutput = [string[]] & { input1: string[] }; + export type Struct3StructOutputArray = [string[]]; + export type Struct3StructOutputStruct = { input1: string[] }; + export type Struct3StructOutput = Struct3StructOutputArray & + Struct3StructOutputStruct; } export interface DataTypesInput extends BaseContract { diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts index 06c2c4dfb..16837d187 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesPure.ts @@ -26,10 +26,13 @@ export declare namespace DataTypesPure { | [number | string | BN, number | string | BN] | { uint256_0: number | string | BN; uint256_1: number | string | BN }; - export type Struct1StructOutput = [string, string] & { + export type Struct1StructOutputArray = [string, string]; + export type Struct1StructOutputStruct = { uint256_0: string; uint256_1: string; }; + export type Struct1StructOutput = Struct1StructOutputArray & + Struct1StructOutputStruct; } export interface DataTypesPure extends BaseContract { diff --git a/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts b/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts index 1e980e8a7..2b7d781d9 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/DataTypesView.ts @@ -26,10 +26,13 @@ export declare namespace DataTypesView { | [number | string | BN, number | string | BN] | { uint256_0: number | string | BN; uint256_1: number | string | BN }; - export type Struct1StructOutput = [string, string] & { + export type Struct1StructOutputArray = [string, string]; + export type Struct1StructOutputStruct = { uint256_0: string; uint256_1: string; }; + export type Struct1StructOutput = Struct1StructOutputArray & + Struct1StructOutputStruct; } export interface DataTypesView extends BaseContract { diff --git a/packages/target-web3-v1-test/types/v0.6.4/Events.ts b/packages/target-web3-v1-test/types/v0.6.4/Events.ts index 5d4cee71d..69222a106 100644 --- a/packages/target-web3-v1-test/types/v0.6.4/Events.ts +++ b/packages/target-web3-v1-test/types/v0.6.4/Events.ts @@ -26,10 +26,10 @@ export declare namespace Events { | [number | string | BN, string] | { index: number | string | BN; name: string }; - export type EventDataStructOutput = [string, string] & { - index: string; - name: string; - }; + export type EventDataStructOutputArray = [string, string]; + export type EventDataStructOutputStruct = { index: string; name: string }; + export type EventDataStructOutput = EventDataStructOutputArray & + EventDataStructOutputStruct; } export type AnonEvent1 = ContractEventLog<{ diff --git a/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts b/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts index 3fafc05ad..6be44d76e 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/Issue552_Reproduction.ts @@ -26,10 +26,13 @@ export declare namespace Issue552_Observer { | [number | string | BN, number | string | BN] | { val: number | string | BN; blockTimestamp: number | string | BN }; - export type ObservationStructOutput = [string, string] & { + export type ObservationStructOutputArray = [string, string]; + export type ObservationStructOutputStruct = { val: string; blockTimestamp: string; }; + export type ObservationStructOutput = ObservationStructOutputArray & + ObservationStructOutputStruct; } export declare namespace Issue552_Reproduction { @@ -40,13 +43,16 @@ export declare namespace Issue552_Reproduction { index: number | string | BN; }; - export type ObservationParamsStructOutput = [ + export type ObservationParamsStructOutputArray = [ Issue552_Observer.ObservationStructOutput[], string - ] & { + ]; + export type ObservationParamsStructOutputStruct = { observations: Issue552_Observer.ObservationStructOutput[]; index: string; }; + export type ObservationParamsStructOutput = + ObservationParamsStructOutputArray & ObservationParamsStructOutputStruct; } export interface Issue552_Reproduction extends BaseContract { diff --git a/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts b/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts index 787f262bd..62a8a9b1e 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts @@ -26,10 +26,9 @@ export declare namespace KingOfTheHill { | [string, number | string | BN] | { bidder: string; value: number | string | BN }; - export type BidStructOutput = [string, string] & { - bidder: string; - value: string; - }; + export type BidStructOutputArray = [string, string]; + export type BidStructOutputStruct = { bidder: string; value: string }; + export type BidStructOutput = BidStructOutputArray & BidStructOutputStruct; } export type HighestBidIncreased = ContractEventLog<{ diff --git a/packages/target-web3-v1/src/codegen/functions.ts b/packages/target-web3-v1/src/codegen/functions.ts index 6f16f3f5c..dedb549fc 100644 --- a/packages/target-web3-v1/src/codegen/functions.ts +++ b/packages/target-web3-v1/src/codegen/functions.ts @@ -36,10 +36,7 @@ function codegenForSingleFunction( ${generateFunctionDocumentation(fn.documentation)} ${overloadedName ?? fn.name}(${codegenInputTypes({ useStructs: true }, fn.inputs)}): ${getTransactionObject( fn, - )}<${codegenOutputTypes( - { returnResultObject: !!options.returnResultObject, useStructs: true}, - fn.outputs, - )}>; + )}<${codegenOutputTypes({ returnResultObject: !!options.returnResultObject, useStructs: true }, fn.outputs)}>; ` } diff --git a/packages/target-web3-v1/src/codegen/structs.ts b/packages/target-web3-v1/src/codegen/structs.ts index 0b1426ad3..07520f345 100644 --- a/packages/target-web3-v1/src/codegen/structs.ts +++ b/packages/target-web3-v1/src/codegen/structs.ts @@ -29,12 +29,12 @@ function generateExports(struct: StructWithName): string { const inputName = `${identifier}${STRUCT_INPUT_POSTFIX}` const outputName = `${identifier}${STRUCT_OUTPUT_POSTFIX}` - const outputNameArray = `${outputName}Array`; - const outputNameObject = `${outputName}Struct`; + const outputNameArray = `${outputName}Array` + const outputNameObject = `${outputName}Struct` const inputType = codegenInputType({ useStructs: false }, struct) const outputType = codegenOutputType({ useStructs: false }, struct) - const [outputTypeArray,outputTypeObject] = outputType.split('&'); + const [outputTypeArray, outputTypeObject] = outputType.split('&') return ` export type ${inputName} = ${inputType} diff --git a/packages/target-web3-v1/src/codegen/types.ts b/packages/target-web3-v1/src/codegen/types.ts index 075498a92..4e108ce44 100644 --- a/packages/target-web3-v1/src/codegen/types.ts +++ b/packages/target-web3-v1/src/codegen/types.ts @@ -128,7 +128,6 @@ export function codegenOutputComplexTypesAsObject( if (namedElements.length > 0) { namedElementsCode = '{' + namedElements.map((t) => `${t.name}: ${codegenOutputType(options, t.type)}`).join(', ') + ' }' - } return namedElementsCode