From eecf09f51564df4f63139f26e65efa1102a9958d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 9 Apr 2018 11:55:23 -0700 Subject: Added a detailed description of `renameOverloadedMethods` (special thanks to @fabioberger). Updated Javascript styles in the Abi-Gen and Utils packages, around support for function overloading. --- packages/utils/src/abi_utils.ts | 85 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 44 deletions(-) (limited to 'packages/utils/src') diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 843b8589b..c4533d42e 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -12,63 +12,60 @@ export const abiUtils = { } return param.type; }, - getFunctionSignature(abi: MethodAbi): string { - const functionName = abi.name; - const parameterTypeList = abi.inputs.map((param: DataItem) => this.parseFunctionParam(param)); + getFunctionSignature(methodAbi: MethodAbi): string { + const functionName = methodAbi.name; + const parameterTypeList = _.map(methodAbi.inputs, (param: DataItem) => this.parseFunctionParam(param)); const functionSignature = `${functionName}(${parameterTypeList})`; return functionSignature; }, + /** + * Solidity supports function overloading whereas TypeScript does not. + * See: https://solidity.readthedocs.io/en/v0.4.21/contracts.html?highlight=overload#function-overloading + * In order to support overloaded functions, we suffix overloaded function names with an index. + * This index should be deterministic, regardless of function ordering within the smart contract. To do so, + * we assign indexes based on the alphabetical order of function signatures. + * + * E.g + * ['f(uint)', 'f(uint,byte32)'] + * Should always be renamed to: + * ['f1(uint)', 'f2(uint,byte32)'] + * Regardless of the order in which these these overloaded functions are declared within the contract ABI. + */ renameOverloadedMethods(inputContractAbi: ContractAbi): ContractAbi { const contractAbi = _.cloneDeep(inputContractAbi); const methodAbis = contractAbi.filter((abi: AbiDefinition) => abi.type === AbiType.Function) as MethodAbi[]; - const methodAbisByOriginalIndex = _.transform( - methodAbis, - (result: Array<{ index: number; methodAbi: MethodAbi }>, methodAbi, i: number) => { - result.push({ index: i, methodAbi }); - }, - [], - ); // Sort method Abis into alphabetical order, by function signature - const methodAbisByOriginalIndexOrdered = _.sortBy(methodAbisByOriginalIndex, [ - (entry: { index: number; methodAbi: MethodAbi }) => { - const functionSignature = this.getFunctionSignature(entry.methodAbi); + const methodAbisOrdered = _.sortBy(methodAbis, [ + (methodAbi: MethodAbi) => { + const functionSignature = this.getFunctionSignature(methodAbi); return functionSignature; }, ]); // Group method Abis by name (overloaded methods will be grouped together, in alphabetical order) - const methodAbisByName = _.transform( - methodAbisByOriginalIndexOrdered, - (result: { [key: string]: Array<{ index: number; methodAbi: MethodAbi }> }, entry) => { - (result[entry.methodAbi.name] || (result[entry.methodAbi.name] = [])).push(entry); - }, - {}, - ); - // Rename overloaded methods to overloadedMethoName_1, overloadedMethoName_2, ... - const methodAbisRenamed = _.transform( - methodAbisByName, - (result: MethodAbi[], methodAbisWithSameName: Array<{ index: number; methodAbi: MethodAbi }>) => { - _.forEach(methodAbisWithSameName, (entry, i: number) => { - if (methodAbisWithSameName.length > 1) { - const overloadedMethodId = i + 1; - const sanitizedMethodName = `${entry.methodAbi.name}_${overloadedMethodId}`; - const indexOfExistingAbiWithSanitizedMethodNameIfExists = _.findIndex( - methodAbis, - methodAbi => methodAbi.name === sanitizedMethodName, + const methodAbisByName: { [key: string]: MethodAbi[] } = {}; + _.each(methodAbisOrdered, methodAbi => { + (methodAbisByName[methodAbi.name] || (methodAbisByName[methodAbi.name] = [])).push(methodAbi); + }); + // Rename overloaded methods to overloadedMethodName1, overloadedMethodName2, ... + _.each(methodAbisByName, methodAbisWithSameName => { + _.each(methodAbisWithSameName, (methodAbi, i: number) => { + if (methodAbisWithSameName.length > 1) { + const overloadedMethodId = i + 1; + const sanitizedMethodName = `${methodAbi.name}${overloadedMethodId}`; + const indexOfExistingAbiWithSanitizedMethodNameIfExists = _.findIndex( + methodAbis, + currentMethodAbi => currentMethodAbi.name === sanitizedMethodName, + ); + if (indexOfExistingAbiWithSanitizedMethodNameIfExists >= 0) { + const methodName = methodAbi.name; + throw new Error( + `Failed to rename overloaded method '${methodName}' to '${sanitizedMethodName}'. A method with this name already exists.`, ); - if (indexOfExistingAbiWithSanitizedMethodNameIfExists >= 0) { - const methodName = entry.methodAbi.name; - throw new Error( - `Failed to rename overloaded method '${methodName}' to '${sanitizedMethodName}'. A method with this name already exists.`, - ); - } - entry.methodAbi.name = sanitizedMethodName; } - // Add method to list of ABIs in its original position - result.splice(entry.index, 0, entry.methodAbi); - }); - }, - [...Array(methodAbis.length)], - ); + methodAbi.name = sanitizedMethodName; + } + }); + }); return contractAbi; }, }; -- cgit v1.2.3