hardhat 3.1.8 → 3.1.10

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 (146) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/src/config.d.ts +20 -0
  3. package/dist/src/config.d.ts.map +1 -1
  4. package/dist/src/config.js +31 -0
  5. package/dist/src/config.js.map +1 -1
  6. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts.map +1 -1
  7. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js +33 -21
  8. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.d.ts.map +1 -1
  10. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js +7 -8
  11. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js.map +1 -1
  12. package/dist/src/internal/builtin-plugins/coverage/instrumentation.d.ts +27 -0
  13. package/dist/src/internal/builtin-plugins/coverage/instrumentation.d.ts.map +1 -0
  14. package/dist/src/internal/builtin-plugins/coverage/instrumentation.js +27 -0
  15. package/dist/src/internal/builtin-plugins/coverage/instrumentation.js.map +1 -0
  16. package/dist/src/internal/builtin-plugins/flatten/task-action.d.ts.map +1 -1
  17. package/dist/src/internal/builtin-plugins/flatten/task-action.js +1 -1
  18. package/dist/src/internal/builtin-plugins/flatten/task-action.js.map +1 -1
  19. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.d.ts +0 -1
  20. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.d.ts.map +1 -1
  21. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.js +4 -8
  22. package/dist/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.js.map +1 -1
  23. package/dist/src/internal/builtin-plugins/network-manager/hook-handlers/config.d.ts.map +1 -1
  24. package/dist/src/internal/builtin-plugins/network-manager/hook-handlers/config.js +58 -20
  25. package/dist/src/internal/builtin-plugins/network-manager/hook-handlers/config.js.map +1 -1
  26. package/dist/src/internal/builtin-plugins/network-manager/type-validation.d.ts.map +1 -1
  27. package/dist/src/internal/builtin-plugins/network-manager/type-validation.js +8 -0
  28. package/dist/src/internal/builtin-plugins/network-manager/type-validation.js.map +1 -1
  29. package/dist/src/internal/builtin-plugins/node/task-action.d.ts.map +1 -1
  30. package/dist/src/internal/builtin-plugins/node/task-action.js +1 -2
  31. package/dist/src/internal/builtin-plugins/node/task-action.js.map +1 -1
  32. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  33. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +1 -1
  34. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  35. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts +0 -3
  36. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts.map +1 -1
  37. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.js +8 -10
  38. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.js.map +1 -1
  39. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph-building.d.ts +2 -1
  40. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph-building.d.ts.map +1 -1
  41. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph-building.js +4 -2
  42. package/dist/src/internal/builtin-plugins/solidity/build-system/dependency-graph-building.js.map +1 -1
  43. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.d.ts +3 -1
  44. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.d.ts.map +1 -1
  45. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.js +8 -2
  46. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.js.map +1 -1
  47. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/error-messages.d.ts.map +1 -1
  48. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/error-messages.js +19 -5
  49. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/error-messages.js.map +1 -1
  50. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.d.ts +8 -1
  51. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.d.ts.map +1 -1
  52. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js +54 -20
  53. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js.map +1 -1
  54. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +22 -0
  55. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
  56. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts.map +1 -1
  57. package/dist/src/internal/builtin-plugins/solidity-test/config.js +1 -0
  58. package/dist/src/internal/builtin-plugins/solidity-test/config.js.map +1 -1
  59. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts +1 -0
  60. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts.map +1 -1
  61. package/dist/src/internal/cli/error-handler.d.ts.map +1 -1
  62. package/dist/src/internal/cli/error-handler.js +15 -0
  63. package/dist/src/internal/cli/error-handler.js.map +1 -1
  64. package/dist/src/internal/cli/telemetry/sentry/reporter.d.ts.map +1 -1
  65. package/dist/src/internal/cli/telemetry/sentry/reporter.js +4 -0
  66. package/dist/src/internal/cli/telemetry/sentry/reporter.js.map +1 -1
  67. package/dist/src/internal/cli/telemetry/sentry/transport.js +1 -0
  68. package/dist/src/internal/cli/telemetry/sentry/transport.js.map +1 -1
  69. package/dist/src/internal/core/config-validation.d.ts +3 -3
  70. package/dist/src/internal/core/config-validation.d.ts.map +1 -1
  71. package/dist/src/internal/core/config-validation.js +48 -18
  72. package/dist/src/internal/core/config-validation.js.map +1 -1
  73. package/dist/src/internal/core/tasks/builders.d.ts +26 -16
  74. package/dist/src/internal/core/tasks/builders.d.ts.map +1 -1
  75. package/dist/src/internal/core/tasks/builders.js +65 -6
  76. package/dist/src/internal/core/tasks/builders.js.map +1 -1
  77. package/dist/src/internal/core/tasks/resolved-task.d.ts +2 -2
  78. package/dist/src/internal/core/tasks/resolved-task.d.ts.map +1 -1
  79. package/dist/src/internal/core/tasks/resolved-task.js +23 -9
  80. package/dist/src/internal/core/tasks/resolved-task.js.map +1 -1
  81. package/dist/src/internal/core/tasks/task-manager.d.ts.map +1 -1
  82. package/dist/src/internal/core/tasks/task-manager.js +14 -6
  83. package/dist/src/internal/core/tasks/task-manager.js.map +1 -1
  84. package/dist/src/internal/core/tasks/validations.d.ts +2 -0
  85. package/dist/src/internal/core/tasks/validations.d.ts.map +1 -1
  86. package/dist/src/internal/core/tasks/validations.js +11 -0
  87. package/dist/src/internal/core/tasks/validations.js.map +1 -1
  88. package/dist/src/internal/deprecated-module-imported-from-hardhat2-plugin.d.ts +2 -0
  89. package/dist/src/internal/deprecated-module-imported-from-hardhat2-plugin.d.ts.map +1 -0
  90. package/dist/src/internal/deprecated-module-imported-from-hardhat2-plugin.js +12 -0
  91. package/dist/src/internal/deprecated-module-imported-from-hardhat2-plugin.js.map +1 -0
  92. package/dist/src/internal/using-hardhat2-plugin-errors.d.ts +35 -0
  93. package/dist/src/internal/using-hardhat2-plugin-errors.d.ts.map +1 -0
  94. package/dist/src/internal/using-hardhat2-plugin-errors.js +98 -0
  95. package/dist/src/internal/using-hardhat2-plugin-errors.js.map +1 -0
  96. package/dist/src/plugins.d.ts +8 -0
  97. package/dist/src/plugins.d.ts.map +1 -1
  98. package/dist/src/plugins.js +13 -0
  99. package/dist/src/plugins.js.map +1 -1
  100. package/dist/src/types/index.d.ts +15 -0
  101. package/dist/src/types/index.d.ts.map +1 -0
  102. package/dist/src/types/index.js +15 -0
  103. package/dist/src/types/index.js.map +1 -0
  104. package/dist/src/types/plugins.d.ts +2 -2
  105. package/dist/src/types/solidity/errors.d.ts +18 -0
  106. package/dist/src/types/solidity/errors.d.ts.map +1 -1
  107. package/dist/src/types/solidity/errors.js.map +1 -1
  108. package/dist/src/types/tasks.d.ts +95 -34
  109. package/dist/src/types/tasks.d.ts.map +1 -1
  110. package/package.json +10 -6
  111. package/src/config.ts +37 -0
  112. package/src/internal/builtin-plugins/coverage/coverage-manager.ts +57 -50
  113. package/src/internal/builtin-plugins/coverage/hook-handlers/solidity.ts +6 -17
  114. package/src/internal/builtin-plugins/coverage/instrumentation.ts +52 -0
  115. package/src/internal/builtin-plugins/flatten/task-action.ts +1 -0
  116. package/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.ts +6 -11
  117. package/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts +65 -21
  118. package/src/internal/builtin-plugins/network-manager/type-validation.ts +9 -0
  119. package/src/internal/builtin-plugins/node/task-action.ts +2 -2
  120. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +1 -0
  121. package/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +14 -11
  122. package/src/internal/builtin-plugins/solidity/build-system/dependency-graph-building.ts +23 -1
  123. package/src/internal/builtin-plugins/solidity/build-system/resolver/dependency-resolver.ts +16 -2
  124. package/src/internal/builtin-plugins/solidity/build-system/resolver/error-messages.ts +19 -5
  125. package/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.ts +103 -29
  126. package/src/internal/builtin-plugins/solidity/type-extensions.ts +28 -0
  127. package/src/internal/builtin-plugins/solidity-test/config.ts +1 -0
  128. package/src/internal/builtin-plugins/solidity-test/type-extensions.ts +1 -0
  129. package/src/internal/cli/error-handler.ts +18 -0
  130. package/src/internal/cli/telemetry/sentry/reporter.ts +5 -0
  131. package/src/internal/cli/telemetry/sentry/transport.ts +1 -0
  132. package/src/internal/core/config-validation.ts +67 -18
  133. package/src/internal/core/tasks/builders.ts +174 -30
  134. package/src/internal/core/tasks/resolved-task.ts +31 -13
  135. package/src/internal/core/tasks/task-manager.ts +23 -5
  136. package/src/internal/core/tasks/validations.ts +40 -0
  137. package/src/internal/deprecated-module-imported-from-hardhat2-plugin.ts +12 -0
  138. package/src/internal/using-hardhat2-plugin-errors.ts +108 -0
  139. package/src/plugins.ts +16 -0
  140. package/src/types/index.ts +14 -0
  141. package/src/types/plugins.ts +2 -2
  142. package/src/types/solidity/errors.ts +22 -0
  143. package/src/types/tasks.ts +133 -36
  144. package/templates/hardhat-3/01-node-test-runner-viem/package.json +10 -10
  145. package/templates/hardhat-3/02-mocha-ethers/package.json +16 -16
  146. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import type { GasAnalyticsManager, GasMeasurement } from "./types.js";
