hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
171 lines • 7.91 kB
JavaScript
import { finished } from "node:stream/promises";
import { styleText } from "node:util";
import { opGenesisState, l1GenesisState, FsAccessPermission, CollectStackTraces, opHardforkFromString, l1HardforkFromString, } from "@nomicfoundation/edr";
import { toBigInt } from "@nomicfoundation/hardhat-utils/bigint";
import { hexStringToBytes } from "@nomicfoundation/hardhat-utils/hex";
import { ALWAYS_COLLECT_STACK_TRACES_VERBOSITY, OPTIMISM_CHAIN_TYPE, } from "../../constants.js";
import { resolveHardfork } from "../network-manager/config-resolution.js";
import { hardhatHardforkToEdrSpecId } from "../network-manager/edr/utils/convert-to-edr.js";
import { warnIfExperimentalHardfork } from "../network-manager/edr/utils/hardfork.js";
import { verbosityToIncludeTraces } from "../network-manager/edr/utils/trace-formatters.js";
import { formatArtifactId } from "./formatters.js";
export async function solidityTestConfigToSolidityTestRunnerConfigArgs({ chainType, projectRoot, hardfork, config, verbosity, observability, testPattern, excludeTestPattern, generateGasReport, eip712CanonicalTypes, testSourcePaths, }) {
const fsPermissions = [
config.fsPermissions?.readWriteFile?.map((p) => ({
access: FsAccessPermission.ReadWriteFile,
path: p,
})) ?? [],
config.fsPermissions?.readFile?.map((p) => ({
access: FsAccessPermission.ReadFile,
path: p,
})) ?? [],
config.fsPermissions?.writeFile?.map((p) => ({
access: FsAccessPermission.WriteFile,
path: p,
})) ?? [],
config.fsPermissions?.dangerouslyReadWriteDirectory?.map((p) => ({
access: FsAccessPermission.DangerouslyReadWriteDirectory,
path: p,
})) ?? [],
config.fsPermissions?.readDirectory?.map((p) => ({
access: FsAccessPermission.ReadDirectory,
path: p,
})) ?? [],
config.fsPermissions?.dangerouslyWriteDirectory?.map((p) => ({
access: FsAccessPermission.DangerouslyWriteDirectory,
path: p,
})) ?? [],
].flat(1);
const hexToBytes = (hex) => hex !== undefined ? hexStringToBytes(hex) : undefined;
const sender = hexToBytes(config.from);
const txOrigin = hexToBytes(config.txOrigin);
const blockCoinbase = hexToBytes(config.coinbase);
const resolvedHardforkName = resolveHardfork(hardfork, chainType);
warnIfExperimentalHardfork(resolvedHardforkName, chainType);
const resolvedHardfork = hardhatHardforkToEdrSpecId(resolvedHardforkName, chainType);
const localPredeploys = chainType === OPTIMISM_CHAIN_TYPE
? opGenesisState(opHardforkFromString(resolvedHardfork))
: l1GenesisState(l1HardforkFromString(resolvedHardfork));
const includeTraces = verbosityToIncludeTraces(verbosity);
const blockGasLimit = typeof config.blockGasLimit === "number" ||
typeof config.blockGasLimit === "bigint"
? toBigInt(config.blockGasLimit)
: undefined;
const disableBlockGasLimit = blockGasLimit === undefined;
const transactionGasCap = typeof config.transactionGasCap === "number" ||
typeof config.transactionGasCap === "bigint"
? toBigInt(config.transactionGasCap)
: undefined;
const disableTransactionGasCap = transactionGasCap === undefined;
const blockDifficulty = config.prevRandao;
let ethRpcUrl;
if (config.forking?.url !== undefined) {
ethRpcUrl = await config.forking.url.get();
}
const forkBlockNumber = config.forking?.blockNumber;
let rpcEndpoints;
if (config.forking?.rpcEndpoints !== undefined) {
rpcEndpoints = {};
for (const [name, configValue] of Object.entries(config.forking.rpcEndpoints)) {
rpcEndpoints[name] = await configValue.get();
}
}
const shouldAlwaysCollectStackTraces = verbosity >= ALWAYS_COLLECT_STACK_TRACES_VERBOSITY;
return {
projectRoot,
hardfork: resolvedHardfork,
...config,
fsPermissions,
localPredeploys,
sender,
txOrigin,
blockCoinbase,
observability,
testPattern: testPattern === "" ? undefined : testPattern,
excludeTestPattern: excludeTestPattern === "" ? undefined : excludeTestPattern,
includeTraces,
blockGasLimit,
disableBlockGasLimit,
transactionGasCap,
disableTransactionGasCap,
blockDifficulty,
ethRpcUrl,
forkBlockNumber,
rpcEndpoints,
generateGasReport,
collectStackTraces: shouldAlwaysCollectStackTraces
? CollectStackTraces.Always
: CollectStackTraces.OnFailure,
eip712CanonicalTypes,
testSourcePaths,
};
}
export function isTestSuiteArtifact(artifact) {
const bytecode = artifact.contract.bytecode;
// Skip abstract contracts and interfaces i.e. those with no bytecode
if (bytecode === "" || bytecode === "0x" || bytecode === undefined) {
return false;
}
const abi = JSON.parse(artifact.contract.abi);
return abi.some(({ type, name }) => {
if (type === "function" && typeof name === "string") {
return name.startsWith("test") || name.startsWith("invariant");
}
return false;
});
}
/**
* Writes the test reporter's output to `output` and waits for the test run to
* complete, returning the error that failed it, if any.
*
* @param runStream The runner's stream of test events.
* @param reporterStream The reporter's stream of output, composed from
* `runStream`.
* @param output Where the reporter's output is written to.
* @returns The error that failed the run, or `undefined` if it succeeded.
*/
export async function writeTestRunOutput(runStream, reporterStream, output) {
const outputStream = reporterStream.pipe(output);
// When the runner reports an error it destroys the run stream with it, which
// also destroys the reporter stream composed from it. `pipe()` neither
// forwards that error to the output stream nor ends it, so without this
// listener the reporter stream emits an `error` event with nothing listening,
// crashing the process, and `finished(outputStream)` below never settles.
// Attached before any `await` so that it can't miss the event.
let reporterStreamError;
reporterStream.on("error", (error) => {
reporterStreamError = error;
if (!outputStream.writableEnded) {
outputStream.end();
}
});
let runError;
try {
// NOTE: We're awaiting the original run stream to finish to catch any
// errors produced by the runner.
await finished(runStream);
// We also await the output stream to finish, as we want to wait for it
// to avoid returning before the whole output was generated.
await finished(outputStream);
}
catch (error) {
runError = error;
}
// Neither await above surfaces an error that reached the reporter stream
// without failing the run stream, e.g. one thrown by the reporter after the
// run stream already ended.
return runError ?? reporterStreamError;
}
export function warnDeprecatedTestFail(artifact, sourceNameToUserSourceName) {
const abi = JSON.parse(artifact.contract.abi);
abi.forEach(({ type, name }) => {
if (type === "function" &&
typeof name === "string" &&
name.startsWith("testFail")) {
const formattedLocation = formatArtifactId(artifact.id, sourceNameToUserSourceName);
const warningMessage = `${styleText("yellow", "Warning")}: ${name} The support for the prefix \`testFail*\` has been removed. Consider using \`vm.expectRevert()\` for testing reverts in ${formattedLocation}\n`;
console.warn(warningMessage);
}
});
}
//# sourceMappingURL=helpers.js.map