hardhat 3.5.0 → 3.6.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/src/internal/builtin-plugins/flatten/task-action.d.ts.map +1 -1
  3. package/dist/src/internal/builtin-plugins/flatten/task-action.js +23 -7
  4. package/dist/src/internal/builtin-plugins/flatten/task-action.js.map +1 -1
  5. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +2 -5
  6. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  7. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +79 -12
  8. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts +6 -10
  10. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts.map +1 -1
  11. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js +54 -43
  12. package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js.map +1 -1
  13. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.d.ts.map +1 -1
  14. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js +2 -1
  15. package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js.map +1 -1
  16. package/dist/src/internal/builtin-plugins/solidity/tasks/build.d.ts.map +1 -1
  17. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js +1 -5
  18. package/dist/src/internal/builtin-plugins/solidity/tasks/build.js.map +1 -1
  19. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +53 -1
  20. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
  21. package/dist/src/internal/cli/error-handler.d.ts +11 -2
  22. package/dist/src/internal/cli/error-handler.d.ts.map +1 -1
  23. package/dist/src/internal/cli/error-handler.js +72 -46
  24. package/dist/src/internal/cli/error-handler.js.map +1 -1
  25. package/dist/src/internal/cli/help/utils.d.ts.map +1 -1
  26. package/dist/src/internal/cli/help/utils.js +3 -2
  27. package/dist/src/internal/cli/help/utils.js.map +1 -1
  28. package/dist/src/internal/cli/init/init.d.ts.map +1 -1
  29. package/dist/src/internal/cli/init/init.js +115 -4
  30. package/dist/src/internal/cli/init/init.js.map +1 -1
  31. package/dist/src/internal/cli/init/package-manager.d.ts +7 -1
  32. package/dist/src/internal/cli/init/package-manager.d.ts.map +1 -1
  33. package/dist/src/internal/cli/init/package-manager.js +14 -2
  34. package/dist/src/internal/cli/init/package-manager.js.map +1 -1
  35. package/dist/src/internal/cli/main.d.ts.map +1 -1
  36. package/dist/src/internal/cli/main.js +4 -2
  37. package/dist/src/internal/cli/main.js.map +1 -1
  38. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts +2 -1
  39. package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts.map +1 -1
  40. package/dist/src/internal/cli/telemetry/error-classification/classifier.js +2 -1
  41. package/dist/src/internal/cli/telemetry/error-classification/classifier.js.map +1 -1
  42. package/dist/src/internal/core/tasks/resolved-task.d.ts.map +1 -1
  43. package/dist/src/internal/core/tasks/resolved-task.js +3 -2
  44. package/dist/src/internal/core/tasks/resolved-task.js.map +1 -1
  45. package/dist/src/internal/core/tasks/utils.d.ts +3 -0
  46. package/dist/src/internal/core/tasks/utils.d.ts.map +1 -1
  47. package/dist/src/internal/core/tasks/utils.js +6 -0
  48. package/dist/src/internal/core/tasks/utils.js.map +1 -1
  49. package/dist/src/internal/core/tasks/validations.js +3 -3
  50. package/dist/src/internal/core/tasks/validations.js.map +1 -1
  51. package/dist/src/internal/using-hardhat2-plugin-errors.d.ts.map +1 -1
  52. package/dist/src/internal/using-hardhat2-plugin-errors.js +8 -11
  53. package/dist/src/internal/using-hardhat2-plugin-errors.js.map +1 -1
  54. package/dist/src/types/solidity/build-system.d.ts +22 -2
  55. package/dist/src/types/solidity/build-system.d.ts.map +1 -1
  56. package/dist/src/types/solidity/build-system.js.map +1 -1
  57. package/package.json +4 -3
  58. package/skills/hardhat/SKILL.md +152 -0
  59. package/skills/hardhat-toolbox-mocha-ethers/SKILL.md +119 -0
  60. package/skills/hardhat-toolbox-viem/SKILL.md +126 -0
  61. package/src/internal/builtin-plugins/flatten/task-action.ts +37 -7
  62. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +126 -21
  63. package/src/internal/builtin-plugins/solidity/build-system/warning-suppression.ts +81 -59
  64. package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +3 -2
  65. package/src/internal/builtin-plugins/solidity/tasks/build.ts +1 -6
  66. package/src/internal/builtin-plugins/solidity/type-extensions.ts +69 -0
  67. package/src/internal/cli/error-handler.ts +103 -72
  68. package/src/internal/cli/help/utils.ts +9 -2
  69. package/src/internal/cli/init/init.ts +141 -0
  70. package/src/internal/cli/init/package-manager.ts +18 -1
  71. package/src/internal/cli/main.ts +5 -3
  72. package/src/internal/cli/telemetry/error-classification/classifier.ts +2 -1
  73. package/src/internal/core/tasks/resolved-task.ts +5 -2
  74. package/src/internal/core/tasks/utils.ts +13 -0
  75. package/src/internal/core/tasks/validations.ts +3 -3
  76. package/src/internal/using-hardhat2-plugin-errors.ts +8 -11
  77. package/src/types/solidity/build-system.ts +24 -5
  78. package/templates/hardhat-3/01-node-test-runner-viem/AGENTS.md +20 -0
  79. package/templates/hardhat-3/01-node-test-runner-viem/package.json +3 -3
  80. package/templates/hardhat-3/02-mocha-ethers/AGENTS.md +20 -0
  81. package/templates/hardhat-3/02-mocha-ethers/package.json +3 -3
  82. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: hardhat-toolbox-viem
3
+ description: Use alongside the `hardhat` skill when the project depends on `@nomicfoundation/hardhat-toolbox-viem`. Covers the viem clients exposed on `network.create()`, contract interaction (`viem.deployContract`, `read`, `write`, `getContractAt`), and `viem.assertions` (revert / event / balance assertions).
4
+ metadata:
5
+ package: "@nomicfoundation/hardhat-toolbox-viem"
6
+ ---
7
+
8
+ # Hardhat toolbox: viem
9
+
10
+ This skill builds on the core **`hardhat`** skill. Load that first for test organization, the `network.create()` shape, `networkHelpers`, fixtures, and the typechecking workflow. Everything below hangs off the connection returned by `network.create()`:
11
+
12
+ ```ts
13
+ import { network } from "hardhat";
14
+ import { describe, it } from "node:test";
15
+ import assert from "node:assert/strict";
16
+
17
+ describe("Counter", async function () {
18
+ const { viem, networkHelpers } = await network.create();
19
+ // ...
20
+ });
21
+ ```
22
+
23
+ ## `viem`: clients and contract interaction
24
+
25
+ ```ts
26
+ // Clients
27
+ const publicClient = await viem.getPublicClient();
28
+ const [owner, alice, bob] = await viem.getWalletClients(); // default accounts
29
+ const testClient = await viem.getTestClient(); // dev-only operations
30
+
31
+ // Deploy a contract, returns a fully typed instance
32
+ const counter = await viem.deployContract("Counter");
33
+
34
+ // Read state (return type inferred from ABI)
35
+ const value = await counter.read.x();
36
+
37
+ // Write transactions. Args and options are type-checked against the ABI at
38
+ // compile time; passing wrong types, wrong argument counts, or `value` on a
39
+ // non-payable function is a TypeScript error
40
+ await counter.write.inc();
41
+ await counter.write.inc({ account: alice.account }); // different sender
42
+ await counter.write.incBy([3n]); // with args
43
+ await counter.write.deposit({ value: 10n ** 18n }); // no parameters and payable
44
+
45
+ // Attach to an already-deployed contract
46
+ const existing = await viem.getContractAt("Counter", "0xabc...");
47
+ ```
48
+
49
+ Avoid using `walletClient.writeContract` to interact with contracts — it has no ABI typing, so wrong args slip through. Prefer the typed instance returned by `viem.deployContract` or `viem.getContractAt`.
50
+
51
+ Inside a `loadFixture` setup function (see the `hardhat` skill for the surrounding pattern), `viem.deployContract` is the canonical deploy step:
52
+
53
+ ```ts
54
+ async function deployCounter() {
55
+ const counter = await viem.deployContract("Counter");
56
+ return { counter };
57
+ }
58
+
59
+ const { counter } = await networkHelpers.loadFixture(deployCounter);
60
+ ```
61
+
62
+ ## `viem.assertions`: Ethereum-specific assertions
63
+
64
+ Use `viem.assertions` for contract-specific checks. Pass the **unawaited** transaction promise as the first argument:
65
+
66
+ ```ts
67
+ // Reverts
68
+ await viem.assertions.revert(counter.write.inc({ account: banned }));
69
+ await viem.assertions.revertWith(
70
+ counter.write.inc({ account: banned }),
71
+ "Not authorized",
72
+ );
73
+ await viem.assertions.revertWithCustomError(
74
+ counter.write.inc({ account: banned }),
75
+ counter,
76
+ "Unauthorized",
77
+ );
78
+ await viem.assertions.revertWithCustomErrorWithArgs(
79
+ counter.write.inc({ account: banned }),
80
+ counter,
81
+ "Unauthorized",
82
+ [banned],
83
+ );
84
+
85
+ // Events
86
+ await viem.assertions.emit(counter.write.inc(), counter, "Increment");
87
+ await viem.assertions.emitWithArgs(counter.write.inc(), counter, "Increment", [
88
+ 1n,
89
+ ]);
90
+
91
+ // ETH balance changes (positive = received, negative = spent, before gas)
92
+ await viem.assertions.balancesHaveChanged(game.write.claim(), {
93
+ [winner]: PRIZE,
94
+ [loser]: -STAKE,
95
+ });
96
+ ```
97
+
98
+ The `*WithArgs` matchers (`revertWithCustomErrorWithArgs` and `emitWithArgs`) accept a `(value) => boolean` predicate at any arg position, alongside concrete values. The plugin also ships an `anyValue` helper for positions you don't care about:
99
+
100
+ ```ts
101
+ import { anyValue } from "@nomicfoundation/hardhat-toolbox-viem/predicates";
102
+
103
+ // Inline predicate at any arg position. Useful for ranges or computed conditions.
104
+ await viem.assertions.revertWithCustomErrorWithArgs(
105
+ contract.write.failing(),
106
+ contract,
107
+ "BadValue",
108
+ [(n: bigint) => n > 100n, "another error arg"],
109
+ );
110
+ await viem.assertions.emitWithArgs(
111
+ counter.write.incBy([3n]),
112
+ counter,
113
+ "Increment",
114
+ [(by: bigint) => by >= 1n],
115
+ );
116
+
117
+ // `anyValue` matches anything — handy for fields you don't care about.
118
+ await viem.assertions.revertWithCustomErrorWithArgs(
119
+ contract.write.failing(),
120
+ contract,
121
+ "BadValue",
122
+ [anyValue, "another error arg"],
123
+ );
124
+ ```
125
+
126
+ For plain TypeScript assertions (equality, arrays, types), use `node:assert/strict`.
@@ -2,6 +2,7 @@ import type { NewTaskActionFunction } from "../../../types/tasks.js";
2
2
 
3
3
  import { styleText } from "node:util";
4
4
 
5
+ import { HardhatError } from "@nomicfoundation/hardhat-errors";
5
6
  import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path";
6
7
 
7
8
  import {
@@ -261,7 +262,8 @@ function getPragmaAbicoderDirectiveInfo(
261
262
  };
262
263
  }
263
264
 
264
- // Returns files sorted in topological order
265
+ // Returns files sorted in topological order. Throws if the graph contains a
266
+ // cycle.
265
267
  function getSortedFiles(dependencyGraph: DependencyGraph): ResolvedFile[] {
266
268
  const sortedFiles: ResolvedFile[] = [];
267
269
  const visitedFiles = new Set<ResolvedFile>();
@@ -273,30 +275,58 @@ function getSortedFiles(dependencyGraph: DependencyGraph): ResolvedFile[] {
273
275
  );
274
276
  };
275
277
 
276
- // Depth-first walking
277
- const walk = (files: ResolvedFile[]) => {
278
+ // Depth-first walking. `importChain` is the chain of files leading to the
279
+ // current visit; a dep that reappears in it indicates a cycle.
280
+ const walk = (
281
+ files: ResolvedFile[],
282
+ importChain: readonly ResolvedFile[],
283
+ ) => {
278
284
  for (const file of files) {
279
- if (visitedFiles.has(file)) continue;
285
+ if (visitedFiles.has(file)) {
286
+ continue;
287
+ }
280
288
 
281
- visitedFiles.add(file);
289
+ throwIfCycle(file, importChain);
282
290
 
283
291
  const dependencies = sortBySourceName(
284
292
  Array.from(dependencyGraph.getDependencies(file)).map((d) => d.file),
285
293
  );
286
294
 
287
- walk(dependencies);
295
+ walk(dependencies, [...importChain, file]);
288
296
 
297
+ visitedFiles.add(file);
289
298
  sortedFiles.push(file);
290
299
  }
291
300
  };
292
301
 
293
302
  const roots = sortBySourceName(dependencyGraph.getRoots().values());
294
303
 
295
- walk(roots);
304
+ walk(roots, []);
296
305
 
297
306
  return sortedFiles;
298
307
  }
299
308
 
309
+ function throwIfCycle(
310
+ file: ResolvedFile,
311
+ importChain: readonly ResolvedFile[],
312
+ ): void {
313
+ const cycleStart = importChain.indexOf(file);
314
+
315
+ if (cycleStart === -1) {
316
+ return;
317
+ }
318
+
319
+ const cycle = [
320
+ ...importChain.slice(cycleStart).map(formatSourceName),
321
+ formatSourceName(file),
322
+ ].join(" -> ");
323
+
324
+ throw new HardhatError(
325
+ HardhatError.ERRORS.CORE.BUILTIN_TASKS.FLATTEN_CYCLIC_DEPENDENCY,
326
+ { cycle },
327
+ );
328
+ }
329
+
300
330
  function removeDuplicateAndSurroundingWhitespaces(str: string): string {
301
331
  return str.replace(/\s+/g, " ").trim();
302
332
  }
@@ -29,6 +29,7 @@ import type {
29
29
  CompilerOutputError,
30
30
  DependencyGraph,
31
31
  SolidityBuildInfo,
32
+ ResolvedBuildOptions,
32
33
  } from "../../../../types/solidity.js";
33
34
 
34
35
  import os from "node:os";
@@ -95,21 +96,6 @@ export function isSolcSolidityCompilerConfig(
95
96
  return config.type === undefined || config.type === "solc";
96
97
  }
97
98
 
98
- // Compiler warnings to suppress from build output.
99
- // Each rule specifies a warning message and the source file it applies to.
100
- // This allows suppressing known warnings from internal files (e.g., console.sol)
101
- // while still showing the same warning type from user code.
102
- export const SUPPRESSED_WARNINGS: Array<{
103
- message: string;
104
- sourceFile: string;
105
- }> = [
106
- {
107
- message:
108
- "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
109
- sourceFile: path.normalize("hardhat/console.sol"),
110
- },
111
- ];
112
-
113
99
  interface CompilationResult {
114
100
  compilationJob: CompilationJob;
115
101
  compilerOutput: CompilerOutput;
@@ -123,6 +109,7 @@ export interface SolidityBuildSystemOptions {
123
109
  readonly artifactsPath: string;
124
110
  readonly cachePath: string;
125
111
  readonly solidityTestsPath: string;
112
+ readonly coverage: boolean;
126
113
  }
127
114
 
128
115
  /**
@@ -291,13 +278,14 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
291
278
  rootFilePaths: string[],
292
279
  options?: BuildOptions,
293
280
  ): Promise<CompilationJobCreationError | Map<string, FileBuildResult>> {
294
- const resolvedOptions: Required<BuildOptions> = {
281
+ const resolvedOptions: ResolvedBuildOptions = {
295
282
  buildProfile: DEFAULT_BUILD_PROFILE,
296
283
  concurrency: Math.max(os.cpus().length - 1, 1),
297
284
  force: false,
298
285
  isolated: false,
299
286
  quiet: false,
300
287
  scope: "contracts",
288
+ cleanupArtifacts: false,
301
289
  ...options,
302
290
  };
303
291
 
@@ -382,6 +370,8 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
382
370
  ReadonlyMap<string, string[]>
383
371
  > = new Map();
384
372
 
373
+ let contractArtifactsAfterBuild: string[] | undefined;
374
+
385
375
  if (isSuccessfulBuild) {
386
376
  log("Emitting artifacts of successful build");
387
377
  await Promise.all(
@@ -411,6 +401,43 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
411
401
  );
412
402
 
413
403
  await saveCache(this.#options.cachePath, this.#compileCache);
404
+
405
+ if (resolvedOptions.cleanupArtifacts) {
406
+ const paths = await this.cleanupArtifacts(rootFilePaths, {
407
+ scope: resolvedOptions.scope,
408
+ });
409
+
410
+ if (resolvedOptions.scope !== "tests") {
411
+ contractArtifactsAfterBuild = paths;
412
+ }
413
+ }
414
+
415
+ if (resolvedOptions.scope !== "tests") {
416
+ if (
417
+ await this.#hooks.hasHandlers(
418
+ "solidity",
419
+ "processArtifactsAfterSuccessfulBuild",
420
+ )
421
+ ) {
422
+ contractArtifactsAfterBuild ??=
423
+ await this.#getCurrentContractArtifactPaths();
424
+
425
+ if (!this.#options.solidityConfig.splitTestsCompilation) {
426
+ // In unified mode the contracts artifacts directory also contains
427
+ // test artifacts, so we must filter them out before invoking the
428
+ // processArtifactsAfterSuccessfulBuild hook.
429
+ contractArtifactsAfterBuild = await this.#filterOutTestArtifacts(
430
+ contractArtifactsAfterBuild,
431
+ );
432
+ }
433
+
434
+ await this.#hooks.runSequentialHandlers(
435
+ "solidity",
436
+ "processArtifactsAfterSuccessfulBuild",
437
+ [contractArtifactsAfterBuild, rootFilePaths, resolvedOptions],
438
+ );
439
+ }
440
+ }
414
441
  }
415
442
 
416
443
  spinner.stop();
@@ -429,10 +456,16 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
429
456
  "We emitted contract artifacts for all the jobs if the build was successful",
430
457
  );
431
458
 
432
- const errors = await Promise.all(
433
- (result.compilerOutput.errors ?? []).map((error) =>
434
- this.remapCompilerError(result.compilationJob, error, true),
435
- ),
459
+ const errors = await this.#hooks.runHandlerChain(
460
+ "solidity",
461
+ "getCompilationJobErrors",
462
+ [result.compilationJob, result.compilerOutput],
463
+ async (_context, nextCompilationJob, nextCompilerOutput) =>
464
+ await Promise.all(
465
+ (nextCompilerOutput.errors ?? []).map((error) =>
466
+ this.remapCompilerError(nextCompilationJob, error, true),
467
+ ),
468
+ ),
436
469
  );
437
470
 
438
471
  this.#printSolcErrorsAndWarnings(errors);
@@ -1078,7 +1111,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
1078
1111
  public async cleanupArtifacts(
1079
1112
  rootFilePaths: string[],
1080
1113
  options: { scope?: BuildScope } = {},
1081
- ): Promise<void> {
1114
+ ): Promise<string[]> {
1082
1115
  const scope = options.scope ?? "contracts";
1083
1116
 
1084
1117
  this.#ensureSplitCompilationModeIfTestsScope(scope);
@@ -1205,6 +1238,77 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
1205
1238
  async () => {},
1206
1239
  );
1207
1240
  }
1241
+
1242
+ return artifactPaths;
1243
+ }
1244
+
1245
+ /**
1246
+ * Returns every `.json` artifact under the contracts artifacts directory,
1247
+ * excluding the project-wide top-level files (e.g. `artifacts.d.ts`) and
1248
+ * the `build-info/` subtree.
1249
+ *
1250
+ * In unified mode, the returned list also contains test artifacts. Callers
1251
+ * that need a contracts-only view should pass the result through
1252
+ * `#filterOutTestArtifacts`.
1253
+ */
1254
+ async #getCurrentContractArtifactPaths(): Promise<string[]> {
1255
+ const artifactsDirectory = await this.getArtifactsDirectory("contracts");
1256
+ const buildInfosDir = path.join(artifactsDirectory, `build-info`);
1257
+
1258
+ const allArtifactFiles = await getAllFilesMatching(
1259
+ artifactsDirectory,
1260
+ (p) => {
1261
+ // Ignore top level files (e.g. the project-wide artifacts.d.ts)
1262
+ if (
1263
+ p.indexOf(path.sep, artifactsDirectory.length + path.sep.length) ===
1264
+ -1
1265
+ ) {
1266
+ return false;
1267
+ }
1268
+ return p.endsWith(".json");
1269
+ },
1270
+ (dir) => dir !== buildInfosDir,
1271
+ );
1272
+
1273
+ return allArtifactFiles;
1274
+ }
1275
+
1276
+ /**
1277
+ * Returns `artifactPaths` with the test artifacts removed.
1278
+ *
1279
+ * This is only meaningful in unified mode, where contracts and tests share
1280
+ * the same artifacts directory. The caller is responsible for ensuring that
1281
+ * precondition.
1282
+ *
1283
+ * Each artifact's user source name is recovered from its path and classified
1284
+ * via `getScope`. Results are cached per source name to avoid repeating the
1285
+ * FS stat work for sibling artifacts. npm-style source names don't resolve
1286
+ * to a real path inside the project, so `getScope` falls back to
1287
+ * `"contracts"`, which is the intended outcome.
1288
+ */
1289
+ async #filterOutTestArtifacts(artifactPaths: string[]): Promise<string[]> {
1290
+ const artifactsDirectory = await this.getArtifactsDirectory("contracts");
1291
+ const scopeBySource = new Map<string, BuildScope>();
1292
+ const contractArtifactPaths: string[] = [];
1293
+
1294
+ for (const artifactPath of artifactPaths) {
1295
+ const userSourceName = toForwardSlash(
1296
+ path.relative(artifactsDirectory, path.dirname(artifactPath)),
1297
+ );
1298
+
1299
+ let scope = scopeBySource.get(userSourceName);
1300
+ if (scope === undefined) {
1301
+ const fsPath = path.resolve(this.#options.projectRoot, userSourceName);
1302
+ scope = await this.getScope(fsPath);
1303
+ scopeBySource.set(userSourceName, scope);
1304
+ }
1305
+
1306
+ if (scope === "contracts") {
1307
+ contractArtifactPaths.push(artifactPath);
1308
+ }
1309
+ }
1310
+
1311
+ return contractArtifactPaths;
1208
1312
  }