2
- import type { TableItemV2 } from "@nomicfoundation/hardhat-utils/format";
2
+ import type { TableItem } from "@nomicfoundation/hardhat-utils/format";
3
3
 
4
4
  import crypto from "node:crypto";
5
5
  import path from "node:path";
6
6
 
7
- import { formatTableV2 } from "@nomicfoundation/hardhat-utils/format";
7
+ import { formatTable } from "@nomicfoundation/hardhat-utils/format";
8
8
  import {
9
9
  ensureDir,
10
10
  getAllFilesMatching,
@@ -142,8 +142,8 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
142
142
  const stats: GasStats = {
143
143
  min: Math.min(...gasValues),
144
144
  max: Math.max(...gasValues),
145
- avg: roundTo(avg(gasValues), 2),
146
- median: roundTo(median(gasValues), 2),
145
+ avg: Math.round(avg(gasValues)),
146
+ median: Math.round(median(gasValues)),
147
147
  calls: gasValues.length,
148
148
  };
149
149
 
@@ -209,7 +209,7 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
209
209
  public _generateGasStatsReport(
210
210
  gasStatsByContract: GasStatsByContract,
211
211
  ): string {
212
- const rows: TableItemV2[] = [];
212
+ const rows: TableItem[] = [];
213
213
 
214
214
  if (gasStatsByContract.size > 0) {
215
215
  rows.push({ type: "title", text: chalk.bold("Gas Usage Statistics") });
@@ -279,7 +279,7 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
279
279
  }
280
280
  }
281
281
 
282
- return formatTableV2(rows);
282
+ return formatTable(rows);
283
283
  }
