diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 5909c4e1..8a6a4512 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -23,7 +23,7 @@ module.exports = (grunt) -> src: ['test/**/*.js'] grunt.loadTasks "tasks" - require('matchdep').filterAll('grunt-*').forEach(grunt.loadNpmTasks) + require('matchdep').filterAll(['grunt-*','!grunt-cli']).forEach(grunt.loadNpmTasks) grunt.registerTask 'default', ['clean'] grunt.registerTask 'build', ['clean', 'coffee'] diff --git a/README.md b/README.md index 3e2b4c4d..4930ed01 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,17 @@ Embark is a framework that allows you to easily develop and deploy DApps. With Embark you can: * Automatically deploy contracts and make them available in your JS code. Embark watches for changes, and if you update a contract, Embark will automatically redeploy the contracts (if needed) and the dapp. +* Use any build pipeline or tool you wish, including grunt and meteor. * Do Test Driven Development with Contracts using Javascript. * Easily deploy to & use decentralized systems such as IPFS. +* Keep track of deployed contracts, deploy only when truly needed. * Quickly create advanced DApps using multiple contracts. See the [Wiki](https://github.com/iurimatias/embark-framework/wiki) for more details. Installation ====== -Requirements: geth (1.0.0), solc (0.9.23), node (0.12.2) and npm +Requirements: geth (1.0.0), solc (0.1.0) or serpent (develop), node (0.12.2) and npm For specs: pyethereum, ethertdd.py @@ -49,6 +51,8 @@ This will automatically deploy the contracts, update their JS bindings and deplo Note that if you update your code it will automatically be re-deployed, contracts included. There is no need to restart embark, refreshing the page on the browser will do. +note: for a demo using meteor do ```embark meteor_demo``` followed by ```embark deploy``` then ```meteor``` + Creating a new DApp ====== @@ -62,7 +66,7 @@ DApp Structure ```Bash app/ - |___ contracts/ #solidity contracts + |___ contracts/ #solidity or serpent contracts |___ html/ |___ css/ |___ js/ @@ -74,7 +78,7 @@ DApp Structure |___ contracts/ #contracts tests ``` -Solidity files in the contracts directory will automatically be deployed with embark run. Changes in any files will automatically be reflected in app, changes to contracts will result in a redeployment and update of their JS Bindings +Solidity/Serpent files in the contracts directory will automatically be deployed with embark run. Changes in any files will automatically be reflected in app, changes to contracts will result in a redeployment and update of their JS Bindings Using Contracts ====== @@ -145,6 +149,7 @@ You can now deploy many instances of the same contract. e.g # config/contracts.yml development: Currency: + deploy: false args: - 100 Usd: @@ -171,16 +176,44 @@ Contracts addresses can be defined, If an address is defined the contract wouldn ... ``` +You can also define contract interfaces (Stubs) and actions to do on deployment + +```Yaml + development: + DataSource: + args: + MyDataSource: + args: + instanceOf: DataSource + Manager: + stubs: + - DataSource + args: + - $MyDataSource + onDeploy: + - Manager.updateStorage($MyDataSource) + - MyDataSource.set(5) + ... +``` + Tests ====== You can run specs with ```embark spec```, it will run any files ending *_spec.js under ```spec/```. -Embark includes a testing lib to fastly run & test your contracts in a EVM. +Embark includes a testing lib to fastly run & test your contracts in a EVM. ```Javascript # spec/contracts/simple_storage_spec.js -EmbarkSpec = require('embark-framework').Tests; +Embark = require('embark-framework'); +Embark.init(); +Embark.blockchainConfig.loadConfigFile('config/blockchain.yml'); +Embark.contractsConfig.loadConfigFile('config/contracts.yml'); + +var files = ['app/contracts/simpleStorage.sol']; +Embark.contractsConfig.init(files, 'development'); + +var EmbarkSpec = Embark.tests(files); describe("SimpleStorage", function() { beforeAll(function() { @@ -222,6 +255,7 @@ The environment is a specific blockchain configuration that can be managed at co rpc_port: 8101 rpc_whitelist: "*" datadir: default + chains: chains_staging.json network_id: 0 console: true account: @@ -231,7 +265,6 @@ The environment is a specific blockchain configuration that can be managed at co See [Configuration](https://github.com/iurimatias/embark-framework/wiki/Configuration). - Deploying only contracts ====== Although embark run will automatically deploy contracts, you can choose to only deploy the contracts to a specific environment @@ -242,6 +275,26 @@ $ embark deploy privatenet embark deploy will deploy all contracts at app/contracts and return the resulting addresses +Structuring Application +====== + +Embark is quite flexible and you can configure you're own directory structure using ```embark.yml``` + +```Yaml +# embark.yml + type: "manual" #other options: meteor, grunt + contracts: ["app/contracts/**/*.sol", "app/contracts/**/*.se"] # contracts files + output: "src/embark.js" # resulting javascript interface + blockchainConfig: "config/blockchain.yml" # blockchain config + contractsConfig: "config/contracts.yml" # contracts config +``` + +Deploying to IPFS +====== + +To deploy a dapp to IPFS, all you need to do is run a local IPFS node and then run ```embark ipfs```. +If you want to deploy to the live net then after configuring you account on ```config/blockchain.yml``` on the ```production``` environment then you can deploy to that chain by specifying the environment ```embark ipfs production```. + LiveReload Plugin ====== @@ -255,5 +308,19 @@ Because embark is internally using grunt tasks, debugging is not straightforward - normally you would write something like `node-debug -p 7000 embark -- deploy` - This gives you nothing with embark. If you look at `deploy` command in [`./bin/embark`](https://github.com/iurimatias/embark-framework/blob/develop/bin/embark#L32-L35) you will notice that it internally runs grunt task `grunt deploy_contracts:[env]` - with this knowledge we can prepare proper command to start debugging -- `node-debug -p 7000 grunt -- deploy_contracts:development` -- [here](https://github.com/iurimatias/embark-framework/blob/develop/tasks/tasks.coffee) is list of all debuggable grunt tasks +- ```node-debug -p 7000 grunt -- deploy_contracts:development``` + + +[here](https://github.com/iurimatias/embark-framework/blob/develop/tasks/tasks.coffee) is list of all debuggable grunt tasks + +EACCESS Error +====== +If you get EACCES (access denied) errors, don't use sudo, try this: + +```Bash +$ mkdir ~/npm-global +$ npm config set prefix ~/npm-global +$ echo 'export PATH="$PATH:$HOME/npm-global/bin"' >>~/.bashrc +$ source ~/.bashrc +$ npm install -g embark-framework grunt-cli +``` diff --git a/bin/embark b/bin/embark old mode 100644 new mode 100755 index 98fd67e3..f9141ca0 --- a/bin/embark +++ b/bin/embark @@ -6,7 +6,7 @@ var wrench = require('wrench'); var grunt = require('grunt'); require('shelljs/global'); var readYaml = require('read-yaml'); -var Embark = require('embark-framework'); +var Embark = require('..'); var run = function(cmd) { if (exec(cmd).code != 0) { @@ -17,22 +17,19 @@ var run = function(cmd) { var deploy = function(env, embarkConfig) { 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'; - } + var chainFile = Embark.blockchainConfig.blockchainConfig[env].chains || embarkConfig.chains || './chains.json'; abi = Embark.deployContracts(env, contractFiles, destFile, chainFile); grunt.file.write(destFile, abi); } program - .version('0.8.4') + .version('0.9.1') program.command('new [name]').description('New application').action(function(name) { if (name === undefined) { @@ -132,13 +129,24 @@ program.command('blockchain [env]').description('run blockchain').action(functio Embark.blockchainConfig.loadConfigFile(embarkConfig.blockchainConfig) Embark.contractsConfig.loadConfigFile(embarkConfig.contractsConfig) - //TODO: better with --exec, but need to fix console bug first - wrench.copyDirSyncRecursive(__dirname + "/../js", "/tmp/js", {forceDelete: true}); + Embark.copyMinerJavascriptToTemp(); Embark.startBlockchain(env, true); } }); +program.command('geth [args...]').description('run geth with specified arguments').action(function(env_, args_) { + var env = env_ || 'development'; + var embarkConfig = readYaml.sync("./embark.yml"); + var args = args_.join(' '); + + Embark.init() + Embark.blockchainConfig.loadConfigFile(embarkConfig.blockchainConfig) + Embark.contractsConfig.loadConfigFile(embarkConfig.contractsConfig) + + Embark.geth(env, args); +}); + program.command('demo').description('create a working dapp with a SimpleStorage contract').action(function() { var boilerPath = path.join(__dirname + '/../boilerplate'); var demoPath = path.join(__dirname + '/../demo'); @@ -169,4 +177,3 @@ if (!process.argv.slice(2).length) { } exit(); - diff --git a/boilerplate/.gitignore b/boilerplate/.gitignore new file mode 100644 index 00000000..5620484f --- /dev/null +++ b/boilerplate/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +chains/development.json diff --git a/boilerplate/Gruntfile.coffee b/boilerplate/Gruntfile.coffee index 92d7f086..150ff372 100644 --- a/boilerplate/Gruntfile.coffee +++ b/boilerplate/Gruntfile.coffee @@ -33,6 +33,7 @@ module.exports = (grunt) -> contracts: src: [ "app/contracts/**/*.sol" + "app/contracts/**/*.se" ] coffee: diff --git a/boilerplate/config/blockchain.yml b/boilerplate/config/blockchain.yml index 0eafb67b..49fb0add 100644 --- a/boilerplate/config/blockchain.yml +++ b/boilerplate/config/blockchain.yml @@ -5,6 +5,7 @@ development: minerthreads: 1 genesis_block: config/genesis/dev_genesis.json datadir: /tmp/embark + chains: config/chains/development.json mine_when_needed: true max_peers: 0 gas_limit: 500000 diff --git a/boilerplate/config/chains/development.json b/boilerplate/config/chains/development.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/boilerplate/config/chains/development.json @@ -0,0 +1 @@ +{} diff --git a/boilerplate/config/genesis/dev_genesis.json b/boilerplate/config/genesis/dev_genesis.json index 9f60d776..9f2d1562 100644 --- a/boilerplate/config/genesis/dev_genesis.json +++ b/boilerplate/config/genesis/dev_genesis.json @@ -1,10 +1,11 @@ { "nonce": "0x0000000000000042", - "difficulty": "0x40000", + "difficulty": "0x0", "alloc": { + "0x3333333333333333333333333333333333333333": {"balance": "15000000000000000000"} }, "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", + "coinbase": "0x3333333333333333333333333333333333333333", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", diff --git a/boilerplate/embark.yml b/boilerplate/embark.yml index 5d19d917..b47e72d2 100644 --- a/boilerplate/embark.yml +++ b/boilerplate/embark.yml @@ -1,5 +1,5 @@ type: "grunt" #other options: meteor, manual -#contracts: ["app/contracts/**/*.sol"] +#contracts: ["app/contracts/**/*.sol", "app/contracts/**/*.se"] #output: "src/embark.js" #blockchainConfig: "config/blockchain.yml" #contractsConfig: "config/contracts.yml" diff --git a/boilerplate/package.json b/boilerplate/package.json index 77076986..c7235070 100644 --- a/boilerplate/package.json +++ b/boilerplate/package.json @@ -10,8 +10,8 @@ "license": "ISC", "homepage": "", "devDependencies": { - "embark-framework": "^0.8.4", - "grunt-embark": "^0.3.0", + "embark-framework": "^0.9.1", + "grunt-embark": "^0.4.1", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-coffee": "^0.13.0", "grunt-contrib-concat": "^0.5.1", diff --git a/demo/config/blockchain.yml b/demo/config/blockchain.yml index 0072c4cb..5c17deba 100644 --- a/demo/config/blockchain.yml +++ b/demo/config/blockchain.yml @@ -5,6 +5,7 @@ development: minerthreads: 1 genesis_block: config/genesis/dev_genesis.json datadir: /tmp/embark + chains: config/chains/development.json mine_when_needed: true gas_limit: 500000 gas_price: 10000000000000 diff --git a/demo/config/chains/development.json b/demo/config/chains/development.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/demo/config/chains/development.json @@ -0,0 +1 @@ +{} diff --git a/demo_meteor/embark.yml b/demo_meteor/embark.yml index d39816df..329544dd 100644 --- a/demo_meteor/embark.yml +++ b/demo_meteor/embark.yml @@ -1,5 +1,5 @@ type: "meteor" -contracts: ["contracts/**/*.sol"] +contracts: ["contracts/**/*.sol", "contracts/**/*.se"] output: "client/embark.js" blockchainConfig: "config/blockchain.yml" contractsConfig: "config/contracts.yml" diff --git a/js/mine.js b/js/mine.js index c03fe5ec..c685ae50 100644 --- a/js/mine.js +++ b/js/mine.js @@ -1,16 +1,75 @@ -var miner_var; +// Adapted from Iuri Matias' Embark framework +// https://github.com/iurimatias/embark-framework +// Modified by ryepdx to mine at regular intervals. +(function() { + var main = function () { + /* TODO: Find a way to load mining config from YML. -if (admin.miner === undefined) { - miner_var = miner; -} -else { - miner_var = admin.miner; -} + if (!loadScript("config.js")) { + console.log("== config.js not found"); + } -miner_var.setEtherbase(web3.eth.accounts[0]); + if (typeof(config) === "undefined") { + config = {}; + console.log("== config is undefined, proceeding with defaults"); + } + + In the meantime, just set an empty config object. + */ + config = {} + + defaults = { + interval_ms: 15000, + initial_ether: 15000000000000000000, + mine_pending_txns: true, + mine_periodically: false, + mine_normally: false, + threads: 1 + } + + for (key in defaults) { + if (config[key] === undefined) { + config[key] = defaults[key]; + } + } + + var miner_obj = (admin.miner === undefined) ? miner : admin.miner; + + if (config.mine_normally) { + miner_obj.start(config.threads); + return; + } + miner_obj.stop(); + + fundAccount(config, miner_obj, function () { + if (config.mine_periodically) start_periodic_mining(config, miner_obj); + if (config.mine_pending_txns) start_transaction_mining(config, miner_obj); + }); + }; + + var fundAccount = function (config, miner_obj, cb) { + var accountFunded = function () { + return (eth.getBalance(eth.coinbase) >= config.initial_ether); + } + + if (accountFunded()) { + return cb(); + } + + console.log("== Funding account"); + miner_obj.start(); + + var blockWatcher = web3.eth.filter("latest").watch(function () { + if (accountFunded()) { + console.log("== Account funded"); + + blockWatcher.stopWatching(); + miner_obj.stop(); + cb(); + } + }); + }; -setInterval(function() { - var minimalAmount = (web3.eth.getBalance(web3.eth.coinbase) >= 15000000000000000000); var pendingTransactions = function() { if (web3.eth.pendingTransactions === undefined || web3.eth.pendingTransactions === null) { return txpool.status.pending || txpool.status.queued; @@ -21,19 +80,60 @@ setInterval(function() { else { return web3.eth.pendingTransactions.length > 0 || web3.eth.getBlock('pending').transactions.length > 0; } - } + }; - if(!web3.eth.mining && (!minimalAmount || pendingTransactions())) { - if (!minimalAmount) { console.log("=== minimal ether amount not reached yet") } - if (pendingTransactions()) { console.log("=== there are pending transactions") } - console.log("=== start mining"); - miner_var.start(); - } - else if (web3.eth.mining && minimalAmount && !pendingTransactions()) { - if (minimalAmount) { console.log("=== minimal ether amount reached") } - if (!pendingTransactions()) { console.log("=== no pending transactions") } - console.log("=== stop mining"); - miner_var.stop(); - } -}, 1000) + var start_periodic_mining = function (config, miner_obj) { + var last_mined_ms = Date.now(); + var timeout_set = false; + miner_obj.start(config.threads); + web3.eth.filter("latest").watch(function () { + if ((config.mine_pending_txns && pendingTransactions()) || timeout_set) { + return; + } + + timeout_set = true; + + var now = Date.now(); + var ms_since_block = now - last_mined_ms; + last_mined_ms = now; + + var next_block_in_ms; + + if (ms_since_block > config.interval_ms) { + next_block_in_ms = 0; + } else { + next_block_in_ms = (config.interval_ms - ms_since_block); + } + + miner_obj.stop(); + console.log("== Looking for next block in " + next_block_in_ms + "ms"); + + setTimeout(function () { + console.log("== Looking for next block"); + timeout_set = false; + miner_obj.start(config.threads); + }, next_block_in_ms); + }); + }; + + var start_transaction_mining = function (config, miner_obj) { + web3.eth.filter("pending").watch(function () { + if (miner_obj.hashrate > 0) return; + + console.log("== Pending transactions! Looking for next block..."); + miner_obj.start(config.threads); + }); + + if (config.mine_periodically) return; + + web3.eth.filter("latest").watch(function () { + if (!pendingTransactions()) { + console.log("== No transactions left. Stopping miner..."); + miner_obj.stop(); + } + }); + }; + + main(); +})(); diff --git a/lib/blockchain.js b/lib/blockchain.js index 5d6a91d0..9ab37a5c 100644 --- a/lib/blockchain.js +++ b/lib/blockchain.js @@ -9,6 +9,7 @@ Blockchain.prototype.generate_basic_command = function() { var address = config.account.address; var cmd = "geth "; + var rpc_api = ['eth', 'web3']; if (config.datadir !== "default") { cmd += "--datadir=\"" + config.datadir + "\" "; @@ -31,6 +32,13 @@ Blockchain.prototype.generate_basic_command = function() { cmd += "--genesis=\"" + config.genesisBlock + "\" "; } + if (config.whisper) { + cmd += "--shh "; + rpc_api.push('shh') + } + + cmd += '--rpcapi "' + rpc_api.join(',') + '" '; + //TODO: this should be configurable cmd += "--maxpeers " + config.maxPeers + " "; @@ -49,6 +57,10 @@ Blockchain.prototype.init_command = function() { return this.generate_basic_command() + "account new "; } +Blockchain.prototype.geth_command = function(geth_args) { + return this.generate_basic_command() + geth_args; +} + Blockchain.prototype.run_command = function(address, use_tmp) { var cmd = this.generate_basic_command(); var config = this.config; @@ -108,4 +120,15 @@ Blockchain.prototype.startChain = function(use_tmp) { exec(this.run_command(address, use_tmp)); } +Blockchain.prototype.execGeth = function(args) { + var cmd = this.geth_command(args); + console.log("executing: " + cmd); + exec(cmd); +} + +Blockchain.prototype.getStartChainCommand = function(use_tmp) { + var address = this.get_address(); + return this.run_command(address, use_tmp); +} + module.exports = Blockchain diff --git a/lib/chain_manager.js b/lib/chain_manager.js index d63c8b85..edc48bbd 100644 --- a/lib/chain_manager.js +++ b/lib/chain_manager.js @@ -6,6 +6,7 @@ ChainManager = function() { this.chainManagerConfig = {}; this.currentChain = {}; this.file = ""; + this.web3 = null; } ChainManager.prototype.loadConfigFile = function(filename) { @@ -34,6 +35,7 @@ ChainManager.prototype.init = function(env, config) { } this.currentChain = this.chainManagerConfig[chainId]; + this.web3 = web3; } ChainManager.prototype.addContract = function(contractName, code, args, address) { diff --git a/lib/compiler.js b/lib/compiler.js index e57af716..7a98aac1 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,3 +1,5 @@ +var shelljs = require('shelljs'); +var shelljs_global = require('shelljs/global'); var web3 = require('web3'); Compiler = function(blockchainConfig) { @@ -18,8 +20,85 @@ Compiler.prototype.init = function(env) { console.log("address is : " + primaryAddress); }; -Compiler.prototype.compile = function(source) { - return web3.eth.compile.solidity(source); +Compiler.prototype.compile_solidity = function(contractFile) { + var cmd, result, output, json, compiled_object; + + cmd = "solc --input-file " + contractFile + " --combined-json binary,json-abi"; + + result = exec(cmd, {silent: true}); + output = result.output; + + if (result.code === 1) { + throw new Error(result.output); + } + + json = JSON.parse(output).contracts; + compiled_object = {} + + for (var className in json) { + var contract = json[className]; + + compiled_object[className] = {}; + compiled_object[className].code = contract.binary; + compiled_object[className].info = {}; + compiled_object[className].info.abiDefinition = JSON.parse(contract["json-abi"]); + } + + return compiled_object; +} + +Compiler.prototype.compile_serpent = function(contractFile) { + var cmd, result, output, json, compiled_object; + + cmd = "serpent compile " + contractFile; + + result = exec(cmd, {silent: true}); + code = result.output; + + if (result.code === 1) { + throw new Error(result.output); + } + + cmd = "serpent mk_full_signature " + contractFile; + result = exec(cmd, {silent: true}); + + if (result.code === 1) { + throw new Error(result.output); + } + + json = JSON.parse(result.output.trim()); + className = contractFile.split('.')[0].split("/").pop(); + + for (var i=0; i < json.length; i++) { + var elem = json[i]; + + if (elem.outputs.length > 0) { + elem.constant = true; + } + } + + compiled_object = {} + compiled_object[className] = {}; + compiled_object[className].code = code.trim(); + compiled_object[className].info = {}; + compiled_object[className].info.abiDefinition = json; + + return compiled_object; +} + + +Compiler.prototype.compile = function(contractFile) { + var extension = contractFile.split('.')[1]; + + if (extension === 'sol') { + return this.compile_solidity(contractFile); + } + else if (extension === 'se') { + return this.compile_serpent(contractFile); + } + else { + throw new Error("extension not known"); + } }; module.exports = Compiler; diff --git a/lib/config/blockchain.js b/lib/config/blockchain.js index 935ba130..15323a9a 100644 --- a/lib/config/blockchain.js +++ b/lib/config/blockchain.js @@ -40,11 +40,13 @@ BlockchainConfig.prototype.config = function(env) { minerthreads: config.minerthreads, genesisBlock: config.genesis_block, datadir: config.datadir, + chains: config.chains, networkId: networkId, maxPeers: 4, port: config.port || "30303", console_toggle: config.console || false, mine_when_needed: config.mine_when_needed || false, + whisper: config.whisper || false, account: config.account } diff --git a/lib/config/contracts.js b/lib/config/contracts.js index 5a42e00a..0e74e1d4 100644 --- a/lib/config/contracts.js +++ b/lib/config/contracts.js @@ -79,9 +79,8 @@ ContractsConfig.prototype.compileContracts = function(env) { // compile files for (j = 0; j < this.contractFiles.length; j++) { contractFile = this.contractFiles[j]; - source = fs.readFileSync(contractFile).toString() - compiled_contracts = this.compiler.compile(source); + compiled_contracts = this.compiler.compile(contractFile); for (var className in compiled_contracts) { var contract = compiled_contracts[className]; @@ -133,6 +132,11 @@ ContractsConfig.prototype.compileContracts = function(env) { contract.types.push('static'); } + contract.deploy = contractConfig.deploy; + if (contractConfig.deploy === undefined) { + contract.deploy = true; + } + if (this.all_contracts.indexOf(className) < 0) { this.all_contracts.push(className); } diff --git a/lib/deploy.js b/lib/deploy.js index 899a8822..e1c84709 100644 --- a/lib/deploy.js +++ b/lib/deploy.js @@ -58,6 +58,11 @@ Deploy.prototype.deploy_contracts = function(env) { className = all_contracts[k]; contract = this.contractDB[className]; + if (contract.deploy === false) { + console.log("skipping " + className); + continue; + } + var realArgs = []; for (var l = 0; l < contract.args.length; l++) { arg = contract.args[l]; @@ -77,7 +82,7 @@ Deploy.prototype.deploy_contracts = function(env) { else { var chainContract = this.chainManager.getContract(className, contract.compiled.code, realArgs); - if (chainContract != undefined) { + if (chainContract != undefined && web3.eth.getCode(chainContract.address) !== "0x") { console.log("contract " + className + " is unchanged and already deployed at " + chainContract.address); this.deployedContracts[className] = chainContract.address; this.execute_cmds(contract.onDeploy); @@ -110,13 +115,12 @@ Deploy.prototype.deploy_contracts = function(env) { } else { console.log("deployed " + className + " at " + contractAddress); + this.chainManager.addContract(className, contract.compiled.code, realArgs, contractAddress); + this.chainManager.save(); } this.deployedContracts[className] = contractAddress; - this.chainManager.addContract(className, contract.compiled.code, realArgs, contractAddress); - this.chainManager.save(); - console.log("deployed " + className + " at " + contractAddress); this.execute_cmds(contract.onDeploy); } } @@ -125,7 +129,7 @@ Deploy.prototype.deploy_contracts = function(env) { }; Deploy.prototype.execute_cmds = function(cmds) { - if (cmds.length === 0) return; + if (cmds == undefined || cmds.length === 0) return; eval(this.generate_abi_file()); for (var i = 0; i < cmds.length; i++) { diff --git a/lib/index.js b/lib/index.js index 95618130..fcc82d3b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -35,6 +35,16 @@ Embark = { chain.startChain(use_tmp); }, + copyMinerJavascriptToTemp: function(){ + //TODO: better with --exec, but need to fix console bug first + wrench.copyDirSyncRecursive(__dirname + "/../js", "/tmp/js", {forceDelete: true}); + }, + + getStartBlockchainCommand: function(env, use_tmp) { + var chain = new Blockchain(this.blockchainConfig.config(env)); + return chain.getStartChainCommand(use_tmp); + }, + deployContracts: function(env, contractFiles, destFile, chainFile) { this.contractsConfig.init(contractFiles, env); @@ -44,8 +54,12 @@ Embark = { return deploy.generate_abi_file(destFile); }, + geth: function(env, args) { + var chain = new Blockchain(this.blockchainConfig.config(env)); + chain.execGeth(args); + }, + release: Release } module.exports = Embark; - diff --git a/package.json b/package.json index cf0d2514..2f8517ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "embark-framework", - "version": "0.8.4", + "version": "0.9.1", "description": "", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/test/blockchain.js b/test/blockchain.js index 3f3cca23..f6414d00 100644 --- a/test/blockchain.js +++ b/test/blockchain.js @@ -10,7 +10,7 @@ describe('embark.blockchain', function() { var blockchain = new Blockchain(blockchainConfig); it('should return correct cmd', function() { - assert.strictEqual(blockchain.generate_basic_command(), "geth --datadir=\"/tmp/embark\" --logfile=\"/tmp/embark.log\" --port 30303 --rpc --rpcport 8101 --networkid "+blockchainConfig.networkId+" --rpccorsdomain \"*\" --minerthreads \"1\" --mine --genesis=\"config/genesis.json\" --maxpeers 4 --password config/password "); + assert.strictEqual(blockchain.generate_basic_command(), "geth --datadir=\"/tmp/embark\" --logfile=\"/tmp/embark.log\" --port 30303 --rpc --rpcport 8101 --rpcaddr localhost --networkid "+blockchainConfig.networkId+" --rpccorsdomain \"*\" --minerthreads \"1\" --mine --genesis=\"config/genesis.json\" --rpcapi \"eth,web3\" --maxpeers 4 --password config/password "); }); }); diff --git a/test/compiler.js b/test/compiler.js new file mode 100644 index 00000000..9e3761df --- /dev/null +++ b/test/compiler.js @@ -0,0 +1,69 @@ +var Compiler = require('../lib/compiler.js'); +var assert = require('assert'); + +describe('embark.compiler', function() { + + describe('compile a file', function() { + var files = [ + 'test/support/contracts/simple_storage.sol' + ]; + + it("should build a correct compiled object", function() { + var compiler = new Compiler(); + + var compiledFile = compiler.compile(files[0]); + + assert.equal(compiledFile.SimpleStorage.code, '606060405260405160208060f78339016040526060805190602001505b806000600050819055505b5060c28060356000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd914604b57806360fe47b114606a5780636d4ce63c14607b576049565b005b605460045060b9565b6040518082815260200191505060405180910390f35b6079600480359060200150609a565b005b608460045060a8565b6040518082815260200191505060405180910390f35b806000600050819055505b50565b6000600060005054905060b6565b90565b6000600050548156'); + + assert.equal(JSON.stringify(compiledFile.SimpleStorage.info.abiDefinition), '[{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"type":"function"},{"inputs":[{"name":"initialValue","type":"uint256"}],"type":"constructor"}]'); + }); + + }); + + describe('compile a file with an error', function() { + var files = [ + 'test/support/contracts/error.sol' + ]; + + it("throw an error", function() { + var compiler = new Compiler(); + + assert.throws(function() { compiler.compile(files[0]) }, Error); + }); + + }); + + describe('compile a serpent file', function() { + var files = [ + 'test/support/contracts/cash.se' + ]; + + it("should build a correct compiled object", function() { + var compiler = new Compiler(); + + var compiledFile = compiler.compile(files[0]); + + assert.equal(compiledFile.cash.code, '6000603f536a0186a000000000000000006040604059905901600090526000815232816020015280905020556103658061003a60003961039f56600061047f537c010000000000000000000000000000000000000000000000000000000060003504638357984f81141561005f57600435604052604060405990590160009052600081526040518160200152809050205460605260206060f35b63693200ce8114156101465760043560a05260243560c0523260e0526040604059905901600090526000815260e051816020015280905020546101005260c051610100511215156101385760c0516040604059905901600090526000815260e05181602001528090502054036040604059905901600090526000815260e0518160200152809050205560c0516040604059905901600090526000815260a05181602001528090502054016040604059905901600090526000815260a0518160200152809050205560c0516101c05260206101c0f3610145565b60006101e05260206101e0f35b5b6380b97fc081141561024c5760043560a05260243560c05260443561020052326102005114151561017e576000610220526020610220f35b6040604059905901600090526000815261020051816020015280905020546101005260c0516101005112151561023e5760c0516040604059905901600090526000815261020051816020015280905020540360406040599059016000905260008152610200518160200152809050205560c0516040604059905901600090526000815260a05181602001528090502054016040604059905901600090526000815260a0518160200152809050205560c0516102e05260206102e0f361024b565b6000610300526020610300f35b5b634c764abc8114156102b4576004356103205260243561034052610340516040604059905901600090526000815261032051816020015280905020540360406040599059016000905260008152610320518160200152809050205560016103a05260206103a0f35b63a92c9b8381141561031c57600435610320526024356103405261034051604060405990590160009052600081526103205181602001528090502054016040604059905901600090526000815261032051816020015280905020556001610400526020610400f35b631d62e92281141561036357600435604052602435610420526104205160406040599059016000905260008152604051816020015280905020556001610460526020610460f35b505b6000f3'); + + assert.equal(JSON.stringify(compiledFile.cash.info.abiDefinition), '[{\"name\":\"addCash(int256,int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"ID\",\"type\":\"int256\"},{\"name\":\"amount\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true},{\"name\":\"balance(int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"address\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true},{\"name\":\"send(int256,int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"recver\",\"type\":\"int256\"},{\"name\":\"value\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true},{\"name\":\"sendFrom(int256,int256,int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"recver\",\"type\":\"int256\"},{\"name\":\"value\",\"type\":\"int256\"},{\"name\":\"from\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true},{\"name\":\"setCash(int256,int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"address\",\"type\":\"int256\"},{\"name\":\"balance\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true},{\"name\":\"subtractCash(int256,int256)\",\"type\":\"function\",\"inputs\":[{\"name\":\"ID\",\"type\":\"int256\"},{\"name\":\"amount\",\"type\":\"int256\"}],\"outputs\":[{\"name\":\"out\",\"type\":\"int256\"}],\"constant\":true}]'); + + }); + + }); + + describe('compile a file with an error', function() { + var files = [ + 'test/support/contracts/error.sol' + ]; + + it("throw an error", function() { + var compiler = new Compiler(); + + assert.throws(function() { compiler.compile(files[0]) }, Error); + }); + + }); + + +}); + diff --git a/test/config.blockchain.js b/test/config.blockchain.js index fa86e16e..d9db6129 100644 --- a/test/config.blockchain.js +++ b/test/config.blockchain.js @@ -43,6 +43,7 @@ describe('embark.config.blockchain', function() { minerthreads: 1, genesis_block: 'config/genesis.json', datadir: '/tmp/embark', + chains: 'chains_development.json', mine_when_needed: true, gas_limit: 123, gas_price: 100, @@ -63,9 +64,11 @@ describe('embark.config.blockchain', function() { gasLimit: 123, gasPrice: 100, rpcWhitelist: "*", + whisper: false, minerthreads: 1, genesisBlock: 'config/genesis.json', datadir: '/tmp/embark', + chains: 'chains_development.json', networkId: 0, maxPeers: 4, port: "30303", @@ -87,12 +90,13 @@ describe('embark.config.blockchain', function() { network_id: 0, minerthreads: 1, datadir: '/tmp/embark', + chains: undefined, mine_when_needed: true, console: false, account: { init: true, password: 'config/password' - } + }, }, staging: {} }; @@ -105,9 +109,11 @@ describe('embark.config.blockchain', function() { gasLimit: 500000, gasPrice: 10000000000000, rpcWhitelist: "*", + whisper: false, minerthreads: 1, genesisBlock: undefined, datadir: '/tmp/embark', + chains: undefined, networkId: 0, maxPeers: 4, port: "30303", diff --git a/test/deploy.js b/test/deploy.js index 7e7fd6b2..e5c1fc46 100644 --- a/test/deploy.js +++ b/test/deploy.js @@ -110,12 +110,13 @@ describe('embark.deploy', function() { deploy.deploy_contracts("development"); it("should deploy contracts", function() { - var all_contracts = ['SimpleStorage', 'BarStorage', 'FooStorage']; + var all_contracts = ['BarStorage', 'FooStorage']; for(var i=0; i < all_contracts.length; i++) { var className = all_contracts[i]; assert.equal(deploy.deployedContracts.hasOwnProperty(className), true); } + assert.notEqual(deploy.deployedContracts.hasOwnProperty('SimpleStorage'), true); }); }); diff --git a/test/support/contracts/cash.se b/test/support/contracts/cash.se new file mode 100644 index 00000000..c70f0c57 --- /dev/null +++ b/test/support/contracts/cash.se @@ -0,0 +1,75 @@ +# This software (Augur) allows buying && selling event outcomes in ethereum +# Copyright (C) 2015 Forecast Foundation +# This program is free software; you can redistribute it &&/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is free software: you can redistribute it &&/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# Any questions please contact joey@augur.net + +data cashcoinBalances[] + +def init(): + # test initial funds + self.cashcoinBalances[tx.origin] = 100000*2^64 + +# @return: cash balance of address +def balance(address): + return(self.cashcoinBalances[address]) + +# should send values as fixed point in UI (1 is 2^64, 4 is 4*2^64, .5 is 2^63, etc.) +# so cashcoin fees could just go to root branch, or we could not have fees besides +# gas fee to do a send transaction +# @return: value sent, 0 if fails +def send(recver, value): + sender = tx.origin + senderBalance = self.cashcoinBalances[sender] + if(senderBalance >= value): + self.cashcoinBalances[sender] -= value + self.cashcoinBalances[recver] += value + return(value) + else: + return(0) + +# @return value of cash sent; fail is 0 +def sendFrom(recver, value, from): + if(from!=tx.origin): + return(0) + senderBalance = self.cashcoinBalances[from] + if(senderBalance >= value): + self.cashcoinBalances[from] -= value + self.cashcoinBalances[recver] += value + return(value) + else: + return(0) + +# make sure only coming from specific contracts +def subtractCash(ID, amount): + #if(!self.whitelist.check(msg.sender)): + # return(-1) + self.cashcoinBalances[ID] -= amount + return(1) + +def addCash(ID, amount): + #if(!self.whitelist.check(msg.sender)): + # return(-1) + self.cashcoinBalances[ID] += amount + return(1) + +def setCash(address, balance): + #if !self.whitelist.check(msg.sender): + # return(-1) + self.cashcoinBalances[address] = balance + return(1) diff --git a/test/support/contracts/error.sol b/test/support/contracts/error.sol new file mode 100644 index 00000000..c15c5158 --- /dev/null +++ b/test/support/contracts/error.sol @@ -0,0 +1,14 @@ +contract SimpleStorage { + uint public storedData; + + function SimpleStorage(uint initialValue) { + storedData2 = initialValue; + } + + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/test/support/instances.yml b/test/support/instances.yml index 04e15644..04172a48 100644 --- a/test/support/instances.yml +++ b/test/support/instances.yml @@ -1,5 +1,6 @@ development: SimpleStorage: + deploy: false args: - 100 BarStorage: