190 lines
6.2 KiB
JavaScript
190 lines
6.2 KiB
JavaScript
require('dotenv').config()
|
|
|
|
const armlet = require('armlet')
|
|
const fs = require('fs')
|
|
const yaml = require('js-yaml');
|
|
const mythXUtil = require('./lib/mythXUtil');
|
|
const asyncPool = require('tiny-async-pool');
|
|
const { MythXIssues, doReport } = require('./lib/issues2eslint');
|
|
|
|
const defaultConcurrentAnalyses = 4
|
|
|
|
async function analyse(contracts, cfg, embark) {
|
|
|
|
cfg.logger = embark.logger
|
|
|
|
// Set analysis parameters
|
|
const limit = cfg.limit || defaultConcurrentAnalyses
|
|
|
|
if (isNaN(limit)) {
|
|
embark.logger.info(`limit parameter should be a number; got ${limit}.`)
|
|
return 1
|
|
}
|
|
if (limit < 0 || limit > defaultConcurrentAnalyses) {
|
|
embark.logger.info(`limit should be between 0 and ${defaultConcurrentAnalyses}.`)
|
|
return 1
|
|
}
|
|
|
|
// Connect to MythX via armlet
|
|
if(!process.env.MYTHX_ETH_ADDRESS || !process.env.MYTHX_PASSWORD) {
|
|
embark.logger.error("Environment variables 'MYTHX_ETH_ADDRESS' and 'MYTHX_PASSWORD' not found. Continuing in evaluation mode.")
|
|
process.env.MYTHX_ETH_ADDRESS = "0x0000000000000000000000000000000000000000"
|
|
process.env.MYTHX_PASSWORD = "trial"
|
|
}
|
|
|
|
const armletClient = new armlet.Client(
|
|
{
|
|
clientToolName: "embark-mythx",
|
|
password: process.env.MYTHX_PASSWORD,
|
|
ethAddress: process.env.MYTHX_ETH_ADDRESS,
|
|
})
|
|
|
|
// Filter contracts based on parameter choice
|
|
let toSubmit = { "contracts": {}, "sources": contracts.sources };
|
|
if(!("ignore" in embark.pluginConfig)) {
|
|
embark.pluginConfig.ignore = []
|
|
}
|
|
|
|
for (let [filename, contractObjects] of Object.entries(contracts.contracts)) {
|
|
for (let [contractName, contract] of Object.entries(contractObjects)) {
|
|
if(!("contracts" in cfg)) {
|
|
if (embark.pluginConfig.ignore.indexOf(contractName) == -1) {
|
|
if(!toSubmit.contracts[filename]) {
|
|
toSubmit.contracts[filename] = {}
|
|
}
|
|
toSubmit.contracts[filename][contractName] = contract;
|
|
}
|
|
} else {
|
|
if (cfg.contracts.indexOf(contractName) >= 0 && embark.pluginConfig.ignore.indexOf(contractName) == -1) {
|
|
if(!toSubmit.contracts[filename]) {
|
|
toSubmit.contracts[filename] = {}
|
|
}
|
|
toSubmit.contracts[filename][contractName] = contract;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop here if no contracts are left
|
|
if(Object.keys(toSubmit.contracts).length === 0) {
|
|
embark.logger.info("No contracts to submit.");
|
|
return 0;
|
|
}
|
|
|
|
const submitObjects = mythXUtil.buildRequestData(toSubmit)
|
|
const { objects, errors } = await doAnalysis(armletClient, cfg, submitObjects, null, limit)
|
|
|
|
const result = doReport(cfg, objects, errors)
|
|
return result
|
|
}
|
|
|
|
async function getStatus(uuid, embark) {
|
|
|
|
// Connect to MythX via armlet
|
|
const armletClient = new armlet.Client(
|
|
{
|
|
clientToolName: "embark-mythx",
|
|
password: process.env.MYTHX_PASSWORD,
|
|
ethAddress: process.env.MYTHX_ETH_ADDRESS,
|
|
})
|
|
|
|
try {
|
|
const results = await armletClient.getIssues(uuid);
|
|
return ghettoReport(embark.logger.info, results);
|
|
} catch (err) {
|
|
embark.logger.warn(err);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const doAnalysis = async (armletClient, config, contracts, contractNames = null, limit) => {
|
|
|
|
const timeout = (config.timeout || 300) * 1000;
|
|
const initialDelay = ('initial-delay' in config) ? config['initial-delay'] * 1000 : undefined;
|
|
const noCacheLookup = ('no-cache-lookup' in config) ? config['no-cache-lookup'] : true;
|
|
|
|
const results = await asyncPool(limit, contracts, async buildObj => {
|
|
|
|
const obj = new MythXIssues(buildObj, config);
|
|
|
|
let analyzeOpts = {
|
|
clientToolName: 'embark-mythx',
|
|
noCacheLookup,
|
|
timeout,
|
|
initialDelay
|
|
};
|
|
|
|
analyzeOpts.data = mythXUtil.cleanAnalyzeDataEmptyProps(obj.buildObj, config.debug, config.logger.debug);
|
|
analyzeOpts.data.analysisMode = config.full ? "full" : "quick";
|
|
if (config.debug > 1) {
|
|
config.logger.debug("analyzeOpts: " + `${util.inspect(analyzeOpts, {depth: null})}`);
|
|
}
|
|
|
|
// request analysis to armlet.
|
|
try {
|
|
//TODO: Call analyze/analyzeWithStatus asynchronously
|
|
config.logger.info("Submitting '" + obj.contractName + "' for " + analyzeOpts.data.analysisMode + " analysis...")
|
|
const {issues, status} = await armletClient.analyzeWithStatus(analyzeOpts);
|
|
obj.uuid = status.uuid;
|
|
|
|
if (status.status === 'Error') {
|
|
return [status, null];
|
|
} else {
|
|
obj.setIssues(issues);
|
|
}
|
|
|
|
return [null, obj];
|
|
} catch (err) {
|
|
let errStr;
|
|
if (typeof err === 'string') {
|
|
errStr = `${err}`;
|
|
} else if (typeof err.message === 'string') {
|
|
errStr = err.message;
|
|
} else {
|
|
errStr = `${util.inspect(err)}`;
|
|
}
|
|
|
|
if (errStr.includes('User or default timeout reached after')
|
|
|| errStr.includes('Timeout reached after')) {
|
|
return [(buildObj.contractName + ": ").yellow + errStr, null];
|
|
} else {
|
|
return [(buildObj.contractName + ": ").red + errStr, null];
|
|
|
|
}
|
|
}
|
|
});
|
|
|
|
return results.reduce((accum, curr) => {
|
|
const [ err, obj ] = curr;
|
|
if (err) {
|
|
accum.errors.push(err);
|
|
} else if (obj) {
|
|
accum.objects.push(obj);
|
|
}
|
|
return accum;
|
|
}, { errors: [], objects: [] });
|
|
};
|
|
|
|
function ghettoReport(logger, results) {
|
|
let issuesCount = 0;
|
|
results.forEach(ele => {
|
|
issuesCount += ele.issues.length;
|
|
});
|
|
|
|
if (issuesCount === 0) {
|
|
logger('No issues found');
|
|
return 0;
|
|
}
|
|
for (const group of results) {
|
|
logger(group.sourceList.join(', ').underline);
|
|
for (const issue of group.issues) {
|
|
logger(yaml.safeDump(issue, {'skipInvalid': true}));
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
module.exports = {
|
|
analyse,
|
|
getStatus
|
|
} |