From 32ff36c347923adf311e38af179d29038b7bc039 Mon Sep 17 00:00:00 2001 From: Oleg Komendant <44612825+Hrom131@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:36:58 +0300 Subject: [PATCH] Add doc comments (#31) * Add doc comments * Add doc comments for the ReporterFacade class --- src/artifacts/CircuitArtifacts.ts | 80 +++++++++++++ src/cache/CircuitsCompileCache.ts | 89 ++++++++++++++- src/cache/CircuitsSetupCache.ts | 74 +++++++++++- src/cache/schemas/compile-schemas.ts | 6 + src/core/compile/CompilationFilesResolver.ts | 27 +++++ src/core/compile/CompilationProcessor.ts | 33 +++++- src/core/compile/TypeGenerationProcessor.ts | 22 ++++ src/core/compiler/CircomCompiler.ts | 96 ++++++++++++++++ src/core/compiler/CircomCompilerDownloader.ts | 107 ++++++++++++++++-- src/core/compiler/CircomCompilerFactory.ts | 64 ++++++++++- .../dependencies/parser/CircomFilesParser.ts | 46 +++++++- .../dependencies/parser/CircomFilesVisitor.ts | 8 ++ .../parser/CircomTemplateInputsVisitor.ts | 13 +++ src/core/setup/SetupFilesResolver.ts | 24 ++++ src/core/setup/SetupProcessor.ts | 25 ++++ src/errors.ts | 9 ++ src/index.ts | 4 + src/reporter/ReporterFacade.ts | 7 ++ src/utils/utils.ts | 30 ++--- 19 files changed, 731 insertions(+), 33 deletions(-) diff --git a/src/artifacts/CircuitArtifacts.ts b/src/artifacts/CircuitArtifacts.ts index cb2149a..dfdeaec 100644 --- a/src/artifacts/CircuitArtifacts.ts +++ b/src/artifacts/CircuitArtifacts.ts @@ -19,6 +19,13 @@ import { ICircuitArtifacts, } from "../types/artifacts/circuit-artifacts"; +/** + * A class for easily managing circuit artifacts, including retrieving existing artifacts, + * creating new ones, and removing outdated artifacts. + * + * This class simplifies the process of handling circuit-related artifacts by providing + * methods for fetching, generating, and cleaning up artifacts that are no longer needed. + */ export class CircuitArtifacts implements ICircuitArtifacts { // Undefined means that the cache is disabled. private _cache?: ArtifactsCache = { @@ -27,6 +34,13 @@ export class CircuitArtifacts implements ICircuitArtifacts { constructor(private readonly _artifactsPath: string) {} + /** + * Retrieves a {@link CircuitArtifact} object based on the provided short name or fully qualified name + * + * @param circuitNameOrFullyQualifiedName The short circuit name or the fully qualified circuit name + * (refer to: {@link getCircuitFullyQualifiedName} for more details) + * @returns A promise that resolves to the {@link CircuitArtifact} object corresponding to the provided name + */ public async readCircuitArtifact(circuitNameOrFullyQualifiedName: string): Promise { const artifactPath = await this._getArtifactPath(circuitNameOrFullyQualifiedName); const fileContent = fsExtra.readFileSync(artifactPath, "utf-8"); @@ -34,6 +48,13 @@ export class CircuitArtifacts implements ICircuitArtifacts { return JSON.parse(fileContent) as CircuitArtifact; } + /** + * Checks if a circuit artifact exists for the given short or fully qualified name + * + * @param circuitNameOrFullyQualifiedName The short circuit name or the fully qualified circuit name + * (refer to: {@link getCircuitFullyQualifiedName} for more details) + * @returns A promise that resolves to `true` if the {@link CircuitArtifact} exists, `false` otherwise + */ public async circuitArtifactExists(circuitNameOrFullyQualifiedName: string): Promise { let artifactPath; try { @@ -49,11 +70,22 @@ export class CircuitArtifacts implements ICircuitArtifacts { return fsExtra.pathExists(artifactPath); } + /** + * Retrieves an array of all fully qualified circuit names for paths discovered by the + * {@link getCircuitArtifactPaths} function + * + * @returns A promise that resolves to an array of fully qualified circuit names + */ public async getAllCircuitFullyQualifiedNames(): Promise { const paths = await this.getCircuitArtifactPaths(); return paths.map((p) => this._getFullyQualifiedNameFromPath(p)).sort(); } + /** + * Retrieves an array of all circuit artifact paths found within the artifacts directory + * + * @returns A promise that resolves to an array of circuit artifact paths + */ public async getCircuitArtifactPaths(): Promise { const cached = this._cache?.artifactPaths; if (cached !== undefined) { @@ -69,20 +101,53 @@ export class CircuitArtifacts implements ICircuitArtifacts { return paths; } + /** + * Constructs and returns the full path to the artifact file using the fully qualified circuit name + * + * @param fullyQualifiedName The fully qualified circuit name + * (refer to: {@link getCircuitFullyQualifiedName} for details) + * @returns The full path to the corresponding circuit artifact file + */ public formCircuitArtifactPathFromFullyQualifiedName(fullyQualifiedName: string): string { const { sourceName, circuitName } = this._parseCircuitFullyQualifiedName(fullyQualifiedName); return path.join(this._artifactsPath, sourceName, `${circuitName}${CIRCUIT_ARTIFACTS_SUFFIX}`); } + /** + * Constructs and returns the fully qualified circuit name based on the provided source name and circuit template name + * + * @example + * // Example usage: + * const qualifiedName = getCircuitFullyQualifiedName("exampleSource", "exampleCircuit"); + * console.log(qualifiedName); // Output: "exampleSource:exampleCircuit" + * + * @param sourceName The name of the source file for the circuit + * @param circuitName The name of the circuit template + * @returns The fully qualified circuit name in the format "sourceName:circuitName" + */ public getCircuitFullyQualifiedName(sourceName: string, circuitName: string): string { return `${sourceName}:${circuitName}`; } + /** + * Retrieves the configured full path to the artifacts directory + * + * @returns The full path to the artifacts directory as a string + */ public getCircuitArtifactsDirFullPath(): string { return this._artifactsPath; } + /** + * Constructs and returns the full path for a specific artifact file based on the provided + * {@link CircuitArtifact} object and the specified {@link ArtifactsFileType | file type} + * + * @param circuitArtifact The {@link CircuitArtifact} object representing the artifact + * @param fileType The {@link ArtifactsFileType | file type} indicating the type of artifact file + * for which to retrieve the path + * @returns The full path of the specified artifact file associated with the provided {@link CircuitArtifact} object + */ public getCircuitArtifactFileFullPath(circuitArtifact: CircuitArtifact, fileType: ArtifactsFileType): string { return path.join( this._artifactsPath, @@ -91,6 +156,14 @@ export class CircuitArtifacts implements ICircuitArtifacts { ); } + /** + * Saves the provided {@link CircuitArtifact} object to the artifacts file + * + * @param circuitArtifact The {@link CircuitArtifact} object to be saved + * @param updatedFileTypes An array of {@link ArtifactsFileType | file types} that have been modified + * during the most recent session, such as during compilation + * @returns A promise that resolves once the save operation is complete + */ public async saveCircuitArtifact(circuitArtifact: CircuitArtifact, updatedFileTypes: ArtifactsFileType[]) { const fullyQualifiedName = this.getCircuitFullyQualifiedName( circuitArtifact.circuitSourceName, @@ -99,6 +172,7 @@ export class CircuitArtifacts implements ICircuitArtifacts { const artifactPath = this.formCircuitArtifactPathFromFullyQualifiedName(fullyQualifiedName); + // Updates the data for files that have been recently modified for (const fileType of updatedFileTypes) { const fileSourcePath: string = this.getCircuitArtifactFileFullPath(circuitArtifact, fileType); @@ -114,6 +188,9 @@ export class CircuitArtifacts implements ICircuitArtifacts { await fsExtra.writeJSON(artifactPath, circuitArtifact, { spaces: 2 }); } + /** + * Clears the local cache of artifacts + */ public clearCache() { if (this._cache === undefined) { return; @@ -124,6 +201,9 @@ export class CircuitArtifacts implements ICircuitArtifacts { }; } + /** + * Disables the local cache of artifacts + */ public disableCache() { this._cache = undefined; } diff --git a/src/cache/CircuitsCompileCache.ts b/src/cache/CircuitsCompileCache.ts index 3889855..4d31adb 100644 --- a/src/cache/CircuitsCompileCache.ts +++ b/src/cache/CircuitsCompileCache.ts @@ -8,7 +8,26 @@ import { CompileCache, CompileCacheEntry } from "../types/cache"; import { CompileFlags } from "../types/core"; import { Reporter } from "../reporter"; +/** + * Class that implements the caching logic for compiling circuits. + * + * This class is responsible for managing the cache of compiled circuit information, + * optimizing the compilation process by storing previously compiled data. + * It allows for a feature that avoids recompiling circuits that have not changed + * since the last compilation, significantly reducing unnecessary compilation overhead. + * + * The class provides methods to clear, disable, and retrieve cached artifacts, + * ensuring that only up-to-date and relevant information is used during compilation. + * + * The caching mechanism enhances performance and efficiency, especially when dealing + * with large and complex circuit designs by minimizing redundant compilation efforts. + */ class BaseCircuitsCompileCache { + /** + * Creates an instance of {@link BaseCircuitsCompileCache} with empty cache data + * + * @returns An instance of {@link BaseCircuitsCompileCache} initialized with empty cache data + */ public static createEmpty(): BaseCircuitsCompileCache { return new BaseCircuitsCompileCache({ _format: CIRCUIT_COMPILE_CACHE_VERSION, @@ -16,6 +35,12 @@ class BaseCircuitsCompileCache { }); } + /** + * Creates an instance of {@link BaseCircuitsCompileCache} using the data read from the specified cache file + * + * @param circuitsCompileCachePath The full path to the compile cache file from which to read the data + * @returns A promise that resolves to an instance of {@link BaseCircuitsCompileCache} populated with the read data + */ public static async readFromFile(circuitsCompileCachePath: string): Promise { let cacheRaw: CompileCache = { _format: CIRCUIT_COMPILE_CACHE_VERSION, @@ -34,6 +59,7 @@ class BaseCircuitsCompileCache { }); } + // Validate the correctness of the data read from the file using the Zod schema const result = CompileCacheSchema.safeParse(cacheRaw); if (result.success) { @@ -53,6 +79,12 @@ class BaseCircuitsCompileCache { constructor(private _compileCache: CompileCache) {} + /** + * Removes cache entries for files that no longer exist. + * + * This method helps keep the cache up-to-date by deleting references + * to non-existent files, ensuring that the cache remains valid. + */ public async removeNonExistingFiles() { await Promise.all( Object.keys(this._compileCache.files).map(async (absolutePath) => { @@ -63,6 +95,11 @@ class BaseCircuitsCompileCache { ); } + /** + * Writes the current cache state to the specified file + * + * @param circuitsCompileCachePath The full path to the compile cache file where the cache will be saved + */ public async writeToFile(circuitsCompileCachePath: string) { fsExtra.outputFileSync( circuitsCompileCachePath, @@ -76,22 +113,57 @@ class BaseCircuitsCompileCache { ); } + /** + * Adds a file cache entry to the cache data using the specified absolute path + * + * @param absolutePath The absolute path to the circuit file + * @param entry The cache entry to be added for the specified file path + */ public addFile(absolutePath: string, entry: CompileCacheEntry) { this._compileCache.files[absolutePath] = entry; } + /** + * Returns all stored cache entries + * + * @returns An array of all stored cache entries + */ public getEntries(): CompileCacheEntry[] { return Object.values(this._compileCache.files); } + /** + * Returns the cache entry for the specified file path, or undefined if no entry exists + * + * @param file The absolute path to the circuit file + * @returns The stored cache entry or undefined if no entry is found + */ public getEntry(file: string): CompileCacheEntry | undefined { return this._compileCache.files[file]; } + /** + * Removes the cache entry for the specified file path from the cache + * + * @param file The absolute path to the circuit file + */ public removeEntry(file: string) { delete this._compileCache.files[file]; } + /** + * Checks if the specified file has changed since the last check based on its content hash and compile flags. + * + * This method compares the current state of the file, identified by its absolute path, + * with the provided content hash and compile flags. If any of these values differ from + * what was recorded previously, the method returns true, indicating that the file has + * been modified. Otherwise, it returns false. + * + * @param absolutePath The absolute path of the file to compare + * @param contentHash The hash of the file content for comparison, used to detect changes + * @param compileFlags The {@link CompileFlags | compile flags} for comparison, which may affect the file's state + * @returns True if the file has changed since the last check, false otherwise + */ public hasFileChanged(absolutePath: string, contentHash: string, compileFlags: CompileFlags): boolean { const cacheEntry = this.getEntry(absolutePath); @@ -111,8 +183,23 @@ class BaseCircuitsCompileCache { } } +/** + * Singleton object that serves as the cache for compilation information related to circuits. + * + * This cache holds the state of the compilation process and allows for efficient reuse + * of previously compiled data, thereby avoiding unnecessary recompilation of unchanged circuits. + * + * To create and properly initialize this cache object, the {@link createCircuitsCompileCache} + * function must be invoked. The cache can help improve performance and manage resources effectively + * during circuit compilation. + */ export let CircuitsCompileCache: BaseCircuitsCompileCache | null = null; +/** + * Creates a singleton instance of the {@link BaseCircuitsCompileCache} class + * + * @param circuitsCompileCachePath The full path to the compile cache file + */ export async function createCircuitsCompileCache(circuitsCompileCachePath?: string) { if (CircuitsCompileCache) { return; @@ -126,7 +213,7 @@ export async function createCircuitsCompileCache(circuitsCompileCachePath?: stri } /** - * Used only in test environments to ensure test atomicity + * @remark Used only in test environments to ensure test atomicity */ export function resetCircuitsCompileCache() { CircuitsCompileCache = null; diff --git a/src/cache/CircuitsSetupCache.ts b/src/cache/CircuitsSetupCache.ts index eca347b..c529ebe 100644 --- a/src/cache/CircuitsSetupCache.ts +++ b/src/cache/CircuitsSetupCache.ts @@ -8,6 +8,11 @@ import { SetupCache, SetupCacheEntry } from "../types/cache"; import { ContributionSettings } from "../types/core"; class BaseCircuitsSetupCache { + /** + * Creates an empty instance of the {@link BaseCircuitsSetupCache} class + * + * @returns A new instance of the {@link BaseCircuitsSetupCache} with empty cache data + */ public static createEmpty(): BaseCircuitsSetupCache { return new BaseCircuitsSetupCache({ _format: CIRCUIT_SETUP_CACHE_VERSION, @@ -15,6 +20,12 @@ class BaseCircuitsSetupCache { }); } + /** + * Creates an instance of {@link BaseCircuitsSetupCache} using the data read from the specified cache file + * + * @param circuitsSetupCachePath The full path to the setup cache file from which to read the data + * @returns A promise that resolves to an instance of {@link BaseCircuitsSetupCache} populated with the read data + */ public static async readFromFile(circuitsSetupCachePath: string): Promise { let cacheRaw: SetupCache = { _format: CIRCUIT_SETUP_CACHE_VERSION, @@ -25,6 +36,7 @@ class BaseCircuitsSetupCache { cacheRaw = await fsExtra.readJson(circuitsSetupCachePath); } + // Validate the correctness of the data read from the file using the Zod schema const result = SetupCacheSchema.safeParse(cacheRaw); if (result.success) { @@ -42,6 +54,12 @@ class BaseCircuitsSetupCache { constructor(private _setupCache: SetupCache) {} + /** + * Removes cache entries for files that no longer exist. + * + * This method helps keep the cache up-to-date by deleting references + * to non-existent files, ensuring that the cache remains valid. + */ public async removeNonExistingFiles() { await Promise.all( Object.keys(this._setupCache.files).map(async (absolutePath) => { @@ -52,28 +70,67 @@ class BaseCircuitsSetupCache { ); } + /** + * Writes the current cache state to a specified file + * + * @param circuitsCompileCachePath The full path to the cache file where the state will be written + */ public async writeToFile(circuitsCompileCachePath: string) { await fsExtra.outputJson(circuitsCompileCachePath, this._setupCache, { spaces: 2, }); } + /** + * Adds a file cache entry to the cache data using the specified absolute path + * + * @param absolutePath The absolute path to the circuit file + * @param entry The cache entry to be added for the specified file path + */ public addFile(absolutePath: string, entry: SetupCacheEntry) { this._setupCache.files[absolutePath] = entry; } + /** + * Returns all stored cache entries + * + * @returns An array of all stored cache entries + */ public getEntries(): SetupCacheEntry[] { return Object.values(this._setupCache.files); } + /** + * Returns the cache entry for the specified file path, or undefined if no entry exists + * + * @param file The absolute path to the circuit file + * @returns The stored cache entry or undefined if no entry is found + */ public getEntry(file: string): SetupCacheEntry | undefined { return this._setupCache.files[file]; } + /** + * Removes the cache entry for the specified file path from the cache + * + * @param file The absolute path to the circuit file + */ public removeEntry(file: string) { delete this._setupCache.files[file]; } + /** + * Checks if the specified file has changed since the last check. + * + * This method compares the current state of the file, identified by its absolute path, + * with the content hash and contribution settings recorded in the cache. If any of these + * values differ, it indicates the file has been modified. + * + * @param artifactAbsolutePath The absolute path of the artifact file to check + * @param r1csContentHash The content hash of the R1CS file to compare + * @param contributionSettings The contribution settings to compare + * @returns True if the file has changed, false otherwise. + */ public hasFileChanged( artifactAbsolutePath: string, r1csContentHash: string, @@ -97,8 +154,23 @@ class BaseCircuitsSetupCache { } } +/** + * Singleton object that serves as the cache for setup information related to circuits. + * + * This cache maintains the state of the setup process and allows for efficient reuse + * of previously stored setup data, thereby avoiding unnecessary reconfiguration of unchanged circuits. + * + * To create and properly initialize this cache object, the {@link createCircuitsSetupCache} + * function must be invoked. The cache helps improve performance and manage resources effectively + * during the setup of circuit configurations. + */ export let CircuitsSetupCache: BaseCircuitsSetupCache | null = null; +/** + * Creates and initializes a singleton instance of the {@link BaseCircuitsSetupCache} class + * + * @param circuitsSetupCachePath The full path to the setup cache file to load + */ export async function createCircuitsSetupCache(circuitsSetupCachePath?: string) { if (CircuitsSetupCache) { return; @@ -112,7 +184,7 @@ export async function createCircuitsSetupCache(circuitsSetupCachePath?: string) } /** - * Used only in test environments to ensure test atomicity + * @remark Used only in test environments to ensure test atomicity */ export function resetCircuitsSetupCache() { CircuitsSetupCache = null; diff --git a/src/cache/schemas/compile-schemas.ts b/src/cache/schemas/compile-schemas.ts index 92b8046..f710047 100644 --- a/src/cache/schemas/compile-schemas.ts +++ b/src/cache/schemas/compile-schemas.ts @@ -1,6 +1,12 @@ import { BigIntOrNestedArray } from "@distributedlab/circom-parser"; import { z } from "zod"; +/** + * {@link https://github.com/colinhacks/zod | Zod} schema for defining a recursive type {@link BigIntOrNestedArray}. + * + * This schema allows for either a `BigInt` value or an array that contains + * other `BigInt` values or nested arrays of `BigInt` values, recursively. + */ export const BigIntOrNestedArraySchema: z.ZodType = z.lazy(() => z.union([z.bigint(), BigIntOrNestedArraySchema.array()]), ); diff --git a/src/core/compile/CompilationFilesResolver.ts b/src/core/compile/CompilationFilesResolver.ts index b01646a..2299d13 100644 --- a/src/core/compile/CompilationFilesResolver.ts +++ b/src/core/compile/CompilationFilesResolver.ts @@ -11,6 +11,14 @@ import { ZKitConfig } from "../../types/zkit-config"; import { ICircuitArtifacts } from "../../types/artifacts/circuit-artifacts"; import { CircomResolvedFileInfo, CompileFlags } from "../../types/core"; +/** + * This class is responsible for determining the list of files and circuits that need to be compiled. + * It selects files by applying various filtering criteria and utilizes cached parameters from previous compilations + * to determine whether a specific circuit requires recompilation. + * + * By leveraging caching and filtering, this class optimizes the compilation process, ensuring only necessary + * circuits are recompiled, thus improving performance and reducing redundant work. + */ export class CompilationFilesResolver { private readonly _zkitConfig: ZKitConfig; private readonly _projectRoot: string; @@ -24,6 +32,25 @@ export class CompilationFilesResolver { this._projectRoot = hardhatConfig.paths.root; } + /** + * Returns the information about circuit files that need to be compiled based on the compilation flags + * and whether the circuit files or their dependencies have changed since the last compilation. + * + * The function follows these steps to determine the list of files to be compiled: + * 1. Finds all `.circom` files within the circuits directory specified in the configuration + * 2. Converts the discovered paths into the `sourceName` format, i.e., + * relative paths with respect to the circuits directory + * 3. Filters the paths based on the compilation settings filtering parameters provided in the configuration + * 4. Builds a {@link DependencyGraph} by resolving all dependencies using the {@link CircomFilesResolver} + * 5. Further filters {@link CircomResolvedFileInfo} objects, excluding files that do not have a main component + * or are not located within the specified circuits directory + * 6. If the `force` flag is not provided, checks whether the compilation is necessary for the identified files + * 7. Returns an array of {@link CircomResolvedFileInfo} objects representing the files that need to be compiled + * + * @param compileFlags The flags to be used during the next compilation + * @param force Whether to force recompilation + * @returns An array of {@link CircomResolvedFileInfo} objects + */ public async getResolvedFilesToCompile( compileFlags: CompileFlags, force: boolean, diff --git a/src/core/compile/CompilationProcessor.ts b/src/core/compile/CompilationProcessor.ts index 483a6da..fe2fe18 100644 --- a/src/core/compile/CompilationProcessor.ts +++ b/src/core/compile/CompilationProcessor.ts @@ -25,6 +25,15 @@ import { CircomResolvedFileInfo, } from "../../types/core"; +/** + * This class handles the entire process of circuit compilation, from configuring the appropriate `circom` compiler + * to managing and organizing the compilation artifact files. The class ensures the correct version and architecture + * of the `circom` compiler is used based on the user's operating system, and it manages the compilation lifecycle, + * including artifact generation and file handling. + * + * This class works alongside other classes such as the {@link CircomCompilerFactory}, + * {@link ICircuitArtifacts | ICircuitArtifacts}. + */ export class CompilationProcessor { private readonly _zkitConfig: ZKitConfig; private readonly _nodeModulesPath: string; @@ -45,10 +54,26 @@ export class CompilationProcessor { ]); } + /** + * Function responsible for compiling circuits, with relevant information passed as a parameter. + * + * The compilation process involves the following steps: + * 1. Identifies the highest pragma version among the passed {@link CircomResolvedFileInfo} objects + * 2. Creates a compiler object with the required version and architecture using {@link CircomCompilerFactory} + * 3. Creates an array of {@link CompilationInfo} objects, + * containing all necessary information for subsequent compilation steps + * 4. Compiles the circuits using the created compiler object, with all results written to a temporary directory + * 5. Upon successful compilation, all files from the temporary directory are moved to the artifact directory + * 6. Saves the artifact information using {@link ICircuitArtifacts | CircuitArtifacts} + * + * @param filesInfoToCompile Information about circuit files needed for compilation + */ public async compile(filesInfoToCompile: CircomResolvedFileInfo[]) { const tempDir: string = path.join(os.tmpdir(), ".zkit", uuid()); try { + // Generates a temporary directory, used as a buffer for files to prevent overwriting + // previous compilation artifacts in case the current compilation fails fsExtra.mkdirSync(tempDir, { recursive: true }); Reporter!.verboseLog("compilation-processor", "Compilation temp directory: %s", [tempDir]); @@ -70,6 +95,7 @@ export class CompilationProcessor { version = this._zkitConfig.compilerVersion; } + // Ensure that the CircomCompilerFactory object is properly instantiated before interacting with it createCircomCompilerFactory(); const compiler = await CircomCompilerFactory!.createCircomCompiler(version, isVersionStrict); @@ -97,6 +123,8 @@ export class CompilationProcessor { info.circuitName, info.circuitFileName, ); + + // Path to the file where errors encountered during compilation will be logged const errorsFilePath: string = getNormalizedFullPath(info.tempArtifactsPath, "errors.log"); fsExtra.mkdirSync(info.tempArtifactsPath, { recursive: true }); @@ -131,6 +159,7 @@ export class CompilationProcessor { } if (info.circuitFileName !== info.circuitName) { + // Renaming generated files after compilation to match the circuit template name rather than the file name renameFilesRecursively(info.tempArtifactsPath, info.circuitFileName, info.circuitName); } @@ -246,7 +275,7 @@ export class CompilationProcessor { return BigInt(`0x${buffer.reverse().toString("hex")}`); }; - /// @dev https://github.com/iden3/r1csfile/blob/d82959da1f88fbd06db0407051fde94afbf8824a/doc/r1cs_bin_format.md#format-of-the-file + // https://github.com/iden3/r1csfile/blob/d82959da1f88fbd06db0407051fde94afbf8824a/doc/r1cs_bin_format.md#format-of-the-file const numberOfSections = readBytes(8, 4); let sectionStart = 12; @@ -254,7 +283,7 @@ export class CompilationProcessor { const sectionType = Number(readBytes(sectionStart, 4)); const sectionSize = Number(readBytes(sectionStart + 4, 8)); - /// @dev Reading header section + // Reading header section if (sectionType == 1) { const totalConstraintsOffset = 4 + 8 + 4 + 32 + 4 + 4 + 4 + 4 + 8; diff --git a/src/core/compile/TypeGenerationProcessor.ts b/src/core/compile/TypeGenerationProcessor.ts index 57d3204..2c67f84 100644 --- a/src/core/compile/TypeGenerationProcessor.ts +++ b/src/core/compile/TypeGenerationProcessor.ts @@ -8,6 +8,18 @@ import { Reporter } from "../../reporter"; import { ZKitConfig } from "../../types/zkit-config"; import { ICircuitArtifacts } from "../../types/artifacts/circuit-artifacts"; +/** + * A class responsible for generating TypeScript types for compiled circuits. + * It uses the {@link https://github.com/dl-solarity/zktype | ZKType} library to create all the necessary + * types for developers, based on the information from the circuit artifact files. + * + * This process ensures that the generated types are aligned with the compiled circuits, making it easier + * for developers to work with them in a type-safe manner. + * + * In addition to generating types, overloaded versions of the `getCircuit` function are also generated, + * which are added to the `hardhat/types/runtime` module. These extensions simplify the writing of tests + * by providing more intuitive type definitions for the circuits. + */ export class TypeGenerationProcessor { private readonly _zkitConfig: ZKitConfig; private readonly _circuitArtifacts: ICircuitArtifacts; @@ -19,6 +31,16 @@ export class TypeGenerationProcessor { this._root = hre.config.paths.root; } + /** + * Generates TypeScript types and overloaded `getCircuit` functions for all existing circuit artifacts. + * + * The generation process follows these steps: + * 1. Retrieve an array of paths to all existing artifacts + * using the {@link ICircuitArtifacts | CircuitArtifacts} interface + * 2. Convert the retrieved paths into the format required by the {@link CircuitTypesGenerator} + * 3. Create an instance of the {@link CircuitTypesGenerator} class with the necessary parameters + * 4. Generate the types based on the provided circuit artifacts + */ public async generateAllTypes() { const circuitsArtifactsPaths: string[] = await Promise.all( (await this._circuitArtifacts.getCircuitArtifactPaths()).map(async (fullPath: string) => { diff --git a/src/core/compiler/CircomCompiler.ts b/src/core/compiler/CircomCompiler.ts index a6ce076..0b6abfa 100644 --- a/src/core/compiler/CircomCompiler.ts +++ b/src/core/compiler/CircomCompiler.ts @@ -9,9 +9,42 @@ import { MAGIC_DESCRIPTOR } from "../../constants"; // eslint-disable-next-line const { Context, CircomRunner, bindings } = require("@distributedlab/circom2"); +/** + * An abstract class that serves as the base for all `circom` compiler implementations. + * This class provides the foundational logic for creating the list of compilation arguments + * from a {@link CompileConfig} object, which will be passed to the compiler during the compilation process. + * + * The `BaseCircomCompiler` class defines the core behavior and structure for all `circom` compilers, + * ensuring consistency across different compiler implementations. It also offers utility methods + * for generating base compilation arguments and handling specific flags related to the compilation process. + * + * Key responsibilities of the `BaseCircomCompiler` class include: + * + * 1. **Abstract Compilation Method**: The `compile` method is abstract and must be implemented by + * any subclass. This method is responsible for executing the actual compilation of circuits based on + * the provided {@link CompileConfig} parameters + * + * 2. **Compilation Argument Generation**: The `getCompilationArgs` method creates a complete list + * of command-line arguments that will be passed to the `circom` compiler. It dynamically generates + * these arguments based on the compilation flags specified in the config + * + * 3. **Base Compilation Arguments**: The `_getBaseCompilationArgs` method provides the essential + * arguments, such as the circuit file path and the output directory for the compiled artifacts. + * It also includes any linked libraries that may be required during the compilation process + * + * 4. **Flag Handling**: For each compilation flag specified in the `CompileConfig`, the class ensures + * that the corresponding flag is added to the arguments list if its value is truthy. This allows + * flexible control over the compilation process + */ abstract class BaseCircomCompiler implements ICircomCompiler { abstract compile(config: CompileConfig): Promise; + /** + * Creates the list of compilation arguments to be passed to the compiler + * + * @param config The configuration object with compilation parameters + * @returns An array of strings representing the compilation parameters that will be passed to the compiler + */ public getCompilationArgs(config: CompileConfig): string[] { const args: string[] = this._getBaseCompilationArgs(config); @@ -22,6 +55,13 @@ abstract class BaseCircomCompiler implements ICircomCompiler { return args; } + /** + * Creates the base list of compilation arguments, such as circuit file path and output path + * + * @param baseConfig The base configuration object containing the circuit file path, + * artifacts path, and linked libraries + * @returns An array of strings representing the essential compilation arguments + */ protected _getBaseCompilationArgs(baseConfig: BaseCompileConfig): string[] { const args = [baseConfig.circuitFullPath, "-o", baseConfig.artifactsFullPath]; @@ -33,11 +73,31 @@ abstract class BaseCircomCompiler implements ICircomCompiler { } } +/** + * A concrete implementation of the {@link BaseCircomCompiler} that handles the compilation of `circom` circuits + * using a binary compiler specified at instantiation. This class is responsible for invoking the compiler + * with the appropriate arguments derived from the provided {@link CompileConfig} object. + * + * The `BinaryCircomCompiler` class encapsulates the logic necessary to execute the compilation process + * and manage errors that may arise during execution. By leveraging the base functionality provided by + * {@link BaseCircomCompiler}, this class can easily integrate with different binary compilers while maintaining + * a consistent interface. + */ export class BinaryCircomCompiler extends BaseCircomCompiler { constructor(private readonly _compiler: string) { super(); } + /** + * Executes the compilation of the specified `circom` circuits using the binary compiler. + * + * The method retrieves the compilation arguments from the configuration and calls the + * binary compiler with these arguments. It handles potential errors during the compilation + * process and throws a specific error if compilation fails. + * + * @param config The configuration object containing parameters for compilation + * @throws HardhatZKitError If the compilation process fails, providing details about the error + */ public async compile(config: CompileConfig) { const compilationArgs: string[] = this.getCompilationArgs(config); @@ -49,11 +109,35 @@ export class BinaryCircomCompiler extends BaseCircomCompiler { } } +/** + * A concrete implementation of the {@link BaseCircomCompiler} designed to compile `circom` circuits using + * a WebAssembly (WASM) compiler. This class integrates the WASM execution environment, managing the + * execution of the compiler and handling error logging efficiently. + * + * The `WASMCircomCompiler` is responsible for executing the compiler in a controlled environment, + * providing a seamless interface for compiling circuits while leveraging the performance advantages + * of WebAssembly. It manages the intricacies of invoking the compiler, processing arguments, and + * capturing errors that may arise during execution. + * + * This class is designed for developers who require efficient compilation of `circom` circuits + * within a WASM context, enhancing performance while maintaining robust error management and + * logging capabilities. + */ export class WASMCircomCompiler extends BaseCircomCompiler { constructor(private readonly _compiler: typeof Context) { super(); } + /** + * Executes the compilation of the specified `circom` circuits using the WebAssembly compiler. + * + * The method manages the creation of an error log file, retrieves the necessary compilation arguments, + * and initializes the {@link CircomRunner} for executing the compilation. It handles potential errors during + * execution and ensures proper cleanup of resources after the compilation process. + * + * @param config The configuration object containing parameters for compilation + * @throws HardhatZKitError If the compilation process fails, providing details about the error + */ public async compile(config: CompileConfig) { const errorFileDescriptor: number = fs.openSync(config.errorFileFullPath, "w"); const compilationArgs: string[] = this.getCompilationArgs(config); @@ -68,6 +152,18 @@ export class WASMCircomCompiler extends BaseCircomCompiler { } } + /** + * Configures and creates an instance of the {@link CircomRunner} for executing the compiler + * with the specified arguments. + * + * This method sets up the necessary bindings and descriptors for managing the execution environment of the compiler, + * ensuring that file system interactions and exit handling are properly defined. + * + * @param callArgs The arguments to be passed to the compiler + * @param quiet A boolean indicating whether to suppress standard error output + * @param errDescriptor The file descriptor for logging errors during compilation + * @returns An instance of {@link CircomRunner} configured with the provided parameters + */ private _getCircomRunner(callArgs: string[], quiet: boolean, errDescriptor: number): typeof CircomRunner { return new CircomRunner({ args: callArgs, diff --git a/src/core/compiler/CircomCompilerDownloader.ts b/src/core/compiler/CircomCompilerDownloader.ts index a22c2c6..9869136 100644 --- a/src/core/compiler/CircomCompilerDownloader.ts +++ b/src/core/compiler/CircomCompilerDownloader.ts @@ -1,6 +1,6 @@ import os from "os"; import path from "path"; -import fs from "fs-extra"; +import fsExtra from "fs-extra"; import https from "https"; import semver from "semver"; import { promisify } from "util"; @@ -23,11 +23,43 @@ import { getHighestVersion } from "./versioning"; import { MultiProcessMutex } from "hardhat/internal/util/multi-process-mutex"; +/** + * Handles the downloading of Circom compilers for various platforms. + * + * The `CircomCompilerDownloader` class manages the retrieval of the appropriate Circom compiler + * binaries based on the operating system and architecture. It provides methods to check if a + * specific version of the compiler has already been downloaded, as well as to download it if + * necessary. This class also supports version management, allowing for both strict and + * flexible version downloads. + * + * The class employs a mutex to prevent concurrent downloads of the same compiler version, + * ensuring thread safety during the downloading process. Additionally, it offers functionality + * to verify downloaded compilers, providing assurance that the binary is valid and functional. + * + * Key functionalities include: + * - Determining the appropriate compiler binary for the current platform + * - Downloading the specified version of the Circom compiler + * - Checking if a particular compiler version is already available locally + * - Retrieving the compiler binary path for a given version + * + * This class is essential for automating the setup of the Circom compilation environment, + * enabling seamless integration within development workflows. + */ export class CircomCompilerDownloader { private static _downloaderPerPlatform: Map = new Map(); private readonly _mutex = new MultiProcessMutex("compiler-download"); + /** + * Determines the appropriate compiler binary based on the operating system and architecture. + * + * This method checks the current architecture and platform to return the corresponding + * {@link CompilerPlatformBinary} value. If the architecture is neither "x64" nor "arm64", + * the function defaults to using the WebAssembly (WASM) binary. + * + * @param arch The architecture to check, defaults to the system's architecture + * @returns The corresponding {@link CompilerPlatformBinary} for the system + */ public static getCompilerPlatformBinary(arch = os.arch()): CompilerPlatformBinary { if (arch !== "x64" && arch !== "arm64") { return CompilerPlatformBinary.WASM; @@ -45,7 +77,22 @@ export class CircomCompilerDownloader { } } - public static getCircomCompilerDownloader(platform: CompilerPlatformBinary, compilersDir: string) { + /** + * Retrieves or creates a {@link CircomCompilerDownloader} instance + * for the specified platform and compilers directory. + * + * This method checks if a downloader for the given platform and directory already exists in the cache. + * If not, it creates a new instance and stores it for future use. This helps to manage multiple + * downloaders efficiently and ensures that only one instance is created per platform-directory pair. + * + * @param platform The platform for which the compiler downloader is needed + * @param compilersDir The directory where the compilers are stored + * @returns The {@link CircomCompilerDownloader} instance for the specified platform and directory + */ + public static getCircomCompilerDownloader( + platform: CompilerPlatformBinary, + compilersDir: string, + ): CircomCompilerDownloader { const key = platform + compilersDir; if (!this._downloaderPerPlatform.has(key)) { @@ -60,12 +107,25 @@ export class CircomCompilerDownloader { private readonly _compilersDir: string, ) {} + /** + * Checks if the specified compiler version is downloaded. + * + * The method verifies the existence of the compiler files based on the provided version and + * whether strict version matching is required. If `isVersionStrict` is true, it checks for the + * exact version in both standard and WASM download paths. If false, it compares the latest + * downloaded version with the requested version using semantic versioning to determine if it + * meets or exceeds the requested version. + * + * @param version The version of the compiler to check for + * @param isVersionStrict Indicates whether to enforce strict version matching + * @returns true if the compiler is downloaded, false otherwise + */ public async isCompilerDownloaded(version: string, isVersionStrict: boolean): Promise { if (isVersionStrict) { const downloadPath = this._getCompilerDownloadPath(version); const downloadPathWasm = this._getWasmCompilerDownloadPath(version); - return (await fs.pathExists(downloadPath)) || fs.pathExists(downloadPathWasm); + return (await fsExtra.pathExists(downloadPath)) || fsExtra.pathExists(downloadPathWasm); } const latestDownloadedVersion = this._getLatestDownloadedCircomVersion(); @@ -73,6 +133,21 @@ export class CircomCompilerDownloader { return semver.gte(latestDownloadedVersion, version); } + /** + * Retrieves the binary path of the specified Circom compiler version. + * + * The method checks for the availability of the compiler binary based on the provided version + * and whether strict version matching is enforced. If strict matching is not required, it + * defaults to the latest downloaded version. The function then checks for the existence of + * both standard and WASM compiler binaries. If found, it returns the binary path along with + * the version and a flag indicating whether it is a WASM binary. If neither binary is found, + * it throws an error indicating that the compiler needs to be downloaded. + * + * @param version The version of the compiler to retrieve + * @param isVersionStrict Indicates whether to enforce strict version matching + * @returns An object containing the binary path, version, and a boolean indicating if it is a WASM binary + * @throws `HardhatZKitError` If the specified compiler version is not downloaded + */ public async getCompilerBinary(version: string, isVersionStrict: boolean): Promise { if (!isVersionStrict) { version = this._getLatestDownloadedCircomVersion(); @@ -85,17 +160,31 @@ export class CircomCompilerDownloader { const compilerBinaryPath = this._getCompilerDownloadPath(version); const wasmCompilerBinaryPath = this._getWasmCompilerDownloadPath(version); - if (await fs.pathExists(wasmCompilerBinaryPath)) { + if (await fsExtra.pathExists(wasmCompilerBinaryPath)) { return { binaryPath: wasmCompilerBinaryPath, version: version, isWasm: true }; } - if (await fs.pathExists(compilerBinaryPath)) { + if (await fsExtra.pathExists(compilerBinaryPath)) { return { binaryPath: compilerBinaryPath, version: version, isWasm: false }; } throw new HardhatZKitError(`Trying to get a Circom compiler v${version} before it was downloaded`); } + /** + * Downloads the specified version of the Circom compiler if it is not already downloaded. + * + * This method utilizes a mutex to ensure that concurrent calls do not lead to multiple + * downloads of the same compiler version. It determines the version to download based on + * whether strict version matching is enforced. If the compiler is already downloaded, the + * function exits early. If not, it initiates the download process and reports the + * downloading information. After downloading, it optionally verifies the downloaded compiler. + * + * @param version The version of the compiler to download + * @param isVersionStrict Indicates whether to enforce strict version matching + * @param verifyCompiler Indicates whether to perform verification on the downloaded compiler + * @throws `HardhatZKitError` If an error occurs during the download or verification process + */ public async downloadCompiler(version: string, isVersionStrict: boolean, verifyCompiler: boolean): Promise { await this._mutex.use(async () => { const versionToDownload = isVersionStrict ? version : await this._getLatestCircomVersion(); @@ -122,7 +211,7 @@ export class CircomCompilerDownloader { private _getLatestDownloadedCircomVersion(): string { try { - const entries = fs.readdirSync(this._compilersDir, { withFileTypes: true }); + const entries = fsExtra.readdirSync(this._compilersDir, { withFileTypes: true }); const versions = entries .filter((entry) => { @@ -131,7 +220,7 @@ export class CircomCompilerDownloader { } const dirPath = path.join(this._compilersDir, entry.name); - const files = fs.readdirSync(dirPath); + const files = fsExtra.readdirSync(dirPath); return files.includes(this._platform) || files.includes("circom.wasm"); }) @@ -218,11 +307,11 @@ export class CircomCompilerDownloader { this._platform !== CompilerPlatformBinary.WINDOWS_ARM && this._platform !== CompilerPlatformBinary.WASM ) { - await fs.chmod(downloadPath, 0o755); + await fsExtra.chmod(downloadPath, 0o755); } if (this._platform !== CompilerPlatformBinary.WASM && !(await this._checkCompilerWork(downloadPath))) { - await fs.unlink(downloadPath); + await fsExtra.unlink(downloadPath); throw new HardhatZKitError("Downloaded compiler is not working"); } diff --git a/src/core/compiler/CircomCompilerFactory.ts b/src/core/compiler/CircomCompilerFactory.ts index 4094322..43a4293 100644 --- a/src/core/compiler/CircomCompilerFactory.ts +++ b/src/core/compiler/CircomCompilerFactory.ts @@ -1,6 +1,6 @@ import os from "os"; import path from "path"; -import fs from "fs-extra"; +import fsExtra from "fs-extra"; import semver from "semver"; import { promisify } from "util"; import { exec } from "child_process"; @@ -17,7 +17,43 @@ import { CompilerInfo, CompilerPlatformBinary, ICircomCompiler, NativeCompiler } // eslint-disable-next-line const { Context } = require("@distributedlab/circom2"); +/** + * Abstract factory class responsible for creating instances of Circom compilers. + * + * This class provides a method to instantiate different types of Circom compilers + * based on the specified version and platform. It includes logic to handle versioning, + * ensure compatibility with supported architectures, and determine the appropriate + * compiler to use (native, binary, or WASM) for the compilation process. + * + * The factory also includes error handling for unsupported versions and facilitates + * the use of binary translators on ARM architectures when necessary. This allows + * developers to seamlessly work with various Circom compiler versions while + * managing dependencies and ensuring optimal performance during circuit compilation. + */ export class BaseCircomCompilerFactory { + /** + * Creates an instance of a Circom compiler based on the specified version and architecture. + * + * This method first checks if the requested Circom compiler version is supported. If the version + * exceeds the latest supported version, an error is thrown. The method attempts to create a native + * compiler first; if successful, the instance is returned immediately. + * + * If the native compiler cannot be created, the method determines the appropriate compiler binary + * for the current platform. If the requested version is strictly enforced and the system architecture + * is arm64, but the version is older than the supported arm64 version, it falls back to using the + * x64 binary. + * + * The method then attempts to create a binary compiler if the platform binary is not WASM. If this + * also fails, it defaults to creating a WASM compiler instance. This provides flexibility in compiler + * selection, accommodating various system architectures and version requirements. + * + * @param version The version of the Circom compiler to create + * @param isVersionStrict Indicates whether strict version matching is required + * @param verifyCompiler Optional flag indicating if the downloaded compiler should be verified + * @returns An instance of `ICircomCompiler` for the specified version + * + * @throws `HardhatZKitError` error if the specified compiler version is unsupported + */ public async createCircomCompiler( version: string, isVersionStrict: boolean, @@ -111,6 +147,7 @@ export class BaseCircomCompilerFactory { try { const execP = promisify(exec); + // Attempts to locate a globally installed Circom compiler using the `whereis` utility const { stdout: circomLocation } = await execP("whereis circom"); const trimmedBinaryPath = circomLocation.trim().split(" "); @@ -156,20 +193,41 @@ export class BaseCircomCompilerFactory { } private _getWasmCompiler(compilerPath: string): typeof Context { - return fs.readFileSync(require.resolve(compilerPath)); + return fsExtra.readFileSync(require.resolve(compilerPath)); } private async _getCompilersDir(): Promise { const compilersDir = path.join(os.homedir(), ".zkit", "compilers"); - await fs.ensureDir(compilersDir); + await fsExtra.ensureDir(compilersDir); return compilersDir; } } +/** + * Singleton instance of the {@link BaseCircomCompilerFactory}. + * + * This variable holds a reference to a single instance of the + * {@link BaseCircomCompilerFactory}. It is initialized when the + * {@link createCircomCompilerFactory} function is called for the first time. + * Subsequent calls to this function will not create a new instance, + * ensuring that there is only one factory managing the creation of + * Circom compilers throughout the application. + * + * This design pattern promotes efficient resource usage and helps + * maintain consistent state when dealing with compiler instances. + */ export let CircomCompilerFactory: BaseCircomCompilerFactory | null = null; +/** + * Creates and initializes the {@link CircomCompilerFactory} singleton. + * + * If the {@link CircomCompilerFactory} instance already exists, the function + * does nothing. Otherwise, it creates a new instance of + * {@link BaseCircomCompilerFactory}, allowing for the management of + * Circom compiler instances throughout the application lifecycle. + */ export function createCircomCompilerFactory() { if (CircomCompilerFactory) { return; diff --git a/src/core/dependencies/parser/CircomFilesParser.ts b/src/core/dependencies/parser/CircomFilesParser.ts index f4bd6de..46d0530 100644 --- a/src/core/dependencies/parser/CircomFilesParser.ts +++ b/src/core/dependencies/parser/CircomFilesParser.ts @@ -1,4 +1,4 @@ -import { getCircomParser, ParserError, MainComponent, BigIntOrNestedArray } from "@distributedlab/circom-parser"; +import { getCircomParser, ParserError, BigIntOrNestedArray } from "@distributedlab/circom-parser"; import { CircomFilesVisitor } from "./CircomFilesVisitor"; import { CircomTemplateInputsVisitor } from "./CircomTemplateInputsVisitor"; @@ -7,10 +7,38 @@ import { Reporter } from "../../../reporter"; import { InputData, ResolvedFileData } from "../../../types/core"; +/** + * A parser class for handling Circom files and extracting relevant data. + * + * This class provides methods to parse the contents of Circom files, including + * extracting template inputs and resolving the overall structure of the Circom + * circuit. It utilizes a visitor pattern to traverse the parsed structures and + * collect necessary information about templates, parameters, and included files. + * + * The class also implements caching mechanisms to optimize repeated parsing + * operations, ensuring that parsed data can be quickly retrieved without the + * need for re-parsing the same file content. + * + * This class uses the {@link https://www.npmjs.com/package/@distributedlab/circom-parser | Circom parser} + * package to facilitate the parsing process. + */ export class CircomFilesParser { private _cache = new Map(); - private _mainComponentsCache = new Map(); + /** + * Parses the content of a Circom file and extracts relevant data. + * + * This method attempts to retrieve the parsed data from a cache using the + * provided absolute path and content hash. If the data is not found in + * the cache, it uses a {@link https://www.npmjs.com/package/@distributedlab/circom-parser | Circom parser} to analyze the file content and + * gather information about its structure and components. + * + * @param fileContent The content of the Circom file as a string + * @param absolutePath The absolute path to the Circom file being parsed + * @param contentHash A hash representing the content of the file, used for cache management + * @returns An object containing the parsed data extracted from the Circom file + * @throws `ParserError` if the parser encounters any issues while parsing the file, such as syntax errors + */ public parse(fileContent: string, absolutePath: string, contentHash: string): ResolvedFileData { const cacheResult = this._getFromCache(absolutePath, contentHash); @@ -37,6 +65,20 @@ export class CircomFilesParser { return { parsedFileData: circomFilesVisitor.fileData }; } + /** + * Parses the input parameters of a specified Circom template from the given file. + * + * This method initializes a {@link https://www.npmjs.com/package/@distributedlab/circom-parser | Circom parser} for the specified file, then utilizes + * a visitor to extract information about the template inputs. It checks for any + * parsing errors and throws a `ParserError` if any issues are encountered. + * The structured input data associated with the specified template is then returned. + * + * @param absolutePath The absolute path to the Circom file containing the template + * @param templateName The name of the template whose inputs are being parsed + * @param parameterValues A record of parameter values used for template input resolution + * @returns A structured record of input data for the specified template + * @throws ParserError If any parsing issues occur while processing the template inputs + */ public parseTemplateInputs( absolutePath: string, templateName: string, diff --git a/src/core/dependencies/parser/CircomFilesVisitor.ts b/src/core/dependencies/parser/CircomFilesVisitor.ts index df31716..54992c7 100644 --- a/src/core/dependencies/parser/CircomFilesVisitor.ts +++ b/src/core/dependencies/parser/CircomFilesVisitor.ts @@ -13,6 +13,14 @@ import { import { CircomFileData } from "../../../types/core"; +/** + * Class responsible for gathering comprehensive information from a Circom file. + * + * The `CircomFilesVisitor` traverses the abstract syntax tree (AST) of Circom files + * to collect data about pragma directives, includes, templates, and components. + * This class provides an efficient means of extracting and organizing the data + * contained within Circom files for further processing or validation. + */ export class CircomFilesVisitor extends CircomVisitor { fileData: CircomFileData; currentTemplate: string | null; diff --git a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts index de06ac8..d71a72f 100644 --- a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts +++ b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts @@ -11,6 +11,19 @@ import { import { InputData } from "../../../types/core"; import { HardhatZKitError } from "../../../errors"; +/** + * Visitor class for the {@link https://www.npmjs.com/package/@distributedlab/circom-parser | @distributedlab/circom-parser} package. + * + * The `CircomTemplateInputsVisitor` is designed to traverse the abstract + * syntax tree (AST) of Circom templates. Its primary role is to + * collect information about the inputs of the Circom circuits, + * specifically the dimensions and types of signals declared in the + * template. + * + * This class provides functionality to visit different components of + * the template structure, enabling efficient extraction and + * organization of input data for further processing or validation. + */ export class CircomTemplateInputsVisitor extends CircomVisitor { templateInputs: Record; expressionVisitor: CircomExpressionVisitor; diff --git a/src/core/setup/SetupFilesResolver.ts b/src/core/setup/SetupFilesResolver.ts index 0dfbb59..9d35118 100644 --- a/src/core/setup/SetupFilesResolver.ts +++ b/src/core/setup/SetupFilesResolver.ts @@ -11,6 +11,15 @@ import { FileFilterSettings, SetupSettings, ZKitConfig } from "../../types/zkit- import { CircuitArtifact, CompilerOutputFileInfo, ICircuitArtifacts } from "../../types/artifacts/circuit-artifacts"; import { CircuitSetupInfo } from "../../types/core"; +/** + * Class responsible for resolving setup files for circuits in a given project. + * + * The SetupFilesResolver manages the retrieval and filtering of circuit setup information based on + * the specified setup settings. It interacts with circuit artifacts to gather necessary data, + * ensuring that the setup process is efficient and up-to-date. The class provides methods to determine + * which circuits require setup and handles caching to optimize performance. It integrates with the + * circuit setup cache to track changes in files and determine if a setup is necessary. + */ export class SetupFilesResolver { private readonly _zkitConfig: ZKitConfig; private readonly _projectRoot: string; @@ -23,6 +32,21 @@ export class SetupFilesResolver { this._projectRoot = hardhatConfig.paths.root; } + /** + * Retrieves information about circuits that need to be set up based on the provided setup settings. + * + * This method follows the execution flow: + * 1. Gathers all fully qualified names of the circuits from the circuit artifacts + * 2. Creates an array of circuit setup information by fetching the setup info for all circuits + * 3. If the force flag is not set, filters out circuits that have not changed since the last setup + * based on their content hash and contribution settings + * 4. Applies additional filtering based on the provided setup settings to finalize the list of circuits + * 5. Returns an array of {@link CircuitSetupInfo} objects + * + * @param setupSettings The settings that dictate how the circuit setup should be performed + * @param force A boolean flag that, when true, skips filtering by file changes during setup + * @returns An array of {@link CircuitSetupInfo} objects containing information about the circuits to be set up + */ public async getCircuitsInfoToSetup(setupSettings: SetupSettings, force: boolean): Promise { const allFullyQualifiedNames: string[] = await this._circuitArtifacts.getAllCircuitFullyQualifiedNames(); diff --git a/src/core/setup/SetupProcessor.ts b/src/core/setup/SetupProcessor.ts index 5230e5d..852b7fe 100644 --- a/src/core/setup/SetupProcessor.ts +++ b/src/core/setup/SetupProcessor.ts @@ -14,12 +14,37 @@ import { getNormalizedFullPath } from "../../utils/path-utils"; import { CircuitArtifact, ICircuitArtifacts } from "../../types/artifacts/circuit-artifacts"; import { ContributionSettings, ProvingSystemType } from "../../types/core"; +/** + * Class responsible for processing the setup of circuit artifacts. + * + * This class facilitates the setup of circuits by generating the necessary + * cryptographic keys and artifacts required for zero-knowledge proofs. + * It utilizes temporary directories for managing intermediate files and + * provides methods for generating both ZKey and VKey files for the specified + * circuit artifacts, ensuring a streamlined setup process. + * + * The setup process involves key generation and artifact saving, with + * appropriate error handling and reporting mechanisms in place. + */ export class SetupProcessor { constructor( private readonly _ptauDirFullPath: string, private readonly _circuitArtifacts: ICircuitArtifacts, ) {} + /** + * Function responsible for setting up circuits, using relevant artifacts and contribution settings. + * + * The setup process involves the following steps: + * 1. Creates a temporary directory for storing intermediate files during the setup process + * 2. Retrieves the required PTAU file path for the specified circuit artifacts + * 3. Generates ZKey files for the circuit artifacts using the provided contribution settings and PTAU file + * 4. Generates VKey files for the circuit artifacts + * 5. Saves the circuit artifacts with the generated ZKey and VKey files + * + * @param circuitArtifacts An array of circuit artifacts that need to be set up + * @param contributionSettings The contribution settings to be used during the setup process + */ public async setup(circuitArtifacts: CircuitArtifact[], contributionSettings: ContributionSettings) { const tempDir: string = path.join(os.tmpdir(), ".zkit", uuid()); diff --git a/src/errors.ts b/src/errors.ts index a1e2a9e..ecc4d63 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,6 +2,15 @@ import { NomicLabsHardhatPluginError } from "hardhat/plugins"; import { PLUGIN_NAME } from "./constants"; +/** + * Custom error class for handling errors within the Hardhat ZKit plugin. + * + * This class extends the {@link NomicLabsHardhatPluginError} to provide + * more specific error handling for the ZKit plugin. It allows for + * the inclusion of a parent error for better context when errors + * are propagated, while maintaining the plugin name for consistent + * error reporting. + */ export class HardhatZKitError extends NomicLabsHardhatPluginError { constructor(message: string, parent?: Error) { super(PLUGIN_NAME, message, parent); diff --git a/src/index.ts b/src/index.ts index 9185eb1..daa86a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -90,6 +90,8 @@ const compile: ActionType = async (taskArgs: CompileTaskConfi env.config, ); + // Flags for specifying the necessary configurations during the setup process. + // R1CS, Wasm, and sym flags are mandatory const compileFlags: CompileFlags = { r1cs: true, wasm: true, @@ -165,6 +167,8 @@ const setup: ActionType = async (taskArgs: SetupTaskConfig, env if (circuitSetupInfoArr.length > 0) { let ptauDir = env.config.zkit.setupSettings.ptauDir; + // If `ptauDir` is not specified in the configuration, + // the `.zkit/ptau` folder in the user's home directory is used as the default location if (ptauDir) { ptauDir = path.isAbsolute(ptauDir) ? ptauDir : getNormalizedFullPath(env.config.paths.root, ptauDir); } else { diff --git a/src/reporter/ReporterFacade.ts b/src/reporter/ReporterFacade.ts index 14aa26c..4575f0a 100644 --- a/src/reporter/ReporterFacade.ts +++ b/src/reporter/ReporterFacade.ts @@ -20,6 +20,13 @@ import { import { createProgressBarProcessor } from "./ProgressBarProcessor"; import { createSpinnerProcessor } from "./SpinnerProcessor"; +/** + * A facade for reporting various types of log information to the user, + * including general information, warnings, errors, and verbose logging for more detailed output. + * + * This class simplifies the interaction with the logging system, providing an easy-to-use interface + * for standard messages and detailed logs during the execution of operations. + */ class ReporterFacade { private _setupReporter!: SetupReporter; private _progressReporter!: ProgressReporter; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c4062a5..4174cfc 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import fs from "fs-extra"; +import fsExtra from "fs-extra"; import https from "https"; import { exec } from "child_process"; @@ -9,13 +9,13 @@ import { Reporter } from "../reporter"; import { ExecCallResult } from "../types/utils"; /** - * Downloads a file from the specified URL. + * Downloads a file from the specified URL * - * @param {string} file - The path to save the file to. - * @param {string} url - The URL to download the file from. - * @param {Function} onFinishReporter - The Reporter callback function for when the download finishes. - * @param {Function} onErrorReporter - The Reporter callback function for when an error occurs during the download. - * @returns {Promise} Whether the file was downloaded successfully. + * @param file The path to save the file to + * @param url The URL to download the file from + * @param onFinishReporter The Reporter callback function for when the download finishes + * @param onErrorReporter The Reporter callback function for when an error occurs during the download + * @returns Whether the file was downloaded successfully */ export async function downloadFile( file: string, @@ -24,8 +24,8 @@ export async function downloadFile( onErrorReporter: () => void, ): Promise { try { - await fs.ensureFile(file); - const fileStream = fs.createWriteStream(file); + await fsExtra.ensureFile(file); + const fileStream = fsExtra.createWriteStream(file); return new Promise((resolve) => { const handleRequest = (currentUrl: string) => { @@ -36,14 +36,14 @@ export async function downloadFile( handleRequest(redirectUrl); } else { onErrorReporter(); - fs.unlink(file, () => resolve(false)); + fsExtra.unlink(file, () => resolve(false)); } return; } if (response.statusCode !== 200) { onErrorReporter(); - fs.unlink(file, () => resolve(false)); + fsExtra.unlink(file, () => resolve(false)); return; } @@ -58,7 +58,7 @@ export async function downloadFile( }) .on("error", () => { onErrorReporter(); - fs.unlink(file, () => resolve(false)); + fsExtra.unlink(file, () => resolve(false)); }); fileStream @@ -70,12 +70,12 @@ export async function downloadFile( }) .on("error", () => { onErrorReporter(); - fs.unlink(file, () => resolve(false)); + fsExtra.unlink(file, () => resolve(false)); }); }); request.on("error", () => { - fs.unlink(file, () => resolve(false)); + fsExtra.unlink(file, () => resolve(false)); }); }; @@ -99,5 +99,5 @@ export async function execCall(execFile: string, callArgs: string[]): Promise