diff --git a/.changeset/hardhat-verify.md b/.changeset/hardhat-verify.md new file mode 100644 index 00000000000..134bb87ef90 --- /dev/null +++ b/.changeset/hardhat-verify.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-verify": patch +--- + +Optimize imports. diff --git a/.changeset/imports-hardhat-ethers.md b/.changeset/imports-hardhat-ethers.md new file mode 100644 index 00000000000..fe1f3f5c89d --- /dev/null +++ b/.changeset/imports-hardhat-ethers.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-ethers": patch +--- + +Optimize imports. diff --git a/.changeset/imports-hardhat-utils.md b/.changeset/imports-hardhat-utils.md new file mode 100644 index 00000000000..4462aaaf9c6 --- /dev/null +++ b/.changeset/imports-hardhat-utils.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-utils": patch +--- + +Optimize imports. diff --git a/.changeset/imports-hardhat.md b/.changeset/imports-hardhat.md new file mode 100644 index 00000000000..b3b7a0bf78e --- /dev/null +++ b/.changeset/imports-hardhat.md @@ -0,0 +1,5 @@ +--- +"hardhat": patch +--- + +Optimize imports. diff --git a/.changeset/imports-ignition.md b/.changeset/imports-ignition.md new file mode 100644 index 00000000000..ceb0bf5d962 --- /dev/null +++ b/.changeset/imports-ignition.md @@ -0,0 +1,7 @@ +--- +"@nomicfoundation/ignition-core": patch +"@nomicfoundation/hardhat-ignition-viem": patch +"@nomicfoundation/hardhat-ignition": patch +--- + +Optimize imports. diff --git a/.changeset/imports-ledger.md b/.changeset/imports-ledger.md new file mode 100644 index 00000000000..98d6b66cdeb --- /dev/null +++ b/.changeset/imports-ledger.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-ledger": patch +--- + +Optimize imports. diff --git a/.changeset/imports-mocha.md b/.changeset/imports-mocha.md new file mode 100644 index 00000000000..b60f0f8448f --- /dev/null +++ b/.changeset/imports-mocha.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-mocha": patch +--- + +Optimize imports. diff --git a/.changeset/imports-node-test-reporter.md b/.changeset/imports-node-test-reporter.md new file mode 100644 index 00000000000..d3b1b579bff --- /dev/null +++ b/.changeset/imports-node-test-reporter.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-node-test-reporter": patch +--- + +Optimize imports. diff --git a/.changeset/imports-typechain.md b/.changeset/imports-typechain.md new file mode 100644 index 00000000000..869618e1770 --- /dev/null +++ b/.changeset/imports-typechain.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-typechain": patch +--- + +Optimize imports. diff --git a/CLAUDE.md b/CLAUDE.md index 3219815f2e1..5195457e664 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,15 +24,25 @@ v-next/hardhat - core logic and cli **Package structure** — Exported code and types (via `package#exports`) live under `src/`, non-exported internals under `src/internal/`. -**Lazy loading external packages** — Hardhat optimizes startup time. Follow this strictly: - -- Top-level imports allowed for: `node:path`, `node:util`, `chalk`, `semver`, `debug` and `import type` -- Everything else: use `await import()` inside the function that needs it - **`hardhat-utils` first** — Before using `node:fs` or writing a utility, check `@nomicfoundation/hardhat-utils`. It covers fs, crypto, hex, error handling, and more. **Errors** — Only throw `HardhatError`. Never `throw new Error()`. Use `HardhatError.isHardhatError()` (not `instanceof`) and `ensureError()` in catch clauses. `./scripts` is exempt. +### Using imports correctly in Hardhat 3 + +Use `await import` only if one of these conditions is met: + +1. The file with the import is part of the `hardhat` package, is always imported at startup (i.e. imported by `hardhat`'s `src/internal/cli/main.ts` or `src/index.ts`, directly or transitively), and the imported module isn't always used (e.g. `./init/init.js` in `hardhat`'s `main.ts`) +2. The import path is dynamic (e.g. the user config path) +3. The file is dynamically loaded by a wrapper that exports the same interface that loads it on first access (mostly used for HRE extensions, e.g. `src/internal/builtin-plugins/network-manager/hook-handlers/hre.ts` in `hardhat`) +4. The dynamic import is used to avoid a circular dependency (e.g. importing the `HRE` at runtime) +5. The import has to happen at a certain point in time (mostly used for import side-effects, e.g. `await import(...)` without doing anything with the imported module) +6. If there's a comment justifying it, and the imported module is cached (i.e. not running `await import(...)` every time, but instead doing something like `if (cachedModule === undefined) { cachedModule = await import(...) }`). Some code duplication in this case is acceptable if that avoids adding unnecessary async logic (e.g. avoid `const module = await getModule()` to avoid repeating just a few conditionals). + +The only accepted imports in the `index.ts` file of plugins (both built-in and external) are their `type-extension`, types and `enums` from `hardhat`, and `hardhat/config`, and potentially a simple file with constants. They can also import files that follow these same rules and restrictions. Everything else should be imported by a callback registered in the plugin object. + +Test files are free to use `await import` freely. + ## Development workflow After modifying a package, within the package run: diff --git a/docs/engineering-guidelines.md b/docs/engineering-guidelines.md index f17c218580c..b869aa9569d 100644 --- a/docs/engineering-guidelines.md +++ b/docs/engineering-guidelines.md @@ -14,7 +14,7 @@ The intention behind the current separation of part of `hardhat` into the `core/ The entry-point of the plugins are meant to only export a description of the plugin, and not implement any functionality. Please don’t import anything in those files. -The only accepted imports are: their `type-extension.ts`, hardhat types, and the `"/config"` module from hardhat. +The only accepted imports in the `index.ts` file of plugins (both built-in and external) are their `type-extension`, types and `enums` from `hardhat`, and `hardhat/config`, and potentially a simple file with constants. They can also import files that follow these same rules and restrictions. Everything else should be imported by a callback registered in the plugin object. ## A3: Always initialize the HRE using the `hre-initialization` module inside of `hardhat` @@ -92,12 +92,18 @@ This is also valid for tests, where things can easily become super brittle by us ## GC3: top-level imports vs dynamic imports -Hardhat v3’s plugin system was designed so that plugin hooks and task actions are lazy loaded. This means that within them, we can use top-level imports, and we aren’t restricted to dynamic imports, like we were in v2. +Hardhat 3's plugin system already handles lazy loading — plugins place business logic behind dynamic imports. Dependencies, conditional dependencies, hook handler factories, and task actions are all dynamically imported. This means most imports should be top-level imports, except for a few cases: -Please apply your own criteria when: +Use `await import` only if one of these conditions is met: -- Importing dependencies known to load slowly. -- The import is in a file that’s always loaded (e.g. it gets loaded if you run `pnpm hardhat --help`). +1. The file with the import is part of the `hardhat` package, is always imported at startup (i.e. imported by `hardhat`'s `src/internal/cli/main.ts` or `src/index.ts`, directly or transitively), and the imported module isn't always used (e.g. `./init/init.js` in `hardhat`'s `main.ts`) +2. The import path is dynamic (e.g. the user config path) +3. The file is dynamically loaded by a wrapper that exports the same interface that loads it on first access (mostly used for HRE extensions, e.g. `src/internal/builtin-plugins/network-manager/hook-handlers/hre.ts` in `hardhat`) +4. The dynamic import is used to avoid a circular dependency (e.g. importing the `HRE` at runtime) +5. The import has to happen at a certain point in time (mostly used for import side-effects, e.g. `await import(...)` without doing anything with the imported module) +6. If there's a comment justifying it, and the imported module is cached (i.e. not running `await import(...)` every time, but instead doing something like `if (cachedModule === undefined) { cachedModule = await import(...) }`). Some code duplication in this case is acceptable if that avoids adding unnecessary async logic (e.g. avoid `const module = await getModule()` to avoid repeating just a few conditionals). + +Test files are free to use `await import` freely. # Testing guidelines diff --git a/v-next/hardhat-ethers/src/internal/hardhat-helpers/hardhat-helpers.ts b/v-next/hardhat-ethers/src/internal/hardhat-helpers/hardhat-helpers.ts index 4be8c28db25..ba9df13aac1 100644 --- a/v-next/hardhat-ethers/src/internal/hardhat-helpers/hardhat-helpers.ts +++ b/v-next/hardhat-ethers/src/internal/hardhat-helpers/hardhat-helpers.ts @@ -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"; import type { Abi, @@ -18,6 +17,9 @@ import { assertHardhatInvariant, HardhatError, } from "@nomicfoundation/hardhat-errors"; +import { Contract, ContractFactory, isAddress, isAddressable } from "ethers"; + +import { HardhatEthersSigner } from "../signers/signers.js"; interface Link { sourceName: string; @@ -68,11 +70,7 @@ export class HardhatHelpers { } public async getSigner(address: string): Promise { - const { HardhatEthersSigner: SignerWithAddressImpl } = await import( - "../signers/signers.js" - ); - - const signerWithAddress = await SignerWithAddressImpl.create( + const signerWithAddress = await HardhatEthersSigner.create( this.#provider, this.#networkName, this.#networkConfig, @@ -179,8 +177,6 @@ export class HardhatHelpers { return this.getContractAtFromArtifact(artifact, address, signer); } - const ethers = await import("ethers"); - if (signer === undefined) { const signers = await this.getSigners(); signer = signers[0]; @@ -192,13 +188,13 @@ export class HardhatHelpers { signer !== undefined ? signer : this.#provider; let resolvedAddress; - if (ethers.isAddressable(address)) { + if (isAddressable(address)) { resolvedAddress = await address.getAddress(); } else { resolvedAddress = address; } - return new ethers.Contract(resolvedAddress, nameOrAbi, signerOrProvider); + return new Contract(resolvedAddress, nameOrAbi, signerOrProvider); } public async getContractAtFromArtifact( @@ -206,8 +202,6 @@ export class HardhatHelpers { address: string | EthersT.Addressable, signer?: EthersT.Signer, ): Promise { - const ethers = await import("ethers"); - if (!this.#isArtifact(artifact)) { throw new HardhatError( HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ARTIFACT_FOR_FACTORY, @@ -220,13 +214,13 @@ export class HardhatHelpers { } let resolvedAddress; - if (ethers.isAddressable(address)) { + if (isAddressable(address)) { resolvedAddress = await address.getAddress(); } else { resolvedAddress = address; } - let contract = new ethers.Contract(resolvedAddress, artifact.abi, signer); + let contract = new Contract(resolvedAddress, artifact.abi, signer); if (contract.runner === null) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- EthersT.Contract overlaps with EthersT.BaseContract @@ -315,8 +309,6 @@ export class HardhatHelpers { } async #collectLibrariesAndLink(artifact: Artifact, libraries: Libraries) { - const ethers = await import("ethers"); - const neededLibraries: Array<{ sourceName: string; libName: string; @@ -334,13 +326,13 @@ export class HardhatHelpers { libraries, )) { let resolvedAddress: string; - if (ethers.isAddressable(linkedLibraryAddress)) { + if (isAddressable(linkedLibraryAddress)) { resolvedAddress = await linkedLibraryAddress.getAddress(); } else { resolvedAddress = linkedLibraryAddress; } - if (!ethers.isAddress(resolvedAddress)) { + if (!isAddress(resolvedAddress)) { throw new HardhatError( HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ADDRESS_TO_LINK_CONTRACT_TO_LIBRARY, { @@ -465,8 +457,6 @@ export class HardhatHelpers { bytecode: EthersT.BytesLike, signer?: EthersT.Signer, ): Promise> { - const { ContractFactory } = await import("ethers"); - if (signer === undefined) { // const signers = await hre.ethers.getSigners(); const signers = await this.getSigners(); diff --git a/v-next/hardhat-ethers/src/internal/initialization.ts b/v-next/hardhat-ethers/src/internal/initialization.ts index 3f7cb53e159..27ad740b98f 100644 --- a/v-next/hardhat-ethers/src/internal/initialization.ts +++ b/v-next/hardhat-ethers/src/internal/initialization.ts @@ -3,6 +3,8 @@ import type { ArtifactManager } from "hardhat/types/artifacts"; import type { NetworkConfig } from "hardhat/types/config"; import type { EthereumProvider } from "hardhat/types/providers"; +import * as ethers from "ethers"; + import { HardhatEthersProvider } from "./hardhat-ethers-provider/hardhat-ethers-provider.js"; import { HardhatHelpers } from "./hardhat-helpers/hardhat-helpers.js"; @@ -12,8 +14,6 @@ export async function initializeEthers( networkConfig: NetworkConfig, artifactManager: ArtifactManager, ): Promise { - const ethers = await import("ethers"); - const provider = new HardhatEthersProvider( ethereumProvider, networkName, diff --git a/v-next/hardhat-ethers/src/internal/signers/derive-private-key.ts b/v-next/hardhat-ethers/src/internal/signers/derive-private-key.ts index dc777d4106b..c3b8d7052f8 100644 --- a/v-next/hardhat-ethers/src/internal/signers/derive-private-key.ts +++ b/v-next/hardhat-ethers/src/internal/signers/derive-private-key.ts @@ -1,3 +1,5 @@ +import type * as Bip39T from "ethereum-cryptography/bip39"; +import type { HDKey as HDKeyT } from "ethereum-cryptography/hdkey"; import type { EdrNetworkHDAccountsConfig, HttpNetworkHDAccountsConfig, @@ -6,6 +8,12 @@ import type { import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes"; +// ethereum-cryptography/bip39 is known to be slow to load, so we lazy load it +let mnemonicToSeedSync: typeof Bip39T.mnemonicToSeedSync | undefined; + +// ethereum-cryptography/hdkey is known to be slow to load, so we lazy load it +let HDKey: typeof HDKeyT | undefined; + const HD_PATH_REGEX = /^m(:?\/\d+'?)+\/?$/; export async function derivePrivateKeys( @@ -67,13 +75,26 @@ async function deriveKeyFromMnemonicAndPath( hdPath: string, passphrase: string, ): Promise { - const { mnemonicToSeedSync } = await import("ethereum-cryptography/bip39"); - const { HDKey } = await import("ethereum-cryptography/hdkey"); - // NOTE: If mnemonic has space or newline at the beginning or end, it will be trimmed. // This is because mnemonic containing them may generate different private keys. const trimmedMnemonic = mnemonic.trim(); + if (mnemonicToSeedSync === undefined) { + const { mnemonicToSeedSync: importedMnemonicToSeedSync } = await import( + "ethereum-cryptography/bip39" + ); + + mnemonicToSeedSync = importedMnemonicToSeedSync; + } + + if (HDKey === undefined) { + const { HDKey: ImportedHDKey } = await import( + "ethereum-cryptography/hdkey" + ); + + HDKey = ImportedHDKey; + } + const seed = mnemonicToSeedSync(trimmedMnemonic, passphrase); const masterKey = HDKey.fromMasterSeed(seed); diff --git a/v-next/hardhat-ignition-viem/src/internal/viem-ignition-helper.ts b/v-next/hardhat-ignition-viem/src/internal/viem-ignition-helper.ts index 45c4e1773e4..0463167dfa4 100644 --- a/v-next/hardhat-ignition-viem/src/internal/viem-ignition-helper.ts +++ b/v-next/hardhat-ignition-viem/src/internal/viem-ignition-helper.ts @@ -44,6 +44,7 @@ import { deploy, isContractFuture, } from "@nomicfoundation/ignition-core"; +import { getContract } from "viem"; export class ViemIgnitionHelperImpl implements ViemIgnitionHelper @@ -347,8 +348,7 @@ export class ViemIgnitionHelperImpl ); } - const viem = await import("viem"); - const contract = viem.getContract({ + const contract = getContract({ address: ViemIgnitionHelperImpl.#ensureAddressFormat( deployedContract.address, ), diff --git a/v-next/hardhat-ignition/src/helpers/read-deployment-parameters.ts b/v-next/hardhat-ignition/src/helpers/read-deployment-parameters.ts index c54c22a3463..fc19e4a67ac 100644 --- a/v-next/hardhat-ignition/src/helpers/read-deployment-parameters.ts +++ b/v-next/hardhat-ignition/src/helpers/read-deployment-parameters.ts @@ -2,6 +2,7 @@ import type { DeploymentParameters } from "@nomicfoundation/ignition-core"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { readUtf8File } from "@nomicfoundation/hardhat-utils/fs"; +import json5 from "json5"; import { bigintReviver } from "../internal/utils/bigintReviver.js"; @@ -9,12 +10,9 @@ export async function readDeploymentParameters( filepath: string, ): Promise { try { - const { - default: { parse }, - } = await import("json5"); const rawFile = await readUtf8File(filepath); - return await parse(rawFile.toString(), bigintReviver); + return await json5.parse(rawFile.toString(), bigintReviver); } catch (e) { if (HardhatError.isHardhatError(e)) { throw e; diff --git a/v-next/hardhat-ignition/src/internal/tasks/deploy.ts b/v-next/hardhat-ignition/src/internal/tasks/deploy.ts index a8e3fd2db8a..c082e07f6a5 100644 --- a/v-next/hardhat-ignition/src/internal/tasks/deploy.ts +++ b/v-next/hardhat-ignition/src/internal/tasks/deploy.ts @@ -18,6 +18,7 @@ import { } from "@nomicfoundation/hardhat-utils/fs"; import { deploy } from "@nomicfoundation/ignition-core"; import chalk from "chalk"; +import json5 from "json5"; import Prompt from "prompts"; import { HardhatArtifactResolver } from "../../helpers/hardhat-artifact-resolver.js"; @@ -276,11 +277,7 @@ async function resolveParametersString( paramString: string, ): Promise { try { - const { - default: { parse }, - } = await import("json5"); - - return await parse(paramString, bigintReviver); + return await json5.parse(paramString, bigintReviver); } catch (e) { if (HardhatError.isHardhatError(e)) { throw e; diff --git a/v-next/hardhat-ignition/src/internal/ui/helpers/calculate-list-transactions-display.ts b/v-next/hardhat-ignition/src/internal/ui/helpers/calculate-list-transactions-display.ts index d29cff73fad..40d1e745780 100644 --- a/v-next/hardhat-ignition/src/internal/ui/helpers/calculate-list-transactions-display.ts +++ b/v-next/hardhat-ignition/src/internal/ui/helpers/calculate-list-transactions-display.ts @@ -1,6 +1,7 @@ import type { ListTransactionsResult } from "@nomicfoundation/ignition-core"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; +import json5 from "json5"; export async function calculateListTransactionsDisplay( deploymentId: string, @@ -34,11 +35,7 @@ export async function calculateListTransactionsDisplay( } if (transaction.params !== undefined) { - const { - default: { stringify }, - } = await import("json5"); - - text += ` - Params: ${stringify( + text += ` - Params: ${json5.stringify( transaction.params, transactionDisplaySerializeReplacer, )}\n`; diff --git a/v-next/hardhat-ledger/src/internal/create-tx.ts b/v-next/hardhat-ledger/src/internal/create-tx.ts index 57057291f0f..975ea5258c4 100644 --- a/v-next/hardhat-ledger/src/internal/create-tx.ts +++ b/v-next/hardhat-ledger/src/internal/create-tx.ts @@ -1,4 +1,5 @@ import type { RpcTransactionRequest } from "@nomicfoundation/hardhat-zod-utils/rpc"; +import type * as MicroEthSignerT from "micro-eth-signer"; import { assertHardhatInvariant, @@ -10,14 +11,24 @@ import { bytesToHexString, bytesToNumber, } from "@nomicfoundation/hardhat-utils/bytes"; -import { addr, Transaction } from "micro-eth-signer"; + +// micro-eth-signer is known to be slow to load, so we lazy load it +let microEthSigner: typeof MicroEthSignerT | undefined; const STRICT_MODE = false; -export function createTx( +export async function createTx( txRequest: RpcTransactionRequest, chainId: bigint, -): Transaction<"eip7702" | "eip1559" | "eip2930" | "legacy"> { +): Promise< + MicroEthSignerT.Transaction<"eip7702" | "eip1559" | "eip2930" | "legacy"> +> { + if (microEthSigner === undefined) { + microEthSigner = await import("micro-eth-signer"); + } + + const { addr, Transaction } = microEthSigner; + const checksummedAddress = addr.addChecksum( bytesToHexString(txRequest.to ?? new Uint8Array()), true, diff --git a/v-next/hardhat-ledger/src/internal/handler.ts b/v-next/hardhat-ledger/src/internal/handler.ts index 877560c9397..2fee11f2b97 100644 --- a/v-next/hardhat-ledger/src/internal/handler.ts +++ b/v-next/hardhat-ledger/src/internal/handler.ts @@ -5,6 +5,9 @@ import type { JsonRpcRequest, JsonRpcResponse, } from "hardhat/types/providers"; +import type * as MicroEthSignerT from "micro-eth-signer"; +import type * as MicroEthSignerTypedDataT from "micro-eth-signer/typed-data"; +import type * as MicroEthSignerUtilsT from "micro-eth-signer/utils"; import { DisconnectedDevice, @@ -38,9 +41,6 @@ import { validateParams, } from "@nomicfoundation/hardhat-zod-utils/rpc"; import debug from "debug"; -import { Transaction } from "micro-eth-signer"; -import * as typed from "micro-eth-signer/typed-data"; -import { add0x, initSig } from "micro-eth-signer/utils"; import * as cache from "./cache.js"; import { createTx } from "./create-tx.js"; @@ -54,6 +54,11 @@ const APP_NOT_OPEN_STATUS_CODE = 0x6511; const log = debug("hardhat:hardhat-ledger:handler"); +// micro-eth-signer is known to be slow to load, so we lazy load it +let microEthSigner: typeof MicroEthSignerT | undefined; +let microEthSignerTypedData: typeof MicroEthSignerTypedDataT | undefined; +let microEthSignerUtils: typeof MicroEthSignerUtilsT | undefined; + interface RetryState { reconnection: number; deviceNotReady: number; @@ -576,6 +581,10 @@ export class LedgerHandler { } async #toRpcSig(sig: Signature): Promise { + if (microEthSignerUtils === undefined) { + microEthSignerUtils = await import("micro-eth-signer/utils"); + } + const recovery = this.#calculateSigRecovery(sig.v - 27); assertHardhatInvariant( @@ -583,7 +592,7 @@ export class LedgerHandler { `Invalid recovery value: ${recovery}. It should be either 0 or 1.`, ); - const nobleSig = initSig( + const nobleSig = microEthSignerUtils.initSig( { r: toBigInt(`0x${sig.r}`), s: toBigInt(`0x${sig.s}`) }, recovery, ); @@ -591,7 +600,7 @@ export class LedgerHandler { const hex64 = nobleSig.toCompactHex(); const vByte = recovery === 0 ? "1b" : "1c"; - return add0x(hex64 + vByte); + return microEthSignerUtils.add0x(hex64 + vByte); } #calculateSigRecovery(v: number): number { @@ -660,12 +669,16 @@ export class LedgerHandler { ); } + if (microEthSignerTypedData === undefined) { + microEthSignerTypedData = await import("micro-eth-signer/typed-data"); + } + const { types, domain, message, primaryType } = typedMessage; /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- A type assertion is necessary because there is no type overlap between the `domain` imported from `@ledgerhq` and the parameter type expected by the function imported from `micro-eth-signer`. */ - const enc = typed.encoder(types, domain as any); + const enc = microEthSignerTypedData.encoder(types, domain as any); /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- A type assertion is necessary because there is no type overlap between the `domain` imported from `@ledgerhq` @@ -765,7 +778,11 @@ export class LedgerHandler { "chainId should be defined", ); - const unsignedTx = createTx(txRequest, this.#chainId); + if (microEthSigner === undefined) { + microEthSigner = await import("micro-eth-signer"); + } + + const unsignedTx = await createTx(txRequest, this.#chainId); const txToSign = unsignedTx.toHex(false).substring(2); @@ -780,7 +797,7 @@ export class LedgerHandler { return this.#eth.signTransaction(path, txToSign, resolution); }); - const signedTx = new Transaction(unsignedTx.type, { + const signedTx = new microEthSigner.Transaction(unsignedTx.type, { ...unsignedTx.raw, r: toBigInt(normalizeHexString(signature.r)), s: toBigInt(normalizeHexString(signature.s)), diff --git a/v-next/hardhat-mocha/src/task-action.ts b/v-next/hardhat-mocha/src/task-action.ts index f201d8fb4d7..80469b6f406 100644 --- a/v-next/hardhat-mocha/src/task-action.ts +++ b/v-next/hardhat-mocha/src/task-action.ts @@ -11,6 +11,7 @@ import { setGlobalOptionsAsEnvVariables } from "@nomicfoundation/hardhat-utils/e import { getAllFilesMatching } from "@nomicfoundation/hardhat-utils/fs"; import debug from "debug"; import { errorResult, successfulResult } from "hardhat/utils/result"; +import Mocha from "mocha"; import { createPerformanceTracker } from "./performance.js"; @@ -137,12 +138,12 @@ const testWithHardhat: NewTaskActionFunction = async ( .map((href) => `--import "${href}"`) .join(" "); } else { - // Import the handler directly when not running in parallel mode + // Import the handler directly when not running in parallel mode. + // This must be a dynamic import because it's loaded for its side-effects + // at this specific point in the mocha setup flow. await import(unhandledRejectionHookPath); } - const { default: Mocha } = await import("mocha"); - const mochaConfig: MochaOptions = { ...hre.config.test.mocha }; if (grep !== undefined && grep !== "") { diff --git a/v-next/hardhat-node-test-reporter/src/github-actions.ts b/v-next/hardhat-node-test-reporter/src/github-actions.ts index 44d56070413..dc0a4824ee1 100644 --- a/v-next/hardhat-node-test-reporter/src/github-actions.ts +++ b/v-next/hardhat-node-test-reporter/src/github-actions.ts @@ -1,4 +1,5 @@ import type { TestEventData } from "./types.js"; +import type * as GithubActionsCoreT from "@actions/core"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -6,6 +7,9 @@ import { fileURLToPath } from "node:url"; import { formatError } from "./error-formatting.js"; import { cleanupTestFailError } from "./node-test-error-utils.js"; +// We don't load github actions core on local runs +let core: typeof GithubActionsCoreT | undefined; + export async function annotatePR( event: TestEventData["test:fail"], ): Promise { @@ -16,6 +20,10 @@ export async function annotatePR( return; } + if (core === undefined) { + core = await import("@actions/core"); + } + const error = event.details.error; const location = getLocation(error); @@ -23,8 +31,6 @@ export async function annotatePR( return; } - const core = await import("@actions/core"); - core.error(formatError(error), { file: location.file, startLine: location.line, diff --git a/v-next/hardhat-typechain/src/internal/generate-types.ts b/v-next/hardhat-typechain/src/internal/generate-types.ts index 1feff35fa62..aa5baba29c6 100644 --- a/v-next/hardhat-typechain/src/internal/generate-types.ts +++ b/v-next/hardhat-typechain/src/internal/generate-types.ts @@ -6,6 +6,8 @@ import path from "node:path"; import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; import debug from "debug"; +import { runTypeChain } from "typechain"; +import { outputTransformers as typechainOutputTransformers } from "typechain/dist/codegen/outputTransformers/index.js"; const log = debug("hardhat:typechain:generate-types"); @@ -22,14 +24,8 @@ export async function generateTypes( return; } - const { runTypeChain } = await import("typechain"); - - const { outputTransformers } = await import( - "typechain/dist/codegen/outputTransformers/index.js" - ); - - removePrettierTransformerIfPresent(outputTransformers); - addCompiledFilesTransformerIfAbsent(outputTransformers); + removePrettierTransformerIfPresent(typechainOutputTransformers); + addCompiledFilesTransformerIfAbsent(typechainOutputTransformers); // Normalize paths to use forward slashes for TypeChain compatibility on Windows // This is necessary because TypeChain expects forward-slash paths, diff --git a/v-next/hardhat-utils/src/internal/global-dir.ts b/v-next/hardhat-utils/src/internal/global-dir.ts index 417a799b96f..26b88c62156 100644 --- a/v-next/hardhat-utils/src/internal/global-dir.ts +++ b/v-next/hardhat-utils/src/internal/global-dir.ts @@ -1,10 +1,9 @@ -import type envPaths from "env-paths"; +import envPaths from "env-paths"; export const HARDHAT_PACKAGE_NAME = "hardhat"; export async function generatePaths( packageName: string, ): Promise { - const { default: envPaths } = await import("env-paths"); return envPaths(packageName); } diff --git a/v-next/hardhat-utils/src/internal/lang.ts b/v-next/hardhat-utils/src/internal/lang.ts index 18cff830ae7..f5518295e92 100644 --- a/v-next/hardhat-utils/src/internal/lang.ts +++ b/v-next/hardhat-utils/src/internal/lang.ts @@ -1,11 +1,10 @@ -import type rfdcT from "rfdc"; +import { createCustomEqual } from "fast-equals"; +import rfdc from "rfdc"; import { isObject } from "../lang.js"; -let clone: ReturnType | null = null; -export async function getDeepCloneFunction(): Promise<(input: T) => T> { - const { default: rfdc } = await import("rfdc"); - +let clone: ReturnType | null = null; +export function getDeepCloneFunction(): (input: T) => T { if (clone === null) { clone = rfdc(); } @@ -61,15 +60,13 @@ let cachedCustomEqual: ((a: unknown, b: unknown) => boolean) | undefined; * * @param x The first value to compare. * @param y The second value to compare. - * @returns A promise that resolves to true if the values are deeply equal, false otherwise. + * @returns True if the values are deeply equal, false otherwise. */ -export async function customFastEqual(x: T, y: T): Promise { +export function customFastEqual(x: T, y: T): boolean { if (cachedCustomEqual !== undefined) { return cachedCustomEqual(x, y); } - const { createCustomEqual } = await import("fast-equals"); - cachedCustomEqual = createCustomEqual({ createCustomConfig: (defaultConfig) => ({ areTypedArraysEqual: (a, b, state) => { diff --git a/v-next/hardhat-utils/src/internal/request.ts b/v-next/hardhat-utils/src/internal/request.ts index ecf5afd59d8..d7aa2ab414b 100644 --- a/v-next/hardhat-utils/src/internal/request.ts +++ b/v-next/hardhat-utils/src/internal/request.ts @@ -1,6 +1,6 @@ import type { DispatcherOptions, RequestOptions } from "../request.js"; import type EventEmitter from "node:events"; -import type UndiciT from "undici"; +import type * as UndiciT from "undici"; import crypto from "node:crypto"; import path from "node:path"; @@ -17,6 +17,10 @@ import { ResponseStatusCodeError, } from "../request.js"; +// We don't load undici on startup because this package is transitively imported +// from too many places and it's too complex to optimize case by case. +let undici: typeof UndiciT | undefined; + export async function generateTempFilePath(filePath: string): Promise { const { dir, ext, name } = path.parse(filePath); @@ -40,9 +44,12 @@ export async function getBaseRequestOptions( headers: Record; throwOnError: true; }> { - const { Dispatcher } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } + const dispatcher = - dispatcherOrDispatcherOptions instanceof Dispatcher + dispatcherOrDispatcherOptions instanceof undici.Dispatcher ? dispatcherOrDispatcherOptions : await getDispatcher(requestUrl, dispatcherOrDispatcherOptions); @@ -90,9 +97,11 @@ export async function getProxyDispatcher( proxy: string, options: Omit, ): Promise { - const { ProxyAgent } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } - return new ProxyAgent({ + return new undici.ProxyAgent({ uri: proxy, ...options, }); @@ -102,18 +111,22 @@ export async function getPoolDispatcher( requestUrl: string, options: UndiciT.Pool.Options, ): Promise { - const { Pool } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } const parsedUrl = new URL(requestUrl); - return new Pool(parsedUrl.origin, options); + return new undici.Pool(parsedUrl.origin, options); } export async function getBasicDispatcher( options: UndiciT.Agent.Options, ): Promise { - const { Agent } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } - return new Agent(options); + return new undici.Agent(options); } export function getBaseDispatcherOptions( diff --git a/v-next/hardhat-utils/src/lang.ts b/v-next/hardhat-utils/src/lang.ts index 8b8089b646d..692b16d16e9 100644 --- a/v-next/hardhat-utils/src/lang.ts +++ b/v-next/hardhat-utils/src/lang.ts @@ -11,7 +11,7 @@ import { * @returns The deep clone of the provided value. */ export async function deepClone(value: T): Promise { - const _deepClone = await getDeepCloneFunction(); + const _deepClone = getDeepCloneFunction(); return _deepClone(value); } diff --git a/v-next/hardhat-utils/src/request.ts b/v-next/hardhat-utils/src/request.ts index e96d18861d6..b05104d6236 100644 --- a/v-next/hardhat-utils/src/request.ts +++ b/v-next/hardhat-utils/src/request.ts @@ -1,7 +1,7 @@ import type EventEmitter from "node:events"; import type { FileHandle } from "node:fs/promises"; import type { ParsedUrlQueryInput } from "node:querystring"; -import type UndiciT from "undici"; +import type * as UndiciT from "undici"; import { open } from "node:fs/promises"; import querystring from "node:querystring"; @@ -33,6 +33,10 @@ export type Dispatcher = UndiciT.Dispatcher; export type TestDispatcher = UndiciT.MockAgent; export type Interceptable = UndiciT.Interceptable; +// We don't load undici on startup because this package is transitively imported +// from too many places and it's too complex to optimize case by case. +let undici: typeof UndiciT | undefined; + /** * Options to configure the dispatcher. * @@ -88,7 +92,9 @@ export async function getRequest( requestOptions: RequestOptions = {}, dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions, ): Promise { - const { request } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } try { const baseRequestOptions = await getBaseRequestOptions( @@ -96,7 +102,7 @@ export async function getRequest( requestOptions, dispatcherOrDispatcherOptions, ); - return await request(url, { + return await undici.request(url, { method: "GET", ...baseRequestOptions, }); @@ -128,7 +134,9 @@ export async function postJsonRequest( requestOptions: RequestOptions = {}, dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions, ): Promise { - const { request } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } try { const { headers, ...baseRequestOptions } = await getBaseRequestOptions( @@ -136,7 +144,7 @@ export async function postJsonRequest( requestOptions, dispatcherOrDispatcherOptions, ); - return await request(url, { + return await undici.request(url, { method: "POST", ...baseRequestOptions, headers: { @@ -173,7 +181,9 @@ export async function postFormRequest( requestOptions: RequestOptions = {}, dispatcherOrDispatcherOptions?: UndiciT.Dispatcher | DispatcherOptions, ): Promise { - const { request } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } try { const { headers, ...baseRequestOptions } = await getBaseRequestOptions( @@ -181,7 +191,7 @@ export async function postFormRequest( requestOptions, dispatcherOrDispatcherOptions, ); - return await request(url, { + return await undici.request(url, { method: "POST", ...baseRequestOptions, headers: { @@ -324,10 +334,12 @@ export async function getTestDispatcher( timeout?: number; } = {}, ): Promise { - const { MockAgent } = await import("undici"); + if (undici === undefined) { + undici = await import("undici"); + } const baseOptions = getBaseDispatcherOptions(options.timeout, true); - return new MockAgent(baseOptions); + return new undici.MockAgent(baseOptions); } /** diff --git a/v-next/hardhat-verify/src/internal/constructor-args.ts b/v-next/hardhat-verify/src/internal/constructor-args.ts index a553275eb25..bd53e6c70d5 100644 --- a/v-next/hardhat-verify/src/internal/constructor-args.ts +++ b/v-next/hardhat-verify/src/internal/constructor-args.ts @@ -1,5 +1,6 @@ import type { JsonFragment } from "@ethersproject/abi"; +import { Interface } from "@ethersproject/abi"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { ensureError } from "@nomicfoundation/hardhat-utils/error"; import { getUnprefixedHexString } from "@nomicfoundation/hardhat-utils/hex"; @@ -23,8 +24,6 @@ export async function encodeConstructorArgs( contract: string, ): Promise { // TODO: consider replacing with @metamask/abi-utils or micro-eth-signer - const { Interface } = await import("@ethersproject/abi"); - const contractInterface = new Interface(abi); try { diff --git a/v-next/hardhat-verify/src/internal/hook-handlers/network.ts b/v-next/hardhat-verify/src/internal/hook-handlers/network.ts index 44c3b3998de..7f854bfc3a6 100644 --- a/v-next/hardhat-verify/src/internal/hook-handlers/network.ts +++ b/v-next/hardhat-verify/src/internal/hook-handlers/network.ts @@ -1,6 +1,8 @@ import type { HookContext, NetworkHooks } from "hardhat/types/hooks"; import type { ChainType, NetworkConnection } from "hardhat/types/network"; +import { Verification } from "../verification-helpers.js"; + export default async (): Promise> => ({ async newConnection( context: HookContext, @@ -8,8 +10,6 @@ export default async (): Promise> => ({ ) { const connection = await next(context); - const { Verification } = await import("../verification-helpers.js"); - connection.verification = new Verification( connection.provider, connection.networkName, diff --git a/v-next/hardhat-verify/src/internal/metadata.ts b/v-next/hardhat-verify/src/internal/metadata.ts index 647882ab356..42d97330a99 100644 --- a/v-next/hardhat-verify/src/internal/metadata.ts +++ b/v-next/hardhat-verify/src/internal/metadata.ts @@ -1,6 +1,7 @@ import util from "node:util"; import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes"; +import { decode } from "cbor2"; import debug from "debug"; const log = debug("hardhat:hardhat-verify:metadata"); @@ -82,8 +83,6 @@ export function getMetadataSectionBytesLength(bytecode: Uint8Array): number { * version field. The function throws if the metadata cannot be decoded. */ async function decodeSolcMetadata(bytecode: Uint8Array): Promise { - const { decode } = await import("cbor2"); - const metadataSectionBytesLength = getMetadataSectionBytesLength(bytecode); const metadataPayload = bytecode.slice( -metadataSectionBytesLength, diff --git a/v-next/hardhat-verify/src/internal/solc-versions.ts b/v-next/hardhat-verify/src/internal/solc-versions.ts index 2a1d4be6a75..b612729d76b 100644 --- a/v-next/hardhat-verify/src/internal/solc-versions.ts +++ b/v-next/hardhat-verify/src/internal/solc-versions.ts @@ -1,6 +1,7 @@ import type { SolidityBuildProfileConfig } from "hardhat/types/config"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; +import semver from "semver"; // TODO: Consider splitting this into two steps: collecting compiler versions // and validating them. Version collection could be delegated to a helper @@ -19,10 +20,10 @@ import { HardhatError } from "@nomicfoundation/hardhat-errors"; * `compilers` and `overrides`. * @throws HardhatError if any version is not supported by Etherscan. */ -export async function resolveSupportedSolcVersions({ +export function resolveSupportedSolcVersions({ compilers, overrides, -}: SolidityBuildProfileConfig): Promise { +}: SolidityBuildProfileConfig): string[] { const solcVersions = compilers.map(({ version }) => version); if (overrides !== undefined) { for (const { version } of Object.values(overrides)) { @@ -33,7 +34,6 @@ export async function resolveSupportedSolcVersions({ // Etherscan only supports solidity versions higher than or equal to v0.4.11. // See https://etherscan.io/solcversions const SUPPORTED_SOLC_VERSION_RANGE = ">=0.4.11"; - const semver = await import("semver"); const unsupportedSolcVersions = solcVersions.filter( (version) => !semver.satisfies(version, SUPPORTED_SOLC_VERSION_RANGE), ); @@ -57,11 +57,9 @@ export async function resolveSupportedSolcVersions({ * @param range A semver range string (e.g. ">=0.4.11") * @returns An array of versions that satisfy the range. */ -export async function filterVersionsByRange( +export function filterVersionsByRange( versions: string[], range: string, -): Promise { - const semver = await import("semver"); - +): string[] { return versions.filter((version) => semver.satisfies(version, range)); } diff --git a/v-next/hardhat-verify/src/internal/verification.ts b/v-next/hardhat-verify/src/internal/verification.ts index 2981347958d..a7d70555d75 100644 --- a/v-next/hardhat-verify/src/internal/verification.ts +++ b/v-next/hardhat-verify/src/internal/verification.ts @@ -146,8 +146,7 @@ Explorer: ${instance.getContractUrl(address)}`); return true; } - const supportedSolcVersions = - await resolveSupportedSolcVersions(buildProfile); + const supportedSolcVersions = resolveSupportedSolcVersions(buildProfile); const deployedBytecode = await Bytecode.getDeployedContractBytecode( resolvedProvider, @@ -155,7 +154,7 @@ Explorer: ${instance.getContractUrl(address)}`); networkName, ); - const compatibleSolcVersions = await filterVersionsByRange( + const compatibleSolcVersions = filterVersionsByRange( supportedSolcVersions, deployedBytecode.solcVersion, ); diff --git a/v-next/hardhat-verify/src/verify.ts b/v-next/hardhat-verify/src/verify.ts index c66f1069cc0..f61b06149ee 100644 --- a/v-next/hardhat-verify/src/verify.ts +++ b/v-next/hardhat-verify/src/verify.ts @@ -1,13 +1,13 @@ import type { VerifyContractArgs } from "./internal/verification.js"; import type { HardhatRuntimeEnvironment } from "hardhat/types/hre"; +import { verifyContract as verify } from "./internal/verification.js"; + export type { VerifyContractArgs } from "./internal/verification.js"; export async function verifyContract( verifyContractArgs: VerifyContractArgs, hre: HardhatRuntimeEnvironment, ): Promise { - const { verifyContract: verify } = await import("./internal/verification.js"); - return verify(verifyContractArgs, hre); } diff --git a/v-next/hardhat-verify/test/solc-versions.ts b/v-next/hardhat-verify/test/solc-versions.ts index 3641390c257..bf7fbff116b 100644 --- a/v-next/hardhat-verify/test/solc-versions.ts +++ b/v-next/hardhat-verify/test/solc-versions.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; -import { assertRejectsWithHardhatError } from "@nomicfoundation/hardhat-test-utils"; +import { assertThrowsHardhatError } from "@nomicfoundation/hardhat-test-utils"; import { resolveSupportedSolcVersions, @@ -34,7 +34,7 @@ describe("solc-versions", () => { const expected = ["0.8.18", "0.7.2", "0.4.11"]; const supportedSolcVersions = - await resolveSupportedSolcVersions(solidityConfig); + resolveSupportedSolcVersions(solidityConfig); assert.deepEqual(supportedSolcVersions, expected); }); @@ -58,7 +58,7 @@ describe("solc-versions", () => { const expected = ["0.5.5", "0.6.4"]; const supportedSolcVersions = - await resolveSupportedSolcVersions(solidityConfig); + resolveSupportedSolcVersions(solidityConfig); assert.deepEqual(supportedSolcVersions, expected); }); @@ -91,7 +91,7 @@ describe("solc-versions", () => { const expected = ["0.8.18", "0.7.2", "0.5.5", "0.6.4"]; const supportedSolcVersions = - await resolveSupportedSolcVersions(solidityConfig); + resolveSupportedSolcVersions(solidityConfig); assert.deepEqual(supportedSolcVersions, expected); }); @@ -116,7 +116,7 @@ describe("solc-versions", () => { const expected = ["0.8.18", "0.8.18"]; const supportedSolcVersions = - await resolveSupportedSolcVersions(solidityConfig); + resolveSupportedSolcVersions(solidityConfig); assert.deepEqual(supportedSolcVersions, expected); }); @@ -143,8 +143,8 @@ describe("solc-versions", () => { preferWasm: false, }; - await assertRejectsWithHardhatError( - resolveSupportedSolcVersions(solidityConfig), + assertThrowsHardhatError( + () => resolveSupportedSolcVersions(solidityConfig), HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.SOLC_VERSION_NOT_SUPPORTED, { unsupportedSolcVersions: ["0.4.10"], @@ -174,8 +174,8 @@ describe("solc-versions", () => { preferWasm: false, }; - await assertRejectsWithHardhatError( - resolveSupportedSolcVersions(solidityConfig), + assertThrowsHardhatError( + () => resolveSupportedSolcVersions(solidityConfig), HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.SOLC_VERSION_NOT_SUPPORTED, { unsupportedSolcVersions: ["0.3.5"], @@ -189,7 +189,7 @@ describe("solc-versions", () => { const versions = ["0.8.18", "0.7.2", "0.4.11", "0.3.5"]; const range = ">=0.4.11"; - const filteredVersions = await filterVersionsByRange(versions, range); + const filteredVersions = filterVersionsByRange(versions, range); assert.deepEqual(filteredVersions, ["0.8.18", "0.7.2", "0.4.11"]); }); @@ -198,7 +198,7 @@ describe("solc-versions", () => { const versions = ["0.3.5", "0.2.1"]; const range = ">=0.4.11"; - const filteredVersions = await filterVersionsByRange(versions, range); + const filteredVersions = filterVersionsByRange(versions, range); assert.deepEqual(filteredVersions, []); }); diff --git a/v-next/hardhat/src/internal/builtin-plugins/gas-analytics/helpers.ts b/v-next/hardhat/src/internal/builtin-plugins/gas-analytics/helpers.ts index 249289b497d..45d8ed549cc 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/gas-analytics/helpers.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/gas-analytics/helpers.ts @@ -43,18 +43,28 @@ export function setGasAnalyticsManager( * from "hardhat/internal/gas-analytics". */ +// Dynamically import the HRE when calling the helpers +let cachedHre: HardhatRuntimeEnvironment | undefined; +async function getHre(): Promise { + if (cachedHre === undefined) { + const { default: hre } = await import("../../../index.js"); + cachedHre = hre; + } + return cachedHre; +} + export async function markTestRunStart(id: string): Promise { - const { default: hre } = await import("../../../index.js"); + const hre = await getHre(); await testRunStart(hre, id); } export async function markTestWorkerDone(id: string): Promise { - const { default: hre } = await import("../../../index.js"); + const hre = await getHre(); await testWorkerDone(hre, id); } export async function markTestRunDone(id: string): Promise { - const { default: hre } = await import("../../../index.js"); + const hre = await getHre(); await testRunDone(hre, id); } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts index fdcb60fe529..f90087af969 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts @@ -1,6 +1,15 @@ +import type * as Bip39T from "ethereum-cryptography/bip39"; +import type { HDKey as HDKeyT } from "ethereum-cryptography/hdkey"; + import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes"; +// ethereum-cryptography/bip39 is known to be slow to load, so we lazy load it +let mnemonicToSeedSync: typeof Bip39T.mnemonicToSeedSync | undefined; + +// ethereum-cryptography/hdkey is known to be slow to load, so we lazy load it +let HDKey: typeof HDKeyT | undefined; + const HD_PATH_REGEX = /^m(:?\/\d+'?)+\/?$/; export async function derivePrivateKeys( @@ -47,13 +56,26 @@ async function deriveKeyFromMnemonicAndPath( hdPath: string, passphrase: string, ): Promise { - const { mnemonicToSeedSync } = await import("ethereum-cryptography/bip39"); - const { HDKey } = await import("ethereum-cryptography/hdkey"); - // NOTE: If mnemonic has space or newline at the beginning or end, it will be trimmed. // This is because mnemonic containing them may generate different private keys. const trimmedMnemonic = mnemonic.trim(); + if (mnemonicToSeedSync === undefined) { + const { mnemonicToSeedSync: importedMnemonicToSeedSync } = await import( + "ethereum-cryptography/bip39" + ); + + mnemonicToSeedSync = importedMnemonicToSeedSync; + } + + if (HDKey === undefined) { + const { HDKey: ImportedHDKey } = await import( + "ethereum-cryptography/hdkey" + ); + + HDKey = ImportedHDKey; + } + const seed = mnemonicToSeedSync(trimmedMnemonic, passphrase); const masterKey = HDKey.fromMasterSeed(seed); diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts index 93487c53521..d8ed8249518 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts @@ -3,6 +3,7 @@ import type { EdrNetworkForkingConfig, } from "../../../../types/config.js"; import type { ChainType } from "../../../../types/network.js"; +import type * as MicroEthSignerAddressT from "micro-eth-signer/address"; import { l1GenesisState, @@ -13,12 +14,14 @@ import { } from "@nomicfoundation/edr"; import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization"; import { hexToBytes } from "ethereum-cryptography/utils"; -import { addr } from "micro-eth-signer/address"; import { OPTIMISM_CHAIN_TYPE } from "../../../constants.js"; import { hardhatAccountsToEdrOwnedAccounts } from "./utils/convert-to-edr.js"; +// micro-eth-signer is known to be slow to load, so we lazy load it +let microEthSignerAddress: typeof MicroEthSignerAddressT | undefined; + const noForkingConfigCacheMarkerObject = {}; /** @@ -131,6 +134,12 @@ async function createGenesisStateAndOwnedAccounts( genesisState: Map; ownedAccounts: Array<{ secretKey: string; balance: bigint }>; }> { + if (microEthSignerAddress === undefined) { + microEthSignerAddress = await import("micro-eth-signer/address"); + } + + const { addr } = microEthSignerAddress; + const ownedAccounts = await hardhatAccountsToEdrOwnedAccounts(accountsConfig); const genesisState: Map = new Map( diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/network.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/network.ts index 8aee0826091..365f7dcfad2 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/network.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/network.ts @@ -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> => { // This map is essential for managing multiple network connections in Hardhat V3. @@ -37,10 +38,6 @@ export default async (): Promise> => { nextJsonRpcRequest: JsonRpcRequest, ) => Promise, ) { - const { createHandlersArray } = await import( - "../request-handlers/handlers-array.js" - ); - const requestHandlers = await initializationMutex.exclusiveRun( async () => { let handlersPerConnection = diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts index 5d903a938ff..9eef8eea0f3 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts @@ -8,13 +8,20 @@ import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex"; import { isHttpNetworkHdAccountsConfig } from "../type-validation.js"; +import { AutomaticSenderHandler } from "./handlers/accounts/automatic-sender-handler.js"; +import { FixedSenderHandler } from "./handlers/accounts/fixed-sender-handler.js"; +import { HDWalletHandler } from "./handlers/accounts/hd-wallet-handler.js"; +import { LocalAccountsHandler } from "./handlers/accounts/local-accounts.js"; +import { ChainIdValidatorHandler } from "./handlers/chain-id/chain-id-handler.js"; +import { AutomaticGasHandler } from "./handlers/gas/automatic-gas-handler.js"; +import { AutomaticGasPriceHandler } from "./handlers/gas/automatic-gas-price-handler.js"; +import { FixedGasHandler } from "./handlers/gas/fixed-gas-handler.js"; +import { FixedGasPriceHandler } from "./handlers/gas/fixed-gas-price-handler.js"; + /** * This function returns an handlers array based on the values in the NetworkConnection and NetworkConfig. * The order of the handlers, if all are present, is: chain handler, gas handlers (gasPrice first, then gas), sender handler and accounts handler. * The order is important to get a correct result when the handlers are executed. - * The handlers are imported dynamically because some might take a long time to load. - * Out of the currently supported handlers, LocalAccountsHandler and, consequently, HDWalletHandler have been identified as the most expensive. - * See https://github.com/NomicFoundation/hardhat/pull/6481 for more details. */ export async function createHandlersArray< ChainTypeT extends ChainType | string, @@ -24,9 +31,6 @@ export async function createHandlersArray< const networkConfig = networkConnection.networkConfig; if (networkConfig.type === "http" && networkConfig.chainId !== undefined) { - const { ChainIdValidatorHandler } = await import( - "./handlers/chain-id/chain-id-handler.js" - ); requestHandlers.push( new ChainIdValidatorHandler( networkConnection.provider, @@ -39,9 +43,6 @@ export async function createHandlersArray< networkConfig.gasPrice === undefined || networkConfig.gasPrice === "auto" ) { - const { AutomaticGasPriceHandler } = await import( - "./handlers/gas/automatic-gas-price-handler.js" - ); // If you use a hook handler that signs locally, you are required to // have all the transaction fields available, including the // gasPrice / maxFeePerGas & maxPriorityFeePerGas. @@ -56,18 +57,12 @@ export async function createHandlersArray< new AutomaticGasPriceHandler(networkConnection.provider), ); } else { - const { FixedGasPriceHandler } = await import( - "./handlers/gas/fixed-gas-price-handler.js" - ); requestHandlers.push( new FixedGasPriceHandler(numberToHexString(networkConfig.gasPrice)), ); } if (networkConfig.gas === undefined || networkConfig.gas === "auto") { - const { AutomaticGasHandler } = await import( - "./handlers/gas/automatic-gas-handler.js" - ); requestHandlers.push( new AutomaticGasHandler( networkConnection.provider, @@ -75,25 +70,16 @@ export async function createHandlersArray< ), ); } else { - const { FixedGasHandler } = await import( - "./handlers/gas/fixed-gas-handler.js" - ); requestHandlers.push( new FixedGasHandler(numberToHexString(networkConfig.gas)), ); } if (networkConfig.from === undefined) { - const { AutomaticSenderHandler } = await import( - "./handlers/accounts/automatic-sender-handler.js" - ); requestHandlers.push( new AutomaticSenderHandler(networkConnection.provider), ); } else { - const { FixedSenderHandler } = await import( - "./handlers/accounts/fixed-sender-handler.js" - ); requestHandlers.push( new FixedSenderHandler(networkConnection.provider, networkConfig.from), ); @@ -103,9 +89,6 @@ export async function createHandlersArray< const accounts = networkConfig.accounts; if (Array.isArray(accounts)) { - const { LocalAccountsHandler } = await import( - "./handlers/accounts/local-accounts.js" - ); const resolvedAccounts = await Promise.all( accounts.map((acc) => acc.getHexString()), ); @@ -114,9 +97,6 @@ export async function createHandlersArray< new LocalAccountsHandler(networkConnection.provider, resolvedAccounts), ); } else if (isHttpNetworkHdAccountsConfig(accounts)) { - const { HDWalletHandler } = await import( - "./handlers/accounts/hd-wallet-handler.js" - ); const hdWalletHandler = await HDWalletHandler.create( networkConnection.provider, await accounts.mnemonic.get(), diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.ts index 2e9f25e03f2..018b1446af7 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.ts @@ -5,6 +5,8 @@ import type { } from "../../../../../../types/providers.js"; import type { RequestHandler } from "../../types.js"; import type { RpcTransactionRequest } from "@nomicfoundation/hardhat-zod-utils/rpc"; +import type * as MicroEthSignerT from "micro-eth-signer"; +import type * as MicroEthSignerTypedDataT from "micro-eth-signer/typed-data"; import { assertHardhatInvariant, @@ -27,27 +29,20 @@ import { rpcTransactionRequest, validateParams, } from "@nomicfoundation/hardhat-zod-utils/rpc"; -import { addr, Transaction } from "micro-eth-signer"; -import { signTyped } from "micro-eth-signer/typed-data"; -import * as typed from "micro-eth-signer/typed-data"; + +// micro-eth-signer is known to be slow to load, so we lazy load it +let microEthSigner: typeof MicroEthSignerT | undefined; +let microEthSignerTypedData: typeof MicroEthSignerTypedDataT | undefined; import { getRequestParams } from "../../../json-rpc.js"; import { ChainId } from "../chain-id/chain-id.js"; -/** - * This handler takes a long time to load. Currently, it is only used in the handlers array, - * where it is imported dynamically, and in the HDWalletHandler, which itself is only loaded - * dynamically. - * If we ever need to import this handler elsewhere, we should either import it dynamically - * or import some of the dependencies of this handler dynamically. - * It has been identified that micro-eth-signer is one of the most expensive dependencies here. - * See https://github.com/NomicFoundation/hardhat/pull/6481 for more details. - */ - const EXTRA_ENTROPY = false; export class LocalAccountsHandler extends ChainId implements RequestHandler { - readonly #addressToPrivateKey: Map = new Map(); - readonly #addresses: string[] = []; + readonly #localAccountsHexPrivateKeys: string[]; + + #addressToPrivateKey: Map | undefined; + #addresses: string[] | undefined; constructor( provider: EthereumProvider, @@ -55,7 +50,29 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { ) { super(provider); - this.#initializePrivateKeys(localAccountsHexPrivateKeys); + this.#localAccountsHexPrivateKeys = localAccountsHexPrivateKeys; + } + + async #getAddressesAndPrivateKeysMap(): Promise<{ + addresses: string[]; + addressToPrivateKey: Map; + }> { + if ( + this.#addresses === undefined || + this.#addressToPrivateKey === undefined + ) { + const { addresses, addressToPrivateKey } = + await this.#initializeAddressesFromPrivateKeys( + this.#localAccountsHexPrivateKeys, + ); + this.#addresses = addresses; + this.#addressToPrivateKey = addressToPrivateKey; + } + + return { + addresses: this.#addresses, + addressToPrivateKey: this.#addressToPrivateKey, + }; } public async handle( @@ -78,9 +95,8 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { jsonRpcRequest.method === "eth_accounts" || jsonRpcRequest.method === "eth_requestAccounts" ) { - return this.#createJsonRpcResponse(jsonRpcRequest.id, [ - ...this.#addresses, - ]); + const { addresses } = await this.#getAddressesAndPrivateKeysMap(); + return this.#createJsonRpcResponse(jsonRpcRequest.id, [...addresses]); } const params = getRequestParams(jsonRpcRequest); @@ -96,10 +112,20 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { ); } - const privateKey = this.#getPrivateKeyForAddress(address); + if (microEthSignerTypedData === undefined) { + microEthSignerTypedData = await import( + "micro-eth-signer/typed-data" + ); + } + + const privateKey = await this.#getPrivateKeyForAddress(address); return this.#createJsonRpcResponse( jsonRpcRequest.id, - typed.personal.sign(data, privateKey, EXTRA_ENTROPY), + microEthSignerTypedData.personal.sign( + data, + privateKey, + EXTRA_ENTROPY, + ), ); } } @@ -116,10 +142,20 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { ); } - const privateKey = this.#getPrivateKeyForAddress(address); + if (microEthSignerTypedData === undefined) { + microEthSignerTypedData = await import( + "micro-eth-signer/typed-data" + ); + } + + const privateKey = await this.#getPrivateKeyForAddress(address); return this.#createJsonRpcResponse( jsonRpcRequest.id, - typed.personal.sign(data, privateKey, EXTRA_ENTROPY), + microEthSignerTypedData.personal.sign( + data, + privateKey, + EXTRA_ENTROPY, + ), ); } } @@ -146,11 +182,19 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { } // if we don't manage the address, the method is forwarded - const privateKey = this.#getPrivateKeyForAddressOrNull(address); + const privateKey = await this.#getPrivateKeyForAddressOrNull(address); if (privateKey !== null) { + if (microEthSignerTypedData === undefined) { + microEthSignerTypedData = await import("micro-eth-signer/typed-data"); + } + return this.#createJsonRpcResponse( jsonRpcRequest.id, - signTyped(typedMessage, privateKey, EXTRA_ENTROPY), + microEthSignerTypedData.signTyped( + typedMessage, + privateKey, + EXTRA_ENTROPY, + ), ); } } @@ -220,7 +264,7 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { txRequest.nonce = await this.#getNonce(txRequest.from); } - const privateKey = this.#getPrivateKeyForAddress(txRequest.from); + const privateKey = await this.#getPrivateKeyForAddress(txRequest.from); const chainId = await this.getChainId(); @@ -235,20 +279,32 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { } } - #initializePrivateKeys(localAccountsHexPrivateKeys: string[]) { + async #initializeAddressesFromPrivateKeys( + localAccountsHexPrivateKeys: string[], + ) { + if (microEthSigner === undefined) { + microEthSigner = await import("micro-eth-signer"); + } + const privateKeys: Uint8Array[] = localAccountsHexPrivateKeys.map((h) => hexStringToBytes(h), ); + const addresses = []; + const addressToPrivateKey = new Map(); for (const pk of privateKeys) { - const address = addr.fromPrivateKey(pk).toLowerCase(); - this.#addressToPrivateKey.set(address, pk); - this.#addresses.push(address); + const address = microEthSigner.addr.fromPrivateKey(pk).toLowerCase(); + addressToPrivateKey.set(address, pk); + addresses.push(address); } + + return { addresses, addressToPrivateKey }; } - #getPrivateKeyForAddress(address: Uint8Array): Uint8Array { - const pk = this.#addressToPrivateKey.get(bytesToHexString(address)); + async #getPrivateKeyForAddress(address: Uint8Array): Promise { + const { addressToPrivateKey } = await this.#getAddressesAndPrivateKeysMap(); + + const pk = addressToPrivateKey.get(bytesToHexString(address)); if (pk === undefined) { throw new HardhatError( @@ -262,9 +318,11 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { return pk; } - #getPrivateKeyForAddressOrNull(address: Uint8Array): Uint8Array | null { + async #getPrivateKeyForAddressOrNull( + address: Uint8Array, + ): Promise { try { - return this.#getPrivateKeyForAddress(address); + return await this.#getPrivateKeyForAddress(address); } catch { return null; } @@ -289,6 +347,12 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler { chainId: number, privateKey: Uint8Array, ): Promise { + if (microEthSigner === undefined) { + microEthSigner = await import("micro-eth-signer"); + } + + const { addr, Transaction } = microEthSigner; + const txData = { ...transactionRequest, gasLimit: transactionRequest.gas, diff --git a/v-next/hardhat/src/internal/builtin-plugins/node/artifacts/build-info-watcher.ts b/v-next/hardhat/src/internal/builtin-plugins/node/artifacts/build-info-watcher.ts index ba760b23585..9e164393c8d 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/node/artifacts/build-info-watcher.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/node/artifacts/build-info-watcher.ts @@ -2,6 +2,7 @@ import type { FSWatcher } from "chokidar"; import path from "node:path"; +import { watch } from "chokidar"; import debug from "debug"; export type BuildInfoWatcher = FSWatcher; @@ -59,8 +60,6 @@ export async function watchBuildInfo( buildInfoDirPath: string, handler: BuildInfoHandler, ): Promise { - const { watch } = await import("chokidar"); - // NOTE: Deleting the build info directory while it is being watched will // effectively cause the watcher to stop working. // NOTE: We use chokidar's `awaitWriteFinish` option because we are certain diff --git a/v-next/hardhat/src/internal/builtin-plugins/node/helpers.ts b/v-next/hardhat/src/internal/builtin-plugins/node/helpers.ts index 7b4f97ed26a..02e26e13d83 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/node/helpers.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/node/helpers.ts @@ -2,6 +2,7 @@ import type { BuildInfo } from "../../../types/artifacts.js"; import type { EdrNetworkAccountsConfig } from "../../../types/config.js"; import type { SolidityBuildInfoOutput } from "../../../types/solidity.js"; import type { EdrProvider } from "../network-manager/edr/edr-provider.js"; +import type * as MicroEthSignerT from "micro-eth-signer"; import path from "node:path"; @@ -11,7 +12,9 @@ import { } from "@nomicfoundation/hardhat-utils/fs"; import { hexStringToBytes } from "@nomicfoundation/hardhat-utils/hex"; import chalk from "chalk"; -import { addr } from "micro-eth-signer"; + +// micro-eth-signer is known to be slow to load, so we lazy load it +let microEthSigner: typeof MicroEthSignerT | undefined; import { sendErrorTelemetry } from "../../cli/telemetry/sentry/reporter.js"; import { isDefaultEdrNetworkHDAccountsConfig } from "../network-manager/edr/edr-provider.js"; @@ -48,8 +51,12 @@ export async function formatEdrNetworkConfigAccounts( maxPrefixLength = privateKeyPrefix.length; } + if (microEthSigner === undefined) { + microEthSigner = await import("micro-eth-signer"); + } + for (const [index, account] of accounts.entries()) { - const address = addr + const address = microEthSigner.addr .fromPrivateKey(hexStringToBytes(await account.privateKey.getHexString())) .toLowerCase(); const balance = (BigInt(account.balance) / 10n ** 18n).toString(10); diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts index 6c6883ecfaf..ef2b1620c08 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts @@ -25,6 +25,7 @@ import { import { getPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex"; import { download } from "@nomicfoundation/hardhat-utils/request"; import { MultiProcessMutex } from "@nomicfoundation/hardhat-utils/synchronization"; +import AdmZip from "adm-zip"; import debug from "debug"; import { NativeCompiler, SolcJsCompiler } from "./compiler.js"; @@ -469,8 +470,6 @@ export class CompilerDownloaderImplementation implements CompilerDownloader { downloadPath.endsWith(".zip") ) { // some window builds are zipped, some are not - const { default: AdmZip } = await import("adm-zip"); - const solcFolder = path.join(this.#compilersDir, build.version); await ensureDir(solcFolder); diff --git a/v-next/hardhat/src/internal/cli/help/get-help-string.ts b/v-next/hardhat/src/internal/cli/help/get-help-string.ts index 19d8b208aeb..87953bfc4aa 100644 --- a/v-next/hardhat/src/internal/cli/help/get-help-string.ts +++ b/v-next/hardhat/src/internal/cli/help/get-help-string.ts @@ -1,6 +1,8 @@ import type { GlobalOptionDefinitions } from "../../../types/global-options.js"; import type { Task } from "../../../types/tasks.js"; +import chalk from "chalk"; + import { GLOBAL_NAME_PADDING, parseOptions, @@ -15,8 +17,6 @@ export async function getHelpString( task: Task, globalOptionDefinitions: GlobalOptionDefinitions, ): Promise { - const { default: chalk } = await import("chalk"); - const { options, positionalArguments } = parseOptions(task); const subtasks = parseSubtasks(task); diff --git a/v-next/hardhat/src/internal/cli/init/init.ts b/v-next/hardhat/src/internal/cli/init/init.ts index b81652d34c4..0064b39bfec 100644 --- a/v-next/hardhat/src/internal/cli/init/init.ts +++ b/v-next/hardhat/src/internal/cli/init/init.ts @@ -29,6 +29,7 @@ import { getHardhatVersion, getLatestHardhatVersion, } from "../../utils/package.js"; +import { BannerManager } from "../banner-manager.js"; import { sendProjectTypeAnalytics } from "../telemetry/analytics/analytics.js"; import { sendErrorTelemetry } from "../telemetry/sentry/reporter.js"; @@ -133,7 +134,6 @@ export async function initHardhat(options?: InitHardhatOptions): Promise { showStarOnGitHubMessage(); try { - const { BannerManager } = await import("../banner-manager.js"); const bannerManager = await BannerManager.getInstance(); await bannerManager.showBanner(); } catch (bannerError) { diff --git a/v-next/hardhat/src/internal/cli/init/prompt.ts b/v-next/hardhat/src/internal/cli/init/prompt.ts index 00fe4127db3..337db34443a 100644 --- a/v-next/hardhat/src/internal/cli/init/prompt.ts +++ b/v-next/hardhat/src/internal/cli/init/prompt.ts @@ -3,14 +3,13 @@ import type { Template } from "./template.js"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { shortenPath } from "@nomicfoundation/hardhat-utils/path"; import chalk from "chalk"; +import enquirer from "enquirer"; export async function promptForHardhatVersion(): Promise< "hardhat-2" | "hardhat-3" > { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const hardhatVersionResponse = await enquirer.prompt<{ hardhatVersion: "hardhat-2" | "hardhat-3"; }>([ @@ -40,8 +39,6 @@ export async function promptForHardhatVersion(): Promise< export async function promptForWorkspace(): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const workspaceResponse = await enquirer.prompt<{ workspace: string }>([ { name: "workspace", @@ -59,8 +56,6 @@ export async function promptForMigrateToEsm( ): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const migrateToEsmResponse = await enquirer.prompt<{ migrateToEsm: boolean }>( [ { @@ -80,8 +75,6 @@ export async function promptForTemplate( ): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const templateResponse = await enquirer.prompt<{ template: string }>([ { name: "template", @@ -102,8 +95,6 @@ export async function promptForTemplate( export async function promptForForce(files: string[]): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const forceResponse = await enquirer.prompt<{ force: boolean }>([ { name: "force", @@ -121,8 +112,6 @@ export async function promptForInstall( ): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const installResponse = await enquirer.prompt<{ install: boolean }>([ { name: "install", @@ -140,8 +129,6 @@ export async function promptForUpdate( ): Promise { ensureTTY(); - const { default: enquirer } = await import("enquirer"); - const updateResponse = await enquirer.prompt<{ update: boolean }>([ { name: "update", diff --git a/v-next/hardhat/src/internal/core/plugins/detect-plugin-npm-dependency-problems.ts b/v-next/hardhat/src/internal/core/plugins/detect-plugin-npm-dependency-problems.ts index ac824496eae..b23b00edb32 100644 --- a/v-next/hardhat/src/internal/core/plugins/detect-plugin-npm-dependency-problems.ts +++ b/v-next/hardhat/src/internal/core/plugins/detect-plugin-npm-dependency-problems.ts @@ -1,4 +1,5 @@ import type { HardhatPlugin } from "../../../types/plugins.js"; +import type { satisfies as SatisfiesT } from "semver"; import path from "node:path"; @@ -9,6 +10,10 @@ import { type PackageJson, } from "@nomicfoundation/hardhat-utils/package"; +// semver is slow to load, and this file is loaded by HookManager, so we lazy +// load it. +let satisfies: typeof SatisfiesT | undefined; + /** * Validate that a plugin is installed and that its peer dependencies are * installed and satisfy the version constraints. @@ -84,7 +89,9 @@ export async function detectPluginNpmDependencyProblems( const installedVersion = dependencyPackageJson.version; - const { satisfies } = await import("semver"); + if (satisfies === undefined) { + ({ satisfies } = await import("semver")); + } if (!satisfies(installedVersion, versionSpec.replace("workspace:", ""))) { throw new HardhatError( diff --git a/v-next/hardhat/src/internal/core/user-interruptions.ts b/v-next/hardhat/src/internal/core/user-interruptions.ts index b216b865ff3..cab7c927493 100644 --- a/v-next/hardhat/src/internal/core/user-interruptions.ts +++ b/v-next/hardhat/src/internal/core/user-interruptions.ts @@ -1,8 +1,11 @@ import type { HookContext, HookManager } from "../../types/hooks.js"; import type { UserInterruptionManager } from "../../types/user-interruptions.js"; +import { createInterface } from "node:readline"; + import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization"; +import chalk from "chalk"; export class UserInterruptionManagerImplementation implements UserInterruptionManager @@ -68,7 +71,6 @@ async function defaultDisplayMessage( interruptor: string, message: string, ) { - const chalk = (await import("chalk")).default; console.log(chalk.blue(`[${interruptor}]`) + ` ${message}`); } @@ -77,8 +79,6 @@ async function defaultRequestInput( interruptor: string, inputDescription: string, ): Promise { - const { createInterface } = await import("node:readline"); - const chalk = (await import("chalk")).default; const rl = createInterface({ input: process.stdin, output: process.stdout, @@ -100,8 +100,6 @@ async function defaultRequestSecretInput( interruptor: string, inputDescription: string, ): Promise { - const { createInterface } = await import("node:readline"); - const chalk = (await import("chalk")).default; const rl = createInterface({ input: process.stdin, output: process.stdout, diff --git a/v-next/hardhat/src/internal/utils/package.ts b/v-next/hardhat/src/internal/utils/package.ts index f2e8b0e2135..120d6240983 100644 --- a/v-next/hardhat/src/internal/utils/package.ts +++ b/v-next/hardhat/src/internal/utils/package.ts @@ -7,6 +7,7 @@ import { findDependencyPackageJson, readClosestPackageJson, } from "@nomicfoundation/hardhat-utils/package"; +import { getRequest } from "@nomicfoundation/hardhat-utils/request"; let cachedHardhatVersion: string | undefined; let cachedLatestHardhatVersion: string | undefined; @@ -50,8 +51,6 @@ export async function getEdrVersion(): Promise { } export async function getLatestHardhatVersion(): Promise { - const { getRequest } = await import("@nomicfoundation/hardhat-utils/request"); - if (cachedLatestHardhatVersion !== undefined) { return cachedLatestHardhatVersion; } diff --git a/v-next/ignition-core/src/internal/execution/future-processor/helpers/future-resolvers.ts b/v-next/ignition-core/src/internal/execution/future-processor/helpers/future-resolvers.ts index ed2c5dd4847..5c086c165f8 100644 --- a/v-next/ignition-core/src/internal/execution/future-processor/helpers/future-resolvers.ts +++ b/v-next/ignition-core/src/internal/execution/future-processor/helpers/future-resolvers.ts @@ -14,6 +14,7 @@ import type { import type { DeploymentLoader } from "../../../deployment-loader/types.js"; import type { DeploymentState } from "../../types/deployment-state.js"; +import { Interface } from "ethers"; import { isAddress } from "ethers/address"; import { @@ -308,7 +309,6 @@ export async function resolveEncodeFunctionCallResult( ): Promise { const artifact = await deploymentLoader.loadArtifact(artifactId); - const { Interface } = await import("ethers"); const iface = new Interface(artifact.abi); return iface.encodeFunctionData(functionName, args);