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.
- package/CHANGELOG.md +38 -0
- package/dist/src/internal/builtin-plugins/flatten/task-action.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/flatten/task-action.js +23 -7
- package/dist/src/internal/builtin-plugins/flatten/task-action.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +2 -5
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +79 -12
- package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts +6 -10
- package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js +54 -43
- package/dist/src/internal/builtin-plugins/solidity/build-system/warning-suppression.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js +2 -1
- package/dist/src/internal/builtin-plugins/solidity/hook-handlers/hre.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.js +1 -5
- package/dist/src/internal/builtin-plugins/solidity/tasks/build.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +53 -1
- package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
- package/dist/src/internal/cli/error-handler.d.ts +11 -2
- package/dist/src/internal/cli/error-handler.d.ts.map +1 -1
- package/dist/src/internal/cli/error-handler.js +72 -46
- package/dist/src/internal/cli/error-handler.js.map +1 -1
- package/dist/src/internal/cli/help/utils.d.ts.map +1 -1
- package/dist/src/internal/cli/help/utils.js +3 -2
- package/dist/src/internal/cli/help/utils.js.map +1 -1
- package/dist/src/internal/cli/init/init.d.ts.map +1 -1
- package/dist/src/internal/cli/init/init.js +115 -4
- package/dist/src/internal/cli/init/init.js.map +1 -1
- package/dist/src/internal/cli/init/package-manager.d.ts +7 -1
- package/dist/src/internal/cli/init/package-manager.d.ts.map +1 -1
- package/dist/src/internal/cli/init/package-manager.js +14 -2
- package/dist/src/internal/cli/init/package-manager.js.map +1 -1
- package/dist/src/internal/cli/main.d.ts.map +1 -1
- package/dist/src/internal/cli/main.js +4 -2
- package/dist/src/internal/cli/main.js.map +1 -1
- package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts +2 -1
- package/dist/src/internal/cli/telemetry/error-classification/classifier.d.ts.map +1 -1
- package/dist/src/internal/cli/telemetry/error-classification/classifier.js +2 -1
- package/dist/src/internal/cli/telemetry/error-classification/classifier.js.map +1 -1
- package/dist/src/internal/core/tasks/resolved-task.d.ts.map +1 -1
- package/dist/src/internal/core/tasks/resolved-task.js +3 -2
- package/dist/src/internal/core/tasks/resolved-task.js.map +1 -1
- package/dist/src/internal/core/tasks/utils.d.ts +3 -0
- package/dist/src/internal/core/tasks/utils.d.ts.map +1 -1
- package/dist/src/internal/core/tasks/utils.js +6 -0
- package/dist/src/internal/core/tasks/utils.js.map +1 -1
- package/dist/src/internal/core/tasks/validations.js +3 -3
- package/dist/src/internal/core/tasks/validations.js.map +1 -1
- package/dist/src/internal/using-hardhat2-plugin-errors.d.ts.map +1 -1
- package/dist/src/internal/using-hardhat2-plugin-errors.js +8 -11
- package/dist/src/internal/using-hardhat2-plugin-errors.js.map +1 -1
- package/dist/src/types/solidity/build-system.d.ts +22 -2
- package/dist/src/types/solidity/build-system.d.ts.map +1 -1
- package/dist/src/types/solidity/build-system.js.map +1 -1
- package/package.json +4 -3
- package/skills/hardhat/SKILL.md +152 -0
- package/skills/hardhat-toolbox-mocha-ethers/SKILL.md +119 -0
- package/skills/hardhat-toolbox-viem/SKILL.md +126 -0
- package/src/internal/builtin-plugins/flatten/task-action.ts +37 -7
- package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +126 -21
- package/src/internal/builtin-plugins/solidity/build-system/warning-suppression.ts +81 -59
- package/src/internal/builtin-plugins/solidity/hook-handlers/hre.ts +3 -2
- package/src/internal/builtin-plugins/solidity/tasks/build.ts +1 -6
- package/src/internal/builtin-plugins/solidity/type-extensions.ts +69 -0
- package/src/internal/cli/error-handler.ts +103 -72
- package/src/internal/cli/help/utils.ts +9 -2
- package/src/internal/cli/init/init.ts +141 -0
- package/src/internal/cli/init/package-manager.ts +18 -1
- package/src/internal/cli/main.ts +5 -3
- package/src/internal/cli/telemetry/error-classification/classifier.ts +2 -1
- package/src/internal/core/tasks/resolved-task.ts +5 -2
- package/src/internal/core/tasks/utils.ts +13 -0
- package/src/internal/core/tasks/validations.ts +3 -3
- package/src/internal/using-hardhat2-plugin-errors.ts +8 -11
- package/src/types/solidity/build-system.ts +24 -5
- package/templates/hardhat-3/01-node-test-runner-viem/AGENTS.md +20 -0
- package/templates/hardhat-3/01-node-test-runner-viem/package.json +3 -3
- package/templates/hardhat-3/02-mocha-ethers/AGENTS.md +20 -0
- package/templates/hardhat-3/02-mocha-ethers/package.json +3 -3
- 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
|
-
|
|
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))
|
|
285
|
+
if (visitedFiles.has(file)) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
280
288
|
|
|
281
|
-
|
|
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:
|
|
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
|
|
433
|
-
|
|
434
|
-
|
|
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<
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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:
|
|
37
|
-
|
|
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
|
|
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
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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<
|
|
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 (
|
|
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 &&
|