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
JavaScript
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