-
Notifications
You must be signed in to change notification settings - Fork 272
/
Copy pathhardhat.plugin.js
301 lines (248 loc) · 9.6 KB
/
hardhat.plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
const path = require('path');
const PluginUI = require('./resources/nomiclabs.ui');
const { extendConfig, task, types } = require("hardhat/config");
const { HardhatPluginError } = require("hardhat/plugins")
const {HARDHAT_NETWORK_RESET_EVENT} = require("hardhat/internal/constants");
const {
TASK_TEST,
TASK_COMPILE,
TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT,
TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE,
TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS
} = require("hardhat/builtin-tasks/task-names");
// Toggled true for `coverage` task only.
let measureCoverage = false;
let configureYulOptimizer = false;
let instrumentedSources;
let optimizerDetails;
// UI for the task flags...
const ui = new PluginUI();
// Workaround for hardhat-viem-plugin and other provider redefinition conflicts
extendConfig((config, userConfig) => {
if (Boolean(process.env.SOLIDITY_COVERAGE)) {
const { cloneDeep } = require("lodash");
const { configureHardhatEVMGas } = require('./resources/nomiclabs.utils');
const API = require('./../lib/api');
const api = new API({});
let hardhatNetworkForCoverage = {};
if (userConfig.networks && userConfig.networks.hardhat) {
hardhatNetworkForCoverage = cloneDeep(userConfig.networks.hardhat);
};
configureHardhatEVMGas(hardhatNetworkForCoverage, api);
config.networks.hardhat = Object.assign(config.networks.hardhat, hardhatNetworkForCoverage);
}
});
subtask(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 hardhat 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.
subtask(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;
// Beginning with v0.8.7, we let the optimizer run if viaIR is true and
// instrument using `abi.encode(bytes8 covHash)`. Otherwise turn the optimizer off.
if (!settings.viaIR) settings.optimizer.enabled = false;
// This sometimes fixed a stack-too-deep bug in ABIEncoderV2 for coverage plugin versions up to 0.8.6
// Although issue should be fixed in 0.8.7, am leaving this option in because it may still be necessary
// to configure optimizer details in some cases.
if (configureYulOptimizer) {
if (optimizerDetails === undefined) {
settings.optimizer.details = {
yul: true,
yulDetails: {
stackAllocation: true,
},
}
// Other configurations may work as well. This loads custom details from .solcoverjs
} else {
settings.optimizer.details = optimizerDetails;
}
}
}
return compilationJob;
});
// Suppress compilation warnings because injected trace function triggers
// complaint about unused variable
subtask(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS).setAction(async (_, __, runSuper) => {
const defaultWarn = console.warn;
if (measureCoverage) {
console.warn = () => {};
}
await runSuper();
console.warn = defaultWarn;
});
/**
* Coverage task implementation
* @param {HardhatUserArgs} args
* @param {HardhatEnv} env
*/
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)
.addOptionalParam('sources', ui.flags.sources, "", types.string)
.addFlag('matrix', ui.flags.testMatrix)
.addFlag('abi', ui.flags.abi)
.setAction(async function(args, env){
const API = require('./../lib/api');
const utils = require('./resources/plugin.utils');
const nomiclabsUtils = require('./resources/nomiclabs.utils');
const pkg = require('./../package.json');
let error;
let ui;
let api;
let config;
let address;
let failedTests = 0;
instrumentedSources = {};
measureCoverage = true;
// Set a variable on the environment so other tasks can detect if this task is executing
env.__SOLIDITY_COVERAGE_RUNNING = true;
try {
config = nomiclabsUtils.normalizeConfig(env.config, args);
ui = new PluginUI(config.logger.log);
api = new API(utils.loadSolcoverJS(config));
optimizerDetails = api.solcOptimizerDetails;
// Catch interrupt signals
process.on("SIGINT", nomiclabsUtils.finish.bind(null, config, api, true));
// Warn about hardhat-viem plugin if present and config hasn't happened
if (env.viem !== undefined && nomiclabsUtils.requiresEVMConfiguration(env.network.config, api)) {
ui.report('hardhat-viem', []);
throw new Error(ui.generate('hardhat-viem'));
}
// Version Info
ui.report('hardhat-versions', [pkg.version]);
// Merge non-null flags into hardhatArguments
const flags = {};
for (const key of Object.keys(args)){
if (args[key] && args[key].length){
flags[key] = args[key]
}
}
env.hardhatArguments = Object.assign(env.hardhatArguments, flags)
// Error if --network flag is set
if (env.hardhatArguments.network){
throw new Error(ui.generate('network-fail'));
}
// ===========================
// Generate abi diff component
// (This flag only useful within codecheck context)
// ===========================
if (args.abi){
measureCoverage = false;
await nomiclabsUtils.generateHumanReadableAbiList(env, api, TASK_COMPILE);
return;
}
// ================
// Instrumentation
// ================
const skipFiles = api.skipFiles || [];
let {
targets,
skipped
} = utils.assembleFiles(config, skipFiles);
targets = api.instrument(targets);
for (const target of targets) {
instrumentedSources[target.canonicalPath] = target.source;
}
utils.reportSkipped(config, skipped);
// ==============
// Compilation
// ==============
ui.report('compilation', []);
config.temp = args.temp;
configureYulOptimizer = api.config.configureYulOptimizer;
// With Hardhat >= 2.0.4, everything should automatically recompile
// after solidity-coverage corrupts the artifacts.
// Prior to that version, we (try to) save artifacts to a temp folder.
if (!config.useHardhatDefaultPaths){
const {
tempArtifactsDir,
tempContractsDir
} = utils.getTempLocations(config);
utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir)
config.paths.artifacts = tempArtifactsDir;
config.paths.cache = nomiclabsUtils.tempCacheDir(config);
}
await api.onPreCompile(config);
await env.run(TASK_COMPILE);
await api.onCompileComplete(config);
// ==============
// Server launch
// ==============
let network
if (nomiclabsUtils.requiresEVMConfiguration(env.network.config, api)) {
network = await nomiclabsUtils.setupHardhatNetwork(env, api, ui);
} else {
network = env.network;
}
accounts = await utils.getAccountsHardhat(network.provider);
nodeInfo = await utils.getNodeInfoHardhat(network.provider);
// Note: this only works if the reset block number is before any transactions have fired on the fork.
// e.g you cannot fork at block 1, send some txs (blocks 2,3,4) and reset to block 2
env.network.provider.on(HARDHAT_NETWORK_RESET_EVENT, async () => {
await api.attachToHardhatVM(env.network.provider);
});
await api.attachToHardhatVM(network.provider);
ui.report('hardhat-network', [
nodeInfo.split('/')[1],
env.network.name,
]);
// Set default account (if not already configured)
nomiclabsUtils.setNetworkFrom(network.config, accounts);
// Run post-launch server hook;
await api.onServerReady(config);
// ======
// Tests
// ======
const testfiles = args.testfiles
? nomiclabsUtils.getTestFilePaths(args.testfiles)
: [];
// Optionally collect tests-per-line-of-code data
nomiclabsUtils.collectTestMatrixData(args, env, api);
try {
failedTests = await env.run(TASK_TEST, {testFiles: testfiles})
} catch (e) {
error = e;
}
await api.onTestsComplete(config);
// =================================
// Output (Istanbul or Test Matrix)
// =================================
(args.matrix)
? await api.saveTestMatrix()
: await api.report();
await api.onIstanbulComplete(config);
} catch(e) {
error = e;
} finally {
measureCoverage = false;
}
await nomiclabsUtils.finish(config, api);
if (error !== undefined ) throw new HardhatPluginError(error);
if (failedTests > 0) throw new HardhatPluginError(ui.generate('tests-fail', [failedTests]));
})