hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
136 lines • 7.71 kB
JavaScript
import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors";
import { bytesToUtf8String } from "@nomicfoundation/hardhat-utils/bytes";
import { getFullyQualifiedName } from "../../../../utils/contract-names.js";
import { buildInfoContainsInlineConfig, resolveFunctionSelector, buildConfigOverride, getFunctionFqn, } from "./helpers.js";
import { extractInlineConfigFromAst } from "./parsing.js";
import { validateInlineOverrides } from "./validation.js";
export { buildInfoContainsInlineConfig, resolveFunctionSelector, buildConfigOverride, getFunctionFqn, } from "./helpers.js";
export { extractInlineConfigFromAst, extractDocText, parseInlineConfigLine, } from "./parsing.js";
export { validateInlineOverrides } from "./validation.js";
/**
* Extracts per-test inline configuration overrides from the NatSpec comments
* in the solc AST. It only extracts them from the build info where each
* test artifact's file was compiled as a root file.
*/
export function getTestFunctionOverrides(testSuiteArtifacts, buildInfosAndOutputs) {
const allRawOverrides = collectRawOverrides(testSuiteArtifacts, buildInfosAndOutputs);
validateInlineOverrides(allRawOverrides.overrides);
return buildTestFunctionOverrides(allRawOverrides);
}
function collectRawOverrides(testSuiteArtifacts, buildInfosAndOutputs) {
const overrides = [];
const methodIdentifiersByContract = new Map();
// Note: We group the artifacts by their build info, so that we only process
// the relevant build infos, and only the root files of each of them.
//
// The last part is important, as a test file can be present in multiple build
// infos in the presence of partial recompilations
//
// At the same time, the same root file could have produced multiple test
// artifacts, so we need a two-level map to avoid processing the root files
// multiple times.
// Build lookup structures for fast access
const artifactsGroupedByBuildInfo = new Map();
const artifactIdsByFqn = new Map();
const buildInfoAndOutputById = new Map(buildInfosAndOutputs.map((bio) => [bio.buildInfoId, bio]));
for (const edrArtifactWithMetadata of testSuiteArtifacts) {
const fqn = getFullyQualifiedName(edrArtifactWithMetadata.edrArtifact.id.source, edrArtifactWithMetadata.edrArtifact.id.name);
const buildInfoId = edrArtifactWithMetadata.buildInfoId;
let artifactsBySource = artifactsGroupedByBuildInfo.get(buildInfoId);
if (artifactsBySource === undefined) {
artifactsBySource = new Map();
artifactsGroupedByBuildInfo.set(buildInfoId, artifactsBySource);
}
let artifacts = artifactsBySource.get(edrArtifactWithMetadata.edrArtifact.id.source);
if (artifacts === undefined) {
artifacts = [];
artifactsBySource.set(edrArtifactWithMetadata.edrArtifact.id.source, artifacts);
}
artifacts.push(edrArtifactWithMetadata);
artifactIdsByFqn.set(fqn, edrArtifactWithMetadata.edrArtifact.id);
}
for (const [buildInfoId, artifactsBySource,] of artifactsGroupedByBuildInfo.entries()) {
const buildInfoAndOutput = buildInfoAndOutputById.get(buildInfoId);
if (buildInfoAndOutput === undefined) {
// We can throw for this error for the first artifact with this build info
// as all of them have the same problem.
const artifacts = artifactsBySource.values().next().value;
assertHardhatInvariant(artifacts !== undefined && artifacts.length > 0, "An artifact must be present for the build info");
const anyArtifact = artifacts[0];
const fqn = getFullyQualifiedName(anyArtifact.userSourceName, anyArtifact.edrArtifact.id.name);
throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY_TESTS.BUILD_INFO_NOT_FOUND_FOR_CONTRACT, {
fqn,
});
}
if (!buildInfoContainsInlineConfig(buildInfoAndOutput.buildInfo)) {
continue;
}
const buildInfoOutput = JSON.parse(bytesToUtf8String(buildInfoAndOutput.output));
for (const [inputSourceName, sourceArtifacts,] of artifactsBySource.entries()) {
const contractNames = new Set(sourceArtifacts.map((a) => a.edrArtifact.id.name));
const source = buildInfoOutput.output.sources[inputSourceName];
const extracted = extractInlineConfigFromAst(source.ast, inputSourceName, contractNames);
overrides.push(...extracted);
for (const artifact of sourceArtifacts) {
const contractName = artifact.edrArtifact.id.name;
const fqn = getFullyQualifiedName(inputSourceName, contractName);
const methodIdentifiers = buildInfoOutput.output.contracts?.[inputSourceName][contractName]?.evm
?.methodIdentifiers;
methodIdentifiersByContract.set(fqn, methodIdentifiers ?? {});
}
}
}
return { overrides, artifactIdsByFqn, methodIdentifiersByContract };
}
function buildTestFunctionOverrides(collected) {
const { overrides, artifactIdsByFqn, methodIdentifiersByContract } = collected;
// Group overrides by function. When the AST provides a functionSelector
// (public/external functions in solc >= 0.6.0), use it to distinguish
// overloaded functions. Otherwise fall back to function name only.
const overridesByFunction = new Map();
for (const override of overrides) {
const functionFqn = getFunctionFqn(override.inputSourceName, override.contractName, override.functionName);
const groupKey = override.functionSelector !== undefined
? `${functionFqn}#${override.functionSelector}`
: functionFqn;
const existing = overridesByFunction.get(groupKey);
if (existing === undefined) {
overridesByFunction.set(groupKey, [override]);
}
else {
existing.push(override);
}
}
// Build TestFunctionOverride objects
const testFunctionOverrides = [];
for (const [_groupKey, groupOverrides] of overridesByFunction.entries()) {
const firstOverride = groupOverrides[0];
const functionFqn = getFunctionFqn(firstOverride.inputSourceName, firstOverride.contractName, firstOverride.functionName);
const contractFqn = `${firstOverride.inputSourceName}:${firstOverride.contractName}`;
const artifactId = artifactIdsByFqn.get(contractFqn);
assertHardhatInvariant(artifactId !== undefined, `Missing artifact id for "${contractFqn}"`);
// Use the AST-provided selector when available, otherwise fall back to
// resolving via methodIdentifiers.
let selector;
if (firstOverride.functionSelector !== undefined) {
selector = `0x${firstOverride.functionSelector}`;
}
else {
const methodIdentifiers = methodIdentifiersByContract.get(contractFqn);
assertHardhatInvariant(methodIdentifiers !== undefined, `Missing method identifiers for "${contractFqn}"`);
selector = resolveFunctionSelector(methodIdentifiers, firstOverride.functionName);
}
if (selector === undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY_TESTS.INLINE_CONFIG_UNRESOLVED_SELECTOR, { functionFqn });
}
testFunctionOverrides.push({
identifier: {
contractArtifact: artifactId,
functionSelector: selector,
},
config: buildConfigOverride(groupOverrides),
});
}
return testFunctionOverrides;
}
//# sourceMappingURL=index.js.map