From 2e8b2554df7a88c9cbe97643329a3c9189283dc3 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Tue, 11 Feb 2020 14:22:41 -0500 Subject: [PATCH] feat(@embark/contracts): add proxyFor property for contracts Requested here https://github.com/embarklabs/embark/issues/1689 Adds proxyFor to contracts that merges the ABI of the parent contract to the child (proxy) contract so that the proxy can use the methods of the parent but is deployed as itself --- packages/stack/contracts-manager/src/index.js | 111 +++++++++++------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/packages/stack/contracts-manager/src/index.js b/packages/stack/contracts-manager/src/index.js index f5b78850c..8ca81c761 100644 --- a/packages/stack/contracts-manager/src/index.js +++ b/packages/stack/contracts-manager/src/index.js @@ -338,7 +338,7 @@ export default class ContractsManager { if (contract.address && typeof contract.address === 'function') { contract.addressHandler = contract.address; - delete contract.addres; + delete contract.address; } else if (contract.address && typeof contract.address === 'string') { contract.deployedAddress = contract.address; } @@ -385,7 +385,7 @@ export default class ContractsManager { } callback(); }, - /*eslint complexity: ["error", 11]*/ + // eslint-disable-next-line complexity function dealWithSpecialConfigs(callback) { let className, contract, parentContractName, parentContract; let dictionary = Object.keys(self.contracts); @@ -393,56 +393,55 @@ export default class ContractsManager { for (className in self.contracts) { contract = self.contracts[className]; - if (contract.instanceOf === undefined) { + if (!contract.instanceOf && !contract.proxyFor) { continue; } - parentContractName = contract.instanceOf; - parentContract = self.contracts[parentContractName]; - - if (parentContract === className) { - self.logger.error(__("%s : instanceOf is set to itself", className)); - continue; - } - - if (parentContract === undefined) { - self.logger.error(__("{{className}}: couldn't find instanceOf contract {{parentContractName}}", { - className: className, - parentContractName: parentContractName - })); - let suggestion = proposeAlternative(parentContractName, dictionary, [className, parentContractName]); - if (suggestion) { - self.logger.warn(__('did you mean "%s"?', suggestion)); + if (contract.instanceOf) { + parentContractName = contract.instanceOf; + parentContract = self.contracts[parentContractName]; + if (!self._isParentContractDependencyCorrect(className, parentContract, 'instanceOf', dictionary)) { + continue; } - continue; + + // If the contract has no args and the parent has them, use the parent's args in its place + if (parentContract.args?.length > 0 && contract.args?.length === 0) { + contract.args = parentContract.args; + } + + if (!contract.code) { + self.logger.error(__("{{className}} has code associated to it but it's configured as an instanceOf {{parentContractName}}", { + className, + parentContractName + })); + } + + contract.path = parentContract.path; + contract.originalFilename = parentContract.originalFilename; + contract.filename = parentContract.filename; + contract.code = parentContract.code; + contract.runtimeBytecode = parentContract.runtimeBytecode; + contract.realRuntimeBytecode = (parentContract.realRuntimeBytecode || parentContract.runtimeBytecode); + contract.gasEstimates = parentContract.gasEstimates; + contract.functionHashes = parentContract.functionHashes; + contract.abiDefinition = parentContract.abiDefinition; + contract.linkReferences = parentContract.linkReferences; + + contract.gas = contract.gas || parentContract.gas; + contract.gasPrice = contract.gasPrice || parentContract.gasPrice; + contract.type = 'instance'; } - if (parentContract.args && parentContract.args.length > 0 && ((contract.args && contract.args.length === 0) || contract.args === undefined)) { - contract.args = parentContract.args; + if (contract.proxyFor) { + parentContractName = contract.proxyFor; + parentContract = self.contracts[parentContractName]; + if (!self._isParentContractDependencyCorrect(className, parentContract, 'proxyFor', dictionary)) { + continue; + } + + // Merge ABI of contract and proxy so that the contract shares both ABIs, but remove the constructor + contract.abiDefinition = contract.abiDefinition.concat(parentContract.abiDefinition.filter(def => def.type !== 'constructor')); } - - if (contract.code !== undefined) { - self.logger.error(__("{{className}} has code associated to it but it's configured as an instanceOf {{parentContractName}}", { - className: className, - parentContractName: parentContractName - })); - } - - contract.path = parentContract.path; - contract.originalFilename = parentContract.originalFilename; - contract.filename = parentContract.filename; - contract.code = parentContract.code; - contract.runtimeBytecode = parentContract.runtimeBytecode; - contract.realRuntimeBytecode = (parentContract.realRuntimeBytecode || parentContract.runtimeBytecode); - contract.gasEstimates = parentContract.gasEstimates; - contract.functionHashes = parentContract.functionHashes; - contract.abiDefinition = parentContract.abiDefinition; - contract.linkReferences = parentContract.linkReferences; - - contract.gas = contract.gas || parentContract.gas; - contract.gasPrice = contract.gasPrice || parentContract.gasPrice; - contract.type = 'instance'; - } callback(); }, @@ -552,6 +551,28 @@ export default class ContractsManager { }); } + _isParentContractDependencyCorrect(className, parentContract, typeOfInheritance, dictionary) { + const parentContractName = parentContract.className; + if (parentContract === className) { + this.logger.error(__("{{className}} : {{typeOfInheritance}} is set to itself", {className, typeOfInheritance})); + return false; + } + + if (parentContract === undefined) { + this.logger.error(__("{{className}}: couldn't find {{typeOfInheritance}} contract {{parentContractName}}", { + className, + parentContractName, + typeOfInheritance + })); + let suggestion = proposeAlternative(parentContractName, dictionary, [className, parentContractName]); + if (suggestion) { + this.logger.warn(__('did you mean "%s"?', suggestion)); + } + return false; + } + return true; + } + _contractsForApi() { const contracts = this.formatContracts(); contracts.forEach((contract) => {