From b5aac4164c72c90bbea051fe13bf5e60e614da5e Mon Sep 17 00:00:00 2001 From: Krzysztof Kaczor Date: Mon, 27 Apr 2020 22:43:37 +0200 Subject: [PATCH] [ethers-v4] Support for overloaded functions --- .../test/Overload.test.ts | 27 ++++ .../types/Overloads.d.ts | 14 +- .../target-ethers-v4/src/codegen/functions.ts | 48 +++++++ .../src/{generation.ts => codegen/index.ts} | 132 +----------------- .../target-ethers-v4/src/codegen/types.ts | 80 +++++++++++ packages/target-ethers-v4/src/index.ts | 2 +- .../target-ethers-v4/test/generation.test.ts | 2 +- 7 files changed, 173 insertions(+), 132 deletions(-) create mode 100644 packages/target-ethers-v4-test/test/Overload.test.ts create mode 100644 packages/target-ethers-v4/src/codegen/functions.ts rename packages/target-ethers-v4/src/{generation.ts => codegen/index.ts} (69%) create mode 100644 packages/target-ethers-v4/src/codegen/types.ts diff --git a/packages/target-ethers-v4-test/test/Overload.test.ts b/packages/target-ethers-v4-test/test/Overload.test.ts new file mode 100644 index 000000000..505138867 --- /dev/null +++ b/packages/target-ethers-v4-test/test/Overload.test.ts @@ -0,0 +1,27 @@ +import { typedAssert } from 'test-utils' + +import { createNewBlockchain, deployContract } from './common' +import { Overloads } from '../types/Overloads' +import { BigNumber } from 'ethers/utils' + +describe('Overloads', () => { + let contract: Overloads + let ganache: any + beforeEach(async () => { + const { ganache: _ganache, signer } = await createNewBlockchain() + ganache = _ganache + contract = await deployContract(signer, 'Overloads') + }) + + afterEach(() => ganache.close()) + + it('works with 1st overload', async () => { + const result = await contract.functions['overload1(int256)'](1) + typedAssert(result, new BigNumber(1)) + }) + + it('works with 2n overload', async () => { + const result = await contract.functions['overload1(uint256,uint256)'](1, 2) + typedAssert(result, new BigNumber(3)) + }) +}) diff --git a/packages/target-ethers-v4-test/types/Overloads.d.ts b/packages/target-ethers-v4-test/types/Overloads.d.ts index b534461c3..73ed4f205 100644 --- a/packages/target-ethers-v4-test/types/Overloads.d.ts +++ b/packages/target-ethers-v4-test/types/Overloads.d.ts @@ -34,10 +34,20 @@ export class Overloads extends Contract { interface: OverloadsInterface; functions: { - overload1(input1: BigNumberish): Promise; + "overload1(int256)"(input1: BigNumberish): Promise; + + "overload1(uint256,uint256)"( + input1: BigNumberish, + input2: BigNumberish + ): Promise; }; - overload1(input1: BigNumberish): Promise; + "overload1(int256)"(input1: BigNumberish): Promise; + + "overload1(uint256,uint256)"( + input1: BigNumberish, + input2: BigNumberish + ): Promise; filters: {}; diff --git a/packages/target-ethers-v4/src/codegen/functions.ts b/packages/target-ethers-v4/src/codegen/functions.ts new file mode 100644 index 000000000..83ca54360 --- /dev/null +++ b/packages/target-ethers-v4/src/codegen/functions.ts @@ -0,0 +1,48 @@ +import { FunctionDeclaration, isConstant, isConstantFn, FunctionDocumentation, getSignatureForFn } from 'typechain' +import { generateInputTypes, generateOutputTypes } from './types' + +export function codegenFunctions(fns: FunctionDeclaration[]): string { + if (fns.length === 1) { + return generateFunction(fns[0]) + } + + return codegenForOverloadedFunctions(fns) +} + +export function codegenForOverloadedFunctions(fns: FunctionDeclaration[]): string { + return fns.map((fn) => generateFunction(fn, `"${getSignatureForFn(fn)}"`)).join('\n') +} + +function generateFunction(fn: FunctionDeclaration, overloadedName?: string): string { + return ` + ${generateFunctionDocumentation(fn.documentation)} + ${overloadedName ?? fn.name}(${generateInputTypes(fn.inputs)}${ + !isConstant(fn) && !isConstantFn(fn) ? 'overrides?: TransactionOverrides' : '' + }): Promise<${ + fn.stateMutability === 'pure' || fn.stateMutability === 'view' + ? generateOutputTypes(fn.outputs) + : 'ContractTransaction' + }>; +` +} + +function generateFunctionDocumentation(doc?: FunctionDocumentation): string { + if (!doc) return '' + + let docString = '/**' + if (doc.details) docString += `\n * ${doc.details}` + if (doc.notice) docString += `\n * ${doc.notice}` + + const params = Object.entries(doc.params || {}) + if (params.length) { + params.forEach(([key, value]) => { + docString += `\n * @param ${key} ${value}` + }) + } + + if (doc.return) docString += `\n * @returns ${doc.return}` + + docString += '\n */' + + return docString +} diff --git a/packages/target-ethers-v4/src/generation.ts b/packages/target-ethers-v4/src/codegen/index.ts similarity index 69% rename from packages/target-ethers-v4/src/generation.ts rename to packages/target-ethers-v4/src/codegen/index.ts index 90cba174a..160326bb4 100644 --- a/packages/target-ethers-v4/src/generation.ts +++ b/packages/target-ethers-v4/src/codegen/index.ts @@ -1,19 +1,14 @@ import { values } from 'lodash' import { - AbiOutputParameter, AbiParameter, BytecodeWithLinkReferences, Contract, - FunctionDocumentation, EventArgDeclaration, EventDeclaration, - EvmOutputType, - EvmType, FunctionDeclaration, - isConstant, - isConstantFn, - TupleType, } from 'typechain' +import { generateInputType, generateInputTypes } from './types' +import { codegenFunctions } from './functions' export function codegenContractTypings(contract: Contract) { const template = ` @@ -52,16 +47,10 @@ export function codegenContractTypings(contract: Contract) { interface: ${contract.name}Interface; functions: { - ${values(contract.functions) - .map((v) => v[0]) - .map(generateFunction) - .join('\n')} + ${values(contract.functions).map(codegenFunctions).join('\n')} }; - ${values(contract.functions) - .map((v) => v[0]) - .map(generateFunction) - .join('\n')} + ${values(contract.functions).map(codegenFunctions).join('\n')} filters: { ${values(contract.events) @@ -199,19 +188,6 @@ function generateLibraryAddressesInterface(contract: Contract, bytecode: Bytecod };` } -function generateFunction(fn: FunctionDeclaration): string { - return ` - ${generateFunctionDocumentation(fn.documentation)} - ${fn.name}(${generateInputTypes(fn.inputs)}${ - !isConstant(fn) && !isConstantFn(fn) ? 'overrides?: TransactionOverrides' : '' - }): Promise<${ - fn.stateMutability === 'pure' || fn.stateMutability === 'view' - ? generateOutputTypes(fn.outputs) - : 'ContractTransaction' - }>; -` -} - function generateEstimateFunction(fn: FunctionDeclaration): string { return ` ${fn.name}(${generateInputTypes(fn.inputs)}): Promise; @@ -226,47 +202,6 @@ function generateInterfaceFunctionDescription(fn: FunctionDeclaration): string { ` } -function generateFunctionDocumentation(doc?: FunctionDocumentation): string { - if (!doc) return '' - - let docString = '/**' - if (doc.details) docString += `\n * ${doc.details}` - if (doc.notice) docString += `\n * ${doc.notice}` - - const params = Object.entries(doc.params || {}) - if (params.length) { - params.forEach(([key, value]) => { - docString += `\n * @param ${key} ${value}` - }) - } - - if (doc.return) docString += `\n * @returns ${doc.return}` - - docString += '\n */' - - return docString -} - -function generateInputTypes(input: Array): string { - if (input.length === 0) { - return '' - } - return ( - input.map((input, index) => `${input.name || `arg${index}`}: ${generateInputType(input.type)}`).join(', ') + ', ' - ) -} - -function generateOutputTypes(outputs: Array): string { - if (outputs.length === 1) { - return generateOutputType(outputs[0].type) - } else { - return `{ - ${outputs.map((t) => t.name && `${t.name}: ${generateOutputType(t.type)}, `).join('')} - ${outputs.map((t, i) => `${i}: ${generateOutputType(t.type)}`).join(', ')} - }` - } -} - function generateParamArrayTypes(params: Array): string { return `[${params.map((param) => generateInputType(param.type)).join(', ')}]` } @@ -313,62 +248,3 @@ function generateEventTypes(eventArgs: EventArgDeclaration[]) { function generateEventArgType(eventArg: EventArgDeclaration): string { return eventArg.isIndexed ? `${generateInputType(eventArg.type)} | null` : 'null' } - -// https://docs.ethers.io/ethers.js/html/api-contract.html#types -function generateInputType(evmType: EvmType): string { - switch (evmType.type) { - case 'integer': - return 'BigNumberish' - case 'uinteger': - return 'BigNumberish' - case 'address': - return 'string' - case 'bytes': - case 'dynamic-bytes': - return 'Arrayish' - case 'array': - return `(${generateInputType(evmType.itemType)})[]` - case 'boolean': - return 'boolean' - case 'string': - return 'string' - case 'tuple': - return generateTupleType(evmType, generateInputType) - } -} - -function generateOutputType(evmType: EvmOutputType): string { - switch (evmType.type) { - case 'integer': - case 'uinteger': - return evmType.bits <= 48 ? 'number' : 'BigNumber' - case 'address': - return 'string' - case 'void': - return 'void' - case 'bytes': - case 'dynamic-bytes': - return 'string' - case 'array': - return `(${generateOutputType(evmType.itemType)})[]` - case 'boolean': - return 'boolean' - case 'string': - return 'string' - case 'tuple': - return generateOutputTupleType(evmType) - } -} - -function generateTupleType(tuple: TupleType, generator: (evmType: EvmType) => string) { - return '{' + tuple.components.map((component) => `${component.name}: ${generator(component.type)}`).join(',') + '}' -} - -function generateOutputTupleType(tuple: TupleType) { - return ( - '{' + - tuple.components.map((component) => `${component.name}: ${generateOutputType(component.type)} ,`).join('\n') + - tuple.components.map((component, index) => `${index}: ${generateOutputType(component.type)}`).join(', ') + - '}' - ) -} diff --git a/packages/target-ethers-v4/src/codegen/types.ts b/packages/target-ethers-v4/src/codegen/types.ts new file mode 100644 index 000000000..574181f94 --- /dev/null +++ b/packages/target-ethers-v4/src/codegen/types.ts @@ -0,0 +1,80 @@ +import { EvmType, EvmOutputType, TupleType, AbiParameter, AbiOutputParameter } from 'typechain' + +export function generateInputTypes(input: Array): string { + if (input.length === 0) { + return '' + } + return ( + input.map((input, index) => `${input.name || `arg${index}`}: ${generateInputType(input.type)}`).join(', ') + ', ' + ) +} + +export function generateOutputTypes(outputs: Array): string { + if (outputs.length === 1) { + return generateOutputType(outputs[0].type) + } else { + return `{ + ${outputs.map((t) => t.name && `${t.name}: ${generateOutputType(t.type)}, `).join('')} + ${outputs.map((t, i) => `${i}: ${generateOutputType(t.type)}`).join(', ')} + }` + } +} + +// https://docs.ethers.io/ethers.js/html/api-contract.html#types +export function generateInputType(evmType: EvmType): string { + switch (evmType.type) { + case 'integer': + return 'BigNumberish' + case 'uinteger': + return 'BigNumberish' + case 'address': + return 'string' + case 'bytes': + case 'dynamic-bytes': + return 'Arrayish' + case 'array': + return `(${generateInputType(evmType.itemType)})[]` + case 'boolean': + return 'boolean' + case 'string': + return 'string' + case 'tuple': + return generateTupleType(evmType, generateInputType) + } +} + +export function generateOutputType(evmType: EvmOutputType): string { + switch (evmType.type) { + case 'integer': + case 'uinteger': + return evmType.bits <= 48 ? 'number' : 'BigNumber' + case 'address': + return 'string' + case 'void': + return 'void' + case 'bytes': + case 'dynamic-bytes': + return 'string' + case 'array': + return `(${generateOutputType(evmType.itemType)})[]` + case 'boolean': + return 'boolean' + case 'string': + return 'string' + case 'tuple': + return generateOutputTupleType(evmType) + } +} + +export function generateTupleType(tuple: TupleType, generator: (evmType: EvmType) => string) { + return '{' + tuple.components.map((component) => `${component.name}: ${generator(component.type)}`).join(',') + '}' +} + +export function generateOutputTupleType(tuple: TupleType) { + return ( + '{' + + tuple.components.map((component) => `${component.name}: ${generateOutputType(component.type)} ,`).join('\n') + + tuple.components.map((component, index) => `${index}: ${generateOutputType(component.type)}`).join(', ') + + '}' + ) +} diff --git a/packages/target-ethers-v4/src/index.ts b/packages/target-ethers-v4/src/index.ts index a9c74199b..2c695a4b8 100644 --- a/packages/target-ethers-v4/src/index.ts +++ b/packages/target-ethers-v4/src/index.ts @@ -13,7 +13,7 @@ import { parse, } from 'typechain' -import { codegenAbstractContractFactory, codegenContractFactory, codegenContractTypings } from './generation' +import { codegenAbstractContractFactory, codegenContractFactory, codegenContractTypings } from './codegen' export interface IEthersCfg { outDir?: string diff --git a/packages/target-ethers-v4/test/generation.test.ts b/packages/target-ethers-v4/test/generation.test.ts index 791fc98bb..91749fae0 100644 --- a/packages/target-ethers-v4/test/generation.test.ts +++ b/packages/target-ethers-v4/test/generation.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { Contract } from 'typechain' -import { codegenContractFactory } from '../src/generation' +import { codegenContractFactory } from '../src/codegen' describe('Ethers generation edge cases', () => { const emptyContract: Contract = {