@diamondslab/diamonds-hardhat-foundry 2.2.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -1
- package/dist/framework/ForgeCoverageFramework.d.ts +70 -0
- package/dist/framework/ForgeCoverageFramework.d.ts.map +1 -0
- package/dist/framework/ForgeCoverageFramework.js +306 -0
- package/dist/framework/ForgeCoverageFramework.js.map +1 -0
- package/dist/framework/ForgeCoverageFramework.old.d.ts +61 -0
- package/dist/framework/ForgeCoverageFramework.old.d.ts.map +1 -0
- package/dist/framework/ForgeCoverageFramework.old.js +238 -0
- package/dist/framework/ForgeCoverageFramework.old.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tasks/coverage.d.ts +2 -0
- package/dist/tasks/coverage.d.ts.map +1 -0
- package/dist/tasks/coverage.js +154 -0
- package/dist/tasks/coverage.js.map +1 -0
- package/dist/tasks/coverage.old.d.ts +2 -0
- package/dist/tasks/coverage.old.d.ts.map +1 -0
- package/dist/tasks/coverage.old.js +173 -0
- package/dist/tasks/coverage.old.js.map +1 -0
- package/dist/types/config.d.ts +91 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/package.json +3 -3
- package/src/framework/ForgeCoverageFramework.ts +390 -0
- package/src/index.ts +7 -6
- package/src/tasks/coverage.ts +258 -0
- package/src/types/config.ts +107 -0
- package/CHANGELOG.md +0 -321
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
3
|
+
import { CoverageOptions } from "../types/config";
|
|
4
|
+
import { isFoundryInstalled } from "../utils/foundry";
|
|
5
|
+
import { Logger } from "../utils/logger";
|
|
6
|
+
import { DeploymentManager } from "./DeploymentManager";
|
|
7
|
+
import { HelperGenerator } from "./HelperGenerator";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ForgeCoverageFramework - Main orchestration class for Forge coverage with Diamonds
|
|
11
|
+
*
|
|
12
|
+
* Coordinates:
|
|
13
|
+
* 1. Diamond deployment via DeploymentManager
|
|
14
|
+
* 2. Helper generation via HelperGenerator
|
|
15
|
+
* 3. Forge coverage execution
|
|
16
|
+
*
|
|
17
|
+
* Mirrors ForgeFuzzingFramework design for consistency and code reuse
|
|
18
|
+
*/
|
|
19
|
+
export class ForgeCoverageFramework {
|
|
20
|
+
private deploymentManager: DeploymentManager;
|
|
21
|
+
private helperGenerator: HelperGenerator;
|
|
22
|
+
|
|
23
|
+
constructor(private hre: HardhatRuntimeEnvironment) {
|
|
24
|
+
this.deploymentManager = new DeploymentManager(hre);
|
|
25
|
+
this.helperGenerator = new HelperGenerator(hre);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run complete Forge coverage workflow
|
|
30
|
+
*
|
|
31
|
+
* Workflow:
|
|
32
|
+
* 1. Validate Foundry installation
|
|
33
|
+
* 2. Deploy or reuse Diamond
|
|
34
|
+
* 3. Generate Solidity helpers
|
|
35
|
+
* 4. Run forge coverage with options
|
|
36
|
+
*
|
|
37
|
+
* @param options - Coverage execution options
|
|
38
|
+
* @returns Promise<boolean> - true if coverage succeeds
|
|
39
|
+
*/
|
|
40
|
+
async runCoverage(options: CoverageOptions = {}): Promise<boolean> {
|
|
41
|
+
const {
|
|
42
|
+
diamondName = "ExampleDiamond",
|
|
43
|
+
networkName = "hardhat",
|
|
44
|
+
force = false,
|
|
45
|
+
skipDeployment = false,
|
|
46
|
+
skipHelpers = false,
|
|
47
|
+
writeDeployedDiamondData = false,
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
Logger.section("Running Forge Coverage with Diamond");
|
|
51
|
+
|
|
52
|
+
// Step 1: Validate Foundry
|
|
53
|
+
if (!isFoundryInstalled()) {
|
|
54
|
+
Logger.error(
|
|
55
|
+
"Foundry is not installed. Please install it: https://book.getfoundry.sh/getting-started/installation"
|
|
56
|
+
);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Step 2: Ensure Diamond deployment
|
|
62
|
+
if (!skipDeployment) {
|
|
63
|
+
Logger.section("Step 1/3: Ensuring Diamond Deployment");
|
|
64
|
+
await this.deploymentManager.ensureDeployment(
|
|
65
|
+
diamondName,
|
|
66
|
+
networkName,
|
|
67
|
+
force,
|
|
68
|
+
writeDeployedDiamondData
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
Logger.info("Skipping deployment (using existing)");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 3: Generate helpers
|
|
75
|
+
if (!skipHelpers) {
|
|
76
|
+
Logger.section("Step 2/3: Generating Solidity Helpers");
|
|
77
|
+
|
|
78
|
+
const deployment = await this.deploymentManager.getDeployment(
|
|
79
|
+
diamondName,
|
|
80
|
+
networkName
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (!deployment) {
|
|
84
|
+
Logger.warn("⚠ No deployment record found");
|
|
85
|
+
if (!skipDeployment) {
|
|
86
|
+
Logger.info("ℹ Using cached deployment (ephemeral)");
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
Logger.info("Using deployment record");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const provider = this.hre.ethers.provider;
|
|
93
|
+
const network = await provider.getNetwork();
|
|
94
|
+
const chainId = Number(network.chainId);
|
|
95
|
+
|
|
96
|
+
const deploymentData = deployment
|
|
97
|
+
? deployment.getDeployedDiamondData()
|
|
98
|
+
: await this.deploymentManager
|
|
99
|
+
.ensureDeployment(diamondName, networkName, false, false)
|
|
100
|
+
.then((d) => d.getDeployedDiamondData());
|
|
101
|
+
|
|
102
|
+
await this.helperGenerator.generateDeploymentHelpers(
|
|
103
|
+
diamondName,
|
|
104
|
+
networkName,
|
|
105
|
+
chainId,
|
|
106
|
+
deploymentData,
|
|
107
|
+
deployment || undefined
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
Logger.info("Skipping helper generation");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Step 4: Run coverage
|
|
114
|
+
Logger.section("Step 3/3: Running Forge Coverage");
|
|
115
|
+
|
|
116
|
+
// Get fork URL for network (same pattern as ForgeFuzzingFramework)
|
|
117
|
+
const provider = this.hre.ethers.provider;
|
|
118
|
+
let forkUrl: string;
|
|
119
|
+
|
|
120
|
+
if (networkName !== "hardhat") {
|
|
121
|
+
forkUrl = (provider as any).connection?.url || "http://127.0.0.1:8545";
|
|
122
|
+
} else {
|
|
123
|
+
forkUrl = "http://127.0.0.1:8545";
|
|
124
|
+
Logger.warn(
|
|
125
|
+
"⚠️ Network is \"hardhat\" - defaulting to localhost fork: http://127.0.0.1:8545"
|
|
126
|
+
);
|
|
127
|
+
Logger.warn("💡 Make sure Hardhat node is running: npx hardhat node");
|
|
128
|
+
Logger.warn("💡 Or specify network explicitly: --network localhost");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const args = this.buildCoverageCommand({ ...options, forkUrl });
|
|
132
|
+
|
|
133
|
+
Logger.info(`Executing: forge coverage ${args.join(" ")}`);
|
|
134
|
+
Logger.info("⏳ Running coverage analysis (this may take a while)...");
|
|
135
|
+
|
|
136
|
+
const success = await this.executeForge(args);
|
|
137
|
+
|
|
138
|
+
if (success) {
|
|
139
|
+
Logger.success("✅ Coverage analysis completed successfully");
|
|
140
|
+
} else {
|
|
141
|
+
Logger.error("❌ Coverage analysis failed");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return success;
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
Logger.error(`Coverage execution failed: ${error.message}`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build complete forge coverage command arguments
|
|
153
|
+
*
|
|
154
|
+
* @param options - Coverage options
|
|
155
|
+
* @returns Array of command arguments
|
|
156
|
+
*/
|
|
157
|
+
private buildCoverageCommand(options: CoverageOptions): string[] {
|
|
158
|
+
const args: string[] = [];
|
|
159
|
+
|
|
160
|
+
// Add fork URL if provided
|
|
161
|
+
if (options.forkUrl) {
|
|
162
|
+
args.push("--fork-url", options.forkUrl);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Add all option groups
|
|
166
|
+
args.push(...this.buildReportOptions(options));
|
|
167
|
+
args.push(...this.buildFilterOptions(options));
|
|
168
|
+
args.push(...this.buildDisplayOptions(options));
|
|
169
|
+
args.push(...this.buildTestOptions(options));
|
|
170
|
+
args.push(...this.buildEvmOptions(options));
|
|
171
|
+
args.push(...this.buildBuildOptions(options));
|
|
172
|
+
|
|
173
|
+
return args.filter((arg) => arg !== "");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build report-related options
|
|
178
|
+
*/
|
|
179
|
+
private buildReportOptions(options: CoverageOptions): string[] {
|
|
180
|
+
const args: string[] = [];
|
|
181
|
+
|
|
182
|
+
// Multiple --report flags
|
|
183
|
+
if (options.report && options.report.length > 0) {
|
|
184
|
+
for (const reportType of options.report) {
|
|
185
|
+
args.push("--report", reportType);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (options.reportFile) {
|
|
190
|
+
args.push("--report-file", options.reportFile);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (options.lcovVersion) {
|
|
194
|
+
args.push("--lcov-version", options.lcovVersion);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (options.includeLibs) {
|
|
198
|
+
args.push("--include-libs");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (options.excludeTests) {
|
|
202
|
+
args.push("--exclude-tests");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (options.irMinimum) {
|
|
206
|
+
args.push("--ir-minimum");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return args;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Build test filtering options
|
|
214
|
+
*/
|
|
215
|
+
private buildFilterOptions(options: CoverageOptions): string[] {
|
|
216
|
+
const args: string[] = [];
|
|
217
|
+
|
|
218
|
+
if (options.matchTest) {
|
|
219
|
+
args.push("--match-test", options.matchTest);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (options.noMatchTest) {
|
|
223
|
+
args.push("--no-match-test", options.noMatchTest);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (options.matchContract) {
|
|
227
|
+
args.push("--match-contract", options.matchContract);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (options.noMatchContract) {
|
|
231
|
+
args.push("--no-match-contract", options.noMatchContract);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.matchPath) {
|
|
235
|
+
args.push("--match-path", options.matchPath);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (options.noMatchPath) {
|
|
239
|
+
args.push("--no-match-path", options.noMatchPath);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (options.noMatchCoverage) {
|
|
243
|
+
args.push("--no-match-coverage", options.noMatchCoverage);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return args;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build display options
|
|
251
|
+
*/
|
|
252
|
+
private buildDisplayOptions(options: CoverageOptions): string[] {
|
|
253
|
+
const args: string[] = [];
|
|
254
|
+
|
|
255
|
+
// Verbosity (-v, -vv, -vvv, etc.)
|
|
256
|
+
if (options.verbosity && options.verbosity > 0) {
|
|
257
|
+
args.push("-" + "v".repeat(options.verbosity));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (options.quiet) {
|
|
261
|
+
args.push("--quiet");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (options.json) {
|
|
265
|
+
args.push("--json");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (options.md) {
|
|
269
|
+
args.push("--md");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (options.color) {
|
|
273
|
+
args.push("--color", options.color);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return args;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Build test execution options
|
|
281
|
+
*/
|
|
282
|
+
private buildTestOptions(options: CoverageOptions): string[] {
|
|
283
|
+
const args: string[] = [];
|
|
284
|
+
|
|
285
|
+
if (options.threads !== undefined) {
|
|
286
|
+
args.push("--threads", options.threads.toString());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (options.fuzzRuns !== undefined) {
|
|
290
|
+
args.push("--fuzz-runs", options.fuzzRuns.toString());
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (options.fuzzSeed) {
|
|
294
|
+
args.push("--fuzz-seed", options.fuzzSeed);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (options.failFast) {
|
|
298
|
+
args.push("--fail-fast");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (options.allowFailure) {
|
|
302
|
+
args.push("--allow-failure");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return args;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build EVM options
|
|
310
|
+
*/
|
|
311
|
+
private buildEvmOptions(options: CoverageOptions): string[] {
|
|
312
|
+
const args: string[] = [];
|
|
313
|
+
|
|
314
|
+
if (options.forkBlockNumber !== undefined) {
|
|
315
|
+
args.push("--fork-block-number", options.forkBlockNumber.toString());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (options.initialBalance) {
|
|
319
|
+
args.push("--initial-balance", options.initialBalance);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (options.sender) {
|
|
323
|
+
args.push("--sender", options.sender);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (options.ffi) {
|
|
327
|
+
args.push("--ffi");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return args;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Build build options
|
|
335
|
+
*/
|
|
336
|
+
private buildBuildOptions(options: CoverageOptions): string[] {
|
|
337
|
+
const args: string[] = [];
|
|
338
|
+
|
|
339
|
+
if (options.force) {
|
|
340
|
+
args.push("--force");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (options.noCache) {
|
|
344
|
+
args.push("--no-cache");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (options.optimize) {
|
|
348
|
+
args.push("--optimize");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (options.optimizerRuns !== undefined) {
|
|
352
|
+
args.push("--optimizer-runs", options.optimizerRuns.toString());
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (options.viaIr) {
|
|
356
|
+
args.push("--via-ir");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return args;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Execute forge coverage command and stream output
|
|
364
|
+
*
|
|
365
|
+
* @param args - Command arguments
|
|
366
|
+
* @returns Promise<boolean> - true if command succeeds
|
|
367
|
+
*/
|
|
368
|
+
private executeForge(args: string[]): Promise<boolean> {
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
const forge = spawn("forge", ["coverage", ...args], {
|
|
371
|
+
cwd: this.hre.config.paths.root,
|
|
372
|
+
stdio: "inherit",
|
|
373
|
+
shell: true,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
forge.on("close", (code) => {
|
|
377
|
+
if (code === 0) {
|
|
378
|
+
resolve(true);
|
|
379
|
+
} else {
|
|
380
|
+
resolve(false);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
forge.on("error", (error) => {
|
|
385
|
+
Logger.error(`Failed to execute forge: ${error.message}`);
|
|
386
|
+
reject(error);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extendConfig, extendEnvironment, internalTask, task } from "hardhat/config";
|
|
2
|
+
import "./tasks/coverage";
|
|
2
3
|
import "./tasks/deploy";
|
|
3
4
|
import "./tasks/generate-helpers";
|
|
4
5
|
import "./tasks/init";
|
|
@@ -7,17 +8,17 @@ import "./types/hardhat";
|
|
|
7
8
|
|
|
8
9
|
import { existsSync, writeFileSync } from "fs";
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
TASK_COMPILE_GET_REMAPPINGS,
|
|
12
|
+
TASK_COMPILE_TRANSFORM_IMPORT_NAME,
|
|
12
13
|
} from "hardhat/builtin-tasks/task-names";
|
|
13
14
|
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
14
15
|
import path from "path";
|
|
15
16
|
import picocolors from "picocolors";
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
getForgeConfig,
|
|
19
|
+
getRemappings,
|
|
20
|
+
HardhatFoundryError,
|
|
21
|
+
installDependency,
|
|
21
22
|
} from "./foundry";
|
|
22
23
|
import { validateConfig } from "./utils/validation";
|
|
23
24
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { task, types } from "hardhat/config";
|
|
2
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
3
|
+
import { Logger } from "../utils/logger";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Task: diamonds-forge:coverage
|
|
7
|
+
*
|
|
8
|
+
* Runs Forge coverage with Diamond deployment.
|
|
9
|
+
* - Ensures Diamond deployment exists
|
|
10
|
+
* - Generates Solidity helpers
|
|
11
|
+
* - Runs forge coverage with specified options
|
|
12
|
+
*
|
|
13
|
+
* Use Hardhat's built-in --network flag to specify the network
|
|
14
|
+
*
|
|
15
|
+
* Design: Mirrors diamonds-forge:test task structure for consistency
|
|
16
|
+
*/
|
|
17
|
+
task("diamonds-forge:coverage", "Run forge coverage for Diamond contracts")
|
|
18
|
+
.addOptionalParam(
|
|
19
|
+
"diamondName",
|
|
20
|
+
"Name of the Diamond to analyze",
|
|
21
|
+
"ExampleDiamond",
|
|
22
|
+
types.string
|
|
23
|
+
)
|
|
24
|
+
// Report options
|
|
25
|
+
.addOptionalParam(
|
|
26
|
+
"report",
|
|
27
|
+
"Report type (summary, lcov, debug, bytecode) - comma-separated for multiple",
|
|
28
|
+
undefined,
|
|
29
|
+
types.string
|
|
30
|
+
)
|
|
31
|
+
.addOptionalParam(
|
|
32
|
+
"reportFile",
|
|
33
|
+
"Output path for report file",
|
|
34
|
+
undefined,
|
|
35
|
+
types.string
|
|
36
|
+
)
|
|
37
|
+
.addOptionalParam(
|
|
38
|
+
"lcovVersion",
|
|
39
|
+
"LCOV format version (v1 or v2)",
|
|
40
|
+
undefined,
|
|
41
|
+
types.string
|
|
42
|
+
)
|
|
43
|
+
.addFlag("includeLibs", "Include libraries in coverage report")
|
|
44
|
+
.addFlag("excludeTests", "Exclude tests from coverage report")
|
|
45
|
+
.addFlag("irMinimum", "Enable viaIR with minimum optimization")
|
|
46
|
+
// Test filtering
|
|
47
|
+
.addOptionalParam(
|
|
48
|
+
"matchTest",
|
|
49
|
+
"Run tests matching pattern",
|
|
50
|
+
undefined,
|
|
51
|
+
types.string
|
|
52
|
+
)
|
|
53
|
+
.addOptionalParam(
|
|
54
|
+
"noMatchTest",
|
|
55
|
+
"Exclude tests matching pattern",
|
|
56
|
+
undefined,
|
|
57
|
+
types.string
|
|
58
|
+
)
|
|
59
|
+
.addOptionalParam(
|
|
60
|
+
"matchContract",
|
|
61
|
+
"Run contracts matching pattern",
|
|
62
|
+
undefined,
|
|
63
|
+
types.string
|
|
64
|
+
)
|
|
65
|
+
.addOptionalParam(
|
|
66
|
+
"noMatchContract",
|
|
67
|
+
"Exclude contracts matching pattern",
|
|
68
|
+
undefined,
|
|
69
|
+
types.string
|
|
70
|
+
)
|
|
71
|
+
.addOptionalParam(
|
|
72
|
+
"matchPath",
|
|
73
|
+
"Run files matching glob",
|
|
74
|
+
undefined,
|
|
75
|
+
types.string
|
|
76
|
+
)
|
|
77
|
+
.addOptionalParam(
|
|
78
|
+
"noMatchPath",
|
|
79
|
+
"Exclude files matching glob",
|
|
80
|
+
undefined,
|
|
81
|
+
types.string
|
|
82
|
+
)
|
|
83
|
+
.addOptionalParam(
|
|
84
|
+
"noMatchCoverage",
|
|
85
|
+
"Exclude files from coverage report",
|
|
86
|
+
undefined,
|
|
87
|
+
types.string
|
|
88
|
+
)
|
|
89
|
+
// Display options
|
|
90
|
+
.addOptionalParam(
|
|
91
|
+
"verbosity",
|
|
92
|
+
"Verbosity level (1-5)",
|
|
93
|
+
undefined,
|
|
94
|
+
types.int
|
|
95
|
+
)
|
|
96
|
+
.addFlag("quiet", "Suppress log output")
|
|
97
|
+
.addFlag("json", "Format output as JSON")
|
|
98
|
+
.addFlag("md", "Format output as Markdown")
|
|
99
|
+
.addOptionalParam(
|
|
100
|
+
"color",
|
|
101
|
+
"Color mode (auto, always, never)",
|
|
102
|
+
undefined,
|
|
103
|
+
types.string
|
|
104
|
+
)
|
|
105
|
+
// Test execution options
|
|
106
|
+
.addOptionalParam(
|
|
107
|
+
"threads",
|
|
108
|
+
"Number of threads to use",
|
|
109
|
+
undefined,
|
|
110
|
+
types.int
|
|
111
|
+
)
|
|
112
|
+
.addOptionalParam(
|
|
113
|
+
"fuzzRuns",
|
|
114
|
+
"Number of fuzz runs",
|
|
115
|
+
undefined,
|
|
116
|
+
types.int
|
|
117
|
+
)
|
|
118
|
+
.addOptionalParam(
|
|
119
|
+
"fuzzSeed",
|
|
120
|
+
"Seed for fuzz randomness",
|
|
121
|
+
undefined,
|
|
122
|
+
types.string
|
|
123
|
+
)
|
|
124
|
+
.addFlag("failFast", "Stop on first failure")
|
|
125
|
+
.addFlag("allowFailure", "Exit 0 even if tests fail")
|
|
126
|
+
// EVM options
|
|
127
|
+
.addOptionalParam(
|
|
128
|
+
"forkBlockNumber",
|
|
129
|
+
"Fork from specific block number",
|
|
130
|
+
undefined,
|
|
131
|
+
types.int
|
|
132
|
+
)
|
|
133
|
+
.addOptionalParam(
|
|
134
|
+
"initialBalance",
|
|
135
|
+
"Initial balance for test contracts",
|
|
136
|
+
undefined,
|
|
137
|
+
types.string
|
|
138
|
+
)
|
|
139
|
+
.addOptionalParam(
|
|
140
|
+
"sender",
|
|
141
|
+
"Test sender address",
|
|
142
|
+
undefined,
|
|
143
|
+
types.string
|
|
144
|
+
)
|
|
145
|
+
.addFlag("ffi", "Enable FFI cheatcode")
|
|
146
|
+
// Build/deployment options
|
|
147
|
+
.addFlag("force", "Force recompile and redeploy")
|
|
148
|
+
.addFlag("noCache", "Disable cache")
|
|
149
|
+
.addFlag("optimize", "Enable Solidity optimizer")
|
|
150
|
+
.addOptionalParam(
|
|
151
|
+
"optimizerRuns",
|
|
152
|
+
"Optimizer runs",
|
|
153
|
+
undefined,
|
|
154
|
+
types.int
|
|
155
|
+
)
|
|
156
|
+
.addFlag("viaIr", "Use Yul IR compilation")
|
|
157
|
+
.addFlag("skipDeployment", "Skip Diamond deployment step")
|
|
158
|
+
.addFlag("skipHelpers", "Skip helper generation step")
|
|
159
|
+
.addFlag("saveDeployment", "Write deployment data to file for reuse")
|
|
160
|
+
.setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => {
|
|
161
|
+
Logger.section("Running Forge Coverage with Diamond");
|
|
162
|
+
|
|
163
|
+
const diamondName = taskArgs.diamondName;
|
|
164
|
+
const networkName = hre.network.name;
|
|
165
|
+
|
|
166
|
+
Logger.info(`Diamond: ${diamondName}`);
|
|
167
|
+
Logger.info(`Network: ${networkName}`);
|
|
168
|
+
|
|
169
|
+
// Log key options
|
|
170
|
+
if (taskArgs.report) Logger.info(`Report: ${taskArgs.report}`);
|
|
171
|
+
if (taskArgs.reportFile) Logger.info(`Report File: ${taskArgs.reportFile}`);
|
|
172
|
+
if (taskArgs.matchTest) Logger.info(`Match Test: ${taskArgs.matchTest}`);
|
|
173
|
+
if (taskArgs.matchContract) Logger.info(`Match Contract: ${taskArgs.matchContract}`);
|
|
174
|
+
if (taskArgs.skipDeployment) Logger.info("Skip Deployment: true");
|
|
175
|
+
if (taskArgs.skipHelpers) Logger.info("Skip Helpers: true");
|
|
176
|
+
if (taskArgs.saveDeployment) Logger.info("Save Deployment: true");
|
|
177
|
+
|
|
178
|
+
// Lazy-load framework to avoid circular dependency during config loading
|
|
179
|
+
const { ForgeCoverageFramework } = await import(
|
|
180
|
+
"../framework/ForgeCoverageFramework.js"
|
|
181
|
+
);
|
|
182
|
+
type CoverageOptions = Parameters<
|
|
183
|
+
InstanceType<typeof ForgeCoverageFramework>["runCoverage"]
|
|
184
|
+
>[0];
|
|
185
|
+
|
|
186
|
+
// Parse comma-separated report types
|
|
187
|
+
const reportTypes = taskArgs.report
|
|
188
|
+
? taskArgs.report.split(",").map((r: string) => r.trim())
|
|
189
|
+
: undefined;
|
|
190
|
+
|
|
191
|
+
// Create coverage options (matches test.ts pattern)
|
|
192
|
+
const options: CoverageOptions = {
|
|
193
|
+
diamondName,
|
|
194
|
+
networkName,
|
|
195
|
+
force: taskArgs.force,
|
|
196
|
+
skipDeployment: taskArgs.skipDeployment,
|
|
197
|
+
skipHelpers: taskArgs.skipHelpers,
|
|
198
|
+
writeDeployedDiamondData: taskArgs.saveDeployment,
|
|
199
|
+
// Report options
|
|
200
|
+
report: reportTypes,
|
|
201
|
+
reportFile: taskArgs.reportFile,
|
|
202
|
+
lcovVersion: taskArgs.lcovVersion,
|
|
203
|
+
includeLibs: taskArgs.includeLibs,
|
|
204
|
+
excludeTests: taskArgs.excludeTests,
|
|
205
|
+
irMinimum: taskArgs.irMinimum,
|
|
206
|
+
// Test filtering
|
|
207
|
+
matchTest: taskArgs.matchTest,
|
|
208
|
+
noMatchTest: taskArgs.noMatchTest,
|
|
209
|
+
matchContract: taskArgs.matchContract,
|
|
210
|
+
noMatchContract: taskArgs.noMatchContract,
|
|
211
|
+
matchPath: taskArgs.matchPath,
|
|
212
|
+
noMatchPath: taskArgs.noMatchPath,
|
|
213
|
+
noMatchCoverage: taskArgs.noMatchCoverage,
|
|
214
|
+
// Display options
|
|
215
|
+
verbosity: taskArgs.verbosity,
|
|
216
|
+
quiet: taskArgs.quiet,
|
|
217
|
+
json: taskArgs.json,
|
|
218
|
+
md: taskArgs.md,
|
|
219
|
+
color: taskArgs.color,
|
|
220
|
+
// Test execution
|
|
221
|
+
threads: taskArgs.threads,
|
|
222
|
+
fuzzRuns: taskArgs.fuzzRuns,
|
|
223
|
+
fuzzSeed: taskArgs.fuzzSeed,
|
|
224
|
+
failFast: taskArgs.failFast,
|
|
225
|
+
allowFailure: taskArgs.allowFailure,
|
|
226
|
+
// EVM options
|
|
227
|
+
forkBlockNumber: taskArgs.forkBlockNumber,
|
|
228
|
+
initialBalance: taskArgs.initialBalance,
|
|
229
|
+
sender: taskArgs.sender,
|
|
230
|
+
ffi: taskArgs.ffi,
|
|
231
|
+
// Build options
|
|
232
|
+
noCache: taskArgs.noCache,
|
|
233
|
+
optimize: taskArgs.optimize,
|
|
234
|
+
optimizerRuns: taskArgs.optimizerRuns,
|
|
235
|
+
viaIr: taskArgs.viaIr,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Run coverage using the framework (same pattern as test.ts)
|
|
239
|
+
const framework = new ForgeCoverageFramework(hre);
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const success = await framework.runCoverage(options);
|
|
243
|
+
|
|
244
|
+
if (success) {
|
|
245
|
+
Logger.section("Coverage Analysis Complete");
|
|
246
|
+
Logger.success("✅ Coverage completed successfully!");
|
|
247
|
+
process.exitCode = 0;
|
|
248
|
+
} else {
|
|
249
|
+
Logger.section("Coverage Analysis Complete");
|
|
250
|
+
Logger.error("❌ Coverage analysis failed");
|
|
251
|
+
process.exitCode = 1;
|
|
252
|
+
}
|
|
253
|
+
} catch (error: any) {
|
|
254
|
+
Logger.error(`Coverage execution failed: ${error.message}`);
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
});
|