diff --git a/docs/plugins.rst b/docs/plugins.rst index 99f8d0845..9162bb646 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -3,7 +3,7 @@ Extending functionality with plugins **To add a plugin to embark:** -1. Add the npm package to package.json +1. Add the npm package to package.json e.g ``npm install embark-babel --save`` 2. Then add the package to ``plugins:`` in embark.json e.g ``"plugins": { "embark-babel": {} }`` @@ -29,6 +29,7 @@ The ``embark`` object then provides an api to extend different functionality of * plugin to add standard contracts or a contract framework (``embark.registerContractConfiguration`` and ``embark.addContractFile``) * plugin to make some contracts available in all environments for use by other contracts or the dapp itself e.g a Token, a DAO, ENS, etc.. (``embark.registerContractConfiguration`` and ``embark.addContractFile``) * plugin to add a libraries such as react or boostrap (``embark.addFileToPipeline``) +* plugin to process contract's binary code before deployment (``embark.beforeDeploy``) * plugin to specify a particular web3 initialization for special provider uses (``embark.registerClientWeb3Provider``) * plugin to create a different contract wrapper (``embark.registerContractsGeneration``) * plugin to add new console commands (``embark.registerConsoleCommand``) @@ -137,6 +138,30 @@ This call is used to add a file to the pipeline so it's included with the dapp o embark.addFileToPipeline("./jquery.js", {skipPipeline: true}); } +**embark.registerBeforeDeploy(callback(options))** + +This call can be used to add handler to process contract code after it was generated, but immediately before it is going to be deployed. +It is useful to replace placeholders with dynamic values. + +options available: + * embarkDeploy - instance of Deploy class. Has following fields: web3, contractsManager, logger, env, chainConfig, gasLimit. + * pluginConfig - plugin configuration object from embark.json + * deploymentAccount - address of account which will be used to deploy this contract + * contract - contract object. + * callback - callback function that handler must call with result object as the only argument. Result object must have field contractCode with (un)modified code from contract.code + +expected return: ignored + +example: + +.. code:: javascript + + module.exports = function(embark) { + embark.registerBeforeDeploy(function(options) { + return options.contract.code.replace(/deaddeaddeaddeaddeaddeaddeaddeaddeaddead/ig, 'c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de'); + }); + } + **embark.registerClientWeb3Provider(callback(options))** @@ -165,7 +190,7 @@ example: By default Embark will use EmbarkJS to declare contracts in the dapp. You can override and use your own client side library. options available: - * contracts - Hash of objects containing all the deployed contracts. (key: contractName, value: contract object) + * contracts - Hash of objects containing all the deployed contracts. (key: contractName, value: contract object) * abiDefinition * code * deployedAddress @@ -210,18 +235,15 @@ expected return: ``string`` (output to print in console) or ``boolean`` (skip co expected doneCallback arguments: ``err`` and ``hash`` of compiled contracts - * Hash of objects containing the compiled contracts. (key: contractName, value: contract object) - + * Hash of objects containing the compiled contracts. (key: contractName, value: contract object) * code - contract bytecode (string) - * runtimeBytecode - contract runtimeBytecode (string) - * gasEstimates - gas estimates for constructor and methods (hash) - * e.g ``{"creation":[20131,38200],"external":{"get()":269,"set(uint256)":20163,"storedData()":224},"internal":{}}`` + * e.g ``{"creation":[20131,38200],"external":{"get()":269,"set(uint256)":20163,"storedData()":224},"internal":{}}`` * functionHashes - object with methods and their corresponding hash identifier (hash) - * e.g ``{"get()":"6d4ce63c","set(uint256)":"60fe47b1","storedData()":"2a1afcd9"}`` + * e.g ``{"get()":"6d4ce63c","set(uint256)":"60fe47b1","storedData()":"2a1afcd9"}`` * abiDefinition - contract abi (array of objects) - * e.g ``[{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}, etc...`` + * e.g ``[{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}, etc...`` below a possible implementation of a solcjs plugin: @@ -271,15 +293,12 @@ e.g ``embark.logger.info("hello")`` This call is used to listen and react to events that happen in Embark such as contract deployment * eventName - name of event to listen to - * available events: - * "contractsDeployed" - triggered when contracts have been deployed - * "file-add", "file-change", "file-remove", "file-event" - triggered on - a file change, args is (filetype, path) - * "abi", "abi-vanila", "abi-contracts-vanila" - triggered when contracts - have been deployed and returns the generated JS code - * "outputDone" - triggered when dapp is (re)generated - * "firstDeploymentDone" - triggered when the dapp is deployed and generated - for the first time + * available events: + * "contractsDeployed" - triggered when contracts have been deployed + * "file-add", "file-change", "file-remove", "file-event" - triggered on a file change, args is (filetype, path) + * "abi", "abi-vanila", "abi-contracts-vanila" - triggered when contracts have been deployed and returns the generated JS code + * "outputDone" - triggered when dapp is (re)generated + * "firstDeploymentDone" - triggered when the dapp is deployed and generated for the first time .. code:: javascript @@ -293,4 +312,3 @@ This call is used to listen and react to events that happen in Embark such as co } }); } - diff --git a/lib/contracts/deploy.js b/lib/contracts/deploy.js index 4638a49e5..769a3824e 100644 --- a/lib/contracts/deploy.js +++ b/lib/contracts/deploy.js @@ -13,6 +13,7 @@ class Deploy { this.logger = options.logger; this.env = options.env; this.chainConfig = options.chainConfig; + this.plugins = options.plugins; this.gasLimit = options.gasLimit; } @@ -182,6 +183,8 @@ class Deploy { } let contractCode = contract.code; + + // Applying linked contracts let contractsList = self.contractsManager.listContracts(); for (let contractObj of contractsList) { let filename = contractObj.filename; @@ -204,47 +207,88 @@ class Deploy { contractCode = contractCode.replace(new RegExp(toReplace, "g"), deployedAddress); } - let contractObject = new self.web3.eth.Contract(contract.abiDefinition); - let deployObject; + // saving code changes back to contract object + contract.code = contractCode; - try { - deployObject = contractObject.deploy({arguments: contractParams, data: "0x" + contractCode}); - } catch(e) { - if (e.indexOf('Invalid number of parameters for "undefined"') >= 0) { - return callback(new Error("attempted to deploy " + contractObject.className + " without specifying parameters")); - } else { - return callback(new Error(e)); - } - } + // selected in deploy_manager + let deploymentAccount = self.web3.eth.defaultAccount || accounts[0]; + let beforeDeployPlugins = self.plugins.getPluginsFor('beforeDeploy'); - self.logger.info("deploying " + contract.className.bold.cyan + " with ".green + contract.gas + " gas".green); + async.waterfall([ + (asyncCallback)=>{ + //self.logger.info("applying beforeDeploy plugins...", beforeDeployPlugins.length); + async.eachSeries(beforeDeployPlugins, (plugin, eachPluginCb)=>{ + self.logger.info("running beforeDeploy plugin " + plugin.name + " ."); - deployObject.send({ - from: accounts[0], - gas: contract.gas, - gasPrice: contract.gasPrice - }).on('receipt', function(receipt) { - if (err) { - self.logger.error("error deploying contract: " + contract.className.cyan); - let errMsg = err.toString(); - if (errMsg === 'Error: The contract code couldn\'t be stored, please check your gas amount.') { - errMsg = 'The contract code couldn\'t be stored, out of gas or constructor error'; + // calling each beforeDeploy handler declared by the plugin + async.eachSeries(plugin.beforeDeploy, (beforeDeployFn, eachCb)=>{ + beforeDeployFn({ + embarkDeploy: self, + pluginConfig: plugin.pluginConfig, + deploymentAccount: deploymentAccount, + contract: contract, + callback: + (function(resObj){ + contract.code = resObj.contractCode; + eachCb(); + }) + }); + }, ()=>{ + //self.logger.info('All beforeDeploy handlers of the plugin has processed.'); + eachPluginCb(); + }); + }, ()=>{ + //self.logger.info('All beforeDeploy plugins has been processed.'); + contractCode = contract.code; + asyncCallback(); + }); + }, + (asyncCallback)=>{ + let contractObject = new self.web3.eth.Contract(contract.abiDefinition); + let deployObject; + + try { + deployObject = contractObject.deploy({arguments: contractParams, data: "0x" + contractCode}); + } catch(e) { + if (e.indexOf('Invalid number of parameters for "undefined"') >= 0) { + return callback(new Error("attempted to deploy " + contractObject.className + " without specifying parameters")); + } else { + return callback(new Error(e)); + } } - self.logger.error(errMsg); - contract.error = errMsg; - self.logger.contractsState(self.contractsManager.contractsState()); - return callback(new Error(err)); - } else if (receipt.contractAddress !== undefined) { - self.logger.info(contract.className.bold.cyan + " deployed at ".green + receipt.contractAddress.bold.cyan); - contract.deployedAddress = receipt.contractAddress; - contract.transactionHash = receipt.transactionHash; - self.logger.contractsState(self.contractsManager.contractsState()); - return callback(null, receipt.contractAddress); + + self.logger.info("deploying " + contract.className.bold.cyan + " with ".green + contract.gas + " gas".green); + + deployObject.send({ + from: deploymentAccount, + gas: contract.gas, + gasPrice: contract.gasPrice + }).on('receipt', function(receipt) { + if (err) { + self.logger.error("error deploying contract: " + contract.className.cyan); + let errMsg = err.toString(); + if (errMsg === 'Error: The contract code couldn\'t be stored, please check your gas amount.') { + errMsg = 'The contract code couldn\'t be stored, out of gas or constructor error'; + } + self.logger.error(errMsg); + contract.error = errMsg; + self.logger.contractsState(self.contractsManager.contractsState()); + return callback(new Error(err)); + } else if (receipt.contractAddress !== undefined) { + self.logger.info(contract.className.bold.cyan + " deployed at ".green + receipt.contractAddress.bold.cyan); + contract.deployedAddress = receipt.contractAddress; + contract.transactionHash = receipt.transactionHash; + self.logger.contractsState(self.contractsManager.contractsState()); + return callback(null, receipt.contractAddress); + } + self.logger.contractsState(self.contractsManager.contractsState()); + }).on('error', function(error) { + return callback(new Error("error deploying =" + contract.className + "= due to error: " + error.message)); + }); } - self.logger.contractsState(self.contractsManager.contractsState()); - }).on('error', function(error) { - return callback(new Error("error deploying =" + contract.className + "= due to error: " + error.message)); - }); + ]); // end of async.waterfall + + }); } diff --git a/lib/contracts/deploy_manager.js b/lib/contracts/deploy_manager.js index 91c8b125f..adf00aec4 100644 --- a/lib/contracts/deploy_manager.js +++ b/lib/contracts/deploy_manager.js @@ -77,6 +77,7 @@ class DeployManager { logger: self.logger, chainConfig: self.chainConfig, env: self.config.env, + plugins: self.plugins, gasLimit: self.gasLimit }); diff --git a/lib/core/plugin.js b/lib/core/plugin.js index c1be1a9f4..581bbaf19 100644 --- a/lib/core/plugin.js +++ b/lib/core/plugin.js @@ -11,6 +11,7 @@ var Plugin = function(options) { this.pluginConfig = options.pluginConfig; this.shouldInterceptLogs = options.interceptLogs; this.clientWeb3Providers = []; + this.beforeDeploy = []; this.contractsGenerators = []; this.pipeline = []; this.pipelineFiles = []; @@ -85,6 +86,11 @@ Plugin.prototype.registerClientWeb3Provider = function(cb) { this.pluginTypes.push('clientWeb3Provider'); }; +Plugin.prototype.registerBeforeDeploy = function(cb) { + this.beforeDeploy.push(cb); + this.pluginTypes.push('beforeDeploy'); +}; + Plugin.prototype.registerContractsGeneration = function(cb) { this.contractsGenerators.push(cb); this.pluginTypes.push('contractGeneration');