From 4ae4d4055bf425afa1777375b09df08edea4e8cd Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 20 Apr 2016 13:55:48 -0700 Subject: [PATCH 01/26] Expect `truffle exec` files to be modules instead of scripts. This reduces complexity significantly and removes the process.exit() ugliness. --- lib/config.js | 11 +++----- lib/exec.js | 59 ++++++++++++------------------------------ lib/require-nocache.js | 6 +++++ 3 files changed, 25 insertions(+), 51 deletions(-) create mode 100644 lib/require-nocache.js diff --git a/lib/config.js b/lib/config.js index 8aefc769eb9..ed5c5855675 100644 --- a/lib/config.js +++ b/lib/config.js @@ -11,6 +11,7 @@ var Exec = require("./exec"); var ConfigurationError = require('./errors/configurationerror'); var Pudding = require("ether-pudding"); var PuddingLoader = require("ether-pudding/loader"); +var requireNoCache = require("./require-nocache"); var Config = { gather: function(truffle_dir, working_dir, argv, desired_environment) { @@ -94,12 +95,6 @@ var Config = { process.exit(); } - config.requireNoCache = function(filePath) { - //console.log("Requring w/o cache: " + path.resolve(filePath)); - delete require.cache[path.resolve(filePath)]; - return require(filePath); - }; - desired_environment = argv.e || argv.environment || process.env.NODE_ENV || desired_environment; if (desired_environment) { @@ -140,7 +135,7 @@ var Config = { // Load the app config. // For now, support both new and old config files. if (fs.existsSync(config.app.configfile)) { - _.merge(config.app.resolved, config.requireNoCache(config.app.configfile)); + _.merge(config.app.resolved, requireNoCache(config.app.configfile)); } else if (fs.existsSync(config.app.oldconfigfile)) { config.app.resolved = loadconf(config.app.oldconfigfile, config.app.resolved); } @@ -152,7 +147,7 @@ var Config = { // Load environment config if (fs.existsSync(config.environments.current.filename)) { - _.merge(config.app.resolved, config.requireNoCache(config.environments.current.filename)); + _.merge(config.app.resolved, requireNoCache(config.environments.current.filename)); } else if (fs.existsSync(config.environments.current.oldfilename)) { config.app.resolved = loadconf(config.environments.current.oldfilename, config.app.resolved); } diff --git a/lib/exec.js b/lib/exec.js index f65786bb03e..391d40c321c 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -1,13 +1,8 @@ -var fs = require("fs"); -var m = require("module"); var path = require("path"); -var vm = require("vm"); - +var requireNoCache = require("./require-nocache"); var Pudding = require("ether-pudding"); var PuddingLoader = require("ether-pudding/loader"); -var _ = require("lodash"); - var Exec = { file: function(config, file, done) { var self = this; @@ -29,8 +24,6 @@ var Exec = { gas: 3141592 }); - var sandbox = {}; - var old_cwd = process.cwd(); var old_dirname = __dirname; @@ -38,50 +31,30 @@ var Exec = { process.chdir(config.working_dir); __dirname = process.cwd(); - var script_over = function(err) { + var cleanup = function() { process.chdir(old_cwd); __dirname = old_dirname; - done(err); - }; - - var new_process = _.merge({}, process); - new_process.exit = function(exit_code) { - if (exit_code != null && exit_code != 0) { - script_over(new Error("Script " + file + " exited with non-zero exit code: " + exit_code)); - } else { - script_over(); - } }; - // Create a sandbox that looks just like the global scope. - sandbox = _.merge(sandbox, global, { - web3: config.web3, - Pudding: Pudding, - process: new_process, - require: function(name) { - if (name.indexOf("./") == 0 || name.indexOf("../") == 0) { - return require(config.working_dir + name); - } else if (fs.existsSync(config.working_dir + "node_modules/" + name)) { - return require(config.working_dir + "node_modules/" + name) - } else { - return require(name); - } - }, - module: m, - __filename: file, - __dirname: __dirname - }); - - PuddingLoader.load(config.environments.current.directory, Pudding, sandbox, function(err) { + PuddingLoader.load(config.environments.current.directory, Pudding, global, function(err) { if (err != null) { + cleanup(); done(err); return; } - var context = vm.createContext(sandbox); - var code = fs.readFileSync(file); - var script = new vm.Script(code, { filename: file}); - script.runInContext(context); + var script = requireNoCache(path.resolve(file)); + + // Callback is always the last argument. + var args = [accounts, config.web3, Pudding]; + args = args.slice(0, script.length - 1); + + args.push(function(err) { + cleanup(); + done(err); + }); + + script.apply(script, args); }); }); } diff --git a/lib/require-nocache.js b/lib/require-nocache.js new file mode 100644 index 00000000000..04c5cfc7300 --- /dev/null +++ b/lib/require-nocache.js @@ -0,0 +1,6 @@ +var path = require("path"); + +module.exports = function(filePath) { + delete require.cache[path.resolve(filePath)]; + return require(filePath); +}; From 35fac07b5d06dbcca162a491955fc6aa9e388b05 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 13 May 2016 00:21:02 -0700 Subject: [PATCH 02/26] Basis for migrations. Lots of refactoring across the board. Warning: This commit relies on the development version of ether-pudding, which isn't released yet. --- cli.js | 32 +++++- index.js | 13 ++- lib/compiler.js | 133 ++++++++++++++++++++++++ lib/config.js | 48 +-------- lib/contracts.js | 258 +++++------------------------------------------ lib/deployer.js | 87 ++++++++++++++++ lib/exec.js | 92 ++++++++--------- lib/linker.js | 61 +++++++++++ lib/migrate.js | 111 ++++++++++++++++++++ lib/profiler.js | 202 +++++++++++++++++++++++++++++++++++++ package.json | 1 + 11 files changed, 705 insertions(+), 333 deletions(-) create mode 100644 lib/compiler.js create mode 100644 lib/deployer.js create mode 100644 lib/linker.js create mode 100644 lib/migrate.js create mode 100644 lib/profiler.js diff --git a/cli.js b/cli.js index a446c95af67..9b81fb36a27 100755 --- a/cli.js +++ b/cli.js @@ -178,7 +178,13 @@ registerTask('create:test', "Create a basic test", function(done) { registerTask('compile', "Compile contracts", function(done) { var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); - Truffle.contracts.compile(config, done); + Truffle.contracts.compile({ + all: config.argv.allContracts === true, + source_directory: config.contracts.directory, + build_directory: config.contracts.build_directory, + quiet: config.argv.quiet === true, + strict: config.argv.strict === true + }, done); }); registerTask('deploy', "Deploy contracts to the network, compiling if needed", function(done) { @@ -226,6 +232,20 @@ registerTask('dist', "Create distributable version of app (minified)", function( }); }); +registerTask('migrate', "Run migrations", function(done) { + var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + + console.log("Using environment " + config.environment + "."); + + + + Truffle.migrate.runAll({ + migrations_directory: config.migrations.directory, + contracts_directory: config.contracts.build_directory, + provider: config.web3.currentProvider + }, done); +}); + registerTask('exec', "Execute a JS file within truffle environment. Script *must* call process.exit() when finished.", function(done) { var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); @@ -241,7 +261,15 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must return; } - Truffle.exec.file(config, file, done); + if (path.isAbsolute(file) == false) { + file = path.join(config.working_dir, file); + } + + Truffle.exec.file({ + file: file, + web3: config.web3, + contracts: config.contracts.built_files + }, done); }); // Supported options: diff --git a/index.js b/index.js index 74c3762b442..9b4a48f34a7 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,14 @@ module.exports = { - init: require("./lib/init"), + build: require("./lib/build"), create: require("./lib/create"), + compiler: require("./lib/compiler"), config: require("./lib/config"), + console: require("./lib/repl"), contracts: require("./lib/contracts"), - build: require("./lib/build"), - test: require("./lib/test"), exec: require("./lib/exec"), - console: require("./lib/repl"), - serve: require("./lib/serve") + init: require("./lib/init"), + migrate: require("./lib/migrate"), + profile: require("./lib/profiler"), + serve: require("./lib/serve"), + test: require("./lib/test") }; diff --git a/lib/compiler.js b/lib/compiler.js new file mode 100644 index 00000000000..0ffc14f3043 --- /dev/null +++ b/lib/compiler.js @@ -0,0 +1,133 @@ +var solc = require("solc"); +var path = require("path"); +var fs = require("fs"); +var async = require("async"); +var Profiler = require("./profiler"); +var CompileError = require("./errors/compileerror"); + +module.exports = { + // source_directory: String. Directory where .sol files can be found. + // quiet: Boolean. Suppress output. Defaults to false. + // strict: Boolean. Return compiler warnings as errors. Defaults to false. + compile_all: function(options, callback) { + var self = this; + Profiler.all_contracts(options.source_directory, function(err, files) { + options.files = files; + self.compile_with_dependencies(options, callback); + }); + }, + + // source_directory: String. Directory where .sol files can be found. + // build_directory: String. Optional. Directory where .sol.js files can be found. Only required if `all` is false. + // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files + // in the build directory to see what needs to be compiled. + // quiet: Boolean. Suppress output. Defaults to false. + // strict: Boolean. Return compiler warnings as errors. Defaults to false. + compile_necessary: function(options, callback) { + var self = this; + options.logger = options.logger || console; + + Profiler.updated(options, function(err, updated) { + if (err) return callback(err); + + if (updated.length == 0 && config.quiet != true) { + options.logger.log("No contracts updated; skipping compilation."); + return callback(); + } + + options.files = updated; + self.compile_with_dependencies(options, callback); + }); + }, + + // { + // files: [...], + // includes: { + // "Foo.sol": "contract Foo {}" // Example + // }, + // source_directory: "..." // or process.cwd() + // strict: false, + // quiet: false + // logger: console + // } + compile: function(options, callback) { + var files = options.files || []; + var includes = options.includes || {}; + var logger = options.logger || console; + var source_directory = options.source_directory || process.cwd(); + + var sources = {}; + + async.each(files, function(file, finished) { + fs.readFile(file, "utf8", function(err, body) { + if (err) return finished(err); + sources[path.relative(source_directory, file)] = body; + finished(); + }); + }, function() { + Object.keys(includes).forEach(function(key) { + sources[key] = includes[key]; + }); + + var result = solc.compile({sources: sources}, 1); + var errors = result.errors || []; + var warnings = result.errors || []; + + if (options.strict == true) { + errors = errors.filter(function(error) { + return error.indexOf("Warning:") < 0; + }); + warnings = warnings.filter(function(error) { + return error.indexOf("Warning:") >= 0; + }); + + if (options.quiet != null) { + warnings.forEach(function(warning) { + logger.log(warning); + }); + } + } + + if (errors.length > 0) { + return callback(new CompileError(result.errors.join())); + } + + // Examine the sources, and ensure the contract we expected was defined + // TODO: This forces contract names to be the same as their filename. This should go. + var filenames = Object.keys(sources); + for (var i = 0; i < filenames.length; i++) { + var filename = filenames[i]; + var expected_contract = path.basename(filename, ".sol"); + + if (result.contracts[expected_contract] == null) { + return callback(new CompileError("Could not find expected contract or library in '" + filename + "': contract or library '" + expected_contract + "' not found.")); + } + } + + callback(null, result.contracts); + }) + }, + + compile_with_dependencies: function(options, callback) { + options.files = options.files || []; + options.includes = options.includes || {}; + options.logger = options.logger || console; + options.source_directory = options.source_directory || process.cwd(); + + var self = this; + Profiler.required_files(options.files, function(err, files) { + if (err) return callback(err); + + files.sort().forEach(function(file) { + if (options.quiet != true) { + var relative = path.relative(options.source_directory, file) + options.logger.log("Compiling " + relative + "..."); + } + }); + + options.files = files; + + self.compile(options, callback); + }); + } +}; diff --git a/lib/config.js b/lib/config.js index ed5c5855675..e42fafcc9df 100644 --- a/lib/config.js +++ b/lib/config.js @@ -10,7 +10,6 @@ var path = require("path"); var Exec = require("./exec"); var ConfigurationError = require('./errors/configurationerror'); var Pudding = require("ether-pudding"); -var PuddingLoader = require("ether-pudding/loader"); var requireNoCache = require("./require-nocache"); var Config = { @@ -59,8 +58,12 @@ var Config = { contracts: { classes: {}, directory: path.join(working_dir, "contracts"), + built_files: [], build_directory: null }, + migrations: { + directory: path.join(working_dir, "migrations"), + }, tests: { directory: path.join(working_dir, "test"), filter: /.*\.(js|es|es6|jsx)$/ @@ -255,49 +258,6 @@ var Config = { } } - // Now merge those contracts with what's in the configuration, if any, using the loader. - Pudding.setWeb3(config.web3); - - // Functionalize this so we can make it synchronous. - function loadContracts(callback) { - if (fs.existsSync(config.contracts.build_directory) == false) { - return callback(); - } - - var contracts = {}; - PuddingLoader.load(config.contracts.build_directory, Pudding, contracts, function(err, names, data) { - if (err) return callback(err); - - data.forEach(function(item) { - var name = item.name; - - // Don't load a contract that's been deleted. - if (!config.contracts.classes[name]) { - return; - } - - var stats; - try { - var stats = fs.statSync(item.file); - } catch (e) { - return callback(e); - } - - var contract = contracts[name]; - config.contracts.classes[name].abi = contract.abi; - config.contracts.classes[name].binary = contract.binary; - config.contracts.classes[name].unlinked_binary = contract.unlinked_binary || contract.binary; - config.contracts.classes[name].address = contract.address; - config.contracts.classes[name].compiled_time = (stats.mtime || stats.ctime).getTime(); - }); - - callback(); - }); - }; - - var loader = deasync(loadContracts); - loader(); - return config; } } diff --git a/lib/contracts.js b/lib/contracts.js index 11e4af729e7..96151a26278 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -4,17 +4,15 @@ var mkdirp = require("mkdirp"); var path = require("path"); var solc = require("solc"); var path = require("path"); +var Compiler = require("./compiler"); var Exec = require("./exec"); var Pudding = require("ether-pudding"); -var PuddingGenerator = require("ether-pudding/generator"); -var ConfigurationError = require("./errors/configurationerror"); -var CompileError = require("./errors/compileerror"); var DeployError = require("./errors/deployerror"); var graphlib = require("graphlib"); var Graph = require("graphlib").Graph; var isAcyclic = require("graphlib/lib/alg").isAcyclic; var postOrder = require("graphlib/lib/alg").postorder; - +var requireNoCache = require("./require-nocache"); var Contracts = { account: null, @@ -38,178 +36,26 @@ var Contracts = { }); }, - update_sources: function(config, callback) { - var contract_names = Object.keys(config.contracts.classes); - async.each(contract_names, function(name, done) { - var contract = config.contracts.classes[name]; - fs.readFile(contract.file, {encoding: "utf8"}, function(err, body) { - if (err) return done(err); - - contract.body = body; - done(); - }); - }, callback); - }, - - compile_necessary: function(config, callback) { + // source_directory: String. Directory where .sol files can be found. + // build_directory: String. Directory where .sol.js files can be found and written to. + // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files + // in the build directory to see what needs to be compiled. + // network_id: network id to link saved contract artifacts. + // quiet: Boolean. Suppress output. Defaults to false. + // strict: Boolean. Return compiler warnings as errors. Defaults to false. + compile: function(options, callback) { var self = this; - this.update_sources(config, function() { - var contract_names = Object.keys(config.contracts.classes); - - var sources = {}; - var updated = {}; - var included = {}; - - for (var i = 0; i < contract_names.length; i++) { - var name = contract_names[i]; - var contract = config.contracts.classes[name]; - - if (contract.source_modified_time > contract.compiled_time || config.argv.compileAll === true) { - updated[name] = true; - } - } - - if (Object.keys(updated).length == 0 && config.argv.quietDeploy == null) { - console.log("No contracts updated; skipping compilation."); - return callback(); - } - - var dependsGraph = self.build_compile_dependency_graph(config, callback); - - if (dependsGraph == null) { - return; - } - - function is_updated(contract_name) { - return updated[contract_name] === true; - } - - function include_source_for(contract_name) { - var contract = config.contracts.classes[contract_name]; - var source_path = path.relative(config.contracts.directory, contract.source); - - if (sources[source_path] != null) { - return; - } - - var full_path = path.resolve(config.working_dir, contract.source) - sources[source_path] = fs.readFileSync(full_path, {encoding: "utf8"}); - - // For graph traversing - included[contract_name] = true; - } - - function walk_down(contract_name) { - if (included[contract_name] === true) { - return; - } - - include_source_for(contract_name); - - var dependencies = dependsGraph.successors(contract_name); - - // console.log("At: " + contract_name); - // console.log(" Dependencies: ", dependencies); - - if (dependencies.length > 0) { - dependencies.forEach(walk_down); - } - } - - function walk_from(contract_name) { - // if (included[contract_name] === true) { - // return; - // } - - var ancestors = dependsGraph.predecessors(contract_name); - var dependencies = dependsGraph.successors(contract_name); - - // console.log("At: " + contract_name); - // console.log(" Ancestors: ", ancestors); - // console.log(" Dependencies: ", dependencies); - - include_source_for(contract_name); - - if (ancestors.length > 0) { - ancestors.forEach(walk_from); - } - - if (dependencies.length > 0) { - dependencies.forEach(walk_down); - } - } - - Object.keys(updated).forEach(walk_from); - - Object.keys(sources).sort().forEach(function(file_path) { - if (config.argv.quietDeploy == null) { - console.log("Compiling " + file_path + "..."); - } - }); - - var result = solc.compile({sources: sources}, 1); - var errors = result.errors || []; - var warnings = result.errors || []; - - if (!config.argv.strict) { - errors = errors.filter(function(error) { - return error.indexOf("Warning:") < 0; - }); - warnings = warnings.filter(function(error) { - return error.indexOf("Warning:") >= 0; - }); - - if (config.argv.quietDeploy == null) { - warnings.forEach(function(warning) { - console.log(warning); - }); - } - } - - if (errors.length > 0) { - return callback(new CompileError(result.errors.join())); - } - - // Examine the sources, and ensure the contract we expected was defined - var filenames = Object.keys(sources); - for (var i = 0; i < filenames.length; i++) { - var filename = filenames[i]; - var expected_contract = path.basename(filename, ".sol"); - - if (result.contracts[expected_contract] == null) { - return callback(new CompileError("Could not find expected contract or library in '" + filename + "': contract or library '" + expected_contract + "' not found.")); - } - } - - for (var i = 0; i < contract_names.length; i++) { - var name = contract_names[i]; - var contract = config.contracts.classes[name]; - var compiled_contract = result.contracts[name]; - - // If we didn't compile this contract this run, continue. - if (compiled_contract == null) { - continue; - } - contract.binary = compiled_contract.bytecode; - contract.unlinked_binary = compiled_contract.bytecode; - contract.abi = JSON.parse(compiled_contract.interface); - } - - callback(); - }); - }, + function finished(err, contracts) { + if (err) return callback(err); + self.write_contracts(contracts, options, callback); + }; - compile: function(config, callback) { - var self = this; - async.series([ - function(c) { - self.compile_necessary(config, c); - }, - function(c) { - self.write_contracts(config, "contracts", c); - } - ], callback); + if (options.all == false) { + Compiler.compile_necessary(options, finished); + } else { + Compiler.compile_all(options, finished); + } }, createContractAndWait: function(config, contract_name) { @@ -252,55 +98,6 @@ var Contracts = { }); }, - build_compile_dependency_graph: function(config, errorCallback) { - if (config.argv.quietDeploy == null) { - console.log("Checking sources..."); - } - // Iterate through all the contracts looking for libraries and building a dependency graph - var dependsGraph = new Graph(); - var contract_names = Object.keys(config.contracts.classes); - for (var i = 0; i < contract_names.length; i++) { - var name = contract_names[i] - var contract = config.contracts.classes[name]; - - if (contract == null) { - errorCallback(new CompileError("Could not find contract '" + name + "' for compiling. Check truffle.json.")); - return null; - } - - // Add the contract to the depend graph - dependsGraph.setNode(name); - - // Find import statements and resolve those import paths, adding them to the graph. - contract.body.split(/;|\n/).filter(function(line) { - return line.indexOf("import") >= 0; - }).forEach(function(line) { - var regex = /import.*("|')([^"']+)("|')*/g; - var match = regex.exec(line); - - if (match == null) return; - - var file = match[2]; - var dependency_name = path.basename(file, ".sol"); - - if (!dependsGraph.hasEdge(name, dependency_name)) { - dependsGraph.setEdge(name, dependency_name); - } - }); - } - // Check for cycles in the graph, the dependency graph needs to be a tree otherwise there's an error - if (!isAcyclic(dependsGraph)) - { - console.log("ERROR: Cycles in dependency graph"); - dependsGraph.edges().forEach(function(o){ - console.log(o.v+" -- depends on --> "+o.w); - }); - errorCallback(new CompileError("Found cyclic dependencies. Adjust your import statements to remove cycles.")); - return null; - } - return dependsGraph; - }, - build_deploy_dependency_graph: function(config, errorCallback) { if (config.argv.quietDeploy == null) { console.log("Collecting dependencies..."); @@ -482,25 +279,18 @@ var Contracts = { }, done); }, - write_contracts: function(config, description, callback) { - var destination = config.contracts.build_directory; - - description = description || "contracts"; - - mkdirp(destination, function(err, result) { + write_contracts: function(contracts, options, callback) { + mkdirp(options.build_directory, function(err, result) { if (err != null) { callback(err); return; } - var display_directory = "." + path.sep + path.relative(config.working_dir, destination); // path.join("./", destination.replace(config.working_dir, "")); - if (config.argv.quietDeploy == null) { - console.log("Writing " + description + " to " + display_directory); + if (options.quiet != true) { + console.log("Writing artifacts to ." + path.sep + path.relative(process.cwd(), options.build_directory)); } - PuddingGenerator.save(config.contracts.classes, destination, {removeExisting: true}); - - callback(); + Pudding.saveAll(contracts, options.build_directory, options).then(callback).catch(callback); }); } } diff --git a/lib/deployer.js b/lib/deployer.js new file mode 100644 index 00000000000..da4618398bc --- /dev/null +++ b/lib/deployer.js @@ -0,0 +1,87 @@ +var EventEmitter = require("events").EventEmitter; +var inherits = require("util").inherits; +var Linker = require("./linker"); + +inherits(Deployer, EventEmitter); + +function Deployer(options) { + Deployer.super_.call(this); + var self = this; + options = options || {}; + this.chain = new Promise(function(accept, reject) { + self._accept = accept; + self._reject = reject; + }); + this.logger = options.logger || console; + if (options.quiet) { + this.logger = {log: function() {}}; + } + this.known_contracts = {}; + (options.contracts || []).forEach(function(contract) { + self.known_contracts[contract.contract_name] = contract; + }) +}; + +// Note: In all code below we overwrite this.chain every time .then() is used +// in order to ensure proper error processing. + +Deployer.prototype.start = function() { + var self = this; + return new Promise(function(accept, reject) { + self.chain = self.chain.then(accept).catch(reject); + self._accept(); + }); +}; + +Deployer.prototype.autolink = function(contract) { + var self = this; + var regex = /__[^_]+_+/g; + + this.chain = this.chain.then(function() { + Linker.autolink(contract, self.known_contracts, self.logger); + }); +}; + +Deployer.prototype.link = function(library, destinations) { + var self = this; + + this.chain = this.chain.then(function() { + Linker.link(library, destinations, self.logger); + }); +}; + +Deployer.prototype.deploy = function() { + var self = this; + var args = Array.prototype.slice.call(arguments); + var contract = args.shift(); + self.chain = this.chain.then(function() { + self.logger.log("Deploying new instance of " + contract.contract_name + "..."); + return contract.new.apply(contract, args); + }).then(function(instance) { + self.logger.log("Saving deployed address: " + instance.address); + contract.address = instance.address; + }); + return self.chain; +}; + +Deployer.prototype.new = function() { + var self = this; + var args = Array.prototype.slice.call(arguments); + var contract = args.shift(); + this.chain = this.chain.then(function() { + self.logger.log("Creating new instance of " + contract.contract_name); + return contract.new.apply(contract, args) + }); + return this.chain; +}; + +Deployer.prototype.then = function(fn) { + var self = this; + this.chain = this.chain.then(function() { + self.logger.log("Running step..."); + return fn(); + }); + return this.chain; +} + +module.exports = Deployer; diff --git a/lib/exec.js b/lib/exec.js index 391d40c321c..cfa68bc8089 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -1,63 +1,59 @@ +var fs = require("fs"); var path = require("path"); +var Module = require('module'); +var vm = require('vm'); var requireNoCache = require("./require-nocache"); -var Pudding = require("ether-pudding"); -var PuddingLoader = require("ether-pudding/loader"); -var Exec = { - file: function(config, file, done) { +var Require = { + // options.file: path to file to execute. Must be a module that exports a function. + // options.args: arguments passed to the exported function within file. If a callback + // is not included in args, exported function is treated as synchronous. + // options.context: Object containing any global variables you'd like set when this + // function is run. + file: function(options, done) { var self = this; + var file = options.file; + options.context = options.context || {}; + + fs.readFile(options.file, {encoding: "utf8"}, function(err, source) { + if (err) return done(err); + + // Modified from here: https://gist.github.com/anatoliychakkaev/1599423 + var m = new Module(file); + + // Provide all the globals listed here: https://nodejs.org/api/globals.html + var context = { + Buffer: Buffer, + __dirname: path.dirname(file), + __filename: file, + clearImmediate: clearImmediate, + clearInterval: clearInterval, + clearTimeout: clearTimeout, + console: console, + exports: exports, + global: global, + module: m, + process: process, + require: require, + setImmediate: setImmediate, + setInterval: setInterval, + setTimeout: setTimeout, + }; - if (path.isAbsolute(file) == false) { - file = path.join(config.working_dir, file); - } - - config.web3.eth.getAccounts(function(error, accounts) { - if (error) { - done(error); - return; - } - - Pudding.setWeb3(config.web3); - - Pudding.defaults({ - from: accounts[0], - gas: 3141592 + // Now add contract names. + Object.keys(options.context).forEach(function(key) { + context[key] = options.context[key]; }); var old_cwd = process.cwd(); var old_dirname = __dirname; - // Change current working directory to that of the project. - process.chdir(config.working_dir); - __dirname = process.cwd(); - - var cleanup = function() { - process.chdir(old_cwd); - __dirname = old_dirname; - }; + var script = vm.createScript(source, file); + script.runInNewContext(context); - PuddingLoader.load(config.environments.current.directory, Pudding, global, function(err) { - if (err != null) { - cleanup(); - done(err); - return; - } - - var script = requireNoCache(path.resolve(file)); - - // Callback is always the last argument. - var args = [accounts, config.web3, Pudding]; - args = args.slice(0, script.length - 1); - - args.push(function(err) { - cleanup(); - done(err); - }); - - script.apply(script, args); - }); + done(null, m.exports); }); } } -module.exports = Exec +module.exports = Require; diff --git a/lib/linker.js b/lib/linker.js new file mode 100644 index 00000000000..3caa913fcdf --- /dev/null +++ b/lib/linker.js @@ -0,0 +1,61 @@ +module.exports = { + link: function(library, destinations, logger) { + var self = this; + + logger = logger || console; + + if (!Array.isArray(destinations)) { + destinations = [destinations]; + } + + var regex = new RegExp("__" + library.contract_name + "_*", "g"); + + if (library.contract_name == null) { + throw new Error("Cannot link a library with no name."); + } + + if (library.address == null) { + throw new Error("Cannot link library: " + library.contract_name + " has no address."); + } + + destinations.forEach(function(destination) { + logger.log("Linking " + library.contract_name + " to " + destination.contract_name); + destination.binary = destination.unlinked_binary.replace(regex, library.address); + }); + }, + + autolink: function(contract, available_contracts, logger) { + var self = this; + var regex = /__[^_]+_+/g; + + logger = logger || console; + + var unlinked_libraries = contract.unlinked_binary.match(regex); + + if (unlinked_libraries.length == 0) { + throw new Error("Cannot auto link " + contract.contract_name + "; " + contract.contract_name + " has no library dependencies.") + } + + unlinked_libraries = unlinked_libraries.map(function(name) { + // Remove underscores + return name.replace(/_/g, ""); + }).sort().filter(function(name, index, arr) { + // Remove duplicates + if (index + 1 >= arr.length) { + return true; + } + + return name != arr[index + 1]; + }); + + unlinked_libraries.forEach(function(name) { + var library = available_contracts[name]; + + if (library == null) { + throw new Error("Cannot auto link " + contract.contract_name + "; " + contract.contract_name + " unknown dependency " + name + ".") + } + + self.link(library, contract, logger); + }); + } +}; diff --git a/lib/migrate.js b/lib/migrate.js new file mode 100644 index 00000000000..0973ed14f96 --- /dev/null +++ b/lib/migrate.js @@ -0,0 +1,111 @@ +var fs = require("fs"); +var dir = require("node-dir"); +var path = require("path"); +var Pudding = require("ether-pudding"); +var Deployer = require("./deployer"); +var Profiler = require("./profiler"); +var Require = require("./exec"); +var async = require("async"); +var Web3 = require("web3"); + +function Migration(file) { + this.file = file; + this.number = parseInt(path.basename(file)); +}; + +Migration.prototype.run = function(options, callback) { + var self = this; + + var web3 = new Web3(); + web3.setProvider(options.provider); + + console.log("Running migration: " + path.relative(options.migrations_directory, this.file)); + + Pudding.requireAll({ + source_directory: options.contracts_directory, + provider: options.provider || (options.web3 != null ? options.web3.currentProvider : null) + }, function(err, contracts) { + if (err) return callback(err); + + web3.eth.getAccounts(function(err, accounts) { + if (err) return callback(err); + + var context = { + web3: web3 + }; + + contracts.forEach(function(contract) { + context[contract.contract_name] = contract; + contract.defaults({ + from: accounts[0] + }); + }); + + var deployer = new Deployer({ + logger: { + log: function(msg) { + console.log(" " + msg); + } + }, + contracts: contracts + }); + + var finish = function(err) { + if (err) return callback(err); + deployer.start().then(callback).catch(callback); + }; + + Require.file({ + file: self.file, + context: context, + args: [deployer] + }, function(err, fn) { + if (fn.length <= 1) { + fn(deployer); + finish(); + } else { + fn(deployer, finish); + } + }); + }); + }); +}; + +var Migrate = { + Migration: Migration, + + assemble: function(options, callback) { + dir.files(options.migrations_directory, function(err, files) { + if (err) return callback(err); + + var migrations = files.map(function(file) { + return new Migration(file); + }); + + // Make sure to sort the prefixes as numbers and not strings. + migrations = migrations.sort(function(a, b) { + if (a.number > b.number) { + return 1; + } else if (a.number < b.number) { + return -1; + } + return 0; + }); + + callback(null, migrations); + }); + }, + + runAll: function(options, callback) { + var self = this; + this.assemble(options, function(err, migrations) { + if (err) return callback(err); + + async.eachSeries(migrations, function(migration, finished) { + migration.run(options, finished); + }, callback); + }); + } +}; + +module.exports = Migrate; diff --git a/lib/profiler.js b/lib/profiler.js new file mode 100644 index 00000000000..5dcf315b165 --- /dev/null +++ b/lib/profiler.js @@ -0,0 +1,202 @@ +// Compares .sol files to their .sol.js counterparts, +// determines which .sol files have been updated. +var dir = require("node-dir"); +var path = require("path"); +var async = require("async"); +var fs = require("fs"); +var SolidityParser = require("solidity-parser"); +var Graph = require("graphlib").Graph; +var isAcyclic = require("graphlib/lib/alg").isAcyclic; +var postOrder = require("graphlib/lib/alg").postorder; + +module.exports = { + all_contracts: function(directory, callback) { + dir.files(directory, function(err, files) { + if (err) return callback(err); + + files = files.filter(function(file) { + // Ignore any files that aren't solidity files. + return path.extname(file) == ".sol" && path.basename(file)[0] != "."; + }); + + callback(null, files); + }); + }, + + updated: function(options, callback) { + var source_directory = options.source_directory; + var build_directory = options.build_directory; + + this.all_contracts(source_directory, function(err, files) { + var expected_build_files = files.map(function(file) { + return path.join(build_directory, path.relative(source_directory, file)); + }); + + async.map(files, fs.stat, function(err, file_stats) { + if (err) return callback(err); + + async.map(expected_build_files, function(expected_file, finished) { + fs.stat(expected_file, function(err, stat) { + // Ignore errors when built files don't exist. + finished(null, stat); + }); + }, function(err, built_file_stats) { + if (err) return callback(err); + + var updated = []; + + for (var i = 0; i < built_file_stats.length; i++) { + var file_stat = file_stats[i]; + var built_file_stat = built_file_stats[i]; + + if (built_file_stat == null) { + updated.push(files[i]); + continue; + } + + var modified_time = (file_stat.mtime || file_stat.ctime).getTime(); + var built_time = (built_file_stats.mtime || built_file_stats.ctime).getTime(); + + if (modified_time > built_time) { + updated.push(files[i]); + } + } + + callback(null, updated); + }); + }); + }); + }, + + imports: function(file, callback) { + fs.readFile(file, "utf8", function(err, body) { + if (err) callback(err); + + //console.log("Parsing " + path.basename(file) + "..."); + + var imports = SolidityParser.parse(body, "imports"); + + var dirname = path.dirname(file); + imports = imports.map(function(i) { + return path.resolve(path.join(dirname, i)); + }); + + callback(null, imports); + }); + }, + + required_files: function(files, callback) { + // Ensure full paths. + files = files.map(function(file) { + return path.resolve(file); + }); + + this.dependency_graph(files, function(err, dependsGraph) { + if (err) return callback(err); + + function is_updated(contract_name) { + return updated[contract_name] === true; + } + + var required = {}; + + function include(file) { + required[file] = true; + } + + function walk_down(file) { + if (required[file] === true) { + return; + } + + include(file); + + var dependencies = dependsGraph.successors(file); + + // console.log("At: " + contract_name); + // console.log(" Dependencies: ", dependencies); + + if (dependencies.length > 0) { + dependencies.forEach(walk_down); + } + } + + function walk_from(file) { + var ancestors = dependsGraph.predecessors(file); + var dependencies = dependsGraph.successors(file); + + // console.log("At: " + contract_name); + // console.log(" Ancestors: ", ancestors); + // console.log(" Dependencies: ", dependencies); + + include(file); + + if (ancestors.length > 0) { + ancestors.forEach(walk_from); + } + + if (dependencies.length > 0) { + dependencies.forEach(walk_down); + } + } + + files.forEach(walk_from); + + callback(null, Object.keys(required)); + }); + }, + + dependency_graph: function(files, callback) { + var self = this; + + // Ensure full paths. + files = files.map(function(file) { + return path.resolve(file); + }); + + // Iterate through all the contracts looking for libraries and building a dependency graph + var dependsGraph = new Graph(); + + var imports_cache = {}; + + function getImports(file, callback) { + if (imports_cache[file] != null) { + callback(null, imports_cache[file]); + } else { + self.imports(file, function(err, imports) { + if (err) return callback(err); + imports_cache[file] = imports; + callback(null, imports); + }); + } + }; + + async.each(files, function(file, finished) { + // Add the contract to the depend graph. + dependsGraph.setNode(file); + + getImports(file, function(err, imports) { + if (err) return callback(err); + + imports.forEach(function(import_path) { + if (!dependsGraph.hasEdge(file, import_path)) { + dependsGraph.setEdge(file, import_path); + } + }); + + finished(); + }) + }, + function() { + // Check for cycles in the graph, the dependency graph needs to be a tree otherwise there's an error + if (!isAcyclic(dependsGraph)) { + var errorMessage = "Found cyclic dependencies. Adjust your import statements to remove cycles.\n\n"; + dependsGraph.edges().forEach(function(o){ + errorMessage += o.v + " -- depends on --> " + o.w + "\n"; + }); + return callback(new CompileError(errorMessage)); + } + callback(null, dependsGraph) + }); + }, +}; diff --git a/package.json b/package.json index 9c0b8627bb6..34186ba0c70 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "rimraf": "^2.4.3", "serve-static": "^1.10.0", "solc": "^0.3.1-1", + "solidity-parser": "^0.0.5", "spawn-args": "^0.1.0", "truffle-default-builder": "0.0.9", "uglify-js": "^2.6.1", From 6fc2f80d8f10519dabb14ea09dff9a9799a2b7d4 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 13 May 2016 13:25:42 -0700 Subject: [PATCH 03/26] Various fixes. Namely, you're not allowed to add deployer steps once the deployment has started. Also, fix a bug in the linker, as well as other refactorings. --- lib/deployer.js | 35 +++++++++++++++++++++++++++++++++-- lib/linker.js | 2 +- lib/migrate.js | 31 +++++++++++++++++++++++++++---- lib/provider.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 lib/provider.js diff --git a/lib/deployer.js b/lib/deployer.js index da4618398bc..b647e44a48d 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -20,6 +20,7 @@ function Deployer(options) { (options.contracts || []).forEach(function(contract) { self.known_contracts[contract.contract_name] = contract; }) + this.started = false; }; // Note: In all code below we overwrite this.chain every time .then() is used @@ -29,11 +30,14 @@ Deployer.prototype.start = function() { var self = this; return new Promise(function(accept, reject) { self.chain = self.chain.then(accept).catch(reject); + self.started = true; self._accept(); }); }; Deployer.prototype.autolink = function(contract) { + this.checkStarted(); + var self = this; var regex = /__[^_]+_+/g; @@ -43,6 +47,8 @@ Deployer.prototype.autolink = function(contract) { }; Deployer.prototype.link = function(library, destinations) { + this.checkStarted(); + var self = this; this.chain = this.chain.then(function() { @@ -51,12 +57,24 @@ Deployer.prototype.link = function(library, destinations) { }; Deployer.prototype.deploy = function() { + this.checkStarted(); + var self = this; var args = Array.prototype.slice.call(arguments); var contract = args.shift(); + self.chain = this.chain.then(function() { - self.logger.log("Deploying new instance of " + contract.contract_name + "..."); - return contract.new.apply(contract, args); + var prefix = "Deploying "; + if (contract.address != null) { + prefix = "Replacing "; + } + + self.logger.log(prefix + contract.contract_name + "..."); + + // Evaluate any arguments if they're promises + return Promise.all(args); + }).then(function(new_args) { + return contract.new.apply(contract, new_args); }).then(function(instance) { self.logger.log("Saving deployed address: " + instance.address); contract.address = instance.address; @@ -65,17 +83,24 @@ Deployer.prototype.deploy = function() { }; Deployer.prototype.new = function() { + this.checkStarted(); + var self = this; var args = Array.prototype.slice.call(arguments); var contract = args.shift(); this.chain = this.chain.then(function() { self.logger.log("Creating new instance of " + contract.contract_name); + // Evaluate any arguments if they're promises + return Promise.all(args); + }).then(function(new_args) { return contract.new.apply(contract, args) }); return this.chain; }; Deployer.prototype.then = function(fn) { + this.checkStarted(); + var self = this; this.chain = this.chain.then(function() { self.logger.log("Running step..."); @@ -84,4 +109,10 @@ Deployer.prototype.then = function(fn) { return this.chain; } +Deployer.prototype.checkStarted = function() { + if (this.started == true) { + throw new Error("Can't add new deployment steps once the deploy has started"); + } +} + module.exports = Deployer; diff --git a/lib/linker.js b/lib/linker.js index 3caa913fcdf..639d295d468 100644 --- a/lib/linker.js +++ b/lib/linker.js @@ -20,7 +20,7 @@ module.exports = { destinations.forEach(function(destination) { logger.log("Linking " + library.contract_name + " to " + destination.contract_name); - destination.binary = destination.unlinked_binary.replace(regex, library.address); + destination.binary = destination.unlinked_binary.replace(regex, library.address.replace("0x", "")); }); }, diff --git a/lib/migrate.js b/lib/migrate.js index 0973ed14f96..cd9e20a80e8 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -4,6 +4,7 @@ var path = require("path"); var Pudding = require("ether-pudding"); var Deployer = require("./deployer"); var Profiler = require("./profiler"); +var Provider = require("./provider"); var Require = require("./exec"); var async = require("async"); var Web3 = require("web3"); @@ -15,11 +16,13 @@ function Migration(file) { Migration.prototype.run = function(options, callback) { var self = this; - + var logger = options.logger || console; var web3 = new Web3(); web3.setProvider(options.provider); - console.log("Running migration: " + path.relative(options.migrations_directory, this.file)); + //Provider.wrap(options.provider); + + logger.log("Running migration: " + path.relative(options.migrations_directory, this.file)); Pudding.requireAll({ source_directory: options.contracts_directory, @@ -39,12 +42,21 @@ Migration.prototype.run = function(options, callback) { contract.defaults({ from: accounts[0] }); + if (options.network_id) { + contract.setNetwork(network_id); + } + + // If this is the initial migration, assume no contracts + // have been deployed previously. + if (options.initial) { + contract.address = null; + } }); var deployer = new Deployer({ logger: { log: function(msg) { - console.log(" " + msg); + logger.log(" " + msg); } }, contracts: contracts @@ -52,7 +64,13 @@ Migration.prototype.run = function(options, callback) { var finish = function(err) { if (err) return callback(err); - deployer.start().then(callback).catch(callback); + deployer.start().then(function() { + logger.log("Saving artifacts..."); + return Pudding.saveAll(contracts, options.contracts_directory, options.network_id); + }).then(callback).catch(function(e) { + logger.log("Error encountered, bailing. Network state unknown. Review successful transactions manually."); + callback(e); + }); }; Require.file({ @@ -101,7 +119,12 @@ var Migrate = { this.assemble(options, function(err, migrations) { if (err) return callback(err); + var first = true; async.eachSeries(migrations, function(migration, finished) { + options.initial = first; + if (first) { + first = false; + } migration.run(options, finished); }, callback); }); diff --git a/lib/provider.js b/lib/provider.js new file mode 100644 index 00000000000..1c2169a2030 --- /dev/null +++ b/lib/provider.js @@ -0,0 +1,28 @@ +module.exports = { + wrap: function(provider, options) { + if (options.verbose || options.verboseRpc) { + this.makeVerbose(provider, options.logger); + } + }, + + makeVerbose: function(provider, logger) { + logger = logger || console; + + // // If you want to see what web3 is sending and receiving. + var oldAsync = provider.sendAsync; + + if (oldAsync.is_verbose) return; + + provider.sendAsync = function(options, callback) { + logger.log(" > " + JSON.stringify(options, null, 2).split("\n").join("\n > ")); + oldAsync.call(provider, options, function(error, result) { + if (error == null) { + logger.log(" < " + JSON.stringify(result, null, 2).split("\n").join("\n < ")); + } + callback(error, result) + }); + }; + + provider.sendAsync.is_verbose = true; + } +}; From 4bc82d7ef2b2b7a962a3a68fc5f4479aebdab85c Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 16 May 2016 15:07:14 -0700 Subject: [PATCH 04/26] Refactoring of configuration and related items to drastically reduce coupling. Refactoring not finished; many modules likely broken. --- cli.js | 140 +++------ environments/test/config.json | 1 - example/migrations/1_initial_migration.js | 5 + example/truffle.js | 4 - lib/config.js | 366 ++++++++-------------- lib/provider.js | 22 ++ package.json | 1 + 7 files changed, 208 insertions(+), 331 deletions(-) delete mode 100644 environments/test/config.json create mode 100644 example/migrations/1_initial_migration.js diff --git a/cli.js b/cli.js index 9b81fb36a27..1e70b4cc8e1 100755 --- a/cli.js +++ b/cli.js @@ -16,6 +16,7 @@ var argv = require('yargs').argv; var truffle_dir = process.env.TRUFFLE_NPM_LOCATION || argv.n || argv.npm_directory || __dirname; var working_dir = process.env.TRUFFLE_WORKING_DIRECTORY || argv.w || argv.working_directory || process.cwd(); +var environment = argv.e || argv.environment || process.env.NODE_ENV || "default"; if (working_dir[working_dir.length - 1] != "/") { working_dir += "/"; @@ -62,7 +63,6 @@ var runTask = function(name) { registerTask('watch', "Watch filesystem for changes and rebuild the project automatically", function(done) { var needs_rebuild = true; - var needs_redeploy = false; chokidar.watch(["app/**/*", "environments/*/contracts/**/*", "contracts/**/*", "truffle.json", "truffle.js"], { ignored: /[\/\\]\./, // Ignore files prefixed with "." @@ -74,24 +74,9 @@ registerTask('watch', "Watch filesystem for changes and rebuild the project auto console.log(colors.cyan(">> File " + display_path + " changed.")); needs_rebuild = true; - - if (display_path.indexOf("contracts/") == 0) { - needs_redeploy = true; - } else { - needs_rebuild = true; - } }); var check_rebuild = function() { - if (needs_redeploy == true) { - needs_redeploy = false; - needs_rebuild = false; - console.log("Redeploying..."); - if (runTask("deploy") != 0) { - printFailure(); - } - } - if (needs_rebuild == true) { needs_rebuild = false; console.log("Rebuilding..."); @@ -140,12 +125,12 @@ registerTask('version', "Show version number and exit", function(done) { }); registerTask('init', "Initialize new Ethereum project, including example contracts and tests", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv); + var config = Truffle.config.detect(); Truffle.init.all(config, done); }); registerTask('create:contract', "Create a basic contract", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv); + var config = Truffle.config.detect(); var name = argv.name; @@ -161,7 +146,7 @@ registerTask('create:contract', "Create a basic contract", function(done) { }); registerTask('create:test', "Create a basic test", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv); + var config = Truffle.config.detect(); var name = argv.name; @@ -177,7 +162,7 @@ registerTask('create:test', "Create a basic test", function(done) { }); registerTask('compile', "Compile contracts", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + var config = Truffle.config.detect(); Truffle.contracts.compile({ all: config.argv.allContracts === true, source_directory: config.contracts.directory, @@ -187,32 +172,8 @@ registerTask('compile', "Compile contracts", function(done) { }, done); }); -registerTask('deploy', "Deploy contracts to the network, compiling if needed", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); - - console.log("Using environment " + config.environment + "."); - - var compile = true; - var link = true; - - if (argv.compile === false) { - compile = false; - } - - // Compile and deploy. - Truffle.contracts.deploy(config, compile, function(err) { - if (err != null) { - done(err); - } else { - // console.log("Rebuilding app with new contracts..."); - // runTask("build"); - done(); - } - }); -}); - registerTask('build', "Build development version of app", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + var config = Truffle.config.detect(); Truffle.build.build(config, function(err) { done(err); if (err == null) { @@ -222,7 +183,7 @@ registerTask('build', "Build development version of app", function(done) { }); registerTask('dist', "Create distributable version of app (minified)", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "production"); + var config = Truffle.config.detect(); console.log("Using environment " + config.environment + "."); Truffle.build.dist(config, function(err) { done(err); @@ -233,21 +194,29 @@ registerTask('dist', "Create distributable version of app (minified)", function( }); registerTask('migrate', "Run migrations", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); - - console.log("Using environment " + config.environment + "."); - + var config = Truffle.config.detect(); + //console.log("Using network " + config.network + "."); - Truffle.migrate.runAll({ - migrations_directory: config.migrations.directory, - contracts_directory: config.contracts.build_directory, - provider: config.web3.currentProvider - }, done); + Truffle.contracts.compile({ + all: argv.all === true || argv.allContracts === true, + source_directory: config.contracts_directory, + build_directory: config.contracts_build_directory, + quiet: argv.quiet === true, + strict: argv.strict === true + }, function(err) { + if (err) return done(rr); + + Truffle.migrate.runAll({ + migrations_directory: config.migrations_directory, + contracts_directory: config.contracts_build_directory, + provider: config.getProvider() + }, done); + }); }); registerTask('exec', "Execute a JS file within truffle environment. Script *must* call process.exit() when finished.", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + var config = Truffle.config.detect(); var file = argv.file; @@ -262,7 +231,7 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must } if (path.isAbsolute(file) == false) { - file = path.join(config.working_dir, file); + file = path.join(config.working_directory, file); } Truffle.exec.file({ @@ -276,12 +245,12 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must // --no-color: Disable color // More to come. registerTask('test', "Run tests", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "test"); + var config = Truffle.config.detect(); console.log("Using environment " + config.environment + "."); // Ensure we're quiet about deploys during tests. - config.argv.quietDeploy = true; + config.argv.quiet = true; var file = argv.file; @@ -297,51 +266,18 @@ registerTask('test', "Run tests", function(done) { }); registerTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + var config = Truffle.config.detect(); Truffle.console.run(config, done); }); registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { - var config = Truffle.config.gather(truffle_dir, working_dir, argv, "development"); + var config = Truffle.config.detect(); console.log("Using environment " + config.environment + "."); Truffle.serve.start(config, argv.port || argv.p || "8080", function() { runTask("watch"); }); }); - - -// registerTask('watch:tests', "Watch filesystem for changes and rerun tests automatically", function(done) { -// -// gaze(["app/**/*", "config/**/*", "contracts/**/*", "test/**/*"], {cwd: working_dir, interval: 1000, debounceDelay: 500}, function() { -// // On changed/added/deleted -// this.on('all', function(event, filePath) { -// if (filePath.match(/\/config\/.*?\/.*?\.sol\.js$/)) { -// // ignore changes to /config/*/*.sol.js since these changes every time -// // tests are run (when contracts are compiled) -// return; -// } -// process.stdout.write("\u001b[2J\u001b[0;0H"); // clear screen -// var display_path = "./" + filePath.replace(working_dir, ""); -// console.log(colors.cyan(">> File " + display_path + " changed.")); -// run_tests(); -// }); -// }); -// -// var run_tests = function() { -// console.log("Running tests..."); -// -// process.chdir(working_dir); -// var config = Truffle.config.gather(truffle_dir, working_dir, argv, "test"); -// config.argv.quietDeploy = true; // Ensure we're quiet about deploys during tests -// -// Test.run(config, function() { console.log("> test run complete; watching for changes..."); }); -// }; -// run_tests(); // run once immediately -// -// }); - - // Default to listing available commands. var current_task = argv._[0]; @@ -354,6 +290,22 @@ if (tasks[current_task] == null) { process.exit(1); } + + // // Check to see if we're working on a dapp meant for 0.2.x or older + // if (fs.existsSync(path.join(working_dir, "config", "app.json"))) { + // console.log("Your dapp is meant for an older version of Truffle. Don't worry, there are two solutions!") + // console.log(""); + // console.log("1) Upgrade you're dapp using the followng instructions (it's easy):"); + // console.log(" https://github.com/ConsenSys/truffle/wiki/Migrating-from-v0.2.x-to-v0.3.0"); + // console.log(""); + // console.log(" ( OR )") + // console.log(""); + // console.log("2) Downgrade to Truffle 0.2.x"); + // console.log(""); + // console.log("Cheers! And file an issue if you run into trouble! https://github.com/ConsenSys/truffle/issues") + // process.exit(); + // } + // Something is keeping the process open. I'm not sure what. // Let's explicitly kill it until I can figure it out. process.exit(runTask(current_task)); diff --git a/environments/test/config.json b/environments/test/config.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/environments/test/config.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/example/migrations/1_initial_migration.js b/example/migrations/1_initial_migration.js new file mode 100644 index 00000000000..9d8460903c7 --- /dev/null +++ b/example/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +module.exports = function(deployer) { + deployer.deploy(ConvertLib); + deployer.autolink(MetaCoin); + deployer.deploy(MetaCoin); +}; diff --git a/example/truffle.js b/example/truffle.js index 395fb5b7c58..6e3970801fe 100644 --- a/example/truffle.js +++ b/example/truffle.js @@ -9,10 +9,6 @@ module.exports = { ], "images/": "images/" }, - deploy: [ - "MetaCoin", - "ConvertLib" - ], rpc: { host: "localhost", port: 8545 diff --git a/lib/config.js b/lib/config.js index e42fafcc9df..14ebf1195b4 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,265 +1,167 @@ var fs = require("fs"); -var dir = require("node-dir"); -var deasync = require("deasync"); -var filesSync = deasync(dir.files); -var subdirSync = deasync(dir.subdirs); var _ = require("lodash"); -var Web3 = require("web3"); -var loadconf = deasync(require("./loadconf")); var path = require("path"); -var Exec = require("./exec"); +var Provider = require("./provider"); var ConfigurationError = require('./errors/configurationerror'); -var Pudding = require("ether-pudding"); var requireNoCache = require("./require-nocache"); - -var Config = { - gather: function(truffle_dir, working_dir, argv, desired_environment) { - var config = {}; - config = _.merge(config, { - argv: argv, - truffle_dir: truffle_dir, - working_dir: working_dir, - web3: new Web3(), - environments: { - directory: path.join(working_dir, "environments"), - configfilename: "config.js", - oldconfigfilename: "config.json", - available: {}, - current: {} - }, - app: { - configfile: path.join(working_dir, "truffle.js"), - oldconfigfile: path.join(working_dir, "truffle.json"), - directory: path.join(working_dir, "app"), - // Default config objects that'll be overwritten by working_dir config. - resolved: { - build: {}, - include_contracts: true, - deploy: [], - after_deploy: [], - rpc: {}, - processors: {}, - } - }, - example: { - directory: path.join(truffle_dir, "example") - }, - templates: { +var findUp = require("find-up"); + +var DEFAULT_CONFIG_FILENAME = "truffle.js"; + +function Config(truffle_directory, working_directory) { + var self = this; + + this._values = { + truffle_directory: truffle_directory || path.resolve(path.join(__dirname, "../")), + working_directory: working_directory || process.cwd() + }; + + var props = { + // First two already set. + truffle_directory: function() {}, + working_directory: function() {}, + + build_directory: function() { + return path.join(self.working_directory, "build"); + }, + contracts_directory: function() { + return path.join(self.working_directory, "contracts"); + }, + contracts_build_directory: function() { + return path.join(self.build_directory, "contracts"); + }, + migrations_directory: function() { + return path.join(self.working_directory, "migrations"); + }, + test_directory: function() { + return path.join(self.working_directory, "test"); + }, + test_file_extension_regexp: function() { + return /.*\.(js|es|es6|jsx)$/ + }, + network: function() { + return "default" + }, + networks: function() { + return { + "default": {}, + "test": {} + } + }, + example_project_directory: function() { + return path.join(self.truffle_directory, "example"); + }, + templates: function() { + return { test: { - filename: path.join(truffle_dir, "templates", "example.js"), + filename: path.join(self.truffle_directory, "templates", "example.js"), variable: "example" }, contract: { - filename: path.join(truffle_dir, "templates", "Example.sol"), + filename: path.join(self.truffle_directory, "templates", "Example.sol"), name: "Example", variable: "example" } - }, - contracts: { - classes: {}, - directory: path.join(working_dir, "contracts"), - built_files: [], - build_directory: null - }, - migrations: { - directory: path.join(working_dir, "migrations"), - }, - tests: { - directory: path.join(working_dir, "test"), - filter: /.*\.(js|es|es6|jsx)$/ - }, - build: { - directory: null, - }, - dist: { - directory: null, - }, - rpc: { - defaults: { - gas: 3141592, - gasPrice: 100000000000, // 100 Shannon, - from: null - } + }; + }, + rpc: function() { + return { + host: "localhost", + port: "8545", + gas: 3141592, + gasPrice: 100000000000, // 100 Shannon, + from: null } - }); - - // Check to see if we're working on a dapp meant for 0.2.x or older - if (fs.existsSync(path.join(working_dir, "config", "app.json"))) { - console.log("Your dapp is meant for an older version of Truffle. Don't worry, there are two solutions!") - console.log(""); - console.log("1) Upgrade you're dapp using the followng instructions (it's easy):"); - console.log(" https://github.com/ConsenSys/truffle/wiki/Migrating-from-v0.2.x-to-v0.3.0"); - console.log(""); - console.log(" ( OR )") - console.log(""); - console.log("2) Downgrade to Truffle 0.2.x"); - console.log(""); - console.log("Cheers! And file an issue if you run into trouble! https://github.com/ConsenSys/truffle/issues") - process.exit(); } + }; + + Object.keys(props).forEach(function(prop) { + self.addProp(prop, props[prop]); + }); +}; + +Config.prototype.addProp = function(key, default_getter) { + Object.defineProperty(this, key, { + get: function() { + return this._values[key] || default_getter(); + }, + set: function(val) { + this._values[key] = val; + }, + configurable: true, + enumerable: true + }); +}; + +Config.prototype.getProvider = function(network_id) { + if (network_id == null) { + network_id = "default"; + } - desired_environment = argv.e || argv.environment || process.env.NODE_ENV || desired_environment; - - if (desired_environment) { - // Try to find the desired environment, and fall back to development if we don't find it. - for (var environment of [desired_environment, "development"]) { - var environment_directory = path.join(config.environments.directory, environment); - if (!fs.existsSync(environment_directory)) { - continue; - } - - // I put this warning here but now I'm not sure if I want it. - if (environment != desired_environment && desired_environment != null) { - console.log("Warning: Couldn't find environment " + desired_environment + "."); - } - - config.environment = desired_environment; - config.environments.current.directory = environment_directory; - config.environments.current.filename = path.join(environment_directory, config.environments.configfilename); - config.environments.current.oldfilename = path.join(environment_directory, config.environments.oldconfigfilename); - - break; - } - } + if (this.networks[network_id] == null) { + throw new ConfigurationError("Cannot find network '" + network_id + "'"); + } - // If we didn't find an environment, but asked for one, error. - if (config.environment == null && desired_environment != null) { - throw new ConfigurationError("Couldn't find any suitable environment. Check environment configuration."); - } + return Provider.create(_.merge({}, this.rpc, this.networks[network_id])); +}; - // Get environments in working directory, if available. - if (fs.existsSync(config.environments.directory)) { - for (var directory of subdirSync(config.environments.directory)) { - name = directory.substring(directory.lastIndexOf("/") + 1) - config.environments.available[name] = directory; - } - } +// Helper function for expecting paths to exist. +Config.expect = function(expected_path, description, extra, callback) { + if (typeof description == "function") { + callback = description; + description = "file"; + extra = ""; + } - // Load the app config. - // For now, support both new and old config files. - if (fs.existsSync(config.app.configfile)) { - _.merge(config.app.resolved, requireNoCache(config.app.configfile)); - } else if (fs.existsSync(config.app.oldconfigfile)) { - config.app.resolved = loadconf(config.app.oldconfigfile, config.app.resolved); - } + if (typeof extra == "function") { + callback = extra; + extra = ""; + } - // Now merge default rpc details, only overwriting if not specified. - _.mergeWith(config.app.resolved.rpc, config.rpc.defaults, function(objValue, srcValue) { - return objValue != null ? objValue : srcValue; - }); + if (description == null) { + description = "file"; + } - // Load environment config - if (fs.existsSync(config.environments.current.filename)) { - _.merge(config.app.resolved, requireNoCache(config.environments.current.filename)); - } else if (fs.existsSync(config.environments.current.oldfilename)) { - config.app.resolved = loadconf(config.environments.current.oldfilename, config.app.resolved); - } + if (extra == null) { + extra = ""; + } - if (fs.existsSync(config.environments.current.directory)) { - // Overwrite build and dist directories - config.build.directory = path.join(config.environments.current.directory, "build"); - config.dist.directory = path.join(config.environments.current.directory, "dist"); - config.contracts.build_directory = path.join(config.environments.current.directory, "contracts"); - } + if (!fs.existsSync(expected_path)) { + var display_path = expected_path.replace(this.working_dir, "./"); + var error = new ConfigurationError("Couldn't find " + description + " at " + display_path + ". " + extra); - // Allow for deprecated build configuration. - if (config.app.resolved.frontend != null) { - config.app.resolved.build = config.app.resolved.frontend; + if (callback != null) { + callback(error); + return false; + } else { + throw error; } + } + return true; +} - // Helper function for expecting paths to exist. - config.expect = function(expected_path, description, extra, callback) { - if (typeof description == "function") { - callback = description; - description = "file"; - extra = ""; - } - - if (typeof extra == "function") { - callback = extra; - extra = ""; - } - - if (description == null) { - description = "file"; - } - - if (extra == null) { - extra = ""; - } - - if (!fs.existsSync(expected_path)) { - var display_path = expected_path.replace(this.working_dir, "./"); - var error = new ConfigurationError("Couldn't find " + description + " at " + display_path + ". " + extra); - - if (callback != null) { - callback(error); - return false; - } else { - throw error; - } - } - return true; - }; - - config.test_connection = function(callback) { - config.web3.eth.getCoinbase(function(error, coinbase) { - if (error != null) { - error = new Error("Could not connect to your RPC client. Please check your RPC configuration."); - } - - callback(error, coinbase) - }); - }; - - // DEPRECATED: Resolve paths for default builder's extra processors. - for (var extension in config.app.resolved.processors) { - var file = config.app.resolved.processors[extension]; - var full_path = path.join(working_dir, file); - config.app.resolved.processors[extension] = full_path; - } +Config.detect = function(filename) { + if (filename == null) { + filename = DEFAULT_CONFIG_FILENAME; + } - var provider = new Web3.providers.HttpProvider("http://" + config.app.resolved.rpc.host + ":" + config.app.resolved.rpc.port); - config.web3.setProvider(provider); + var file = findUp.sync(filename); - if (argv.verboseRpc != null) { - // // If you want to see what web3 is sending and receiving. - var oldAsync = config.web3.currentProvider.sendAsync; - config.web3.currentProvider.sendAsync = function(options, callback) { - console.log(" > " + JSON.stringify(options, null, 2).split("\n").join("\n > ")); - oldAsync.call(config.web3.currentProvider, options, function(error, result) { - if (error == null) { - console.log(" < " + JSON.stringify(result, null, 2).split("\n").join("\n < ")); - } - callback(error, result) - }); - }; - } + if (file == null) { + throw new ConfigurationError("Could not find suitable configuration file."); + } - // Get contracts in working directory, if available. - if (fs.existsSync(config.contracts.directory)) { - for (file of filesSync(config.contracts.directory)) { + return this.load(file); +}; - // Ignore any files that aren't solidity files. - if (path.extname(file) != ".sol" || path.basename(file)[0] == ".") { - continue; - } +Config.load = function(file) { + var config = new Config(); - var name = path.basename(file, ".sol"); - var relative_path = file.replace(config.working_dir, "./"); - var stats = fs.statSync(file); + config.working_directory = path.dirname(path.resolve(file)); - config.contracts.classes[name] = { - file: file, - source: relative_path, - source_modified_time: (stats.mtime || stats.ctime).getTime(), - compiled_time: 0 // This will be overwritten if we find a compiled file - } - } - } + var static_config = requireNoCache(file); - return config; - } -} + return _.merge(config, static_config); +}; module.exports = Config; diff --git a/lib/provider.js b/lib/provider.js index 1c2169a2030..e9aa8a12f36 100644 --- a/lib/provider.js +++ b/lib/provider.js @@ -1,8 +1,13 @@ +var Web3 = require("web3"); + module.exports = { wrap: function(provider, options) { + options = options || {}; + if (options.verbose || options.verboseRpc) { this.makeVerbose(provider, options.logger); } + return provider; }, makeVerbose: function(provider, logger) { @@ -24,5 +29,22 @@ module.exports = { }; provider.sendAsync.is_verbose = true; + }, + + create: function(options) { + var provider = new Web3.providers.HttpProvider("http://" + options.host + ":" + options.port); + return this.wrap(provider, options); + }, + + test_connection: function(provider, callback) { + var web3 = new Web3(); + web3.setProvider(provider); + web3.eth.getCoinbase(function(error, coinbase) { + if (error != null) { + error = new Error("Could not connect to your RPC client. Please check your RPC configuration."); + } + + callback(error, coinbase) + }); } }; diff --git a/package.json b/package.json index 34186ba0c70..e627f238be9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "deasync": "^0.1.3", "ether-pudding": "2.0.6", "finalhandler": "^0.4.0", + "find-up": "^1.1.2", "graphlib": "^2.0.0", "jsmin": "^1.0.1", "lodash": "^4.5.1", From 4c3191b6832e7aac9e58a619d1317eb759519f4b Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 18 May 2016 11:35:45 -0700 Subject: [PATCH 05/26] Full migration functionality and recording it on the blockchain. --- cli.js | 7 +- example/contracts/Migrations.sol | 21 ++++ example/environments/development/config.js | 1 - example/environments/production/config.js | 1 - example/environments/staging/config.js | 1 - example/environments/test/config.js | 1 - example/migrations/0_initial_migration.js | 3 + ...ial_migration.js => 1_deploy_contracts.js} | 0 lib/migrate.js | 102 +++++++++++++++--- 9 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 example/contracts/Migrations.sol delete mode 100644 example/environments/development/config.js delete mode 100644 example/environments/production/config.js delete mode 100644 example/environments/staging/config.js delete mode 100644 example/environments/test/config.js create mode 100644 example/migrations/0_initial_migration.js rename example/migrations/{1_initial_migration.js => 1_deploy_contracts.js} (100%) diff --git a/cli.js b/cli.js index 1e70b4cc8e1..22aca0a582c 100755 --- a/cli.js +++ b/cli.js @@ -205,12 +205,13 @@ registerTask('migrate', "Run migrations", function(done) { quiet: argv.quiet === true, strict: argv.strict === true }, function(err) { - if (err) return done(rr); + if (err) return done(err); - Truffle.migrate.runAll({ + Truffle.migrate.run({ migrations_directory: config.migrations_directory, contracts_directory: config.contracts_build_directory, - provider: config.getProvider() + provider: config.getProvider(), + reset: argv.reset || false }, done); }); }); diff --git a/example/contracts/Migrations.sol b/example/contracts/Migrations.sol new file mode 100644 index 00000000000..132f325c10f --- /dev/null +++ b/example/contracts/Migrations.sol @@ -0,0 +1,21 @@ +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _ + } + + function Migrations() { + owner = msg.sender; + } + + function setCompleted(uint completed) restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/example/environments/development/config.js b/example/environments/development/config.js deleted file mode 100644 index 4ba52ba2c8d..00000000000 --- a/example/environments/development/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/example/environments/production/config.js b/example/environments/production/config.js deleted file mode 100644 index 4ba52ba2c8d..00000000000 --- a/example/environments/production/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/example/environments/staging/config.js b/example/environments/staging/config.js deleted file mode 100644 index 4ba52ba2c8d..00000000000 --- a/example/environments/staging/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/example/environments/test/config.js b/example/environments/test/config.js deleted file mode 100644 index 4ba52ba2c8d..00000000000 --- a/example/environments/test/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/example/migrations/0_initial_migration.js b/example/migrations/0_initial_migration.js new file mode 100644 index 00000000000..5b309e062f2 --- /dev/null +++ b/example/migrations/0_initial_migration.js @@ -0,0 +1,3 @@ +module.exports = function(deployer) { + deployer.deploy(Migrations); +}; diff --git a/example/migrations/1_initial_migration.js b/example/migrations/1_deploy_contracts.js similarity index 100% rename from example/migrations/1_initial_migration.js rename to example/migrations/1_deploy_contracts.js diff --git a/lib/migrate.js b/lib/migrate.js index cd9e20a80e8..a6dbdb36957 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -20,8 +20,6 @@ Migration.prototype.run = function(options, callback) { var web3 = new Web3(); web3.setProvider(options.provider); - //Provider.wrap(options.provider); - logger.log("Running migration: " + path.relative(options.migrations_directory, this.file)); Pudding.requireAll({ @@ -33,26 +31,42 @@ Migration.prototype.run = function(options, callback) { web3.eth.getAccounts(function(err, accounts) { if (err) return callback(err); + // Initial context. var context = { web3: web3 }; + // Add contracts to context and prepare contracts. contracts.forEach(function(contract) { context[contract.contract_name] = contract; + + // Set defaults based on configuration. contract.defaults({ - from: accounts[0] + from: options.from || accounts[0], + gas: options.gas, + gasPrice: options.gasPrice }); + + // During migrations, we could be on a network that takes a long time to accept + // transactions (i.e., contract deployment close to block size). Because successful + // migration is more important than wait time in those cases, we'll synchronize "forever". + contract.synchronization_timeout = 0; + if (options.network_id) { contract.setNetwork(network_id); } // If this is the initial migration, assume no contracts // have been deployed previously. - if (options.initial) { + if (options.reset == true) { contract.address = null; } }); + if (options.reset == true) { + options.reset = false; + } + var deployer = new Deployer({ logger: { log: function(msg) { @@ -65,9 +79,17 @@ Migration.prototype.run = function(options, callback) { var finish = function(err) { if (err) return callback(err); deployer.start().then(function() { + logger.log("Saving successful migration to network..."); + var Migrations = context["Migrations"]; + if (Migrations && Migrations.address) { + return Migrations.deployed().setCompleted(self.number); + } + }).then(function() { logger.log("Saving artifacts..."); return Pudding.saveAll(contracts, options.contracts_directory, options.network_id); - }).then(callback).catch(function(e) { + }).then(function() { + callback(); + }).catch(function(e) { logger.log("Error encountered, bailing. Network state unknown. Review successful transactions manually."); callback(e); }); @@ -114,19 +136,73 @@ var Migrate = { }); }, - runAll: function(options, callback) { + run: function(options, callback) { + var self = this; + + if (options.reset == true) { + return this.runAll(options, callback); + } + + self.lastCompletedMigration(options, function(err, last_migration) { + if (err) return callback(err); + + // Don't rerun the last completed migration. + self.runFrom(last_migration + 1, options, callback); + }); + }, + + runFrom: function(number, options, callback) { var self = this; this.assemble(options, function(err, migrations) { if (err) return callback(err); - var first = true; - async.eachSeries(migrations, function(migration, finished) { - options.initial = first; - if (first) { - first = false; + while (migrations.length > 0) { + if (migrations[0].number >= number) { + break; } - migration.run(options, finished); - }, callback); + + migrations.shift(); + } + + self.runMigrations(migrations, options, callback); + }); + }, + + runAll: function(options, callback) { + var self = this; + this.assemble(options, function(err, migrations) { + if (err) return callback(err); + + self.runMigrations(migrations, options, callback); + }); + }, + + runMigrations: function(migrations, options, callback) { + async.eachSeries(migrations, function(migration, finished) { + migration.run(options, function(err) { + if (err) return finished(err); + finished(); + }); + }, callback); + }, + + lastCompletedMigration: function(options, callback) { + var migrations_contract = path.resolve(path.join(options.contracts_directory, "Migrations.sol.js")); + + Pudding.requireFile(migrations_contract, function(err, Migrations) { + if (err) return callback(new Error("Could not find built Migrations contract.")); + + if (Migrations.address == null) { + return callback(null, 0); + } + + Migrations.setProvider(options.provider); + + var migrations = Migrations.deployed(); + + migrations.last_completed_migration.call().then(function(completed_migration) { + callback(null, completed_migration.toNumber()); + }).catch(callback); }); } }; From 55c8afc589087dac48f000a9160a220990e9e0f8 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 18 May 2016 11:56:17 -0700 Subject: [PATCH 06/26] Make init work. Also, migration numbers need to be >= 1 --- cli.js | 4 ++-- ...{0_initial_migration.js => 1_initial_migration.js} | 0 .../{1_deploy_contracts.js => 2_deploy_contracts.js} | 0 lib/config.js | 4 ++++ lib/init.js | 11 ++++------- 5 files changed, 10 insertions(+), 9 deletions(-) rename example/migrations/{0_initial_migration.js => 1_initial_migration.js} (100%) rename example/migrations/{1_deploy_contracts.js => 2_deploy_contracts.js} (100%) diff --git a/cli.js b/cli.js index 22aca0a582c..661a7cb9d6b 100755 --- a/cli.js +++ b/cli.js @@ -125,8 +125,8 @@ registerTask('version', "Show version number and exit", function(done) { }); registerTask('init', "Initialize new Ethereum project, including example contracts and tests", function(done) { - var config = Truffle.config.detect(); - Truffle.init.all(config, done); + var config = Truffle.config.default(); + Truffle.init(config.working_directory, done); }); registerTask('create:contract', "Create a basic contract", function(done) { diff --git a/example/migrations/0_initial_migration.js b/example/migrations/1_initial_migration.js similarity index 100% rename from example/migrations/0_initial_migration.js rename to example/migrations/1_initial_migration.js diff --git a/example/migrations/1_deploy_contracts.js b/example/migrations/2_deploy_contracts.js similarity index 100% rename from example/migrations/1_deploy_contracts.js rename to example/migrations/2_deploy_contracts.js diff --git a/lib/config.js b/lib/config.js index 14ebf1195b4..7a47dece461 100644 --- a/lib/config.js +++ b/lib/config.js @@ -140,6 +140,10 @@ Config.expect = function(expected_path, description, extra, callback) { return true; } +Config.default = function() { + return new Config(); +}; + Config.detect = function(filename) { if (filename == null) { filename = DEFAULT_CONFIG_FILENAME; diff --git a/lib/init.js b/lib/init.js index d0caa57dd9e..eaca2801d54 100644 --- a/lib/init.js +++ b/lib/init.js @@ -1,10 +1,7 @@ var copy = require("./copy"); -var File = require("./file"); +var path = require("path"); -var Init = { - all: function(config, callback) { - copy(config.example.directory, config.working_dir, callback); - } +module.exports = function(destination, callback) { + var example_directory = path.resolve(path.join(__dirname, "..", "example")); + copy(example_directory, destination, callback); } - -module.exports = Init From 8d71347577d425314c0f734cfdd295649ccbfacc Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 18 May 2016 14:22:43 -0700 Subject: [PATCH 07/26] Fix modified time vs. built time detection, and remove a bunch of deploy code that no longer needs to exist. --- lib/compiler.js | 3 +- lib/contracts.js | 218 ++--------------------------------------------- lib/profiler.js | 4 +- 3 files changed, 8 insertions(+), 217 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index 0ffc14f3043..a3a1831bde0 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -30,8 +30,7 @@ module.exports = { Profiler.updated(options, function(err, updated) { if (err) return callback(err); - if (updated.length == 0 && config.quiet != true) { - options.logger.log("No contracts updated; skipping compilation."); + if (updated.length == 0 && options.quiet != true) { return callback(); } diff --git a/lib/contracts.js b/lib/contracts.js index 96151a26278..a77be5075f5 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -48,7 +48,11 @@ var Contracts = { function finished(err, contracts) { if (err) return callback(err); - self.write_contracts(contracts, options, callback); + if (contracts != null && Object.keys(contracts).length > 0) { + self.write_contracts(contracts, options, callback); + } else { + callback(); + } }; if (options.all == false) { @@ -58,218 +62,6 @@ var Contracts = { } }, - createContractAndWait: function(config, contract_name) { - var self = this; - - var tx = { - from: this.account, - gas: config.app.resolved.rpc.gas || config.app.resolved.rpc.gasLimit, - gasPrice: config.app.resolved.rpc.gasPrice, // 100 Shannon - data: config.contracts.classes[contract_name].binary - }; - - return new Promise(function(accept, reject) { - config.web3.eth.sendTransaction(tx, function(err, hash) { - if (err != null) { - return reject(err); - } - - var interval = setInterval(function() { - config.web3.eth.getTransactionReceipt(hash, function(err, receipt) { - if (err != null) { - clearInterval(interval); - - return reject(err); - } - if (receipt != null) { - if (config.argv.quietDeploy == null) { - console.log("Deployed: " + contract_name + " to address: " + receipt.contractAddress); - } - - accept({ - name: contract_name, - address: receipt.contractAddress - }); - clearInterval(interval); - } - }); - }, 500); - }) - }); - }, - - build_deploy_dependency_graph: function(config, errorCallback) { - if (config.argv.quietDeploy == null) { - console.log("Collecting dependencies..."); - } - // Iterate through all the contracts looking for libraries and building a dependency graph - var dependsGraph = new Graph(); - for (var i = 0; i < config.app.resolved.deploy.length; i++) { - var key = config.app.resolved.deploy[i]; - var contract_class = config.contracts.classes[key]; - - if (contract_class == null) { - errorCallback(new DeployError("Could not find contract '" + key + "' for linking. Check truffle.json.")); - return null; - } - - if (contract_class.binary == null){ - errorCallback(new DeployError("Could not find compiled binary for contract '" + key + "'. Check truffle.json.")); - return null; - } - // Add the contract to the depend graph - dependsGraph.setNode(key); - - // Find references to any librarys - // Library references are embedded in the bytecode of contracts with the format - // "__Lib___________________________________" , where "Lib" is your library name and the whole - // string is 40 characters long. This is the placeholder for the Lib's address. - - var regex = /__([^_]*)_*/g; - var matches; - while ( (matches = regex.exec(contract_class.unlinked_binary)) !== null ) { - var lib = matches[1]; - if (!dependsGraph.hasEdge(key,lib)) { - dependsGraph.setEdge(key, lib); - } - } - } - // Check for cycles in the graph, the dependency graph needs to be a tree otherwise there's an error - if (!isAcyclic(dependsGraph)) - { - console.log("ERROR: Cycles in dependency graph"); - dependsGraph.edges().forEach(function(o){ - console.log(o.v+" -- depends on --> "+o.w); - }); - errorCallback(new DeployError("Error linker found cyclic dependencies. Adjust your import statements to remove cycles.")); - return null; - } - return dependsGraph; - }, - - link_dependencies: function(config, contract_name) { - var self = this; - return function(dependency_addresses) { - var contract = config.contracts.classes[contract_name]; - - //All of the library dependencies to this contract have been deployed - //Inject the address of each lib into this contract and then deploy it. - dependency_addresses.forEach(function(lib) { - if (config.argv.quietDeploy == null) { - console.log("Linking Library: " + lib.name + " to contract: " + contract_name + " at address: " + lib.address); - } - - var bin_address = lib.address.replace("0x", ""); - var re = new RegExp("__" + lib.name + "_*", "g"); - contract.binary = contract.unlinked_binary.replace(re, bin_address); - }); - - return self.createContractAndWait(config, contract_name); - } - }, - - deploy: function(config, compile, done_deploying) { - var self = this; - - if (typeof compile == "function") { - done_deploying = compile; - compile = true; - } - - if (compile == null) { - compile == true; - } - - async.series([ - function(c) { - self.get_account(config, c); - }, - function(c) { - if (compile == true) { - self.compile_necessary(config, c); - } else { - c(); - } - }, - function(c) { - Pudding.setWeb3(config.web3); - var dependsGraph = self.build_deploy_dependency_graph(config, c); - - if( dependsGraph == null) { - return; - } - - var dependsOrdering = postOrder(dependsGraph, dependsGraph.nodes()); - var deploy_promise = null; - var contract_name; // This is in global scope so that it can be used in the .catch below - - // Iterate over the dependency grpah in post order, deploy libraries first so we can - // capture their addresses and use them to deploy the contracts that depend on them - for(var i = 0; i < dependsOrdering.length; i++) { - contract_name = dependsOrdering[i]; - var contract_class = config.contracts.classes[contract_name]; - - if (contract_class == null) { - c(new DeployError("Could not find contract '" + contract_name + "' for deployment. Check truffle.json.")); - return; - } - - var dependencies = dependsGraph.successors(contract_name); - - // When we encounter a Library that is not dependant on other libraries, we can just - // deploy it as normal - if (dependencies.length == 0) { - deploy_promise = self.createContractAndWait(config, contract_name); - - // Store the promise in the graph so we can fetch it later - dependsGraph.setNode(contract_name, deploy_promise); - } - // Contracts that have dependencies need to wait until those dependencies have been deployed - // so we can inject the address into their byte code - else - { - // Collect all the promises for the libraries this contract depends on into a list - // NOTE: since this loop is traversing in post-order, we can be assured that this list - // will contain ALL of the dependencies of this contract - var depends_promises = dependencies.map(dependsGraph.node, dependsGraph); - - // Wait for all the dependencies to be committed and then do the linking step - deploy_promise = Promise.all(depends_promises).then( - self.link_dependencies(config, contract_name) - ); - - // It's possible that this contract is a dependency of some other contract so we store - // it in the graph just in case - dependsGraph.setNode(contract_name,deploy_promise); - } - } - ///Now wait for all of the outstanding deployments to complete - Promise.all(dependsGraph.nodes().map(dependsGraph.node, dependsGraph)) - .then(function(deployed_contracts) { - deployed_contracts.forEach(function(a) { - config.contracts.classes[a.name].address = a.address; - }); - c(); - }).catch(function(err) { - c(new DeployError(err.message, contract_name)); - }); - }, - function(c) { - self.write_contracts(config, "built contract files", c); - }, - function(c) { - self.after_deploy(config, c); - } - ], function(err) { - if (err != null) { - done_deploying(err); - return; - } else { - done_deploying(); - } - }); - }, - after_deploy: function(config, done) { async.eachSeries(config.app.resolved.after_deploy, function(file, iterator_callback) { if (config.argv.quietDeploy == null) { diff --git a/lib/profiler.js b/lib/profiler.js index 5dcf315b165..f068afef207 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -29,7 +29,7 @@ module.exports = { this.all_contracts(source_directory, function(err, files) { var expected_build_files = files.map(function(file) { - return path.join(build_directory, path.relative(source_directory, file)); + return path.join(build_directory, path.dirname(path.relative(source_directory, file)), path.basename(file) + ".js"); }); async.map(files, fs.stat, function(err, file_stats) { @@ -55,7 +55,7 @@ module.exports = { } var modified_time = (file_stat.mtime || file_stat.ctime).getTime(); - var built_time = (built_file_stats.mtime || built_file_stats.ctime).getTime(); + var built_time = (built_file_stat.mtime || built_file_stat.ctime).getTime(); if (modified_time > built_time) { updated.push(files[i]); From 8e0fd1122a3c3e58a46f27f8eb6f62c9a36ca27f Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 23 May 2016 09:42:43 -0700 Subject: [PATCH 08/26] Continue work on managing networks through the config. Currently unfinished. --- lib/config.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/config.js b/lib/config.js index 7a47dece461..9e1103fff78 100644 --- a/lib/config.js +++ b/lib/config.js @@ -8,18 +8,20 @@ var findUp = require("find-up"); var DEFAULT_CONFIG_FILENAME = "truffle.js"; -function Config(truffle_directory, working_directory) { +function Config(truffle_directory, working_directory, network) { var self = this; this._values = { truffle_directory: truffle_directory || path.resolve(path.join(__dirname, "../")), - working_directory: working_directory || process.cwd() + working_directory: working_directory || process.cwd(), + network: network || "default" }; var props = { - // First two already set. + // First three already set. truffle_directory: function() {}, working_directory: function() {}, + network: function() {}, build_directory: function() { return path.join(self.working_directory, "build"); @@ -39,9 +41,6 @@ function Config(truffle_directory, working_directory) { test_file_extension_regexp: function() { return /.*\.(js|es|es6|jsx)$/ }, - network: function() { - return "default" - }, networks: function() { return { "default": {}, @@ -68,7 +67,7 @@ function Config(truffle_directory, working_directory) { return { host: "localhost", port: "8545", - gas: 3141592, + gas: 4712388, gasPrice: 100000000000, // 100 Shannon, from: null } @@ -93,16 +92,16 @@ Config.prototype.addProp = function(key, default_getter) { }); }; -Config.prototype.getProvider = function(network_id) { - if (network_id == null) { - network_id = "default"; - } +Config.prototype.getProvider = function(options) { + options = options || {}; + + var network_id = options.network_id || this.network; if (this.networks[network_id] == null) { throw new ConfigurationError("Cannot find network '" + network_id + "'"); } - return Provider.create(_.merge({}, this.rpc, this.networks[network_id])); + return Provider.create(_.merge(options, this.rpc, this.networks[network_id])); }; // Helper function for expecting paths to exist. @@ -144,7 +143,7 @@ Config.default = function() { return new Config(); }; -Config.detect = function(filename) { +Config.detect = function(network, filename) { if (filename == null) { filename = DEFAULT_CONFIG_FILENAME; } @@ -155,17 +154,23 @@ Config.detect = function(filename) { throw new ConfigurationError("Could not find suitable configuration file."); } - return this.load(file); + return this.load(file, network); }; -Config.load = function(file) { +Config.load = function(file, network) { var config = new Config(); config.working_directory = path.dirname(path.resolve(file)); var static_config = requireNoCache(file); - return _.merge(config, static_config); + config = _.merge(config, static_config); + + if (network) { + config.network = network; + } + + return config; }; module.exports = Config; From 31c9bd097494e07e8c1de351d3450ca21a82a4ec Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 23 May 2016 09:44:48 -0700 Subject: [PATCH 09/26] Rewrite test runner to new migrations systems. In doing so, create a contracts provisioner so we don't have to write the same code all the time (i.e., tests and migrations). --- cli.js | 109 +++++--- lib/contracts.js | 53 ++-- lib/migrate.js | 158 ++++++------ lib/test.js | 634 +++++++++++++++++------------------------------ package.json | 1 + 5 files changed, 405 insertions(+), 550 deletions(-) diff --git a/cli.js b/cli.js index 661a7cb9d6b..31ecfc575c4 100755 --- a/cli.js +++ b/cli.js @@ -7,10 +7,13 @@ var fs = require("fs"); var chokidar = require('chokidar'); var deasync = require("deasync"); var colors = require('colors/safe'); +var temp = require("temp").track(); +var filesSync = deasync(require("node-dir").files); var Truffle = require('./index.js'); var ConfigurationError = require("./lib/errors/configurationerror"); var ExtendableError = require("./lib/errors/extendableerror"); +var copy = require('./lib/copy'); var argv = require('yargs').argv; @@ -33,6 +36,10 @@ var registerTask = function(name, description, fn) { }; } +var printNetwork = function() { + console.log("Using network " + environment + "."); +}; + var printSuccess = function() { console.log(colors.green("Completed without errors on " + new Date().toString())); }; @@ -130,7 +137,7 @@ registerTask('init', "Initialize new Ethereum project, including example contrac }); registerTask('create:contract', "Create a basic contract", function(done) { - var config = Truffle.config.detect(); + var config = Truffle.config.detect(environment); var name = argv.name; @@ -146,7 +153,10 @@ registerTask('create:contract', "Create a basic contract", function(done) { }); registerTask('create:test', "Create a basic test", function(done) { - var config = Truffle.config.detect(); + // Force the test environment. + environment = "test"; + printNetwork(); + var config = Truffle.config.detect(environment); var name = argv.name; @@ -162,18 +172,18 @@ registerTask('create:test', "Create a basic test", function(done) { }); registerTask('compile', "Compile contracts", function(done) { - var config = Truffle.config.detect(); + var config = Truffle.config.detect(environment); Truffle.contracts.compile({ - all: config.argv.allContracts === true, - source_directory: config.contracts.directory, - build_directory: config.contracts.build_directory, - quiet: config.argv.quiet === true, - strict: config.argv.strict === true + all: argv.all === true, + source_directory: config.contracts_directory, + build_directory: config.contracts_build_directory, + quiet: argv.quiet === true, + strict: argv.strict === true }, done); }); registerTask('build', "Build development version of app", function(done) { - var config = Truffle.config.detect(); + var config = Truffle.config.detect(environment); Truffle.build.build(config, function(err) { done(err); if (err == null) { @@ -183,8 +193,7 @@ registerTask('build', "Build development version of app", function(done) { }); registerTask('dist', "Create distributable version of app (minified)", function(done) { - var config = Truffle.config.detect(); - console.log("Using environment " + config.environment + "."); + var config = Truffle.config.detect(environment); Truffle.build.dist(config, function(err) { done(err); if (err == null) { @@ -194,9 +203,7 @@ registerTask('dist', "Create distributable version of app (minified)", function( }); registerTask('migrate', "Run migrations", function(done) { - var config = Truffle.config.detect(); - - //console.log("Using network " + config.network + "."); + var config = Truffle.config.detect(environment); Truffle.contracts.compile({ all: argv.all === true || argv.allContracts === true, @@ -209,15 +216,17 @@ registerTask('migrate', "Run migrations", function(done) { Truffle.migrate.run({ migrations_directory: config.migrations_directory, - contracts_directory: config.contracts_build_directory, - provider: config.getProvider(), + build_directory: config.contracts_build_directory, + provider: config.getProvider({ + verbose: argv.verboseRpc + }), reset: argv.reset || false }, done); }); }); registerTask('exec', "Execute a JS file within truffle environment. Script *must* call process.exit() when finished.", function(done) { - var config = Truffle.config.detect(); + var config = Truffle.config.detect(environment); var file = argv.file; @@ -246,34 +255,68 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must // --no-color: Disable color // More to come. registerTask('test', "Run tests", function(done) { - var config = Truffle.config.detect(); - - console.log("Using environment " + config.environment + "."); + environment = "test"; + var config = Truffle.config.detect(environment); - // Ensure we're quiet about deploys during tests. - config.argv.quiet = true; + var files = []; - var file = argv.file; - - if (file == null && argv._.length > 1) { - file = argv._[1]; + if (argv.file) { + files = [argv.file]; + } else if (argv._.length > 1) { + Array.prototype.push.apply(files, argv._); + files.shift(); // Remove "test" } - if (file == null) { - Truffle.test.run(config, done); - } else { - Truffle.test.run(config, file, done); + if (files.length == 0) { + files = filesSync(config.test_directory); } + + files = files.filter(function(file) { + return file.match(config.test_file_extension_regexp) != null; + }); + + // if (files.length == 0) { + // return done(new Error("Cannot find any valid test files. Bailing.")); + // } + + temp.mkdir('test-', function(err, temporaryDirectory) { + if (err) return done(err); + + function cleanup() { + var args = arguments; + // Ensure directory cleanup. + temp.cleanup(function(err) { + // Ignore cleanup errors. + done.apply(null, args); + }); + }; + + // Copy all the built files over to a temporary directory, because we + // don't want to save any tests artifacts. + copy(config.contracts_build_directory, temporaryDirectory, function(err) { + if (err) return done(err); + + Truffle.test.run({ + compileAll: argv.compileAll, + contracts_directory: config.contracts_directory, + build_directory: temporaryDirectory, + migrations_directory: config.migrations_directory, + test_files: files, + provider: config.getProvider({ + verbose: argv.verboseRpc + }), + }, cleanup); + }); + }); }); registerTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(done) { - var config = Truffle.config.detect(); + var config = Truffle.config.detect(environment); Truffle.console.run(config, done); }); registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { - var config = Truffle.config.detect(); - console.log("Using environment " + config.environment + "."); + var config = Truffle.config.detect(environment); Truffle.serve.start(config, argv.port || argv.p || "8080", function() { runTask("watch"); }); diff --git a/lib/contracts.js b/lib/contracts.js index a77be5075f5..af137411f5b 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -2,37 +2,50 @@ var async = require("async"); var fs = require("fs"); var mkdirp = require("mkdirp"); var path = require("path"); -var solc = require("solc"); -var path = require("path"); var Compiler = require("./compiler"); var Exec = require("./exec"); var Pudding = require("ether-pudding"); -var DeployError = require("./errors/deployerror"); -var graphlib = require("graphlib"); -var Graph = require("graphlib").Graph; -var isAcyclic = require("graphlib/lib/alg").isAcyclic; -var postOrder = require("graphlib/lib/alg").postorder; -var requireNoCache = require("./require-nocache"); +var Web3 = require("web3"); var Contracts = { account: null, - get_account: function(config, callback) { + provision: function(options, callback) { var self = this; + var logger = options.logger || console; + var web3 = new Web3(); + web3.setProvider(options.provider); - if (config.app.resolved.rpc.from != null) { - this.account = config.app.resolved.rpc.from; - } + Pudding.requireAll({ + source_directory: options.build_directory, + provider: options.provider + }, function(err, contracts) { + if (err) return callback(err); - if (this.account != null) { - return callback(null, this.account); - } + web3.eth.getAccounts(function(err, accounts) { + if (err) return callback(err); + + // Add contracts to context and prepare contracts. + contracts.forEach(function(contract) { + // Set defaults based on configuration. + contract.defaults({ + from: options.from || accounts[0], + gas: options.gas, + gasPrice: options.gasPrice + }); + + if (options.network_id) { + contract.setNetwork(network_id); + } - config.web3.eth.getAccounts(function(err, result) { - if (err != null) return callback(err); + // If all addresses should be reset. + if (options.reset == true) { + contract.address = null; + } + }); - self.account = result[0]; - callback(null, self.account); + callback(null, contracts); + }); }); }, @@ -78,7 +91,7 @@ var Contracts = { return; } - if (options.quiet != true) { + if (options.quiet != true && options.quietWrite != true) { console.log("Writing artifacts to ." + path.sep + path.relative(process.cwd(), options.build_directory)); } diff --git a/lib/migrate.js b/lib/migrate.js index a6dbdb36957..027f51a4de9 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -1,6 +1,7 @@ var fs = require("fs"); var dir = require("node-dir"); var path = require("path"); +var Contracts = require("./contracts"); var Pudding = require("ether-pudding"); var Deployer = require("./deployer"); var Profiler = require("./profiler"); @@ -14,100 +15,77 @@ function Migration(file) { this.number = parseInt(path.basename(file)); }; -Migration.prototype.run = function(options, callback) { +Migration.prototype.run = function(options, contracts, callback) { var self = this; var logger = options.logger || console; + + if (options.quiet) { + logger = { + log: function() {} + } + }; + var web3 = new Web3(); web3.setProvider(options.provider); logger.log("Running migration: " + path.relative(options.migrations_directory, this.file)); - Pudding.requireAll({ - source_directory: options.contracts_directory, - provider: options.provider || (options.web3 != null ? options.web3.currentProvider : null) - }, function(err, contracts) { - if (err) return callback(err); - - web3.eth.getAccounts(function(err, accounts) { - if (err) return callback(err); - - // Initial context. - var context = { - web3: web3 - }; + // Initial context. + var context = { + web3: web3 + }; - // Add contracts to context and prepare contracts. - contracts.forEach(function(contract) { - context[contract.contract_name] = contract; + // Add contracts to context and prepare contracts. + contracts.forEach(function(contract) { + context[contract.contract_name] = contract; - // Set defaults based on configuration. - contract.defaults({ - from: options.from || accounts[0], - gas: options.gas, - gasPrice: options.gasPrice - }); - - // During migrations, we could be on a network that takes a long time to accept - // transactions (i.e., contract deployment close to block size). Because successful - // migration is more important than wait time in those cases, we'll synchronize "forever". - contract.synchronization_timeout = 0; - - if (options.network_id) { - contract.setNetwork(network_id); - } - - // If this is the initial migration, assume no contracts - // have been deployed previously. - if (options.reset == true) { - contract.address = null; - } - }); + // During migrations, we could be on a network that takes a long time to accept + // transactions (i.e., contract deployment close to block size). Because successful + // migration is more important than wait time in those cases, we'll synchronize "forever". + contract.synchronization_timeout = 0; + }); - if (options.reset == true) { - options.reset = false; + var deployer = new Deployer({ + logger: { + log: function(msg) { + logger.log(" " + msg); } + }, + contracts: contracts + }); - var deployer = new Deployer({ - logger: { - log: function(msg) { - logger.log(" " + msg); - } - }, - contracts: contracts - }); - - var finish = function(err) { - if (err) return callback(err); - deployer.start().then(function() { - logger.log("Saving successful migration to network..."); - var Migrations = context["Migrations"]; - if (Migrations && Migrations.address) { - return Migrations.deployed().setCompleted(self.number); - } - }).then(function() { - logger.log("Saving artifacts..."); - return Pudding.saveAll(contracts, options.contracts_directory, options.network_id); - }).then(function() { - callback(); - }).catch(function(e) { - logger.log("Error encountered, bailing. Network state unknown. Review successful transactions manually."); - callback(e); - }); - }; - - Require.file({ - file: self.file, - context: context, - args: [deployer] - }, function(err, fn) { - if (fn.length <= 1) { - fn(deployer); - finish(); - } else { - fn(deployer, finish); - } - }); + var finish = function(err) { + if (err) return callback(err); + deployer.start().then(function() { + if (options.save === false) return; + logger.log("Saving successful migration to network..."); + var Migrations = context["Migrations"]; + if (Migrations && Migrations.address) { + return Migrations.deployed().setCompleted(self.number); + } + }).then(function() { + if (options.save === false) return; + logger.log("Saving artifacts..."); + return Pudding.saveAll(contracts, options.build_directory, options.network_id); + }).then(function() { + callback(); + }).catch(function(e) { + logger.log("Error encountered, bailing. Network state unknown. Review successful transactions manually."); + callback(e); }); + }; + + Require.file({ + file: self.file, + context: context, + args: [deployer] + }, function(err, fn) { + if (fn.length <= 1) { + fn(deployer); + finish(); + } else { + fn(deployer, finish); + } }); }; @@ -178,16 +156,20 @@ var Migrate = { }, runMigrations: function(migrations, options, callback) { - async.eachSeries(migrations, function(migration, finished) { - migration.run(options, function(err) { - if (err) return finished(err); - finished(); - }); - }, callback); + Contracts.provision(options, function(err, contracts) { + if (err) return callback(err); + + async.eachSeries(migrations, function(migration, finished) { + migration.run(options, contracts, function(err) { + if (err) return finished(err); + finished(); + }); + }, callback); + }); }, lastCompletedMigration: function(options, callback) { - var migrations_contract = path.resolve(path.join(options.contracts_directory, "Migrations.sol.js")); + var migrations_contract = path.resolve(path.join(options.build_directory, "Migrations.sol.js")); Pudding.requireFile(migrations_contract, function(err, Migrations) { if (err) return callback(new Error("Could not find built Migrations contract.")); diff --git a/lib/test.js b/lib/test.js index ea325492aa4..4b1fc014f67 100644 --- a/lib/test.js +++ b/lib/test.js @@ -3,70 +3,52 @@ var chai = require("chai"); var dir = require("node-dir"); var path = require("path"); var fs = require("fs"); - +var Web3 = require("web3"); var Contracts = require("./contracts"); - +var Migrate = require('./migrate'); var Pudding = require("ether-pudding"); -var PuddingLoader = require("ether-pudding/loader"); -var loadconf = require("./loadconf"); var Promise = require("bluebird"); - var ExtendableError = require("./errors/extendableerror"); - var SolidityCoder = require("web3/lib/solidity/coder.js"); chai.use(require("./assertions")); -var rpc = function(method, arg, cb) { - var req = { - jsonrpc: "2.0", - method: method, - id: new Date().getTime() - }; - if (arguments.length == 3) { - req.params = arg; - } else { - cb = arg; - } - - var intermediary = function(err, result) { - if (err != null) { - cb(err); - return; - } - - if (result.error != null) { - cb(new Error("RPC Error: " + (result.error.message || result.error))); - return; - } +var BEFORE_TIMEOUT = 120000; +var TEST_TIMEOUT = 300000; + +function TestRunner(options) { + this.options = options; + this.logger = options.logger || console; + this.provider = options.provider; + this.can_shapshot = false; + this.initial_snapshot = null; + this.known_events = {}; + this.web3 = new Web3(); + this.web3.setProvider(options.provider); + this.contracts = []; + + // For each test + this.currentTestStartBlock = null; +}; - cb(null, result); - }; +TestRunner.prototype.initialize = function(callback) { + var self = this; - web3.currentProvider.sendAsync(req, intermediary); -}; + var afterStateReset = function(err) { + if (err) return callback(err); -// Deploy all configured contracts to the chain without recompiling -var redeploy_contracts = function(config, recompile, done) { - Contracts.deploy(config, recompile, function(err) { - if (err != null) { - // Format our error messages so they print better with mocha. - if (err instanceof ExtendableError) { - err.formatForMocha(); - } + Contracts.provision(self.options, function(err, contracts) { + if (err) return callback(err); - done(err); - return; - } + self.contracts = contracts; + self.known_events = {}; - Pudding.setWeb3(config.web3); - PuddingLoader.load(config.environments.current.directory, Pudding, global, function(err, contract_names) { // Go through all abis and record events we know about. - for (var i = 0; i < contract_names.length; i++) { - var name = contract_names[i]; - Truffle.contracts[name] = global[name]; + self.contracts.forEach(function(contract) { + // make the contract globally available + global[contract.contract_name] = contract; - var abi = global[name].abi; + var abi = contract.abi; for (var j = 0; j < abi.length; j++) { var item = abi[j]; @@ -74,414 +56,252 @@ var redeploy_contracts = function(config, recompile, done) { if (item.type == "event") { var signature = item.name + "(" + item.inputs.map(function(param) {return param.type;}).join(",") + ")"; - Truffle.known_events["0x" + web3.sha3(signature)] = { + self.known_events["0x" + web3.sha3(signature)] = { signature: signature, abi_entry: item }; } } - } + }); - done(); + callback(); }); - }); -}; - -var Test = { - setup: function(config, callback) { - var BEFORE_TIMEOUT = 120000; - var TEST_TIMEOUT = 300000; - - // `accounts` will be populated before each contract() - // invocation and passed to it so tests don't have to call it themselves. - var accounts = []; - - global.web3 = config.web3; - - // Make Promise global so tests have access to it. - global.Promise = Promise; - - // Use custom assertions. - global.assert = chai.assert; - - global.Truffle = { - can_snapshot: false, - starting_snapshot_id: null, - contracts: {}, - known_events: {}, - - redeploy: function(recompile) { - return new Promise(function(resolve, reject) { - redeploy_contracts(config, recompile, function(err) { - if (err != null) { - reject(err); - } else { - resolve(); - } - }); - }); - }, + }; - handle_errs: function(done) { Promise.onPossiblyUnhandledRejection(done); }, + if (self.initial_snapshot == null) { + // Make the initial deployment (full migration). + self.deploy(function(err) { + if (err) return callback(err); - reset: function(cb) { - var self = this; - this.revert(this.starting_snapshot_id, function(err, result) { - if (err != null) { - cb(err); - return; - } + self.snapshot(function(err, initial_snapshot) { + if (err == null) { + self.can_snapshot = true; + self.initial_snapshot = initial_snapshot; + } + afterStateReset(); + }); + }); + } else { + self.resetState(afterStateReset); + } +}; - // Snapshot again, resetting the snapshot id. - self.snapshot(function(err, result) { - if (err != null) { - cb(err); - return; - } +TestRunner.prototype.deploy = function(callback) { + Migrate.run({ + migrations_directory: this.options.migrations_directory, + build_directory: this.options.build_directory, + provider: this.options.provider, + reset: true, + quiet: true + }, callback); +}; - Truffle.starting_snapshot_id = result.result; - cb(); - }); - }); - }, +TestRunner.prototype.resetState = function(callback) { + var self = this; + if (this.can_snapshot) { + this.revert(this.initial_snapshot, function(err) { + if (err) return callback(err); + self.snapshot(function(err, snapshot) { + if (err) return callback(err); + self.initial_snapshot = snapshot; + callback(); + }); + }); + } else { + this.deploy(callback); + } +}; - snapshot: function(cb) { - rpc("evm_snapshot", cb); - }, +TestRunner.prototype.startTest = function(mocha, callback) { + var self = this; + this.web3.eth.getBlockNumber(function(err, result) { + if (err) return callback(err); - revert: function(snapshot_id, cb) { - rpc("evm_revert", [snapshot_id], cb); - } - }; + result = web3.toBigNumber(result); - global.contract = function(name, opts, tests) { - if (typeof opts == "function") { - tests = opts; - opts = { - reset_state: false - }; - } + // Add one in base 10 + self.currentTestStartBlock = result.plus(1, 10); - if (opts.reset_state == null) { - opts.reset_state = false; - } - - describe("Contract: " + name, function() { - this.timeout(TEST_TIMEOUT); + callback(); + }); +}; - //var _original_contracts = {}; +TestRunner.prototype.endTest = function(mocha, callback) { + var self = this; - before("reset evm before each suite", function(done) { - this.timeout(BEFORE_TIMEOUT); + if (mocha.currentTest.state != "failed") { + return callback(); + } - if (Truffle.can_snapshot == false) { - return done(); - } + var logs = []; - // If we can snapshot, but haven't yet deployed, let's - // deploy for the first time. - if (Truffle.starting_snapshot_id == null) { - redeploy_contracts.call(this, config, false, function(err) { - if (err != null) { - done(err); - return; - } - - Truffle.snapshot(function(err, result) { - if (err != null) { - done(err); - return; - } - - Truffle.starting_snapshot_id = result.result; - done(); - }); - }); - } else { - Truffle.reset(done); - } - }); + // There's no API for eth_getLogs? + this.rpc("eth_getLogs", [{ + fromBlock: "0x" + this.currentTestStartBlock.toString(16) + }], function(err, result) { + if (err) return callback(err); - before("redeploy before each suite", function(done) { - this.timeout(BEFORE_TIMEOUT); + var logs = result.result; - // We don't need this step if we were able to reset. - if (Truffle.can_snapshot == true) { - return done(); - } + if (logs.length == 0) { + self.logger.log(" > No events were emitted"); + return callback(); + } - redeploy_contracts.call(this, config, false, function(err) { + self.logger.log("\n Events emitted during test:"); + self.logger.log( " ---------------------------"); + self.logger.log(""); - // Store address that was first deployed, in case we redeploy - // from within a test - // for (var name in config.contracts.classes) { - // var contract = global[name]; - // _original_contracts[name] = contract.address; - // } + logs.forEach(function(log) { + var event = self.known_events[log.topics[0]]; - done(err); - }); - }); - - // afterEach("restore contract address", function(done) { - // for (var name in _original_contracts) { - // global[name].address = _original_contracts[name]; - // } - // done(); - // }); + if (event == null) { + return; + } - var startingBlock; + var types = event.abi_entry.inputs.map(function(input) { + return input.indexed == true ? null : input.type; + }).filter(function(type) { + return type != null; + }); + var values = SolidityCoder.decodeParams(types, log.data.replace("0x", "")); + var index = 0; + + var line = " " + event.abi_entry.name + "("; + line += event.abi_entry.inputs.map(function(input) { + var value; + if (input.indexed == true) { + value = ""; + } else { + value = values[index]; + index += 1; + } - beforeEach("record block number of test start", function(done) { - web3.eth.getBlockNumber(function(err, result) { - if (err) return done(err); + return input.name + ": " + value.toString(); + }).join(", "); + line += ")"; + self.logger.log(line); + }); + self.logger.log( "\n ---------------------------"); + callback(); + }); +}; - result = web3.toBigNumber(result); +TestRunner.prototype.snapshot = function(callback) { + this.rpc("evm_snapshot", function(err, result) { + if (err) return callback(err); + callback(null, result.result); + }); +}, - // Add one in base 10 - startingBlock = result.plus(1, 10); +TestRunner.prototype.revert = function(snapshot_id, callback) { + this.rpc("evm_revert", [snapshot_id], callback); +} - done(); - }); - }); +TestRunner.prototype.rpc = function(method, arg, cb) { + var req = { + jsonrpc: "2.0", + method: method, + id: new Date().getTime() + }; + if (arguments.length == 3) { + req.params = arg; + } else { + cb = arg; + } - afterEach("check logs on failure", function(done) { - if (this.currentTest.state != "failed") { - return done(); - } - var logs = []; - - // There's no API for eth_getLogs? - rpc("eth_getLogs", [{ - fromBlock: "0x" + startingBlock.toString(16) - }], function(err, result) { - if (err) return done(err); - - var logs = result.result; - - if (logs.length == 0) { - console.log(" > No events were emitted"); - return done(); - } - - console.log("\n Events emitted during test:"); - console.log( " ---------------------------"); - console.log(""); - - // logs.sort(function(a, b) { - // var ret = a.blockNumber - b.blockNumber; - // if (ret == 0) { - // return a.logIndex - b.logIndex; - // } - // return ret; - // }); - - - logs.forEach(function(log) { - var event = Truffle.known_events[log.topics[0]]; - - if (event == null) { - return; - } - - var types = event.abi_entry.inputs.map(function(input) { - return input.indexed == true ? null : input.type; - }).filter(function(type) { - return type != null; - }); - var values = SolidityCoder.decodeParams(types, log.data.replace("0x", "")); - var index = 0; - - var line = " " + event.abi_entry.name + "("; - line += event.abi_entry.inputs.map(function(input) { - var value; - if (input.indexed == true) { - value = ""; - } else { - value = values[index]; - index += 1; - } - - return input.name + ": " + value.toString(); - }).join(", "); - line += ")"; - console.log(line); - }); - console.log( "\n ---------------------------"); - done(); - }); - }); + var intermediary = function(err, result) { + if (err != null) { + cb(err); + return; + } - if (opts.reset_state == true) { - var snapshot_id; - beforeEach("snapshot state before each test", function(done) { - if (!Truffle.can_snapshot) { - // can't snapshot/revert, redeploy instead - return redeploy_contracts(false, done); - } - Truffle.snapshot(function(err, result) { - snapshot_id = result.result; - done(); - }); - }); + if (result.error != null) { + cb(new Error("RPC Error: " + (result.error.message || result.error))); + return; + } - afterEach("revert state after each test", function(done) { - if (!Truffle.can_snapshot) { - return done(); - } - Truffle.revert(snapshot_id, function(err, ret) { - done(); - }); - }); - } + cb(null, result); + }; - tests(accounts); - }); - }; + this.provider.sendAsync(req, intermediary); +}; - (new Promise(function(accept, reject) { - // Get the accounts - web3.eth.getAccounts(function(error, accs) { - if (error != null) { - reject(error); +var Test = { + run: function(options, callback) { + // Compile if needed. This will + Contracts.compile({ + all: options.compileAll === true, + source_directory: options.contracts_directory, + build_directory: options.build_directory, + quiet: false, + quietWrite: true, + strict: options.strict + }, function(err) { + if (err) return callback(err); + + // Override console.warn() because web3 outputs gross errors to it. + // e.g., https://github.com/ethereum/web3.js/blob/master/lib/web3/allevents.js#L61 + // Output looks like this during tests: https://gist.github.com/tcoulter/1988349d1ec65ce6b958 + var warn = console.warn; + console.warn = function(message) { + if (message == "cannot find event for log") { return; + } else { + warn.apply(console, arguments); } + }; - for (var account of accs) { - accounts.push(account); - } + // `accounts` will be populated before each contract() invocation + // and passed to it so tests don't have to call it themselves. + var web3 = new Web3(); + web3.setProvider(options.provider); - Pudding.defaults({ - from: accounts[0], - gas: 3141592 - }); + web3.eth.getAccounts(function(err, accounts) { + if (err) return callback(err); - accept(); - }); - })).then(function() { - return new Promise(function(accept, reject) { - // Compile if needed. + global.web3 = web3; + global.assert = chai.assert; - if (config.argv.compile === false) { - accept(); - return; - } + var runner = new TestRunner(options); - // Compile all the contracts and get the available accounts. - // We only need to do this once, and can get it outside of - // mocha. - console.log("Compiling contracts..."); - Contracts.compile(config, function(err) { - if (err != null) { - reject(err); - } else { - accept(); - } - }); - }); - }).then(function() { - return new Promise(function(accept, reject) { - // Check to see if the ethereum client can snapshot - Truffle.snapshot(function(err, result) { - if (err == null) { - Truffle.can_snapshot = true; + global.contract = function(name, tests) { + if (typeof opts == "function") { + tests = name; + name = ""; } - accept(); - }); - }); - }).then(callback).catch(callback); - }, - - run: function(config, file, callback) { - // Override console.warn() because web3 outputs gross errors to it. - // e.g., https://github.com/ethereum/web3.js/blob/master/lib/web3/allevents.js#L61 - // Output looks like this during tests: https://gist.github.com/tcoulter/1988349d1ec65ce6b958 - var warn = console.warn; - console.warn = function(message) { - if (message == "cannot find event for log") { - return; - } else { - warn.apply(console, arguments); - } - }; - - if (typeof file == "function") { - callback = file; - file = null; - config.expect(config.tests.directory, "tests directory"); - } - - if (file != null) { - if (path.isAbsolute(file) == false) { - file = path.resolve(config.working_dir, file); - } - - config.expect(file, "test file"); - } - - this.setup(config, function(err) { - if (err != null) { - callback(err); - return; - } - // Change current working directory to that of the project. - process.chdir(config.working_dir); - __dirname = process.cwd(); + describe("Contract: " + name, function() { + this.timeout(TEST_TIMEOUT); - // If errors aren't caught in Promises, make sure they're thrown - // and don't keep the process open. - Promise.onPossiblyUnhandledRejection(function(e, promise) { - throw e; - }); - - var mocha = new Mocha(config.app.resolved.mocha || { - useColors: true - }); + before("prepare suite", function(done) { + this.timeout(BEFORE_TIMEOUT); + runner.initialize(done); + }); - var runMocha = function() { - // TODO: Catch any errors here, and fail. - mocha.run(function(failures) { - callback(null, failures); - }); - }; + beforeEach("before test", function(done) { + runner.startTest(this, done); + }); - if (file != null) { - mocha.addFile(file); - runMocha(); - return; - } + afterEach("after test", function(done) { + runner.endTest(this, done); + }); - dir.files(config.tests.directory, function(err, files) { - if (err != null) { - callback(err); - return; - } + tests(accounts); + }); + }; - // if running via the 'watch:tests' task, we want to be able to run - // (require) our test files repeatedly, so this is a hack to make it - // work. we copy each test file to a temp filename and load that - // instead of the original to avoid getting cached. - // files = files.map(function(f) { - // var src = fs.readFileSync(f); - // f = temp.path({prefix: "truffle-", suffix: "-"+path.basename(f)}) - // fs.writeFileSync(f, src); - // return f; - // }); - - var mocha = new Mocha(config.app.resolved.mocha || { + var mocha = new Mocha(options.mocha || { useColors: true }); - for (var file of files.sort()) { - if (file.match(config.tests.filter)) { - mocha.addFile(file); - } - } + // // Change current working directory to that of the project. + // process.chdir(config.working_dir); + // __dirname = process.cwd(); - // Change current working directory to that of the project. - process.chdir(config.working_dir); - __dirname = process.cwd(); + options.test_files.forEach(function(file) { + mocha.addFile(file); + }); // If errors aren't caught in Promises, make sure they're thrown // and don't keep the process open. @@ -489,11 +309,7 @@ var Test = { throw e; }); - // TODO: Catch any errors here, and fail. mocha.run(function(failures) { - // files.forEach(function(f) { - // fs.unlinkSync(f); // cleanup our temp files - // }); console.warn = warn; callback(null, failures); }); diff --git a/package.json b/package.json index e627f238be9..288db465c53 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "solc": "^0.3.1-1", "solidity-parser": "^0.0.5", "spawn-args": "^0.1.0", + "temp": "^0.8.3", "truffle-default-builder": "0.0.9", "uglify-js": "^2.6.1", "web3": "^0.15.2", From bd4f56791070fe7db8aaba4868d3d7c4e6309d89 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 23 May 2016 09:46:00 -0700 Subject: [PATCH 10/26] Detect when abstract contracts are passed to autolink(), and don't do anything if there's nothing to link. --- lib/linker.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/linker.js b/lib/linker.js index 639d295d468..8faeacd874f 100644 --- a/lib/linker.js +++ b/lib/linker.js @@ -25,6 +25,9 @@ module.exports = { }, autolink: function(contract, available_contracts, logger) { + // Abstract contract passed. + if (contract.binary == null) return; + var self = this; var regex = /__[^_]+_+/g; @@ -32,6 +35,9 @@ module.exports = { var unlinked_libraries = contract.unlinked_binary.match(regex); + // Nothing to link. + if (unlinked_libraries == null) return; + if (unlinked_libraries.length == 0) { throw new Error("Cannot auto link " + contract.contract_name + "; " + contract.contract_name + " has no library dependencies.") } From 851feaf6d2eb102f0ff12d7c5f49dc07b09660db Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 23 May 2016 09:46:42 -0700 Subject: [PATCH 11/26] Allow autolink() to autolink all libraries and contracts if none are passed as parameters. Also allow deploy() to take an array of contracts, or an array of array, which will deploy those contracts in parallel, speeding up that migration. --- lib/deployer.js | 73 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/lib/deployer.js b/lib/deployer.js index b647e44a48d..ab7d6e51212 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -2,6 +2,29 @@ var EventEmitter = require("events").EventEmitter; var inherits = require("util").inherits; var Linker = require("./linker"); +var Actions = { + deploy: function(contract, args, logger) { + return new Promise(function(accept, reject) { + var prefix = "Deploying "; + if (contract.address != null) { + prefix = "Replacing "; + } + + logger.log(prefix + contract.contract_name + "..."); + + // Evaluate any arguments if they're promises + Promise.all(args).then(function(new_args) { + return contract.new.apply(contract, new_args); + }).then(function(instance) { + logger.log(contract.contract_name + ": " + instance.address); + contract.address = instance.address; + accept(); + }).catch(reject); + }); + } +}; + + inherits(Deployer, EventEmitter); function Deployer(options) { @@ -36,8 +59,17 @@ Deployer.prototype.start = function() { }; Deployer.prototype.autolink = function(contract) { + var self = this; this.checkStarted(); + // autolink all contracts available. + if (contract == null) { + Object.keys(this.known_contracts).forEach(function(contract_name) { + self.autolink(self.known_contracts[contract_name]); + }); + return; + } + var self = this; var regex = /__[^_]+_+/g; @@ -63,23 +95,36 @@ Deployer.prototype.deploy = function() { var args = Array.prototype.slice.call(arguments); var contract = args.shift(); - self.chain = this.chain.then(function() { - var prefix = "Deploying "; - if (contract.address != null) { - prefix = "Replacing "; - } + if (Array.isArray(contract)) { + return this.deployMany(contract); + } + + this.chain = this.chain.then(function() { + return Actions.deploy(contract, args, self.logger); + }); - self.logger.log(prefix + contract.contract_name + "..."); + return this.chain; +}; - // Evaluate any arguments if they're promises - return Promise.all(args); - }).then(function(new_args) { - return contract.new.apply(contract, new_args); - }).then(function(instance) { - self.logger.log("Saving deployed address: " + instance.address); - contract.address = instance.address; +Deployer.prototype.deployMany = function(arr) { + var self = this; + this.chain = this.chain.then(function() { + var deployments = arr.map(function(args) { + var contract; + + if (Array.isArray(args)) { + contract = args.shift(); + } else { + contract = args; + args = []; + } + + return Actions.deploy(contract, args, self.logger); + }); + return Promise.all(deployments); }); - return self.chain; + + return this.chain; }; Deployer.prototype.new = function() { From 5533489e3029966f8527fb401ffbbcf6af72ce0a Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Tue, 24 May 2016 09:00:47 -0700 Subject: [PATCH 12/26] Get truffle build/serve working. --- cli.js | 26 ++++++++++++++----- lib/build.js | 67 ++++++++++++++++++------------------------------ lib/config.js | 9 ++++++- lib/contracts.js | 10 ++++---- lib/migrate.js | 4 +-- lib/profiler.js | 2 +- lib/serve.js | 4 +-- lib/test.js | 4 +-- package.json | 6 ++--- 9 files changed, 67 insertions(+), 65 deletions(-) diff --git a/cli.js b/cli.js index 31ecfc575c4..1454750b72c 100755 --- a/cli.js +++ b/cli.js @@ -176,7 +176,7 @@ registerTask('compile', "Compile contracts", function(done) { Truffle.contracts.compile({ all: argv.all === true, source_directory: config.contracts_directory, - build_directory: config.contracts_build_directory, + contracts_build_directory: config.contracts_build_directory, quiet: argv.quiet === true, strict: argv.strict === true }, done); @@ -184,7 +184,17 @@ registerTask('compile', "Compile contracts", function(done) { registerTask('build', "Build development version of app", function(done) { var config = Truffle.config.detect(environment); - Truffle.build.build(config, function(err) { + Truffle.build.build({ + builder: config.build, + build_directory: config.build_directory, + working_directory: config.working_directory, + contracts_build_directory: config.contracts_build_directory, + processors: config.processors, // legacy option for default builder + provider: config.getProvider({ + verbose: argv.verboseRpc + }), + rpc: config.getRPCConfig() + }, function(err) { done(err); if (err == null) { printSuccess(); @@ -206,9 +216,9 @@ registerTask('migrate', "Run migrations", function(done) { var config = Truffle.config.detect(environment); Truffle.contracts.compile({ - all: argv.all === true || argv.allContracts === true, + all: argv.compileAll === true, source_directory: config.contracts_directory, - build_directory: config.contracts_build_directory, + contracts_build_directory: config.contracts_build_directory, quiet: argv.quiet === true, strict: argv.strict === true }, function(err) { @@ -216,7 +226,7 @@ registerTask('migrate', "Run migrations", function(done) { Truffle.migrate.run({ migrations_directory: config.migrations_directory, - build_directory: config.contracts_build_directory, + contracts_build_directory: config.contracts_build_directory, provider: config.getProvider({ verbose: argv.verboseRpc }), @@ -299,7 +309,7 @@ registerTask('test', "Run tests", function(done) { Truffle.test.run({ compileAll: argv.compileAll, contracts_directory: config.contracts_directory, - build_directory: temporaryDirectory, + contracts_build_directory: temporaryDirectory, migrations_directory: config.migrations_directory, test_files: files, provider: config.getProvider({ @@ -317,7 +327,9 @@ registerTask('console', "Run a console with deployed contracts instantiated and registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { var config = Truffle.config.detect(environment); - Truffle.serve.start(config, argv.port || argv.p || "8080", function() { + Truffle.serve.start({ + build_directory: config.build_directory + }, argv.p || argv.port || "8080", function() { runTask("watch"); }); }); diff --git a/lib/build.js b/lib/build.js index 2f189b96909..55e756c8eb7 100644 --- a/lib/build.js +++ b/lib/build.js @@ -1,10 +1,10 @@ var async = require("async"); var Promise = require("bluebird"); var mkdirp = Promise.promisify(require("mkdirp")); -var rimraf = Promise.promisify(require("rimraf")); +var del = require("del"); var fs = require("fs"); var DefaultBuilder = require("truffle-default-builder"); -var PuddingLoader = require("ether-pudding/loader"); +var Contracts = require("./contracts"); var BuildError = require("./errors/builderror"); var child_process = require("child_process"); var spawnargs = require("spawn-args"); @@ -27,7 +27,7 @@ CommandBuilder.prototype.build = function(options, callback) { WORKING_DIRECTORY: options.working_directory, NODE_ENV: options.environment, BUILD_DESTINATION_DIRECTORY: options.destination_directory, - BUILD_CONTRACTS_DIRECTORY: options.contracts_directory, + BUILD_CONTRACTS_DIRECTORY: options.contracts_build_directory, WEB3_PROVIDER_LOCATION: "http://" + options.rpc.host + ":" + options.rpc.port }) }); @@ -50,41 +50,22 @@ CommandBuilder.prototype.build = function(options, callback) { }; var Build = { - clean: function(destination, callback) { + clean: function(options, callback) { + + var destination = options.build_directory; + var contracts_build_directory = options.contracts_build_directory; + // Clean first. - rimraf(destination + '/*').then(function() { + del([destination + '/*', "!" + contracts_build_directory]).then(function() { return mkdirp(destination); }).then(function() { callback(); }).catch(callback); }, - get_contract_data: function(config, callback) { - if (config.app.resolved.include_contracts === false) { - return callback(null, []); - } - - var warning = "Warning: No compiled contracts found. App will be built without compiled contracts."; - - if (fs.existsSync(config.contracts.build_directory) == false) { - console.log(warning); - callback(null, []); - } else { - PuddingLoader.contract_data(config.contracts.build_directory, function(err, contracts) { - if (err) return callback(err); - - if (contracts.length == 0) { - console.log(warning); - } - - callback(null, contracts); - }); - } - }, - // Note: key is a legacy parameter that will eventually be removed. // It's specific to the default builder and we should phase it out. - build: function(config, key, callback) { + build: function(options, key, callback) { var self = this; if (typeof key == "function") { @@ -92,12 +73,13 @@ var Build = { key = "build"; } - var builder = config.app.resolved.build; + var logger = options.logger || console; + var builder = options.builder; // No builder specified. Ignore the build then. if (typeof builder == "undefined") { - if (config.argv.quietDeploy == null) { - console.log("No build configuration specified. Not building."); + if (options.quiet != true) { + logger.log("No build configuration specified. Not building."); } return callback(); } @@ -109,7 +91,7 @@ var Build = { // a proper build function, then assume it's configuration // for the default builder. if (builder.hasOwnProperty("build") == false || typeof builder.build !== "function") { - builder = new DefaultBuilder(config.app.resolved.build, key, config.app.resolved.processors); + builder = new DefaultBuilder(builder, key, options.processors); } } else { // If they've only provided a build function, use that. @@ -124,22 +106,23 @@ var Build = { clean = builder.clean; } - clean(config.build.directory, function(err) { + clean(options, function(err) { if (err) return callback(err); - self.get_contract_data(config, function(err, contracts) { + Contracts.provision(options, function(err, contracts) { if (err) return callback(err); - var options = { - working_directory: config.working_dir, + var resolved_options = { + working_directory: options.working_directory, contracts: contracts, - contracts_directory: config.contracts.build_directory, - rpc: config.app.resolved.rpc, - environment: config.environment, - destination_directory: config.build.directory, + contracts_directory: options.contracts_build_directory, + destination_directory: options.build_directory, + rpc: options.rpc, + provider: options.provider, + network: options.network }; - builder.build(options, function(err) { + builder.build(resolved_options, function(err) { if (!err) return callback(); if (typeof err == "string") { diff --git a/lib/config.js b/lib/config.js index 9e1103fff78..6a3b5d8e090 100644 --- a/lib/config.js +++ b/lib/config.js @@ -95,13 +95,20 @@ Config.prototype.addProp = function(key, default_getter) { Config.prototype.getProvider = function(options) { options = options || {}; + var rpc_options = this.getRPCConfig(options); + return Provider.create(rpc_options); +}; + +Config.prototype.getRPCConfig = function(options) { + options = options || {}; + var network_id = options.network_id || this.network; if (this.networks[network_id] == null) { throw new ConfigurationError("Cannot find network '" + network_id + "'"); } - return Provider.create(_.merge(options, this.rpc, this.networks[network_id])); + return _.merge(options, this.rpc, this.networks[network_id]) }; // Helper function for expecting paths to exist. diff --git a/lib/contracts.js b/lib/contracts.js index af137411f5b..921b4a1681a 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -17,7 +17,7 @@ var Contracts = { web3.setProvider(options.provider); Pudding.requireAll({ - source_directory: options.build_directory, + source_directory: options.contracts_build_directory, provider: options.provider }, function(err, contracts) { if (err) return callback(err); @@ -50,7 +50,7 @@ var Contracts = { }, // source_directory: String. Directory where .sol files can be found. - // build_directory: String. Directory where .sol.js files can be found and written to. + // contracts_build_directory: String. Directory where .sol.js files can be found and written to. // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files // in the build directory to see what needs to be compiled. // network_id: network id to link saved contract artifacts. @@ -85,17 +85,17 @@ var Contracts = { }, write_contracts: function(contracts, options, callback) { - mkdirp(options.build_directory, function(err, result) { + mkdirp(options.contracts_build_directory, function(err, result) { if (err != null) { callback(err); return; } if (options.quiet != true && options.quietWrite != true) { - console.log("Writing artifacts to ." + path.sep + path.relative(process.cwd(), options.build_directory)); + console.log("Writing artifacts to ." + path.sep + path.relative(process.cwd(), options.contracts_build_directory)); } - Pudding.saveAll(contracts, options.build_directory, options).then(callback).catch(callback); + Pudding.saveAll(contracts, options.contracts_build_directory, options).then(callback).catch(callback); }); } } diff --git a/lib/migrate.js b/lib/migrate.js index 027f51a4de9..c15d2161904 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -66,7 +66,7 @@ Migration.prototype.run = function(options, contracts, callback) { }).then(function() { if (options.save === false) return; logger.log("Saving artifacts..."); - return Pudding.saveAll(contracts, options.build_directory, options.network_id); + return Pudding.saveAll(contracts, options.contracts_build_directory, options.network_id); }).then(function() { callback(); }).catch(function(e) { @@ -169,7 +169,7 @@ var Migrate = { }, lastCompletedMigration: function(options, callback) { - var migrations_contract = path.resolve(path.join(options.build_directory, "Migrations.sol.js")); + var migrations_contract = path.resolve(path.join(options.contracts_build_directory, "Migrations.sol.js")); Pudding.requireFile(migrations_contract, function(err, Migrations) { if (err) return callback(new Error("Could not find built Migrations contract.")); diff --git a/lib/profiler.js b/lib/profiler.js index f068afef207..82a8a86143e 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -25,7 +25,7 @@ module.exports = { updated: function(options, callback) { var source_directory = options.source_directory; - var build_directory = options.build_directory; + var build_directory = options.contracts_build_directory; this.all_contracts(source_directory, function(err, files) { var expected_build_files = files.map(function(file) { diff --git a/lib/serve.js b/lib/serve.js index 3ea30c7a612..7231feb63f9 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -3,8 +3,8 @@ var finalhandler = require('finalhandler'); var serveStatic = require('serve-static'); var Serve = { - start: function(config, port, done) { - var serve = serveStatic(config.build.directory); + start: function(options, port, done) { + var serve = serveStatic(options.build_directory); var server = http.createServer(function(req, res) { var done = finalhandler(req, res); diff --git a/lib/test.js b/lib/test.js index 4b1fc014f67..f9a2b46261c 100644 --- a/lib/test.js +++ b/lib/test.js @@ -89,7 +89,7 @@ TestRunner.prototype.initialize = function(callback) { TestRunner.prototype.deploy = function(callback) { Migrate.run({ migrations_directory: this.options.migrations_directory, - build_directory: this.options.build_directory, + contracts_build_directory: this.options.contracts_build_directory, provider: this.options.provider, reset: true, quiet: true @@ -233,7 +233,7 @@ var Test = { Contracts.compile({ all: options.compileAll === true, source_directory: options.contracts_directory, - build_directory: options.build_directory, + contracts_build_directory: options.contracts_build_directory, quiet: false, quietWrite: true, strict: options.strict diff --git a/package.json b/package.json index 288db465c53..362821398d7 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "colors": "^1.1.2", "cpr": "^0.4.3", "deasync": "^0.1.3", - "ether-pudding": "2.0.6", + "del": "^2.2.0", + "ether-pudding": "git://github.com/Consensys/ether-pudding.git#develop", "finalhandler": "^0.4.0", "find-up": "^1.1.2", "graphlib": "^2.0.0", @@ -25,10 +26,9 @@ "mkdirp": "^0.5.1", "mocha": "^2.3.3", "node-dir": "^0.1.10", - "rimraf": "^2.4.3", "serve-static": "^1.10.0", "solc": "^0.3.1-1", - "solidity-parser": "^0.0.5", + "solidity-parser": "^0.0.8", "spawn-args": "^0.1.0", "temp": "^0.8.3", "truffle-default-builder": "0.0.9", From 8e036e996506412976eea05f21025f41b1b11171 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Tue, 24 May 2016 09:01:00 -0700 Subject: [PATCH 13/26] Cleanup solc garbage output. --- lib/compiler.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/compiler.js b/lib/compiler.js index a3a1831bde0..6a961c0918a 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,4 +1,10 @@ var solc = require("solc"); + +// Clean up after solc. +var listeners = process.listeners("uncaughtException"); +var solc_listener = listeners[listeners.length - 1]; +process.removeListener("uncaughtException", solc_listener); + var path = require("path"); var fs = require("fs"); var async = require("async"); @@ -68,7 +74,14 @@ module.exports = { sources[key] = includes[key]; }); + // Add the listener back in, just in case I need it. + process.on("uncaughtException", solc_listener); + var result = solc.compile({sources: sources}, 1); + + // Alright, now remove it. + process.removeListener("uncaughtException", solc_listener); + var errors = result.errors || []; var warnings = result.errors || []; From 69d3a7503d50ccd000b34fbd7dd64255088507bc Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Tue, 24 May 2016 11:54:24 -0700 Subject: [PATCH 14/26] Make example deploy a bit simpler. --- example/migrations/2_deploy_contracts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/migrations/2_deploy_contracts.js b/example/migrations/2_deploy_contracts.js index 9d8460903c7..ebb3a480d7e 100644 --- a/example/migrations/2_deploy_contracts.js +++ b/example/migrations/2_deploy_contracts.js @@ -1,5 +1,5 @@ module.exports = function(deployer) { deployer.deploy(ConvertLib); - deployer.autolink(MetaCoin); + deployer.autolink(); deployer.deploy(MetaCoin); }; From b7d8f8aade4c423bbb259245c311bc1cccead28a Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Tue, 24 May 2016 11:59:35 -0700 Subject: [PATCH 15/26] Upgrade to newest release of EtherPudding. --- lib/build.js | 2 +- lib/repl.js | 4 ---- package.json | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/build.js b/lib/build.js index 55e756c8eb7..db56dd8b00d 100644 --- a/lib/build.js +++ b/lib/build.js @@ -115,7 +115,7 @@ var Build = { var resolved_options = { working_directory: options.working_directory, contracts: contracts, - contracts_directory: options.contracts_build_directory, + contracts_build_directory: options.contracts_build_directory, destination_directory: options.build_directory, rpc: options.rpc, provider: options.provider, diff --git a/lib/repl.js b/lib/repl.js index ed11ea92a0b..0854deb19e2 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1,8 +1,4 @@ var repl = require("repl"); - -global.Pudding = require("ether-pudding"); -var PuddingLoader = require("ether-pudding/loader"); - var Repl = { run: function(config, done) { Pudding.setWeb3(config.web3); diff --git a/package.json b/package.json index 362821398d7..731330327af 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "cpr": "^0.4.3", "deasync": "^0.1.3", "del": "^2.2.0", - "ether-pudding": "git://github.com/Consensys/ether-pudding.git#develop", + "ether-pudding": "^3.0.0", "finalhandler": "^0.4.0", "find-up": "^1.1.2", "graphlib": "^2.0.0", @@ -31,7 +31,7 @@ "solidity-parser": "^0.0.8", "spawn-args": "^0.1.0", "temp": "^0.8.3", - "truffle-default-builder": "0.0.9", + "truffle-default-builder": "^1.0.0", "uglify-js": "^2.6.1", "web3": "^0.15.2", "yargs": "^3.27.0" From e0d46c469990c66bb58ae7176fcc566a82bb5c37 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Tue, 24 May 2016 14:53:08 -0700 Subject: [PATCH 16/26] Rewrite console. This paves the way for a completely interactive truffle. 'compile', 'migrate' and 'build' commands added to REPL for ease of use. --- cli.js | 14 +++- lib/contracts.js | 1 + lib/repl.js | 162 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 163 insertions(+), 14 deletions(-) diff --git a/cli.js b/cli.js index 1454750b72c..fa7e8694893 100755 --- a/cli.js +++ b/cli.js @@ -322,7 +322,19 @@ registerTask('test', "Run tests", function(done) { registerTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(done) { var config = Truffle.config.detect(environment); - Truffle.console.run(config, done); + Truffle.console.run({ + working_directory: config.working_directory, + contracts_directory: config.contracts_directory, + contracts_build_directory: config.contracts_build_directory, + migrations_directory: config.migrations_directory, + provider: config.getProvider({ + verbose: argv.verboseRpc + }), + builder: config.build, + build_directory: config.build_directory, + processors: config.processors, // legacy option for default builder + rpc: config.getRPCConfig() + }, done); }); registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { diff --git a/lib/contracts.js b/lib/contracts.js index 921b4a1681a..0ee1e5431c9 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -61,6 +61,7 @@ var Contracts = { function finished(err, contracts) { if (err) return callback(err); + if (contracts != null && Object.keys(contracts).length > 0) { self.write_contracts(contracts, options, callback); } else { diff --git a/lib/repl.js b/lib/repl.js index 0854deb19e2..6a586acd6a0 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1,19 +1,155 @@ var repl = require("repl"); -var Repl = { - run: function(config, done) { - Pudding.setWeb3(config.web3); - global.web3 = config.web3; - PuddingLoader.load(config.environments.current.directory, Pudding, global, function() { - try { - var r = repl.start("truffle(" + config.environment + ")> "); - r.on("exit", function() { - process.exit(1); - }); - } catch(e) { - console.log(e.stack); +var Contracts = require("./contracts"); +var Migrate = require("./migrate"); +var Build = require("./build"); +var Web3 = require("web3"); +var vm = require("vm"); + +function TruffleInterpreter(options) { + this.options = options; + this.contracts = []; + this.r = null; +}; + +TruffleInterpreter.prototype.start = function() { + var self = this; + var options = this.options; + + var web3 = new Web3(); + web3.setProvider(options.provider); + + this.provision(function(err) { + if (err) return done(err); + + var prefix = "truffle(default)> "; + + if (options.network != null && options.network != "default") { + prefix = "truffle(" + options.network + ")> "; + } + + try { + self.r = repl.start({ + prompt: prefix, + eval: self.interpret.bind(self) + }); + + self.r.on("exit", function() { process.exit(1); - } + }); + + self.resetContracts(); + self.r.context.web3 = web3; + + } catch(e) { + console.log(e.stack); + process.exit(1); + } + }); +}; + +TruffleInterpreter.prototype.provision = function(callback) { + var self = this; + Contracts.provision(this.options, function(err, contracts) { + if (err) return callback(err); + + self.contracts = contracts; + self.resetContracts(); + + callback(); + }); +}; + +TruffleInterpreter.prototype.resetContracts = function() { + var self = this; + + if (this.r != null) { + this.contracts.forEach(function(contract) { + self.r.context[contract.contract_name] = contract; + }); + } +} + +TruffleInterpreter.prototype.compile = function(all, callback) { + var options = this.options; + + if (typeof all == "function") { + callback = all; + all = false; + } + + Contracts.compile({ + all: !!all, + source_directory: options.contracts_directory, + contracts_build_directory: options.contracts_build_directory + }, callback); +}; + +TruffleInterpreter.prototype.migrate = function(reset, callback) { + var options = this.options; + + if (typeof reset == "function") { + callback = reset; + reset = false; + } + + this.compile(false, function(err) { + if (err) return callback(err); + + Migrate.run({ + migrations_directory: options.migrations_directory, + contracts_build_directory: options.contracts_build_directory, + provider: options.provider, + reset: !!reset + }, function(err) { + console.log(""); + callback(err); + }); + }); +}; + +TruffleInterpreter.prototype.build = function(callback) { + var options = this.options; + + Build.build({ + builder: options.builder, + build_directory: options.build_directory, + working_directory: options.working_directory, + contracts_build_directory: options.contracts_build_directory, + processors: options.processors, // legacy option for default builder + provider: options.provider, + rpc: options.rpc + }, callback); +}; + +TruffleInterpreter.prototype.interpret = function(cmd, context, filename, callback) { + switch (cmd.trim()) { + case "compile": + return this.compile(callback); + case "migrate": + return this.migrate(callback); + case "build": + return this.build(callback); + } + + var result; + try { + result = vm.runInContext(cmd, context, { + displayErrors: false }); + } catch (e) { + return callback(e); + } + callback(null, result); +} + +var Repl = { + TruffleInterpreter: TruffleInterpreter, + + run: function(options) { + var self = this; + + var interpreter = new TruffleInterpreter(options); + interpreter.start(); } } From ff560c0baa1cc990f68f2eeaef7746fa9227e754 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 25 May 2016 13:15:13 -0700 Subject: [PATCH 17/26] Get truffle exec to work. --- cli.js | 47 ++++++++++++++++++---------- index.js | 2 +- lib/contracts.js | 2 +- lib/exec.js | 59 ----------------------------------- lib/migrate.js | 4 +-- lib/require.js | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 79 deletions(-) delete mode 100644 lib/exec.js create mode 100644 lib/require.js diff --git a/cli.js b/cli.js index fa7e8694893..7afbb90a41d 100755 --- a/cli.js +++ b/cli.js @@ -1,7 +1,7 @@ #!/usr/bin/env node require("babel-register"); -var web3 = require("web3"); +var Web3 = require("web3"); var path = require("path"); var fs = require("fs"); var chokidar = require('chokidar'); @@ -202,16 +202,6 @@ registerTask('build', "Build development version of app", function(done) { }); }); -registerTask('dist', "Create distributable version of app (minified)", function(done) { - var config = Truffle.config.detect(environment); - Truffle.build.dist(config, function(err) { - done(err); - if (err == null) { - printSuccess(); - } - }); -}); - registerTask('migrate', "Run migrations", function(done) { var config = Truffle.config.detect(environment); @@ -254,11 +244,36 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must file = path.join(config.working_directory, file); } - Truffle.exec.file({ - file: file, - web3: config.web3, - contracts: config.contracts.built_files - }, done); + var provider = config.getProvider({ + verbose: argv.verboseRpc + }); + + Truffle.contracts.provision({ + contracts_build_directory: config.contracts_build_directory, + provider: provider, + }, function(err, contracts) { + if (err) return done(err); + + var web3 = new Web3(); + web3.setProvider(provider); + + var context = { + web3: web3 + }; + + contracts.forEach(function(contract) { + context[contract.contract_name] = contract; + }); + + Truffle.require({ + file: file, + context: context + }, function(err, fn) { + if (err) return done(err); + fn(done); + }); + }); + }); // Supported options: diff --git a/index.js b/index.js index 9b4a48f34a7..439319ef6a6 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,7 @@ module.exports = { config: require("./lib/config"), console: require("./lib/repl"), contracts: require("./lib/contracts"), - exec: require("./lib/exec"), + require: require("./lib/require"), init: require("./lib/init"), migrate: require("./lib/migrate"), profile: require("./lib/profiler"), diff --git a/lib/contracts.js b/lib/contracts.js index 0ee1e5431c9..7edbdf8ee90 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -3,7 +3,7 @@ var fs = require("fs"); var mkdirp = require("mkdirp"); var path = require("path"); var Compiler = require("./compiler"); -var Exec = require("./exec"); +var Require = require("./require"); var Pudding = require("ether-pudding"); var Web3 = require("web3"); diff --git a/lib/exec.js b/lib/exec.js deleted file mode 100644 index cfa68bc8089..00000000000 --- a/lib/exec.js +++ /dev/null @@ -1,59 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var Module = require('module'); -var vm = require('vm'); -var requireNoCache = require("./require-nocache"); - -var Require = { - // options.file: path to file to execute. Must be a module that exports a function. - // options.args: arguments passed to the exported function within file. If a callback - // is not included in args, exported function is treated as synchronous. - // options.context: Object containing any global variables you'd like set when this - // function is run. - file: function(options, done) { - var self = this; - var file = options.file; - options.context = options.context || {}; - - fs.readFile(options.file, {encoding: "utf8"}, function(err, source) { - if (err) return done(err); - - // Modified from here: https://gist.github.com/anatoliychakkaev/1599423 - var m = new Module(file); - - // Provide all the globals listed here: https://nodejs.org/api/globals.html - var context = { - Buffer: Buffer, - __dirname: path.dirname(file), - __filename: file, - clearImmediate: clearImmediate, - clearInterval: clearInterval, - clearTimeout: clearTimeout, - console: console, - exports: exports, - global: global, - module: m, - process: process, - require: require, - setImmediate: setImmediate, - setInterval: setInterval, - setTimeout: setTimeout, - }; - - // Now add contract names. - Object.keys(options.context).forEach(function(key) { - context[key] = options.context[key]; - }); - - var old_cwd = process.cwd(); - var old_dirname = __dirname; - - var script = vm.createScript(source, file); - script.runInNewContext(context); - - done(null, m.exports); - }); - } -} - -module.exports = Require; diff --git a/lib/migrate.js b/lib/migrate.js index c15d2161904..6af596aaae0 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -6,7 +6,7 @@ var Pudding = require("ether-pudding"); var Deployer = require("./deployer"); var Profiler = require("./profiler"); var Provider = require("./provider"); -var Require = require("./exec"); +var Require = require("./require"); var async = require("async"); var Web3 = require("web3"); @@ -75,7 +75,7 @@ Migration.prototype.run = function(options, contracts, callback) { }); }; - Require.file({ + Require({ file: self.file, context: context, args: [deployer] diff --git a/lib/require.js b/lib/require.js new file mode 100644 index 00000000000..c3d7a1143f2 --- /dev/null +++ b/lib/require.js @@ -0,0 +1,80 @@ +var fs = require("fs"); +var path = require("path"); +var Module = require('module'); +var vm = require('vm'); + +// options.file: path to file to execute. Must be a module that exports a function. +// options.args: arguments passed to the exported function within file. If a callback +// is not included in args, exported function is treated as synchronous. +// options.context: Object containing any global variables you'd like set when this +// function is run. +module.exports = function(options, done) { + var self = this; + var file = options.file; + + fs.readFile(options.file, {encoding: "utf8"}, function(err, source) { + if (err) return done(err); + + // Modified from here: https://gist.github.com/anatoliychakkaev/1599423 + var m = new Module(file); + + // Provide all the globals listed here: https://nodejs.org/api/globals.html + var context = { + Buffer: Buffer, + __dirname: path.dirname(file), + __filename: file, + clearImmediate: clearImmediate, + clearInterval: clearInterval, + clearTimeout: clearTimeout, + console: console, + exports: exports, + global: global, + module: m, + process: process, + require: function(pkgPath) { + // Ugh. Simulate a full require function for the file. + pkgPath = pkgPath.trim(); + + // If absolute, just require. + if (path.isAbsolute(pkgPath)) { + return require(pkgPath); + } + + // If relative, it's relative to the file. + if (pkgPath[0] == ".") { + return require(path.join(path.dirname(file), pkgPath)); + } else { + // Not absolute, not relative, must be a locally installed modules. + // Here we have to require from the node_modules directory directly. + var moduleDir = path.join(path.dirname(file), "node_modules"); + try { + return require(path.join(moduleDir, pkgPath)); + } catch (e) { + // Shave off path we added so the error message looks like normal. + e.message = e.message.replace(moduleDir + "/", ""); + throw e; + } + } + }, + setImmediate: setImmediate, + setInterval: setInterval, + setTimeout: setTimeout, + }; + + // Now add contract names. + Object.keys(options.context || {}).forEach(function(key) { + context[key] = options.context[key]; + }); + + var old_cwd = process.cwd(); + + process.chdir(path.dirname(file)); + + var script = vm.createScript(source, file); + script.runInNewContext(context); + + process.chdir(old_cwd); + + done(null, m.exports); + }); +}; From 5ffe00a0adc6d0bfce277e349eeda6718e7b9195 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Wed, 25 May 2016 16:02:04 -0700 Subject: [PATCH 18/26] Start work on managing network information. Still very buggy. Added `truffle networks` --- cli.js | 156 ++++++++++++++++++++++++++++--------------- lib/config.js | 54 +++++++++------ lib/contracts.js | 11 +--- lib/deployer.js | 33 +++++++++- lib/migrate.js | 24 ++++--- lib/repl.js | 6 +- lib/require.js | 167 ++++++++++++++++++++++++++++++----------------- lib/test.js | 4 ++ 8 files changed, 300 insertions(+), 155 deletions(-) diff --git a/cli.js b/cli.js index 7afbb90a41d..bd780cd6510 100755 --- a/cli.js +++ b/cli.js @@ -1,7 +1,6 @@ #!/usr/bin/env node require("babel-register"); -var Web3 = require("web3"); var path = require("path"); var fs = require("fs"); var chokidar = require('chokidar'); @@ -137,7 +136,7 @@ registerTask('init', "Initialize new Ethereum project, including example contrac }); registerTask('create:contract', "Create a basic contract", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); var name = argv.name; @@ -156,7 +155,7 @@ registerTask('create:test', "Create a basic test", function(done) { // Force the test environment. environment = "test"; printNetwork(); - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); var name = argv.name; @@ -172,27 +171,29 @@ registerTask('create:test', "Create a basic test", function(done) { }); registerTask('compile', "Compile contracts", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); Truffle.contracts.compile({ all: argv.all === true, source_directory: config.contracts_directory, contracts_build_directory: config.contracts_build_directory, quiet: argv.quiet === true, - strict: argv.strict === true + strict: argv.strict === true, + network: config.network, + network_id: config.network_id }, done); }); registerTask('build', "Build development version of app", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); Truffle.build.build({ builder: config.build, build_directory: config.build_directory, working_directory: config.working_directory, contracts_build_directory: config.contracts_build_directory, processors: config.processors, // legacy option for default builder - provider: config.getProvider({ - verbose: argv.verboseRpc - }), + network: config.network, + network_id: config.network_id, + provider: config.provider, rpc: config.getRPCConfig() }, function(err) { done(err); @@ -203,30 +204,32 @@ registerTask('build', "Build development version of app", function(done) { }); registerTask('migrate', "Run migrations", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); Truffle.contracts.compile({ all: argv.compileAll === true, source_directory: config.contracts_directory, contracts_build_directory: config.contracts_build_directory, + network: config.network, quiet: argv.quiet === true, - strict: argv.strict === true + strict: argv.strict === true, + network: config.network, + network_id: config.network_id }, function(err) { if (err) return done(err); Truffle.migrate.run({ migrations_directory: config.migrations_directory, contracts_build_directory: config.contracts_build_directory, - provider: config.getProvider({ - verbose: argv.verboseRpc - }), + provider: config.provider, + network: config.network, reset: argv.reset || false }, done); }); }); registerTask('exec', "Execute a JS file within truffle environment. Script *must* call process.exit() when finished.", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); var file = argv.file; @@ -244,36 +247,11 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must file = path.join(config.working_directory, file); } - var provider = config.getProvider({ - verbose: argv.verboseRpc - }); - - Truffle.contracts.provision({ + Truffle.require.exec({ + file: file, contracts_build_directory: config.contracts_build_directory, - provider: provider, - }, function(err, contracts) { - if (err) return done(err); - - var web3 = new Web3(); - web3.setProvider(provider); - - var context = { - web3: web3 - }; - - contracts.forEach(function(contract) { - context[contract.contract_name] = contract; - }); - - Truffle.require({ - file: file, - context: context - }, function(err, fn) { - if (err) return done(err); - fn(done); - }); + provider: config.provider }); - }); // Supported options: @@ -281,7 +259,7 @@ registerTask('exec', "Execute a JS file within truffle environment. Script *must // More to come. registerTask('test', "Run tests", function(done) { environment = "test"; - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); var files = []; @@ -327,24 +305,24 @@ registerTask('test', "Run tests", function(done) { contracts_build_directory: temporaryDirectory, migrations_directory: config.migrations_directory, test_files: files, - provider: config.getProvider({ - verbose: argv.verboseRpc - }), + network: "test", + network_id: "default", + provider: config.provider }, cleanup); }); }); }); registerTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); Truffle.console.run({ working_directory: config.working_directory, contracts_directory: config.contracts_directory, contracts_build_directory: config.contracts_build_directory, migrations_directory: config.migrations_directory, - provider: config.getProvider({ - verbose: argv.verboseRpc - }), + network: config.network, + network_id: config.network_id, + provider: config.provider, builder: config.build, build_directory: config.build_directory, processors: config.processors, // legacy option for default builder @@ -353,7 +331,7 @@ registerTask('console', "Run a console with deployed contracts instantiated and }); registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { - var config = Truffle.config.detect(environment); + var config = Truffle.config.detect(environment, argv); Truffle.serve.start({ build_directory: config.build_directory }, argv.p || argv.port || "8080", function() { @@ -361,6 +339,80 @@ registerTask('serve', "Serve app on localhost and rebuild changes as needed", fu }); }); +registerTask('networks', "Show addresses for deployed contracts on each network", function(done) { + var config = Truffle.config.detect(environment, argv); + Truffle.contracts.provision({ + contracts_build_directory: config.contracts_build_directory, + provider: config.provider, + network: config.network, + network_id: config.network_id + }, function(err, contracts) { + if (err) return done(err); + + var ids_to_names = {}; + var networks = {}; + + Object.keys(config.networks).forEach(function(network_name) { + var network = config.networks[network_name]; + + // Ignore the test network that's configured by default. + if (network_name == "test" && network.network_id == null) { + return; + } + + var network_id = network.network_id || "default"; + ids_to_names[network_id] = network_name; + networks[network_name] = []; + }); + + contracts.forEach(function(contract) { + Object.keys(contract.all_networks).forEach(function(network_id) { + var network_name = ids_to_names[network_id] || network_id; + + if (networks[network_name] == null) { + networks[network_name] = []; + } + + networks[network_name].push(contract); + }); + }); + + Object.keys(networks).sort().forEach(function(network_name) { + + console.log("") + + networks[network_name] = networks[network_name].sort(function(a, b) { + a = a.contract_name; + b = b.contract_name; + if (a > b) return 1; + if (a < b) return -1; + return 0; + }); + + var output = networks[network_name].map(function(contract) { + if (contract.address == null) { + return null; + } + + return contract.contract_name + ": " + contract.address; + }).filter(function(line) { + return line != null; + }); + + if (output.length == 0) { + output = ["No contracts deployed."]; + } + + console.log("Network: " + network_name); + console.log(" " + output.join("\n ")) + }); + + console.log(""); + + done(); + }); +}); + // Default to listing available commands. var current_task = argv._[0]; diff --git a/lib/config.js b/lib/config.js index 6a3b5d8e090..65a87316ab8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -14,14 +14,16 @@ function Config(truffle_directory, working_directory, network) { this._values = { truffle_directory: truffle_directory || path.resolve(path.join(__dirname, "../")), working_directory: working_directory || process.cwd(), - network: network || "default" + network: network || "default", + verboseRpc: false }; var props = { - // First three already set. + // These are already set. truffle_directory: function() {}, working_directory: function() {}, network: function() {}, + verboseRpc: function() {}, build_directory: function() { return path.join(self.working_directory, "build"); @@ -43,10 +45,26 @@ function Config(truffle_directory, working_directory, network) { }, networks: function() { return { - "default": {}, + // "default": {}, "test": {} } }, + network_id: function() { + if (!self.network || !self.networks[self.network] || !self.networks[self.network].network_id) { + return "default"; + } + + return self.networks[self.network].network_id; + }, + network_config: function() { + var conf = self.networks[self.network]; + + if (conf == null && self.network == "default") { + return {}; + } + + return conf; + }, example_project_directory: function() { return path.join(self.truffle_directory, "example"); }, @@ -71,6 +89,11 @@ function Config(truffle_directory, working_directory, network) { gasPrice: 100000000000, // 100 Shannon, from: null } + }, + provider: function() { + var options = self.getRPCConfig(); + options.verboseRpc = self.verboseRpc; + return Provider.create(options); } }; @@ -92,23 +115,12 @@ Config.prototype.addProp = function(key, default_getter) { }); }; -Config.prototype.getProvider = function(options) { - options = options || {}; - - var rpc_options = this.getRPCConfig(options); - return Provider.create(rpc_options); -}; - -Config.prototype.getRPCConfig = function(options) { - options = options || {}; - - var network_id = options.network_id || this.network; - - if (this.networks[network_id] == null) { +Config.prototype.getRPCConfig = function() { + if (this.network_config == null) { throw new ConfigurationError("Cannot find network '" + network_id + "'"); } - return _.merge(options, this.rpc, this.networks[network_id]) + return _.merge(this.rpc, this.network_config) }; // Helper function for expecting paths to exist. @@ -150,7 +162,7 @@ Config.default = function() { return new Config(); }; -Config.detect = function(network, filename) { +Config.detect = function(network, argv, filename) { if (filename == null) { filename = DEFAULT_CONFIG_FILENAME; } @@ -161,17 +173,17 @@ Config.detect = function(network, filename) { throw new ConfigurationError("Could not find suitable configuration file."); } - return this.load(file, network); + return this.load(file, network, argv); }; -Config.load = function(file, network) { +Config.load = function(file, network, argv) { var config = new Config(); config.working_directory = path.dirname(path.resolve(file)); var static_config = requireNoCache(file); - config = _.merge(config, static_config); + config = _.merge(config, static_config, argv); if (network) { config.network = network; diff --git a/lib/contracts.js b/lib/contracts.js index 7edbdf8ee90..6bfbb54e8a4 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -35,7 +35,7 @@ var Contracts = { }); if (options.network_id) { - contract.setNetwork(network_id); + contract.setNetwork(options.network_id); } // If all addresses should be reset. @@ -76,15 +76,6 @@ var Contracts = { } }, - after_deploy: function(config, done) { - async.eachSeries(config.app.resolved.after_deploy, function(file, iterator_callback) { - if (config.argv.quietDeploy == null) { - console.log("Running post deploy script " + file + "..."); - } - Exec.file(config, file, iterator_callback); - }, done); - }, - write_contracts: function(contracts, options, callback) { mkdirp(options.contracts_build_directory, function(err, result) { if (err != null) { diff --git a/lib/deployer.js b/lib/deployer.js index ab7d6e51212..2ba54c5be92 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -1,6 +1,8 @@ var EventEmitter = require("events").EventEmitter; var inherits = require("util").inherits; var Linker = require("./linker"); +var Require = require("./require"); +var path = require("path"); var Actions = { deploy: function(contract, args, logger) { @@ -42,7 +44,9 @@ function Deployer(options) { this.known_contracts = {}; (options.contracts || []).forEach(function(contract) { self.known_contracts[contract.contract_name] = contract; - }) + }); + this.provider = options.provider; + this.basePath = options.basePath || process.cwd(); this.started = false; }; @@ -143,6 +147,33 @@ Deployer.prototype.new = function() { return this.chain; }; +Deployer.prototype.exec = function(file) { + this.checkStarted(); + + var self = this; + + if (path.isAbsolute(file) == false) { + file = path.resolve(path.join(this.basePath, file)); + } + + this.chain = this.chain.then(function() { + self.logger.log("Running " + file + "..."); + // Evaluate any arguments if they're promises + return new Promise(function(accept, reject) { + Require.exec({ + file: file, + contracts: Object.keys(self.known_contracts).map(function(key) { + return self.known_contracts[key]; + }), + provider: self.provider + }, function(err) { + if (err) return reject(err); + accept(); + }); + }); + }); +}; + Deployer.prototype.then = function(fn) { this.checkStarted(); diff --git a/lib/migrate.js b/lib/migrate.js index 6af596aaae0..b1da2538cee 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -10,9 +10,10 @@ var Require = require("./require"); var async = require("async"); var Web3 = require("web3"); -function Migration(file) { - this.file = file; +function Migration(file, network) { + this.file = path.resolve(file); this.number = parseInt(path.basename(file)); + this.network = network || "default"; }; Migration.prototype.run = function(options, contracts, callback) { @@ -51,7 +52,9 @@ Migration.prototype.run = function(options, contracts, callback) { logger.log(" " + msg); } }, - contracts: contracts + contracts: contracts, + provider: options.provider, + basePath: path.dirname(this.file) }); var finish = function(err) { @@ -75,16 +78,19 @@ Migration.prototype.run = function(options, contracts, callback) { }); }; - Require({ + Require.file({ file: self.file, context: context, args: [deployer] }, function(err, fn) { - if (fn.length <= 1) { - fn(deployer); + if (!fn || !fn.length || fn.length == 0) { + return callback(new Error("Migration " + self.file + " invalid or does not take any parameters")); + } + if (fn.length == 1 || fn.length == 2) { + fn(deployer, self.network); finish(); - } else { - fn(deployer, finish); + } else if (fn.length == 3) { + fn(deployer, self.network, finish); } }); }; @@ -97,7 +103,7 @@ var Migrate = { if (err) return callback(err); var migrations = files.map(function(file) { - return new Migration(file); + return new Migration(file, options.network); }); // Make sure to sort the prefixes as numbers and not strings. diff --git a/lib/repl.js b/lib/repl.js index 6a586acd6a0..af75bcf6273 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -80,7 +80,9 @@ TruffleInterpreter.prototype.compile = function(all, callback) { Contracts.compile({ all: !!all, source_directory: options.contracts_directory, - contracts_build_directory: options.contracts_build_directory + contracts_build_directory: options.contracts_build_directory, + network: options.network, + network_id: options.network_id }, callback); }; @@ -98,6 +100,8 @@ TruffleInterpreter.prototype.migrate = function(reset, callback) { Migrate.run({ migrations_directory: options.migrations_directory, contracts_build_directory: options.contracts_build_directory, + network: options.network, + network_id: options.network_id, provider: options.provider, reset: !!reset }, function(err) { diff --git a/lib/require.js b/lib/require.js index c3d7a1143f2..f25a95860bb 100644 --- a/lib/require.js +++ b/lib/require.js @@ -2,79 +2,124 @@ var fs = require("fs"); var path = require("path"); var Module = require('module'); var vm = require('vm'); +var Web3 = require("web3"); // options.file: path to file to execute. Must be a module that exports a function. // options.args: arguments passed to the exported function within file. If a callback // is not included in args, exported function is treated as synchronous. // options.context: Object containing any global variables you'd like set when this // function is run. -module.exports = function(options, done) { - var self = this; - var file = options.file; - - fs.readFile(options.file, {encoding: "utf8"}, function(err, source) { - if (err) return done(err); - - // Modified from here: https://gist.github.com/anatoliychakkaev/1599423 - var m = new Module(file); - - // Provide all the globals listed here: https://nodejs.org/api/globals.html - var context = { - Buffer: Buffer, - __dirname: path.dirname(file), - __filename: file, - clearImmediate: clearImmediate, - clearInterval: clearInterval, - clearTimeout: clearTimeout, - console: console, - exports: exports, - global: global, - module: m, - process: process, - require: function(pkgPath) { - // Ugh. Simulate a full require function for the file. - pkgPath = pkgPath.trim(); - - // If absolute, just require. - if (path.isAbsolute(pkgPath)) { - return require(pkgPath); - } - - // If relative, it's relative to the file. - if (pkgPath[0] == ".") { - return require(path.join(path.dirname(file), pkgPath)); - } else { - // Not absolute, not relative, must be a locally installed modules. - // Here we have to require from the node_modules directory directly. - var moduleDir = path.join(path.dirname(file), "node_modules"); - try { - return require(path.join(moduleDir, pkgPath)); - } catch (e) { - // Shave off path we added so the error message looks like normal. - e.message = e.message.replace(moduleDir + "/", ""); - throw e; +var Require = { + file: function(options, done) { + var self = this; + var file = options.file; + + fs.readFile(options.file, {encoding: "utf8"}, function(err, source) { + if (err) return done(err); + + // Modified from here: https://gist.github.com/anatoliychakkaev/1599423 + var m = new Module(file); + + // Provide all the globals listed here: https://nodejs.org/api/globals.html + var context = { + Buffer: Buffer, + __dirname: path.dirname(file), + __filename: file, + clearImmediate: clearImmediate, + clearInterval: clearInterval, + clearTimeout: clearTimeout, + console: console, + exports: exports, + global: global, + module: m, + process: process, + require: function(pkgPath) { + // Ugh. Simulate a full require function for the file. + pkgPath = pkgPath.trim(); + + // If absolute, just require. + if (path.isAbsolute(pkgPath)) { + return require(pkgPath); } - } - }, - setImmediate: setImmediate, - setInterval: setInterval, - setTimeout: setTimeout, - }; - // Now add contract names. - Object.keys(options.context || {}).forEach(function(key) { - context[key] = options.context[key]; + // If relative, it's relative to the file. + if (pkgPath[0] == ".") { + return require(path.join(path.dirname(file), pkgPath)); + } else { + // Not absolute, not relative, must be a locally installed modules. + // Here we have to require from the node_modules directory directly. + var moduleDir = path.join(path.dirname(file), "node_modules"); + try { + return require(path.join(moduleDir, pkgPath)); + } catch (e) { + // Shave off path we added so the error message looks like normal. + e.message = e.message.replace(moduleDir + "/", ""); + throw e; + } + } + }, + setImmediate: setImmediate, + setInterval: setInterval, + setTimeout: setTimeout, + }; + + // Now add contract names. + Object.keys(options.context || {}).forEach(function(key) { + context[key] = options.context[key]; + }); + + var old_cwd = process.cwd(); + + process.chdir(path.dirname(file)); + + var script = vm.createScript(source, file); + script.runInNewContext(context); + + process.chdir(old_cwd); + + done(null, m.exports); }); + }, + + exec: function(options, done) { + var self = this; - var old_cwd = process.cwd(); + var provision = function(callback) { + if (options.contracts != null) { + callback(null, options.contracts); + } else { + Contracts.provision({ + contracts_build_directory: options.contracts_build_directory, + provider: options.provider, + network: options.network, + network_id: options.network_id + }, callback); + } + }; + + provision(function(err, contracts) { + if (err) return done(err); - process.chdir(path.dirname(file)); + var web3 = new Web3(); + web3.setProvider(options.provider); - var script = vm.createScript(source, file); - script.runInNewContext(context); + var context = { + web3: web3 + }; - process.chdir(old_cwd); + contracts.forEach(function(contract) { + context[contract.contract_name] = contract; + }); - done(null, m.exports); - }); + self.file({ + file: options.file, + context: context + }, function(err, fn) { + if (err) return done(err); + fn(done); + }); + }); + } }; + +module.exports = Require; diff --git a/lib/test.js b/lib/test.js index f9a2b46261c..d6a01d1caed 100644 --- a/lib/test.js +++ b/lib/test.js @@ -90,6 +90,8 @@ TestRunner.prototype.deploy = function(callback) { Migrate.run({ migrations_directory: this.options.migrations_directory, contracts_build_directory: this.options.contracts_build_directory, + network: this.options.network, + network_id: this.options.network_id, provider: this.options.provider, reset: true, quiet: true @@ -234,6 +236,8 @@ var Test = { all: options.compileAll === true, source_directory: options.contracts_directory, contracts_build_directory: options.contracts_build_directory, + network: options.network, + network_id: options.network_id, quiet: false, quietWrite: true, strict: options.strict From 068e5a6618b78c4c9f56b9cbfd0d7e5107cc86fc Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Thu, 26 May 2016 15:43:03 -0700 Subject: [PATCH 19/26] Truffle is no longer a Truffle project (I'm taking Truffle out of your Truffles). Tests are updated and new ones added. This resulted in some nice refactorings elsewhere. --- cli.js | 26 ++++++---- contracts/Example.sol | 5 -- lib/compiler.js | 16 +++--- lib/config.js | 33 +++++++----- lib/contracts.js | 8 +-- lib/create.js | 38 +++++++------- lib/profiler.js | 27 +++++----- lib/repl.js | 2 +- lib/test.js | 2 +- package.json | 7 ++- test/compile.js | 115 ++++++++++++++++++++++++++++++++++++++++++ test/create.es6 | 62 ----------------------- test/create.js | 49 ++++++++++++++++++ test/init.es6 | 28 ---------- test/init.js | 26 ++++++++++ truffle.js | 6 --- 16 files changed, 281 insertions(+), 169 deletions(-) delete mode 100644 contracts/Example.sol create mode 100644 test/compile.js delete mode 100644 test/create.es6 create mode 100644 test/create.js delete mode 100644 test/init.es6 create mode 100644 test/init.js delete mode 100644 truffle.js diff --git a/cli.js b/cli.js index bd780cd6510..39204c381b3 100755 --- a/cli.js +++ b/cli.js @@ -147,7 +147,7 @@ registerTask('create:contract', "Create a basic contract", function(done) { if (name == null) { throw new ConfigurationError("Please specify a name. Example: truffle create:contract MyContract"); } else { - Truffle.create.contract(config, name, done); + Truffle.create.contract(config.contracts_directory, name, done); } }); @@ -166,7 +166,7 @@ registerTask('create:test', "Create a basic test", function(done) { if (name == null) { throw new ConfigurationError("Please specify a name. Example: truffle create:test MyTest"); } else { - Truffle.create.test(config, name, done); + Truffle.create.test(config.test_directory, name, done); } }); @@ -174,7 +174,7 @@ registerTask('compile', "Compile contracts", function(done) { var config = Truffle.config.detect(environment, argv); Truffle.contracts.compile({ all: argv.all === true, - source_directory: config.contracts_directory, + contracts_directory: config.contracts_directory, contracts_build_directory: config.contracts_build_directory, quiet: argv.quiet === true, strict: argv.strict === true, @@ -208,7 +208,7 @@ registerTask('migrate', "Run migrations", function(done) { Truffle.contracts.compile({ all: argv.compileAll === true, - source_directory: config.contracts_directory, + contracts_directory: config.contracts_directory, contracts_build_directory: config.contracts_build_directory, network: config.network, quiet: argv.quiet === true, @@ -294,11 +294,7 @@ registerTask('test', "Run tests", function(done) { }); }; - // Copy all the built files over to a temporary directory, because we - // don't want to save any tests artifacts. - copy(config.contracts_build_directory, temporaryDirectory, function(err) { - if (err) return done(err); - + function run() { Truffle.test.run({ compileAll: argv.compileAll, contracts_directory: config.contracts_directory, @@ -309,6 +305,18 @@ registerTask('test', "Run tests", function(done) { network_id: "default", provider: config.provider }, cleanup); + }; + + // Copy all the built files over to a temporary directory, because we + // don't want to save any tests artifacts. Only do this if the build directory + // exists. + fs.stat(config.contracts_build_directory, function(err, stat) { + if (err) return run(); + + copy(config.contracts_build_directory, temporaryDirectory, function(err) { + if (err) return done(err); + run(); + }); }); }); }); diff --git a/contracts/Example.sol b/contracts/Example.sol deleted file mode 100644 index e3c752092c2..00000000000 --- a/contracts/Example.sol +++ /dev/null @@ -1,5 +0,0 @@ -contract Example { - function Example() { - // constructor - } -} diff --git a/lib/compiler.js b/lib/compiler.js index 6a961c0918a..82599783d8f 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -12,18 +12,18 @@ var Profiler = require("./profiler"); var CompileError = require("./errors/compileerror"); module.exports = { - // source_directory: String. Directory where .sol files can be found. + // contracts_directory: String. Directory where .sol files can be found. // quiet: Boolean. Suppress output. Defaults to false. // strict: Boolean. Return compiler warnings as errors. Defaults to false. compile_all: function(options, callback) { var self = this; - Profiler.all_contracts(options.source_directory, function(err, files) { + Profiler.all_contracts(options.contracts_directory, function(err, files) { options.files = files; self.compile_with_dependencies(options, callback); }); }, - // source_directory: String. Directory where .sol files can be found. + // contracts_directory: String. Directory where .sol files can be found. // build_directory: String. Optional. Directory where .sol.js files can be found. Only required if `all` is false. // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files // in the build directory to see what needs to be compiled. @@ -50,7 +50,7 @@ module.exports = { // includes: { // "Foo.sol": "contract Foo {}" // Example // }, - // source_directory: "..." // or process.cwd() + // contracts_directory: "..." // or process.cwd() // strict: false, // quiet: false // logger: console @@ -59,14 +59,14 @@ module.exports = { var files = options.files || []; var includes = options.includes || {}; var logger = options.logger || console; - var source_directory = options.source_directory || process.cwd(); + var contracts_directory = options.contracts_directory || process.cwd(); var sources = {}; async.each(files, function(file, finished) { fs.readFile(file, "utf8", function(err, body) { if (err) return finished(err); - sources[path.relative(source_directory, file)] = body; + sources[path.relative(contracts_directory, file)] = body; finished(); }); }, function() { @@ -124,7 +124,7 @@ module.exports = { options.files = options.files || []; options.includes = options.includes || {}; options.logger = options.logger || console; - options.source_directory = options.source_directory || process.cwd(); + options.contracts_directory = options.contracts_directory || process.cwd(); var self = this; Profiler.required_files(options.files, function(err, files) { @@ -132,7 +132,7 @@ module.exports = { files.sort().forEach(function(file) { if (options.quiet != true) { - var relative = path.relative(options.source_directory, file) + var relative = path.relative(options.contracts_directory, file) options.logger.log("Compiling " + relative + "..."); } }); diff --git a/lib/config.js b/lib/config.js index 65a87316ab8..595059f66df 100644 --- a/lib/config.js +++ b/lib/config.js @@ -5,6 +5,8 @@ var Provider = require("./provider"); var ConfigurationError = require('./errors/configurationerror'); var requireNoCache = require("./require-nocache"); var findUp = require("find-up"); +var temp = require("temp").track(); +var Init = require("./init"); var DEFAULT_CONFIG_FILENAME = "truffle.js"; @@ -68,19 +70,6 @@ function Config(truffle_directory, working_directory, network) { example_project_directory: function() { return path.join(self.truffle_directory, "example"); }, - templates: function() { - return { - test: { - filename: path.join(self.truffle_directory, "templates", "example.js"), - variable: "example" - }, - contract: { - filename: path.join(self.truffle_directory, "templates", "Example.sol"), - name: "Example", - variable: "example" - } - }; - }, rpc: function() { return { host: "localhost", @@ -123,6 +112,10 @@ Config.prototype.getRPCConfig = function() { return _.merge(this.rpc, this.network_config) }; +Config.prototype.with = function(obj) { + return _.extend({}, this, obj); +}; + // Helper function for expecting paths to exist. Config.expect = function(expected_path, description, extra, callback) { if (typeof description == "function") { @@ -192,4 +185,18 @@ Config.load = function(file, network, argv) { return config; }; +Config.sandbox = function(callback) { + var self = this; + temp.mkdir("truffle-sandbox-", function(err, dirPath) { + if (err) return callback(err); + + Init(dirPath, function(err) { + if (err) return callback(err); + + var config = self.load(path.join(dirPath, "truffle.js")); + callback(null, config); + }); + }); +}; + module.exports = Config; diff --git a/lib/contracts.js b/lib/contracts.js index 6bfbb54e8a4..023e1db7eeb 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -49,7 +49,7 @@ var Contracts = { }); }, - // source_directory: String. Directory where .sol files can be found. + // contracts_directory: String. Directory where .sol files can be found. // contracts_build_directory: String. Directory where .sol.js files can be found and written to. // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files // in the build directory to see what needs to be compiled. @@ -65,7 +65,7 @@ var Contracts = { if (contracts != null && Object.keys(contracts).length > 0) { self.write_contracts(contracts, options, callback); } else { - callback(); + callback(null, []); } }; @@ -87,7 +87,9 @@ var Contracts = { console.log("Writing artifacts to ." + path.sep + path.relative(process.cwd(), options.contracts_build_directory)); } - Pudding.saveAll(contracts, options.contracts_build_directory, options).then(callback).catch(callback); + Pudding.saveAll(contracts, options.contracts_build_directory, options).then(function() { + callback(null, contracts); + }).catch(callback); }); } } diff --git a/lib/create.js b/lib/create.js index 48a6fa0de9a..0a60e8e05e5 100644 --- a/lib/create.js +++ b/lib/create.js @@ -2,14 +2,22 @@ var util = require("./util"); var file = require("./file"); var path = require("path"); -var Create = { - contract: function(config, name, callback) { - if (!config.expect(config.contracts.directory, "contracts directory", callback)) { - return; - } +var templates = { + test: { + filename: path.join(__dirname, "../", "templates", "example.js"), + variable: "example" + }, + contract: { + filename: path.join(__dirname, "../", "templates", "Example.sol"), + name: "Example", + variable: "example" + } +}; - var from = config.templates.contract.filename; - var to = path.join(config.contracts.directory, name + ".sol"); +var Create = { + contract: function(directory, name, callback) { + var from = templates.contract.filename; + var to = path.join(directory, name + ".sol"); file.duplicate(from, to, function(err) { if (err != null) { @@ -17,17 +25,13 @@ var Create = { return; } - file.replace(to, config.templates.contract.name, name, callback); + file.replace(to, templates.contract.name, name, callback); }); }, - test: function(config, name, callback) { - if (!config.expect(config.tests.directory, "tests directory", callback)) { - return; - } - + test: function(directory, name, callback) { var underscored = util.toUnderscoreFromCamel(name); - var from = config.templates.test.filename; - var to = path.join(config.tests.directory, underscored + ".js"); + var from = templates.test.filename; + var to = path.join(directory, underscored + ".js"); file.duplicate(from, to, function(err) { if (err != null) { @@ -35,8 +39,8 @@ var Create = { return; } - file.replace(to, config.templates.contract.name, name, function() { - file.replace(to, config.templates.contract.variable, underscored, callback); + file.replace(to, templates.contract.name, name, function() { + file.replace(to, templates.contract.variable, underscored, callback); }); }); } diff --git a/lib/profiler.js b/lib/profiler.js index 82a8a86143e..f56d1b9918a 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -4,6 +4,7 @@ var dir = require("node-dir"); var path = require("path"); var async = require("async"); var fs = require("fs"); +var Pudding = require("ether-pudding"); var SolidityParser = require("solidity-parser"); var Graph = require("graphlib").Graph; var isAcyclic = require("graphlib/lib/alg").isAcyclic; @@ -24,38 +25,40 @@ module.exports = { }, updated: function(options, callback) { - var source_directory = options.source_directory; + var contracts_directory = options.contracts_directory; var build_directory = options.contracts_build_directory; - this.all_contracts(source_directory, function(err, files) { + this.all_contracts(contracts_directory, function(err, files) { var expected_build_files = files.map(function(file) { - return path.join(build_directory, path.dirname(path.relative(source_directory, file)), path.basename(file) + ".js"); + return path.join(build_directory, path.dirname(path.relative(contracts_directory, file)), path.basename(file) + ".js"); }); async.map(files, fs.stat, function(err, file_stats) { if (err) return callback(err); - async.map(expected_build_files, function(expected_file, finished) { - fs.stat(expected_file, function(err, stat) { - // Ignore errors when built files don't exist. - finished(null, stat); + async.map(expected_build_files, function(build_file, finished) { + Pudding.requireFile(build_file, options, function(err, contract) { + // Ignore errors, i.e., if the file doesn't exist. + finished(null, contract); }); - }, function(err, built_file_stats) { + }, function(err, contracts) { if (err) return callback(err); var updated = []; - for (var i = 0; i < built_file_stats.length; i++) { + for (var i = 0; i < contracts.length; i++) { var file_stat = file_stats[i]; - var built_file_stat = built_file_stats[i]; + var contract = contracts[i]; - if (built_file_stat == null) { + if (contract == null) { updated.push(files[i]); continue; } var modified_time = (file_stat.mtime || file_stat.ctime).getTime(); - var built_time = (built_file_stat.mtime || built_file_stat.ctime).getTime(); + + // Note that the network is already set for is in Pudding.requireFile(). + var built_time = contract.updated_at || 0; if (modified_time > built_time) { updated.push(files[i]); diff --git a/lib/repl.js b/lib/repl.js index af75bcf6273..53489507865 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -79,7 +79,7 @@ TruffleInterpreter.prototype.compile = function(all, callback) { Contracts.compile({ all: !!all, - source_directory: options.contracts_directory, + contracts_directory: options.contracts_directory, contracts_build_directory: options.contracts_build_directory, network: options.network, network_id: options.network_id diff --git a/lib/test.js b/lib/test.js index d6a01d1caed..c25a6e2d2bd 100644 --- a/lib/test.js +++ b/lib/test.js @@ -234,7 +234,7 @@ var Test = { // Compile if needed. This will Contracts.compile({ all: options.compileAll === true, - source_directory: options.contracts_directory, + contracts_directory: options.contracts_directory, contracts_build_directory: options.contracts_build_directory, network: options.network, network_id: options.network_id, diff --git a/package.json b/package.json index 731330327af..9f7c39def54 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "cpr": "^0.4.3", "deasync": "^0.1.3", "del": "^2.2.0", - "ether-pudding": "^3.0.0", + "ether-pudding": "^3.0.1", "finalhandler": "^0.4.0", "find-up": "^1.1.2", "graphlib": "^2.0.0", @@ -37,15 +37,14 @@ "yargs": "^3.27.0" }, "devDependencies": { - "babel-cli": "^6.4.5", - "temp": "^0.8.3" + "babel-cli": "^6.4.5" }, "bin": { "truffle": "./cli.js", "truffle-exec": "./exec.js" }, "scripts": { - "test": "node ./cli.js test" + "test": "mocha" }, "repository": { "type": "git", diff --git a/test/compile.js b/test/compile.js new file mode 100644 index 00000000000..846e8bfe2b1 --- /dev/null +++ b/test/compile.js @@ -0,0 +1,115 @@ +var assert = require("chai").assert; +var Config = require("../lib/config"); +var Contracts = require("../lib/contracts"); +var Pudding = require("ether-pudding"); +var path = require("path"); +var fs = require("fs"); + +describe("compile", function() { + var config; + + before("Create a sandbox", function(done) { + this.timeout(5000); + // Note: Config.sandbox() calls Init. + Config.sandbox(function(err, result) { + if (err) return done(err); + config = result; + done(); + }); + }); + + before("edit config", function() { + config.networks = { + "default": { + "network_id": "default" + }, + "secondary": { + "network_id": "12345" + } + } + }); + + it('compiles all initial contracts', function(done) { + this.timeout(10000); + + Contracts.compile(config.with({ + all: false, + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + assert.equal(Object.keys(contracts).length, 3, "Didn't compile the expected number of contracts"); + done(); + }); + }); + + it('compiles no contracts after no updates', function(done) { + this.timeout(10000); + + Contracts.compile(config.with({ + all: false, + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + assert.equal(Object.keys(contracts).length, 0, "Compiled a contract even though we weren't expecting it"); + done(); + }); + }); + + it('compiles contract and dependencies after an update', function(done) { + this.timeout(10000); + + var file_to_update = path.resolve(path.join(config.contracts_directory, "MetaCoin.sol")); + + // Update the modification time to simulate an edit. + var newTime = new Date().getTime(); + fs.utimesSync(file_to_update, newTime, newTime); + + Contracts.compile(config.with({ + all: false, + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + assert.equal(Object.keys(contracts).length, 2, "Expected MetaCoin and ConvertLib to be compiled"); + done(); + }); + }); + + it('contracts should only have one network', function(done) { + var file = path.resolve(path.join(config.contracts_build_directory, "MetaCoin.sol.js")); + + Pudding.requireFile(file, function(err, contract) { + if (err) return done(err); + assert.equal(contract.networks().length, 1, "Expected the contract to only be managing one network"); + done(); + }); + }); + + it('compiles all contracts after multiple changes after a change in network', function(done) { + this.timeout(10000); + + config.network = "secondary"; + + Contracts.compile(config.with({ + all: false, + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + assert.equal(Object.keys(contracts).length, 3, "Expected all contracts to be compiled on a second network"); + done(); + }); + }); + + it('contracts should new have two networks', function(done) { + var file = path.resolve(path.join(config.contracts_build_directory, "MetaCoin.sol.js")); + + Pudding.requireFile(file, function(err, contract) { + if (err) return done(err); + assert.equal(contract.networks().length, 2, "Expected the contract to be managing two networks"); + done(); + }); + }); +}); diff --git a/test/create.es6 b/test/create.es6 deleted file mode 100644 index 48505c4c3e7..00000000000 --- a/test/create.es6 +++ /dev/null @@ -1,62 +0,0 @@ -var temp = require("temp").track(); -var path = require("path"); -var fs = require("fs"); -var Config = require("../lib/config"); -var Init = require("../lib/init"); -var Create = require("../lib/create"); - -describe('truffle:create', function() { - // Paths relative to app truffle directory. - var truffle_dir = path.resolve("./"); - var temp_dir = temp.mkdirSync(); - - before("initialize environment", function(done) { - var config = Config.gather(truffle_dir, temp_dir, {}, null); - Init.all(config, done); - }); - - it('successfully creates a new contract', function(done) { - var argv = { - name: "MyNewContract" - }; - - var config = Config.gather(truffle_dir, temp_dir, argv, "development"); - Create.contract(config, argv.name, function(err) { - if (err != null) { - return done(err); - } - - var expected_file = path.join(temp_dir, "contracts", "MyNewContract.sol"); - assert.isTrue(fs.existsSync(expected_file), `Contract to be created doesns't exist, ${expected_file}`); - - var file_data = fs.readFileSync(expected_file, {encoding: "utf8"}); - assert.isNotNull(file_data, "File's data is null"); - assert.notEqual(file_data, "", "File's data is blank"); - - done(); - }); - }); // it - - it('successfully creates a new test', function(done) { - var argv = { - name: "MyOtherNewContract" - }; - - var config = Config.gather(truffle_dir, temp_dir, argv, "development"); - Create.test(config, argv.name, function(err) { - if (err != null) { - return done(err); - } - - var expected_file = path.join(temp_dir, "test", "my_other_new_contract.js"); - assert.isTrue(fs.existsSync(expected_file), `Test to be created doesns't exist, ${expected_file}`); - - var file_data = fs.readFileSync(expected_file, {encoding: "utf8"}); - assert.isNotNull(file_data, "File's data is null"); - assert.notEqual(file_data, "", "File's data is blank"); - - done(); - }); - }); // it - -}); diff --git a/test/create.js b/test/create.js new file mode 100644 index 00000000000..6309ed270fb --- /dev/null +++ b/test/create.js @@ -0,0 +1,49 @@ +var assert = require("chai").assert; +var path = require("path"); +var fs = require("fs"); +var Config = require("../lib/config"); +var Create = require("../lib/create"); + +describe('create', function() { + var config; + + before("Create a sandbox", function(done) { + this.timeout(5000); + Config.sandbox(function(err, result) { + if (err) return done(err); + config = result; + done(); + }); + }); + + it('creates a new contract', function(done) { + Create.contract(config.contracts_directory, "MyNewContract", function(err) { + if (err) return done(err); + + var expected_file = path.join(config.contracts_directory, "MyNewContract.sol"); + assert.isTrue(fs.existsSync(expected_file), `Contract to be created doesns't exist, ${expected_file}`); + + var file_data = fs.readFileSync(expected_file, {encoding: "utf8"}); + assert.isNotNull(file_data, "File's data is null"); + assert.notEqual(file_data, "", "File's data is blank"); + + done(); + }); + }); // it + + it('creates a new test', function(done) { + Create.test(config.test_directory, "MyNewTest", function(err) { + if (err) return done(err); + + var expected_file = path.join(config.test_directory, "my_new_test.js"); + assert.isTrue(fs.existsSync(expected_file), `Test to be created doesns't exist, ${expected_file}`); + + var file_data = fs.readFileSync(expected_file, {encoding: "utf8"}); + assert.isNotNull(file_data, "File's data is null"); + assert.notEqual(file_data, "", "File's data is blank"); + + done(); + }); + }); // it + +}); diff --git a/test/init.es6 b/test/init.es6 deleted file mode 100644 index 29e2612c966..00000000000 --- a/test/init.es6 +++ /dev/null @@ -1,28 +0,0 @@ -var temp = require("temp").track(); -var path = require("path"); -var fs = require("fs"); -var Config = require("../lib/config"); -var Init = require("../lib/init") - -describe('truffle:init', function() { - // Paths relative to app truffle directory. - var truffle_dir = path.resolve("./"); - - it('successfully copies example configuration', function(done) { - var temp_dir = temp.mkdirSync(); - var config = Config.gather(truffle_dir, temp_dir, {}, null); - - Init.all(config, function(err) { - if (err != null) { - return done(err); - } - - assert.isTrue(fs.existsSync(path.join(temp_dir, "app")), "app directory not created successfully"); - assert.isTrue(fs.existsSync(path.join(temp_dir, "environments")), "config directory not created successfully"); - assert.isTrue(fs.existsSync(path.join(temp_dir, "contracts")), "contracts directory not created successfully"); - assert.isTrue(fs.existsSync(path.join(temp_dir, "test")), "tests directory not created successfully"); - - done(); - }); - }); -}); diff --git a/test/init.js b/test/init.js new file mode 100644 index 00000000000..7a97b4f3700 --- /dev/null +++ b/test/init.js @@ -0,0 +1,26 @@ +var assert = require("chai").assert; +var Config = require("../lib/config"); +var fs = require("fs"); +var path = require('path'); + +describe('init', function() { + var config; + + before("Create a sandbox", function(done) { + this.timeout(5000); + // Note: Config.sandbox() calls Init. + Config.sandbox(function(err, result) { + if (err) return done(err); + config = result; + done(); + }); + }); + + it('copies example configuration', function(done) { + assert.isTrue(fs.existsSync(path.join(config.working_directory, "app")), "app directory not created successfully"); + assert.isTrue(fs.existsSync(path.join(config.working_directory, "contracts")), "contracts directory not created successfully"); + assert.isTrue(fs.existsSync(path.join(config.working_directory, "test")), "tests directory not created successfully"); + + done(); + }); +}); diff --git a/truffle.js b/truffle.js deleted file mode 100644 index dc2d3eaf98f..00000000000 --- a/truffle.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - rpc: { - host: "localhost", - port: 8545 - } -}; From ece93502956a33d03afb49639b8b7bcc69a31be1 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Thu, 26 May 2016 16:01:08 -0700 Subject: [PATCH 20/26] Move networks functionality to profiler. --- cli.js | 55 +++++-------------------------------------------- lib/profiler.js | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/cli.js b/cli.js index 39204c381b3..f7294e84b1e 100755 --- a/cli.js +++ b/cli.js @@ -349,62 +349,17 @@ registerTask('serve', "Serve app on localhost and rebuild changes as needed", fu registerTask('networks', "Show addresses for deployed contracts on each network", function(done) { var config = Truffle.config.detect(environment, argv); - Truffle.contracts.provision({ - contracts_build_directory: config.contracts_build_directory, - provider: config.provider, - network: config.network, - network_id: config.network_id - }, function(err, contracts) { - if (err) return done(err); - - var ids_to_names = {}; - var networks = {}; - - Object.keys(config.networks).forEach(function(network_name) { - var network = config.networks[network_name]; - - // Ignore the test network that's configured by default. - if (network_name == "test" && network.network_id == null) { - return; - } - var network_id = network.network_id || "default"; - ids_to_names[network_id] = network_name; - networks[network_name] = []; - }); - - contracts.forEach(function(contract) { - Object.keys(contract.all_networks).forEach(function(network_id) { - var network_name = ids_to_names[network_id] || network_id; - - if (networks[network_name] == null) { - networks[network_name] = []; - } - - networks[network_name].push(contract); - }); - }); + Truffle.profile.deployed_networks(config, function(err, networks) { + if (err) return callback(err); Object.keys(networks).sort().forEach(function(network_name) { console.log("") - networks[network_name] = networks[network_name].sort(function(a, b) { - a = a.contract_name; - b = b.contract_name; - if (a > b) return 1; - if (a < b) return -1; - return 0; - }); - - var output = networks[network_name].map(function(contract) { - if (contract.address == null) { - return null; - } - - return contract.contract_name + ": " + contract.address; - }).filter(function(line) { - return line != null; + var output = Object.keys(networks[network_name]).sort().map(function(contract_name) { + var address = networks[network_name][contract_name]; + return contract_name + ": " + address; }); if (output.length == 0) { diff --git a/lib/profiler.js b/lib/profiler.js index f56d1b9918a..c3e2de066cd 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -71,6 +71,44 @@ module.exports = { }); }, + deployed_networks: function(options, callback) { + Pudding.requireAll(options.contracts_build_directory, function(err, contracts) { + if (err) return callback(err); + + var ids_to_names = {}; + var networks = {}; + + Object.keys(options.networks).forEach(function(network_name) { + var network = options.networks[network_name]; + + // Ignore the test network that's configured by default. + if (network_name == "test" && network.network_id == null) { + return; + } + + var network_id = network.network_id || "default"; + ids_to_names[network_id] = network_name; + networks[network_name] = {}; + }); + + contracts.forEach(function(contract) { + Object.keys(contract.all_networks).forEach(function(network_id) { + var network_name = ids_to_names[network_id] || network_id; + + if (networks[network_name] == null) { + networks[network_name] = {}; + } + + if (contract.address == null) return; + + networks[network_name][contract.contract_name] = contract.address; + }); + }); + + callback(null, networks); + }); + }, + imports: function(file, callback) { fs.readFile(file, "utf8", function(err, body) { if (err) callback(err); From b670f275169e01bf12cc90c4d41c5755e1cb1d50 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Thu, 26 May 2016 16:19:37 -0700 Subject: [PATCH 21/26] Move Config.sandbox() to Init.sanbox() (feels right). Start on migration tests (more to come). --- lib/config.js | 16 ----------- lib/init.js | 20 +++++++++++++- test/compile.js | 5 ++-- test/create.js | 4 +-- test/init.js | 5 ++-- test/migrate.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 test/migrate.js diff --git a/lib/config.js b/lib/config.js index 595059f66df..2579f73b1ff 100644 --- a/lib/config.js +++ b/lib/config.js @@ -5,8 +5,6 @@ var Provider = require("./provider"); var ConfigurationError = require('./errors/configurationerror'); var requireNoCache = require("./require-nocache"); var findUp = require("find-up"); -var temp = require("temp").track(); -var Init = require("./init"); var DEFAULT_CONFIG_FILENAME = "truffle.js"; @@ -185,18 +183,4 @@ Config.load = function(file, network, argv) { return config; }; -Config.sandbox = function(callback) { - var self = this; - temp.mkdir("truffle-sandbox-", function(err, dirPath) { - if (err) return callback(err); - - Init(dirPath, function(err) { - if (err) return callback(err); - - var config = self.load(path.join(dirPath, "truffle.js")); - callback(null, config); - }); - }); -}; - module.exports = Config; diff --git a/lib/init.js b/lib/init.js index eaca2801d54..7deff13d7d1 100644 --- a/lib/init.js +++ b/lib/init.js @@ -1,7 +1,25 @@ var copy = require("./copy"); var path = require("path"); +var temp = require("temp").track(); +var Config = require("./config"); -module.exports = function(destination, callback) { +var Init = function(destination, callback) { var example_directory = path.resolve(path.join(__dirname, "..", "example")); copy(example_directory, destination, callback); } + +Init.sandbox = function(callback) { + var self = this; + temp.mkdir("truffle-sandbox-", function(err, dirPath) { + if (err) return callback(err); + + Init(dirPath, function(err) { + if (err) return callback(err); + + var config = Config.load(path.join(dirPath, "truffle.js")); + callback(null, config); + }); + }); +}; + +module.exports = Init; diff --git a/test/compile.js b/test/compile.js index 846e8bfe2b1..ba197b0151c 100644 --- a/test/compile.js +++ b/test/compile.js @@ -1,5 +1,5 @@ var assert = require("chai").assert; -var Config = require("../lib/config"); +var Init = require("../lib/init"); var Contracts = require("../lib/contracts"); var Pudding = require("ether-pudding"); var path = require("path"); @@ -10,8 +10,7 @@ describe("compile", function() { before("Create a sandbox", function(done) { this.timeout(5000); - // Note: Config.sandbox() calls Init. - Config.sandbox(function(err, result) { + Init.sandbox(function(err, result) { if (err) return done(err); config = result; done(); diff --git a/test/create.js b/test/create.js index 6309ed270fb..4a0e3f1bf0d 100644 --- a/test/create.js +++ b/test/create.js @@ -1,7 +1,7 @@ var assert = require("chai").assert; var path = require("path"); var fs = require("fs"); -var Config = require("../lib/config"); +var Init = require("../lib/init"); var Create = require("../lib/create"); describe('create', function() { @@ -9,7 +9,7 @@ describe('create', function() { before("Create a sandbox", function(done) { this.timeout(5000); - Config.sandbox(function(err, result) { + Init.sandbox(function(err, result) { if (err) return done(err); config = result; done(); diff --git a/test/init.js b/test/init.js index 7a97b4f3700..82e5364716d 100644 --- a/test/init.js +++ b/test/init.js @@ -1,5 +1,5 @@ var assert = require("chai").assert; -var Config = require("../lib/config"); +var Init = require("../lib/init"); var fs = require("fs"); var path = require('path'); @@ -8,8 +8,7 @@ describe('init', function() { before("Create a sandbox", function(done) { this.timeout(5000); - // Note: Config.sandbox() calls Init. - Config.sandbox(function(err, result) { + Init.sandbox(function(err, result) { if (err) return done(err); config = result; done(); diff --git a/test/migrate.js b/test/migrate.js new file mode 100644 index 00000000000..7a2fec3e7e0 --- /dev/null +++ b/test/migrate.js @@ -0,0 +1,73 @@ +var assert = require("chai").assert; +var Init = require("../lib/init"); +var Migrate = require("../lib/migrate"); +var Contracts = require("../lib/contracts"); +var Profiler = require("../lib/profiler"); +var Pudding = require("ether-pudding"); +var path = require("path"); +var fs = require("fs"); + +describe("migrate", function() { + var config; + + before("Create a sandbox", function(done) { + this.timeout(5000); + Init.sandbox(function(err, result) { + if (err) return done(err); + config = result; + done(); + }); + }); + + before("edit config", function() { + config.networks = { + "default": { + "network_id": "default" + }, + "secondary": { + "network_id": "12345" + } + } + }); + + it('profiles a new project as not having any contracts deployed', function(done) { + Profiler.deployed_networks(config, function(err, networks) { + if (err) return done(err); + + assert.equal(Object.keys(networks).length, 2, "Should have results for two networks from profiler"); + assert.equal(Object.keys(networks["default"]), 0, "Default network should not have been deployed to"); + assert.equal(Object.keys(networks["secondary"]), 0, "Secondary network should not have been deployed to"); + done(); + }) + }); + + it('links libraries in initial project, and runs all migrations', function(done) { + this.timeout(10000); + + Contracts.compile(config.with({ + all: false, + quiet: true + }), function(err) { + if (err) return done(err); + + Migrate.run(config.with({ + reset: true, + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + Profiler.deployed_networks(config, function(err, networks) { + if (err) return done(err); + + assert.equal(Object.keys(networks).length, 2, "Should have results for two networks from profiler"); + assert.equal(Object.keys(networks["default"]).length, 3, "Default network should have three contracts deployed"); + assert.isNotNull(networks["default"]["MetaCoin"], "MetaCoin contract should have an address"); + assert.isNotNull(networks["default"]["ConvertLib"], "ConvertLib library should have an address"); + assert.isNotNull(networks["default"]["Migrations"], "Migrations contract should have an address"); + assert.equal(Object.keys(networks["secondary"]), 0, "Secondary network should not have been deployed to"); + done(); + }); + }); + }); + }); +}); From ec61b33e534b44552af3e6995b394b6dcb537638 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 27 May 2016 13:49:00 -0700 Subject: [PATCH 22/26] Add more migration tests. Refactor cli tasks and command runner into their own modules. Severely beef up console: Now you can enter any normal console command and work with your contracts directly. Add checks to ensure certain functions receive the options they expect. --- cli.js | 413 ++++------------------------------------ index.js | 5 +- lib/build.js | 12 ++ lib/command.js | 54 ++++++ lib/config.js | 16 +- lib/contracts.js | 17 +- lib/create.js | 1 + lib/deployer.js | 12 ++ lib/errors/taskerror.js | 10 + lib/expect.js | 11 ++ lib/migrate.js | 22 ++- lib/profiler.js | 6 +- lib/repl.js | 90 +++------ lib/require.js | 9 + lib/serve.js | 4 +- lib/tasks.js | 315 ++++++++++++++++++++++++++++++ lib/test.js | 11 ++ test/migrate.js | 50 ++++- 18 files changed, 584 insertions(+), 474 deletions(-) create mode 100644 lib/command.js create mode 100644 lib/errors/taskerror.js create mode 100644 lib/expect.js create mode 100644 lib/tasks.js diff --git a/cli.js b/cli.js index f7294e84b1e..1428af0d62c 100755 --- a/cli.js +++ b/cli.js @@ -1,392 +1,48 @@ #!/usr/bin/env node require("babel-register"); -var path = require("path"); -var fs = require("fs"); -var chokidar = require('chokidar'); -var deasync = require("deasync"); -var colors = require('colors/safe'); -var temp = require("temp").track(); -var filesSync = deasync(require("node-dir").files); -var Truffle = require('./index.js'); - -var ConfigurationError = require("./lib/errors/configurationerror"); +var Command = require("./lib/command"); +var Tasks = require("./lib/tasks"); +var TaskError = require("./lib/errors/taskerror"); var ExtendableError = require("./lib/errors/extendableerror"); -var copy = require('./lib/copy'); - -var argv = require('yargs').argv; - -var truffle_dir = process.env.TRUFFLE_NPM_LOCATION || argv.n || argv.npm_directory || __dirname; -var working_dir = process.env.TRUFFLE_WORKING_DIRECTORY || argv.w || argv.working_directory || process.cwd(); -var environment = argv.e || argv.environment || process.env.NODE_ENV || "default"; - -if (working_dir[working_dir.length - 1] != "/") { - working_dir += "/"; -} - -var pkg = JSON.parse(fs.readFileSync(path.join(truffle_dir, "package.json"), {encoding: "utf8"})); - -var tasks = {}; -var registerTask = function(name, description, fn) { - tasks[name] = { - name: name, - description: description, - fn: fn - }; -} - -var printNetwork = function() { - console.log("Using network " + environment + "."); -}; - -var printSuccess = function() { - console.log(colors.green("Completed without errors on " + new Date().toString())); -}; - -var printFailure = function() { - console.log(colors.red("Completed with errors on " + new Date().toString())); -}; - -var runTask = function(name) { - try { - var fn = deasync(tasks[name].fn); - return fn() || 0; - } catch (e) { - if (e instanceof ExtendableError) { - console.log(e.message); - - if (argv.stack != null) { - console.log(e.stack); - } +var command = new Command(Tasks); +command.run(process.argv.slice(2), function(err) { + if (err) { + if (err instanceof TaskError) { + command.run("list", function() {}); } else { - // Bubble up all other unexpected errors. - console.log(e.stack || e.toString()); - } - return 1; - } -}; - -registerTask('watch', "Watch filesystem for changes and rebuild the project automatically", function(done) { - var needs_rebuild = true; - - chokidar.watch(["app/**/*", "environments/*/contracts/**/*", "contracts/**/*", "truffle.json", "truffle.js"], { - ignored: /[\/\\]\./, // Ignore files prefixed with "." - cwd: working_dir, - ignoreInitial: true - }).on('all', function(event, filePath) { - // On changed/added/deleted - var display_path = path.join("./", filePath.replace(working_dir, "")); - console.log(colors.cyan(">> File " + display_path + " changed.")); - - needs_rebuild = true; - }); - - var check_rebuild = function() { - if (needs_rebuild == true) { - needs_rebuild = false; - console.log("Rebuilding..."); - if (runTask("build") != 0) { - printFailure(); + if (err instanceof ExtendableError) { + console.log(err.message); + } else { + // Bubble up all other unexpected errors. + console.log(err.stack || err.toString()); } } - - setTimeout(check_rebuild, 200); - }; - - check_rebuild(); -}); - -registerTask('list', "List all available tasks", function(done) { - console.log("Truffle v" + pkg.version + " - a development framework for Ethereum"); - console.log(""); - console.log("Usage: truffle [command] [options]"); - console.log(""); - console.log("Commands:"); - console.log(""); - - var sorted = Object.keys(tasks).sort(); - - var longestTask = sorted.reduce(function(a, b) { - var first = typeof a == "string" ? a.length : a; - return Math.max(first, b.length); - }); - - for (var i = 0; i < sorted.length; i++) { - var task = tasks[sorted[i]]; - var heading = task.name; - while (heading.length < longestTask) { - heading += " "; - } - console.log(" " + heading + " => " + task.description) - } - - console.log(""); - done(); -}); - -registerTask('version', "Show version number and exit", function(done) { - console.log("Truffle v" + pkg.version); - done(); -}); - -registerTask('init', "Initialize new Ethereum project, including example contracts and tests", function(done) { - var config = Truffle.config.default(); - Truffle.init(config.working_directory, done); -}); - -registerTask('create:contract', "Create a basic contract", function(done) { - var config = Truffle.config.detect(environment, argv); - - var name = argv.name; - - if (name == null && argv._.length > 1) { - name = argv._[1]; - } - - if (name == null) { - throw new ConfigurationError("Please specify a name. Example: truffle create:contract MyContract"); - } else { - Truffle.create.contract(config.contracts_directory, name, done); - } -}); - -registerTask('create:test', "Create a basic test", function(done) { - // Force the test environment. - environment = "test"; - printNetwork(); - var config = Truffle.config.detect(environment, argv); - - var name = argv.name; - - if (name == null && argv._.length > 1) { - name = argv._[1]; - } - - if (name == null) { - throw new ConfigurationError("Please specify a name. Example: truffle create:test MyTest"); - } else { - Truffle.create.test(config.test_directory, name, done); - } -}); - -registerTask('compile', "Compile contracts", function(done) { - var config = Truffle.config.detect(environment, argv); - Truffle.contracts.compile({ - all: argv.all === true, - contracts_directory: config.contracts_directory, - contracts_build_directory: config.contracts_build_directory, - quiet: argv.quiet === true, - strict: argv.strict === true, - network: config.network, - network_id: config.network_id - }, done); -}); - -registerTask('build', "Build development version of app", function(done) { - var config = Truffle.config.detect(environment, argv); - Truffle.build.build({ - builder: config.build, - build_directory: config.build_directory, - working_directory: config.working_directory, - contracts_build_directory: config.contracts_build_directory, - processors: config.processors, // legacy option for default builder - network: config.network, - network_id: config.network_id, - provider: config.provider, - rpc: config.getRPCConfig() - }, function(err) { - done(err); - if (err == null) { - printSuccess(); - } - }); -}); - -registerTask('migrate', "Run migrations", function(done) { - var config = Truffle.config.detect(environment, argv); - - Truffle.contracts.compile({ - all: argv.compileAll === true, - contracts_directory: config.contracts_directory, - contracts_build_directory: config.contracts_build_directory, - network: config.network, - quiet: argv.quiet === true, - strict: argv.strict === true, - network: config.network, - network_id: config.network_id - }, function(err) { - if (err) return done(err); - - Truffle.migrate.run({ - migrations_directory: config.migrations_directory, - contracts_build_directory: config.contracts_build_directory, - provider: config.provider, - network: config.network, - reset: argv.reset || false - }, done); - }); -}); - -registerTask('exec', "Execute a JS file within truffle environment. Script *must* call process.exit() when finished.", function(done) { - var config = Truffle.config.detect(environment, argv); - - var file = argv.file; - - if (file == null && argv._.length > 1) { - file = argv._[1]; } - - if (file == null) { - console.log("Please specify a file, passing the path of the script you'd like the run. Note that all scripts *must* call process.exit() when finished."); - done(); - return; - } - - if (path.isAbsolute(file) == false) { - file = path.join(config.working_directory, file); - } - - Truffle.require.exec({ - file: file, - contracts_build_directory: config.contracts_build_directory, - provider: config.provider - }); }); -// Supported options: -// --no-color: Disable color -// More to come. -registerTask('test', "Run tests", function(done) { - environment = "test"; - var config = Truffle.config.detect(environment, argv); - - var files = []; - - if (argv.file) { - files = [argv.file]; - } else if (argv._.length > 1) { - Array.prototype.push.apply(files, argv._); - files.shift(); // Remove "test" - } - - if (files.length == 0) { - files = filesSync(config.test_directory); - } - - files = files.filter(function(file) { - return file.match(config.test_file_extension_regexp) != null; - }); - - // if (files.length == 0) { - // return done(new Error("Cannot find any valid test files. Bailing.")); - // } +//var environment = argv.e || argv.environment || process.env.NODE_ENV || "default"; +// +// if (working_dir[working_dir.length - 1] != "/") { +// working_dir += "/"; +// } +// +// var printNetwork = function() { +// console.log("Using network " + environment + "."); +// }; +// +// var printSuccess = function() { +// console.log(colors.green("Completed without errors on " + new Date().toString())); +// }; +// +// var printFailure = function() { +// console.log(colors.red("Completed with errors on " + new Date().toString())); +// }; +// +// - temp.mkdir('test-', function(err, temporaryDirectory) { - if (err) return done(err); - function cleanup() { - var args = arguments; - // Ensure directory cleanup. - temp.cleanup(function(err) { - // Ignore cleanup errors. - done.apply(null, args); - }); - }; - - function run() { - Truffle.test.run({ - compileAll: argv.compileAll, - contracts_directory: config.contracts_directory, - contracts_build_directory: temporaryDirectory, - migrations_directory: config.migrations_directory, - test_files: files, - network: "test", - network_id: "default", - provider: config.provider - }, cleanup); - }; - - // Copy all the built files over to a temporary directory, because we - // don't want to save any tests artifacts. Only do this if the build directory - // exists. - fs.stat(config.contracts_build_directory, function(err, stat) { - if (err) return run(); - - copy(config.contracts_build_directory, temporaryDirectory, function(err) { - if (err) return done(err); - run(); - }); - }); - }); -}); - -registerTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(done) { - var config = Truffle.config.detect(environment, argv); - Truffle.console.run({ - working_directory: config.working_directory, - contracts_directory: config.contracts_directory, - contracts_build_directory: config.contracts_build_directory, - migrations_directory: config.migrations_directory, - network: config.network, - network_id: config.network_id, - provider: config.provider, - builder: config.build, - build_directory: config.build_directory, - processors: config.processors, // legacy option for default builder - rpc: config.getRPCConfig() - }, done); -}); - -registerTask('serve', "Serve app on localhost and rebuild changes as needed", function(done) { - var config = Truffle.config.detect(environment, argv); - Truffle.serve.start({ - build_directory: config.build_directory - }, argv.p || argv.port || "8080", function() { - runTask("watch"); - }); -}); - -registerTask('networks', "Show addresses for deployed contracts on each network", function(done) { - var config = Truffle.config.detect(environment, argv); - - Truffle.profile.deployed_networks(config, function(err, networks) { - if (err) return callback(err); - - Object.keys(networks).sort().forEach(function(network_name) { - - console.log("") - - var output = Object.keys(networks[network_name]).sort().map(function(contract_name) { - var address = networks[network_name][contract_name]; - return contract_name + ": " + address; - }); - - if (output.length == 0) { - output = ["No contracts deployed."]; - } - - console.log("Network: " + network_name); - console.log(" " + output.join("\n ")) - }); - - console.log(""); - - done(); - }); -}); - -// Default to listing available commands. -var current_task = argv._[0]; - -if (current_task == null) { - current_task = "list"; -} - -if (tasks[current_task] == null) { - console.log(colors.red("Unknown command: " + current_task)); - process.exit(1); -} // // Check to see if we're working on a dapp meant for 0.2.x or older @@ -403,8 +59,3 @@ if (tasks[current_task] == null) { // console.log("Cheers! And file an issue if you run into trouble! https://github.com/ConsenSys/truffle/issues") // process.exit(); // } - -// Something is keeping the process open. I'm not sure what. -// Let's explicitly kill it until I can figure it out. -process.exit(runTask(current_task)); -//runTask(current_task); diff --git a/index.js b/index.js index 439319ef6a6..ae625a8fa4d 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +var pkg = require("./package.json"); + module.exports = { build: require("./lib/build"), create: require("./lib/create"), @@ -10,5 +12,6 @@ module.exports = { migrate: require("./lib/migrate"), profile: require("./lib/profiler"), serve: require("./lib/serve"), - test: require("./lib/test") + test: require("./lib/test"), + version: pkg.version }; diff --git a/lib/build.js b/lib/build.js index db56dd8b00d..686c1ff0368 100644 --- a/lib/build.js +++ b/lib/build.js @@ -9,6 +9,7 @@ var BuildError = require("./errors/builderror"); var child_process = require("child_process"); var spawnargs = require("spawn-args"); var _ = require("lodash"); +var expect = require("./expect"); function CommandBuilder(command) { this.command = command; @@ -68,6 +69,17 @@ var Build = { build: function(options, key, callback) { var self = this; + expect.options(options, [ + "builder", + "build_directory", + "working_directory", + "contracts_build_directory", + "network", + "network_id", + "provider", + "rpc" + ]); + if (typeof key == "function") { callback = key; key = "build"; diff --git a/lib/command.js b/lib/command.js new file mode 100644 index 00000000000..42fc95f6339 --- /dev/null +++ b/lib/command.js @@ -0,0 +1,54 @@ +var TaskError = require("./errors/taskerror"); +var yargs = require("yargs"); +var _ = require("lodash"); + +function Command(tasks) { + this.tasks = tasks; +}; + +Command.prototype.getTask = function(command) { + var argv = yargs.parse(command); + + if (argv._.length == 0) { + return null; + } + + var task_name = argv._[0]; + var task = this.tasks[task_name]; + + return task; +}; + +Command.prototype.run = function(command, options, callback) { + if (typeof options == "function") { + callback = options; + options = {}; + } + + var task = this.getTask(command); + + if (task == null) { + if (Array.isArray(command)) { + command = command.join(" ") + } + + return callback(new TaskError("Cannot find task for command: " + command)); + } + + var argv = yargs.parse(command); + // Remove the task name itself. + if (argv._) { + argv._.shift(); + } + + // We don't need this. + delete argv["$0"]; + + options = _.extend(options, argv); + + task(options, function(err) { + callback(err); + }); +}; + +module.exports = Command; diff --git a/lib/config.js b/lib/config.js index 2579f73b1ff..5c90fbdaab9 100644 --- a/lib/config.js +++ b/lib/config.js @@ -114,6 +114,10 @@ Config.prototype.with = function(obj) { return _.extend({}, this, obj); }; +Config.prototype.merge = function(obj) { + return _.extend(this, obj); +}; + // Helper function for expecting paths to exist. Config.expect = function(expected_path, description, extra, callback) { if (typeof description == "function") { @@ -153,7 +157,7 @@ Config.default = function() { return new Config(); }; -Config.detect = function(network, argv, filename) { +Config.detect = function(options, filename) { if (filename == null) { filename = DEFAULT_CONFIG_FILENAME; } @@ -164,21 +168,17 @@ Config.detect = function(network, argv, filename) { throw new ConfigurationError("Could not find suitable configuration file."); } - return this.load(file, network, argv); + return this.load(file, options); }; -Config.load = function(file, network, argv) { +Config.load = function(file, options) { var config = new Config(); config.working_directory = path.dirname(path.resolve(file)); var static_config = requireNoCache(file); - config = _.merge(config, static_config, argv); - - if (network) { - config.network = network; - } + config = _.merge(config, static_config, options); return config; }; diff --git a/lib/contracts.js b/lib/contracts.js index 023e1db7eeb..f257bb6d3ae 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -3,13 +3,11 @@ var fs = require("fs"); var mkdirp = require("mkdirp"); var path = require("path"); var Compiler = require("./compiler"); -var Require = require("./require"); var Pudding = require("ether-pudding"); var Web3 = require("web3"); +var expect = require("./expect"); var Contracts = { - account: null, - provision: function(options, callback) { var self = this; var logger = options.logger || console; @@ -59,6 +57,13 @@ var Contracts = { compile: function(options, callback) { var self = this; + expect.options(options, [ + "contracts_directory", + "contracts_build_directory", + "network", + "network_id" + ]); + function finished(err, contracts) { if (err) return callback(err); @@ -69,10 +74,10 @@ var Contracts = { } }; - if (options.all == false) { - Compiler.compile_necessary(options, finished); - } else { + if (options.all === true || options.compileAll === true) { Compiler.compile_all(options, finished); + } else { + Compiler.compile_necessary(options, finished); } }, diff --git a/lib/create.js b/lib/create.js index 0a60e8e05e5..0132401be7a 100644 --- a/lib/create.js +++ b/lib/create.js @@ -30,6 +30,7 @@ var Create = { }, test: function(directory, name, callback) { var underscored = util.toUnderscoreFromCamel(name); + underscored = underscored.replace(/\./g, "_"); var from = templates.test.filename; var to = path.join(directory, underscored + ".js"); diff --git a/lib/deployer.js b/lib/deployer.js index 2ba54c5be92..850d2e97221 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -2,6 +2,7 @@ var EventEmitter = require("events").EventEmitter; var inherits = require("util").inherits; var Linker = require("./linker"); var Require = require("./require"); +var expect = require("./expect"); var path = require("path"); var Actions = { @@ -33,6 +34,13 @@ function Deployer(options) { Deployer.super_.call(this); var self = this; options = options || {}; + + expect.options(options, [ + "provider", + "network", + "network_id" + ]); + this.chain = new Promise(function(accept, reject) { self._accept = accept; self._reject = reject; @@ -45,6 +53,8 @@ function Deployer(options) { (options.contracts || []).forEach(function(contract) { self.known_contracts[contract.contract_name] = contract; }); + this.network = options.network; + this.network_id = options.network_id; this.provider = options.provider; this.basePath = options.basePath || process.cwd(); this.started = false; @@ -165,6 +175,8 @@ Deployer.prototype.exec = function(file) { contracts: Object.keys(self.known_contracts).map(function(key) { return self.known_contracts[key]; }), + network: self.network, + network_id: self.network_id, provider: self.provider }, function(err) { if (err) return reject(err); diff --git a/lib/errors/taskerror.js b/lib/errors/taskerror.js new file mode 100644 index 00000000000..d2b85ec99c5 --- /dev/null +++ b/lib/errors/taskerror.js @@ -0,0 +1,10 @@ +var ExtendableError = require("./extendableerror"); +var inherits = require("util").inherits; + +inherits(TaskError, ExtendableError); + +function TaskError(message) { + TaskError.super_.call(this, message); +}; + +module.exports = TaskError; diff --git a/lib/expect.js b/lib/expect.js new file mode 100644 index 00000000000..589ccc27675 --- /dev/null +++ b/lib/expect.js @@ -0,0 +1,11 @@ +var Expect = { + options: function(options, expected_keys) { + expected_keys.forEach(function(key) { + if (options[key] == null) { + throw new Error("Expected parameter '" + key + "' not passed to function."); + } + }); + } +} + +module.exports = Expect; diff --git a/lib/migrate.js b/lib/migrate.js index b1da2538cee..ee0836dcbda 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -9,11 +9,11 @@ var Provider = require("./provider"); var Require = require("./require"); var async = require("async"); var Web3 = require("web3"); +var expect = require("./expect"); -function Migration(file, network) { +function Migration(file) { this.file = path.resolve(file); this.number = parseInt(path.basename(file)); - this.network = network || "default"; }; Migration.prototype.run = function(options, contracts, callback) { @@ -53,6 +53,8 @@ Migration.prototype.run = function(options, contracts, callback) { } }, contracts: contracts, + network: options.network, + network_id: options.network_id, provider: options.provider, basePath: path.dirname(this.file) }); @@ -69,7 +71,7 @@ Migration.prototype.run = function(options, contracts, callback) { }).then(function() { if (options.save === false) return; logger.log("Saving artifacts..."); - return Pudding.saveAll(contracts, options.contracts_build_directory, options.network_id); + return Pudding.saveAll(contracts, options.contracts_build_directory, options); }).then(function() { callback(); }).catch(function(e) { @@ -87,10 +89,10 @@ Migration.prototype.run = function(options, contracts, callback) { return callback(new Error("Migration " + self.file + " invalid or does not take any parameters")); } if (fn.length == 1 || fn.length == 2) { - fn(deployer, self.network); + fn(deployer, options.network); finish(); } else if (fn.length == 3) { - fn(deployer, self.network, finish); + fn(deployer, options.network, finish); } }); }; @@ -123,6 +125,14 @@ var Migrate = { run: function(options, callback) { var self = this; + expect.options(options, [ + "migrations_directory", + "contracts_build_directory", + "provider", + "network", + "network_id" + ]); + if (options.reset == true) { return this.runAll(options, callback); } @@ -177,7 +187,7 @@ var Migrate = { lastCompletedMigration: function(options, callback) { var migrations_contract = path.resolve(path.join(options.contracts_build_directory, "Migrations.sol.js")); - Pudding.requireFile(migrations_contract, function(err, Migrations) { + Pudding.requireFile(migrations_contract, options, function(err, Migrations) { if (err) return callback(new Error("Could not find built Migrations contract.")); if (Migrations.address == null) { diff --git a/lib/profiler.js b/lib/profiler.js index c3e2de066cd..bb7f39384d6 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -99,9 +99,11 @@ module.exports = { networks[network_name] = {}; } - if (contract.address == null) return; + var address = contract.all_networks[network_id].address; - networks[network_name][contract.contract_name] = contract.address; + if (address == null) return; + + networks[network_name][contract.contract_name] = address; }); }); diff --git a/lib/repl.js b/lib/repl.js index 53489507865..d56608bdaaa 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1,14 +1,16 @@ var repl = require("repl"); +var Command = require("./command"); var Contracts = require("./contracts"); -var Migrate = require("./migrate"); -var Build = require("./build"); var Web3 = require("web3"); var vm = require("vm"); +var expect = require("./expect"); +var _ = require("lodash"); -function TruffleInterpreter(options) { +function TruffleInterpreter(tasks, options) { this.options = options; this.contracts = []; this.r = null; + this.command = new Command(tasks); }; TruffleInterpreter.prototype.start = function() { @@ -69,70 +71,9 @@ TruffleInterpreter.prototype.resetContracts = function() { } } -TruffleInterpreter.prototype.compile = function(all, callback) { - var options = this.options; - - if (typeof all == "function") { - callback = all; - all = false; - } - - Contracts.compile({ - all: !!all, - contracts_directory: options.contracts_directory, - contracts_build_directory: options.contracts_build_directory, - network: options.network, - network_id: options.network_id - }, callback); -}; - -TruffleInterpreter.prototype.migrate = function(reset, callback) { - var options = this.options; - - if (typeof reset == "function") { - callback = reset; - reset = false; - } - - this.compile(false, function(err) { - if (err) return callback(err); - - Migrate.run({ - migrations_directory: options.migrations_directory, - contracts_build_directory: options.contracts_build_directory, - network: options.network, - network_id: options.network_id, - provider: options.provider, - reset: !!reset - }, function(err) { - console.log(""); - callback(err); - }); - }); -}; - -TruffleInterpreter.prototype.build = function(callback) { - var options = this.options; - - Build.build({ - builder: options.builder, - build_directory: options.build_directory, - working_directory: options.working_directory, - contracts_build_directory: options.contracts_build_directory, - processors: options.processors, // legacy option for default builder - provider: options.provider, - rpc: options.rpc - }, callback); -}; - TruffleInterpreter.prototype.interpret = function(cmd, context, filename, callback) { - switch (cmd.trim()) { - case "compile": - return this.compile(callback); - case "migrate": - return this.migrate(callback); - case "build": - return this.build(callback); + if (this.command.getTask(cmd.trim()) != null) { + return this.command.run(cmd.trim(), this.options, callback); } var result; @@ -149,10 +90,23 @@ TruffleInterpreter.prototype.interpret = function(cmd, context, filename, callba var Repl = { TruffleInterpreter: TruffleInterpreter, - run: function(options) { + run: function(tasks, options) { var self = this; - var interpreter = new TruffleInterpreter(options); + expect.options(options, [ + "working_directory", + "contracts_directory", + "contracts_build_directory", + "migrations_directory", + "network", + "network_id", + "provider", + "builder", + "build_directory", + "rpc" + ]); + + var interpreter = new TruffleInterpreter(tasks, options); interpreter.start(); } } diff --git a/lib/require.js b/lib/require.js index f25a95860bb..3ad691e7ccb 100644 --- a/lib/require.js +++ b/lib/require.js @@ -2,6 +2,8 @@ var fs = require("fs"); var path = require("path"); var Module = require('module'); var vm = require('vm'); +var expect = require("./expect"); +var Contracts = require("./contracts"); var Web3 = require("web3"); // options.file: path to file to execute. Must be a module that exports a function. @@ -84,6 +86,13 @@ var Require = { exec: function(options, done) { var self = this; + expect.options(options, [ + "file", + "provider", + "network", + "network_id" + ]); + var provision = function(callback) { if (options.contracts != null) { callback(null, options.contracts); diff --git a/lib/serve.js b/lib/serve.js index 7231feb63f9..50f74b2f76f 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -3,7 +3,7 @@ var finalhandler = require('finalhandler'); var serveStatic = require('serve-static'); var Serve = { - start: function(options, port, done) { + start: function(options, done) { var serve = serveStatic(options.build_directory); var server = http.createServer(function(req, res) { @@ -11,6 +11,8 @@ var Serve = { serve(req, res, done); }); + var port = options.port || options.p || 8080; + server.listen(port); console.log("Serving app on port " + port + "..."); done(); diff --git a/lib/tasks.js b/lib/tasks.js new file mode 100644 index 00000000000..82ef986b468 --- /dev/null +++ b/lib/tasks.js @@ -0,0 +1,315 @@ +var Truffle = require('../index.js'); +var path = require("path"); +var dir = require("node-dir"); +var temp = require("temp").track(); +var fs = require("fs"); +var copy = require("./copy"); +var chokidar = require("chokidar"); +var colors = require("colors"); + +var Tasks = {}; + +function createTask(name, description, fn) { + Tasks[name] = function(options, done) { + if (typeof options == "function") { + done = options; + options = {}; + } + + options.logger = options.logger || console; + + fn(options, done); + }; + Tasks[name].description = description; + Tasks[name].task_name = name; +}; + +createTask('list', "List all available tasks", function(options, done) { + options.logger.log("Truffle v" + Truffle.version + " - a development framework for Ethereum"); + options.logger.log(""); + options.logger.log("Usage: truffle [command] [options]"); + options.logger.log(""); + options.logger.log("Commands:"); + options.logger.log(""); + + var sorted = Object.keys(Tasks).sort(); + + var longestTask = sorted.reduce(function(a, b) { + var first = typeof a == "string" ? a.length : a; + return Math.max(first, b.length); + }); + + for (var i = 0; i < sorted.length; i++) { + var task = Tasks[sorted[i]]; + var heading = task.task_name; + while (heading.length < longestTask) { + heading += " "; + } + options.logger.log(" " + heading + " => " + task.description) + } + + options.logger.log(""); + done(); +}); + +createTask('version', "Show version number and exit", function(options, done) { + options.logger.log("Truffle v" + Truffle.version); + done(); +}); + +createTask('init', "Initialize new Ethereum project, including example contracts and tests", function(options, done) { + var config = Truffle.config.default(); + Truffle.init(config.working_directory, done); +}); + +createTask('create:contract', "Create a basic contract", function(options, done) { + var config = Truffle.config.detect(options); + + var name = options.name; + + if (name == null && options._.length > 0) { + name = options._[0]; + } + + if (name == null) { + return done(new ConfigurationError("Please specify a name. Example: truffle create:contract MyContract")); + } else { + Truffle.create.contract(config.contracts_directory, name, done); + } +}); + +createTask('create:test', "Create a basic test", function(options, done) { + var config = Truffle.config.detect(options); + + var name = options.name; + + if (name == null && options._.length > 0) { + name = options._[0]; + } + + if (name == null) { + return done(new ConfigurationError("Please specify a name. Example: truffle create:test MyTest")); + } else { + Truffle.create.test(config.test_directory, name, done); + } +}); + +createTask('compile', "Compile contracts", function(options, done) { + var config = Truffle.config.detect(options); + Truffle.contracts.compile(config, done); +}); + +createTask('build', "Build development version of app", function(options, done) { + var config = Truffle.config.detect(options); + Truffle.build.build(config.with({ + builder: config.build, + processors: config.processors, // legacy option for default builder + rpc: config.getRPCConfig() + }), done); +}); + +createTask('migrate', "Run migrations", function(options, done) { + var config = Truffle.config.detect(options); + + Truffle.contracts.compile(config, function(err) { + if (err) return done(err); + Truffle.migrate.run(config, done); + }); +}); + +createTask('exec', "Execute a JS file within truffle environment", function(options, done) { + var config = Truffle.config.detect(options); + + var file = options.file; + + if (file == null && options._.length > 0) { + file = options._[0]; + } + + if (file == null) { + options.logger.log("Please specify a file, passing the path of the script you'd like the run. Note that all scripts *must* call process.exit() when finished."); + done(); + return; + } + + if (path.isAbsolute(file) == false) { + file = path.join(process.cwd(), file); + } + + Truffle.require.exec(config.with({ + file: file + }), done); +}); + +// Supported options: +// --no-color: Disable color +// More to come. +createTask('test', "Run tests", function(options, done) { + var config = Truffle.config.detect(options); + config.network = "test"; + + var files = []; + + if (options.file) { + files = [options.file]; + } else if (options._.length > 0) { + Array.prototype.push.apply(files, options._); + } + + function getFiles(callback) { + if (files.length != 0) { + return callback(null, files); + } + + dir.files(config.test_directory, callback); + }; + + getFiles(function(err, files) { + files = files.filter(function(file) { + return file.match(config.test_file_extension_regexp) != null; + }); + + temp.mkdir('test-', function(err, temporaryDirectory) { + if (err) return done(err); + + function cleanup() { + var args = arguments; + // Ensure directory cleanup. + temp.cleanup(function(err) { + // Ignore cleanup errors. + done.apply(null, args); + }); + }; + + function run() { + Truffle.test.run(config.with({ + test_files: files, + contracts_build_directory: temporaryDirectory, + }), cleanup); + }; + + // Copy all the built files over to a temporary directory, because we + // don't want to save any tests artifacts. Only do this if the build directory + // exists. + fs.stat(config.contracts_build_directory, function(err, stat) { + if (err) return run(); + + copy(config.contracts_build_directory, temporaryDirectory, function(err) { + if (err) return done(err); + run(); + }); + }); + }); + }); +}); + +createTask('console', "Run a console with deployed contracts instantiated and available (REPL)", function(options, done) { + var config = Truffle.config.detect(options); + + var available_tasks = Object.keys(Tasks).filter(function(task_name) { + return task_name != "console" && task_name != "init" && task_name != "watch" && task_name != "serve"; + }); + + var tasks = {}; + available_tasks.forEach(function(task_name) { + tasks[task_name] = Tasks[task_name]; + }); + + Truffle.console.run(tasks, config.with({ + builder: config.build, + processors: config.processors, // legacy option for default builder + }), done); +}); + +createTask('serve', "Serve app on localhost and rebuild changes as needed", function(options, done) { + var config = Truffle.config.detect(options); + Truffle.serve.start(config, function() { + runTask("watch"); + }); +}); + +createTask('networks', "Show addresses for deployed contracts on each network", function(options, done) { + var config = Truffle.config.detect(options); + + Truffle.profile.deployed_networks(config, function(err, networks) { + if (err) return callback(err); + + Object.keys(networks).sort().forEach(function(network_name) { + + options.logger.log("") + + var output = Object.keys(networks[network_name]).sort().map(function(contract_name) { + var address = networks[network_name][contract_name]; + return contract_name + ": " + address; + }); + + if (output.length == 0) { + output = ["No contracts deployed."]; + } + + options.logger.log("Network: " + network_name); + options.logger.log(" " + output.join("\n ")) + }); + + options.logger.log(""); + + done(); + }); +}); + +createTask('watch', "Watch filesystem for changes and rebuild the project automatically", function(options, done) { + var config = Truffle.config.detect(options); + + var printSuccess = function() { + options.logger.log(colors.green("Completed without errors on " + new Date().toString())); + }; + + var printFailure = function() { + options.logger.log(colors.red("Completed with errors on " + new Date().toString())); + }; + + var needs_rebuild = true; + + var watchPaths = [ + path.join(config.working_directory, "app/**/*"), + path.join(config.contracts_build_directory, "/**/*"), + path.join(config.contracts_directory, "/**/*"), + path.join(config.working_directory, "truffle.json"), + path.join(config.working_directory, "truffle.js") + ]; + + chokidar.watch(watchPaths, { + ignored: /[\/\\]\./, // Ignore files prefixed with "." + cwd: config.working_directory, + ignoreInitial: true + }).on('all', function(event, filePath) { + // On changed/added/deleted + var display_path = path.join("./", filePath.replace(config.working_directory, "")); + options.logger.log(colors.cyan(">> File " + display_path + " changed.")); + + needs_rebuild = true; + }); + + var check_rebuild = function() { + if (needs_rebuild == true) { + needs_rebuild = false; + options.logger.log("Rebuilding..."); + + Tasks.build(options, function(err) { + if (err) { + printFailure(); + } else { + printSuccess(); + } + done(err); + }); + } + + setTimeout(check_rebuild, 200); + }; + + check_rebuild(); +}); + + +module.exports = Tasks; diff --git a/lib/test.js b/lib/test.js index c25a6e2d2bd..b8f79243fdf 100644 --- a/lib/test.js +++ b/lib/test.js @@ -10,6 +10,7 @@ var Pudding = require("ether-pudding"); var Promise = require("bluebird"); var ExtendableError = require("./errors/extendableerror"); var SolidityCoder = require("web3/lib/solidity/coder.js"); +var expect = require("./expect"); chai.use(require("./assertions")); @@ -231,6 +232,16 @@ TestRunner.prototype.rpc = function(method, arg, cb) { var Test = { run: function(options, callback) { + expect.options(options, [ + "contracts_directory", + "contracts_build_directory", + "migrations_directory", + "test_files", + "network", + "network_id", + "provider" + ]); + // Compile if needed. This will Contracts.compile({ all: options.compileAll === true, diff --git a/test/migrate.js b/test/migrate.js index 7a2fec3e7e0..39c202b4a3b 100644 --- a/test/migrate.js +++ b/test/migrate.js @@ -51,7 +51,6 @@ describe("migrate", function() { if (err) return done(err); Migrate.run(config.with({ - reset: true, quiet: true }), function(err, contracts) { if (err) return done(err); @@ -70,4 +69,53 @@ describe("migrate", function() { }); }); }); + + it('should migrate secondary network without altering primary network', function(done) { + this.timeout(10000); + + config.network = "secondary"; + + var currentAddresses = {}; + + Profiler.deployed_networks(config, function(err, networks) { + if (err) return done(err); + + ["MetaCoin", "ConvertLib", "Migrations"].forEach(function(contract_name) { + currentAddresses[contract_name] = networks["default"][contract_name]; + }); + + Contracts.compile(config.with({ + quiet: true + }), function(err) { + if (err) return done(err); + + Migrate.run(config.with({ + quiet: true + }), function(err, contracts) { + if (err) return done(err); + + Profiler.deployed_networks(config, function(err, networks) { + if (err) return done(err); + + assert.equal(Object.keys(networks).length, 2, "Should have results for two networks from profiler"); + assert.equal(Object.keys(networks["default"]).length, 3, "Default network should have three contracts deployed"); + assert.equal(networks["default"]["MetaCoin"], currentAddresses["MetaCoin"], "MetaCoin contract updated on default network"); + assert.equal(networks["default"]["ConvertLib"], currentAddresses["ConvertLib"], "ConvertLib library updated on default network"); + assert.equal(networks["default"]["Migrations"], currentAddresses["Migrations"], "Migrations contract updated on default network"); + assert.equal(Object.keys(networks["secondary"]).length, 3, "Secondary network should have three contracts deployed"); + assert.isNotNull(networks["secondary"]["MetaCoin"], "MetaCoin contract should have an address on secondary network"); + assert.isNotNull(networks["secondary"]["ConvertLib"], "ConvertLib library should have an address on secondary network"); + assert.isNotNull(networks["secondary"]["Migrations"], "Migrations contract should have an address on secondary network"); + + Object.keys(networks["default"]).forEach(function(contract_name) { + assert.notEqual(networks["secondary"][contract_name], networks["default"][contract_name], "Contract " + contract_name + " has the same address on both networks") + }); + + done(); + }); + }); + }); + + }); + }); }); From 985acb6de2a188167efa0d9ff077ae1da1735f49 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 27 May 2016 14:41:53 -0700 Subject: [PATCH 23/26] Reprovision after each command in the console so you can work with newly deployed/compiled contracts. --- lib/command.js | 2 +- lib/config.js | 4 +--- lib/contracts.js | 5 ----- lib/migrate.js | 8 ++++++++ lib/repl.js | 10 +++++++++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/command.js b/lib/command.js index 42fc95f6339..e7719f93e94 100644 --- a/lib/command.js +++ b/lib/command.js @@ -44,7 +44,7 @@ Command.prototype.run = function(command, options, callback) { // We don't need this. delete argv["$0"]; - options = _.extend(options, argv); + options = _.extend(_.clone(options), argv); task(options, function(err) { callback(err); diff --git a/lib/config.js b/lib/config.js index 5c90fbdaab9..e0901f5dd46 100644 --- a/lib/config.js +++ b/lib/config.js @@ -178,9 +178,7 @@ Config.load = function(file, options) { var static_config = requireNoCache(file); - config = _.merge(config, static_config, options); - - return config; + return _.merge(config, static_config, options); }; module.exports = Config; diff --git a/lib/contracts.js b/lib/contracts.js index f257bb6d3ae..3dc60beb27a 100644 --- a/lib/contracts.js +++ b/lib/contracts.js @@ -35,11 +35,6 @@ var Contracts = { if (options.network_id) { contract.setNetwork(options.network_id); } - - // If all addresses should be reset. - if (options.reset == true) { - contract.address = null; - } }); callback(null, contracts); diff --git a/lib/migrate.js b/lib/migrate.js index ee0836dcbda..be5b48df7ee 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -1,3 +1,5 @@ + + var fs = require("fs"); var dir = require("node-dir"); var path = require("path"); @@ -175,6 +177,12 @@ var Migrate = { Contracts.provision(options, function(err, contracts) { if (err) return callback(err); + if (options.reset == true) { + contracts.forEach(function(contract) { + contract.address = null; + }) + } + async.eachSeries(migrations, function(migration, finished) { migration.run(options, contracts, function(err) { if (err) return finished(err); diff --git a/lib/repl.js b/lib/repl.js index d56608bdaaa..942af18154a 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -51,6 +51,7 @@ TruffleInterpreter.prototype.start = function() { TruffleInterpreter.prototype.provision = function(callback) { var self = this; + Contracts.provision(this.options, function(err, contracts) { if (err) return callback(err); @@ -72,8 +73,15 @@ TruffleInterpreter.prototype.resetContracts = function() { } TruffleInterpreter.prototype.interpret = function(cmd, context, filename, callback) { + var self = this; + if (this.command.getTask(cmd.trim()) != null) { - return this.command.run(cmd.trim(), this.options, callback); + return this.command.run(cmd.trim(), this.options, function(err) { + if (err) return callback(err); + + // Reprovision after each command is it may change contracts. + self.provision(callback); + }); } var result; From 68fd4ab549a3733bef5e516975c85d7ddc776d11 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 3 Jun 2016 12:27:35 -0700 Subject: [PATCH 24/26] Deployer: Move actions into individual functions that can be queued or executed immediately; pull out deferred chain so it's less complicated and not coupled to deployer. Other files: various fixes/improvements. --- lib/deferredchain.js | 43 ++++++++ lib/deployer.js | 229 +++++++++++++++++++++++-------------------- lib/tasks.js | 3 +- package.json | 4 +- 4 files changed, 167 insertions(+), 112 deletions(-) create mode 100644 lib/deferredchain.js diff --git a/lib/deferredchain.js b/lib/deferredchain.js new file mode 100644 index 00000000000..ba423ee9a3d --- /dev/null +++ b/lib/deferredchain.js @@ -0,0 +1,43 @@ +function DeferredChain() { + var self = this; + this.chain = new Promise(function(accept, reject) { + self._accept = accept; + self._reject = reject; + }); + + this.await = new Promise(function() { + self._done = arguments[0]; + }); + this.started = false; +}; + +DeferredChain.prototype.then = function(fn) { + var self = this; + this.chain = this.chain.then(function() { + var args = Array.prototype.slice.call(arguments); + + return fn.apply(null, args); + }); + + return this; +}; + +DeferredChain.prototype.catch = function(fn) { + var self = this; + this.chain = this.chain.catch(function() { + var args = Array.prototype.slice.call(arguments); + + return fn.apply(null, args); + }); + + return this; +}; + +DeferredChain.prototype.start = function() { + this.started = true; + this.chain = this.chain.then(this._done); + this._accept(); + return this.await; +}; + +module.exports = DeferredChain; diff --git a/lib/deployer.js b/lib/deployer.js index 850d2e97221..786e68818ef 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -1,35 +1,121 @@ -var EventEmitter = require("events").EventEmitter; -var inherits = require("util").inherits; var Linker = require("./linker"); var Require = require("./require"); var expect = require("./expect"); var path = require("path"); +var DeferredChain = require("./deferredchain"); var Actions = { - deploy: function(contract, args, logger) { - return new Promise(function(accept, reject) { + deployAndLink: function(contract, args, deployer) { + var self = this; + return function() { + // Autolink the contract at deploy time. + Linker.autolink(contract, deployer.known_contracts, deployer.logger); + + return self.deploy(contract, args, deployer)(); + } + }, + + deployAndLinkMany: function(arr, deployer) { + return function() { + // Perform all autolinking before deployment. + arr.forEach(function(args) { + var contract; + + if (Array.isArray(args)) { + contract = args[0]; + } else { + contract = args; + } + + // Autolink the contract at deploy time. + Linker.autolink(contract, deployer.known_contracts, deployer.logger); + }); + + var deployments = arr.map(function(args) { + var contract; + + if (Array.isArray(args)) { + contract = args.shift(); + } else { + contract = args; + args = []; + } + + return Actions.deploy(contract, args, deployer)(); + }); + + return Promise.all(deployments); + }; + }, + + deploy: function(contract, args, deployer) { + return function() { var prefix = "Deploying "; if (contract.address != null) { prefix = "Replacing "; } - logger.log(prefix + contract.contract_name + "..."); + deployer.logger.log(prefix + contract.contract_name + "..."); // Evaluate any arguments if they're promises - Promise.all(args).then(function(new_args) { + return Promise.all(args).then(function(new_args) { return contract.new.apply(contract, new_args); }).then(function(instance) { - logger.log(contract.contract_name + ": " + instance.address); + deployer.logger.log(contract.contract_name + ": " + instance.address); contract.address = instance.address; - accept(); - }).catch(reject); - }); + }); + }; + }, + + autolink: function(contract, deployer) { + return function() { + Linker.autolink(contract, deployer.known_contracts, deployer.logger); + }; + }, + + link: function(library, destinations, deployer) { + return function() { + Linker.link(library, destinations, deployer.logger); + }; + }, + + new: function(contract, args, deployer) { + return function() { + self.logger.log("Creating new instance of " + contract.contract_name); + // Evaluate any arguments if they're promises + return Promise.all(args).then(function(new_args) { + return contract.new.apply(contract, args) + }); + }; + }, + + exec: function(file, deployer) { + return function() { + if (path.isAbsolute(file) == false) { + file = path.resolve(path.join(deployer.basePath, file)); + } + + deployer.logger.log("Running " + file + "..."); + // Evaluate any arguments if they're promises + return new Promise(function(accept, reject) { + Require.exec({ + file: file, + contracts: Object.keys(deployer.known_contracts).map(function(key) { + return deployer.known_contracts[key]; + }), + network: deployer.network, + network_id: deployer.network_id, + provider: deployer.provider + }, function(err) { + if (err) return reject(err); + accept(); + }); + }); + }; } }; -inherits(Deployer, EventEmitter); - function Deployer(options) { Deployer.super_.call(this); var self = this; @@ -41,10 +127,7 @@ function Deployer(options) { "network_id" ]); - this.chain = new Promise(function(accept, reject) { - self._accept = accept; - self._reject = reject; - }); + this.chain = new DeferredChain(); this.logger = options.logger || console; if (options.quiet) { this.logger = {log: function() {}}; @@ -57,24 +140,17 @@ function Deployer(options) { this.network_id = options.network_id; this.provider = options.provider; this.basePath = options.basePath || process.cwd(); - this.started = false; }; // Note: In all code below we overwrite this.chain every time .then() is used // in order to ensure proper error processing. Deployer.prototype.start = function() { - var self = this; - return new Promise(function(accept, reject) { - self.chain = self.chain.then(accept).catch(reject); - self.started = true; - self._accept(); - }); + return this.chain.start(); }; Deployer.prototype.autolink = function(contract) { var self = this; - this.checkStarted(); // autolink all contracts available. if (contract == null) { @@ -84,28 +160,14 @@ Deployer.prototype.autolink = function(contract) { return; } - var self = this; - var regex = /__[^_]+_+/g; - - this.chain = this.chain.then(function() { - Linker.autolink(contract, self.known_contracts, self.logger); - }); + this.queueOrExec(Actions.autolink(contract, self)); }; Deployer.prototype.link = function(library, destinations) { - this.checkStarted(); - - var self = this; - - this.chain = this.chain.then(function() { - Linker.link(library, destinations, self.logger); - }); + return this.queueOrExec(Actions.link(library, destinations, this)); }; Deployer.prototype.deploy = function() { - this.checkStarted(); - - var self = this; var args = Array.prototype.slice.call(arguments); var contract = args.shift(); @@ -113,94 +175,43 @@ Deployer.prototype.deploy = function() { return this.deployMany(contract); } - this.chain = this.chain.then(function() { - return Actions.deploy(contract, args, self.logger); - }); - - return this.chain; + return this.queueOrExec(Actions.deployAndLink(contract, args, this)); }; Deployer.prototype.deployMany = function(arr) { - var self = this; - this.chain = this.chain.then(function() { - var deployments = arr.map(function(args) { - var contract; - - if (Array.isArray(args)) { - contract = args.shift(); - } else { - contract = args; - args = []; - } - - return Actions.deploy(contract, args, self.logger); - }); - return Promise.all(deployments); - }); - - return this.chain; + return this.queueOrExec(Actions.deployAndLinkMany(arr, this)); }; Deployer.prototype.new = function() { - this.checkStarted(); - - var self = this; var args = Array.prototype.slice.call(arguments); var contract = args.shift(); - this.chain = this.chain.then(function() { - self.logger.log("Creating new instance of " + contract.contract_name); - // Evaluate any arguments if they're promises - return Promise.all(args); - }).then(function(new_args) { - return contract.new.apply(contract, args) - }); - return this.chain; + + return this.queueOrExec(Actions.new(contract, args, this)); }; Deployer.prototype.exec = function(file) { - this.checkStarted(); - - var self = this; - - if (path.isAbsolute(file) == false) { - file = path.resolve(path.join(this.basePath, file)); - } - - this.chain = this.chain.then(function() { - self.logger.log("Running " + file + "..."); - // Evaluate any arguments if they're promises - return new Promise(function(accept, reject) { - Require.exec({ - file: file, - contracts: Object.keys(self.known_contracts).map(function(key) { - return self.known_contracts[key]; - }), - network: self.network, - network_id: self.network_id, - provider: self.provider - }, function(err) { - if (err) return reject(err); - accept(); - }); - }); - }); + return this.queueOrExec(Actions.exec(file, this)); }; Deployer.prototype.then = function(fn) { - this.checkStarted(); - var self = this; - this.chain = this.chain.then(function() { + + return this.queueOrExec(function() { self.logger.log("Running step..."); return fn(); }); - return this.chain; -} +}; -Deployer.prototype.checkStarted = function() { - if (this.started == true) { - throw new Error("Can't add new deployment steps once the deploy has started"); +Deployer.prototype.queueOrExec = function(fn) { + var self = this; + + if (this.chain.started == true) { + return new Promise(function(accept, reject) { + accept(); + }).then(fn); + } else { + return this.chain.then(fn); } -} +}; module.exports = Deployer; diff --git a/lib/tasks.js b/lib/tasks.js index 82ef986b468..4d22ad99605 100644 --- a/lib/tasks.js +++ b/lib/tasks.js @@ -222,9 +222,10 @@ createTask('console', "Run a console with deployed contracts instantiated and av }); createTask('serve', "Serve app on localhost and rebuild changes as needed", function(options, done) { + var self = this; var config = Truffle.config.detect(options); Truffle.serve.start(config, function() { - runTask("watch"); + Tasks.watch(options, done); }); }); diff --git a/package.json b/package.json index 9f7c39def54..b0169444ff6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "cpr": "^0.4.3", "deasync": "^0.1.3", "del": "^2.2.0", - "ether-pudding": "^3.0.1", + "ether-pudding": "^3.0.3", "finalhandler": "^0.4.0", "find-up": "^1.1.2", "graphlib": "^2.0.0", @@ -31,7 +31,7 @@ "solidity-parser": "^0.0.8", "spawn-args": "^0.1.0", "temp": "^0.8.3", - "truffle-default-builder": "^1.0.0", + "truffle-default-builder": "^1.0.1", "uglify-js": "^2.6.1", "web3": "^0.15.2", "yargs": "^3.27.0" From f06df5712a98e55cf5b14ac3dd85c968bc489de9 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Fri, 3 Jun 2016 12:30:16 -0700 Subject: [PATCH 25/26] Whoops, I made one untested change before committing last time. :( --- lib/deployer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/deployer.js b/lib/deployer.js index 786e68818ef..d4847301bd1 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -115,9 +115,7 @@ var Actions = { } }; - function Deployer(options) { - Deployer.super_.call(this); var self = this; options = options || {}; From f0d8e19f47fa8d263359386bfe0006588875a2d7 Mon Sep 17 00:00:00 2001 From: Tim Coulter Date: Mon, 6 Jun 2016 12:59:03 -0700 Subject: [PATCH 26/26] Clean up API a bit. --- lib/deployer.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/deployer.js b/lib/deployer.js index d4847301bd1..a5eca0d32bc 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -170,14 +170,10 @@ Deployer.prototype.deploy = function() { var contract = args.shift(); if (Array.isArray(contract)) { - return this.deployMany(contract); + return this.queueOrExec(Actions.deployAndLinkMany(contract, this)); + } else { + return this.queueOrExec(Actions.deployAndLink(contract, args, this)); } - - return this.queueOrExec(Actions.deployAndLink(contract, args, this)); -}; - -Deployer.prototype.deployMany = function(arr) { - return this.queueOrExec(Actions.deployAndLinkMany(arr, this)); }; Deployer.prototype.new = function() {