284
284
  }
285
285
 
@@ -332,8 +332,3 @@ export function findDuplicates<T>(arr: T[]): T[] {
332
332
 
333
333
  return [...duplicates];
334
334
  }
335
-
336
- export function roundTo(value: number, decimals: number): number {
337
- const factor = 10 ** decimals;
338
- return Math.round(value * factor) / factor;
339
- }
@@ -38,33 +38,77 @@ export async function extendUserConfig(
38
38
  const networks: Record<string, NetworkUserConfig> =
39
39
  extendedConfig.networks ?? {};
40
40
 
41
- const localhostConfig: Omit<HttpNetworkUserConfig, "url"> = {
42
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- This is always http
43
- ...(networks.localhost as HttpNetworkUserConfig),
44
- };
41
+ const localhostConfig: NetworkUserConfig | undefined = networks.localhost;
42
+ const defaultConfig: NetworkUserConfig | undefined = networks.default;
43
+ const nodeConfig: NetworkUserConfig | undefined = networks.node;
45
44
 
46
- const defaultConfig: Partial<EdrNetworkUserConfig> = {
47
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- This is always edr
48
- ...(networks.default as EdrNetworkUserConfig),
49
- };
45
+ let extendedLocalhostConfig: NetworkUserConfig;
46
+ if (
47
+ localhostConfig === undefined ||
48
+ localhostConfig.type === undefined ||
49
+ localhostConfig.type === "http"
50
+ ) {
51
+ extendedLocalhostConfig = {
52
+ url: "http://localhost:8545",
53
+ type: "http",
54
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
55
+ -- We cast it here because otherwise TS complains that some fields are
56
+ always overwritten, which is not true for js incomplete configs. */
57
+ ...(localhostConfig as Partial<HttpNetworkUserConfig>),
58
+ };
59
+ } else {
60
+ extendedLocalhostConfig = localhostConfig;
61
+ }
62
+
63
+ const defaultEdrNetworkConfigValues = {
64
+ chainId: 31337,
65
+ gas: "auto",
66
+ gasMultiplier: 1,
67
+ gasPrice: "auto",
68
+ type: "edr-simulated",
69
+ } as const;
70
+
71
+ let extendedDefaultConfig: NetworkUserConfig;
72
+ if (
73
+ defaultConfig === undefined ||
74
+ defaultConfig.type === undefined ||
75
+ defaultConfig.type === "edr-simulated"
76
+ ) {
77
+ extendedDefaultConfig = {
78
+ ...defaultEdrNetworkConfigValues,
79
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
80
+ -- We cast it here because otherwise TS complains that some fields are
81
+ always overwritten, which is not true for js incomplete configs. */
82
+ ...(defaultConfig as Partial<EdrNetworkUserConfig>),
83
+ };
84
+ } else {
85
+ extendedDefaultConfig = defaultConfig;
86
+ }
87
+
88
+ let extendedNodeConfig: NetworkUserConfig;
89
+ if (
90
+ nodeConfig === undefined ||
91
+ nodeConfig.type === undefined ||
92
+ nodeConfig.type === "edr-simulated"
93
+ ) {
94
+ extendedNodeConfig = {
95
+ ...defaultEdrNetworkConfigValues,
96
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
97
+ -- We cast it here because otherwise TS complains that url and http will
98
+ be always overwritten, which is not true for js incomplete configs. */
99
+ ...(nodeConfig as Partial<EdrNetworkUserConfig>),
100
+ };
101
+ } else {
102
+ extendedNodeConfig = nodeConfig;
103
+ }
50
104
 
51
105
  return {
52
106
  ...extendedConfig,
53
107
  networks: {
54
108
  ...networks,
55
- localhost: {
56
- url: "http://localhost:8545",
57
- ...localhostConfig,
58
- type: "http",
59
- },
60
- [DEFAULT_NETWORK_NAME]: {
61
- chainId: 31337,
62
- gas: "auto",
63
- gasMultiplier: 1,
64
- gasPrice: "auto",
65
- ...defaultConfig,
66
- type: "edr-simulated",
67
- },
109
+ localhost: extendedLocalhostConfig,
110
+ [DEFAULT_NETWORK_NAME]: extendedDefaultConfig,
111
+ node: extendedNodeConfig,
68
112
  },
69
113
  };
70
114
  }
