@nomicfoundation/hardhat-viem
Version:
Hardhat plugin for viem
240 lines (228 loc) • 9.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const promises_1 = require("fs/promises");
const config_1 = require("hardhat/config");
const task_names_1 = require("hardhat/builtin-tasks/task-names");
const contract_names_1 = require("hardhat/utils/contract-names");
const fs_utils_1 = require("hardhat/internal/util/fs-utils");
const source_names_1 = require("hardhat/utils/source-names");
/**
* Override task that generates an `artifacts.d.ts` file with `never`
* types for duplicate contract names. This file is used in conjunction with
* the `artifacts.d.ts` file inside each contract directory to type
* `hre.artifacts`.
*/
(0, config_1.subtask)(task_names_1.TASK_COMPILE_SOLIDITY).setAction(async (_, { config, artifacts }, runSuper) => {
const superRes = await runSuper();
const duplicateContractNames = await findDuplicateContractNames(artifacts);
const duplicateArtifactsDTs = generateDuplicateArtifactsDefinition(duplicateContractNames);
try {
await (0, promises_1.writeFile)((0, path_1.join)(config.paths.artifacts, "artifacts.d.ts"), duplicateArtifactsDTs);
}
catch (error) {
console.error("Error writing artifacts definition:", error);
}
return superRes;
});
/**
* Override task to emit TypeScript and definition files for each contract.
* Generates a `.d.ts` file per contract, and a `artifacts.d.ts` per solidity
* file, which is used in conjunction to the root `artifacts.d.ts`
* to type `hre.artifacts`.
*/
(0, config_1.subtask)(task_names_1.TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(async (_, { artifacts, config }, runSuper) => {
const { artifactsEmittedPerFile } = await runSuper();
const duplicateContractNames = await findDuplicateContractNames(artifacts);
await Promise.all(artifactsEmittedPerFile.map(async ({ file, artifactsEmitted }) => {
const srcDir = (0, path_1.join)(config.paths.artifacts, file.sourceName);
await (0, promises_1.mkdir)(srcDir, {
recursive: true,
});
const contractTypeData = await Promise.all(artifactsEmitted.map(async (contractName) => {
const fqn = (0, contract_names_1.getFullyQualifiedName)(file.sourceName, contractName);
const artifact = await artifacts.readArtifact(fqn);
const isDuplicate = duplicateContractNames.has(contractName);
const declaration = generateContractDeclaration(artifact, isDuplicate);
const typeName = `${contractName}$Type`;
return { contractName, fqn, typeName, declaration };
}));
const fp = [];
for (const { contractName, declaration } of contractTypeData) {
fp.push((0, promises_1.writeFile)((0, path_1.join)(srcDir, `${contractName}.d.ts`), declaration));
}
const dTs = generateArtifactsDefinition(contractTypeData);
fp.push((0, promises_1.writeFile)((0, path_1.join)(srcDir, "artifacts.d.ts"), dTs));
try {
await Promise.all(fp);
}
catch (error) {
console.error("Error writing artifacts definition:", error);
}
}));
return { artifactsEmittedPerFile };
});
/**
* Override task for cleaning up outdated artifacts.
* Deletes directories with stale `artifacts.d.ts` files that no longer have
* a matching `.sol` file.
*/
(0, config_1.subtask)(task_names_1.TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS).setAction(async (_, { config, artifacts }, runSuper) => {
const superRes = await runSuper();
const fqns = await artifacts.getAllFullyQualifiedNames();
const existingSourceNames = new Set(fqns.map((fqn) => (0, contract_names_1.parseFullyQualifiedName)(fqn).sourceName));
const allArtifactsDTs = await (0, fs_utils_1.getAllFilesMatching)(config.paths.artifacts, (f) => f.endsWith("artifacts.d.ts"));
for (const artifactDTs of allArtifactsDTs) {
const dir = (0, path_1.dirname)(artifactDTs);
const sourceName = (0, source_names_1.replaceBackslashes)((0, path_1.relative)(config.paths.artifacts, dir));
// If sourceName is empty, it means that the artifacts.d.ts file is in the
// root of the artifacts directory, and we shouldn't delete it.
if (sourceName === "") {
continue;
}
if (!existingSourceNames.has(sourceName)) {
await (0, promises_1.rm)(dir, { force: true, recursive: true });
}
}
return superRes;
});
const AUTOGENERATED_FILE_PREFACE = `// This file was autogenerated by hardhat-viem, do not edit it.
// prettier-ignore
// tslint:disable
// eslint-disable`;
/**
* Generates TypeScript code that extends the `ArtifactsMap` with `never` types
* for duplicate contract names.
*/
function generateDuplicateArtifactsDefinition(duplicateContractNames) {
return `${AUTOGENERATED_FILE_PREFACE}
import "hardhat/types/artifacts";
declare module "hardhat/types/artifacts" {
interface ArtifactsMap {
${Array.from(duplicateContractNames)
.map((name) => `${name}: never;`)
.join("\n ")}
}
interface ContractTypesMap {
${Array.from(duplicateContractNames)
.map((name) => `${name}: never;`)
.join("\n ")}
}
}
`;
}
/**
* Generates TypeScript code to declare a contract and its associated
* TypeScript types.
*/
function generateContractDeclaration(artifact, isDuplicate) {
const { contractName, sourceName } = artifact;
const fqn = (0, contract_names_1.getFullyQualifiedName)(sourceName, contractName);
const validNames = isDuplicate ? [fqn] : [contractName, fqn];
const json = JSON.stringify(artifact, undefined, 2);
const contractTypeName = `${contractName}$Type`;
const constructorAbi = artifact.abi.find(({ type }) => type === "constructor");
const inputs = constructorAbi !== undefined ? constructorAbi.inputs : [];
const constructorArgs = inputs.length > 0
? `constructorArgs: [${inputs
.map(({ name, type }) => getArgType(name, type))
.join(", ")}]`
: `constructorArgs?: []`;
return `${AUTOGENERATED_FILE_PREFACE}
import type { Address } from "viem";
${inputs.length > 0
? `import type { AbiParameterToPrimitiveType, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";`
: `import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";`}
import "@nomicfoundation/hardhat-viem/types";
export interface ${contractTypeName} ${json}
declare module "@nomicfoundation/hardhat-viem/types" {
${validNames
.map((name) => `export function deployContract(
contractName: "${name}",
${constructorArgs},
config?: DeployContractConfig
): Promise<GetContractReturnType<${contractTypeName}["abi"]>>;`)
.join("\n ")}
${validNames
.map((name) => `export function sendDeploymentTransaction(
contractName: "${name}",
${constructorArgs},
config?: SendDeploymentTransactionConfig
): Promise<{
contract: GetContractReturnType<${contractTypeName}["abi"]>;
deploymentTransaction: GetTransactionReturnType;
}>;`)
.join("\n ")}
${validNames
.map((name) => `export function getContractAt(
contractName: "${name}",
address: Address,
config?: GetContractAtConfig
): Promise<GetContractReturnType<${contractTypeName}["abi"]>>;`)
.join("\n ")}
}
`;
}
/**
* Generates TypeScript code to extend the `ArtifactsMap` interface with
* contract types.
*/
function generateArtifactsDefinition(contractTypeData) {
return `${AUTOGENERATED_FILE_PREFACE}
import "hardhat/types/artifacts";
import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";
${contractTypeData
.map((ctd) => `import { ${ctd.typeName} } from "./${ctd.contractName}";`)
.join("\n")}
declare module "hardhat/types/artifacts" {
interface ArtifactsMap {
${contractTypeData
.map((ctd) => `["${ctd.contractName}"]: ${ctd.typeName};`)
.join("\n ")}
${contractTypeData
.map((ctd) => `["${ctd.fqn}"]: ${ctd.typeName};`)
.join("\n ")}
}
interface ContractTypesMap {
${contractTypeData
.map((ctd) => `["${ctd.contractName}"]: GetContractReturnType<${ctd.typeName}["abi"]>;`)
.join("\n ")}
${contractTypeData
.map((ctd) => `["${ctd.fqn}"]: GetContractReturnType<${ctd.typeName}["abi"]>;`)
.join("\n ")}
}
}
`;
}
/**
* Returns the type of a function argument in one of the following formats:
* - If the 'name' is provided:
* "name: AbiParameterToPrimitiveType<{ name: string; type: string; }>"
*
* - If the 'name' is empty:
* "AbiParameterToPrimitiveType<{ name: string; type: string; }>"
*/
function getArgType(name, type) {
const argType = `AbiParameterToPrimitiveType<${JSON.stringify({
name,
type,
})}>`;
return name !== "" && name !== undefined ? `${name}: ${argType}` : argType;
}
/**
* Returns a set of duplicate contract names.
*/
async function findDuplicateContractNames(artifacts) {
const fqns = await artifacts.getAllFullyQualifiedNames();
const contractNames = fqns.map((fqn) => (0, contract_names_1.parseFullyQualifiedName)(fqn).contractName);
const duplicates = new Set();
const existing = new Set();
for (const name of contractNames) {
if (existing.has(name)) {
duplicates.add(name);
}
existing.add(name);
}
return duplicates;
}
//# sourceMappingURL=tasks.js.map