Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions hardhat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const plugin = require("./plugins/hardhat.plugin");
const PluginUI = require('./plugins/resources/nomiclabs.ui');

// UI for the task flags...
const ui = new PluginUI();

task("coverage", "Generates a code coverage report for tests")
.addOptionalParam("testfiles", ui.flags.file, "", types.string)
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, "", types.string)
.addOptionalParam('temp', ui.flags.temp, "", types.string)
.setAction(async function(args, env){
await plugin(args, env)
});
8 changes: 8 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ class API {
})
}

hardhatTraceHandler(trace, isTraceFromCall){
if (trace.bytecode && trace.bytecode.instructions){
for (const instruction of trace.bytecode.instructions){
this.collector.trackHardhatEVMInstruction(instruction)
}
}
}

// ========
// File I/O
// ========
Expand Down
29 changes: 24 additions & 5 deletions lib/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,34 @@ class DataCollector {
if (this.validOpcodes[info.opcode.name] && info.stack.length > 0){
const idx = info.stack.length - 1;
let hash = web3Utils.toHex(info.stack[idx]).toString();
hash = this._normalizeHash(hash);

if(this.instrumentationData[hash]){
this.instrumentationData[hash].hits++;
}
this._registerHash(hash)
}
} catch (err) { /*Ignore*/ };
}

/**
* Converts pushData value to string and registers in instrumentation map.
* @param {HardhatEVMTraceInstruction} instruction
*/
trackHardhatEVMInstruction(instruction){
if (instruction.pushData){
let hash = `0x` + instruction.pushData.toString('hex');
this._registerHash(hash)
}
}

/**
* Normalizes has string and marks hit.
* @param {String} hash bytes32 hash
*/
_registerHash(hash){
hash = this._normalizeHash(hash);

if(this.instrumentationData[hash]){
this.instrumentationData[hash].hits++;
}
}

/**
* Left-pads zero prefixed bytes 32 hashes to length 66. The '59' in the
* comparison below is arbitrary. It provides a margin for recurring zeros
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@
"recursive-readdir": "^2.2.2",
"sc-istanbul": "^0.4.5",
"shelljs": "^0.8.3",
"web3": "^1.3.0"
"web3": "1.2.9"
},
"devDependencies": {
"@nomiclabs/buidler": "^1.3.6",
"@nomiclabs/buidler-truffle5": "^1.3.4",
"@nomiclabs/buidler-web3": "^1.3.4",
"@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@truffle/contract": "^4.0.36",
"buidler-gas-reporter": "^0.1.3",
"decache": "^4.5.1",
"hardhat": "^2.0.2",
"mocha": "5.2.0",
"nyc": "^14.1.1",
"solc": "^0.5.10",
Expand Down
74 changes: 19 additions & 55 deletions plugins/buidler.plugin.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
const API = require('./../lib/api');
const utils = require('./resources/plugin.utils');
const buidlerUtils = require('./resources/buidler.utils');
const PluginUI = require('./resources/buidler.ui');
const buidlerUtils = require('./resources/nomiclabs.utils');
const PluginUI = require('./resources/nomiclabs.ui');

const pkg = require('./../package.json');
const death = require('death');
const path = require('path');
const Web3 = require('web3');

const { task, types } = require("@nomiclabs/buidler/config");
const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins");

const {
TASK_TEST,
TASK_COMPILE,
TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT,
TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE,
TASK_COMPILE_GET_COMPILER_INPUT
} = require("@nomiclabs/buidler/builtin-tasks/task-names");

ensurePluginLoadedWithUsePlugin();

function plugin() {

// UI for the task flags...
const ui = new PluginUI();

let measureCoverage = false;
let instrumentedSources;
// Unset useLiteralContent due to solc metadata size restriction
task(TASK_COMPILE_GET_COMPILER_INPUT).setAction(async (_, __, runSuper) => {
const input = await runSuper();
input.settings.metadata.useLiteralContent = false;
return input;
})

task("coverage", "Generates a code coverage report for tests")

Expand All @@ -36,7 +42,6 @@ function plugin() {
let ui;
let api;
let config;
instrumentedSources = {};

try {
death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals
Expand All @@ -48,7 +53,7 @@ function plugin() {
// ==============
// Server launch
// ==============
const network = buidlerUtils.setupNetwork(env, api, ui);
const network = buidlerUtils.setupBuidlerNetwork(env, api, ui);

const client = api.client || require('ganache-cli');
const address = await api.ganache(client);
Expand Down Expand Up @@ -86,9 +91,6 @@ function plugin() {
} = utils.assembleFiles(config, skipFiles);

targets = api.instrument(targets);
for (const target of targets) {
instrumentedSources[target.canonicalPath] = target.source;
}
utils.reportSkipped(config, skipped);

// ==============
Expand All @@ -102,11 +104,14 @@ function plugin() {
} = utils.getTempLocations(config);

utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir)
utils.save(targets, config.paths.sources, tempContractsDir);
utils.save(skipped, config.paths.sources, tempContractsDir);

config.paths.sources = tempContractsDir;
config.paths.artifacts = tempArtifactsDir;
config.paths.cache = buidlerUtils.tempCacheDir(config);
config.solc.optimizer.enabled = false;

measureCoverage = true;
await env.run(TASK_COMPILE);

await api.onCompileComplete(config);
Expand All @@ -132,55 +137,14 @@ function plugin() {
await api.onIstanbulComplete(config);

} catch(e) {
error = e;
} finally {
measureCoverage = false;
error = e;
}

await buidlerUtils.finish(config, api);

if (error !== undefined ) throw error;
if (process.exitCode > 0) throw new Error(ui.generate('tests-fail', [process.exitCode]));
});

task(TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT).setAction(async (_, { config }, runSuper) => {
const solcInput = await runSuper();
if (measureCoverage) {
// The source name here is actually the global name in the solc input,
// but buidler uses the fully qualified contract names.
for (const [sourceName, source] of Object.entries(solcInput.sources)) {
const absolutePath = path.join(config.paths.root, sourceName);
// Patch in the instrumented source code.
if (absolutePath in instrumentedSources) {
source.content = instrumentedSources[absolutePath];
}
}
}
return solcInput;
});

// Solidity settings are best set here instead of the TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT task.
task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, __, runSuper) => {
const compilationJob = await runSuper();
if (measureCoverage && typeof compilationJob === "object") {
if (compilationJob.solidityConfig.settings === undefined) {
compilationJob.solidityConfig.settings = {};
}

const { settings } = compilationJob.solidityConfig;
if (settings.metadata === undefined) {
settings.metadata = {};
}
if (settings.optimizer === undefined) {
settings.optimizer = {};
}
// Unset useLiteralContent due to solc metadata size restriction
settings.metadata.useLiteralContent = false;
// Override optimizer settings for all compilers
settings.optimizer.enabled = false;
}
return compilationJob;
});
})
}

module.exports = plugin;
Loading