@@ -344,6 +344,15 @@ function refineEdrNetworkUserConfig(
344
344
  { defaultChainType = GENERIC_CHAIN_TYPE, networks = {} }: HardhatUserConfig,
345
345
  ctx: RefinementCtx,
346
346
  ): void {
347
+ const nodeNetwork = networks.node;
348
+ if (nodeNetwork !== undefined && nodeNetwork.type !== "edr-simulated") {
349
+ ctx.addIssue({
350
+ code: z.ZodIssueCode.custom,
351
+ path: ["networks", "node", "type"],
352
+ message: "The node network type must be 'edr-simulated'",
353
+ });
354
+ }
355
+
347
356
  for (const [networkName, network] of Object.entries(networks)) {
348
357
  if (network.type === "edr-simulated") {
349
358
  const { chainType, hardfork, minGasPrice, initialBaseFeePerGas } =
@@ -12,7 +12,6 @@ import { ensureDir, exists } from "@nomicfoundation/hardhat-utils/fs";
12
12
  import chalk from "chalk";
13
13
  import debug from "debug";
14
14
 
15
- import { DEFAULT_NETWORK_NAME } from "../../constants.js";
16
15
  import { isSupportedChainType } from "../../edr/chain-type.js";
17
16
  import { BUILD_INFO_DIR_NAME } from "../artifacts/artifact-manager.js";
18
17
  import { EdrProvider } from "../network-manager/edr/edr-provider.js";
@@ -42,7 +41,7 @@ const nodeAction: NewTaskActionFunction<NodeActionArguments> = async (
42
41
  const network =
43
42
  hre.globalOptions.network !== undefined
44
43
  ? hre.globalOptions.network
45
- : DEFAULT_NETWORK_NAME;
44
+ : "node";
46
45
 
47
46
  if (!(network in hre.config.networks)) {
48
47
  throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.NETWORK_NOT_FOUND, {
@@ -64,6 +63,7 @@ const nodeAction: NewTaskActionFunction<NodeActionArguments> = async (
64
63
  } = {
65
64
  network,
66
65
  };
66
+
67
67
  // NOTE: We create an empty network config override here. We add to it based
68
68
  // on the result of arguments parsing. We can expand the list of arguments
69
69
  // as much as needed.
@@ -418,6 +418,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
418
418
  rootFilePaths.toSorted(), // We sort them to have a deterministic order
419
419
  this.#options.projectRoot,
420
420
  readSourceFileFactory(this.#hooks),
421
+ this.#hooks,
421
422
  );
422
423
 
423
424
  const { buildProfileName, buildProfile } = this.#getBuildProfile(
@@ -152,13 +152,8 @@ export class CompilerDownloaderImplementation implements CompilerDownloader {
152
152
  readonly #platform: CompilerPlatform;
153
153
  readonly #compilersDir: string;
154
154
  readonly #downloadFunction: typeof download;
155
+ readonly #mutexCompilerList: MultiProcessMutex;
155
156
 
156
- readonly #mutexCompiler = new MultiProcessMutex("compiler-download");
157
- readonly #mutexCompilerList = new MultiProcessMutex("compiler-download-list");
158
-
159
- /**
160
- * Use CompilerDownloader.getConcurrencySafeDownloader instead
161
- */
162
157
  constructor(
163
158
  platform: CompilerPlatform,
164
159
  compilersDir: string,
@@ -166,6 +161,9 @@ export class CompilerDownloaderImplementation implements CompilerDownloader {
166
161
  ) {
167
162
  this.#platform = platform;
168
163
  this.#compilersDir = compilersDir;
164
+ this.#mutexCompilerList = new MultiProcessMutex(
165
+ path.join(compilersDir, "compiler-download-list"),
166
+ );
169
167
  this.#downloadFunction = downloadFunction;
170
168
  }
171
169
 
@@ -200,11 +198,16 @@ export class CompilerDownloaderImplementation implements CompilerDownloader {
200
198
  }
201
199
 
202
200
  public async downloadCompiler(version: string): Promise<boolean> {
203
- // Since only one process at a time can acquire the mutex, we avoid the risk of downloading the same compiler multiple times.
204
- // This is because the mutex blocks access until a compiler has been fully downloaded, preventing any new process
205
- // from checking whether that version of the compiler exists. Without mutex it might incorrectly
206
- // return false, indicating that the compiler isn't present, even though it is currently being downloaded.
207
- return this.#mutexCompiler.use(async () => {
201
+ // A per-version mutex ensures that only one process at a time can download a given compiler version,
202
+ // while still allowing different compiler versions to be downloaded in parallel.
203
+ // Without the mutex, a concurrent process might check whether a version exists, incorrectly
204
+ // find it missing (because another process is still downloading it), and start a redundant download.
205
+
206
+ const mutex = new MultiProcessMutex(
207
+ path.join(this.#compilersDir, `compiler-download-${version}`),
208
+ );
209
+
210
+ return mutex.use(async () => {
208
211
  const isCompilerDownloaded = await this.isCompilerDownloaded(version);
209
212
 
210
213
  if (isCompilerDownloaded === true) {
@@ -1,3 +1,5 @@
1
+ import type { RemappingsReaderFunction } from "./resolver/remapped-npm-packages-graph.js";
2
+ import type { HookManager } from "../../../../types/hooks.js";
1
3
  import type { ResolvedFile } from "../../../../types/solidity/resolved-file.js";
2
4
 
3
5
  import { HardhatError } from "@nomicfoundation/hardhat-errors";
@@ -17,8 +19,28 @@ export async function buildDependencyGraph(
17
19
  rootFiles: string[],
18
20
  projectRoot: string,
19
21
  readFile: (absPath: string) => Promise<string>,
22
+ hookManager: HookManager,
20
23
  ): Promise<DependencyGraphImplementation> {
21
- const resolver = await ResolverImplementation.create(projectRoot, readFile);
24
+ // Create the wrapper function that captures the hook manager
25
+ const remappingsReader: RemappingsReaderFunction = (
26
+ packageName,
27
+ packageVersion,
28
+ packagePath,
29
+ defaultBehavior,
30
+ ) =>
31
+ hookManager.runHandlerChain(
32
+ "solidity",
33
+ "readNpmPackageRemappings",
34
+ [packageName, packageVersion, packagePath],
35
+ async (_context, name, version, path) =>
36
+ defaultBehavior(name, version, path),
37
+ );
38
+
39
+ const resolver = await ResolverImplementation.create(
40
+ projectRoot,
41
+ readFile,
42
+ remappingsReader,
43
+ );
22
44
 
23
45
  const dependencyGraph = new DependencyGraphImplementation();
24
46
 
@@ -38,6 +38,7 @@ import { parseNpmDirectImport } from "./npm-module-parsing.js";
38
38
  import {
39
39
  isResolvedUserRemapping,
40
40
  RemappedNpmPackagesGraphImplementation,
41
+ type RemappingsReaderFunction,
41
42
  } from "./remapped-npm-packages-graph.js";
42
43
  import { applyValidRemapping, formatRemapping } from "./remappings.js";
43
44
  import {
@@ -86,14 +87,18 @@ export class ResolverImplementation implements Resolver {
86
87
  *
87
88
  * @param projectRoot The absolute path to the Hardhat project root.
88
89
  * @param readUtf8File A function that reads a UTF-8 file.
90
+ * @param remappingsReader Optional function to read remappings from packages.
89
91
  * @returns The resolver or the user remapping errors found.
90
92
  */
91
93
  public static async create(
92
94
  projectRoot: string,
93
95
  readUtf8File: (absPath: string) => Promise<string>,
96
+ remappingsReader?: RemappingsReaderFunction,
94
97
  ): Promise<Resolver> {
95
- const map =
96
- await RemappedNpmPackagesGraphImplementation.create(projectRoot);
98
+ const map = await RemappedNpmPackagesGraphImplementation.create(
99
+ projectRoot,
100
+ remappingsReader,
101
+ );
97
102
 
98
103
  return new ResolverImplementation(projectRoot, map, readUtf8File);
99
104
  }
@@ -691,6 +696,13 @@ export class ResolverImplementation implements Resolver {
691
696
  );
692
697
 
693
698
  if (dependencyResolution === undefined) {
699
+ // Check if the from file's package has foundry.toml
700
+ const foundryTomlPath = path.join(
701
+ from.package.rootFsPath,
702
+ "foundry.toml",
703
+ );
704
+ const fromHasFoundryToml = await exists(foundryTomlPath);
705
+
694
706
  return {
695
707
  success: false,
696
708
  error: {
@@ -698,6 +710,7 @@ export class ResolverImplementation implements Resolver {
698
710
  fromFsPath: from.fsPath,
699
711
  importPath,
700
712
  installationName: parsedDirectImport.package,
713
+ importerPackageHasFoundryToml: fromHasFoundryToml,
701
714
  },
702
715
  };
703
716
  }
@@ -1098,6 +1111,7 @@ export class ResolverImplementation implements Resolver {
1098
1111
  type: RootResolutionErrorType.NPM_ROOT_FILE_OF_UNINSTALLED_PACKAGE,
1099
1112
  npmModule,
1100
1113
  installationName: error.installationName,
1114
+ projectHasFoundryToml: error.importerPackageHasFoundryToml,
1101
1115
  };
1102
1116
  }
1103
1117
  case ImportResolutionErrorType.IMPORT_WITH_REMAPPING_ERRORS: {
@@ -57,7 +57,14 @@ Note that the npm module is being remapped by ${formatRemappingReference(error.u
57
57
  }
58
58
 
59
59
  case RootResolutionErrorType.NPM_ROOT_FILE_OF_UNINSTALLED_PACKAGE: {
60
- return `The package "${error.installationName}" is not installed.`;
60
+ const baseMessage = `The package "${error.installationName}" is not installed.`;
61
+ if (error.projectHasFoundryToml === true) {
62
+ return `${baseMessage}
63
+
64
+ Your project has a foundry.toml, and you may need to install the "@nomicfoundation/hardhat-foundry" plugin.
65
+ Learn more about Hardhat's Foundry compatibility here: https://hardhat.org/foundry-compatibility`;
66
+ }
67
+ return baseMessage;
61
68
  }
62
69
 
63
70
  case RootResolutionErrorType.NPM_ROOT_FILE_RESOLUTION_WITH_REMAPPING_ERRORS: {
@@ -107,13 +114,13 @@ export function formatImportResolutionError(
107
114
  }
108
115
 
109
116
  case ImportResolutionErrorType.ILLEGAL_RELATIVE_IMPORT: {
110
- return "The import has too many '../', and trying to leave its package.";
117
+ return "The import has too many '../' and is trying to leave its package.";
111
118
  }
112
119
 
113
120
  case ImportResolutionErrorType.RELATIVE_IMPORT_INTO_NODE_MODULES: {
114
121
  return `You are trying to import a file from your node_modules directory with its file system path.
115
122
 
116
- You should write your the path of your imports into npm modules just as you would do in JavaScript files.`;
123
+ You should write your imports into npm modules just as you would do in JavaScript files.`;
117
124
  }
118
125
 
119
126
  case ImportResolutionErrorType.IMPORT_WITH_INVALID_NPM_SYNTAX: {
@@ -121,7 +128,14 @@ You should write your the path of your imports into npm modules just as you woul
121
128
  }
122
129
 
123
130
  case ImportResolutionErrorType.IMPORT_OF_UNINSTALLED_PACKAGE: {
124
- return `The package "${error.installationName}" is not installed.`;
131
+ const baseMessage = `The package "${error.installationName}" is not installed.`;
132
+ if (error.importerPackageHasFoundryToml === true) {
133
+ return `${baseMessage}
134
+
135
+ The file importing this package is inside a Foundry project (foundry.toml detected), and you may need to install the "@nomicfoundation/hardhat-foundry" plugin.
136
+ Learn more about Hardhat's Foundry compatibility here: https://hardhat.org/foundry-compatibility`;
137
+ }
138
+ return baseMessage;
125
139
  }
126
140
 
127
141
  case ImportResolutionErrorType.IMPORT_WITH_REMAPPING_ERRORS: {
@@ -148,7 +162,7 @@ If you still want to be able to do it, try adding this remapping "${error.sugges
148
162
  ? "the package"
149
163
  : "the project";
150
164
 
151
- const message = `The file ${error.packageExportsResolvedSubpath ?? error.subpath} doesn't exist within ${packageOrProject}.`;
165
+ const message = `The file "${error.packageExportsResolvedSubpath ?? error.subpath}" doesn't exist within ${packageOrProject}.`;
152
166
 
153
167
  return formatResolutionErrorRemappingsOrPackageExportsNotes({
154
168
  message,
@@ -37,6 +37,17 @@ import { UserRemappingType } from "./types.js";
37
37
 
38
38
  const HARDHAT_PROJECT_INPUT_SOURCE_NAME_ROOT = "project";
39
39
 
40
+ export type RemappingsReaderFunction = (
41
+ packageName: string,
42
+ packageVersion: string,
43
+ packagePath: string,
44
+ defaultBehavior: (
45
+ name: string,
46
+ version: string,
47
+ path: string,
48
+ ) => Promise<Array<{ remappings: string[]; source: string }>>,
49
+ ) => Promise<Array<{ remappings: string[]; source: string }>>;
50
+
40
51
  export function isResolvedUserRemapping(
41
52
  remapping: Remapping | ResolvedUserRemapping,
42
53
  ): remapping is ResolvedUserRemapping {
@@ -55,6 +66,11 @@ export class RemappedNpmPackagesGraphImplementation
55
66
  */
56
67
  readonly #hardhatProjectPackage: ResolvedNpmPackage;
57
68
 
69
+ /**
70
+ * The remappings reader function to use when reading package remappings.
71
+ */
72
+ readonly #remappingsReader: RemappingsReaderFunction;
73
+
58
74
  /**
59
75
  * This is a map of all the npm packages. Every package that has been
60
76
  * loaded by this class, is present in this map.
@@ -104,6 +120,12 @@ export class RemappedNpmPackagesGraphImplementation
104
120
 
105
121
  public static async create(
106
122
  projectRootPath: string,
123
+ remappingsReader: RemappingsReaderFunction = (
124
+ packageName,
125
+ packageVersion,
126
+ packagePath,
127
+ defaultBehavior,
128
+ ) => defaultBehavior(packageName, packageVersion, packagePath),
107
129
  ): Promise<RemappedNpmPackagesGraphImplementation> {
108
130
  const projectPackageJson = await readJsonFile<PackageJson>(
109
131
  path.join(projectRootPath, "package.json"),
@@ -117,11 +139,18 @@ export class RemappedNpmPackagesGraphImplementation
117
139
  inputSourceNameRoot: HARDHAT_PROJECT_INPUT_SOURCE_NAME_ROOT,
118
140
  };
119
141
 
120
- return new RemappedNpmPackagesGraphImplementation(resolvedNpmPackage);
142
+ return new RemappedNpmPackagesGraphImplementation(
143
+ resolvedNpmPackage,
144
+ remappingsReader,
145
+ );
121
146
  }
122
147
 
123
- private constructor(hardhatProjectPackage: ResolvedNpmPackage) {
148
+ private constructor(
149
+ hardhatProjectPackage: ResolvedNpmPackage,
150
+ remappingsReader: RemappingsReaderFunction,
151
+ ) {
124
152
  this.#hardhatProjectPackage = hardhatProjectPackage;
153
+ this.#remappingsReader = remappingsReader;
125
154
  this.#insertNewPackage(hardhatProjectPackage);
126
155
  }
127
156
 
@@ -397,41 +426,88 @@ export class RemappedNpmPackagesGraphImplementation
397
426
  UserRemappingError[]
398
427
  >
399
428
  > {
400
- const remappingsTxtFiles = await getAllFilesMatching(
429
+ const allRemappings = await this.#remappingsReader(
430
+ npmPackage.name,
431
+ npmPackage.version,
401
432
  npmPackage.rootFsPath,
402
- (f) => path.basename(f) === "remappings.txt",
403
- (f) => !f.endsWith("node_modules"),
433
+ async (_packageName, _packageVersion, packagePath) =>
434
+ this.#defaultReadPackageRemappings(packagePath),
404
435
  );
405
436
 
406
- const remappings = [];
407
- const errors = [];
437
+ return this.#parseAndDeduplicateRemappings(npmPackage, allRemappings);
438
+ }
408
439
 
409
- for (const remappingsTxtFsPath of remappingsTxtFiles) {
410
- const packageRemappingsTxtContents =
411
- await readUtf8File(remappingsTxtFsPath);
440
+ /**
441
+ * The default behavior of reading all the remappings.txt files in a package.
442
+ * @param packagePath The fs path to the root of the package.
443
+ * @returns An array with one entry per remappings.txt file, with the
444
+ * contents of the file and the fs path to the file.
445
+ */
446
+ async #defaultReadPackageRemappings(
447
+ packagePath: string,
448
+ ): Promise<Array<{ remappings: string[]; source: string }>> {
449
+ const remappingsTxtFiles = await getAllFilesMatching(
450
+ packagePath,
451
+ (f) => path.basename(f) === "remappings.txt",
452
+ (f) => !f.endsWith("node_modules"),
453
+ );
412
454
 
413
- const rawUserRemappings = packageRemappingsTxtContents
455
+ const results: Array<{ remappings: string[]; source: string }> = [];
456
+ for (const file of remappingsTxtFiles) {
457
+ const contents = await readUtf8File(file);
458
+ const lines = contents
414
459
  .split("\n")
415
460
  .map((line) => line.trim())
416
- .filter((line) => line !== "")
417
- .filter((line) => !line.startsWith("#"));
461
+ .filter((line) => line !== "" && !line.startsWith("#"));
462
+ results.push({ remappings: lines, source: file });
463
+ }
464
+
465
+ return results;
466
+ }
418
467
 
419
- for (const userRemapping of rawUserRemappings) {
420
- const result = await this.#parseUserRemapping(
468
+ /**
469
+ * Parses and deduplicates by "context:prefix" all the remappings from the
470
+ * package.
471
+ *
472
+ * @param npmPackage The npm package.
473
+ * @param allRemappings An array with all the remappings.txt files in the
474
+ * package and their content.
475
+ * @returns A result with the parsed and deduplicated remappings, or an error
476
+ * if there was a problem parsing any of them.
477
+ */
478
+ #parseAndDeduplicateRemappings(
479
+ npmPackage: ResolvedNpmPackage,
480
+ allRemappings: Array<{ remappings: string[]; source: string }>,
481
+ ): Result<
482
+ Array<LocalUserRemapping | UnresolvedNpmUserRemapping>,
483
+ UserRemappingError[]
484
+ > {
485
+ const remappings: Array<LocalUserRemapping | UnresolvedNpmUserRemapping> =
486
+ [];
487
+ const errors: UserRemappingError[] = [];
488
+ const seen = new Set<string>(); // Track by "context:prefix"
489
+
490
+ for (const { remappings: remappingStrings, source } of allRemappings) {
491
+ for (const remappingString of remappingStrings) {
492
+ const result = this.#parseUserRemapping(
421
493
  npmPackage,
422
- remappingsTxtFsPath,
423
- userRemapping,
494
+ source,
495
+ remappingString,
424
496
  );
425
497
 
426
498
  if (!result.success) {
427
499
  errors.push(result.error);
428
- } else {
429
- // If parsing returned `undefined`, it means that it should be
430
- // ignored.
431
- if (result.value === undefined) {
432
- continue;
433
- }
500
+ continue;
501
+ }
502
+
503
+ if (result.value === undefined) {
504
+ continue;
505
+ }
434
506
 
507
+ // Deduplicate by (context + prefix) - first occurrence wins
508
+ const key = `${result.value.context}:${result.value.prefix}`;
509
+ if (!seen.has(key)) {
510
+ seen.add(key);
435
511
  remappings.push(result.value);
436
512
  }
437
513
  }
@@ -454,15 +530,13 @@ export class RemappedNpmPackagesGraphImplementation
454
530
  * @returns The parsed user remapping, or undefined if it should be ignored.
455
531
  * If the parsing and validation fails, an error is returned.
456
532
  */
457
- async #parseUserRemapping(
533
+ #parseUserRemapping(
458
534
  npmPackage: ResolvedNpmPackage,
459
535
  sourceOfTheRemapping: string,
460
536
  remappingString: string,
461
- ): Promise<
462
- Result<
463
- LocalUserRemapping | UnresolvedNpmUserRemapping | undefined,
464
- UserRemappingError
465
- >
537
+ ): Result<
538
+ LocalUserRemapping | UnresolvedNpmUserRemapping | undefined,
539
+ UserRemappingError
466
540
  > {
467
541
  // We first parse the remapping string and validate that it doesn't have
468
542
  // a context starting with `npm/`, and that the prefix and targets end in /.
@@ -242,5 +242,33 @@ declare module "../../../types/hooks.js" {
242
242
  nextSolcConfig: SolcConfig,
243
243
  ) => Promise<CompilerOutput>,
244
244
  ): Promise<CompilerOutput>;
245
+
246
+ /**
247
+ * Provide a handler for this hook to supply remappings for npm packages.
248
+ *
249
+ * This hook is called when the resolver needs to read remappings for a package.
250
+ * Handlers can provide remappings from alternative sources (e.g., foundry.toml)
251
+ * in addition to the default remappings.txt files.
252
+ *
253
+ * @param context The hook context.
254
+ * @param packageName The name of the npm package.
255
+ * @param packageVersion The version of the npm package.
256
+ * @param packagePath The absolute filesystem path to the package root.
257
+ * @param next A function to get remappings from other sources (including default behavior).
258
+ * @returns An array of remapping sources, each containing an array of remapping strings
259
+ * and the source path they came from.
260
+ */
261
+ readNpmPackageRemappings: (
262
+ context: HookContext,
263
+ packageName: string,
264
+ packageVersion: string,
265
+ packagePath: string,
266
+ next: (
267
+ nextContext: HookContext,
268
+ nextPackageName: string,
269
+ nextPackageVersion: string,
270
+ nextPackagePath: string,
271
+ ) => Promise<Array<{ remappings: string[]; source: string }>>,
272
+ ) => Promise<Array<{ remappings: string[]; source: string }>>;
245
273
  }
246
274
  }
@@ -44,6 +44,7 @@ const solidityTestUserConfigType = z.object({
44
44
  coinbase: z.string().startsWith("0x").optional(),
45
45
  blockTimestamp: z.bigint().optional(),
46
46
  prevRandao: z.bigint().optional(),
47
+ gasLimit: z.bigint().optional(),
47
48
  blockGasLimit: z.bigint().or(z.literal(false)).optional(),
48
49
  fuzz: z
49
50
  .object({
@@ -35,6 +35,7 @@ declare module "../../../types/test.js" {
35
35
  coinbase?: string; // 0x-prefixed hex string
36
36
  blockTimestamp?: bigint;
37
37
  prevRandao?: bigint;
38
+ gasLimit?: bigint;
38
39
  blockGasLimit?: bigint | false;
39
40
 
40
41
  fuzz?: {