mirror of https://github.com/embarklabs/embark.git
Merge branch 'develop' of github.com:iurimatias/embark-framework into develop
This commit is contained in:
commit
f9b7f5c034
|
@ -160,13 +160,18 @@ Solidity/Serpent files in the contracts directory will automatically be deployed
|
|||
Libraries and languages available
|
||||
======
|
||||
|
||||
Embark can build and deploy contracts coded in Solidity. It will make them available on the client side using EmbarkJS and Web3.js.
|
||||
Embark can build and deploy contracts coded in Solidity and now also in Vyper. It will make them available on the client side using EmbarkJS and Web3.js.
|
||||
|
||||
Further documentation for these can be found below:
|
||||
|
||||
* Smart Contracts: [Solidity](https://solidity.readthedocs.io/en/develop/) and [Serpent](https://github.com/ethereum/wiki/wiki/Serpent)
|
||||
* Smart Contracts:
|
||||
* [Solidity](https://solidity.readthedocs.io/en/develop/)
|
||||
* [Vyper](https://vyper.readthedocs.io/en/latest/index.html)
|
||||
* [Serpent](https://github.com/ethereum/wiki/wiki/Serpent)
|
||||
* Client Side: [Web3.js](https://github.com/ethereum/wiki/wiki/JavaScript-API) and [EmbarkJS](#embarkjs)
|
||||
|
||||
However, to use Vyper, you need to have Vyper installed on you computer beforehand. Meaning that doing `vyper contract.v.py` is possible.
|
||||
|
||||
Using Contracts
|
||||
======
|
||||
Embark will automatically take care of deployment for you and set all needed JS bindings. For example, the contract below:
|
||||
|
|
14
lib/cmd.js
14
lib/cmd.js
|
@ -18,6 +18,7 @@ class Cmd {
|
|||
this.simulator();
|
||||
this.test();
|
||||
this.reset();
|
||||
this.graph();
|
||||
this.upload();
|
||||
this.versionCmd();
|
||||
this.otherCommands();
|
||||
|
@ -179,6 +180,18 @@ class Cmd {
|
|||
});
|
||||
}
|
||||
|
||||
graph() {
|
||||
program
|
||||
.command('graph [environment]')
|
||||
.description('generates documentation based on the smart contracts configured')
|
||||
.action(function (env, options) {
|
||||
embark.graph({
|
||||
env: env || 'development',
|
||||
logfile: options.logfile
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
program
|
||||
.command('reset')
|
||||
|
@ -215,6 +228,7 @@ class Cmd {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = Cmd;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
const Viz = require('viz.js');
|
||||
const fs = require('fs');
|
||||
|
||||
class GraphGenerator {
|
||||
constructor(engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
generate() {
|
||||
let id = 0;
|
||||
let contractString = "";
|
||||
let relationshipString = "";
|
||||
let idMapping = {};
|
||||
let contractInheritance = {};
|
||||
|
||||
|
||||
for (let contract in this.engine.contractsManager.contracts) {
|
||||
id++;
|
||||
|
||||
idMapping[contract] = id;
|
||||
|
||||
let contractLabel = "";
|
||||
|
||||
contractLabel += `${contract}`;
|
||||
let tooltip = contract;
|
||||
|
||||
if(this.engine.contractsManager.contracts[contract].instanceOf !== undefined &&
|
||||
this.engine.contractsManager.contracts[this.engine.contractsManager.contracts[contract].instanceOf] !== undefined){
|
||||
contractInheritance[contract] = this.engine.contractsManager.contracts[contract].instanceOf;
|
||||
contractLabel += ": " + this.engine.contractsManager.contracts[contract].instanceOf;
|
||||
tooltip += " instance of " + this.engine.contractsManager.contracts[contract].instanceOf;
|
||||
} else {
|
||||
contractLabel += "|";
|
||||
|
||||
for(let i = 0; i < this.engine.contractsManager.contracts[contract].abiDefinition.length; i++){
|
||||
switch(this.engine.contractsManager.contracts[contract].abiDefinition[i].type){
|
||||
case 'fallback':
|
||||
contractLabel += "«fallback»()\\l";
|
||||
break;
|
||||
case 'constructor':
|
||||
contractLabel += "«constructor»(";
|
||||
this.engine.contractsManager.contracts[contract].abiDefinition[i].inputs.forEach(function(elem, index){
|
||||
contractLabel += (index == 0 ? "" : ", ") + elem.type;
|
||||
});
|
||||
contractLabel += ")\\l";
|
||||
break;
|
||||
case 'event':
|
||||
contractLabel += "«event»" + this.engine.contractsManager.contracts[contract].abiDefinition[i].name + "(";
|
||||
this.engine.contractsManager.contracts[contract].abiDefinition[i].inputs.forEach(function(elem, index){
|
||||
contractLabel += (index == 0 ? "" : ", ") + elem.type;
|
||||
});
|
||||
contractLabel += ")\\l";
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
let fHashes = this.engine.contractsManager.contracts[contract].functionHashes;
|
||||
if(fHashes != {} && fHashes != undefined){
|
||||
for(let method in this.engine.contractsManager.contracts[contract].functionHashes){
|
||||
contractLabel += method + '\\l';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let others = '';
|
||||
if(!this.engine.contractsManager.contracts[contract].deploy){
|
||||
others = 'fontcolor="#c3c3c3", color="#a0a0a0"';
|
||||
tooltip += " (not deployed)";
|
||||
}
|
||||
|
||||
contractString += `${id}[label = "{${contractLabel}}", tooltip="${tooltip}", fillcolor=gray95, ${others}]\n`;
|
||||
|
||||
}
|
||||
|
||||
for (let c in this.engine.contractsManager.contractDependencies){
|
||||
let contractDependencies = Array.from(new Set(this.engine.contractsManager.contractDependencies[c]));
|
||||
contractDependencies.forEach(function(d){
|
||||
if(idMapping[c] !== undefined && idMapping[d] !== undefined){
|
||||
relationshipString += `${idMapping[d]}->${idMapping[c]}[constraint=true, arrowtail=diamond, tooltip="${c} uses ${d}"]\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let c in contractInheritance){
|
||||
relationshipString += `${idMapping[contractInheritance[c]]}->${idMapping[c]}[tooltip="${c} instance of ${contractInheritance[c]}"]\n`;
|
||||
}
|
||||
|
||||
|
||||
let dot = `
|
||||
digraph Contracts {
|
||||
node[shape=record,style=filled]
|
||||
edge[dir=back, arrowtail=empty]
|
||||
${contractString}
|
||||
${relationshipString}
|
||||
}`;
|
||||
|
||||
let svg = Viz(dot);
|
||||
|
||||
let filename = "diagram.svg";
|
||||
|
||||
fs.writeFileSync(filename, svg, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GraphGenerator;
|
|
@ -7,9 +7,10 @@ class Compiler {
|
|||
}
|
||||
|
||||
compile_contracts(contractFiles, cb) {
|
||||
const self = this;
|
||||
let available_compilers = {};
|
||||
|
||||
let pluginCompilers = this.plugins.getPluginsProperty('compilers', 'compilers');
|
||||
let pluginCompilers = self.plugins.getPluginsProperty('compilers', 'compilers');
|
||||
pluginCompilers.forEach(function (compilerObject) {
|
||||
available_compilers[compilerObject.extension] = compilerObject.cb;
|
||||
});
|
||||
|
@ -18,10 +19,13 @@ class Compiler {
|
|||
|
||||
async.eachObject(available_compilers,
|
||||
function (extension, compiler, callback) {
|
||||
// TODO: warn about files it doesn't know how to compile
|
||||
let matchingFiles = contractFiles.filter(function (file) {
|
||||
let fileMatch = file.filename.match(/\.[0-9a-z]+$/);
|
||||
return (fileMatch && (fileMatch[0] === extension));
|
||||
if (fileMatch && (fileMatch[0] === extension)) {
|
||||
file.compiled = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
compiler.call(compiler, matchingFiles || [], function (err, compileResult) {
|
||||
|
@ -30,6 +34,12 @@ class Compiler {
|
|||
});
|
||||
},
|
||||
function (err) {
|
||||
contractFiles.forEach(file => {
|
||||
if (!file.compiled) {
|
||||
self.logger.warn(`${file.filename} doesn't have a compatible contract compiler. Maybe a plugin exists for it.`);
|
||||
}
|
||||
});
|
||||
|
||||
cb(err, compiledObject);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -305,7 +305,8 @@ class Deploy {
|
|||
let contractObject = new self.web3.eth.Contract(contract.abiDefinition);
|
||||
|
||||
try {
|
||||
deployObject = contractObject.deploy({arguments: contractParams, data: "0x" + contractCode});
|
||||
const dataCode = contractCode.startsWith('0x') ? contractCode : "0x" + contractCode;
|
||||
deployObject = contractObject.deploy({arguments: contractParams, data: dataCode});
|
||||
} catch(e) {
|
||||
if (e.message.indexOf('Invalid number of parameters for "undefined"') >= 0) {
|
||||
return next(new Error("attempted to deploy " + contract.className + " without specifying parameters"));
|
||||
|
|
|
@ -17,6 +17,7 @@ class DeployManager {
|
|||
this.gasLimit = false;
|
||||
this.fatalErrors = false;
|
||||
this.deployOnlyOnConfig = false;
|
||||
this.onlyCompile = options.onlyCompile !== undefined ? options.onlyCompile : false;
|
||||
}
|
||||
|
||||
deployContracts(done) {
|
||||
|
@ -33,6 +34,13 @@ class DeployManager {
|
|||
self.contractsManager.deployOnlyOnConfig = self.deployOnlyOnConfig; // temporary, should refactor
|
||||
self.contractsManager.build(callback);
|
||||
},
|
||||
function checkCompileOnly(contractsManager, callback){
|
||||
if(self.onlyCompile){
|
||||
self.events.emit('contractsDeployed', contractsManager);
|
||||
return done();
|
||||
}
|
||||
return callback(null, contractsManager);
|
||||
},
|
||||
function checkWeb3IsConnected(contractsManager, callback) {
|
||||
if (!self.web3) {
|
||||
return callback(Error("no web3 instance found"));
|
||||
|
|
|
@ -47,6 +47,7 @@ Config.prototype.loadConfigFiles = function(options) {
|
|||
this.loadPipelineConfigFile();
|
||||
|
||||
this.loadContractsConfigFile();
|
||||
this.loadExternalContractsFiles();
|
||||
this.loadWebServerConfigFile();
|
||||
this.loadChainTrackerFile();
|
||||
this.loadPluginContractFiles();
|
||||
|
@ -60,6 +61,7 @@ Config.prototype.reloadConfig = function() {
|
|||
this.loadContractsConfigFile();
|
||||
this.loadPipelineConfigFile();
|
||||
this.loadContractsConfigFile();
|
||||
this.loadExternalContractsFiles();
|
||||
this.loadChainTrackerFile();
|
||||
};
|
||||
|
||||
|
@ -140,6 +142,23 @@ Config.prototype.loadContractsConfigFile = function() {
|
|||
this.contractsConfig = this._mergeConfig(configFilePath, configObject, this.env);
|
||||
};
|
||||
|
||||
Config.prototype.loadExternalContractsFiles = function() {
|
||||
let contracts = this.contractsConfig.contracts;
|
||||
for (let contractName in contracts) {
|
||||
let contract = contracts[contractName];
|
||||
if (!contract.file) {
|
||||
continue;
|
||||
}
|
||||
if (fs.existsSync(contract.file)) {
|
||||
this.contractsFiles.push(new File({filename: contract.file, type: "dapp_file", basedir: '', path: contract.file}));
|
||||
} else if (fs.existsSync(path.join('./node_modules/', contract.file))) {
|
||||
this.contractsFiles.push(new File({filename: path.join('./node_modules/', contract.file), type: "dapp_file", basedir: '', path: path.join('./node_modules/', contract.file)}));
|
||||
} else {
|
||||
this.logger.error("contract file not found: " + contract.file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Config.prototype.loadStorageConfigFile = function() {
|
||||
var versions = utils.recursiveMerge({"ipfs-api": "17.2.4"}, this.embarkConfig.versions || {});
|
||||
|
||||
|
|
|
@ -137,6 +137,9 @@ class Engine {
|
|||
this.registerModule('solidity', {
|
||||
contractDirectories: self.config.contractDirectories
|
||||
});
|
||||
this.registerModule('vyper', {
|
||||
contractDirectories: self.config.contractDirectories
|
||||
});
|
||||
|
||||
this.contractsManager = new ContractsManager({
|
||||
contractFiles: this.config.contractsFiles,
|
||||
|
@ -153,7 +156,8 @@ class Engine {
|
|||
logger: this.logger,
|
||||
plugins: this.plugins,
|
||||
events: this.events,
|
||||
contractsManager: this.contractsManager
|
||||
contractsManager: this.contractsManager,
|
||||
onlyCompile: options.onlyCompile
|
||||
});
|
||||
|
||||
this.events.on('file-event', function (fileType, _path) {
|
||||
|
|
47
lib/index.js
47
lib/index.js
|
@ -194,6 +194,53 @@ class Embark {
|
|||
return new Test(options);
|
||||
}
|
||||
|
||||
graph(options) {
|
||||
options.onlyCompile = true;
|
||||
|
||||
let engine = new Engine({
|
||||
env: options.env,
|
||||
version: this.version,
|
||||
embarkConfig: options.embarkConfig || 'embark.json',
|
||||
logfile: options.logfile
|
||||
});
|
||||
engine.init();
|
||||
|
||||
|
||||
async.parallel([
|
||||
|
||||
function (callback) {
|
||||
let pluginList = engine.plugins.listPlugins();
|
||||
if (pluginList.length > 0) {
|
||||
engine.logger.info("loaded plugins: " + pluginList.join(", "));
|
||||
}
|
||||
|
||||
engine.startMonitor();
|
||||
engine.startService("libraryManager");
|
||||
engine.startService("pipeline");
|
||||
engine.startService("codeGenerator");
|
||||
engine.startService("deployment", {onlyCompile: true});
|
||||
|
||||
engine.deployManager.deployContracts(function (err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err, _result) {
|
||||
if (err) {
|
||||
engine.logger.error(err.message);
|
||||
engine.logger.info(err.stack);
|
||||
} else {
|
||||
|
||||
const GraphGenerator = require('./cmds/graph.js');
|
||||
let graphGen = new GraphGenerator(engine);
|
||||
graphGen.generate();
|
||||
|
||||
engine.logger.info("Done".underline);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
reset() {
|
||||
let resetCmd = require('./cmds/reset.js');
|
||||
resetCmd();
|
||||
|
|
|
@ -22,7 +22,8 @@ class Solidity {
|
|||
let filename = file.filename;
|
||||
|
||||
for (let directory of self.contractDirectories) {
|
||||
filename = filename.replace(directory, '');
|
||||
let match = new RegExp("^" + directory);
|
||||
filename = filename.replace(match, '');
|
||||
}
|
||||
|
||||
file.content(function(fileContent) {
|
||||
|
@ -48,7 +49,7 @@ class Solidity {
|
|||
});
|
||||
},
|
||||
function compileContracts(callback) {
|
||||
self.logger.info("compiling contracts...");
|
||||
self.logger.info("compiling solidity contracts...");
|
||||
let jsonObj = {
|
||||
language: 'Solidity',
|
||||
sources: input,
|
||||
|
@ -75,7 +76,6 @@ class Solidity {
|
|||
return callback(new Error("Solidity errors: " + output.errors[i].formattedMessage).message);
|
||||
}
|
||||
}
|
||||
self.logger.warn(output.errors.join('\n'));
|
||||
}
|
||||
callback(null, output);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
let solc;
|
||||
|
||||
let fs = require('fs-extra');
|
||||
let path = require('path');
|
||||
|
||||
function findImports(filename) {
|
||||
if (fs.existsSync(filename)) {
|
||||
return {contents: fs.readFileSync(filename).toString()};
|
||||
}
|
||||
if (fs.existsSync(path.join('./node_modules/', filename))) {
|
||||
return {contents: fs.readFileSync(path.join('./node_modules/', filename)).toString()};
|
||||
}
|
||||
return {error: 'File not found'};
|
||||
}
|
||||
|
||||
process.on('message', function (msg) {
|
||||
if (msg.action === 'loadCompiler') {
|
||||
solc = require(msg.solcLocation);
|
||||
|
@ -8,7 +21,7 @@ process.on('message', function (msg) {
|
|||
|
||||
if (msg.action === 'compile') {
|
||||
// TODO: only available in 0.4.11; need to make versions warn about this
|
||||
let output = solc.compileStandardWrapper(JSON.stringify(msg.jsonObj));
|
||||
let output = solc.compileStandardWrapper(JSON.stringify(msg.jsonObj), findImports);
|
||||
process.send({result: "compilation", output: output});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
let async = require('../../utils/async_extend.js');
|
||||
const shelljs = require('shelljs');
|
||||
const path = require('path');
|
||||
|
||||
class Vyper {
|
||||
|
||||
constructor(embark, options) {
|
||||
this.logger = embark.logger;
|
||||
this.events = embark.events;
|
||||
this.contractDirectories = options.contractDirectories;
|
||||
|
||||
embark.registerCompiler(".py", this.compile_vyper.bind(this));
|
||||
}
|
||||
|
||||
compile_vyper(contractFiles, cb) {
|
||||
let self = this;
|
||||
async.waterfall([
|
||||
function compileContracts(callback) {
|
||||
self.logger.info("compiling vyper contracts...");
|
||||
const compiled_object = {};
|
||||
async.each(contractFiles,
|
||||
function (file, fileCb) {
|
||||
const className = path.basename(file.filename).split('.')[0];
|
||||
compiled_object[className] = {};
|
||||
async.parallel([
|
||||
function getByteCode(paraCb) {
|
||||
shelljs.exec(`vyper ${file.filename}`, {silent: true}, (code, stdout, stderr) => {
|
||||
if (stderr) {
|
||||
return paraCb(stderr);
|
||||
}
|
||||
if (code !== 0) {
|
||||
return paraCb(`Vyper exited with error code ${code}`);
|
||||
}
|
||||
if (!stdout) {
|
||||
return paraCb('Execution returned no bytecode');
|
||||
}
|
||||
const byteCode = stdout.replace(/\n/g, '');
|
||||
compiled_object[className].runtimeBytecode = byteCode;
|
||||
compiled_object[className].realRuntimeBytecode = byteCode;
|
||||
compiled_object[className].code = byteCode;
|
||||
paraCb();
|
||||
});
|
||||
},
|
||||
function getABI(paraCb) {
|
||||
shelljs.exec(`vyper -f json ${file.filename}`, {silent: true}, (code, stdout, stderr) => {
|
||||
if (stderr) {
|
||||
return paraCb(stderr);
|
||||
}
|
||||
if (code !== 0) {
|
||||
return paraCb(`Vyper exited with error code ${code}`);
|
||||
}
|
||||
if (!stdout) {
|
||||
return paraCb('Execution returned no ABI');
|
||||
}
|
||||
let ABI = [];
|
||||
try {
|
||||
ABI = JSON.parse(stdout.replace(/\n/g, ''));
|
||||
} catch (e) {
|
||||
return paraCb('ABI is not valid JSON');
|
||||
}
|
||||
compiled_object[className].abiDefinition = ABI;
|
||||
paraCb();
|
||||
});
|
||||
}
|
||||
], fileCb);
|
||||
},
|
||||
function (err) {
|
||||
callback(err, compiled_object);
|
||||
});
|
||||
}
|
||||
], function (err, result) {
|
||||
cb(err, result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Vyper;
|
|
@ -10063,6 +10063,11 @@
|
|||
"extsprintf": "1.3.0"
|
||||
}
|
||||
},
|
||||
"viz.js": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/viz.js/-/viz.js-1.8.1.tgz",
|
||||
"integrity": "sha512-KrSNgnIxec+JCAqDPliO6xYA69ToH2WTYB2Kbt8Bp/XRUvm23rTyfffFi4rvQLFkIRNUz/xCnnqhh/gChhsgGA=="
|
||||
},
|
||||
"vlq": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
"underscore": "^1.8.3",
|
||||
"underscore.string": "^3.3.4",
|
||||
"url-loader": "^0.6.2",
|
||||
"viz.js": "^1.8.1",
|
||||
"web3": "1.0.0-beta.27",
|
||||
"webpack": "^3.10.0",
|
||||
"window-size": "^1.1.0"
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
pragma solidity ^0.4.17;
|
||||
|
||||
contract SimpleStorageTest2 {
|
||||
uint public storedData;
|
||||
|
||||
function() public payable { }
|
||||
|
||||
function SimpleStorage(uint initialValue) public {
|
||||
storedData = initialValue;
|
||||
}
|
||||
|
||||
function set(uint x) public {
|
||||
storedData = x;
|
||||
}
|
||||
|
||||
function get() public view returns (uint retVal) {
|
||||
return storedData;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -62,6 +62,15 @@
|
|||
["$MyToken2", "$SimpleStorage"],
|
||||
100
|
||||
]
|
||||
},
|
||||
"ERC20": {
|
||||
"file": "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"
|
||||
},
|
||||
"SimpleStorageTest": {
|
||||
"file": "./some_folder/test_contract.sol",
|
||||
"args": [
|
||||
1000
|
||||
]
|
||||
}
|
||||
},
|
||||
"afterDeploy": [
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"config": "config/",
|
||||
"versions": {
|
||||
"web3.js": "1.0.0-beta.27",
|
||||
"solc": "0.4.17",
|
||||
"solc": "0.4.18",
|
||||
"ipfs-api": "17.2.6"
|
||||
},
|
||||
"plugins": {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"jquery": "^1.11.3",
|
||||
"react": "^16.0.0",
|
||||
"react-bootstrap": "^0.32.0",
|
||||
"react-dom": "^16.2.0"
|
||||
"react-dom": "^16.2.0",
|
||||
"zeppelin-solidity": "^1.8.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
pragma solidity ^0.4.17;
|
||||
|
||||
import "another_folder/another_test.sol";
|
||||
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
|
||||
contract SimpleStorageTest is Ownable {
|
||||
uint public storedData;
|
||||
|
||||
function() public payable { }
|
||||
|
||||
function SimpleStorage(uint initialValue) public {
|
||||
storedData = initialValue;
|
||||
}
|
||||
|
||||
function set(uint x) public {
|
||||
storedData = x;
|
||||
}
|
||||
|
||||
function get() public view returns (uint retVal) {
|
||||
return storedData;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue