@nomicfoundation/hardhat-utils 4.1.0 → 4.1.1

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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/src/bytecode.d.ts +4 -0
  3. package/dist/src/bytecode.d.ts.map +1 -1
  4. package/dist/src/bytecode.js +12 -7
  5. package/dist/src/bytecode.js.map +1 -1
  6. package/dist/src/crypto.d.ts.map +1 -1
  7. package/dist/src/crypto.js +10 -2
  8. package/dist/src/crypto.js.map +1 -1
  9. package/dist/src/fs.d.ts.map +1 -1
  10. package/dist/src/fs.js +21 -35
  11. package/dist/src/fs.js.map +1 -1
  12. package/dist/src/internal/bytecode.d.ts +11 -0
  13. package/dist/src/internal/bytecode.d.ts.map +1 -1
  14. package/dist/src/internal/bytecode.js +39 -2
  15. package/dist/src/internal/bytecode.js.map +1 -1
  16. package/dist/src/internal/fs.d.ts +15 -0
  17. package/dist/src/internal/fs.d.ts.map +1 -1
  18. package/dist/src/internal/fs.js +44 -0
  19. package/dist/src/internal/fs.js.map +1 -1
  20. package/dist/src/internal/global-dir.d.ts +2 -2
  21. package/dist/src/internal/global-dir.d.ts.map +1 -1
  22. package/dist/src/internal/global-dir.js +7 -1
  23. package/dist/src/internal/global-dir.js.map +1 -1
  24. package/dist/src/internal/lang.d.ts +3 -2
  25. package/dist/src/internal/lang.d.ts.map +1 -1
  26. package/dist/src/internal/lang.js +12 -6
  27. package/dist/src/internal/lang.js.map +1 -1
  28. package/dist/src/lang.js +2 -2
  29. package/dist/src/lang.js.map +1 -1
  30. package/dist/src/request.d.ts +11 -11
  31. package/dist/src/request.d.ts.map +1 -1
  32. package/dist/src/request.js +6 -6
  33. package/dist/src/request.js.map +1 -1
  34. package/dist/src/synchronization.js +6 -6
  35. package/dist/src/synchronization.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/bytecode.ts +15 -6
  38. package/src/crypto.ts +15 -2
  39. package/src/fs.ts +33 -53
  40. package/src/internal/bytecode.ts +64 -5
  41. package/src/internal/fs.ts +70 -0
  42. package/src/internal/global-dir.ts +10 -4
  43. package/src/internal/lang.ts +15 -6
  44. package/src/lang.ts +2 -2
  45. package/src/request.ts +16 -16
  46. package/src/synchronization.ts +6 -6
@@ -29,6 +29,17 @@ export interface LibraryAddresses {
29
29
  [contractName: string]: PrefixedHexString;
30
30
  }
31
31
 
32
+ export interface BytecodeReplacement {
33
+ start: number;
34
+ length: number;
35
+ value: string;
36
+ }
37
+
38
+ interface LibraryLinksIndex {
39
+ byName: Map<string, LibraryLink[]>;
40
+ byFqn: Map<string, LibraryLink>;
41
+ }
42
+
32
43
  /**
33
44
  * Check that the provided library addresses are valid Ethereum addresses.
34
45
  * If any of them are not, an InvalidLibraryAddressError is thrown.
@@ -61,13 +72,15 @@ export function checkAmbiguousOrUnnecessaryLinks(
61
72
  ): void {
62
73
  const ambiguousLibraries: Record<string, LibraryLink[]> = {};
63
74
  const unnecessaryLibraries: string[] = [];
75
+ const neededLibrariesIndex = indexLibraryLinks(neededLibraries);
64
76
 
65
77
  for (const providedLibraryName of Object.keys(providedLibraries)) {
66
- const matchingLibraries = neededLibraries.filter(
67
- ({ libraryName, libraryFqn }) =>
68
- libraryName === providedLibraryName ||
69
- libraryFqn === providedLibraryName,
70
- );
78
+ const matchingLibraryByFqn =
79
+ neededLibrariesIndex.byFqn.get(providedLibraryName);
80
+ const matchingLibraries =
81
+ matchingLibraryByFqn !== undefined
82
+ ? [matchingLibraryByFqn]
83
+ : neededLibrariesIndex.byName.get(providedLibraryName) ?? [];
71
84
 
72
85
  if (matchingLibraries.length > 1) {
73
86
  ambiguousLibraries[providedLibraryName] = matchingLibraries;
@@ -85,6 +98,24 @@ export function checkAmbiguousOrUnnecessaryLinks(
85
98
  }
86
99
  }
87
100
 
101
+ function indexLibraryLinks(neededLibraries: LibraryLink[]): LibraryLinksIndex {
102
+ const byName = new Map<string, LibraryLink[]>();
103
+ const byFqn = new Map<string, LibraryLink>();
104
+
105
+ for (const neededLibrary of neededLibraries) {
106
+ byFqn.set(neededLibrary.libraryFqn, neededLibrary);
107
+
108
+ const sameNameLibraries = byName.get(neededLibrary.libraryName);
109
+ if (sameNameLibraries === undefined) {
110
+ byName.set(neededLibrary.libraryName, [neededLibrary]);
111
+ } else {
112
+ sameNameLibraries.push(neededLibrary);
113
+ }
114
+ }
115
+
116
+ return { byName, byFqn };
117
+ }
118
+
88
119
  /**
89
120
  * Check that each library is only provided once, either by its name or its
90
121
  * fully qualified name. If a library is provided more than once, an
@@ -127,3 +158,31 @@ export function checkMissingLibraryAddresses(
127
158
 
128
159
  throw new MissingLibrariesError(missingLibraries);
129
160
  }
161
+
162
+ /**
163
+ * Apply a set of replacements to a bytecode string, returning the resulting
164
+ * bytecode. Each replacement overwrites `length` characters starting at
165
+ * `start` with `value`. Replacements must not overlap.
166
+ */
167
+ export function applyBytecodeReplacements(
168
+ bytecode: string,
169
+ replacements: BytecodeReplacement[],
170
+ ): string {
171
+ if (replacements.length === 0) {
172
+ return bytecode;
173
+ }
174
+
175
+ replacements.sort((a, b) => a.start - b.start);
176
+
177
+ const parts: string[] = [];
178
+ let position = 0;
179
+
180
+ for (const { start, length, value } of replacements) {
181
+ parts.push(bytecode.slice(position, start), value);
182
+ position = start + length;
183
+ }
184
+
185
+ parts.push(bytecode.slice(position));
186
+
187
+ return parts.join("");
188
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Dirent } from "node:fs";
2
2
 
3
3
  import fsPromises from "node:fs/promises";
4
+ import path from "node:path";
4
5
 
5
6
  import { ensureNodeErrnoExceptionError } from "../error.js";
6
7
  import { FileSystemAccessError, NotADirectoryError } from "../errors/fs.js";
@@ -55,3 +56,72 @@ export async function isDirectoryDirentAware(
55
56
 
56
57
  return await isDirectory(absolutePath);
57
58
  }
