@diamondslab/diamonds-hardhat-foundry 2.2.2 → 2.3.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 +61 -0
- package/dist/framework/ForgeCoverageFramework.d.ts.map +1 -0
- package/dist/framework/ForgeCoverageFramework.js +238 -0
- package/dist/framework/ForgeCoverageFramework.js.map +1 -0
- package/dist/framework/HelperGenerator.d.ts.map +1 -1
- package/dist/framework/HelperGenerator.js +7 -3
- package/dist/framework/HelperGenerator.js.map +1 -1
- 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 +173 -0
- package/dist/tasks/coverage.js.map +1 -0
- package/dist/types/config.d.ts +80 -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 +287 -0
- package/src/framework/HelperGenerator.ts +9 -5
- package/src/index.ts +7 -6
- package/src/tasks/coverage.ts +307 -0
- package/src/types/config.ts +94 -0
- package/CHANGELOG.md +0 -321
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,307 @@
|
|
|
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
|
+
task("diamonds-forge:coverage", "Run forge coverage for Diamond contracts")
|
|
16
|
+
.addParam(
|
|
17
|
+
"diamondName",
|
|
18
|
+
"Name of the Diamond to analyze",
|
|
19
|
+
undefined,
|
|
20
|
+
types.string
|
|
21
|
+
)
|
|
22
|
+
// Report options
|
|
23
|
+
.addOptionalParam(
|
|
24
|
+
"report",
|
|
25
|
+
"Report type (summary, lcov, debug, bytecode) - can be used multiple times by passing comma-separated values",
|
|
26
|
+
undefined,
|
|
27
|
+
types.string
|
|
28
|
+
)
|
|
29
|
+
.addOptionalParam(
|
|
30
|
+
"reportFile",
|
|
31
|
+
"Output path for report file",
|
|
32
|
+
undefined,
|
|
33
|
+
types.string
|
|
34
|
+
)
|
|
35
|
+
.addOptionalParam(
|
|
36
|
+
"lcovVersion",
|
|
37
|
+
"LCOV format version",
|
|
38
|
+
undefined,
|
|
39
|
+
types.string
|
|
40
|
+
)
|
|
41
|
+
.addFlag("includeLibs", "Include libraries in coverage report")
|
|
42
|
+
.addFlag("excludeTests", "Exclude tests from coverage report")
|
|
43
|
+
.addFlag("irMinimum", "Enable viaIR with minimum optimization")
|
|
44
|
+
// Test filtering
|
|
45
|
+
.addOptionalParam(
|
|
46
|
+
"matchTest",
|
|
47
|
+
"Run tests matching pattern (--match-test)",
|
|
48
|
+
undefined,
|
|
49
|
+
types.string
|
|
50
|
+
)
|
|
51
|
+
.addOptionalParam(
|
|
52
|
+
"noMatchTest",
|
|
53
|
+
"Exclude tests matching pattern (--no-match-test)",
|
|
54
|
+
undefined,
|
|
55
|
+
types.string
|
|
56
|
+
)
|
|
57
|
+
.addOptionalParam(
|
|
58
|
+
"matchContract",
|
|
59
|
+
"Run contracts matching pattern (--match-contract)",
|
|
60
|
+
undefined,
|
|
61
|
+
types.string
|
|
62
|
+
)
|
|
63
|
+
.addOptionalParam(
|
|
64
|
+
"noMatchContract",
|
|
65
|
+
"Exclude contracts matching pattern (--no-match-contract)",
|
|
66
|
+
undefined,
|
|
67
|
+
types.string
|
|
68
|
+
)
|
|
69
|
+
.addOptionalParam(
|
|
70
|
+
"matchPath",
|
|
71
|
+
"Run files matching glob (--match-path)",
|
|
72
|
+
undefined,
|
|
73
|
+
types.string
|
|
74
|
+
)
|
|
75
|
+
.addOptionalParam(
|
|
76
|
+
"noMatchPath",
|
|
77
|
+
"Exclude files matching glob (--no-match-path)",
|
|
78
|
+
undefined,
|
|
79
|
+
types.string
|
|
80
|
+
)
|
|
81
|
+
.addOptionalParam(
|
|
82
|
+
"noMatchCoverage",
|
|
83
|
+
"Exclude files from coverage report (--no-match-coverage)",
|
|
84
|
+
undefined,
|
|
85
|
+
types.string
|
|
86
|
+
)
|
|
87
|
+
// Display options
|
|
88
|
+
.addOptionalParam(
|
|
89
|
+
"verbosity",
|
|
90
|
+
"Verbosity level (1-5, more v's = more verbose)",
|
|
91
|
+
undefined,
|
|
92
|
+
types.int
|
|
93
|
+
)
|
|
94
|
+
.addFlag("quiet", "Suppress log output")
|
|
95
|
+
.addFlag("json", "Format output as JSON")
|
|
96
|
+
.addFlag("md", "Format output as Markdown")
|
|
97
|
+
.addOptionalParam(
|
|
98
|
+
"color",
|
|
99
|
+
"Color mode (auto, always, never)",
|
|
100
|
+
undefined,
|
|
101
|
+
types.string
|
|
102
|
+
)
|
|
103
|
+
// Test execution options
|
|
104
|
+
.addOptionalParam(
|
|
105
|
+
"threads",
|
|
106
|
+
"Number of threads to use",
|
|
107
|
+
undefined,
|
|
108
|
+
types.int
|
|
109
|
+
)
|
|
110
|
+
.addOptionalParam(
|
|
111
|
+
"fuzzRuns",
|
|
112
|
+
"Number of fuzz runs",
|
|
113
|
+
undefined,
|
|
114
|
+
types.int
|
|
115
|
+
)
|
|
116
|
+
.addOptionalParam(
|
|
117
|
+
"fuzzSeed",
|
|
118
|
+
"Seed for fuzz randomness",
|
|
119
|
+
undefined,
|
|
120
|
+
types.string
|
|
121
|
+
)
|
|
122
|
+
.addFlag("failFast", "Stop on first failure")
|
|
123
|
+
.addFlag("allowFailure", "Exit 0 even if tests fail")
|
|
124
|
+
// EVM options
|
|
125
|
+
.addOptionalParam(
|
|
126
|
+
"forkBlockNumber",
|
|
127
|
+
"Fork from specific block number",
|
|
128
|
+
undefined,
|
|
129
|
+
types.int
|
|
130
|
+
)
|
|
131
|
+
.addOptionalParam(
|
|
132
|
+
"initialBalance",
|
|
133
|
+
"Initial balance for test contracts",
|
|
134
|
+
undefined,
|
|
135
|
+
types.string
|
|
136
|
+
)
|
|
137
|
+
.addOptionalParam(
|
|
138
|
+
"sender",
|
|
139
|
+
"Test sender address",
|
|
140
|
+
undefined,
|
|
141
|
+
types.string
|
|
142
|
+
)
|
|
143
|
+
.addFlag("ffi", "Enable FFI cheatcode")
|
|
144
|
+
// Build options
|
|
145
|
+
.addFlag("force", "Force recompile and redeploy")
|
|
146
|
+
.addFlag("noCache", "Disable cache")
|
|
147
|
+
.addFlag("optimize", "Enable Solidity optimizer")
|
|
148
|
+
.addOptionalParam(
|
|
149
|
+
"optimizerRuns",
|
|
150
|
+
"Optimizer runs",
|
|
151
|
+
undefined,
|
|
152
|
+
types.int
|
|
153
|
+
)
|
|
154
|
+
.addFlag("viaIr", "Use Yul IR compilation")
|
|
155
|
+
.setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => {
|
|
156
|
+
Logger.section("Running Forge Coverage with Diamond");
|
|
157
|
+
|
|
158
|
+
const diamondName = taskArgs.diamondName;
|
|
159
|
+
const networkName = hre.network.name;
|
|
160
|
+
|
|
161
|
+
// Validate required parameters
|
|
162
|
+
if (!diamondName) {
|
|
163
|
+
Logger.error("--diamond-name is required");
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Logger.info(`Diamond: ${diamondName}`);
|
|
169
|
+
Logger.info(`Network: ${networkName}`);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Lazy-load framework classes to avoid circular dependency
|
|
173
|
+
const { DeploymentManager } = await import("../framework/DeploymentManager.js");
|
|
174
|
+
const { HelperGenerator } = await import("../framework/HelperGenerator.js");
|
|
175
|
+
|
|
176
|
+
// Step 1: Ensure Diamond deployment
|
|
177
|
+
Logger.section("Step 1/3: Ensuring Diamond Deployment");
|
|
178
|
+
|
|
179
|
+
const deploymentManager = new DeploymentManager(hre);
|
|
180
|
+
|
|
181
|
+
await deploymentManager.ensureDeployment(
|
|
182
|
+
diamondName,
|
|
183
|
+
networkName,
|
|
184
|
+
taskArgs.force || false,
|
|
185
|
+
false // Don't write deployment data for coverage (ephemeral by default)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Step 2: Generate helpers
|
|
189
|
+
Logger.section("Step 2/3: Generating Solidity Helpers");
|
|
190
|
+
|
|
191
|
+
const deployment = await deploymentManager.getDeployment(
|
|
192
|
+
diamondName,
|
|
193
|
+
networkName
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (!deployment) {
|
|
197
|
+
Logger.error("No deployment found. Cannot generate helpers.");
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const provider = hre.ethers.provider;
|
|
203
|
+
const network = await provider.getNetwork();
|
|
204
|
+
const chainId = Number(network.chainId);
|
|
205
|
+
const deploymentData = deployment.getDeployedDiamondData();
|
|
206
|
+
|
|
207
|
+
const helperGenerator = new HelperGenerator(hre);
|
|
208
|
+
await helperGenerator.generateDeploymentHelpers(
|
|
209
|
+
diamondName,
|
|
210
|
+
networkName,
|
|
211
|
+
chainId,
|
|
212
|
+
deploymentData,
|
|
213
|
+
deployment
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Step 3: Run coverage
|
|
217
|
+
Logger.section("Step 3/3: Running Forge Coverage");
|
|
218
|
+
|
|
219
|
+
// Construct fork URL for network
|
|
220
|
+
// Coverage requires forking from a running network to access deployed contracts
|
|
221
|
+
let forkUrl: string;
|
|
222
|
+
if (networkName !== "hardhat") {
|
|
223
|
+
// Use the configured network's URL
|
|
224
|
+
forkUrl = (provider as any)._hardhatProvider?._wrapped?.url || "http://127.0.0.1:8545";
|
|
225
|
+
Logger.info(`Forking from ${networkName}: ${forkUrl}`);
|
|
226
|
+
} else {
|
|
227
|
+
// Default to localhost for hardhat network
|
|
228
|
+
// This assumes user has `npx hardhat node` running
|
|
229
|
+
forkUrl = "http://127.0.0.1:8545";
|
|
230
|
+
Logger.warn(`⚠️ Network is "${networkName}" - defaulting to localhost fork: ${forkUrl}`);
|
|
231
|
+
Logger.warn(`💡 Make sure Hardhat node is running: npx hardhat node`);
|
|
232
|
+
Logger.warn(`💡 Or specify network explicitly: --network localhost`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Lazy-load framework to avoid circular dependency
|
|
236
|
+
const { ForgeCoverageFramework } = await import("../framework/ForgeCoverageFramework.js");
|
|
237
|
+
type CoverageOptions = Parameters<InstanceType<typeof ForgeCoverageFramework>["runCoverage"]>[0];
|
|
238
|
+
|
|
239
|
+
// Build coverage options from task args
|
|
240
|
+
const options: CoverageOptions = {
|
|
241
|
+
// Fork URL
|
|
242
|
+
forkUrl,
|
|
243
|
+
|
|
244
|
+
// Report options
|
|
245
|
+
report: taskArgs.report ? taskArgs.report.split(",") : undefined,
|
|
246
|
+
reportFile: taskArgs.reportFile,
|
|
247
|
+
lcovVersion: taskArgs.lcovVersion,
|
|
248
|
+
includeLibs: taskArgs.includeLibs,
|
|
249
|
+
excludeTests: taskArgs.excludeTests,
|
|
250
|
+
irMinimum: taskArgs.irMinimum,
|
|
251
|
+
|
|
252
|
+
// Test filtering
|
|
253
|
+
matchTest: taskArgs.matchTest,
|
|
254
|
+
noMatchTest: taskArgs.noMatchTest,
|
|
255
|
+
matchContract: taskArgs.matchContract,
|
|
256
|
+
noMatchContract: taskArgs.noMatchContract,
|
|
257
|
+
matchPath: taskArgs.matchPath,
|
|
258
|
+
noMatchPath: taskArgs.noMatchPath,
|
|
259
|
+
noMatchCoverage: taskArgs.noMatchCoverage,
|
|
260
|
+
|
|
261
|
+
// Display options
|
|
262
|
+
verbosity: taskArgs.verbosity,
|
|
263
|
+
quiet: taskArgs.quiet,
|
|
264
|
+
json: taskArgs.json,
|
|
265
|
+
md: taskArgs.md,
|
|
266
|
+
color: taskArgs.color as "auto" | "always" | "never" | undefined,
|
|
267
|
+
|
|
268
|
+
// Test execution options
|
|
269
|
+
threads: taskArgs.threads,
|
|
270
|
+
fuzzRuns: taskArgs.fuzzRuns,
|
|
271
|
+
fuzzSeed: taskArgs.fuzzSeed,
|
|
272
|
+
failFast: taskArgs.failFast,
|
|
273
|
+
allowFailure: taskArgs.allowFailure,
|
|
274
|
+
|
|
275
|
+
// EVM options
|
|
276
|
+
forkBlockNumber: taskArgs.forkBlockNumber,
|
|
277
|
+
initialBalance: taskArgs.initialBalance,
|
|
278
|
+
sender: taskArgs.sender,
|
|
279
|
+
ffi: taskArgs.ffi,
|
|
280
|
+
|
|
281
|
+
// Build options
|
|
282
|
+
force: taskArgs.force,
|
|
283
|
+
noCache: taskArgs.noCache,
|
|
284
|
+
optimize: taskArgs.optimize,
|
|
285
|
+
optimizerRuns: taskArgs.optimizerRuns,
|
|
286
|
+
viaIr: taskArgs.viaIr,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Run coverage
|
|
290
|
+
const framework = new ForgeCoverageFramework(hre);
|
|
291
|
+
const success = await framework.runCoverage(options);
|
|
292
|
+
|
|
293
|
+
if (success) {
|
|
294
|
+
Logger.section("Coverage Analysis Complete");
|
|
295
|
+
Logger.success("Coverage analysis completed successfully!");
|
|
296
|
+
process.exitCode = 0;
|
|
297
|
+
} else {
|
|
298
|
+
Logger.section("Coverage Analysis Complete");
|
|
299
|
+
Logger.error("Coverage analysis failed");
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
}
|
|
302
|
+
} catch (error: any) {
|
|
303
|
+
Logger.error(`Coverage execution failed: ${error.message}`);
|
|
304
|
+
process.exitCode = 1;
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
});
|
package/src/types/config.ts
CHANGED
|
@@ -52,3 +52,97 @@ export const DEFAULT_CONFIG: Required<DiamondsFoundryConfig> = {
|
|
|
52
52
|
reuseDeployment: true,
|
|
53
53
|
forgeTestArgs: [],
|
|
54
54
|
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Type for forge coverage report formats
|
|
58
|
+
*/
|
|
59
|
+
export type CoverageReportType = "summary" | "lcov" | "debug" | "bytecode";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Type for color output modes
|
|
63
|
+
*/
|
|
64
|
+
export type ColorMode = "auto" | "always" | "never";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Coverage options for forge coverage command
|
|
68
|
+
* Maps to all available forge coverage command-line options
|
|
69
|
+
*/
|
|
70
|
+
export interface CoverageOptions {
|
|
71
|
+
// Report options
|
|
72
|
+
/** Report type(s) - can specify multiple */
|
|
73
|
+
report?: CoverageReportType[];
|
|
74
|
+
/** Output path for report file */
|
|
75
|
+
reportFile?: string;
|
|
76
|
+
/** LCOV format version (default: 1) */
|
|
77
|
+
lcovVersion?: string;
|
|
78
|
+
/** Include libraries in coverage report */
|
|
79
|
+
includeLibs?: boolean;
|
|
80
|
+
/** Exclude tests from coverage report */
|
|
81
|
+
excludeTests?: boolean;
|
|
82
|
+
/** Enable viaIR with minimum optimization */
|
|
83
|
+
irMinimum?: boolean;
|
|
84
|
+
|
|
85
|
+
// Test filtering
|
|
86
|
+
/** Run tests matching regex pattern */
|
|
87
|
+
matchTest?: string;
|
|
88
|
+
/** Exclude tests matching regex pattern */
|
|
89
|
+
noMatchTest?: string;
|
|
90
|
+
/** Run contracts matching regex pattern */
|
|
91
|
+
matchContract?: string;
|
|
92
|
+
/** Exclude contracts matching regex pattern */
|
|
93
|
+
noMatchContract?: string;
|
|
94
|
+
/** Run files matching glob pattern */
|
|
95
|
+
matchPath?: string;
|
|
96
|
+
/** Exclude files matching glob pattern */
|
|
97
|
+
noMatchPath?: string;
|
|
98
|
+
/** Exclude files from coverage report matching regex */
|
|
99
|
+
noMatchCoverage?: string;
|
|
100
|
+
|
|
101
|
+
// Display options
|
|
102
|
+
/** Verbosity level (1-5) - more v's = more verbose */
|
|
103
|
+
verbosity?: number;
|
|
104
|
+
/** Suppress log output */
|
|
105
|
+
quiet?: boolean;
|
|
106
|
+
/** Format output as JSON */
|
|
107
|
+
json?: boolean;
|
|
108
|
+
/** Format output as Markdown */
|
|
109
|
+
md?: boolean;
|
|
110
|
+
/** Color output mode */
|
|
111
|
+
color?: ColorMode;
|
|
112
|
+
|
|
113
|
+
// Test execution options
|
|
114
|
+
/** Number of threads to use for parallel execution */
|
|
115
|
+
threads?: number;
|
|
116
|
+
/** Number of fuzz test runs */
|
|
117
|
+
fuzzRuns?: number;
|
|
118
|
+
/** Seed for fuzz test randomness (for reproducibility) */
|
|
119
|
+
fuzzSeed?: string;
|
|
120
|
+
/** Stop running tests after first failure */
|
|
121
|
+
failFast?: boolean;
|
|
122
|
+
/** Exit with code 0 even if tests fail */
|
|
123
|
+
allowFailure?: boolean;
|
|
124
|
+
|
|
125
|
+
// EVM options
|
|
126
|
+
/** Fork URL for network state */
|
|
127
|
+
forkUrl?: string;
|
|
128
|
+
/** Fork from specific block number */
|
|
129
|
+
forkBlockNumber?: number;
|
|
130
|
+
/** Initial balance for deployed test contracts */
|
|
131
|
+
initialBalance?: string;
|
|
132
|
+
/** Address to use as test sender */
|
|
133
|
+
sender?: string;
|
|
134
|
+
/** Enable FFI (Foreign Function Interface) cheatcode */
|
|
135
|
+
ffi?: boolean;
|
|
136
|
+
|
|
137
|
+
// Build options
|
|
138
|
+
/** Force recompilation and cache clearing */
|
|
139
|
+
force?: boolean;
|
|
140
|
+
/** Disable compiler cache */
|
|
141
|
+
noCache?: boolean;
|
|
142
|
+
/** Enable Solidity optimizer */
|
|
143
|
+
optimize?: boolean;
|
|
144
|
+
/** Number of optimizer runs */
|
|
145
|
+
optimizerRuns?: number;
|
|
146
|
+
/** Use Yul intermediate representation compilation */
|
|
147
|
+
viaIr?: boolean;
|
|
148
|
+
}
|