Merge pull request #329 from hodlbank/develop

Fixing #325: beforeDeploy plugins feature
This commit is contained in:
Iuri Matias 2018-01-17 19:41:34 -05:00 committed by GitHub
commit 8eb6298ece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 55 deletions

View File

@ -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,32 @@ 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) {
var code = options.contract.code.replace(/deaddeaddeaddeaddeaddeaddeaddeaddeaddead/ig, 'c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de');
options.callback({ contractCode: code });
return; // ignored
});
}
**embark.registerClientWeb3Provider(callback(options))**
@ -165,7 +192,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 +237,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 +295,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 +314,3 @@ This call is used to listen and react to events that happen in Embark such as co
}
});
}

View File

@ -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,94 @@ 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) {
callback(new Error("attempted to deploy " + contractObject.className + " without specifying parameters"));
asyncCallback();
return;
} else {
callback(new Error(e));
asyncCallback();
return;
}
}
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));
});
asyncCallback();
}
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
});
}

View File

@ -77,6 +77,7 @@ class DeployManager {
logger: self.logger,
chainConfig: self.chainConfig,
env: self.config.env,
plugins: self.plugins,
gasLimit: self.gasLimit
});

View File

@ -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');