59
+
60
+ /**
61
+ * Recursively walk the directory tree rooted at `dirFrom`, appending the
62
+ * absolute paths of every file accepted by `matches` to `results`. Descent
63
+ * into a subdirectory is gated by `directoryFilter`. When either callback is
64
+ * omitted, every file or directory is accepted.
65
+ */
66
+ export async function collectAllFilesMatching(
67
+ dirFrom: string,
68
+ results: string[],
69
+ matches?: (absolutePathToFile: string) => Promise<boolean> | boolean,
70
+ directoryFilter?: (absolutePathToDir: string) => Promise<boolean> | boolean,
71
+ ): Promise<void> {
72
+ const dirContent = await readdirWithFileTypesOrEmpty(dirFrom);
73
+
74
+ await Promise.all(
75
+ dirContent.map(async (dirent) => {
76
+ const absolutePathToFile = path.join(dirFrom, dirent.name);
77
+ if (await isDirectoryDirentAware(absolutePathToFile, dirent)) {
78
+ if (
79
+ directoryFilter === undefined ||
80
+ (await directoryFilter(absolutePathToFile))
81
+ ) {
82
+ await collectAllFilesMatching(
83
+ absolutePathToFile,
84
+ results,
85
+ matches,
86
+ directoryFilter,
87
+ );
88
+ }
89
+
90
+ return;
91
+ } else if (matches === undefined || (await matches(absolutePathToFile))) {
92
+ results.push(absolutePathToFile);
93
+ }
94
+ }),
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Recursively walk the directory tree rooted at `dirFrom`, appending the
100
+ * absolute paths of every directory accepted by `matches` to `results`. When
101
+ * a directory matches, its descendants are not explored; when it does not,
102
+ * the walk continues into its subdirectories. When `matches` is omitted,
103
+ * every directory is accepted and recursion stops at the top level.
104
+ */
105
+ export async function collectAllDirectoriesMatching(
106
+ dirFrom: string,
107
+ results: string[],
108
+ matches?: (absolutePathToDir: string) => Promise<boolean> | boolean,
109
+ ): Promise<void> {
110
+ const dirContent = await readdirWithFileTypesOrEmpty(dirFrom);
111
+
112
+ await Promise.all(
113
+ dirContent.map(async (dirent) => {
114
+ const absolutePathToFile = path.join(dirFrom, dirent.name);
115
+ if (!(await isDirectoryDirentAware(absolutePathToFile, dirent))) {
116
+ return;
117
+ }
118
+
119
+ if (matches === undefined || (await matches(absolutePathToFile))) {
120
+ results.push(absolutePathToFile);
121
+ return;
122
+ }
123
+
124
+ await collectAllDirectoriesMatching(absolutePathToFile, results, matches);
125
+ }),
126
+ );
127
+ }
@@ -1,9 +1,15 @@
1
- import envPaths from "env-paths";
1
+ import type { Paths } from "env-paths";
2
2
 
3
3
  export const HARDHAT_PACKAGE_NAME = "hardhat";
4
4
 
5
- export async function generatePaths(
6
- packageName: string,
7
- ): Promise<envPaths.Paths> {
5
+ // We don't load env-paths on startup because this module is transitively
6
+ // imported from many places but generatePaths is rarely called during
7
+ // bootstrap.
8
+ let envPaths: ((name: string) => Paths) | undefined;
9
+
10
+ export async function generatePaths(packageName: string): Promise<Paths> {
11
+ if (envPaths === undefined) {
12
+ ({ default: envPaths } = await import("env-paths"));
13
+ }
8
14
  return envPaths(packageName);
9
15
  }
@@ -1,11 +1,15 @@
1
- import { createCustomEqual } from "fast-equals";
2
- import rfdc from "rfdc";
1
+ import type Rfdc from "rfdc";
3
2
 
4
3
  import { isObject } from "../lang.js";
5
4
 
6
- let clone: ReturnType<typeof rfdc> | null = null;
7
- export function getDeepCloneFunction(): <T>(input: T) => T {
8
- if (clone === null) {
5
+ // We don't load rfdc on startup because it adds unnecessary
6
+ // overhead when this module is transitively imported but deep clone
7
+ // is not used.
8
+ let clone: ReturnType<typeof Rfdc> | undefined;
9
+
10
+ export async function getDeepCloneFunction(): Promise<ReturnType<typeof Rfdc>> {
11
+ if (clone === undefined) {
12
+ const { default: rfdc } = await import("rfdc");
9
13
  clone = rfdc();
10
14
  }
11
15
 
@@ -53,6 +57,9 @@ export function deepMergeImpl<T extends object, S extends object>(
53
57
  return result;
54
58
  }
55
59
 
60
+ // We don't load fast-equals on startup because it adds unnecessary
61
+ // overhead when this module is transitively imported but deep equal
62
+ // is not used.
56
63
  let cachedCustomEqual: ((a: unknown, b: unknown) => boolean) | undefined;
57
64
 
58
65
  /**
@@ -62,11 +69,13 @@ let cachedCustomEqual: ((a: unknown, b: unknown) => boolean) | undefined;
62
69
  * @param y The second value to compare.
63
70
  * @returns True if the values are deeply equal, false otherwise.
64
71
  */
65
- export function customFastEqual<T>(x: T, y: T): boolean {
72
+ export async function customFastEqual<T>(x: T, y: T): Promise<boolean> {
66
73
  if (cachedCustomEqual !== undefined) {
67
74
  return cachedCustomEqual(x, y);
68
75
  }
69
76
 
77
+ const { createCustomEqual } = await import("fast-equals");
78
+
70
79
  cachedCustomEqual = createCustomEqual({
71
80
  createCustomConfig: (defaultConfig) => ({
72
81
  areTypedArraysEqual: (a, b, state) => {
package/src/lang.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  * @returns The deep clone of the provided value.
12
12
  */
13
13
  export async function deepClone<T>(value: T): Promise<T> {
14
- const _deepClone = getDeepCloneFunction();
14
+ const _deepClone = await getDeepCloneFunction();
15
15
 
16
16
  return _deepClone<T>(value);
17
17
  }
@@ -24,7 +24,7 @@ export async function deepClone<T>(value: T): Promise<T> {
24
24
  * @returns True if the values are deeply equal, false otherwise.
25
25
  */
26
26
  export async function deepEqual<T>(x: T, y: T): Promise<boolean> {
27
- return customFastEqual(x, y);
27
+ return await customFastEqual(x, y);
28
28
  }
29
29
 
30
30
  /**
package/src/request.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type EventEmitter from "node:events";
2
2
  import type { FileHandle } from "node:fs/promises";
3
3
  import type { ParsedUrlQueryInput } from "node:querystring";
4
- import type * as UndiciT from "undici";
4
+ import type * as Undici from "undici";
5
5
 
6
6
  import { open } from "node:fs/promises";
7
7
  import querystring from "node:querystring";
8
- import stream from "node:stream/promises";
8
+ import { pipeline } from "node:stream/promises";
9
9
 
10
10
  import { ensureError } from "./error.js";
11
11
  import {
@@ -24,18 +24,18 @@ import {
24
24
  handleError,
25
25
  } from "./internal/request.js";
26
26
 
27
- export const DEFAULT_TIMEOUT_IN_MILLISECONDS = 300_000; // Aligned with unidici
27
+ export const DEFAULT_TIMEOUT_IN_MILLISECONDS = 300_000; // Aligned with undici
28
28
  export const DEFAULT_MAX_REDIRECTS = 10;
29
29
  export const DEFAULT_POOL_MAX_CONNECTIONS = 128;
30
30
  export const DEFAULT_USER_AGENT = "Hardhat";
31
31
 
32
- export type Dispatcher = UndiciT.Dispatcher;
33
- export type TestDispatcher = UndiciT.MockAgent;
34
- export type Interceptable = UndiciT.Interceptable;
32
+ export type Dispatcher = Undici.Dispatcher;
33
+ export type TestDispatcher = Undici.MockAgent;
34
+ export type Interceptable = Undici.Interceptable;
35
35
 
36
36
  // We don't load undici on startup because this package is transitively imported
37
37
  // from too many places and it's too complex to optimize case by case.
38
- let undici: typeof UndiciT | undefined;
38
+ let undici: typeof Undici | undefined;
39
39
 
40
40
  /**
41
41
  * Options to configure the dispatcher.
@@ -90,7 +90,7 @@ export interface HttpResponse {
90
90
  export async function getRequest(
91
91
  url: string,
92
92
  requestOptions: RequestOptions = {},
93
- dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions,
93
+ dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
94
94
  ): Promise<HttpResponse> {
95
95
  if (undici === undefined) {
96
96
  undici = await import("undici");
@@ -132,7 +132,7 @@ export async function postJsonRequest(
132
132
  url: string,
133
133
  body: unknown,
134
134
  requestOptions: RequestOptions = {},
135
- dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions,
135
+ dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
136
136
  ): Promise<HttpResponse> {
137
137
  if (undici === undefined) {
138
138
  undici = await import("undici");
@@ -179,7 +179,7 @@ export async function postFormRequest(
179
179
  url: string,
180
180
  body: unknown,
181
181
  requestOptions: RequestOptions = {},
182
- dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions,
182
+ dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
183
183
  ): Promise<HttpResponse> {
184
184
  if (undici === undefined) {
185
185
  undici = await import("undici");
@@ -225,7 +225,7 @@ export async function download(
225
225
  url: string,
226
226
  destination: string,
227
227
  requestOptions: RequestOptions = {},
228
- dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions,
228
+ dispatcherOrDispatcherOptions?: Undici.Dispatcher | DispatcherOptions,
229
229
  ): Promise<void> {
230
230
  let statusCode: number | undefined;
231
231
  let tempFilePath: string | undefined;
@@ -240,7 +240,7 @@ export async function download(
240
240
  url,
241
241
  requestOptions,
242
242
  dispatcherOrDispatcherOptions,
243
- )) as UndiciT.Dispatcher.ResponseData;
243
+ )) as Undici.Dispatcher.ResponseData;
244
244
  const { body } = response;
245
245
  statusCode = response.statusCode;
246
246
 
@@ -257,7 +257,7 @@ export async function download(
257
257
 
258
258
  const fileStream = fileHandle.createWriteStream();
259
259
 
260
- await stream.pipeline(body, fileStream);
260
+ await pipeline(body, fileStream);
261
261
  } finally {
262
262
  // NOTE: Historically, not closing the file handle caused issues on Windows,
263
263
  // for example, when trying to move the file previously written to by this function
@@ -284,9 +284,9 @@ export async function download(
284
284
 
285
285
  /**
286
286
  * Creates a dispatcher based on the provided options.
287
- * If the `proxy` option is set, it creates a {@link UndiciT.ProxyAgent} dispatcher.
288
- * If the `pool` option is set to `true`, it creates a {@link UndiciT.Pool} dispatcher.
289
- * Otherwise, it creates a basic {@link UndiciT.Agent} dispatcher.
287
+ * If the `proxy` option is set, it creates a {@link Undici.ProxyAgent} dispatcher.
288
+ * If the `pool` option is set to `true`, it creates a {@link Undici.Pool} dispatcher.
289
+ * Otherwise, it creates a basic {@link Undici.Agent} dispatcher.
290
290
  *
291
291
  * @param url The url to make requests to.
292
292
  * @param options The options to configure the dispatcher. See {@link DispatcherOptions}.
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
2
+ import fs from "node:fs";
3
+ import { hostname } from "node:os";
4
+ import path from "node:path";
5
5
 
6
6
  import { createDebug } from "./debug.js";
7
7
  import { ensureError, ensureNodeErrnoExceptionError } from "./error.js";
@@ -382,11 +382,11 @@ export class MultiProcessMutex {
382
382
  }
383
383
 
384
384
  // Different hostname — can't verify PID remotely
385
- if (metadata.hostname !== os.hostname()) {
385
+ if (metadata.hostname !== hostname()) {
386
386
  throw new IncompatibleHostnameMultiProcessMutexError(
387
387
  lockPath,
388
388
  metadata.hostname,
389
- os.hostname(),
389
+ hostname(),
390
390
  );
391
391
  }
392
392
 
@@ -475,7 +475,7 @@ export class MultiProcessMutex {
475
475
  #buildMetadata(): LockMetadata {
476
476
  return {
477
477
  pid: process.pid,
478
- hostname: os.hostname(),
478
+ hostname: hostname(),
479
479
  createdAt: Date.now(),
480
480
  ...(process.getuid !== undefined ? { uid: process.getuid() } : {}),
481
481
  platform: process.platform,