2018-07-30 18:33:01 +00:00
|
|
|
const async = require('async');
|
2018-07-20 17:03:55 +00:00
|
|
|
const Viz = require('viz.js');
|
|
|
|
const fs = require('fs');
|
|
|
|
|
2018-07-26 16:58:25 +00:00
|
|
|
// TODO: refactor this class to use the plugin api and not refeneences directly
|
2018-07-20 17:03:55 +00:00
|
|
|
class GraphGenerator {
|
2018-07-30 19:17:54 +00:00
|
|
|
constructor(embark, _options) {
|
2018-07-20 17:03:55 +00:00
|
|
|
const self = this;
|
|
|
|
this.events = embark.events;
|
2018-07-30 19:12:49 +00:00
|
|
|
this.contracts = [];
|
2018-07-20 17:03:55 +00:00
|
|
|
|
|
|
|
this.events.setCommandHandler("graph:create", function(options, cb) {
|
|
|
|
self.generate(options);
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
generate(options) {
|
2018-07-30 18:33:01 +00:00
|
|
|
const self = this;
|
2018-07-20 17:03:55 +00:00
|
|
|
let id = 0;
|
|
|
|
let contractString = "";
|
|
|
|
let relationshipString = "";
|
|
|
|
let idMapping = {};
|
|
|
|
let contractInheritance = {};
|
2018-07-30 18:33:01 +00:00
|
|
|
let contractsDependencies = {};
|
|
|
|
|
|
|
|
async.waterfall([
|
|
|
|
function getContractList(next) {
|
2018-07-30 19:12:49 +00:00
|
|
|
self.events.request('contracts:list', (err, contracts) => {
|
|
|
|
self.contracts = contracts;
|
2018-07-30 18:33:01 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function getContractsDependencies(next) {
|
|
|
|
self.events.request('contracts:dependencies', (err, _contractsDependencies) => {
|
|
|
|
contractsDependencies = _contractsDependencies;
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function (next) {
|
2018-07-30 19:12:49 +00:00
|
|
|
for (let contract of self.contracts) {
|
2018-07-30 18:33:01 +00:00
|
|
|
if (options.skipUndeployed && !contract.deploy) continue;
|
|
|
|
|
|
|
|
id++;
|
|
|
|
|
|
|
|
idMapping[contract.className] = id;
|
|
|
|
|
|
|
|
let contractLabel = "";
|
|
|
|
|
|
|
|
contractLabel += `${contract.className}`;
|
|
|
|
let tooltip = contract.className;
|
|
|
|
|
2018-07-30 19:12:49 +00:00
|
|
|
if (contract.instanceOf !== undefined && self.getContract(contract.instanceOf) !== undefined) {
|
2018-07-30 18:33:01 +00:00
|
|
|
contractInheritance[contract.className] = contract.instanceOf;
|
|
|
|
contractLabel += ": " + contract.instanceOf;
|
|
|
|
tooltip += " instance of " + contract.instanceOf;
|
|
|
|
} else {
|
|
|
|
if (!(options.skipFunctions === true && options.skipEvents === true)) contractLabel += "|";
|
|
|
|
|
|
|
|
for (let i = 0; i < contract.abiDefinition.length; i++) {
|
|
|
|
let abiDef = contract.abiDefinition[i];
|
|
|
|
if (abiDef.type == 'event' && options.skipEvents) continue;
|
|
|
|
if (['constructor', 'fallback'].indexOf(abiDef.type) > -1 && options.skipFunctions) continue;
|
|
|
|
|
|
|
|
switch(abiDef.type){
|
|
|
|
case 'fallback':
|
|
|
|
contractLabel += "«fallback»()\\l";
|
|
|
|
break;
|
|
|
|
case 'constructor':
|
|
|
|
contractLabel += "«constructor»(";
|
|
|
|
abiDef.inputs.forEach(function(elem, index){
|
|
|
|
contractLabel += (index == 0 ? "" : ", ") + elem.type;
|
|
|
|
});
|
|
|
|
contractLabel += ")\\l";
|
|
|
|
break;
|
|
|
|
case 'event':
|
|
|
|
contractLabel += "«event»" + abiDef.name + "(";
|
|
|
|
abiDef.inputs.forEach(function(elem, index){
|
|
|
|
contractLabel += (index == 0 ? "" : ", ") + elem.type;
|
|
|
|
});
|
|
|
|
contractLabel += ")\\l";
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let fHashes = contract.functionHashes;
|
|
|
|
if (fHashes != {} && fHashes != undefined && !options.skipFunctions){
|
|
|
|
for (let method in contract.functionHashes){
|
|
|
|
contractLabel += method + '\\l';
|
|
|
|
}
|
|
|
|
}
|
2018-07-20 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 18:33:01 +00:00
|
|
|
let others = '';
|
|
|
|
if (!contract.deploy){
|
|
|
|
others = 'fontcolor="#c3c3c3", color="#a0a0a0"';
|
|
|
|
tooltip += " (not deployed)";
|
2018-07-20 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 18:33:01 +00:00
|
|
|
contractString += `${id}[label = "{${contractLabel}}", tooltip="${tooltip}", fillcolor=gray95, ${others}]\n`;
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
function (next) {
|
|
|
|
for (let c in contractsDependencies) {
|
|
|
|
let contractDependencies = Array.from(new Set(contractsDependencies[c]));
|
|
|
|
contractDependencies.forEach((d) => {
|
|
|
|
if (idMapping[c] !== undefined && idMapping[d] !== undefined) {
|
2018-07-30 19:12:49 +00:00
|
|
|
if ((options.skipUndeployed && self.getContract(c).deploy && self.getContract(d).deploy) || !options.skipUndeployed) {
|
2018-07-30 18:33:01 +00:00
|
|
|
relationshipString += `${idMapping[d]}->${idMapping[c]}[constraint=true, arrowtail=diamond, tooltip="${c} uses ${d}"]\n`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
function (next) {
|
|
|
|
for (let c in contractInheritance){
|
2018-07-30 19:12:49 +00:00
|
|
|
if(options.skipUndeployed && !self.getContract(contractInheritance[c]).deploy) continue;
|
2018-07-20 17:03:55 +00:00
|
|
|
|
2018-07-30 18:33:01 +00:00
|
|
|
relationshipString += `${idMapping[contractInheritance[c]]}->${idMapping[c]}[tooltip="${c} instance of ${contractInheritance[c]}"]\n`;
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
function (next) {
|
|
|
|
let dot = `
|
2018-07-20 17:03:55 +00:00
|
|
|
digraph Contracts {
|
|
|
|
node[shape=record,style=filled]
|
|
|
|
edge[dir=back, arrowtail=empty]
|
|
|
|
${contractString}
|
|
|
|
${relationshipString}
|
|
|
|
}`;
|
|
|
|
|
2018-07-30 18:33:01 +00:00
|
|
|
let svg = Viz(dot);
|
2018-07-20 17:03:55 +00:00
|
|
|
|
2018-10-01 18:39:45 +00:00
|
|
|
fs.writeFileSync(options.output, svg, (err) => {
|
2018-07-30 18:33:01 +00:00
|
|
|
if (err) throw err;
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
], function(_err, _result) {
|
2018-07-20 17:03:55 +00:00
|
|
|
});
|
|
|
|
}
|
2018-07-30 19:12:49 +00:00
|
|
|
|
|
|
|
getContract(contractName) {
|
2018-07-30 19:17:54 +00:00
|
|
|
return this.contracts.find((contract) => { return contract.className === contractName; });
|
2018-07-30 19:12:49 +00:00
|
|
|
}
|
2018-07-20 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = GraphGenerator;
|