Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error messages #11

Merged
merged 5 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [v0.2.7]

* Improved error messages during the artifact extraction process

## [v0.2.4-v0.2.6]

* Added ability to recover after error during artifact extraction
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.6",
"version": "0.2.7",
"description": "Unleash TypeScript bindings for Circom circuits",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
114 changes: 84 additions & 30 deletions src/core/CircuitArtifactGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
ArtifactGeneratorConfig,
} from "../types";

import { ASTParserError } from "../errors";
import { ErrorObj } from "../errors/common";

/**
* `CircuitArtifactGenerator` is responsible for generating circuit artifacts based on the AST files.
*
Expand Down Expand Up @@ -51,10 +54,10 @@ export default class CircuitArtifactGenerator {
/**
* Generates circuit artifacts based on the ASTs.
*/
public async generateCircuitArtifacts(): Promise<string[]> {
public async generateCircuitArtifacts(): Promise<ErrorObj[]> {
const astFilePaths = this._circuitArtifactGeneratorConfig.circuitsASTPaths;

const errors: string[] = [];
const errors: ErrorObj[] = [];

for (const astFilePath of astFilePaths) {
const circuitArtifact = await this.getCircuitArtifact(astFilePath);
Expand Down Expand Up @@ -98,22 +101,23 @@ export default class CircuitArtifactGenerator {
data: await this._extractArtifact(pathToTheAST),
error: null,
};
} catch (error: any) {
} catch (error: any | ASTParserError) {
return {
data: this._getDefaultArtifact(pathToTheAST, CircuitArtifactGenerator.DEFAULT_CIRCUIT_FORMAT),
error: error!.message,
error: error,
};
}
}

/**
* Returns the template arguments for the circuit.
*
* @param circuitArtifact - The circuit artifact.
* @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> {
private getTemplateArgs(circuitArtifact: CircuitArtifact, args: string[], names: any[]): Record<string, bigint> {
if (args.length === 0) {
return {};
}
Expand All @@ -123,7 +127,7 @@ export default class CircuitArtifactGenerator {
for (let i = 0; i < args.length; i++) {
const argObj = (args[i] as any)["Number"];

result[names[i]] = BigInt(this.resolveNumber(argObj));
result[names[i]] = BigInt(this.resolveNumber(circuitArtifact, argObj));
}

return result;
Expand All @@ -132,9 +136,13 @@ export default class CircuitArtifactGenerator {
/**
* Resolves the variable from
*/
private resolveVariable(variableObj: any) {
private resolveVariable(circuitArtifact: CircuitArtifact, variableObj: any) {
if (!variableObj || !variableObj.name) {
throw new Error(`The argument ${variableObj} is not a variable`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The argument is not a variable`,
variableObj,
);
}

return variableObj.name;
Expand All @@ -143,19 +151,27 @@ export default class CircuitArtifactGenerator {
/**
* Resolves the number from the AST.
*/
private resolveNumber(numberObj: any) {
private resolveNumber(circuitArtifact: CircuitArtifact, numberObj: any) {
if (!numberObj || !numberObj.length || numberObj.length < 2) {
throw new Error(`The argument ${numberObj} is not a number`);
throw new ASTParserError(this._getCircuitFullName(circuitArtifact), `The argument is not a number`, numberObj);
}

if (!numberObj[1] || !numberObj[1].length || numberObj[1].length < 2) {
throw new Error(`The argument ${numberObj} is of unexpected format`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The argument is of unexpected format`,
numberObj,
);
}

const actualArg = numberObj[1][1];

if (!actualArg || !actualArg.length || numberObj[1].length < 1) {
throw new Error(`The argument ${numberObj} is of unexpected format`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The argument is of unexpected format`,
actualArg,
);
}

return actualArg[0];
Expand All @@ -164,7 +180,7 @@ export default class CircuitArtifactGenerator {
/**
* Resolves the dimensions of the signal.
*/
private resolveDimension(dimensions: number[]): number[] {
private resolveDimension(circuitArtifact: CircuitArtifact, dimensions: number[]): number[] {
const result: number[] = [];

for (const dimension of dimensions) {
Expand All @@ -181,16 +197,20 @@ export default class CircuitArtifactGenerator {
(numberObj !== undefined && variableObj !== undefined) ||
(numberObj === undefined && variableObj === undefined)
) {
throw new Error(`The dimension ${dimension} is of unexpected format`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The dimension is of unexpected format`,
dimension,
);
}

if (numberObj) {
result.push(this.resolveNumber(numberObj));
result.push(this.resolveNumber(circuitArtifact, numberObj));

continue;
}

result.push(this.resolveVariable(variableObj));
result.push(this.resolveVariable(circuitArtifact, variableObj));
}

return result;
Expand Down Expand Up @@ -265,11 +285,11 @@ export default class CircuitArtifactGenerator {
/**
* Finds the template for the circuit based on the circuit name.
*
* @param {CircuitArtifact} circuitArtifact - The circuit artifact.
* @param {CircomCompilerOutput[]} compilerOutputs - The compiler outputs of the circuit.
* @param {string} circuitName - The name of the circuit.
* @returns {Template} The template for the circuit.
*/
private _findTemplateForCircuit(compilerOutputs: CircomCompilerOutput[], circuitName: string): Template {
private _findTemplateForCircuit(circuitArtifact: CircuitArtifact, compilerOutputs: CircomCompilerOutput[]): Template {
for (const compilerOutput of compilerOutputs) {
if (!compilerOutput.definitions || compilerOutput.definitions.length < 1) {
continue;
Expand All @@ -280,13 +300,17 @@ export default class CircuitArtifactGenerator {
continue;
}

if (definition.Template.name === circuitName) {
if (definition.Template.name === circuitArtifact.circuitName) {
return definition.Template;
}
}
}

throw new Error(`The template for the circuit ${circuitName} could not be found.`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The template for the circuit could not be found.`,
undefined,
);
}

/**
Expand All @@ -304,26 +328,35 @@ export default class CircuitArtifactGenerator {

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);
const template = this._findTemplateForCircuit(circuitArtifact, ast.circomCompilerOutput);
const templateArgs = this.getTemplateArgs(
circuitArtifact,
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) ||
!this._validateInitializationBlock(circuitArtifact, ast.sourcePath, statement.InitializationBlock) ||
statement.InitializationBlock.xtype.Signal[0] === SignalTypeNames.Intermediate
) {
continue;
}

const dimensions = this.resolveDimension(statement.InitializationBlock.initializations[0].Declaration.dimensions);
const dimensions = this.resolveDimension(
circuitArtifact,
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}`,
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The template argument is missing in the circuit ${circuitArtifact.circuitName}`,
dimension,
);
}

Expand Down Expand Up @@ -382,7 +415,7 @@ export default class CircuitArtifactGenerator {
*/
private _validateCircuitAST(ast: CircuitAST): void {
if (!ast.circomCompilerOutput) {
throw new Error(`The circomCompilerOutput field is missing in the circuit AST`);
throw new Error(`The circomCompilerOutput field is missing in the circuit AST: ${ast.sourcePath}`);
}

if (
Expand All @@ -406,15 +439,24 @@ export default class CircuitArtifactGenerator {
/**
* Validates the initialization block in the circuit AST.
*
* @param {CircuitArtifact} circuitArtifact - The default circuit artifact.
* @param {string} astSourcePath - The source path of the AST.
* @param {any} initializationBlock - The initialization block to be validated.
*
* @returns {boolean} Returns `true` if the initialization block is valid, `false` otherwise.
* @throws {Error} If the initialization block is missing required fields.
*/
private _validateInitializationBlock(astSourcePath: string, initializationBlock: any): boolean {
private _validateInitializationBlock(
circuitArtifact: CircuitArtifact,
astSourcePath: string,
initializationBlock: any,
): boolean {
if (!initializationBlock.xtype) {
throw new Error(`The initialization block xtype is missing in the circuit AST: ${astSourcePath}`);
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The initialization block xtype is missing in the circuit AST`,
initializationBlock,
);
}

if (
Expand All @@ -423,8 +465,10 @@ export default class CircuitArtifactGenerator {
!initializationBlock.initializations[0].Declaration ||
!initializationBlock.initializations[0].Declaration.name
) {
throw new Error(
throw new ASTParserError(
this._getCircuitFullName(circuitArtifact),
`The initializations field of initialization block is missing or incomplete in the circuit AST: ${astSourcePath}`,
initializationBlock,
);
}

Expand All @@ -434,4 +478,14 @@ export default class CircuitArtifactGenerator {

return true;
}

/**
* Returns the full name of the circuit.
*
* @param {CircuitArtifact} artifact - The circuit artifact.
* @returns {string} The full name of the circuit.
*/
private _getCircuitFullName(artifact: CircuitArtifact): string {
return `${artifact.sourceName}:${artifact.circuitName}`;
}
}
3 changes: 2 additions & 1 deletion src/core/CircuitTypesGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CircuitArtifactGenerator from "./CircuitArtifactGenerator";
import { normalizeName } from "../utils";

import { CircuitArtifact, ArtifactWithPath } from "../types";
import { ErrorObj } from "../errors/common";

/**
* `CircuitTypesGenerator` is need for generating TypeScript bindings based on circuit artifacts.
Expand Down Expand Up @@ -55,7 +56,7 @@ export class CircuitTypesGenerator extends ZkitTSGenerator {
*
* @returns {Promise<void>} A promise that resolves when all types have been generated.
*/
public async generateTypes(): Promise<string[]> {
public async generateTypes(): Promise<ErrorObj[]> {
const errorsWhenGenArtifacts = await this._artifactsGenerator.generateCircuitArtifacts();

const circuitArtifacts = this._fetchCircuitArtifacts();
Expand Down
19 changes: 19 additions & 0 deletions src/errors/ParseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface ASTParserErrorDetails {
circuitFullNames: string;
message: string;
context: any;
}

export class ASTParserError extends Error {
public error: ASTParserErrorDetails;

constructor(fullName: string, message: string, context: any) {
super();
this.message = message;
this.error = {
circuitFullNames: fullName,
message,
context,
};
}
}
3 changes: 3 additions & 0 deletions src/errors/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ASTParserError } from "./ParseError";

export type ErrorObj = { message: string } | ASTParserError | null;
2 changes: 2 additions & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./common";
export * from "./ParseError";
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from "./types";
export * from "./constants";

export * from "./core/CircuitTypesGenerator";

export * from "./errors";
4 changes: 3 additions & 1 deletion src/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ASTParserError } from "../errors";

export interface Result<T> {
data: T;
error: string | null;
error: { message: string } | ASTParserError | null;
}
8 changes: 3 additions & 5 deletions test/CircuitArtifactGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("Circuit Artifact Generation", function () {
it("should return an error if the name in the initialization block is missing", async function () {
const result = await artifactGenerator.getCircuitArtifact("test/mocks/InvalidInitializationBlockName.json");

expect(result.error).to.be.equal(
expect(result.error!.message).to.be.equal(
"The initializations field of initialization block is missing or incomplete in the circuit AST: test/fixture/InvalidInitializationBlockName.circom",
);
});
Expand All @@ -86,14 +86,12 @@ describe("Circuit Artifact Generation", function () {
it("should return an error if the template block is missing", async function () {
const result = await artifactGenerator.getCircuitArtifact("test/mocks/InvalidTemplateBlock.json");

expect(result.error).to.be.equal("The template for the circuit Multiplier2 could not be found.");
expect(result.error!.message).to.be.equal("The template for the circuit could not be found.");
});

it("should return an error if the xtype of the initialization block is missing", async function () {
const result = await artifactGenerator.getCircuitArtifact("test/mocks/InvalidXTypeField.json");

expect(result.error).to.be.equal(
"The initialization block xtype is missing in the circuit AST: test/fixture/InvalidXTypeField.circom",
);
expect(result.error!.message).to.be.equal("The initialization block xtype is missing in the circuit AST");
});
});
Loading