UNPKG

@nomicfoundation/hardhat-viem

Version:
240 lines (228 loc) 9.53 kB
"use strict"; 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