From d8c2296da360736ad9d6a6d359071518e012181e Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Mon, 16 Dec 2024 20:39:10 +0000 Subject: [PATCH 01/11] refactor: sort and rename type validation variables for consistency --- .../network-manager/config-resolution.ts | 4 +- .../network-manager/hook-handlers/config.ts | 4 +- .../request-handlers/hanlders-array.ts | 4 +- .../network-manager/type-extensions/config.ts | 2 +- .../network-manager/type-validation.ts | 175 +++++++------ .../network-manager/hook-handlers/config.ts | 239 ++++++++++-------- 6 files changed, 239 insertions(+), 189 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts index 239959845f..0b2ec4da8d 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts @@ -28,7 +28,7 @@ import { EDR_NETWORK_DEFAULT_COINBASE, } from "./edr/edr-provider.js"; import { HardforkName } from "./edr/types/hardfork.js"; -import { isHdAccountsConfig } from "./type-validation.js"; +import { isHdAccountsUserConfig } from "./type-validation.js"; export function resolveGasConfig(value: GasUserConfig = "auto"): GasConfig { return value === "auto" ? value : BigInt(value); @@ -48,7 +48,7 @@ export function resolveHttpNetworkAccounts( }); } - if (isHdAccountsConfig(accounts)) { + if (isHdAccountsUserConfig(accounts)) { return { ...DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS, ...accounts, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts index ad990a74df..39573499af 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -25,11 +25,11 @@ import { resolveInitialBaseFeePerGas, resolveMiningConfig, } from "../config-resolution.js"; -import { validateUserConfig } from "../type-validation.js"; +import { validateNetworkUserConfig } from "../type-validation.js"; export default async (): Promise> => ({ extendUserConfig, - validateUserConfig, + validateUserConfig: validateNetworkUserConfig, resolveUserConfig, }); diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts index ad452132e5..686074dbaa 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts @@ -6,7 +6,7 @@ import type { import { numberToHexString } from "@ignored/hardhat-vnext-utils/hex"; -import { isHdAccountsConfig } from "../type-validation.js"; +import { isHdAccountsUserConfig } from "../type-validation.js"; import { AutomaticSenderHandler } from "./handlers/accounts/automatic-sender-handler.js"; import { FixedSenderHandler } from "./handlers/accounts/fixed-sender-handler.js"; @@ -96,7 +96,7 @@ export async function createHandlersArray< requestHandlers.push( new LocalAccountsHandler(networkConnection.provider, resolvedAccounts), ); - } else if (isHdAccountsConfig(accounts)) { + } else if (isHdAccountsUserConfig(accounts)) { requestHandlers.push( new HDWalletHandler( networkConnection.provider, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts index 8424d2752c..eef0c4a990 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts @@ -141,8 +141,8 @@ declare module "../../../../types/config.js" { // HTTP network specific url: string; - timeout: number; httpHeaders: Record; + timeout: number; } export type HttpNetworkAccountsConfig = diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 2545192912..793803845e 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -18,21 +18,7 @@ import { } from "@ignored/hardhat-vnext-zod-utils"; import { z } from "zod"; -const chainTypeSchema = unionType( - [z.literal("l1"), z.literal("optimism"), z.literal("generic")], - "Expected 'l1', 'optimism', or 'generic'", -); - -const userGasSchema = unionType( - [ - z.literal("auto"), - z.number().int().positive().safe(), - z.bigint().positive(), - ], - "Expected 'auto', a positive safe int, or positive bigint", -); - -const accountsPrivateKeySchema = unionType( +const accountsPrivateKeyUserConfigSchema = unionType( [ configurationVariableSchema, z @@ -43,87 +29,105 @@ const accountsPrivateKeySchema = unionType( `Expected a hex-encoded private key or a Configuration Variable`, ); -// TODO: We don't support config variables in EDR networks yet -const edrPrivateKeySchemaMessage = `Expected a hex-encoded private key`; -const edrPrivateKeySchema = z - .string() - .refine( - (val) => - getUnprefixedHexString(val).length === 64 && - /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val)), - { message: edrPrivateKeySchemaMessage }, - ); - -const httpNetworkHDAccountsUserConfig = z.object({ +const httpNetworkHDAccountsUserConfigSchema = z.object({ mnemonic: z.string(), - initialIndex: z.optional(z.number().int()), count: z.optional(z.number().int().positive()), - path: z.optional(z.string()), + initialIndex: z.optional(z.number().int()), passphrase: z.optional(z.string()), + path: z.optional(z.string()), }); -const httpNetworkUserConfigAccountsSchema = conditionalUnionType( +const httpNetworkAccountsUserConfigSchema = conditionalUnionType( [ [(data) => data === "remote", z.literal("remote")], - [(data) => Array.isArray(data), z.array(accountsPrivateKeySchema)], - [isObject, httpNetworkHDAccountsUserConfig], + [ + (data) => Array.isArray(data), + z.array(accountsPrivateKeyUserConfigSchema), + ], + [isObject, httpNetworkHDAccountsUserConfigSchema], ], `Expected 'remote', an array with private keys or Configuration Variables, or an object with HD account details`, ); +const chainTypeUserConfigSchema = unionType( + [z.literal("l1"), z.literal("optimism"), z.literal("generic")], + "Expected 'l1', 'optimism', or 'generic'", +); + +const gasUserConfigSchema = unionType( + [ + z.literal("auto"), + z.number().int().positive().safe(), + z.bigint().positive(), + ], + "Expected 'auto', a positive safe int, or positive bigint", +); + const httpNetworkUserConfigSchema = z.object({ type: z.literal("http"), + accounts: z.optional(httpNetworkAccountsUserConfigSchema), chainId: z.optional(z.number().int()), - chainType: z.optional(chainTypeSchema), + chainType: z.optional(chainTypeUserConfigSchema), from: z.optional(z.string()), - gas: z.optional(userGasSchema), + gas: z.optional(gasUserConfigSchema), gasMultiplier: z.optional(z.number()), - gasPrice: z.optional(userGasSchema), - accounts: z.optional(httpNetworkUserConfigAccountsSchema), + gasPrice: z.optional(gasUserConfigSchema), // HTTP network specific url: z.string().url(), // TODO: this should be a sensitiveUrlSchema - timeout: z.optional(z.number()), httpHeaders: z.optional(z.record(z.string())), + timeout: z.optional(z.number()), }); -const edrNetworkUserConfigAccountSchema = z.object({ - privateKey: edrPrivateKeySchema, +const edrPrivateKeyUserConfigSchema = z + .string() + .refine( + (val) => + getUnprefixedHexString(val).length === 64 && + /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val)), + { message: "Expected a hex-encoded private key" }, + ); + +const edrNetworkAccountUserConfigSchema = z.object({ balance: unionType( [z.string(), z.bigint().positive()], "Expected a string or a positive bigint", ), + privateKey: edrPrivateKeyUserConfigSchema, }); -const edrNetworkHDAccountsUserConfig = z.object({ +const edrNetworkHDAccountsUserConfigSchema = z.object({ mnemonic: z.optional(z.string()), - initialIndex: z.optional(z.number().int()), - count: z.optional(z.number().int().positive()), - path: z.optional(z.string()), accountsBalance: unionType( [z.string(), z.bigint().positive()], "Expected a string or a positive bigint", ).optional(), + count: z.optional(z.number().int().positive()), + initialIndex: z.optional(z.number().int()), passphrase: z.optional(z.string()), + path: z.optional(z.string()), }); -const edrNetworkUserConfigAccountsSchema = conditionalUnionType( +const edrNetworkAccountsUserConfigSchema = conditionalUnionType( [ - [(data) => Array.isArray(data), z.array(edrNetworkUserConfigAccountSchema)], - [isObject, edrNetworkHDAccountsUserConfig], + [(data) => Array.isArray(data), z.array(edrNetworkAccountUserConfigSchema)], + [isObject, edrNetworkHDAccountsUserConfigSchema], ], `Expected an array with with objects with private key and balance or Configuration Variables, or an object with HD account details`, ); const edrNetworkUserConfigSchema = z.object({ type: z.literal("edr"), + accounts: z.optional(edrNetworkAccountsUserConfigSchema), chainId: z.number().int().optional(), - chainType: z.optional(chainTypeSchema), + chainType: z.optional(chainTypeUserConfigSchema), from: z.optional(z.string()).optional(), - gas: userGasSchema.optional(), + gas: gasUserConfigSchema.optional(), gasMultiplier: z.number().optional(), - gasPrice: userGasSchema.optional(), - accounts: z.optional(edrNetworkUserConfigAccountsSchema), + gasPrice: gasUserConfigSchema.optional(), + + // EDR network specific + // TODO: add the rest of the fields }); const networkUserConfigSchema = z.discriminatedUnion("type", [ @@ -132,69 +136,83 @@ const networkUserConfigSchema = z.discriminatedUnion("type", [ ]); const userConfigSchema = z.object({ - defaultChainType: z.optional(chainTypeSchema), + defaultChainType: z.optional(chainTypeUserConfigSchema), defaultNetwork: z.optional(z.string()), networks: z.optional(z.record(networkUserConfigSchema)), }); -const gasSchema = unionType( +const chainTypeConfigSchema = unionType( + [z.literal("l1"), z.literal("optimism"), z.literal("generic")], + "Expected 'l1', 'optimism', or 'generic'", +); + +const gasConfigSchema = unionType( [z.literal("auto"), z.bigint().positive()], "Expected 'auto' or positive bigint", ); -const httpNetworkHDAccountsConfig = z.object({ +const httpNetworkHDAccountsConfigSchema = z.object({ mnemonic: z.string(), - initialIndex: z.number().int(), count: z.number().int().positive(), - path: z.string(), + initialIndex: z.number().int(), passphrase: z.string(), + path: z.string(), }); -const httpNetworkAccountsSchema = conditionalUnionType( +const httpNetworkAccountsConfigSchema = conditionalUnionType( [ [(data) => data === "remote", z.literal("remote")], [ (data) => Array.isArray(data), z.array(resolvedConfigurationVariableSchema), ], - [isObject, httpNetworkHDAccountsConfig], + [isObject, httpNetworkHDAccountsConfigSchema], ], `Expected 'remote', an array with of ResolvedConfigurationVariables, or an object with HD account details`, ); const httpNetworkConfigSchema = z.object({ type: z.literal("http"), + accounts: httpNetworkAccountsConfigSchema, chainId: z.optional(z.number().int()), - chainType: z.optional(chainTypeSchema), + chainType: z.optional(chainTypeConfigSchema), from: z.optional(z.string()), - gas: gasSchema, + gas: gasConfigSchema, gasMultiplier: z.number(), - gasPrice: gasSchema, - accounts: httpNetworkAccountsSchema, + gasPrice: gasConfigSchema, // HTTP network specific url: sensitiveUrlSchema, - timeout: z.number(), httpHeaders: z.record(z.string()), + timeout: z.number(), }); -const edrNetworkAccountSchema = z.object({ - privateKey: edrPrivateKeySchema, +const edrPrivateKeyConfigSchema = z + .string() + .refine( + (val) => + getUnprefixedHexString(val).length === 64 && + /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val)), + { message: "Expected a hex-encoded private key" }, + ); + +const edrNetworkAccountConfigSchema = z.object({ balance: z.bigint().positive(), + privateKey: edrPrivateKeyConfigSchema, }); const edrNetworkHDAccountsConfig = z.object({ mnemonic: z.string(), - initialIndex: z.number().int(), - count: z.number().int().positive(), - path: z.string(), accountsBalance: z.bigint(), + count: z.number().int().positive(), + initialIndex: z.number().int(), passphrase: z.string(), + path: z.string(), }); -const edrNetworkAccountsSchema = conditionalUnionType( +const edrNetworkAccountsConfigSchema = conditionalUnionType( [ - [(data) => Array.isArray(data), z.array(edrNetworkAccountSchema)], + [(data) => Array.isArray(data), z.array(edrNetworkAccountConfigSchema)], [isObject, edrNetworkHDAccountsConfig], ], `Expected an array with with objects with private key and balance, or an object with HD account details`, @@ -202,14 +220,17 @@ const edrNetworkAccountsSchema = conditionalUnionType( const edrNetworkConfigSchema = z.object({ type: z.literal("edr"), + // TODO: make this required and resolve the accounts in the config hook handler + accounts: z.optional(edrNetworkAccountsConfigSchema), chainId: z.number().int(), - chainType: z.optional(chainTypeSchema), + chainType: z.optional(chainTypeConfigSchema), from: z.optional(z.string()), - gas: gasSchema, + gas: gasConfigSchema, gasMultiplier: z.number(), - gasPrice: gasSchema, - // TODO: make this required and resolve the accounts in the config hook handler - accounts: z.optional(edrNetworkAccountsSchema), + gasPrice: gasConfigSchema, + + // EDR network specific + // TODO: add the rest of the fields }); const networkConfigSchema = z.discriminatedUnion("type", [ @@ -226,18 +247,18 @@ export function isNetworkConfig( export function validateNetworkConfig( networkConfig: unknown, -): Array<{ message: string; path: Array }> { +): HardhatUserConfigValidationError[] { const result = networkConfigSchema.safeParse(networkConfig); return result.error?.errors ?? []; } -export async function validateUserConfig( +export async function validateNetworkUserConfig( userConfig: HardhatUserConfig, ): Promise { return validateUserConfigZodType(userConfig, userConfigSchema); } -export function isHdAccountsConfig( +export function isHdAccountsUserConfig( accounts: HttpNetworkAccountsUserConfig, ): accounts is HttpNetworkHDAccountsUserConfig { return isObject(accounts); diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 4093c73c5e..4ce49b35fd 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -14,7 +14,7 @@ import { extendUserConfig, resolveUserConfig, } from "../../../../../src/internal/builtin-plugins/network-manager/hook-handlers/config.js"; -import { validateUserConfig } from "../../../../../src/internal/builtin-plugins/network-manager/type-validation.js"; +import { validateNetworkUserConfig } from "../../../../../src/internal/builtin-plugins/network-manager/type-validation.js"; import { FixedValueConfigurationVariable, resolveConfigurationVariable, @@ -118,7 +118,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - const validationErrors = await validateUserConfig(config); + const validationErrors = await validateNetworkUserConfig(config); assertValidationErrors(validationErrors, []); }); @@ -130,7 +130,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -147,7 +147,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -164,7 +164,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -185,7 +185,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -204,7 +204,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -227,7 +227,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -250,7 +250,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -273,7 +273,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -294,7 +294,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - let validationErrors = await validateUserConfig( + let validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithInvalidGas as any, @@ -317,7 +317,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - validationErrors = await validateUserConfig( + validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithNonSafeIntGas as any, @@ -340,7 +340,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - validationErrors = await validateUserConfig( + validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithNegativeGas as any, @@ -367,7 +367,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -388,7 +388,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - let validationErrors = await validateUserConfig( + let validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithInvalidGasPrice as any, @@ -411,7 +411,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - validationErrors = await validateUserConfig( + validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithNonSafeIntGasPrice as any, @@ -434,7 +434,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - validationErrors = await validateUserConfig( + validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithNegativeGasPrice as any, @@ -460,7 +460,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -482,7 +482,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -505,7 +505,7 @@ describe("network-manager/hook-handlers/config", () => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ - const validationErrors = await validateUserConfig(config as any); + const validationErrors = await validateNetworkUserConfig(config as any); assertValidationErrors(validationErrors, [ { @@ -526,7 +526,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - let validationErrors = await validateUserConfig( + let validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithStringHeaders as any, @@ -551,7 +551,7 @@ describe("network-manager/hook-handlers/config", () => { }, }; - validationErrors = await validateUserConfig( + validationErrors = await validateNetworkUserConfig( /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing invalid network type for js users */ configWithInvalidHeaderValue as any, @@ -587,7 +587,7 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = "remote"; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -600,7 +600,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assert.equal(validationErrors.length, 0); }); @@ -614,7 +614,7 @@ describe("network-manager/hook-handlers/config", () => { }; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -625,7 +625,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -639,7 +639,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -654,7 +654,7 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = ["0xaaaa"]; let validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -667,7 +667,8 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = [ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb", ]; - validationErrors = await validateUserConfig(hardhatUserConfig); + validationErrors = + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -684,7 +685,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -699,63 +700,81 @@ describe("network-manager/hook-handlers/config", () => { it("should fail with invalid types", async () => { hardhatUserConfig.networks.localhost.accounts = 123; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts"], - message: - "Expected 'remote', an array with private keys or Configuration Variables, or an object with HD account details", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts"], + message: + "Expected 'remote', an array with private keys or Configuration Variables, or an object with HD account details", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = [{}]; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", 0], - message: - "Expected a hex-encoded private key or a Configuration Variable", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", 0], + message: + "Expected a hex-encoded private key or a Configuration Variable", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { asd: 123 }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "mnemonic"], + message: "Required", + }, + ], + ); }); it("should fail with invalid HttpNetworkHDAccountsConfig", async () => { hardhatUserConfig.networks.localhost.accounts = { mnemonic: 123 }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Expected string, received number", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "mnemonic"], + message: "Expected string, received number", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { mnemonic: "valid", initialIndex: "asd", }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "initialIndex"], - message: "Expected number, received string", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "initialIndex"], + message: "Expected number, received string", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { mnemonic: "valid", initialIndex: 1, count: "asd", }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "count"], - message: "Expected number, received string", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "count"], + message: "Expected number, received string", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { mnemonic: "valid", @@ -763,30 +782,39 @@ describe("network-manager/hook-handlers/config", () => { count: 1, path: 123, }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "path"], - message: "Expected string, received number", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "path"], + message: "Expected string, received number", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { type: 123 }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "mnemonic"], + message: "Required", + }, + ], + ); hardhatUserConfig.networks.localhost.accounts = { initialIndex: 1, }; - assertValidationErrors(await validateUserConfig(hardhatUserConfig), [ - { - path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", - }, - ]); + assertValidationErrors( + await validateNetworkUserConfig(hardhatUserConfig), + [ + { + path: ["networks", "localhost", "accounts", "mnemonic"], + message: "Required", + }, + ], + ); }); }); @@ -831,7 +859,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -847,7 +875,7 @@ describe("network-manager/hook-handlers/config", () => { }; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -862,7 +890,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, []); }); @@ -879,7 +907,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -898,7 +926,7 @@ describe("network-manager/hook-handlers/config", () => { ]; let validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -915,7 +943,8 @@ describe("network-manager/hook-handlers/config", () => { }, ]; - validationErrors = await validateUserConfig(hardhatUserConfig); + validationErrors = + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], @@ -934,7 +963,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -961,7 +990,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -974,7 +1003,7 @@ describe("network-manager/hook-handlers/config", () => { it("should fail with invalid types", async () => { hardhatUserConfig.networks.localhost.accounts = 123; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts"], @@ -986,7 +1015,7 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = [{}]; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", 0, "balance"], @@ -1006,7 +1035,7 @@ describe("network-manager/hook-handlers/config", () => { }, ]; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", 0, "balance"], @@ -1017,7 +1046,7 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = [{ balance: "" }]; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], @@ -1028,7 +1057,7 @@ describe("network-manager/hook-handlers/config", () => { hardhatUserConfig.networks.localhost.accounts = [{ balance: 213 }]; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", 0, "balance"], @@ -1045,7 +1074,7 @@ describe("network-manager/hook-handlers/config", () => { { privateKey: 123 }, ]; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", 0, "balance"], @@ -1065,7 +1094,7 @@ describe("network-manager/hook-handlers/config", () => { ]; const validationErrors = - await validateUserConfig(hardhatUserConfig); + await validateNetworkUserConfig(hardhatUserConfig); assertValidationErrors(validationErrors, [ { @@ -1082,7 +1111,7 @@ describe("network-manager/hook-handlers/config", () => { it("should fail with invalid HD accounts", async () => { hardhatUserConfig.networks.localhost.accounts = { mnemonic: 123 }; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", "mnemonic"], @@ -1096,7 +1125,7 @@ describe("network-manager/hook-handlers/config", () => { initialIndex: "asd", }; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", "initialIndex"], @@ -1111,7 +1140,7 @@ describe("network-manager/hook-handlers/config", () => { count: "asd", }; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", "count"], @@ -1127,7 +1156,7 @@ describe("network-manager/hook-handlers/config", () => { path: 123, }; assertValidationErrors( - await validateUserConfig(hardhatUserConfig), + await validateNetworkUserConfig(hardhatUserConfig), [ { path: ["networks", "localhost", "accounts", "path"], From f71af73e2c8d4c4cc6ae1032f5405b638a7893f0 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Wed, 18 Dec 2024 14:14:39 +0000 Subject: [PATCH 02/11] feat: normalize account validation & allow config vars in private keys --- .../network-manager/config-resolution.ts | 15 +++++--- .../network-manager/edr/edr-provider.ts | 10 ++++-- .../edr/utils/convert-to-edr.ts | 19 ++++++----- .../network-manager/hook-handlers/config.ts | 5 ++- .../network-manager/type-extensions/config.ts | 4 +-- .../network-manager/type-validation.ts | 34 ++++++------------- .../internal/core/configuration-variables.ts | 13 +++---- v-next/hardhat/src/types/config.ts | 2 +- .../network-manager/hook-handlers/config.ts | 27 ++++++++++----- 9 files changed, 67 insertions(+), 62 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts index 0b2ec4da8d..d3ba9add86 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts @@ -62,12 +62,19 @@ export function resolveEdrNetworkAccounts( accounts: | EdrNetworkAccountsUserConfig | undefined = DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS, + resolveConfigurationVariable: ConfigurationResolver, ): EdrNetworkAccountsConfig { if (Array.isArray(accounts)) { - return accounts.map(({ privateKey, balance }) => ({ - privateKey: normalizeHexString(privateKey), - balance: BigInt(balance), - })); + return accounts.map(({ privateKey, balance }) => { + if (typeof privateKey === "string") { + privateKey = normalizeHexString(privateKey); + } + + return { + privateKey: resolveConfigurationVariable(privateKey), + balance: BigInt(balance), + }; + }); } return { diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts index 69c0093d8e..52e0af669c 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts @@ -141,12 +141,14 @@ export class EdrProvider extends EventEmitter implements EthereumProvider { const vmTraceDecoder = await createVmTraceDecoder(); + const providerConfig = await getProviderConfig(networkConfig); + const context = await getGlobalEdrContext(); const provider = await context.createProvider( networkConfig.chainType === "optimism" ? OPTIMISM_CHAIN_TYPE : GENERIC_CHAIN_TYPE, // TODO: l1 is missing here - getProviderConfig(networkConfig), + providerConfig, { enable: loggerConfig.enabled, decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs, @@ -537,7 +539,9 @@ export class EdrProvider extends EventEmitter implements EthereumProvider { } } -function getProviderConfig(networkConfig: EdrNetworkConfig): ProviderConfig { +async function getProviderConfig( + networkConfig: EdrNetworkConfig, +): Promise { return { allowBlocksWithSameTimestamp: networkConfig.allowBlocksWithSameTimestamp, allowUnlimitedContractSize: networkConfig.allowUnlimitedContractSize, @@ -551,7 +555,7 @@ function getProviderConfig(networkConfig: EdrNetworkConfig): ProviderConfig { coinbase: Buffer.from(networkConfig.coinbase), enableRip7212: networkConfig.enableRip7212, fork: hardhatForkingConfigToEdrForkConfig(networkConfig.forking), - genesisAccounts: hardhatAccountsToEdrGenesisAccounts( + genesisAccounts: await hardhatAccountsToEdrGenesisAccounts( networkConfig.accounts, ), hardfork: hardhatHardforkToEdrSpecId(networkConfig.hardfork), diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts index 62b42d4a93..3acf4e9a36 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts @@ -38,6 +38,7 @@ import { } from "@ignored/edr-optimism"; import { bytesToHexString } from "@ignored/hardhat-vnext-utils/bytes"; +import { FixedValueConfigurationVariable } from "../../../../core/configuration-variables.js"; import { derivePrivateKeys } from "../../accounts/derive-private-keys.js"; import { DEFAULT_EDR_NETWORK_BALANCE } from "../edr-provider.js"; import { HardforkName } from "../types/hardfork.js"; @@ -220,17 +221,17 @@ export function edrRpcDebugTraceToHardhat( }; } -export function hardhatAccountsToEdrGenesisAccounts( +export async function hardhatAccountsToEdrGenesisAccounts( accounts: EdrNetworkAccountsConfig, -): GenesisAccount[] { +): Promise { const normalizedAccounts = normalizeEdrNetworkAccountsConfig(accounts); - return normalizedAccounts.map((account) => { - return { - secretKey: account.privateKey, - balance: account.balance, - }; - }); + const accountPromises = normalizedAccounts.map(async (account) => ({ + secretKey: await account.privateKey.getHexString(), + balance: account.balance, + })); + + return Promise.all(accountPromises); } function normalizeEdrNetworkAccountsConfig( @@ -247,7 +248,7 @@ function normalizeEdrNetworkAccountsConfig( accounts.count, accounts.passphrase, ).map((pk) => ({ - privateKey: bytesToHexString(pk), + privateKey: new FixedValueConfigurationVariable(bytesToHexString(pk)), balance: accounts.accountsBalance ?? DEFAULT_EDR_NETWORK_BALANCE, })); } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 39573499af..69f7329abb 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -124,7 +124,10 @@ export async function resolveUserConfig( if (networkConfig.type === "edr") { const resolvedNetworkConfig: EdrNetworkConfig = { type: "edr", - accounts: resolveEdrNetworkAccounts(networkConfig.accounts), + accounts: resolveEdrNetworkAccounts( + networkConfig.accounts, + resolveConfigurationVariable, + ), chainId: networkConfig.chainId ?? 31337, chainType: networkConfig.chainType, from: networkConfig.from, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts index eef0c4a990..7135819161 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts @@ -78,7 +78,7 @@ declare module "../../../../types/config.js" { export interface EdrNetworkAccountUserConfig { balance: string | bigint; - privateKey: string; + privateKey: SensitiveString; } export interface EdrNetworkHDAccountsUserConfig { @@ -196,7 +196,7 @@ declare module "../../../../types/config.js" { export interface EdrNetworkAccountConfig { balance: bigint; - privateKey: string; + privateKey: ResolvedConfigurationVariable; } export interface EdrNetworkHDAccountsConfig { diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 793803845e..d39c301f65 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -6,7 +6,10 @@ import type { } from "../../../types/config.js"; import type { HardhatUserConfigValidationError } from "../../../types/hooks.js"; -import { getUnprefixedHexString } from "@ignored/hardhat-vnext-utils/hex"; +import { + getUnprefixedHexString, + isHexString, +} from "@ignored/hardhat-vnext-utils/hex"; import { isObject } from "@ignored/hardhat-vnext-utils/lang"; import { conditionalUnionType, @@ -23,8 +26,9 @@ const accountsPrivateKeyUserConfigSchema = unionType( configurationVariableSchema, z .string() - .refine((val) => getUnprefixedHexString(val).length === 64) - .refine((val) => /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val))), + .refine( + (val) => isHexString(val) && getUnprefixedHexString(val).length === 64, + ), ], `Expected a hex-encoded private key or a Configuration Variable`, ); @@ -79,21 +83,12 @@ const httpNetworkUserConfigSchema = z.object({ timeout: z.optional(z.number()), }); -const edrPrivateKeyUserConfigSchema = z - .string() - .refine( - (val) => - getUnprefixedHexString(val).length === 64 && - /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val)), - { message: "Expected a hex-encoded private key" }, - ); - const edrNetworkAccountUserConfigSchema = z.object({ balance: unionType( [z.string(), z.bigint().positive()], "Expected a string or a positive bigint", ), - privateKey: edrPrivateKeyUserConfigSchema, + privateKey: accountsPrivateKeyUserConfigSchema, }); const edrNetworkHDAccountsUserConfigSchema = z.object({ @@ -168,7 +163,7 @@ const httpNetworkAccountsConfigSchema = conditionalUnionType( ], [isObject, httpNetworkHDAccountsConfigSchema], ], - `Expected 'remote', an array with of ResolvedConfigurationVariables, or an object with HD account details`, + `Expected 'remote', an array of ResolvedConfigurationVariables, or an object with HD account details`, ); const httpNetworkConfigSchema = z.object({ @@ -187,18 +182,9 @@ const httpNetworkConfigSchema = z.object({ timeout: z.number(), }); -const edrPrivateKeyConfigSchema = z - .string() - .refine( - (val) => - getUnprefixedHexString(val).length === 64 && - /^[0-9a-fA-F]+$/.test(getUnprefixedHexString(val)), - { message: "Expected a hex-encoded private key" }, - ); - const edrNetworkAccountConfigSchema = z.object({ balance: z.bigint().positive(), - privateKey: edrPrivateKeyConfigSchema, + privateKey: resolvedConfigurationVariableSchema, }); const edrNetworkHDAccountsConfig = z.object({ diff --git a/v-next/hardhat/src/internal/core/configuration-variables.ts b/v-next/hardhat/src/internal/core/configuration-variables.ts index 0c42abc2a3..883defc0e5 100644 --- a/v-next/hardhat/src/internal/core/configuration-variables.ts +++ b/v-next/hardhat/src/internal/core/configuration-variables.ts @@ -5,10 +5,7 @@ import type { import type { HookManager } from "../../types/hooks.js"; import { HardhatError } from "@ignored/hardhat-vnext-errors"; -import { - isHexString, - normalizeHexString, -} from "@ignored/hardhat-vnext-utils/hex"; +import { normalizeHexString } from "@ignored/hardhat-vnext-utils/hex"; export function resolveConfigurationVariable( hooks: HookManager, @@ -66,15 +63,13 @@ abstract class BaseResolvedConfigurationVariable public async getHexString(): Promise { const value = await this.get(); - - const trimmedLowercaseValue = value.trim().toLowerCase(); - if (!isHexString(trimmedLowercaseValue)) { + try { + return normalizeHexString(value); + } catch { throw new HardhatError(HardhatError.ERRORS.GENERAL.INVALID_HEX_STRING, { value, }); } - - return normalizeHexString(trimmedLowercaseValue); } } diff --git a/v-next/hardhat/src/types/config.ts b/v-next/hardhat/src/types/config.ts index 61fa4924fb..e801e37381 100644 --- a/v-next/hardhat/src/types/config.ts +++ b/v-next/hardhat/src/types/config.ts @@ -40,7 +40,7 @@ export interface ResolvedConfigurationVariable { /** * Returns the value of the configuration variable, validating that is a - * valid hex string. Trimming any speaces, and making sure it's lowecase and + * valid hex string. Trimming any spaces, and making sure it's lowecase and * that it starts with 0x. */ getHexString(): Promise; diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 4ce49b35fd..d68820f59c 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -912,7 +912,8 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected string, received number", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ]); }); @@ -931,7 +932,8 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected a hex-encoded private key", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ]); @@ -948,7 +950,8 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected a hex-encoded private key", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ]); }); @@ -968,7 +971,8 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected a hex-encoded private key", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ]); }); @@ -1023,7 +1027,8 @@ describe("network-manager/hook-handlers/config", () => { }, { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Required", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ], ); @@ -1050,7 +1055,8 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Required", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ], ); @@ -1065,7 +1071,8 @@ describe("network-manager/hook-handlers/config", () => { }, { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Required", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ], ); @@ -1082,7 +1089,8 @@ describe("network-manager/hook-handlers/config", () => { }, { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected string, received number", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, ], ); @@ -1099,7 +1107,8 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "accounts", 0, "privateKey"], - message: "Expected a hex-encoded private key", + message: + "Expected a hex-encoded private key or a Configuration Variable", }, { path: ["networks", "localhost", "accounts", 0, "balance"], From 7747a15693b7cdb8ab873a05fed037102ffbb0bd Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Wed, 18 Dec 2024 22:30:27 +0000 Subject: [PATCH 03/11] feat: allow config vars in http network url --- .../network-manager/hook-handlers/config.ts | 2 +- .../network-manager/network-manager.ts | 2 +- .../network-manager/type-extensions/config.ts | 4 +-- .../network-manager/type-validation.ts | 4 +-- .../network-manager/hook-handlers/config.ts | 25 +++++++++++-------- .../network-manager/network-connection.ts | 5 ++-- .../network-manager/network-manager.ts | 7 +++--- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 69f7329abb..7b7d31d356 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -113,7 +113,7 @@ export async function resolveUserConfig( gas: resolveGasConfig(networkConfig.gas), gasMultiplier: networkConfig.gasMultiplier ?? 1, gasPrice: resolveGasConfig(networkConfig.gasPrice), - url: networkConfig.url, + url: resolveConfigurationVariable(networkConfig.url), timeout: networkConfig.timeout ?? 20_000, httpHeaders: networkConfig.httpHeaders ?? {}, }; diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/network-manager.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/network-manager.ts index e43cc75ca7..5f2daec7d5 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/network-manager.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/network-manager.ts @@ -180,7 +180,7 @@ export class NetworkManagerImplementation implements NetworkManager { } return HttpProvider.create({ - url: resolvedNetworkConfig.url, + url: await resolvedNetworkConfig.url.getUrl(), networkName: resolvedNetworkName, extraHeaders: resolvedNetworkConfig.httpHeaders, timeout: resolvedNetworkConfig.timeout, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts index 7135819161..e74c1e26a3 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts @@ -22,7 +22,7 @@ declare module "../../../../types/config.js" { gasPrice?: GasUserConfig; // HTTP network specific - url: string; + url: SensitiveString; httpHeaders?: Record; timeout?: number; } @@ -140,7 +140,7 @@ declare module "../../../../types/config.js" { gasPrice: GasConfig; // HTTP network specific - url: string; + url: ResolvedConfigurationVariable; httpHeaders: Record; timeout: number; } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index d39c301f65..bb2010b642 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -78,7 +78,7 @@ const httpNetworkUserConfigSchema = z.object({ gasPrice: z.optional(gasUserConfigSchema), // HTTP network specific - url: z.string().url(), // TODO: this should be a sensitiveUrlSchema + url: sensitiveUrlSchema, httpHeaders: z.optional(z.record(z.string())), timeout: z.optional(z.number()), }); @@ -177,7 +177,7 @@ const httpNetworkConfigSchema = z.object({ gasPrice: gasConfigSchema, // HTTP network specific - url: sensitiveUrlSchema, + url: resolvedConfigurationVariableSchema, httpHeaders: z.record(z.string()), timeout: z.number(), }); diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts index d68820f59c..251c14558f 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -465,7 +465,7 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "url"], - message: "Required", + message: "Expected a URL or a Configuration Variable", }, ]); }); @@ -487,7 +487,7 @@ describe("network-manager/hook-handlers/config", () => { assertValidationErrors(validationErrors, [ { path: ["networks", "localhost", "url"], - message: "Invalid url", + message: "Expected a URL or a Configuration Variable", }, ]); }); @@ -1202,7 +1202,7 @@ describe("network-manager/hook-handlers/config", () => { nextUserConfig: HardhatUserConfig, /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast for simplicity as we won't test this */ - ) => nextUserConfig as HardhatConfig; + ) => nextUserConfig as unknown as HardhatConfig; const resolvedConfig = await resolveUserConfig( extendedConfig, @@ -1222,7 +1222,7 @@ describe("network-manager/hook-handlers/config", () => { gasMultiplier: 1, gasPrice: "auto", accounts: "remote", - url: "http://localhost:8545", + url: new FixedValueConfigurationVariable("http://localhost:8545"), timeout: 20_000, httpHeaders: {}, }, @@ -1258,7 +1258,7 @@ describe("network-manager/hook-handlers/config", () => { nextUserConfig: HardhatUserConfig, /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast for simplicity as we won't test this */ - ) => nextUserConfig as HardhatConfig; + ) => nextUserConfig as unknown as HardhatConfig; const resolvedConfig = await resolveUserConfig( userConfig, @@ -1268,6 +1268,7 @@ describe("network-manager/hook-handlers/config", () => { assert.equal(resolvedConfig.defaultChainType, "generic"); assert.equal(resolvedConfig.defaultNetwork, "myNetwork"); + assert.equal(resolvedConfig.networks.myNetwork.type, "http"); assert.deepEqual(resolvedConfig.networks, { myNetwork: { type: "http", @@ -1282,7 +1283,7 @@ describe("network-manager/hook-handlers/config", () => { "0x000006d4548a3ac17d72b372ae1e416bf65b8ead", ), ], - url: "http://node.myNetwork.com", + url: new FixedValueConfigurationVariable("http://node.myNetwork.com"), timeout: 10_000, httpHeaders: { "Content-Type": "application/json", @@ -1322,7 +1323,7 @@ describe("network-manager/hook-handlers/config", () => { nextUserConfig: HardhatUserConfig, /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast for simplicity as we won't test this */ - ) => nextUserConfig as HardhatConfig; + ) => nextUserConfig as unknown as HardhatConfig; const resolvedConfig = await resolveUserConfig( userConfig, @@ -1353,7 +1354,9 @@ describe("network-manager/hook-handlers/config", () => { "0x000006d4548a3ac17d72b372ae1e416bf65b8ddd", ), ], - url: "http://node.myNetwork.com", + url: new FixedValueConfigurationVariable( + "http://node.myNetwork.com", + ), timeout: 10_000, httpHeaders: { "Content-Type": "application/json", @@ -1390,7 +1393,7 @@ describe("network-manager/hook-handlers/config", () => { nextUserConfig: HardhatUserConfig, /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast for simplicity as we won't test this */ - ) => nextUserConfig as HardhatConfig; + ) => nextUserConfig as unknown as HardhatConfig; const resolvedConfig = await resolveUserConfig( userConfig, @@ -1414,7 +1417,9 @@ describe("network-manager/hook-handlers/config", () => { path: "m/44'/60'/0'/0", passphrase: "passphrase", }, - url: "http://node.myNetwork.com", + url: new FixedValueConfigurationVariable( + "http://node.myNetwork.com", + ), timeout: 10_000, httpHeaders: { "Content-Type": "application/json", diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-connection.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-connection.ts index 12239bb8d5..6076ae8dd3 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-connection.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-connection.ts @@ -6,6 +6,7 @@ import { describe, it } from "node:test"; import { HttpProvider } from "../../../../src/internal/builtin-plugins/network-manager/http-provider.js"; import { NetworkConnectionImplementation } from "../../../../src/internal/builtin-plugins/network-manager/network-connection.js"; +import { FixedValueConfigurationVariable } from "../../../../src/internal/core/configuration-variables.js"; describe("NetworkConnectionImplementation", () => { const localhostNetworkConfig: NetworkConfig = { @@ -17,7 +18,7 @@ describe("NetworkConnectionImplementation", () => { gasMultiplier: 1, gasPrice: "auto", accounts: [], - url: "http://localhost:8545", + url: new FixedValueConfigurationVariable("http://localhost:8545"), timeout: 20_000, httpHeaders: {}, }; @@ -28,7 +29,7 @@ describe("NetworkConnectionImplementation", () => { const createProvider = async (): Promise => { expectedProvider = await HttpProvider.create({ - url: localhostNetworkConfig.url, + url: await localhostNetworkConfig.url.getUrl(), networkName: "localhost", extraHeaders: localhostNetworkConfig.httpHeaders, timeout: localhostNetworkConfig.timeout, diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-manager.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-manager.ts index 15812fdacd..2960b4e7a2 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-manager.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/network-manager.ts @@ -17,6 +17,7 @@ import { expectTypeOf } from "expect-type"; import { createHardhatRuntimeEnvironment } from "../../../../src/hre.js"; import { NetworkManagerImplementation } from "../../../../src/internal/builtin-plugins/network-manager/network-manager.js"; import { validateNetworkConfig } from "../../../../src/internal/builtin-plugins/network-manager/type-validation.js"; +import { FixedValueConfigurationVariable } from "../../../../src/internal/core/configuration-variables.js"; describe("NetworkManagerImplementation", () => { let hre: HardhatRuntimeEnvironment; @@ -31,7 +32,7 @@ describe("NetworkManagerImplementation", () => { gasMultiplier: 1, gasPrice: "auto", accounts: [], - url: "http://localhost:8545", + url: new FixedValueConfigurationVariable("http://localhost:8545"), timeout: 20_000, httpHeaders: {}, }, @@ -44,7 +45,7 @@ describe("NetworkManagerImplementation", () => { gasMultiplier: 1, gasPrice: "auto", accounts: [], - url: "http://node.customNetwork.com", + url: new FixedValueConfigurationVariable("http://node.customNetwork.com"), timeout: 20_000, httpHeaders: {}, }, @@ -57,7 +58,7 @@ describe("NetworkManagerImplementation", () => { gasMultiplier: 1, gasPrice: "auto", accounts: [], - url: "http://node.myNetwork.com", + url: new FixedValueConfigurationVariable("http://node.myNetwork.com"), timeout: 20_000, httpHeaders: {}, }, From ceb803f21e7853348384a14985833dbd78d8078c Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Wed, 18 Dec 2024 23:01:56 +0000 Subject: [PATCH 04/11] style: normalize usage of z.optional --- .../network-manager/type-validation.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index bb2010b642..9dc25469cc 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -93,10 +93,12 @@ const edrNetworkAccountUserConfigSchema = z.object({ const edrNetworkHDAccountsUserConfigSchema = z.object({ mnemonic: z.optional(z.string()), - accountsBalance: unionType( - [z.string(), z.bigint().positive()], - "Expected a string or a positive bigint", - ).optional(), + accountsBalance: z.optional( + unionType( + [z.string(), z.bigint().positive()], + "Expected a string or a positive bigint", + ), + ), count: z.optional(z.number().int().positive()), initialIndex: z.optional(z.number().int()), passphrase: z.optional(z.string()), @@ -114,12 +116,12 @@ const edrNetworkAccountsUserConfigSchema = conditionalUnionType( const edrNetworkUserConfigSchema = z.object({ type: z.literal("edr"), accounts: z.optional(edrNetworkAccountsUserConfigSchema), - chainId: z.number().int().optional(), + chainId: z.optional(z.number().int()), chainType: z.optional(chainTypeUserConfigSchema), - from: z.optional(z.string()).optional(), - gas: gasUserConfigSchema.optional(), - gasMultiplier: z.number().optional(), - gasPrice: gasUserConfigSchema.optional(), + from: z.optional(z.string()), + gas: z.optional(gasUserConfigSchema), + gasMultiplier: z.optional(z.number()), + gasPrice: z.optional(gasUserConfigSchema), // EDR network specific // TODO: add the rest of the fields From 96dd4edfdbc27589601ad35515cba506383d696f Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Thu, 19 Dec 2024 21:05:02 +0000 Subject: [PATCH 05/11] feat: add Edr Network missing schemas and refine number schemas --- .../network-manager/type-validation.ts | 204 ++++++++++++++---- 1 file changed, 164 insertions(+), 40 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 9dc25469cc..83c888fa59 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -21,6 +21,13 @@ import { } from "@ignored/hardhat-vnext-zod-utils"; import { z } from "zod"; +const nonnegativeNumberSchema = z.number().nonnegative(); +const nonnegativeIntSchema = z.number().int().nonnegative(); +const nonnegativeBigIntSchema = z.bigint().nonnegative(); + +const blockNumberSchema = nonnegativeIntSchema; +const chainIdSchema = nonnegativeIntSchema; + const accountsPrivateKeyUserConfigSchema = unionType( [ configurationVariableSchema, @@ -35,8 +42,8 @@ const accountsPrivateKeyUserConfigSchema = unionType( const httpNetworkHDAccountsUserConfigSchema = z.object({ mnemonic: z.string(), - count: z.optional(z.number().int().positive()), - initialIndex: z.optional(z.number().int()), + count: z.optional(nonnegativeIntSchema), + initialIndex: z.optional(nonnegativeIntSchema), passphrase: z.optional(z.string()), path: z.optional(z.string()), }); @@ -58,49 +65,47 @@ const chainTypeUserConfigSchema = unionType( "Expected 'l1', 'optimism', or 'generic'", ); +const gasUnitUserConfigSchema = unionType( + [nonnegativeIntSchema.safe(), nonnegativeBigIntSchema], + "Expected a positive safe int or a positive bigint", +); + const gasUserConfigSchema = unionType( - [ - z.literal("auto"), - z.number().int().positive().safe(), - z.bigint().positive(), - ], + [z.literal("auto"), gasUnitUserConfigSchema], "Expected 'auto', a positive safe int, or positive bigint", ); const httpNetworkUserConfigSchema = z.object({ type: z.literal("http"), accounts: z.optional(httpNetworkAccountsUserConfigSchema), - chainId: z.optional(z.number().int()), + chainId: z.optional(chainIdSchema), chainType: z.optional(chainTypeUserConfigSchema), from: z.optional(z.string()), gas: z.optional(gasUserConfigSchema), - gasMultiplier: z.optional(z.number()), + gasMultiplier: z.optional(nonnegativeNumberSchema), gasPrice: z.optional(gasUserConfigSchema), // HTTP network specific url: sensitiveUrlSchema, httpHeaders: z.optional(z.record(z.string())), - timeout: z.optional(z.number()), + timeout: z.optional(nonnegativeNumberSchema), }); +const accountBalanceUserConfigSchema = unionType( + [z.string(), nonnegativeBigIntSchema], + "Expected a string or a positive bigint", +); + const edrNetworkAccountUserConfigSchema = z.object({ - balance: unionType( - [z.string(), z.bigint().positive()], - "Expected a string or a positive bigint", - ), + balance: accountBalanceUserConfigSchema, privateKey: accountsPrivateKeyUserConfigSchema, }); const edrNetworkHDAccountsUserConfigSchema = z.object({ mnemonic: z.optional(z.string()), - accountsBalance: z.optional( - unionType( - [z.string(), z.bigint().positive()], - "Expected a string or a positive bigint", - ), - ), - count: z.optional(z.number().int().positive()), - initialIndex: z.optional(z.number().int()), + accountsBalance: z.optional(accountBalanceUserConfigSchema), + count: z.optional(nonnegativeIntSchema), + initialIndex: z.optional(nonnegativeIntSchema), passphrase: z.optional(z.string()), path: z.optional(z.string()), }); @@ -113,18 +118,77 @@ const edrNetworkAccountsUserConfigSchema = conditionalUnionType( `Expected an array with with objects with private key and balance or Configuration Variables, or an object with HD account details`, ); +const hardforkHistoryUserConfig = z.map(z.string(), blockNumberSchema); + +const edrNetworkChainUserConfigSchema = z.object({ + hardforkHistory: z.optional(hardforkHistoryUserConfig), +}); + +const edrNetworkChainsUserConfigSchema = z.map( + chainIdSchema, + edrNetworkChainUserConfigSchema, +); + +const edrNetworkForkingUserConfig = z.object({ + enabled: z.optional(z.boolean()), + url: z.string(), + blockNumber: z.optional(blockNumberSchema), + httpHeaders: z.optional(z.record(z.string())), +}); + +const edrNetworkMempoolUserConfigSchema = z.object({ + order: z.optional( + unionType( + [z.literal("fifo"), z.literal("priority")], + "Expected 'fifo' or 'priority'", + ), + ), +}); + +const edrNetworkMiningUserConfigSchema = z.object({ + auto: z.optional(z.boolean()), + interval: z.optional( + unionType( + [ + nonnegativeIntSchema, + z.tuple([nonnegativeIntSchema, nonnegativeIntSchema]), + ], + "Expected a number or an array of numbers", + ), + ), + mempool: z.optional(edrNetworkMempoolUserConfigSchema), +}); + const edrNetworkUserConfigSchema = z.object({ type: z.literal("edr"), accounts: z.optional(edrNetworkAccountsUserConfigSchema), - chainId: z.optional(z.number().int()), + chainId: z.optional(chainIdSchema), chainType: z.optional(chainTypeUserConfigSchema), from: z.optional(z.string()), gas: z.optional(gasUserConfigSchema), - gasMultiplier: z.optional(z.number()), + gasMultiplier: z.optional(nonnegativeNumberSchema), gasPrice: z.optional(gasUserConfigSchema), // EDR network specific - // TODO: add the rest of the fields + allowBlocksWithSameTimestamp: z.optional(z.boolean()), + allowUnlimitedContractSize: z.optional(z.boolean()), + blockGasLimit: z.optional(gasUnitUserConfigSchema), + chains: z.optional(edrNetworkChainsUserConfigSchema), + coinbase: z.optional(z.string()), + enableRip7212: z.optional(z.boolean()), + enableTransientStorage: z.optional(z.boolean()), + forking: z.optional(edrNetworkForkingUserConfig), + hardfork: z.optional(z.string()), + initialBaseFeePerGas: z.optional(gasUnitUserConfigSchema), + initialDate: z.optional( + unionType([z.string(), z.instanceof(Date)], "Expected a string or a Date"), + ), + loggingEnabled: z.optional(z.boolean()), + minGasPrice: z.optional(gasUnitUserConfigSchema), + mining: z.optional(edrNetworkMiningUserConfigSchema), + networkId: z.optional(chainIdSchema), + throwOnCallFailures: z.optional(z.boolean()), + throwOnTransactionFailures: z.optional(z.boolean()), }); const networkUserConfigSchema = z.discriminatedUnion("type", [ @@ -143,15 +207,17 @@ const chainTypeConfigSchema = unionType( "Expected 'l1', 'optimism', or 'generic'", ); +const gasUnitConfigSchema = nonnegativeBigIntSchema; + const gasConfigSchema = unionType( - [z.literal("auto"), z.bigint().positive()], + [z.literal("auto"), gasUnitConfigSchema], "Expected 'auto' or positive bigint", ); const httpNetworkHDAccountsConfigSchema = z.object({ mnemonic: z.string(), - count: z.number().int().positive(), - initialIndex: z.number().int(), + count: nonnegativeIntSchema, + initialIndex: nonnegativeIntSchema, passphrase: z.string(), path: z.string(), }); @@ -171,29 +237,31 @@ const httpNetworkAccountsConfigSchema = conditionalUnionType( const httpNetworkConfigSchema = z.object({ type: z.literal("http"), accounts: httpNetworkAccountsConfigSchema, - chainId: z.optional(z.number().int()), + chainId: z.optional(chainIdSchema), chainType: z.optional(chainTypeConfigSchema), from: z.optional(z.string()), gas: gasConfigSchema, - gasMultiplier: z.number(), + gasMultiplier: nonnegativeNumberSchema, gasPrice: gasConfigSchema, // HTTP network specific url: resolvedConfigurationVariableSchema, httpHeaders: z.record(z.string()), - timeout: z.number(), + timeout: nonnegativeNumberSchema, }); +const accountBalanceConfigSchema = nonnegativeBigIntSchema; + const edrNetworkAccountConfigSchema = z.object({ - balance: z.bigint().positive(), + balance: accountBalanceConfigSchema, privateKey: resolvedConfigurationVariableSchema, }); const edrNetworkHDAccountsConfig = z.object({ mnemonic: z.string(), - accountsBalance: z.bigint(), - count: z.number().int().positive(), - initialIndex: z.number().int(), + accountsBalance: accountBalanceConfigSchema, + count: nonnegativeIntSchema, + initialIndex: nonnegativeIntSchema, passphrase: z.string(), path: z.string(), }); @@ -206,19 +274,75 @@ const edrNetworkAccountsConfigSchema = conditionalUnionType( `Expected an array with with objects with private key and balance, or an object with HD account details`, ); +const hardforkHistoryConfig = z.map(z.string(), blockNumberSchema); + +const edrNetworkChainConfigSchema = z.object({ + hardforkHistory: hardforkHistoryConfig, +}); + +const edrNetworkChainsConfigSchema = z.map( + chainIdSchema, + edrNetworkChainConfigSchema, +); + +const edrNetworkForkingConfig = z.object({ + enabled: z.boolean(), + url: z.string(), + cacheDir: z.string(), + blockNumber: z.optional(blockNumberSchema), + httpHeaders: z.optional(z.record(z.string())), +}); + +const edrNetworkMempoolConfigSchema = z.object({ + order: unionType( + [z.literal("fifo"), z.literal("priority")], + "Expected 'fifo' or 'priority'", + ), +}); + +const edrNetworkMiningConfigSchema = z.object({ + auto: z.boolean(), + interval: unionType( + [ + nonnegativeIntSchema, + z.tuple([nonnegativeIntSchema, nonnegativeIntSchema]), + ], + "Expected a number or an array of numbers", + ), + mempool: z.optional(edrNetworkMempoolConfigSchema), +}); + const edrNetworkConfigSchema = z.object({ type: z.literal("edr"), - // TODO: make this required and resolve the accounts in the config hook handler - accounts: z.optional(edrNetworkAccountsConfigSchema), - chainId: z.number().int(), + accounts: edrNetworkAccountsConfigSchema, + chainId: chainIdSchema, chainType: z.optional(chainTypeConfigSchema), from: z.optional(z.string()), gas: gasConfigSchema, - gasMultiplier: z.number(), + gasMultiplier: nonnegativeNumberSchema, gasPrice: gasConfigSchema, // EDR network specific - // TODO: add the rest of the fields + allowBlocksWithSameTimestamp: z.boolean(), + allowUnlimitedContractSize: z.boolean(), + blockGasLimit: gasUnitConfigSchema, + chains: edrNetworkChainsConfigSchema, + coinbase: z.instanceof(Uint8Array), + enableRip7212: z.boolean(), + enableTransientStorage: z.boolean(), + forking: z.optional(edrNetworkForkingConfig), + hardfork: z.string(), + initialBaseFeePerGas: z.optional(gasUnitConfigSchema), + initialDate: unionType( + [z.string(), z.instanceof(Date)], + "Expected a string or a Date", + ), + loggingEnabled: z.boolean(), + minGasPrice: gasUnitConfigSchema, + mining: edrNetworkMiningConfigSchema, + networkId: chainIdSchema, + throwOnCallFailures: z.boolean(), + throwOnTransactionFailures: z.boolean(), }); const networkConfigSchema = z.discriminatedUnion("type", [ From babb4f62d62835c30142e4f3ee3919581396a3ea Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Thu, 19 Dec 2024 21:17:52 +0000 Subject: [PATCH 06/11] feat: allow config vars in edr forking config url --- .../builtin-plugins/network-manager/config-resolution.ts | 3 ++- .../builtin-plugins/network-manager/edr/edr-provider.ts | 2 +- .../network-manager/edr/utils/convert-to-edr.ts | 6 +++--- .../builtin-plugins/network-manager/hook-handlers/config.ts | 1 + .../network-manager/type-extensions/config.ts | 4 ++-- .../builtin-plugins/network-manager/type-validation.ts | 4 ++-- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts index d3ba9add86..7f8bbf825e 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts @@ -90,6 +90,7 @@ export function resolveEdrNetworkAccounts( export function resolveForkingConfig( forkingUserConfig: EdrNetworkForkingUserConfig | undefined, cacheDir: string, + resolveConfigurationVariable: ConfigurationResolver, ): EdrNetworkForkingConfig | undefined { if (forkingUserConfig === undefined) { return undefined; @@ -105,7 +106,7 @@ export function resolveForkingConfig( return { enabled: forkingUserConfig.enabled ?? true, - url: forkingUserConfig.url, + url: resolveConfigurationVariable(forkingUserConfig.url), cacheDir: path.join(cacheDir, "edr-fork-cache"), blockNumber: forkingUserConfig.blockNumber !== undefined diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts index 52e0af669c..a8b45a6538 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts @@ -554,7 +554,7 @@ async function getProviderConfig( // TODO: remove this cast when EDR updates the interface to accept Uint8Array coinbase: Buffer.from(networkConfig.coinbase), enableRip7212: networkConfig.enableRip7212, - fork: hardhatForkingConfigToEdrForkConfig(networkConfig.forking), + fork: await hardhatForkingConfigToEdrForkConfig(networkConfig.forking), genesisAccounts: await hardhatAccountsToEdrGenesisAccounts( networkConfig.accounts, ), diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts index 3acf4e9a36..7006677a3d 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts @@ -279,13 +279,13 @@ export function hardhatChainsToEdrChains( return edrChains; } -export function hardhatForkingConfigToEdrForkConfig( +export async function hardhatForkingConfigToEdrForkConfig( forkingConfig: EdrNetworkForkingConfig | undefined, -): ForkConfig | undefined { +): Promise { let fork: ForkConfig | undefined; if (forkingConfig !== undefined && forkingConfig.enabled === true) { fork = { - jsonRpcUrl: forkingConfig.url, + jsonRpcUrl: await forkingConfig.url.getUrl(), blockNumber: forkingConfig.blockNumber, httpHeaders: forkingConfig.httpHeaders, }; diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 7b7d31d356..0d8c83fac4 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -147,6 +147,7 @@ export async function resolveUserConfig( forking: resolveForkingConfig( networkConfig.forking, resolvedConfig.paths.cache, + resolveConfigurationVariable, ), hardfork: resolveHardfork( networkConfig.hardfork, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts index e74c1e26a3..42b324d5dd 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts @@ -106,7 +106,7 @@ declare module "../../../../types/config.js" { export interface EdrNetworkForkingUserConfig { enabled?: boolean; - url: string; + url: SensitiveString; blockNumber?: number; httpHeaders?: Record; } @@ -224,7 +224,7 @@ declare module "../../../../types/config.js" { export interface EdrNetworkForkingConfig { enabled: boolean; - url: string; + url: ResolvedConfigurationVariable; cacheDir: string; blockNumber?: bigint; httpHeaders?: HttpHeader[]; diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 83c888fa59..134a9c0123 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -131,7 +131,7 @@ const edrNetworkChainsUserConfigSchema = z.map( const edrNetworkForkingUserConfig = z.object({ enabled: z.optional(z.boolean()), - url: z.string(), + url: sensitiveUrlSchema, blockNumber: z.optional(blockNumberSchema), httpHeaders: z.optional(z.record(z.string())), }); @@ -287,7 +287,7 @@ const edrNetworkChainsConfigSchema = z.map( const edrNetworkForkingConfig = z.object({ enabled: z.boolean(), - url: z.string(), + url: resolvedConfigurationVariableSchema, cacheDir: z.string(), blockNumber: z.optional(blockNumberSchema), httpHeaders: z.optional(z.record(z.string())), From d0d9557b5d19be82e878d22b3f3fe0f643853078 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Mon, 23 Dec 2024 15:46:18 +0000 Subject: [PATCH 07/11] feat: validate hardfork --- .../builtin-plugins/network-manager/type-validation.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 134a9c0123..a9383779b1 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -21,6 +21,8 @@ import { } from "@ignored/hardhat-vnext-zod-utils"; import { z } from "zod"; +import { HardforkName } from "./edr/types/hardfork.js"; + const nonnegativeNumberSchema = z.number().nonnegative(); const nonnegativeIntSchema = z.number().int().nonnegative(); const nonnegativeBigIntSchema = z.bigint().nonnegative(); @@ -178,7 +180,7 @@ const edrNetworkUserConfigSchema = z.object({ enableRip7212: z.optional(z.boolean()), enableTransientStorage: z.optional(z.boolean()), forking: z.optional(edrNetworkForkingUserConfig), - hardfork: z.optional(z.string()), + hardfork: z.optional(z.nativeEnum(HardforkName)), initialBaseFeePerGas: z.optional(gasUnitUserConfigSchema), initialDate: z.optional( unionType([z.string(), z.instanceof(Date)], "Expected a string or a Date"), @@ -331,7 +333,7 @@ const edrNetworkConfigSchema = z.object({ enableRip7212: z.boolean(), enableTransientStorage: z.boolean(), forking: z.optional(edrNetworkForkingConfig), - hardfork: z.string(), + hardfork: z.nativeEnum(HardforkName), initialBaseFeePerGas: z.optional(gasUnitConfigSchema), initialDate: unionType( [z.string(), z.instanceof(Date)], From 1b357336c7b9037721700ef68df808b62e98d446 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Mon, 23 Dec 2024 16:13:22 +0000 Subject: [PATCH 08/11] style: add 'Schema' postfix to names missing it --- .../network-manager/type-validation.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index a9383779b1..25b1e19cb7 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -120,10 +120,10 @@ const edrNetworkAccountsUserConfigSchema = conditionalUnionType( `Expected an array with with objects with private key and balance or Configuration Variables, or an object with HD account details`, ); -const hardforkHistoryUserConfig = z.map(z.string(), blockNumberSchema); +const hardforkHistoryUserConfigSchema = z.map(z.string(), blockNumberSchema); const edrNetworkChainUserConfigSchema = z.object({ - hardforkHistory: z.optional(hardforkHistoryUserConfig), + hardforkHistory: z.optional(hardforkHistoryUserConfigSchema), }); const edrNetworkChainsUserConfigSchema = z.map( @@ -131,7 +131,7 @@ const edrNetworkChainsUserConfigSchema = z.map( edrNetworkChainUserConfigSchema, ); -const edrNetworkForkingUserConfig = z.object({ +const edrNetworkForkingUserConfigSchema = z.object({ enabled: z.optional(z.boolean()), url: sensitiveUrlSchema, blockNumber: z.optional(blockNumberSchema), @@ -179,7 +179,7 @@ const edrNetworkUserConfigSchema = z.object({ coinbase: z.optional(z.string()), enableRip7212: z.optional(z.boolean()), enableTransientStorage: z.optional(z.boolean()), - forking: z.optional(edrNetworkForkingUserConfig), + forking: z.optional(edrNetworkForkingUserConfigSchema), hardfork: z.optional(z.nativeEnum(HardforkName)), initialBaseFeePerGas: z.optional(gasUnitUserConfigSchema), initialDate: z.optional( @@ -259,7 +259,7 @@ const edrNetworkAccountConfigSchema = z.object({ privateKey: resolvedConfigurationVariableSchema, }); -const edrNetworkHDAccountsConfig = z.object({ +const edrNetworkHDAccountsConfigSchema = z.object({ mnemonic: z.string(), accountsBalance: accountBalanceConfigSchema, count: nonnegativeIntSchema, @@ -271,15 +271,15 @@ const edrNetworkHDAccountsConfig = z.object({ const edrNetworkAccountsConfigSchema = conditionalUnionType( [ [(data) => Array.isArray(data), z.array(edrNetworkAccountConfigSchema)], - [isObject, edrNetworkHDAccountsConfig], + [isObject, edrNetworkHDAccountsConfigSchema], ], `Expected an array with with objects with private key and balance, or an object with HD account details`, ); -const hardforkHistoryConfig = z.map(z.string(), blockNumberSchema); +const hardforkHistoryConfigSchema = z.map(z.string(), blockNumberSchema); const edrNetworkChainConfigSchema = z.object({ - hardforkHistory: hardforkHistoryConfig, + hardforkHistory: hardforkHistoryConfigSchema, }); const edrNetworkChainsConfigSchema = z.map( @@ -287,7 +287,7 @@ const edrNetworkChainsConfigSchema = z.map( edrNetworkChainConfigSchema, ); -const edrNetworkForkingConfig = z.object({ +const edrNetworkForkingConfigSchema = z.object({ enabled: z.boolean(), url: resolvedConfigurationVariableSchema, cacheDir: z.string(), @@ -332,7 +332,7 @@ const edrNetworkConfigSchema = z.object({ coinbase: z.instanceof(Uint8Array), enableRip7212: z.boolean(), enableTransientStorage: z.boolean(), - forking: z.optional(edrNetworkForkingConfig), + forking: z.optional(edrNetworkForkingConfigSchema), hardfork: z.nativeEnum(HardforkName), initialBaseFeePerGas: z.optional(gasUnitConfigSchema), initialDate: unionType( From 8bc4fae67ba2d0094916de6c679b6623820aab67 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Tue, 24 Dec 2024 15:22:32 +0000 Subject: [PATCH 09/11] feat: add validations to hardfork, minGasPrice and initialBaseFeePerGas --- .../network-manager/config-resolution.ts | 4 +- .../network-manager/edr/types/hardfork.ts | 19 +++++ .../network-manager/type-validation.ts | 83 +++++++++++++++++-- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts index 7f8bbf825e..17776da020 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts @@ -27,7 +27,7 @@ import { DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS, EDR_NETWORK_DEFAULT_COINBASE, } from "./edr/edr-provider.js"; -import { HardforkName } from "./edr/types/hardfork.js"; +import { HardforkName, LATEST_HARDFORK } from "./edr/types/hardfork.js"; import { isHdAccountsUserConfig } from "./type-validation.js"; export function resolveGasConfig(value: GasUserConfig = "auto"): GasConfig { @@ -282,7 +282,7 @@ export function resolveHardfork( } if (enableTransientStorage === true) { - return HardforkName.CANCUN; + return LATEST_HARDFORK; } else { return HardforkName.SHANGHAI; } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/types/hardfork.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/types/hardfork.ts index d06d2e8877..b87d884ee0 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/types/hardfork.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/types/hardfork.ts @@ -17,3 +17,22 @@ export enum HardforkName { SHANGHAI = "shanghai", CANCUN = "cancun", } + +const HARDFORK_ORDER = Object.values(HardforkName); + +export const LATEST_HARDFORK: HardforkName = + HARDFORK_ORDER[HARDFORK_ORDER.length - 1]; + +/** + * Check if `hardforkA` is greater than or equal to `hardforkB`, + * that is, if it includes all its changes. + */ +export function hardforkGte( + hardforkA: HardforkName, + hardforkB: HardforkName, +): boolean { + const indexA = HARDFORK_ORDER.lastIndexOf(hardforkA); + const indexB = HARDFORK_ORDER.lastIndexOf(hardforkB); + + return indexA >= indexB; +} diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index 25b1e19cb7..cbe3392250 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -21,7 +21,11 @@ import { } from "@ignored/hardhat-vnext-zod-utils"; import { z } from "zod"; -import { HardforkName } from "./edr/types/hardfork.js"; +import { + hardforkGte, + HardforkName, + LATEST_HARDFORK, +} from "./edr/types/hardfork.js"; const nonnegativeNumberSchema = z.number().nonnegative(); const nonnegativeIntSchema = z.number().int().nonnegative(); @@ -29,6 +33,7 @@ const nonnegativeBigIntSchema = z.bigint().nonnegative(); const blockNumberSchema = nonnegativeIntSchema; const chainIdSchema = nonnegativeIntSchema; +const hardforkNameSchema = z.nativeEnum(HardforkName); const accountsPrivateKeyUserConfigSchema = unionType( [ @@ -120,7 +125,10 @@ const edrNetworkAccountsUserConfigSchema = conditionalUnionType( `Expected an array with with objects with private key and balance or Configuration Variables, or an object with HD account details`, ); -const hardforkHistoryUserConfigSchema = z.map(z.string(), blockNumberSchema); +const hardforkHistoryUserConfigSchema = z.map( + hardforkNameSchema, + blockNumberSchema, +); const edrNetworkChainUserConfigSchema = z.object({ hardforkHistory: z.optional(hardforkHistoryUserConfigSchema), @@ -180,7 +188,7 @@ const edrNetworkUserConfigSchema = z.object({ enableRip7212: z.optional(z.boolean()), enableTransientStorage: z.optional(z.boolean()), forking: z.optional(edrNetworkForkingUserConfigSchema), - hardfork: z.optional(z.nativeEnum(HardforkName)), + hardfork: z.optional(hardforkNameSchema), initialBaseFeePerGas: z.optional(gasUnitUserConfigSchema), initialDate: z.optional( unionType([z.string(), z.instanceof(Date)], "Expected a string or a Date"), @@ -193,10 +201,64 @@ const edrNetworkUserConfigSchema = z.object({ throwOnTransactionFailures: z.optional(z.boolean()), }); -const networkUserConfigSchema = z.discriminatedUnion("type", [ - httpNetworkUserConfigSchema, - edrNetworkUserConfigSchema, -]); +const networkUserConfigSchema = z + .discriminatedUnion("type", [ + httpNetworkUserConfigSchema, + edrNetworkUserConfigSchema, + ]) + // The superRefine is used to perform additional validation of correlated + // fields of the edr network that are not possible to express with Zod's + // built-in validation methods. + // Ideally, it should be applied to the edrNetworkUserConfigSchema, but it + // returns a ZodEffects, which is not compatible with the discriminatedUnion + // method, so it is applied to the networkUserConfigSchema instead. + .superRefine((networkConfig, ctx) => { + if (networkConfig.type === "edr") { + const { + hardfork = LATEST_HARDFORK, + minGasPrice, + initialBaseFeePerGas, + enableTransientStorage, + } = networkConfig; + + if (hardforkGte(hardfork, HardforkName.LONDON)) { + if (minGasPrice !== undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "minGasPrice is not valid for networks with EIP-1559. Try an older hardfork or remove it.", + }); + } + } else { + if (initialBaseFeePerGas !== undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "initialBaseFeePerGas is only valid for networks with EIP-1559. Try a newer hardfork or remove it.", + }); + } + } + + if ( + !hardforkGte(hardfork, HardforkName.CANCUN) && + enableTransientStorage === true + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `'enableTransientStorage' is not supported for hardforks before 'cancun'. Please use a hardfork from 'cancun' onwards to enable this feature.`, + }); + } + if ( + hardforkGte(hardfork, HardforkName.CANCUN) && + enableTransientStorage === false + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `'enableTransientStorage' must be enabled for hardforks 'cancun' or later. To disable this feature, use a hardfork before 'cancun'.`, + }); + } + } + }); const userConfigSchema = z.object({ defaultChainType: z.optional(chainTypeUserConfigSchema), @@ -276,7 +338,10 @@ const edrNetworkAccountsConfigSchema = conditionalUnionType( `Expected an array with with objects with private key and balance, or an object with HD account details`, ); -const hardforkHistoryConfigSchema = z.map(z.string(), blockNumberSchema); +const hardforkHistoryConfigSchema = z.map( + hardforkNameSchema, + blockNumberSchema, +); const edrNetworkChainConfigSchema = z.object({ hardforkHistory: hardforkHistoryConfigSchema, @@ -333,7 +398,7 @@ const edrNetworkConfigSchema = z.object({ enableRip7212: z.boolean(), enableTransientStorage: z.boolean(), forking: z.optional(edrNetworkForkingConfigSchema), - hardfork: z.nativeEnum(HardforkName), + hardfork: hardforkNameSchema, initialBaseFeePerGas: z.optional(gasUnitConfigSchema), initialDate: unionType( [z.string(), z.instanceof(Date)], From 5bc9b04680641b6cb4a95a4364a11c9505c20406 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Thu, 26 Dec 2024 13:40:05 +0000 Subject: [PATCH 10/11] feat: allow config vars in account mnemonic and passphrase fields --- .../accounts/derive-private-keys.ts | 22 +++++++++++---- .../network-manager/config-resolution.ts | 28 +++++++++++++------ .../network-manager/edr/edr-provider.ts | 14 ++++++---- .../edr/utils/convert-to-edr.ts | 10 +++---- .../request-handlers/hanlders-array.ts | 8 +++--- .../network-manager/type-extensions/config.ts | 16 +++++------ .../network-manager/type-validation.ts | 25 +++++++++++------ .../network-manager/hook-handlers/config.ts | 14 +++++----- .../network-manager/request-handlers/e2e.ts | 4 +-- 9 files changed, 88 insertions(+), 53 deletions(-) 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 7698b6aee1..b42ea51487 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,15 +1,25 @@ +import type { SensitiveString } from "../../../../types/config.js"; + import { HardhatError } from "@ignored/hardhat-vnext-errors"; import { mnemonicToSeedSync } from "ethereum-cryptography/bip39"; import { HDKey } from "ethereum-cryptography/hdkey"; const HD_PATH_REGEX = /^m(:?\/\d+'?)+\/?$/; -export const DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS = { - initialIndex: 0, - count: 20, - path: "m/44'/60'/0'/0", - passphrase: "", -}; +export interface DefaultHDAccountsConfigParams { + initialIndex: number; + count: number; + path: string; + passphrase: SensitiveString; +} + +export const DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS: DefaultHDAccountsConfigParams = + { + initialIndex: 0, + count: 20, + path: "m/44'/60'/0'/0", + passphrase: "", + }; export function derivePrivateKeys( mnemonic: string, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts index 17776da020..3cb63b84d5 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts @@ -49,9 +49,15 @@ export function resolveHttpNetworkAccounts( } if (isHdAccountsUserConfig(accounts)) { + const { passphrase: defaultPassphrase, ...defaultHdAccountRest } = + DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS; + const { mnemonic, passphrase, ...hdAccountRest } = accounts; + return { - ...DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS, - ...accounts, + ...defaultHdAccountRest, + ...hdAccountRest, + mnemonic: resolveConfigurationVariable(mnemonic), + passphrase: resolveConfigurationVariable(passphrase ?? defaultPassphrase), }; } @@ -77,13 +83,19 @@ export function resolveEdrNetworkAccounts( }); } + const { + mnemonic: defaultMnemonic, + accountsBalance: defaultAccountsBalance, + passphrase: defaultPassphrase, + ...defaultHdAccountRest + } = DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS; + const { mnemonic, passphrase, accountsBalance, ...hdAccountRest } = accounts; return { - ...DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS, - ...accounts, - accountsBalance: BigInt( - accounts.accountsBalance ?? - DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS.accountsBalance, - ), + ...defaultHdAccountRest, + ...hdAccountRest, + mnemonic: resolveConfigurationVariable(mnemonic ?? defaultMnemonic), + accountsBalance: BigInt(accountsBalance ?? defaultAccountsBalance), + passphrase: resolveConfigurationVariable(passphrase ?? defaultPassphrase), }; } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts index a8b45a6538..906eadcb25 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts @@ -1,10 +1,7 @@ import type { SolidityStackTrace } from "./stack-traces/solidity-stack-trace.js"; import type { LoggerConfig } from "./types/logger.js"; import type { TracingConfig } from "./types/node-types.js"; -import type { - EdrNetworkConfig, - EdrNetworkHDAccountsConfig, -} from "../../../../types/config.js"; +import type { EdrNetworkConfig } from "../../../../types/config.js"; import type { EthereumProvider, EthSubscription, @@ -18,6 +15,7 @@ import type { CompilerInput, CompilerOutput, } from "../../../../types/solidity/compiler-io.js"; +import type { DefaultHDAccountsConfigParams } from "../accounts/derive-private-keys.js"; import type { JsonRpcRequestWrapperFunction } from "../network-manager.js"; import type { RawTrace, @@ -81,10 +79,16 @@ const log = debug("hardhat:core:hardhat-network:provider"); export const EDR_NETWORK_DEFAULT_COINBASE = "0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e"; +interface EdrNetworkDefaultHDAccountsConfigParams + extends DefaultHDAccountsConfigParams { + mnemonic: string; + accountsBalance: bigint; +} + export const EDR_NETWORK_MNEMONIC = "test test test test test test test test test test test junk"; export const DEFAULT_EDR_NETWORK_BALANCE = 10000000000000000000000n; -export const DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS: EdrNetworkHDAccountsConfig = +export const DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS: EdrNetworkDefaultHDAccountsConfigParams = { ...DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS, mnemonic: EDR_NETWORK_MNEMONIC, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts index 7006677a3d..4cfca939ea 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts @@ -224,7 +224,7 @@ export function edrRpcDebugTraceToHardhat( export async function hardhatAccountsToEdrGenesisAccounts( accounts: EdrNetworkAccountsConfig, ): Promise { - const normalizedAccounts = normalizeEdrNetworkAccountsConfig(accounts); + const normalizedAccounts = await normalizeEdrNetworkAccountsConfig(accounts); const accountPromises = normalizedAccounts.map(async (account) => ({ secretKey: await account.privateKey.getHexString(), @@ -234,19 +234,19 @@ export async function hardhatAccountsToEdrGenesisAccounts( return Promise.all(accountPromises); } -function normalizeEdrNetworkAccountsConfig( +async function normalizeEdrNetworkAccountsConfig( accounts: EdrNetworkAccountsConfig, -): EdrNetworkAccountConfig[] { +): Promise { if (Array.isArray(accounts)) { return accounts; } return derivePrivateKeys( - accounts.mnemonic, + await accounts.mnemonic.get(), accounts.path, accounts.initialIndex, accounts.count, - accounts.passphrase, + await accounts.passphrase.get(), ).map((pk) => ({ privateKey: new FixedValueConfigurationVariable(bytesToHexString(pk)), balance: accounts.accountsBalance ?? DEFAULT_EDR_NETWORK_BALANCE, diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts index 686074dbaa..cc0da03cf4 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts @@ -6,7 +6,7 @@ import type { import { numberToHexString } from "@ignored/hardhat-vnext-utils/hex"; -import { isHdAccountsUserConfig } from "../type-validation.js"; +import { isHdAccountsConfig } from "../type-validation.js"; import { AutomaticSenderHandler } from "./handlers/accounts/automatic-sender-handler.js"; import { FixedSenderHandler } from "./handlers/accounts/fixed-sender-handler.js"; @@ -96,15 +96,15 @@ export async function createHandlersArray< requestHandlers.push( new LocalAccountsHandler(networkConnection.provider, resolvedAccounts), ); - } else if (isHdAccountsUserConfig(accounts)) { + } else if (isHdAccountsConfig(accounts)) { requestHandlers.push( new HDWalletHandler( networkConnection.provider, - accounts.mnemonic, + await accounts.mnemonic.get(), accounts.path, accounts.initialIndex, accounts.count, - accounts.passphrase, + await accounts.passphrase.get(), ), ); } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts index 42b324d5dd..bbb90b46cb 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts @@ -33,10 +33,10 @@ declare module "../../../../types/config.js" { | HttpNetworkHDAccountsUserConfig; export interface HttpNetworkHDAccountsUserConfig { - mnemonic: string; + mnemonic: SensitiveString; count?: number; initialIndex?: number; - passphrase?: string; + passphrase?: SensitiveString; path?: string; } @@ -82,11 +82,11 @@ declare module "../../../../types/config.js" { } export interface EdrNetworkHDAccountsUserConfig { - mnemonic?: string; + mnemonic?: SensitiveString; accountsBalance?: string | bigint; count?: number; initialIndex?: number; - passphrase?: string; + passphrase?: SensitiveString; path?: string; } @@ -151,10 +151,10 @@ declare module "../../../../types/config.js" { | HttpNetworkHDAccountsConfig; export interface HttpNetworkHDAccountsConfig { - mnemonic: string; + mnemonic: ResolvedConfigurationVariable; count: number; initialIndex: number; - passphrase: string; + passphrase: ResolvedConfigurationVariable; path: string; } @@ -200,11 +200,11 @@ declare module "../../../../types/config.js" { } export interface EdrNetworkHDAccountsConfig { - mnemonic: string; + mnemonic: ResolvedConfigurationVariable; accountsBalance: bigint; count: number; initialIndex: number; - passphrase: string; + passphrase: ResolvedConfigurationVariable; path: string; } diff --git a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts index cbe3392250..1aabf35534 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts @@ -1,6 +1,8 @@ import type { HardhatUserConfig, + HttpNetworkAccountsConfig, HttpNetworkAccountsUserConfig, + HttpNetworkHDAccountsConfig, HttpNetworkHDAccountsUserConfig, NetworkConfig, } from "../../../types/config.js"; @@ -15,6 +17,7 @@ import { conditionalUnionType, configurationVariableSchema, resolvedConfigurationVariableSchema, + sensitiveStringSchema, sensitiveUrlSchema, unionType, validateUserConfigZodType, @@ -48,10 +51,10 @@ const accountsPrivateKeyUserConfigSchema = unionType( ); const httpNetworkHDAccountsUserConfigSchema = z.object({ - mnemonic: z.string(), + mnemonic: sensitiveStringSchema, count: z.optional(nonnegativeIntSchema), initialIndex: z.optional(nonnegativeIntSchema), - passphrase: z.optional(z.string()), + passphrase: z.optional(sensitiveStringSchema), path: z.optional(z.string()), }); @@ -109,11 +112,11 @@ const edrNetworkAccountUserConfigSchema = z.object({ }); const edrNetworkHDAccountsUserConfigSchema = z.object({ - mnemonic: z.optional(z.string()), + mnemonic: z.optional(sensitiveStringSchema), accountsBalance: z.optional(accountBalanceUserConfigSchema), count: z.optional(nonnegativeIntSchema), initialIndex: z.optional(nonnegativeIntSchema), - passphrase: z.optional(z.string()), + passphrase: z.optional(sensitiveStringSchema), path: z.optional(z.string()), }); @@ -279,10 +282,10 @@ const gasConfigSchema = unionType( ); const httpNetworkHDAccountsConfigSchema = z.object({ - mnemonic: z.string(), + mnemonic: resolvedConfigurationVariableSchema, count: nonnegativeIntSchema, initialIndex: nonnegativeIntSchema, - passphrase: z.string(), + passphrase: resolvedConfigurationVariableSchema, path: z.string(), }); @@ -322,11 +325,11 @@ const edrNetworkAccountConfigSchema = z.object({ }); const edrNetworkHDAccountsConfigSchema = z.object({ - mnemonic: z.string(), + mnemonic: resolvedConfigurationVariableSchema, accountsBalance: accountBalanceConfigSchema, count: nonnegativeIntSchema, initialIndex: nonnegativeIntSchema, - passphrase: z.string(), + passphrase: resolvedConfigurationVariableSchema, path: z.string(), }); @@ -442,3 +445,9 @@ export function isHdAccountsUserConfig( ): accounts is HttpNetworkHDAccountsUserConfig { return isObject(accounts); } + +export function isHdAccountsConfig( + accounts: HttpNetworkAccountsConfig, +): accounts is HttpNetworkHDAccountsConfig { + return isObject(accounts); +} diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts index 251c14558f..7462a5571c 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts @@ -729,7 +729,7 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", + message: "Expected a string or a Configuration Variable", }, ], ); @@ -742,7 +742,7 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Expected string, received number", + message: "Expected a string or a Configuration Variable", }, ], ); @@ -798,7 +798,7 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", + message: "Expected a string or a Configuration Variable", }, ], ); @@ -811,7 +811,7 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Required", + message: "Expected a string or a Configuration Variable", }, ], ); @@ -1124,7 +1124,7 @@ describe("network-manager/hook-handlers/config", () => { [ { path: ["networks", "localhost", "accounts", "mnemonic"], - message: "Expected string, received number", + message: "Expected a string or a Configuration Variable", }, ], ); @@ -1411,11 +1411,11 @@ describe("network-manager/hook-handlers/config", () => { gasMultiplier: 1.5, gasPrice: 100n, accounts: { - mnemonic: "asd asd asd", + mnemonic: new FixedValueConfigurationVariable("asd asd asd"), initialIndex: 0, count: 20, path: "m/44'/60'/0'/0", - passphrase: "passphrase", + passphrase: new FixedValueConfigurationVariable("passphrase"), }, url: new FixedValueConfigurationVariable( "http://node.myNetwork.com", diff --git a/v-next/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/e2e.ts b/v-next/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/e2e.ts index d401462fe4..10300c3963 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/e2e.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/e2e.ts @@ -1,4 +1,4 @@ -import type { HttpNetworkHDAccountsConfig } from "../../../../../src/types/config.js"; +import type { HttpNetworkHDAccountsUserConfig } from "../../../../../src/types/config.js"; import assert from "node:assert/strict"; import { describe, it } from "node:test"; @@ -196,7 +196,7 @@ describe("request-handlers - e2e", () => { const MOCKED_GAS_LIMIT = 21000; const GAS_MULTIPLIER = 1.337; const LATEST_BASE_FEE_IN_MOCKED_PROVIDER = 80; - const HD_ACCOUNT: HttpNetworkHDAccountsConfig = { + const HD_ACCOUNT: HttpNetworkHDAccountsUserConfig = { mnemonic: "couch hunt wisdom giant regret supreme issue sing enroll ankle type husband", path: "m/44'/60'/0'/0/", From 4768d7d13d0745d9ba0229508e197d23c6f15041 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Thu, 26 Dec 2024 14:28:54 +0000 Subject: [PATCH 11/11] fix: resolve fork config in node task --- .../hardhat/src/internal/builtin-plugins/node/task-action.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v-next/hardhat/src/internal/builtin-plugins/node/task-action.ts b/v-next/hardhat/src/internal/builtin-plugins/node/task-action.ts index edd81f4170..1d20955fe4 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/node/task-action.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/node/task-action.ts @@ -5,6 +5,7 @@ import { HardhatError } from "@ignored/hardhat-vnext-errors"; import { exists } from "@ignored/hardhat-vnext-utils/fs"; import chalk from "chalk"; +import { resolveConfigurationVariable } from "../../core/configuration-variables.js"; import { resolveForkingConfig } from "../network-manager/config-resolution.js"; import { JsonRpcServerImplementation } from "./json-rpc/server.js"; @@ -79,6 +80,8 @@ const nodeAction: NewTaskActionFunction = async ( : undefined), }, hre.config.paths.cache, + (strOrConfigVar) => + resolveConfigurationVariable(hre.hooks, strOrConfigVar), ); } else if (args.forkBlockNumber !== -1) { // NOTE: We could make the error more specific here.