Merge branch 'develop' of github.com:iurimatias/embark-framework into develop

This commit is contained in:
Iuri Matias 2018-04-13 16:56:10 -04:00
commit f9b7f5c034
19 changed files with 382 additions and 13 deletions

View File

@ -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:

View File

@ -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')
@ -214,6 +227,7 @@ class Cmd {
process.exit(0);
});
}
}

110
lib/cmds/graph.js Normal file
View File

@ -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;

View File

@ -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);
}
);

View File

@ -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"));

View File

@ -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"));

View File

@ -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 || {});

View File

@ -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) {

View File

@ -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();

View File

@ -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);
});

View File

@ -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});
}
});

View File

@ -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;

5
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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": [

View File

@ -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": {

View File

@ -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"
}
}

View File

@ -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;
}
}