2017-03-29 17:50:05 +00:00
|
|
|
let toposort = require('toposort');
|
|
|
|
let async = require('async');
|
2016-08-14 12:04:34 +00:00
|
|
|
|
2017-03-29 17:50:05 +00:00
|
|
|
let Compiler = require('./compiler.js');
|
2017-12-19 19:07:48 +00:00
|
|
|
let utils = require('../utils/utils.js');
|
2017-02-19 18:17:28 +00:00
|
|
|
|
2016-09-28 01:04:40 +00:00
|
|
|
// TODO: create a contract object
|
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
class ContractsManager {
|
|
|
|
constructor(options) {
|
|
|
|
this.contractFiles = options.contractFiles;
|
|
|
|
this.contractsConfig = options.contractsConfig;
|
|
|
|
this.contracts = {};
|
|
|
|
this.logger = options.logger;
|
|
|
|
this.plugins = options.plugins;
|
|
|
|
this.contractDependencies = {};
|
2018-01-13 16:38:10 +00:00
|
|
|
this.gasLimit = options.gasLimit;
|
2016-10-31 01:31:29 +00:00
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
|
|
|
|
build(done) {
|
|
|
|
let self = this;
|
|
|
|
async.waterfall([
|
|
|
|
function compileContracts(callback) {
|
2017-12-16 20:39:30 +00:00
|
|
|
let compiler = new Compiler({plugins: self.plugins, logger: self.logger});
|
2017-03-30 11:12:39 +00:00
|
|
|
compiler.compile_contracts(self.contractFiles, function (err, compiledObject) {
|
2017-02-17 12:14:44 +00:00
|
|
|
self.compiledContracts = compiledObject;
|
2017-02-28 13:03:03 +00:00
|
|
|
callback(err);
|
2017-02-17 12:14:44 +00:00
|
|
|
});
|
2017-03-30 11:12:39 +00:00
|
|
|
},
|
|
|
|
function prepareContractsFromConfig(callback) {
|
|
|
|
let className, contract;
|
|
|
|
for (className in self.contractsConfig.contracts) {
|
|
|
|
contract = self.contractsConfig.contracts[className];
|
2016-09-28 01:04:40 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
contract.className = className;
|
|
|
|
contract.args = contract.args || [];
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
self.contracts[className] = contract;
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
},
|
|
|
|
function prepareContractsFromCompilation(callback) {
|
|
|
|
let className, compiledContract, contractConfig, contract;
|
|
|
|
for (className in self.compiledContracts) {
|
|
|
|
compiledContract = self.compiledContracts[className];
|
|
|
|
contractConfig = self.contractsConfig.contracts[className];
|
|
|
|
|
|
|
|
contract = self.contracts[className] || {className: className, args: []};
|
|
|
|
|
|
|
|
contract.code = compiledContract.code;
|
|
|
|
contract.runtimeBytecode = compiledContract.runtimeBytecode;
|
|
|
|
contract.realRuntimeBytecode = (contract.realRuntimeBytecode || contract.runtimeBytecode);
|
|
|
|
contract.swarmHash = compiledContract.swarmHash;
|
|
|
|
contract.gasEstimates = compiledContract.gasEstimates;
|
|
|
|
contract.functionHashes = compiledContract.functionHashes;
|
|
|
|
contract.abiDefinition = compiledContract.abiDefinition;
|
2017-07-16 16:10:17 +00:00
|
|
|
contract.filename = compiledContract.filename;
|
2017-03-30 11:12:39 +00:00
|
|
|
|
|
|
|
contract.gas = (contractConfig && contractConfig.gas) || self.contractsConfig.gas || 'auto';
|
|
|
|
self.adjustGas(contract);
|
|
|
|
|
|
|
|
contract.gasPrice = contract.gasPrice || self.contractsConfig.gasPrice;
|
|
|
|
contract.type = 'file';
|
|
|
|
contract.className = className;
|
|
|
|
|
|
|
|
self.contracts[className] = contract;
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
},
|
2017-12-14 21:16:51 +00:00
|
|
|
function setDeployIntention(callback) {
|
|
|
|
let className, contract;
|
|
|
|
for (className in self.contracts) {
|
|
|
|
contract = self.contracts[className];
|
|
|
|
contract.deploy = (contract.deploy === undefined) || contract.deploy;
|
|
|
|
if (contract.code === "") {
|
|
|
|
self.logger.info("assuming " + className + " to be an interface");
|
|
|
|
contract.deploy = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
},
|
2017-03-30 11:12:39 +00:00
|
|
|
/*eslint complexity: ["error", 11]*/
|
|
|
|
function dealWithSpecialConfigs(callback) {
|
|
|
|
let className, contract, parentContractName, parentContract;
|
2017-12-19 19:07:48 +00:00
|
|
|
let dictionary = Object.keys(self.contracts);
|
2016-09-28 01:04:40 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
for (className in self.contracts) {
|
|
|
|
contract = self.contracts[className];
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
if (contract.instanceOf === undefined) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
parentContractName = contract.instanceOf;
|
|
|
|
parentContract = self.contracts[parentContractName];
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
if (parentContract === className) {
|
|
|
|
self.logger.error(className + ": instanceOf is set to itself");
|
|
|
|
continue;
|
|
|
|
}
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
if (parentContract === undefined) {
|
|
|
|
self.logger.error(className + ": couldn't find instanceOf contract " + parentContractName);
|
2017-12-20 14:41:12 +00:00
|
|
|
let suggestion = utils.proposeAlternative(parentContractName, dictionary, [className, parentContractName]);
|
2017-12-19 19:07:48 +00:00
|
|
|
if (suggestion) {
|
|
|
|
self.logger.warn('did you mean "' + suggestion + '"?');
|
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
continue;
|
|
|
|
}
|
2016-09-28 01:04:40 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
if (parentContract.args && parentContract.args.length > 0 && ((contract.args && contract.args.length === 0) || contract.args === undefined)) {
|
|
|
|
contract.args = parentContract.args;
|
|
|
|
}
|
2016-10-02 15:07:56 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
if (contract.code !== undefined) {
|
|
|
|
self.logger.error(className + " has code associated to it but it's configured as an instanceOf " + parentContractName);
|
|
|
|
}
|
2016-09-28 01:04:40 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
contract.code = parentContract.code;
|
|
|
|
contract.runtimeBytecode = parentContract.runtimeBytecode;
|
|
|
|
contract.gasEstimates = parentContract.gasEstimates;
|
|
|
|
contract.functionHashes = parentContract.functionHashes;
|
|
|
|
contract.abiDefinition = parentContract.abiDefinition;
|
2016-10-31 00:48:16 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
contract.gas = contract.gas || parentContract.gas;
|
|
|
|
contract.gasPrice = contract.gasPrice || parentContract.gasPrice;
|
|
|
|
contract.type = 'instance';
|
2016-09-28 01:04:40 +00:00
|
|
|
|
2016-10-29 16:02:07 +00:00
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
callback();
|
|
|
|
},
|
|
|
|
function removeContractsWithNoCode(callback) {
|
|
|
|
let className, contract;
|
2017-12-19 19:07:48 +00:00
|
|
|
let dictionary = Object.keys(self.contracts);
|
2017-03-30 11:12:39 +00:00
|
|
|
for (className in self.contracts) {
|
|
|
|
contract = self.contracts[className];
|
|
|
|
|
|
|
|
if (contract.code === undefined) {
|
|
|
|
self.logger.error(className + " has no code associated");
|
2017-12-19 19:07:48 +00:00
|
|
|
let suggestion = utils.proposeAlternative(className, dictionary, [className]);
|
|
|
|
if (suggestion) {
|
|
|
|
self.logger.warn('did you mean "' + suggestion + '"?');
|
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
delete self.contracts[className];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.logger.trace(self.contracts);
|
|
|
|
callback();
|
|
|
|
},
|
|
|
|
function determineDependencies(callback) {
|
|
|
|
let className, contract;
|
|
|
|
for (className in self.contracts) {
|
|
|
|
contract = self.contracts[className];
|
|
|
|
|
2017-07-16 17:31:40 +00:00
|
|
|
// look in code for dependencies
|
|
|
|
let libMatches = (contract.code.match(/\:(.*?)(?=_)/g) || []);
|
|
|
|
for (let match of libMatches) {
|
|
|
|
self.contractDependencies[className] = self.contractDependencies[className] || [];
|
|
|
|
self.contractDependencies[className].push(match.substr(1));
|
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
|
2017-07-16 17:31:40 +00:00
|
|
|
// look in arguments for dependencies
|
|
|
|
if (contract.args === []) continue;
|
2017-03-30 11:12:39 +00:00
|
|
|
let ref = contract.args;
|
|
|
|
for (let j = 0; j < ref.length; j++) {
|
|
|
|
let arg = ref[j];
|
|
|
|
if (arg[0] === "$") {
|
|
|
|
self.contractDependencies[className] = self.contractDependencies[className] || [];
|
|
|
|
self.contractDependencies[className].push(arg.substr(1));
|
|
|
|
}
|
2016-10-29 16:02:07 +00:00
|
|
|
}
|
2017-12-20 19:30:01 +00:00
|
|
|
|
|
|
|
// look in onDeploy for dependencies
|
|
|
|
if (contract.onDeploy === [] || contract.onDeploy === undefined) continue;
|
|
|
|
let regex = /\$\w+/g;
|
|
|
|
contract.onDeploy.map((cmd) => {
|
|
|
|
cmd.replace(regex, (match) => {
|
|
|
|
self.contractDependencies[className] = self.contractDependencies[className] || [];
|
|
|
|
self.contractDependencies[className].push(match.substr(1));
|
|
|
|
});
|
|
|
|
});
|
2016-10-29 16:02:07 +00:00
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
callback();
|
2016-10-29 16:02:07 +00:00
|
|
|
}
|
2017-10-14 10:12:54 +00:00
|
|
|
], function (err, _result) {
|
2017-03-30 11:12:39 +00:00
|
|
|
if (err) {
|
|
|
|
self.logger.error("Error Compiling/Building contracts: " + err);
|
|
|
|
}
|
|
|
|
self.logger.trace("finished".underline);
|
|
|
|
done(err, self);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getContract(className) {
|
|
|
|
return this.contracts[className];
|
2016-09-27 04:55:35 +00:00
|
|
|
}
|
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
sortContracts(contractList) {
|
|
|
|
let converted_dependencies = [], i;
|
|
|
|
|
|
|
|
for (let contract in this.contractDependencies) {
|
|
|
|
let dependencies = this.contractDependencies[contract];
|
|
|
|
for (i = 0; i < dependencies.length; i++) {
|
|
|
|
converted_dependencies.push([contract, dependencies[i]]);
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 04:55:35 +00:00
|
|
|
|
2017-12-20 19:54:47 +00:00
|
|
|
let orderedDependencies;
|
|
|
|
|
|
|
|
try {
|
2017-12-28 13:26:02 +00:00
|
|
|
orderedDependencies = toposort(converted_dependencies.filter((x) => x[0] != x[1])).reverse();
|
2017-12-20 19:54:47 +00:00
|
|
|
} catch(e) {
|
|
|
|
this.logger.error(("Error: " + e.message).red);
|
|
|
|
this.logger.error("there are two or more contracts that depend on each other in a cyclic manner".bold.red);
|
|
|
|
this.logger.error("Embark couldn't determine which one to deploy first".red);
|
2017-12-20 19:58:59 +00:00
|
|
|
throw new Error("CyclicDependencyError");
|
|
|
|
//process.exit(0);
|
2017-12-20 19:54:47 +00:00
|
|
|
}
|
2016-09-27 04:55:35 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
let newList = contractList.sort(function (a, b) {
|
|
|
|
let order_a = orderedDependencies.indexOf(a.className);
|
|
|
|
let order_b = orderedDependencies.indexOf(b.className);
|
|
|
|
return order_a - order_b;
|
|
|
|
});
|
2016-09-27 04:55:35 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
return newList;
|
2016-08-14 12:04:34 +00:00
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
|
|
|
|
// TODO: should be built contracts
|
|
|
|
listContracts() {
|
|
|
|
let contracts = [];
|
|
|
|
for (let className in this.contracts) {
|
|
|
|
let contract = this.contracts[className];
|
|
|
|
contracts.push(contract);
|
2016-10-21 03:31:42 +00:00
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
return this.sortContracts(contracts);
|
|
|
|
}
|
2016-10-21 03:31:42 +00:00
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
contractsState() {
|
|
|
|
let data = [];
|
|
|
|
|
|
|
|
for (let className in this.contracts) {
|
|
|
|
let contract = this.contracts[className];
|
|
|
|
|
|
|
|
let contractData;
|
|
|
|
|
|
|
|
if (contract.deploy === false) {
|
|
|
|
contractData = [
|
|
|
|
className.green,
|
|
|
|
'Interface or set to not deploy'.green,
|
|
|
|
"\t\tn/a".green
|
|
|
|
];
|
|
|
|
} else if (contract.error) {
|
|
|
|
contractData = [
|
|
|
|
className.green,
|
2017-12-14 20:03:08 +00:00
|
|
|
(contract.error).split("\n")[0].replace(/Error: /g, '').substring(0, 32).red,
|
2017-03-30 11:12:39 +00:00
|
|
|
'\t\tError'.red
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
contractData = [
|
|
|
|
className.green,
|
|
|
|
(contract.deployedAddress || '...').green,
|
|
|
|
((contract.deployedAddress !== undefined) ? "\t\tDeployed".green : "\t\tPending".magenta)
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
data.push(contractData);
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
2016-09-22 22:24:01 +00:00
|
|
|
}
|
|
|
|
|
2017-03-30 11:12:39 +00:00
|
|
|
adjustGas(contract) {
|
|
|
|
let maxGas, adjustedGas;
|
|
|
|
if (contract.gas === 'auto') {
|
|
|
|
if (contract.deploy || contract.deploy === undefined) {
|
|
|
|
if (contract.gasEstimates.creation !== undefined) {
|
|
|
|
// TODO: should sum it instead
|
|
|
|
maxGas = Math.max(contract.gasEstimates.creation[0], contract.gasEstimates.creation[1], 500000);
|
|
|
|
} else {
|
|
|
|
maxGas = 500000;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
maxGas = 500000;
|
|
|
|
}
|
|
|
|
// TODO: put a check so it doesn't go over the block limit
|
|
|
|
adjustedGas = Math.round(maxGas * 1.40);
|
|
|
|
adjustedGas += 25000;
|
|
|
|
contract.gas = adjustedGas;
|
2018-01-13 16:38:10 +00:00
|
|
|
if (this.gasLimit && this.gasLimit > contract.gas) {
|
|
|
|
contract.gas = this.gasLimit;
|
|
|
|
}
|
2017-03-30 11:12:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-22 22:24:01 +00:00
|
|
|
|
2016-08-14 12:04:34 +00:00
|
|
|
module.exports = ContractsManager;
|