hardhat 3.9.1 → 3.11.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 (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.js +1 -1
  3. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.d.ts.map +1 -1
  4. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js +2 -0
  5. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js.map +1 -1
  6. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/solidity-stack-trace.d.ts +2 -2
  7. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/solidity-stack-trace.d.ts.map +1 -1
  8. package/dist/src/internal/builtin-plugins/network-manager/edr/stack-traces/solidity-stack-trace.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/network-manager/edr/types/hardfork.d.ts +17 -1
  10. package/dist/src/internal/builtin-plugins/network-manager/edr/types/hardfork.d.ts.map +1 -1
  11. package/dist/src/internal/builtin-plugins/network-manager/edr/types/hardfork.js +19 -2
  12. package/dist/src/internal/builtin-plugins/network-manager/edr/types/hardfork.js.map +1 -1
  13. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts.map +1 -1
  14. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js +5 -1
  15. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js.map +1 -1
  16. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/hardfork.d.ts +8 -0
  17. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/hardfork.d.ts.map +1 -1
  18. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/hardfork.js +30 -1
  19. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/hardfork.js.map +1 -1
  20. package/dist/src/internal/builtin-plugins/node/task-action.d.ts.map +1 -1
  21. package/dist/src/internal/builtin-plugins/node/task-action.js +2 -37
  22. package/dist/src/internal/builtin-plugins/node/task-action.js.map +1 -1
  23. package/dist/src/internal/builtin-plugins/node/utils/resolve-node-connection-params.d.ts +19 -0
  24. package/dist/src/internal/builtin-plugins/node/utils/resolve-node-connection-params.d.ts.map +1 -0
  25. package/dist/src/internal/builtin-plugins/node/utils/resolve-node-connection-params.js +47 -0
  26. package/dist/src/internal/builtin-plugins/node/utils/resolve-node-connection-params.js.map +1 -0
  27. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  28. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +34 -7
  29. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  30. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts +1 -0
  31. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts.map +1 -1
  32. package/dist/src/internal/builtin-plugins/solidity-test/config.js +31 -5
  33. package/dist/src/internal/builtin-plugins/solidity-test/config.js.map +1 -1
  34. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts +9 -3
  35. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts.map +1 -1
  36. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js +60 -27
  37. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js.map +1 -1
  38. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.d.ts +1 -1
  39. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.d.ts.map +1 -1
  40. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.js +19 -10
  41. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.js.map +1 -1
  42. package/dist/src/internal/builtin-plugins/solidity-test/helpers.d.ts.map +1 -1
  43. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js +4 -1
  44. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js.map +1 -1
  45. package/dist/src/internal/builtin-plugins/solidity-test/inline-config/index.d.ts +1 -1
  46. package/dist/src/internal/builtin-plugins/solidity-test/inline-config/index.d.ts.map +1 -1
  47. package/dist/src/internal/builtin-plugins/solidity-test/inline-config/index.js +13 -5
  48. package/dist/src/internal/builtin-plugins/solidity-test/inline-config/index.js.map +1 -1
  49. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js +2 -2
  50. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js.map +1 -1
  51. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts +6 -0
  52. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts.map +1 -1
  53. package/package.json +4 -4
  54. package/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts +1 -1
  55. package/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +3 -0
  56. package/src/internal/builtin-plugins/network-manager/edr/stack-traces/solidity-stack-trace.ts +1 -1
  57. package/src/internal/builtin-plugins/network-manager/edr/types/hardfork.ts +20 -3
  58. package/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +5 -0
  59. package/src/internal/builtin-plugins/network-manager/edr/utils/hardfork.ts +53 -1
  60. package/src/internal/builtin-plugins/node/task-action.ts +2 -52
  61. package/src/internal/builtin-plugins/node/utils/resolve-node-connection-params.ts +79 -0
  62. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +51 -8
  63. package/src/internal/builtin-plugins/solidity-test/config.ts +52 -5
  64. package/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.ts +64 -25
  65. package/src/internal/builtin-plugins/solidity-test/eip712/index.ts +23 -13
  66. package/src/internal/builtin-plugins/solidity-test/helpers.ts +5 -1
  67. package/src/internal/builtin-plugins/solidity-test/inline-config/index.ts +20 -9
  68. package/src/internal/builtin-plugins/solidity-test/task-action.ts +2 -2
  69. package/src/internal/builtin-plugins/solidity-test/type-extensions.ts +6 -0
  70. package/templates/hardhat-3/01-node-test-runner-viem/package.json +1 -1
  71. package/templates/hardhat-3/02-mocha-ethers/package.json +3 -3
  72. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ import type { EdrNetworkConfigOverride } from "../../../../types/config.js";
2
+ import type { ChainType } from "../../../../types/network.js";
3
+
4
+ import { HardhatError } from "@nomicfoundation/hardhat-errors";
5
+
6
+ import { isSupportedChainType } from "../../../edr/chain-type.js";
7
+
8
+ export interface NodeConnectionArguments {
9
+ chainType?: string;
10
+ chainId: number;
11
+ fork?: string;
12
+ forkBlockNumber: number;
13
+ }
14
+
15
+ export interface ResolvedNodeConnectionParams {
16
+ network: string;
17
+ chainType?: ChainType;
18
+ override?: EdrNetworkConfigOverride;
19
+ }
20
+
21
+ /**
22
+ * Resolves the arguments passed to the `node` task into the connection
23
+ * parameters used to create the underlying network connection.
24
+ */
25
+ export function resolveNodeConnectionParams(
26
+ network: string,
27
+ args: NodeConnectionArguments,
28
+ ): ResolvedNodeConnectionParams {
29
+ const connectionParams: ResolvedNodeConnectionParams = {
30
+ network,
31
+ };
32
+
33
+ // NOTE: We create an empty network config override here. We add to it based
34
+ // on the result of arguments parsing. We can expand the list of arguments
35
+ // as much as needed.
36
+ const networkConfigOverride: EdrNetworkConfigOverride = {};
37
+
38
+ if (args.chainType !== undefined) {
39
+ if (!isSupportedChainType(args.chainType)) {
40
+ throw new HardhatError(
41
+ HardhatError.ERRORS.CORE.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
42
+ {
43
+ value: args.chainType,
44
+ type: "ChainType",
45
+ name: "chainType",
46
+ },
47
+ );
48
+ }
49
+
50
+ connectionParams.chainType = args.chainType;
51
+ }
52
+
53
+ if (args.chainId !== -1) {
54
+ networkConfigOverride.chainId = args.chainId;
55
+ }
56
+
57
+ // NOTE: --fork-block-number is only valid if --fork is specified
58
+ if (args.fork !== undefined) {
59
+ networkConfigOverride.forking = {
60
+ enabled: true,
61
+ url: args.fork,
62
+ ...(args.forkBlockNumber !== -1
63
+ ? { blockNumber: args.forkBlockNumber }
64
+ : undefined),
65
+ };
66
+ } else if (args.forkBlockNumber !== -1) {
67
+ // NOTE: We could make the error more specific here.
68
+ throw new HardhatError(
69
+ HardhatError.ERRORS.CORE.ARGUMENTS.MISSING_VALUE_FOR_ARGUMENT,
70
+ {
71
+ argument: "fork",
72
+ },
73
+ );
74
+ }
75
+
76
+ connectionParams.override = networkConfigOverride;
77
+
78
+ return connectionParams;
79
+ }
@@ -1327,20 +1327,63 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
1327
1327
  options?: CompileBuildInfoOptions,
1328
1328
  ): Promise<CompilerOutput> {
1329
1329
  const quiet = options?.quiet ?? false;
1330
+ const compilerType = buildInfo.compilerType ?? "solc";
1330
1331
 
1331
- // Build info recompilation is always solc-only: build info files are
1332
- // produced by solc and must be recompiled with the same solc version.
1333
- // We bypass both downloadCompilers and getCompiler hooks this is a
1334
- // self-contained solc replay path, not plugin-configurable compilation.
1335
- await downloadSolcCompilers(new Set([buildInfo.solcVersion]), quiet);
1332
+ if (compilerType === "solc") {
1333
+ // Build info recompilation for solc is self-contained: solc build info
1334
+ // files must be replayed with the same solc version. We bypass both
1335
+ // downloadCompilers and getCompiler hooks to preserve the historical solc
1336
+ // path and avoid making it depend on the current project config.
1337
+ await downloadSolcCompilers(new Set([buildInfo.solcVersion]), quiet);
1336
1338
 
1337
- const compiler = await getCompiler(buildInfo.solcVersion, {
1338
- preferWasm: false,
1339
- });
1339
+ const solcCompiler = await getCompiler(buildInfo.solcVersion, {
1340
+ preferWasm: false,
1341
+ });
1342
+
1343
+ return await solcCompiler.compile(buildInfo.input);
1344
+ }
1345
+
1346
+ const partialCompilerConfig =
1347
+ this.#getPartialSolidityCompilerConfigFromBuildInfo(buildInfo);
1348
+
1349
+ await this.#hooks.runParallelHandlers("solidity", "downloadCompilers", [
1350
+ [partialCompilerConfig],
1351
+ quiet,
1352
+ ]);
1353
+
1354
+ const compiler = await this.#hooks.runHandlerChain(
1355
+ "solidity",
1356
+ "getCompiler",
1357
+ [partialCompilerConfig],
1358
+ async () => {
1359
+ throw new HardhatError(
1360
+ HardhatError.ERRORS.CORE.SOLIDITY.BUILD_INFO_COMPILER_TYPE_NOT_HANDLED,
1361
+ {
1362
+ compilerType,
1363
+ version: buildInfo.solcVersion,
1364
+ },
1365
+ );
1366
+ },
1367
+ );
1340
1368
 
