diff --git a/bin/embark b/bin/embark index 1a73026d6..091afb1f4 100644 --- a/bin/embark +++ b/bin/embark @@ -15,17 +15,24 @@ var run = function(cmd) { } var deploy = function(env, embarkConfig) { - contractFiles = grunt.file.expand(embarkConfig.contracts); - destFile = embarkConfig.output - Embark.init() - Embark.blockchainConfig.loadConfigFile(embarkConfig.blockchainConfig) - Embark.contractsConfig.loadConfigFile(embarkConfig.contractsConfig) - abi = Embark.deployContracts(env, contractFiles, destFile) + var contractFiles = grunt.file.expand(embarkConfig.contracts); + var destFile = embarkConfig.output; + var chainFile = embarkConfig.chains; + + Embark.init(); + Embark.blockchainConfig.loadConfigFile(embarkConfig.blockchainConfig); + Embark.contractsConfig.loadConfigFile(embarkConfig.contractsConfig); + + if (chainFile === undefined) { + chainFile = './chains.json'; + } + + abi = Embark.deployContracts(env, contractFiles, destFile, chainFile); grunt.file.write(destFile, abi); } program - .version('0.7.3') + .version('0.8.0') program.command('new [name]').description('New application').action(function(name) { if (name === undefined) { diff --git a/boilerplate/chains.json b/boilerplate/chains.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/boilerplate/chains.json @@ -0,0 +1 @@ +{} diff --git a/boilerplate/package.json b/boilerplate/package.json index a6797e1e1..7323ecd3f 100644 --- a/boilerplate/package.json +++ b/boilerplate/package.json @@ -10,8 +10,8 @@ "license": "ISC", "homepage": "", "devDependencies": { - "embark-framework": "^0.7.3", - "grunt-embark": "^0.2.0", + "embark-framework": "^0.8.0", + "grunt-embark": "^0.3.0", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-coffee": "^0.13.0", "grunt-contrib-concat": "^0.5.1", diff --git a/lib/chain_manager.js b/lib/chain_manager.js new file mode 100644 index 000000000..157d6423a --- /dev/null +++ b/lib/chain_manager.js @@ -0,0 +1,54 @@ +var fs = require('fs'); +var web3 = require('web3'); +var sha3_256 = require('js-sha3').sha3_256; + +ChainManager = function() { + this.currentChain = {}; + this.file = ""; +} + +ChainManager.prototype.loadConfigFile = function(filename) { + try { + var obj = JSON.parse(fs.readFileSync(filename)); + this.file = filename; + this.chainManagerConfig = obj; + } catch (e) { + throw new Error("error reading " + filename); + } + return this; +}; + +ChainManager.prototype.loadConfig = function(config) { + this.chainManagerConfig = config; + return this; +}; + +ChainManager.prototype.init = function(env, config) { + web3.setProvider(new web3.providers.HttpProvider("http://" + config.rpcHost + ":" + config.rpcPort)); + + var chainId = web3.eth.getBlock(0).hash; + + if (this.chainManagerConfig[chainId] === undefined) { + this.chainManagerConfig[chainId] = {contracts: {}}; + } + + this.currentChain = this.chainManagerConfig[chainId]; +} + +ChainManager.prototype.addContract = function(contractName, code, address) { + this.currentChain.contracts[sha3_256(code)] = { + name: contractName, + address: address + } +} + +ChainManager.prototype.getContract = function(code) { + return this.currentChain.contracts[sha3_256(code)]; +} + +ChainManager.prototype.save = function() { + fs.writeFileSync(this.file, JSON.stringify(this.chainManagerConfig)); +} + +module.exports = ChainManager; + diff --git a/lib/config/contracts.js b/lib/config/contracts.js index d95b45770..5a42e00ac 100644 --- a/lib/config/contracts.js +++ b/lib/config/contracts.js @@ -122,6 +122,7 @@ ContractsConfig.prototype.compileContracts = function(env) { contract.gasLimit = contractConfig.gas_limit || contract.gasLimit; contract.args = contractConfig.args || []; contract.address = contractConfig.address; + contract.onDeploy = contractConfig.onDeploy || []; if (contractConfig.instanceOf !== undefined) { contract.types.push('instance'); diff --git a/lib/deploy.js b/lib/deploy.js index 158421bd4..81fcf08a4 100644 --- a/lib/deploy.js +++ b/lib/deploy.js @@ -10,9 +10,11 @@ sleep = function sleep(ms) { while (new Date().getTime() < start + ms); } -Deploy = function(env, contractFiles, blockchainConfig, contractsConfig) { +Deploy = function(env, contractFiles, blockchainConfig, contractsConfig, chainManager) { //this.blockchainConfig = (new Config.Blockchain()).loadConfigFile('config/blockchain.yml').config(env); this.blockchainConfig = blockchainConfig; + this.chainManager = chainManager; + this.chainManager.init(env, this.blockchainConfig); //this.contractsManager = (new Config.Contracts(contractFiles, blockchainConfig)).loadConfigFile('config/contracts.yml'); this.contractsManager = contractsConfig; @@ -56,6 +58,7 @@ Deploy.prototype.deploy_contracts = function(env) { className = all_contracts[k]; contract = this.contractDB[className]; + if (contract.address !== undefined) { this.deployedContracts[className] = contract.address; @@ -63,52 +66,82 @@ Deploy.prototype.deploy_contracts = function(env) { console.log("contract " + className + " at " + contract.address); } else { - contractObject = web3.eth.contract(contract.compiled.info.abiDefinition); + var chainContract = this.chainManager.getContract(contract.compiled.code); - realArgs = []; - for (var l = 0; l < contract.args.length; l++) { - arg = contract.args[l]; - if (arg[0] === "$") { - realArgs.push(this.deployedContracts[arg.substr(1)]); - } else { - realArgs.push(arg); - } - } - - contractParams = realArgs; - contractParams.push({ - from: primaryAddress, - data: contract.compiled.code, - gas: contract.gasLimit, - gasPrice: contract.gasPrice - }); - - console.log('trying to obtain ' + className + ' address...'); - - while((receipt = this.deploy_contract(contractObject, contractParams)) === false) { - console.log("timeout... failed to deploy contract.. retrying..."); - } - - var contractAddress = receipt.contractAddress; - - if (web3.eth.getCode(contractAddress) === "0x") { - console.log("========="); - console.log("contract was deployed at " + contractAddress + " but doesn't seem to be working"); - console.log("try adjusting your gas values"); - console.log("========="); + if (chainContract != undefined) { + console.log("contract " + className + " is unchanged and already deployed at " + chainContract.address); } else { + + contractObject = web3.eth.contract(contract.compiled.info.abiDefinition); + + realArgs = []; + for (var l = 0; l < contract.args.length; l++) { + arg = contract.args[l]; + if (arg[0] === "$") { + realArgs.push(this.deployedContracts[arg.substr(1)]); + } else { + realArgs.push(arg); + } + } + + contractParams = realArgs; + contractParams.push({ + from: primaryAddress, + data: contract.compiled.code, + gas: contract.gasLimit, + gasPrice: contract.gasPrice + }); + + console.log('trying to obtain ' + className + ' address...'); + + while((receipt = this.deploy_contract(contractObject, contractParams)) === false) { + console.log("timeout... failed to deploy contract.. retrying..."); + } + + var contractAddress = receipt.contractAddress; + + if (web3.eth.getCode(contractAddress) === "0x") { + console.log("========="); + console.log("contract was deployed at " + contractAddress + " but doesn't seem to be working"); + console.log("try adjusting your gas values"); + console.log("========="); + } + else { + console.log("deployed " + className + " at " + contractAddress); + } + + this.deployedContracts[className] = contractAddress; + this.chainManager.addContract(className, contract.compiled.code, contractAddress); + this.chainManager.save(); + console.log("deployed " + className + " at " + contractAddress); + this.execute_cmds(contract.onDeploy); } - - this.deployedContracts[className] = contractAddress; - - console.log("deployed " + className + " at " + contractAddress); } } }; +Deploy.prototype.execute_cmds = function(cmds) { + if (cmds.length === 0) return; + + eval(this.generate_abi_file()); + for (var i = 0; i < cmds.length; i++) { + var cmd = cmds[i]; + + for(className in this.deployedContracts) { + var contractAddress = this.deployedContracts[className]; + + var re = new RegExp("\\$" + className, 'g'); + cmd = cmd.replace(re, '"' + contractAddress + '"'); + } + + console.log("executing: " + cmd); + eval(cmd); + } +} + Deploy.prototype.generate_abi_file = function() { var result; diff --git a/lib/index.js b/lib/index.js index 25b56b619..956181303 100644 --- a/lib/index.js +++ b/lib/index.js @@ -16,12 +16,14 @@ var Deploy = require('./deploy.js'); var Release = require('./ipfs.js'); var Config = require('./config/config.js'); var Compiler = require('./compiler.js'); +var ChainManager = require('./chain_manager.js'); Embark = { init: function() { this.blockchainConfig = (new Config.Blockchain()); this.compiler = (new Compiler(this.blockchainConfig)); this.contractsConfig = (new Config.Contracts(this.blockchainConfig, this.compiler)); + this.chainManager = (new ChainManager()); }, tests: function(contractFiles) { @@ -33,9 +35,11 @@ Embark = { chain.startChain(use_tmp); }, - deployContracts: function(env, contractFiles, destFile) { + deployContracts: function(env, contractFiles, destFile, chainFile) { this.contractsConfig.init(contractFiles, env); - var deploy = new Deploy(env, contractFiles, this.blockchainConfig.config(env), this.contractsConfig); + + this.chainManager.loadConfigFile(chainFile) + var deploy = new Deploy(env, contractFiles, this.blockchainConfig.config(env), this.contractsConfig, this.chainManager); deploy.deploy_contracts(env); return deploy.generate_abi_file(destFile); }, diff --git a/package.json b/package.json index cbb9f03f7..d583c7b5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "embark-framework", - "version": "0.7.3", + "version": "0.8.0", "description": "", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -17,6 +17,7 @@ "grunt": "^0.4.5", "hashmerge": "^1.0.2", "jasmine": "^2.3.1", + "js-sha3": "^0.3.1", "meteor-build-client": "^0.1.6", "methodmissing": "^0.0.3", "mkdirp": "^0.5.1", diff --git a/test/chain_manager.js b/test/chain_manager.js new file mode 100644 index 000000000..26dcd13dd --- /dev/null +++ b/test/chain_manager.js @@ -0,0 +1,60 @@ +var ChainManager = require('../lib/chain_manager.js'); +var Config = require('../lib/config/config.js'); +var Blockchain = require('../lib/blockchain.js'); +var assert = require('assert'); +var fs = require('fs'); + +describe('embark.chain_manager', function() { + var chainFile = './test/support/chain_manager.json'; + fs.writeFileSync(chainFile, '{}'); + + var chainManager = (new ChainManager()).loadConfigFile(chainFile); + var blockchainConfig = (new Config.Blockchain()).loadConfigFile('test/support/blockchain.yml').config('development'); + + describe('#init', function() { + chainManager.init('development', blockchainConfig); + + it('should initialize chain', function() { + var chain = chainManager.chainManagerConfig['0x629e768beb87dc8c54a475d310a7196e86c97d0006e5a6d34a8217726c90223f'] + assert.equal(chain != undefined, true); + }); + }); + + describe('#addContract', function() { + + it('should register a contract in the chain', function() { + chainManager.addContract("Foo", "123456", "0x123"); + + var chain = chainManager.chainManagerConfig['0x629e768beb87dc8c54a475d310a7196e86c97d0006e5a6d34a8217726c90223f'] + var contract = chain.contracts["d7190eb194ff9494625514b6d178c87f99c5973e28c398969d2233f2960a573e"] + + assert.equal(contract.name, "Foo"); + assert.equal(contract.address, "0x123"); + }); + + }); + + describe('#getContract', function() { + + it('should a contract in the chain', function() { + var contract = chainManager.getContract("123456"); + + assert.equal(contract.name, "Foo"); + assert.equal(contract.address, "0x123"); + }); + + }); + + describe('#save', function() { + + it('should save changes in the chain', function() { + chainManager.save(); + + var chainFile = './test/support/chain_manager.json'; + var content = fs.readFileSync(chainFile).toString(); + assert.equal(content, '{"0x629e768beb87dc8c54a475d310a7196e86c97d0006e5a6d34a8217726c90223f":{"contracts":{"d7190eb194ff9494625514b6d178c87f99c5973e28c398969d2233f2960a573e":{"name":"Foo","address":"0x123"}}}}'); + }); + + }); + +}); diff --git a/test/deploy.js b/test/deploy.js index 4673dcf95..c5df8511e 100644 --- a/test/deploy.js +++ b/test/deploy.js @@ -2,16 +2,18 @@ var Config = require('../lib/config/config.js'); var Deploy = require('../lib/deploy.js'); var Compiler = require('../lib/compiler.js'); var assert = require('assert'); +var web3 = require('web3'); setDeployConfig = function(config) { var _blockchainConfig = (new Config.Blockchain()).loadConfigFile(config.blockchain); var blockchainConfig = _blockchainConfig.config("development"); var compiler = new Compiler(_blockchainConfig); var contractsConfig = new Config.Contracts(blockchainConfig, compiler); + var chainManager = (new ChainManager()).loadConfigFile('./test/support/chain_manager.json'); contractsConfig.loadConfigFile(config.contracts); contractsConfig.init(config.files, 'development'); compiler.init('development'); - return new Deploy('development', config.files, blockchainConfig, contractsConfig); + return new Deploy('development', config.files, blockchainConfig, contractsConfig, chainManager); } describe('embark.deploy', function() { @@ -119,6 +121,52 @@ describe('embark.deploy', function() { }); + describe('contracts deploy script', function() { + var files = [ + 'test/support/contracts/data_source.sol', + 'test/support/contracts/manager.sol' + ]; + + describe('#deploy_contracts', function() { + var deploy = setDeployConfig({ + files: files, + blockchain: 'test/support/blockchain.yml', + contracts: 'test/support/arguments3.yml' + }); + deploy.deploy_contracts("development"); + + it("should deploy contracts", function() { + var all_contracts = ['DataSource', 'MyDataSource', 'Manager']; + for(var i=0; i < all_contracts.length; i++) { + var className = all_contracts[i]; + + assert.equal(deploy.deployedContracts.hasOwnProperty(className), true); + } + }); + + it("should execute deploy changes", function() { + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8101')); + web3.eth.defaultAccount = web3.eth.accounts[0]; + + data_source_abi = deploy.contractDB['DataSource'].compiled.info.abiDefinition; + data_source_address = deploy.deployedContracts['DataSource']; + my_data_source_abi = deploy.contractDB['MyDataSource'].compiled.info.abiDefinition; + my_data_source_address = deploy.deployedContracts['MyDataSource']; + manager_abi = deploy.contractDB['Manager'].compiled.info.abiDefinition; + manager_address = deploy.deployedContracts['Manager']; + + DataSource = web3.eth.contract(data_source_abi).at(data_source_address); + MyDataSource = web3.eth.contract(my_data_source_abi).at(my_data_source_address); + ManagerSource = web3.eth.contract(manager_abi).at(manager_address); + + assert.equal(DataSource.storeData().toNumber(), 5); + assert.equal(Manager.data().toString(), my_data_source_address); + }); + + }); + + }); + describe('contracts with addresses defined', function() { var files = [ 'test/support/contracts/simple_storage.sol' diff --git a/test/support/arguments3.yml b/test/support/arguments3.yml new file mode 100644 index 000000000..7fe418f86 --- /dev/null +++ b/test/support/arguments3.yml @@ -0,0 +1,15 @@ +development: + DataSource: + args: + MyDataSource: + args: + instanceOf: DataSource + Manager: + stubs: + - DataSource + args: + - $DataSource + onDeploy: + - DataSource.set(5) + - Manager.update($MyDataSource) +staging: diff --git a/test/support/chain_manager.json b/test/support/chain_manager.json new file mode 100644 index 000000000..ccb1943fb --- /dev/null +++ b/test/support/chain_manager.json @@ -0,0 +1 @@ +{"0x629e768beb87dc8c54a475d310a7196e86c97d0006e5a6d34a8217726c90223f":{"contracts":{"d7190eb194ff9494625514b6d178c87f99c5973e28c398969d2233f2960a573e":{"name":"Foo","address":"0x123"}}}} \ No newline at end of file diff --git a/test/support/contracts/data_source.sol b/test/support/contracts/data_source.sol new file mode 100644 index 000000000..90b0c7843 --- /dev/null +++ b/test/support/contracts/data_source.sol @@ -0,0 +1,11 @@ +contract DataSource { + uint public storeData; + + function DataSource() { + } + + function set(uint num) { + storeData = num; + } + +} diff --git a/test/support/contracts/manager.sol b/test/support/contracts/manager.sol new file mode 100644 index 000000000..0445cee2e --- /dev/null +++ b/test/support/contracts/manager.sol @@ -0,0 +1,11 @@ +contract Manager { + address public data; + + function Manager(address dataAddress) { + data = dataAddress; + } + + function update(address _addr) { + data = _addr; + } +}