Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
Libraries,
} from "../../types.js";
import type { HardhatEthersProvider } from "../hardhat-ethers-provider/hardhat-ethers-provider.js";
import type { HardhatEthersSigner } from "../signers/signers.js";
import type { ethers as EthersT } from "ethers";
Comment on lines 6 to 7
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from a dynamic import to a static import here means the signer implementation (and its transitive deps like ethers) will be loaded as soon as this module is evaluated, even in flows that never call getSigner/getSigners. If the goal is to remove per-call await import(...) overhead but keep lazy loading, consider caching the imported module/class the first time getSigner is called (lazy + cached) rather than importing it unconditionally at top-level.

Copilot uses AI. Check for mistakes.
import type {
Abi,
Expand All @@ -19,6 +18,8 @@ import {
HardhatError,
} from "@nomicfoundation/hardhat-errors";

import { HardhatEthersSigner } from "../signers/signers.js";

interface Link {
sourceName: string;
libraryName: string;
Expand Down Expand Up @@ -68,11 +69,7 @@ export class HardhatHelpers {
}

public async getSigner(address: string): Promise<HardhatEthersSigner> {
const { HardhatEthersSigner: SignerWithAddressImpl } = await import(
"../signers/signers.js"
);

const signerWithAddress = await SignerWithAddressImpl.create(
const signerWithAddress = await HardhatEthersSigner.create(
this.#provider,
this.#networkName,
this.#networkConfig,
Expand Down
11 changes: 3 additions & 8 deletions v-next/hardhat-utils/src/internal/lang.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import type rfdcT from "rfdc";
import rfdc from "rfdc";

import { isObject } from "../lang.js";

let clone: ReturnType<typeof rfdcT> | null = null;
export async function getDeepCloneFunction(): Promise<<T>(input: T) => T> {
const { default: rfdc } = await import("rfdc");

if (clone === null) {
clone = rfdc();
}
const clone = rfdc();

export function getDeepCloneFunction(): <T>(input: T) => T {
return clone;
Comment on lines +1 to 8
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDeepCloneFunction now eagerly imports rfdc and initializes clone at module load (const clone = rfdc()). Because @nomicfoundation/hardhat-utils/lang is imported widely for unrelated helpers (e.g. isObject, sleep), this change makes every consumer pay the rfdc import/init cost even when deepClone is never called, which can regress startup performance and changes when failures would surface (module-load vs first-call).

Consider restoring lazy initialization while keeping deepClone synchronous, e.g. cache clone but only require("rfdc")/initialize it inside getDeepCloneFunction (using createRequire in ESM), or otherwise ensure this eager load is actually on a guaranteed-hot path.

Copilot uses AI. Check for mistakes.
}

Expand Down
6 changes: 2 additions & 4 deletions v-next/hardhat-utils/src/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import {
* @param value The value to clone.
* @returns The deep clone of the provided value.
*/
export async function deepClone<T>(value: T): Promise<T> {
const _deepClone = await getDeepCloneFunction();

return _deepClone<T>(value);
export function deepClone<T>(value: T): T {
return getDeepCloneFunction()<T>(value);
}
Comment on lines +13 to 15
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing deepClone from async (returning Promise<T>) to a synchronous function (returning T) is a breaking public API change for @nomicfoundation/hardhat-utils/lang. Any external consumers using promise chaining (e.g. deepClone(x).then(...)) or typed as Promise will break.

If the sync behavior is required for perf, consider introducing a new sync API (and keeping the existing async deepClone as a thin wrapper for backwards compatibility), or ensure this is released with an appropriate major-version bump and explicit migration notes.

Copilot uses AI. Check for mistakes.

/**
Expand Down
24 changes: 12 additions & 12 deletions v-next/hardhat-utils/test/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("lang", () => {
describe("deepClone", () => {
it("Should clone an object", async () => {
const obj = { a: 1, b: 2, c: { d: 3 } };
const clonedObj = await deepClone(obj);
const clonedObj = deepClone(obj);

assert.deepEqual(clonedObj, obj);
assert.notEqual(clonedObj, obj);
Expand All @@ -27,7 +27,7 @@ describe("lang", () => {

it("Should clone an array", async () => {
const arr = [1, 2, [3]];
const clonedArr = await deepClone(arr);
const clonedArr = deepClone(arr);

assert.deepEqual(clonedArr, arr);
assert.notEqual(clonedArr, arr);
Expand All @@ -37,47 +37,47 @@ describe("lang", () => {

it("Should clone a string", async () => {
const str = "hello";
const clonedStr = await deepClone(str);
const clonedStr = deepClone(str);

assert.equal(clonedStr, str);
expectTypeOf(clonedStr).toBeString();
});

it("Should clone a number", async () => {
const num = 42;
const clonedNum = await deepClone(num);
const clonedNum = deepClone(num);

assert.equal(clonedNum, num);
expectTypeOf(clonedNum).toBeNumber();
});

it("Should clone null", async () => {
const n = null;
const clonedN = await deepClone(n);
const clonedN = deepClone(n);

assert.equal(clonedN, n);
expectTypeOf(clonedN).toBeNull();
});

it("Should clone undefined", async () => {
const u = undefined;
const clonedU = await deepClone(u);
const clonedU = deepClone(u);

assert.equal(clonedU, u);
expectTypeOf(clonedU).toBeUndefined();
});

it("Should reference a function", async () => {
const fn = () => {};
const clonedFn = await deepClone(fn);
const clonedFn = deepClone(fn);

assert.equal(clonedFn, fn);
expectTypeOf(clonedFn).toEqualTypeOf<typeof fn>();
});

it("Should clone a Date", async () => {
const date = new Date();
const clonedDate = await deepClone(date);
const clonedDate = deepClone(date);

assert.deepEqual(clonedDate, date);
assert.notEqual(clonedDate, date);
Expand All @@ -89,7 +89,7 @@ describe("lang", () => {
["a", 1],
["b", 2],
]);
const clonedMap = await deepClone(map);
const clonedMap = deepClone(map);

assert.deepEqual(clonedMap, map);
assert.notEqual(clonedMap, map);
Expand All @@ -98,7 +98,7 @@ describe("lang", () => {

it("Should clone a Set", async () => {
const set = new Set([1, 2]);
const clonedSet = await deepClone(set);
const clonedSet = deepClone(set);

assert.deepEqual(clonedSet, set);
assert.notEqual(clonedSet, set);
Expand All @@ -107,7 +107,7 @@ describe("lang", () => {

it("Should clone a Buffer", async () => {
const buffer = Buffer.from("test");
const clonedBuffer = await deepClone(buffer);
const clonedBuffer = deepClone(buffer);

const expected: { [key: number]: number } = {};
for (let i = 0; i < buffer.length; i++) {
Expand All @@ -124,7 +124,7 @@ describe("lang", () => {
return arguments;
}
const args = testFunc(1, "2", false);
const clonedArgs = await deepClone(args);
const clonedArgs = deepClone(args);

assert.deepEqual(clonedArgs, { "0": 1, "1": "2", "2": false });
expectTypeOf(clonedArgs).toHaveProperty("0").toBeNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export function resolveCoinbase(
export async function resolveChainDescriptors(
chainDescriptors: ChainDescriptorsUserConfig | undefined,
): Promise<ChainDescriptorsConfig> {
const resolvedChainDescriptors: ChainDescriptorsConfig = await deepClone(
const resolvedChainDescriptors: ChainDescriptorsConfig = deepClone(
DEFAULT_CHAIN_DESCRIPTORS,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { RequestHandler } from "../request-handlers/types.js";
import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization";

import { isJsonRpcResponse } from "../json-rpc.js";
import { createHandlersArray } from "../request-handlers/handlers-array.js";

export default async (): Promise<Partial<NetworkHooks>> => {
// This map is essential for managing multiple network connections in Hardhat V3.
Expand All @@ -37,10 +38,6 @@ export default async (): Promise<Partial<NetworkHooks>> => {
nextJsonRpcRequest: JsonRpcRequest,
) => Promise<JsonRpcResponse>,
) {
const { createHandlersArray } = await import(
"../request-handlers/handlers-array.js"
);

const requestHandlers = await initializationMutex.exclusiveRun(
async () => {
let handlersPerConnection =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class CompilationJobImplementation implements CompilationJob {
// from other files (e.g. new Foo()), and it won't output its bytecode if
// it's not asked for. This would prevent EDR from doing any runtime
// analysis.
const outputSelection = await deepClone(settings.outputSelection ?? {});
const outputSelection = deepClone(settings.outputSelection ?? {});
outputSelection["*"] ??= {};
outputSelection["*"][""] ??= [];
outputSelection["*"]["*"] ??= [];
Expand Down
Loading