1209
1313
 
1210
1314
  public async compileBuildInfo(
@@ -1417,6 +1521,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
1417
1521
  msg,
1418
1522
  this.#options.solidityTestsPath,
1419
1523
  this.#options.projectRoot,
1524
+ this.#options.coverage,
1420
1525
  );
1421
1526
  }
1422
1527
 
@@ -1,97 +1,119 @@
1
1
  import path from "node:path";
2
2
 
3
- // Compiler warnings to suppress from build output.
4
- // Supports two types of suppression rules:
5
- //
6
- // 1. scope: 'specific-file' - Suppress warnings from specific file paths
7
- // - Use this to suppress known warnings from internal/library files (e.g., console.sol)
8
- // - The same warning type will still be shown for user code
9
- //
10
- // 2. scope: 'test-files' - Suppress warnings from all test files
11
- // - Test files are identified as:
12
- // * Files ending in .t.sol (e.g., Counter.t.sol)
13
- // * Files inside test/contracts/ directory
14
- // (e.g., test/contracts/Example.sol)
15
- // - Use this for warnings that are acceptable in test code but not in production code
16
- // (e.g., missing SPDX license identifiers or pragma statements)
17
- export const SUPPRESSED_WARNINGS: Array<
18
- | {
19
- message: string;
20
- scope: "specific-file";
21
- filePath: string;
22
- }
23
- | {
24
- message: string;
25
- scope: "test-files";
26
- }
27
- > = [
3
+ import { COVERAGE_LIBRARY_FILE_NAME } from "@nomicfoundation/edr";
4
+
5
+ export const NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING =
6
+ "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.";
7
+ export const SPDX_WARNING = "SPDX license identifier not provided";
8
+ export const PRAGMA_WARNING =
9
+ "Source file does not specify required compiler version";
10
+ const CONTRACT_SIZE_WARNING = "Contract code size is";
11
+
12
+ // Suppression rules are grouped by scope. Each array has its own match logic
13
+ // in `shouldSuppressWarning` function; add new entries to the array that fits, or add
14
+ // a new array + block for a new scope.
15
+
16
+ // Warnings tied to a specific internal file (e.g. console.sol). Suppressed
17
+ // only when the warning points at that file.
18
+ const SPECIFIC_FILE_RULES: ReadonlyArray<{
19
+ message: string;
20
+ filePath: string;
21
+ }> = [
28
22
  {
29
- message:
30
- "Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
31
- scope: "specific-file",
23
+ message: NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING,
32
24
  // Normalize to handle different OS path separators
33
25
  filePath: path.normalize("hardhat/console.sol"),
34
26
  },
27
+ ];
28
+
29
+ // Warnings acceptable in test files. Suppressed when the warning points at a
30
+ // file ending in `.t.sol` or inside the configured Solidity test directory.
31
+ const TEST_FILE_WARNING_MESSAGES: readonly string[] = [
32
+ SPDX_WARNING,
33
+ PRAGMA_WARNING,
34
+ ];
35
+
36
+ // Warnings suppressed only when running with `--coverage`. An entry with no
37
+ // `filePath` matches the message anywhere (e.g. contract-size warnings that
38
+ // fire on user files as a side effect of instrumentation); an entry with a
39
+ // `filePath` only matches when the diagnostic also points at that file
40
+ // (e.g. the injected coverage library file, which users can't edit).
41
+ const COVERAGE_MODE_RULES: ReadonlyArray<{
42
+ message: string;
43
+ filePath?: string;
44
+ }> = [
45
+ { message: CONTRACT_SIZE_WARNING },
35
46
  {
36
- message: "SPDX license identifier not provided",
37
- scope: "test-files",
38
- },
39
- {
40
- message: "Source file does not specify required compiler version",
41
- scope: "test-files",
47
+ message: NATSPEC_MEMORY_SAFE_ASSEMBLY_WARNING,
48
+ filePath: COVERAGE_LIBRARY_FILE_NAME,
42
49
  },
43
50
  ];
44
51
 
45
52
  /**
46
- * Determines if a compiler warning should be suppressed based on the suppression rules.
53
+ * Determines if a compiler warning should be suppressed.
47
54
  *
48
55
  * @param errorMessage - The formatted error message from the compiler
49
56
  * @param absoluteSolidityTestsPath - Absolute path to the Solidity test directory
50
57
  * @param absoluteProjectRoot - Absolute path to the project root
58
+ * @param coverage - Whether the build is running with `--coverage` enabled
51
59
  * @returns true if the warning should be suppressed, false otherwise
52
60
  */
53
61
  export function shouldSuppressWarning(
54
62
  errorMessage: string,
55
63
  absoluteSolidityTestsPath: string,
56
64
  absoluteProjectRoot: string,
65
+ coverage: boolean,
57
66
  ): boolean {
58
- // Compute relative path from project root to test directory.
59
- // Example:
60
- // absoluteSolidityTestsPath: /workspaces/hardhat-4/packages/example-project/test/contracts
61
- // absoluteProjectRoot: /workspaces/hardhat-4/packages/example-project
62
- // relativeTestPath: test/contracts/ - note the addition of the `/`
63
- // to avoid partial matches, e.g.: test/contractsUtils/
64
- const relativeTestPath = path.join(
65
- path.relative(absoluteProjectRoot, absoluteSolidityTestsPath),
66
- "/",
67
- );
68
-
69
- return SUPPRESSED_WARNINGS.some((rule) => {
70
- if (!errorMessage.includes(rule.message)) {
71
- return false;
72
- }
67
+ // Warnings suppressed only when running with `--coverage`.
68
+ if (
69
+ coverage &&
70
+ COVERAGE_MODE_RULES.some(
71
+ (rule) =>
72
+ errorMessage.includes(rule.message) &&
73
+ (rule.filePath === undefined || errorMessage.includes(rule.filePath)),
74
+ )
75
+ ) {
76
+ return true;
77
+ }
73
78
 
74
- if (rule.scope === "specific-file") {
75
- return errorMessage.includes(rule.filePath);
76
- }
79
+ // Warnings tied to a specific internal file (e.g. console.sol).
80
+ if (
81
+ SPECIFIC_FILE_RULES.some(
82
+ (rule) =>
83
+ errorMessage.includes(rule.message) &&
84
+ errorMessage.includes(rule.filePath),
85
+ )
86
+ ) {
87
+ return true;
88
+ }
77
89
 
78
- // Check if the message contains a path to a test file
90
+ // Warnings allowed in test files.
91
+ if (TEST_FILE_WARNING_MESSAGES.some((m) => errorMessage.includes(m))) {
79
92
  // Test files are identified by:
80
93
  // - Ending in .t.sol (e.g., Counter.t.sol)
81
94
  // - Being inside the configured Solidity test directory
82
-
83
95
  if (/\.t\.sol(:|$|\s)/.test(errorMessage)) {
84
96
  return true;
85
97
  }
86
98
 
87
- // Extract file path from error message
99
+ // Compute relative path from project root to test directory.
100
+ // Example:
101
+ // absoluteSolidityTestsPath: /workspaces/hardhat-4/packages/example-project/test/contracts
102
+ // absoluteProjectRoot: /workspaces/hardhat-4/packages/example-project
103
+ // relativeTestPath: test/contracts/ - note the addition of the `/`
104
+ // to avoid partial matches, e.g.: test/contractsUtils/
105
+ const relativeTestPath = path.join(
106
+ path.relative(absoluteProjectRoot, absoluteSolidityTestsPath),
107
+ "/",
108
+ );
109
+
110
+ // Extract file path from error message.
88
111
  // Format: "Warning: message\n --> path/to/file.sol:line:column:"
89
112
  const pathMatches = errorMessage.match(/-->\s+([^\s:]+\.sol)/);
90
-
91
113
  if (pathMatches !== null) {
92
114
  return pathMatches[1].includes(relativeTestPath);
93
115
  }
116
+ }
94
117
 
95
- return false;
96
- });
118
+ return false;
97
119
  }
@@ -114,7 +114,7 @@ class LazySolidityBuildSystem implements SolidityBuildSystem {
114
114
  public async cleanupArtifacts(
115
115
  rootFilePaths: string[],
116
116
  options: { scope?: BuildScope } = {},
117
- ): Promise<void> {
117
+ ): Promise<string[]> {
118
118
  const buildSystem = await this.#getBuildSystem();
119
119
  return await buildSystem.cleanupArtifacts(rootFilePaths, options);
120
120
  }
@@ -152,7 +152,7 @@ class LazySolidityBuildSystem implements SolidityBuildSystem {
152
152
 
153
153
  export default async (): Promise<Partial<HardhatRuntimeEnvironmentHooks>> => {
154
154
  return {
155
- created: async (_context, hre) => {
155
+ created: async (context, hre) => {
156
156
  hre.solidity = new LazySolidityBuildSystem(hre.hooks, {
157
157
  solidityConfig: hre.config.solidity,
158
158
  projectRoot: hre.config.paths.root,
@@ -160,6 +160,7 @@ export default async (): Promise<Partial<HardhatRuntimeEnvironmentHooks>> => {
160
160
  artifactsPath: hre.config.paths.artifacts,
161
161
  cachePath: hre.config.paths.cache,
162
162
  solidityTestsPath: hre.config.paths.tests.solidity,
163
+ coverage: context.globalOptions.coverage,
163
164
  });
164
165
  },
165
166
  };
@@ -186,6 +186,7 @@ async function runSolidityBuild({
186
186
  buildProfile,
187
187
  quiet,
188
188
  scope,
189
+ cleanupArtifacts: isFullBuild,
189
190
  },
190
191
  );
191
192
 
@@ -194,12 +195,6 @@ async function runSolidityBuild({
194
195
  // We use the result keys in case a hook added or removed root files
195
196
  const builtRootPaths = [...results.keys()];
196
197
 
197
- if (isFullBuild) {
198
- await solidity.cleanupArtifacts(builtRootPaths, {
199
- scope,
200
- });
201
- }
202
-
203
198
  const preBuildRoots = new Set([...contractRootPaths, ...testRootPaths]);
204
199
  if (
205
200
  builtRootPaths.length === preBuildRoots.size &&