1341
1369
  return await compiler.compile(buildInfo.input);
1342
1370
  }
1343
1371
 
1372
+ #getPartialSolidityCompilerConfigFromBuildInfo(
1373
+ buildInfo: SolidityBuildInfo,
1374
+ ): SolidityCompilerConfig {
1375
+ return {
1376
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
1377
+ Build infos may come from setups whose compiler type is unknown to the
1378
+ current Hardhat type definitions, but plugins can still handle them via
1379
+ the compiler hooks. */
1380
+ type: (buildInfo.compilerType ??
1381
+ "solc") as SolidityCompilerConfig["type"],
1382
+ version: buildInfo.solcVersion,
1383
+ settings: buildInfo.input.settings,
1384
+ };
1385
+ }
1386
+
1344
1387
  async #downloadConfiguredCompilers(quiet = false): Promise<void> {
1345
1388
  // We always print that we are downloading the compilers
1346
1389
  quiet = false;
@@ -32,6 +32,22 @@ import { DEFAULT_TEST_PROFILE } from "./test-profiles.js";
32
32
  export const DEFAULT_FUZZ_SEED =
33
33
  "0x7727ea51af0441c20da14dcd68a15dac8c9ebd589c5be8fa8c87c1d3720450bc";
34
34
 
35
+ const nonnegativeSafeIntOrBigInt = unionType(
36
+ [z.number().int().nonnegative().safe(), z.bigint().nonnegative()],
37
+ "Expected a nonnegative safe int or a nonnegative bigint",
38
+ );
39
+
40
+ const nonnegativeSafeIntOrSafeBigInt = unionType(
41
+ [
42
+ z.number().int().nonnegative().safe(),
43
+ z
44
+ .bigint()
45
+ .nonnegative()
46
+ .refine((value) => value <= BigInt(Number.MAX_SAFE_INTEGER)),
47
+ ],
48
+ "Expected a nonnegative safe int or a nonnegative safe bigint",
49
+ );
50
+
35
51
  const solidityTestProfileUserConfigType = z.object({
36
52
  fsPermissions: z
37
53
  .object({
@@ -54,6 +70,7 @@ const solidityTestProfileUserConfigType = z.object({
54
70
  blockTimestamp: z.bigint().optional(),
55
71
  prevRandao: z.bigint().optional(),
56
72
  gasLimit: z.bigint().optional(),
73
+ memoryLimit: nonnegativeSafeIntOrBigInt.optional(),
57
74
  blockGasLimit: z.number().or(z.bigint()).or(z.literal(false)).optional(),
58
75
  transactionGasCap: z.number().or(z.bigint()).or(z.literal(false)).optional(),
59
76
  fuzz: z
@@ -67,6 +84,7 @@ const solidityTestProfileUserConfigType = z.object({
67
84
  includeStorage: z.boolean().optional(),
68
85
  includePushBytes: z.boolean().optional(),
69
86
  showLogs: z.boolean().optional(),
87
+ timeout: nonnegativeSafeIntOrSafeBigInt.optional(),
70
88
  })
71
89
  .optional(),
72
90
  forking: z
@@ -92,6 +110,7 @@ const solidityTestProfileUserConfigType = z.object({
92
110
  includeStorage: z.boolean().optional(),
93
111
  includePushBytes: z.boolean().optional(),
94
112
  shrinkRunLimit: z.number().optional(),
113
+ timeout: nonnegativeSafeIntOrSafeBigInt.optional(),
95
114
  })
96
115
  .optional(),
97
116
  eip712Types: z
@@ -222,12 +241,22 @@ export async function resolveSolidityTestUserConfig(
222
241
  resolveConfigurationVariable,
223
242
  );
224
243
 
244
+ const {
245
+ memoryLimit,
246
+ fuzz,
247
+ invariant,
248
+ eip712Types,
249
+ ...otherProfileUserConfig
250
+ } = profileUserConfig ?? {};
251
+
225
252
  const resolvedDefaultProfile = {
226
253
  rpcCachePath: defaultRpcCachePath,
227
- ...profileUserConfig,
228
- fuzz: resolveFuzzConfig(profileUserConfig?.fuzz),
254
+ ...otherProfileUserConfig,
255
+ memoryLimit: memoryLimit !== undefined ? BigInt(memoryLimit) : undefined,
256
+ fuzz: resolveFuzzConfig(fuzz),
257
+ invariant: resolveInvariantConfig(invariant),
229
258
  forking: resolvedForking,
230
- eip712Types: resolveEip712TypesConfig(profileUserConfig?.eip712Types),
259
+ eip712Types: resolveEip712TypesConfig(eip712Types),
231
260
  };
232
261
 
233
262
  return {
@@ -251,9 +280,27 @@ export async function resolveSolidityTestUserConfig(
251
280
  export function resolveFuzzConfig(
252
281
  fuzzUserConfig: SolidityTestProfileUserConfig["fuzz"] = {},
253
282
  ): SolidityTestProfileConfig["fuzz"] {
283
+ const { seed, timeout, ...otherFuzzUserConfig } = fuzzUserConfig;
284
+
285
+ return {
286
+ ...otherFuzzUserConfig,
287
+ seed: seed ?? DEFAULT_FUZZ_SEED,
288
+ ...(timeout !== undefined ? { timeout: Number(timeout) } : {}),
289
+ };
290
+ }
291
+
292
+ export function resolveInvariantConfig(
293
+ invariantUserConfig: SolidityTestProfileUserConfig["invariant"],
294
+ ): SolidityTestProfileConfig["invariant"] {
295
+ if (invariantUserConfig === undefined) {
296
+ return undefined;
297
+ }
298
+
299
+ const { timeout, ...otherInvariantUserConfig } = invariantUserConfig;
300
+
254
301
  return {
255
- ...fuzzUserConfig,
256
- seed: fuzzUserConfig.seed ?? DEFAULT_FUZZ_SEED,
302
+ ...otherInvariantUserConfig,
303
+ ...(timeout !== undefined ? { timeout: Number(timeout) } : {}),
257
304
  };
258
305
  }
259
306
 
@@ -2,6 +2,12 @@ import type { CollectedStruct } from "./ast-walker.js";
2
2
 
3
3
  import { HardhatError } from "@nomicfoundation/hardhat-errors";
4
4
 
5
+ export const SELECTED_CONFLICT_REMEDIATION =
6
+ "Rename one of the structs, or scope your `test.solidity.eip712Types.include` / `exclude` globs in `hardhat.config.ts` so that only one of them is selected.";
7
+
8
+ export const DEPENDENCY_CONFLICT_REMEDIATION =
9
+ "Rename one of the structs so the name has a single definition. The clashing name is pulled in as a dependency of a selected struct, so if you don't need that selected struct, deselecting it with your `test.solidity.eip712Types.include` / `exclude` globs in `hardhat.config.ts` also resolves the conflict.";
10
+
5
11
  /**
6
12
  * Produces the flat list of canonical EIP-712 type strings expected by EDR.
7
13
  *
@@ -25,14 +31,28 @@ import { HardhatError } from "@nomicfoundation/hardhat-errors";
25
31
  * constructs and propagates `None` through the dep graph so dependents are
26
32
  * also dropped.
27
33
  *
28
- * Only names in `selectedNames` are emitted; non-selected structs still
29
- * participate in dep resolution so cross-file deps inline correctly.
34
+ * Selection is scoped by source, not by struct name: `selectedSources` holds
35
+ * the project-relative paths matched by the user's include/exclude globs. Only
36
+ * structs from those sources are emitted, but structs elsewhere still feed dep
37
+ * resolution so cross-file deps inline correctly. Scoping by source is what
38
+ * lets a non-selected file define a same-named struct without it being mistaken
39
+ * for selected.
30
40
  */
31
41
  export function canonicalizeStructs(
32
42
  structs: CollectedStruct[],
33
- selectedNames: Set<string>,
43
+ selectedSources: Set<string>,
34
44
  ): string[] {
35
- const byName = indexByName(structs, selectedNames);
45
+ // A name is "selected" if any of its definitions lives in a selected source.
46
+ // Drives what's emitted and, in `indexByName`, whether a selected definition
47
+ // exists to win a name collision.
48
+ const selectedNames = new Set<string>();
49
+ for (const struct of structs) {
50
+ if (selectedSources.has(struct.sourcePath)) {
51
+ selectedNames.add(struct.name);
52
+ }
53
+ }
54
+
55
+ const byName = indexByName(structs, selectedSources, selectedNames);
36
56
  const knownNames = new Set(byName.keys());
37
57
  const encodable = computeEncodable(byName, knownNames);
38
58
  const result: string[] = [];
@@ -189,15 +209,27 @@ function transitiveDeps(
189
209
  * non decodable definition silently win over an encodable one, dropping the
190
210
  * struct from the canonical output.
191
211
  *
192
- * Conflicts on a name reachable from any selected struct (selected roots plus
193
- * their transitive deps) throw, since the selected struct's inlined dep head
194
- * would otherwise depend on which conflicting copy happened to be seen first.
195
- * Conflicts on names truly unreachable from the selected set are silently
196
- * deduped (first wins). Selected structs are processed first so they win over
197
- * non-selected copies.
212
+ * A name clash aborts the run only when it could change the output; otherwise
213
+ * the first definition wins silently. Selection is scoped by source
214
+ * (`selectedSources`), not by name, so a non-selected source whose struct name
215
+ * collides with a selected one stays non-selected. `selectedNames` is the
216
+ * derived set of names with at least one selected definition. Selected structs
217
+ * are processed first, so the already-stored definition in any collision is
218
+ * selected when one exists. Two rules resolve conflicts:
219
+ *
220
+ * - Both sides selected: throw immediately — genuinely ambiguous which to
221
+ * emit.
222
+ * - Current side non-selected: defer; the stored definition keeps the name.
223
+ * Throw only if the name is reachable from a selected struct (its
224
+ * transitive deps), since then which copy got inlined would depend on
225
+ * iteration order. Names unreachable from the selected set are silently
226
+ * deduped (first wins), so a non-selected struct that merely shares a
227
+ * name with a selected root or an unreferenced struct never aborts the
228
+ * run.
198
229
  */
199
230
  function indexByName(
200
231
  structs: CollectedStruct[],
232
+ selectedSources: Set<string>,
201
233
  selectedNames: Set<string>,
202
234
  ): Map<string, CollectedStruct> {
203
235
  const byName = new Map<string, CollectedStruct>();
@@ -209,8 +241,8 @@ function indexByName(
209
241
  >();
210
242
 
211
243
  const ordered = [
212
- ...structs.filter((s) => selectedNames.has(s.name)),
213
- ...structs.filter((s) => !selectedNames.has(s.name)),
244
+ ...structs.filter((s) => selectedSources.has(s.sourcePath)),
245
+ ...structs.filter((s) => !selectedSources.has(s.sourcePath)),
214
246
  ];
215
247
 
216
248
  for (const struct of ordered) {
@@ -228,24 +260,31 @@ function indexByName(
228
260
  continue;
229
261
  }
230
262
 
231
- if (!selectedNames.has(struct.name)) {
232
- if (!deferredConflicts.has(struct.name)) {
233
- deferredConflicts.set(struct.name, {
263
+ if (selectedSources.has(struct.sourcePath)) {
264
+ // Selected-vs-selected conflict: both this and the stored definition are
265
+ // selected (selected structs come first), so it's ambiguous which to emit.
266
+ throw new HardhatError(
267
+ HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
268
+ {
269
+ name: struct.name,
234
270
  firstSource: sourceByName.get(struct.name) ?? "<unknown>",
235
271
  secondSource: struct.sourcePath,
236
- });
237
- }
238
- continue;
272
+ remediation: SELECTED_CONFLICT_REMEDIATION,
273
+ },
274
+ );
239
275
  }
240
276
 
241
- throw new HardhatError(
242
- HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
243
- {
244
- name: struct.name,
277
+ // Non-selected side, so the stored definition (selected if the name has
278
+ // one, since selected sources come first) keeps the name. Defer: throw
279
+ // later only if the name is reachable from a selected struct. Sharing a
280
+ // name with a selected root or an unreferenced struct is harmless and
281
+ // must not abort.
282
+ if (!deferredConflicts.has(struct.name)) {
283
+ deferredConflicts.set(struct.name, {
245
284
  firstSource: sourceByName.get(struct.name) ?? "<unknown>",
246
285
  secondSource: struct.sourcePath,
247
- },
248
- );
286
+ });
287
+ }
249
288
  }
250
289
 
251
290
  if (deferredConflicts.size > 0) {
@@ -254,7 +293,7 @@ function indexByName(
254
293
  if (reachable.has(name)) {
255
294
  throw new HardhatError(
256
295
  HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
257
- { name, ...sources },
296
+ { name, ...sources, remediation: DEPENDENCY_CONFLICT_REMEDIATION },
258
297
  );
259
298
  }
260
299
  }
@@ -2,10 +2,12 @@ import type { CollectedStruct } from "./ast-walker.js";
2
2
  import type { SolidityBuildInfoOutput } from "../../../../types/solidity/solidity-artifacts.js";
3
3
  import type { BuildInfoAndOutput } from "../edr-artifacts.js";
4
4
 
5
+ import { HardhatError } from "@nomicfoundation/hardhat-errors";
5
6
  import {
6
7
  bytesIncludesUtf8String,
7
- bytesToUtf8String,
8
+ parseJsonBytes,
8
9
  } from "@nomicfoundation/hardhat-utils/bytes";
10
+ import { ensureError } from "@nomicfoundation/hardhat-utils/error";
9
11
 
10
12
  import { isPathSelected } from "../../../utils/glob.js";
11
13
  import { toUserSourceName } from "../../solidity/source-names.js";
@@ -35,11 +37,11 @@ export interface Eip712TypesConfig {
35
37
  * When `include` is empty/unset the feature is off: collection short-circuits
36
38
  * and returns an empty list without parsing any build info.
37
39
  */
38
- export function collectEip712CanonicalTypes(
40
+ export async function collectEip712CanonicalTypes(
39
41
  buildInfosAndOutputs: BuildInfoAndOutput[],
40
42
  inputToUserSource: ReadonlyMap<string, string>,
41
43
  config: Eip712TypesConfig,
42
- ): string[] {
44
+ ): Promise<string[]> {
43
45
  const { include, exclude } = config;
44
46
 
45
47
  if (include.length === 0) {
@@ -47,18 +49,26 @@ export function collectEip712CanonicalTypes(
47
49
  }
48
50
 
49
51
  const collected: CollectedStruct[] = [];
50
- const selectedNames = new Set<string>();
52
+ const selectedSources = new Set<string>();
51
53
 
52
- for (const { buildInfo, output } of buildInfosAndOutputs) {
54
+ for (const { buildInfoId, buildInfo, output } of buildInfosAndOutputs) {
53
55
  // Byte-level fast path: a build info whose source bytes don't contain
54
56
  // `struct ` can't define any EIP-712 type, so skip JSON-parsing its output.
55
57
  if (!bytesIncludesUtf8String(buildInfo, "struct ")) {
56
58
  continue;
57
59
  }
58
60
 
59
- const parsedOutput: SolidityBuildInfoOutput = JSON.parse(
60
- bytesToUtf8String(output),
61
- );
61
+ let parsedOutput: SolidityBuildInfoOutput;
62
+ try {
63
+ parsedOutput = await parseJsonBytes<SolidityBuildInfoOutput>(output);
64
+ } catch (error) {
65
+ ensureError(error);
66
+ throw new HardhatError(
67
+ HardhatError.ERRORS.CORE.SOLIDITY.BUILD_INFO_OUTPUT_PARSE_ERROR,
68
+ { buildInfoId },
69
+ error,
70
+ );
71
+ }
62
72
 
63
73
  const sources = parsedOutput.output.sources;
64
74
  if (sources === undefined) {
@@ -92,7 +102,9 @@ export function collectEip712CanonicalTypes(
92
102
  toUserSourceName(inputSourceName);
93
103
 
94
104
  // Collect every source so non-selected files can serve as dep targets;
95
- // selection is enforced at emit time via `selectedNames`.
105
+ // `selectedSources` enforces selection at emit time. Tracked per source,
106
+ // not per struct name, so a non-selected source defining a same-named
107
+ // struct isn't treated as selected (see `canonicalizeStructs`).
96
108
  const structs = extractStructsFromAst(
97
109
  source.ast,
98
110
  userSourceName,
@@ -101,12 +113,10 @@ export function collectEip712CanonicalTypes(
101
113
  collected.push(...structs);
102
114
 
103
115
  if (isPathSelected(userSourceName, include, exclude)) {
104
- for (const s of structs) {
105
- selectedNames.add(s.name);
106
- }
116
+ selectedSources.add(userSourceName);
107
117
  }
108
118
  }
109
119
  }
110
120
 
111
- return canonicalizeStructs(collected, selectedNames);
121
+ return canonicalizeStructs(collected, selectedSources);
112
122
  }
@@ -25,6 +25,7 @@ import { hexStringToBytes } from "@nomicfoundation/hardhat-utils/hex";
25
25
  import { DEFAULT_VERBOSITY, OPTIMISM_CHAIN_TYPE } from "../../constants.js";
26
26
  import { resolveHardfork } from "../network-manager/config-resolution.js";
27
27
  import { hardhatHardforkToEdrSpecId } from "../network-manager/edr/utils/convert-to-edr.js";
28
+ import { warnIfExperimentalHardfork } from "../network-manager/edr/utils/hardfork.js";
28
29
  import { verbosityToIncludeTraces } from "../network-manager/edr/utils/trace-formatters.js";
29
30
 
30
31
  import { formatArtifactId } from "./formatters.js";
@@ -88,8 +89,11 @@ export async function solidityTestConfigToSolidityTestRunnerConfigArgs({
88
89
  const txOrigin = hexToBytes(config.txOrigin);
89
90
  const blockCoinbase = hexToBytes(config.coinbase);
90
91
 
92
+ const resolvedHardforkName = resolveHardfork(hardfork, chainType);
93
+ warnIfExperimentalHardfork(resolvedHardforkName, chainType);
94
+
91
95
  const resolvedHardfork = hardhatHardforkToEdrSpecId(
92
- resolveHardfork(hardfork, chainType),
96
+ resolvedHardforkName,
93
97
  chainType,
94
98
  );
95
99
 
@@ -10,7 +10,8 @@ import {
10
10
  HardhatError,
11
11
  assertHardhatInvariant,
12
12
  } from "@nomicfoundation/hardhat-errors";
13
- import { bytesToUtf8String } from "@nomicfoundation/hardhat-utils/bytes";
13
+ import { parseJsonBytes } from "@nomicfoundation/hardhat-utils/bytes";
14
+ import { ensureError } from "@nomicfoundation/hardhat-utils/error";
14
15
 
15
16
  import { getFullyQualifiedName } from "../../../../utils/contract-names.js";
16
17
 
@@ -48,11 +49,11 @@ interface CollectedOverrides {
48
49
  * in the solc AST. It only extracts them from the build info where each
49
50
  * test artifact's file was compiled as a root file.
50
51
  */
51
- export function getTestFunctionOverrides(
52
+ export async function getTestFunctionOverrides(
52
53
  testSuiteArtifacts: EdrArtifactWithMetadata[],
53
54
  buildInfosAndOutputs: BuildInfoAndOutput[],
54
- ): TestFunctionOverride[] {
55
- const allRawOverrides = collectRawOverrides(
55
+ ): Promise<TestFunctionOverride[]> {
56
+ const allRawOverrides = await collectRawOverrides(
56
57
  testSuiteArtifacts,
57
58
  buildInfosAndOutputs,
58
59
  );
@@ -62,10 +63,10 @@ export function getTestFunctionOverrides(
62
63
  return buildTestFunctionOverrides(allRawOverrides);
63
64
  }
64
65
 
65
- function collectRawOverrides(
66
+ async function collectRawOverrides(
66
67
  testSuiteArtifacts: EdrArtifactWithMetadata[],
67
68
  buildInfosAndOutputs: BuildInfoAndOutput[],
68
- ): CollectedOverrides {
69
+ ): Promise<CollectedOverrides> {
69
70
  const overrides: RawInlineOverride[] = [];
70
71
  const methodIdentifiersByContract = new Map<string, Record<string, string>>();
71
72
 
@@ -153,9 +154,19 @@ function collectRawOverrides(
153
154
  continue;
154
155
  }
155
156
 
156
- const buildInfoOutput: SolidityBuildInfoOutput = JSON.parse(
157
- bytesToUtf8String(buildInfoAndOutput.output),
158
- );
157
+ let buildInfoOutput: SolidityBuildInfoOutput;
158
+ try {
159
+ buildInfoOutput = await parseJsonBytes<SolidityBuildInfoOutput>(
160
+ buildInfoAndOutput.output,
161
+ );
162
+ } catch (error) {
163
+ ensureError(error);
164
+ throw new HardhatError(
165
+ HardhatError.ERRORS.CORE.SOLIDITY.BUILD_INFO_OUTPUT_PARSE_ERROR,
166
+ { buildInfoId },
167
+ error,
168
+ );
169
+ }
159
170
 
160
171
  for (const [
161
172
  inputSourceName,
@@ -231,12 +231,12 @@ const runSolidityTests: NewTaskActionFunction<TestActionArguments> = async (
231
231
  }
232
232
  }
233
233
 
234
- const testFunctionOverrides = getTestFunctionOverrides(
234
+ const testFunctionOverrides = await getTestFunctionOverrides(
235
235
  testSuiteArtifacts,
236
236
  allBuildInfosAndOutputs,
237
237
  );
238
238
 
239
- const eip712CanonicalTypes = collectEip712CanonicalTypes(
239
+ const eip712CanonicalTypes = await collectEip712CanonicalTypes(
240
240
  allBuildInfosAndOutputs,
241
241
  sourceNameToUserSourceName,
242
242
  eip712Types,
@@ -33,6 +33,7 @@ declare module "../../../types/test.js" {
33
33
  includeStorage?: boolean;
34
34
  includePushBytes?: boolean;
35
35
  shrinkRunLimit?: number;
36
+ timeout?: number | bigint;
36
37
  }
37
38
 
38
39
  export interface SolidityTestFuzzUserConfig {
@@ -45,6 +46,7 @@ declare module "../../../types/test.js" {
45
46
  includeStorage?: boolean;
46
47
  includePushBytes?: boolean;
47
48
  showLogs?: boolean;
49
+ timeout?: number | bigint;
48
50
  }
49
51
 
50
52
  export interface SolidityTestForkingUserConfig {
@@ -66,6 +68,7 @@ declare module "../../../types/test.js" {
66
68
  blockTimestamp?: bigint;
67
69
  prevRandao?: bigint;
68
70
  gasLimit?: bigint;
71
+ memoryLimit?: number | bigint;
69
72
  blockGasLimit?: number | bigint | false;
70
73
  transactionGasCap?: number | bigint | false;
71
74
  fuzz?: SolidityTestFuzzUserConfig;
@@ -108,6 +111,7 @@ declare module "../../../types/test.js" {
108
111
  includeStorage?: boolean;
109
112
  includePushBytes?: boolean;
110
113
  shrinkRunLimit?: number;
114
+ timeout?: number;
111
115
  }
112
116
 
113
117
  export interface SolidityTestFuzzConfig {
@@ -120,6 +124,7 @@ declare module "../../../types/test.js" {
120
124
  includeStorage?: boolean;
121
125
  includePushBytes?: boolean;
122
126
  showLogs?: boolean;
127
+ timeout?: number;
123
128
  }
124
129
 
125
130
  export interface SolidityTestForkingConfig {
@@ -142,6 +147,7 @@ declare module "../../../types/test.js" {
142
147
  blockTimestamp?: bigint;
143
148
  prevRandao?: bigint;
144
149
  gasLimit?: bigint;
150
+ memoryLimit?: bigint;
145
151
  blockGasLimit?: number | bigint | false;
146
152
  transactionGasCap?: number | bigint | false;
147
153
  fuzz: SolidityTestFuzzConfig;
@@ -5,7 +5,7 @@
5
5
  "description": "A TypeScript Hardhat project using Node Test Runner and Viem",
6
6
  "type": "module",
7
7
  "devDependencies": {
8
- "hardhat": "workspace:^3.9.1",
8
+ "hardhat": "workspace:^3.11.0",
9
9
  "@nomicfoundation/hardhat-toolbox-viem": "workspace:^5.0.7",
10
10
  "@nomicfoundation/hardhat-ignition": "workspace:^3.1.8",
11
11
  "@types/node": "^22.8.5",
@@ -5,9 +5,9 @@
5
5
  "description": "A TypeScript Hardhat project using Mocha and Ethers.js",
6
6
  "type": "module",
7
7
  "devDependencies": {
8
- "hardhat": "workspace:^3.9.1",
8
+ "hardhat": "workspace:^3.11.0",
9
9
  "@nomicfoundation/hardhat-toolbox-mocha-ethers": "workspace:^3.0.7",
10
- "@nomicfoundation/hardhat-ethers": "workspace:^4.0.14",
10
+ "@nomicfoundation/hardhat-ethers": "workspace:^4.0.15",
11
11
  "@nomicfoundation/hardhat-ignition": "workspace:^3.1.8",
12
12
  "@types/chai": "^5.2.3",
13
13
  "@types/chai-as-promised": "^8.0.1",
@@ -20,7 +20,7 @@
20
20
  "typescript": "~6.0.3"
21
21
  },
22
22
  "peerDependencies": {
23
- "@nomicfoundation/hardhat-ethers": "workspace:^4.0.14",
23
+ "@nomicfoundation/hardhat-ethers": "workspace:^4.0.15",
24
24
  "@nomicfoundation/hardhat-ethers-chai-matchers": "workspace:^3.0.11",
25
25
  "@nomicfoundation/hardhat-ignition": "workspace:^3.1.8",
26
26
  "@nomicfoundation/hardhat-ignition-ethers": "workspace:^3.1.6",
@@ -5,7 +5,7 @@
5
5
  "description": "A minimal Hardhat project",
6
6
  "type": "module",
7
7
  "devDependencies": {
8
- "hardhat": "workspace:^3.9.1",
8
+ "hardhat": "workspace:^3.11.0",
9
9
  "@types/node": "^22.8.5",
10
10
  "typescript": "~6.0.3"
11
11
  }