Skip to content

Commit

Permalink
Minor Fixes (#10)
Browse files Browse the repository at this point in the history
* Fixed wrong calldata public signals count

* Use abs paths only

* Fixed abs path bugs

* Added ability to recover after error during artifact extraction
  • Loading branch information
KyrylR authored Aug 8, 2024
1 parent 5b9b228 commit eeaaaf3
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 120 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [v0.2.4-v0.2.6]

* Added ability to recover after error during artifact extraction
* Fixed bug with the linearization of public signals
* Changed Public signals type to NumberLike
* Added calculateWitness method to the wrapper class

## [v0.2.2-v0.2.3]

- Fixed bug during the package publishing process
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/zktype",
"version": "0.2.5",
"version": "0.2.6",
"description": "Unleash TypeScript bindings for Circom circuits",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
15 changes: 7 additions & 8 deletions src/core/BaseTSGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export default class BaseTSGenerator {
* Returns the path to the output directory for the generated TypeScript files.
*/
public getOutputTypesDir(): string {
return this._zktypeConfig.outputTypesDir ?? "generated-types/circuits";
const relativePath: string = this._zktypeConfig.outputTypesDir ?? "generated-types/circuits";

return path.join(this._projectRoot, relativePath);
}

/**
Expand All @@ -58,16 +60,13 @@ export default class BaseTSGenerator {
* @param {string} content - The content to be saved.
*/
protected _saveFileContent(typePath: string, content: string): void {
if (!fs.existsSync(path.join(this._projectRoot, this.getOutputTypesDir(), path.dirname(typePath)))) {
fs.mkdirSync(path.join(this._projectRoot, this.getOutputTypesDir(), path.dirname(typePath)), {
if (!fs.existsSync(path.join(this.getOutputTypesDir(), path.dirname(typePath)))) {
fs.mkdirSync(path.join(this.getOutputTypesDir(), path.dirname(typePath)), {
recursive: true,
});
}

fs.writeFileSync(
path.join(this._projectRoot, this.getOutputTypesDir(), typePath),
[this._getPreamble(), content].join("\n\n"),
);
fs.writeFileSync(path.join(this.getOutputTypesDir(), typePath), [this._getPreamble(), content].join("\n\n"));
}

/**
Expand Down Expand Up @@ -179,7 +178,7 @@ export default class BaseTSGenerator {
* Expects to get the path to the circuit file, relative to the directory where the generated types are stored.
*/
protected _checkIfCircuitExists(pathToCircuit: string): boolean {
const pathFromRoot = path.join(this._projectRoot, this.getOutputTypesDir(), pathToCircuit);
const pathFromRoot = path.join(this.getOutputTypesDir(), pathToCircuit);

return fs.existsSync(pathFromRoot);
}
Expand Down
209 changes: 139 additions & 70 deletions src/core/CircuitArtifactGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { InternalType, SignalTypeNames, SignalVisibilityNames } from "../constan
import {
Stmt,
Signal,
Result,
Template,
CircuitAST,
SignalType,
Expand All @@ -30,6 +31,11 @@ export default class CircuitArtifactGenerator {
*/
public static readonly CURRENT_FORMAT: string = "zktype-circom-artifact-1";

/**
* The default format version of the circuit artifact.
*/
public static readonly DEFAULT_CIRCUIT_FORMAT: string = "zktype-circom-artifact-default";

private readonly _projectRoot: string;
private readonly _circuitArtifactGeneratorConfig: ArtifactGeneratorConfig;

Expand All @@ -45,98 +51,68 @@ export default class CircuitArtifactGenerator {
/**
* Generates circuit artifacts based on the ASTs.
*/
public async generateCircuitArtifacts(): Promise<void> {
public async generateCircuitArtifacts(): Promise<string[]> {
const astFilePaths = this._circuitArtifactGeneratorConfig.circuitsASTPaths;

const errors: string[] = [];

for (const astFilePath of astFilePaths) {
const circuitArtifact = await this.extractArtifact(astFilePath);

this._saveArtifact(
{
...circuitArtifact,
basePath: this._circuitArtifactGeneratorConfig.basePath,
},
this._circuitArtifactGeneratorConfig.basePath,
);
const circuitArtifact = await this.getCircuitArtifact(astFilePath);

if (circuitArtifact.error) {
errors.push(circuitArtifact.error);
}

this._saveArtifact(circuitArtifact.data, this._circuitArtifactGeneratorConfig.basePath);
}

return errors;
}

/**
* Returns the configuration of the `CircuitArtifactGenerator`.
*/
public getOutputArtifactsDir(): string {
return this._circuitArtifactGeneratorConfig.outputArtifactsDir ?? "artifacts/circuits";
const relativePath: string = this._circuitArtifactGeneratorConfig.outputArtifactsDir ?? "artifacts/circuits";

return path.join(this._projectRoot, relativePath);
}

/**
* Extracts the artifact information from the AST JSON file.
*
* All the fields that are required for the artifact are extracted from the AST are validated.
*
* Will throw an error if:
* - The AST is missing.
* - _validateCircuitAST function fails.
*
* Will return an error if failed to get necessary information from the AST.
*
* @param {string} pathToTheAST - The path to the AST JSON file.
* @returns {Promise<CircuitArtifact>} A promise that resolves to the extracted circuit artifact.
*/
public async extractArtifact(pathToTheAST: string): Promise<CircuitArtifact> {
const ast: CircuitAST | undefined = JSON.parse(fs.readFileSync(pathToTheAST, "utf-8"));

if (!ast) {
throw new Error(`The circuit AST is missing. Path: ${pathToTheAST}`);
}

this._validateCircuitAST(ast);

const circuitArtifact: CircuitArtifact = {
_format: CircuitArtifactGenerator.CURRENT_FORMAT,
circuitName: ast.circomCompilerOutput[0].main_component![1].Call.id,
sourceName: ast.sourcePath,
basePath: "",
compilerVersion: ast.circomCompilerOutput[0].compiler_version.join("."),
signals: [],
};

const template = this._findTemplateForCircuit(ast.circomCompilerOutput, circuitArtifact.circuitName);
const templateArgs = this.getTemplateArgs(ast.circomCompilerOutput[0].main_component![1].Call.args, template.args);

for (const statement of template.body.Block.stmts) {
if (
!statement.InitializationBlock ||
!this._validateInitializationBlock(ast.sourcePath, statement.InitializationBlock) ||
statement.InitializationBlock.xtype.Signal[0] === SignalTypeNames.Intermediate
) {
continue;
}

const dimensions = this.resolveDimension(statement.InitializationBlock.initializations[0].Declaration.dimensions);
const resolvedDimensions = dimensions.map((dimension: any) => {
if (typeof dimension === "string") {
const templateArg = templateArgs[dimension];

if (!templateArg) {
throw new Error(
`The template argument ${dimension} is missing in the circuit ${circuitArtifact.circuitName}`,
);
}

return Number(templateArg);
}

return Number(dimension);
});

const signal: Signal = {
type: statement.InitializationBlock.xtype.Signal[0] as SignalType,
internalType: this._getInternalType(statement.InitializationBlock.initializations[0].Declaration),
visibility: this._getSignalVisibility(ast.circomCompilerOutput[0], statement),
name: statement.InitializationBlock.initializations[0].Declaration.name,
dimensions: resolvedDimensions,
public async getCircuitArtifact(pathToTheAST: string): Promise<Result<CircuitArtifact>> {
try {
return {
data: await this._extractArtifact(pathToTheAST),
error: null,
};
} catch (error: any) {
return {
data: this._getDefaultArtifact(pathToTheAST, CircuitArtifactGenerator.DEFAULT_CIRCUIT_FORMAT),
error: error!.message,
};

circuitArtifact.signals.push(signal);
}

return circuitArtifact;
}

/**
* Returns the template arguments for the circuit.
*
* @param {string[]} args - The arguments of the template.
* @param {any[]} names - The names of the arguments.
* @returns {Record<string, bigint>} The template arguments for the circuit.
*/
private getTemplateArgs(args: string[], names: any[]): Record<string, bigint> {
if (args.length === 0) {
return {};
Expand All @@ -153,6 +129,9 @@ export default class CircuitArtifactGenerator {
return result;
}

/**
* Resolves the variable from
*/
private resolveVariable(variableObj: any) {
if (!variableObj || !variableObj.name) {
throw new Error(`The argument ${variableObj} is not a variable`);
Expand All @@ -161,6 +140,9 @@ export default class CircuitArtifactGenerator {
return variableObj.name;
}

/**
* Resolves the number from the AST.
*/
private resolveNumber(numberObj: any) {
if (!numberObj || !numberObj.length || numberObj.length < 2) {
throw new Error(`The argument ${numberObj} is not a number`);
Expand All @@ -179,6 +161,9 @@ export default class CircuitArtifactGenerator {
return actualArg[0];
}

/**
* Resolves the dimensions of the signal.
*/
private resolveDimension(dimensions: number[]): number[] {
const result: number[] = [];

Expand Down Expand Up @@ -215,7 +200,7 @@ export default class CircuitArtifactGenerator {
* Cleans the artifacts directory by removing all files and subdirectories.
*/
public cleanArtifacts(): void {
const artifactsDir = path.join(this._projectRoot, this.getOutputArtifactsDir());
const artifactsDir = this.getOutputArtifactsDir();

if (fs.existsSync(artifactsDir)) {
fs.rmSync(artifactsDir, { recursive: true, force: true });
Expand All @@ -230,7 +215,7 @@ export default class CircuitArtifactGenerator {
*/
private _saveArtifact(artifact: CircuitArtifact, commonPath: string = ""): void {
const circuitArtifactPath = path
.join(this._projectRoot, this.getOutputArtifactsDir(), artifact.sourceName.replace(commonPath, ""))
.join(this.getOutputArtifactsDir(), artifact.sourceName.replace(commonPath, ""))
.replace(path.extname(artifact.sourceName), ".json");

fs.mkdirSync(circuitArtifactPath.replace(path.basename(circuitArtifactPath), ""), { recursive: true });
Expand Down Expand Up @@ -304,6 +289,90 @@ export default class CircuitArtifactGenerator {
throw new Error(`The template for the circuit ${circuitName} could not be found.`);
}

/**
* Extracts the artifact information from the AST JSON file.
*
* @param {string} pathToTheAST - The path to the AST JSON file.
* @returns {Promise<CircuitArtifact>} A promise that resolves to the extracted circuit artifact.
*/
private async _extractArtifact(pathToTheAST: string): Promise<CircuitArtifact> {
const ast: CircuitAST | undefined = JSON.parse(fs.readFileSync(pathToTheAST, "utf-8"));

if (!ast) {
throw new Error(`The circuit AST is missing. Path: ${pathToTheAST}`);
}

const circuitArtifact: CircuitArtifact = this._getDefaultArtifact(pathToTheAST);

const template = this._findTemplateForCircuit(ast.circomCompilerOutput, circuitArtifact.circuitName);
const templateArgs = this.getTemplateArgs(ast.circomCompilerOutput[0].main_component![1].Call.args, template.args);

for (const statement of template.body.Block.stmts) {
if (
!statement.InitializationBlock ||
!this._validateInitializationBlock(ast.sourcePath, statement.InitializationBlock) ||
statement.InitializationBlock.xtype.Signal[0] === SignalTypeNames.Intermediate
) {
continue;
}

const dimensions = this.resolveDimension(statement.InitializationBlock.initializations[0].Declaration.dimensions);
const resolvedDimensions = dimensions.map((dimension: any) => {
if (typeof dimension === "string") {
const templateArg = templateArgs[dimension];

if (!templateArg) {
throw new Error(
`The template argument ${dimension} is missing in the circuit ${circuitArtifact.circuitName}`,
);
}

return Number(templateArg);
}

return Number(dimension);
});

const signal: Signal = {
type: statement.InitializationBlock.xtype.Signal[0] as SignalType,
internalType: this._getInternalType(statement.InitializationBlock.initializations[0].Declaration),
visibility: this._getSignalVisibility(ast.circomCompilerOutput[0], statement),
name: statement.InitializationBlock.initializations[0].Declaration.name,
dimensions: resolvedDimensions,
};

circuitArtifact.signals.push(signal);
}

return circuitArtifact;
}

/**
* Creates a default circuit artifact.
*
* @param {string} pathToTheAST - The path to the AST JSON file.
* @param {string} format - The format of the circuit artifact.
* @returns {CircuitArtifact} The default circuit artifact.
*/
private _getDefaultArtifact(pathToTheAST: string, format?: string): CircuitArtifact {
const ast: CircuitAST | undefined = JSON.parse(fs.readFileSync(pathToTheAST, "utf-8"));

if (!ast) {
throw new Error(`The circuit AST is missing. Path: ${pathToTheAST}`);
}

this._validateCircuitAST(ast);

return {
_format: format ?? CircuitArtifactGenerator.CURRENT_FORMAT,
circuitName: ast.circomCompilerOutput[0].main_component![1].Call.id,
sourceName: ast.sourcePath,
basePath: this._circuitArtifactGeneratorConfig.basePath,
compilerVersion: ast.circomCompilerOutput[0].compiler_version.join("."),
signals: [],
};
}

/**
* Validates the AST of a circuit to ensure it meets the expected structure.
*
Expand Down
Loading

0 comments on commit eeaaaf3

Please sign in to comment.