UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

229 lines • 10.1 kB
import path from "node:path"; import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; import { isObject } from "@nomicfoundation/hardhat-utils/lang"; import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path"; import { conditionalUnionType, incompatibleFieldType, sensitiveStringSchema, sensitiveUrlSchema, unionType, validateUserConfigZodType, } from "@nomicfoundation/hardhat-zod-utils"; import { z } from "zod"; import { DEFAULT_TEST_PROFILE, RESERVED_TEST_PROFILE_NAMES, TEST_PROFILE_NAME_PATTERN, } from "./test-profiles.js"; // the keccak256 of "built for ethereum" export const DEFAULT_FUZZ_SEED = "0x7727ea51af0441c20da14dcd68a15dac8c9ebd589c5be8fa8c87c1d3720450bc"; const nonnegativeSafeIntOrBigInt = unionType([z.number().int().nonnegative().safe(), z.bigint().nonnegative()], "Expected a nonnegative safe int or a nonnegative bigint"); const nonnegativeSafeIntOrSafeBigInt = unionType([ z.number().int().nonnegative().safe(), z .bigint() .nonnegative() .refine((value) => value <= BigInt(Number.MAX_SAFE_INTEGER)), ], "Expected a nonnegative safe int or a nonnegative safe bigint"); const solidityTestProfileUserConfigType = z.object({ fsPermissions: z .object({ readWriteFile: z.array(z.string()).optional(), readFile: z.array(z.string()).optional(), writeFile: z.array(z.string()).optional(), dangerouslyReadWriteDirectory: z.array(z.string()).optional(), readDirectory: z.array(z.string()).optional(), dangerouslyWriteDirectory: z.array(z.string()).optional(), }) .optional(), isolate: z.boolean().optional(), ffi: z.boolean().optional(), allowInternalExpectRevert: z.boolean().optional(), from: z.string().startsWith("0x").optional(), txOrigin: z.string().startsWith("0x").optional(), initialBalance: z.bigint().optional(), blockBaseFeePerGas: z.bigint().optional(), coinbase: z.string().startsWith("0x").optional(), blockTimestamp: z.bigint().optional(), prevRandao: z.bigint().optional(), gasLimit: z.bigint().optional(), memoryLimit: nonnegativeSafeIntOrBigInt.optional(), blockGasLimit: z.number().or(z.bigint()).or(z.literal(false)).optional(), transactionGasCap: z.number().or(z.bigint()).or(z.literal(false)).optional(), fuzz: z .object({ failurePersistDir: z.string().optional(), failurePersistFile: z.string().optional(), runs: z.number().optional(), maxTestRejects: z.number().optional(), seed: z.string().optional(), dictionaryWeight: z.number().optional(), includeStorage: z.boolean().optional(), includePushBytes: z.boolean().optional(), showLogs: z.boolean().optional(), timeout: nonnegativeSafeIntOrSafeBigInt.optional(), }) .optional(), forking: z .object({ url: z.optional(sensitiveUrlSchema), blockNumber: z.optional(unionType([z.number().int().nonnegative().safe(), z.bigint().nonnegative()], "Expected a nonnegative safe int or a nonnegative bigint")), rpcEndpoints: z.record(sensitiveStringSchema).optional(), }) .optional(), invariant: z .object({ failurePersistDir: z.string().optional(), runs: z.number().optional(), depth: z.number().optional(), failOnRevert: z.boolean().optional(), callOverride: z.boolean().optional(), dictionaryWeight: z.number().optional(), includeStorage: z.boolean().optional(), includePushBytes: z.boolean().optional(), shrinkRunLimit: z.number().optional(), timeout: nonnegativeSafeIntOrSafeBigInt.optional(), }) .optional(), eip712Types: z .object({ include: z.array(z.string()).optional(), exclude: z.array(z.string()).optional(), }) .optional(), }); const solidityTestFlatUserConfigType = solidityTestProfileUserConfigType.extend({ profiles: incompatibleFieldType("This field is incompatible with the flat solidity test config"), }); const solidityTestProfilesUserConfigType = z.object({ profiles: z .record(z.string(), solidityTestProfileUserConfigType) .refine((profiles) => DEFAULT_TEST_PROFILE in profiles, "The 'default' profile is required when using Solidity test profiles") .superRefine((profiles, ctx) => { for (const profileName of Object.keys(profiles)) { if (!TEST_PROFILE_NAME_PATTERN.test(profileName)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: [profileName], message: "Invalid profile name. Profile names must start with a letter or a number, and can only contain letters, numbers, underscores and dashes", }); continue; } if (RESERVED_TEST_PROFILE_NAMES.includes(profileName)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: [profileName], message: `\`${profileName}\` can't be used as a profile name, as it's an inline test configuration key`, }); } } }), }); const solidityTestUserConfigType = conditionalUnionType([ [ (data) => isObject(data) && "profiles" in data && Object.keys(data).length === 1, solidityTestProfilesUserConfigType, ], [isObject, solidityTestFlatUserConfigType], ], "Expected a Solidity test config or a `{ profiles: { ... } }` wrapper"); const userConfigType = z.object({ paths: z .object({ test: conditionalUnionType([ [isObject, z.object({ solidity: z.string().optional() })], [(data) => typeof data === "string", z.string()], ], "Expected a string or an object with an optional 'solidity' property").optional(), }) .optional(), test: z .object({ solidity: solidityTestUserConfigType.optional(), }) .optional(), }); export function resolveSolidityTestForkingConfig(forkingUserConfig, resolveConfigurationVariable) { if (forkingUserConfig === undefined) { return undefined; } const resolvedRpcEndpoints = {}; if (forkingUserConfig.rpcEndpoints !== undefined) { for (const [name, url] of Object.entries(forkingUserConfig.rpcEndpoints)) { resolvedRpcEndpoints[name] = resolveConfigurationVariable(url); } } return { ...forkingUserConfig, blockNumber: forkingUserConfig.blockNumber !== undefined ? BigInt(forkingUserConfig.blockNumber) : undefined, url: forkingUserConfig.url !== undefined ? resolveConfigurationVariable(forkingUserConfig.url) : undefined, rpcEndpoints: resolvedRpcEndpoints, }; } export function validateSolidityTestUserConfig(userConfig) { return validateUserConfigZodType(userConfig, userConfigType); } export async function resolveSolidityTestUserConfig(userConfig, resolvedConfig, resolveConfigurationVariable) { let testsPath = userConfig.paths?.tests; // TODO: use isObject when the type narrowing issue is fixed testsPath = typeof testsPath === "object" ? testsPath.solidity : testsPath; testsPath ??= "test"; const rpcCachePath = path.join(resolvedConfig.paths.cache, "edr"); const solidityUserConfig = userConfig.test?.solidity; // The flat config shape is sugar for a single `default` profile. let profilesUserConfig; if (solidityUserConfig !== undefined && "profiles" in solidityUserConfig) { profilesUserConfig = solidityUserConfig.profiles; } else { profilesUserConfig = { [DEFAULT_TEST_PROFILE]: solidityUserConfig ?? {} }; } assertHardhatInvariant(DEFAULT_TEST_PROFILE in profilesUserConfig, "default profile must be present when the profiles wrapper user config is supplied"); const profiles = {}; for (const [profileName, profileUserConfig] of Object.entries(profilesUserConfig)) { profiles[profileName] = resolveSolidityTestProfileUserConfig(profileUserConfig, rpcCachePath, resolveConfigurationVariable); } return { ...resolvedConfig, paths: { ...resolvedConfig.paths, tests: { ...resolvedConfig.paths.tests, solidity: resolveFromRoot(resolvedConfig.paths.root, testsPath), }, }, test: { ...resolvedConfig.test, solidity: { profiles }, }, }; } function resolveSolidityTestProfileUserConfig(profileUserConfig, rpcCachePath, resolveConfigurationVariable) { const { memoryLimit, fuzz, invariant, forking, eip712Types, ...otherProfileUserConfig } = profileUserConfig; return { rpcCachePath, ...otherProfileUserConfig, memoryLimit: memoryLimit !== undefined ? BigInt(memoryLimit) : undefined, fuzz: resolveFuzzConfig(fuzz), invariant: resolveInvariantConfig(invariant), forking: resolveSolidityTestForkingConfig(forking, resolveConfigurationVariable), eip712Types: resolveEip712TypesConfig(eip712Types), }; } export function resolveFuzzConfig(fuzzUserConfig = {}) { const { seed, timeout, ...otherFuzzUserConfig } = fuzzUserConfig; return { ...otherFuzzUserConfig, seed: seed ?? DEFAULT_FUZZ_SEED, ...(timeout !== undefined ? { timeout: Number(timeout) } : {}), }; } export function resolveInvariantConfig(invariantUserConfig) { if (invariantUserConfig === undefined) { return undefined; } const { timeout, ...otherInvariantUserConfig } = invariantUserConfig; return { ...otherInvariantUserConfig, ...(timeout !== undefined ? { timeout: Number(timeout) } : {}), }; } export function resolveEip712TypesConfig(eip712TypesUserConfig = {}) { return { include: eip712TypesUserConfig.include ?? [], exclude: eip712TypesUserConfig.exclude ?? [], }; } //# sourceMappingURL=config.js.map