diff --git a/yarn-project/noir-protocol-circuits-types/src/scripts/cleanup_artifacts.ts b/yarn-project/noir-protocol-circuits-types/src/scripts/cleanup_artifacts.ts index 1317b093ee60..2981673277cf 100644 --- a/yarn-project/noir-protocol-circuits-types/src/scripts/cleanup_artifacts.ts +++ b/yarn-project/noir-protocol-circuits-types/src/scripts/cleanup_artifacts.ts @@ -11,7 +11,7 @@ async function cleanupArtifacts(target: string) { } const fileData = JSON.parse((await readFile(join(target, file), 'utf8')).toString()); fileData.file_map = {}; - fileData.debug_symbols = {}; + fileData.debug_symbols = ''; await writeFile(join(target, file), JSON.stringify(fileData)); } } diff --git a/yarn-project/simulator/src/client.ts b/yarn-project/simulator/src/client.ts index 3675767b2856..e156792b24e0 100644 --- a/yarn-project/simulator/src/client.ts +++ b/yarn-project/simulator/src/client.ts @@ -1,4 +1,4 @@ export * from './private/index.js'; export { WASMSimulator } from './private/providers/acvm_wasm.js'; -export { type SimulationProvider } from './private/providers/simulation_provider.js'; +export { type SimulationProvider, type DecodedError } from './private/providers/simulation_provider.js'; export * from './common/index.js'; diff --git a/yarn-project/simulator/src/private/providers/acvm_wasm.ts b/yarn-project/simulator/src/private/providers/acvm_wasm.ts index 70ec0d2ad428..5327ebd70115 100644 --- a/yarn-project/simulator/src/private/providers/acvm_wasm.ts +++ b/yarn-project/simulator/src/private/providers/acvm_wasm.ts @@ -7,7 +7,7 @@ import type { NoirCompiledCircuit } from '@aztec/stdlib/noir'; import { type ACIRCallback, acvm } from '../acvm/acvm.js'; import type { ACVMWitness } from '../acvm/acvm_types.js'; -import { type SimulationProvider, parseErrorPayload } from './simulation_provider.js'; +import { type SimulationProvider, enrichNoirError } from './simulation_provider.js'; export class WASMSimulator implements SimulationProvider { constructor(protected log = createLogger('wasm-simulator')) {} @@ -42,7 +42,7 @@ export class WASMSimulator implements SimulationProvider { } catch (err) { // Typescript types catched errors as unknown or any, so we need to narrow its type to check if it has raw assertion payload. if (typeof err === 'object' && err !== null && 'rawAssertionPayload' in err) { - const parsed = parseErrorPayload(compiledCircuit.abi, err as ExecutionError); + const parsed = enrichNoirError(compiledCircuit, err as ExecutionError); this.log.debug('execution failed', { hash: compiledCircuit.hash, error: parsed, diff --git a/yarn-project/simulator/src/private/providers/acvm_wasm_with_blobs.ts b/yarn-project/simulator/src/private/providers/acvm_wasm_with_blobs.ts index 7245264ba518..dd17a8da792c 100644 --- a/yarn-project/simulator/src/private/providers/acvm_wasm_with_blobs.ts +++ b/yarn-project/simulator/src/private/providers/acvm_wasm_with_blobs.ts @@ -5,7 +5,7 @@ import type { NoirCompiledCircuit } from '@aztec/stdlib/noir'; import type { ACIRCallback, ACIRExecutionResult } from '../acvm/acvm.js'; import type { ACVMWitness } from '../acvm/acvm_types.js'; -import { type SimulationProvider, parseErrorPayload } from './simulation_provider.js'; +import { type SimulationProvider, enrichNoirError } from './simulation_provider.js'; /** * A simulation provider that uses the WASM simulator with the ability to handle blobs via the foreign call handler. @@ -33,7 +33,7 @@ export class WASMSimulatorWithBlobs implements SimulationProvider { } catch (err) { // Typescript types catched errors as unknown or any, so we need to narrow its type to check if it has raw assertion payload. if (typeof err === 'object' && err !== null && 'rawAssertionPayload' in err) { - throw parseErrorPayload(compiledCircuit.abi, err as ExecutionError); + throw enrichNoirError(compiledCircuit, err as ExecutionError); } throw new Error(`Circuit execution failed: ${err}`); } diff --git a/yarn-project/simulator/src/private/providers/simulation_provider.ts b/yarn-project/simulator/src/private/providers/simulation_provider.ts index 5ec7229d3e7b..be5d32f42728 100644 --- a/yarn-project/simulator/src/private/providers/simulation_provider.ts +++ b/yarn-project/simulator/src/private/providers/simulation_provider.ts @@ -1,9 +1,10 @@ import type { ExecutionError } from '@aztec/noir-acvm_js'; import { abiDecodeError } from '@aztec/noir-noirc_abi'; -import type { Abi, WitnessMap } from '@aztec/noir-types'; +import type { WitnessMap } from '@aztec/noir-types'; +import { parseDebugSymbols } from '@aztec/stdlib/abi'; import type { NoirCompiledCircuit } from '@aztec/stdlib/noir'; -import type { ACIRCallback, ACIRExecutionResult } from '../acvm/acvm.js'; +import { type ACIRCallback, type ACIRExecutionResult, extractCallStack } from '../acvm/acvm.js'; import type { ACVMWitness } from '../acvm/acvm_types.js'; /** @@ -14,20 +15,20 @@ export interface SimulationProvider { executeUserCircuit(acir: Buffer, initialWitness: ACVMWitness, callback: ACIRCallback): Promise; } -export type ErrorWithPayload = ExecutionError & { decodedAssertionPayload?: any }; +export type DecodedError = ExecutionError & { decodedAssertionPayload?: any; noirCallStack?: string[] }; -// Error handling taken from noir/noir-repo/tooling/noir_js/src/witness_generation.ts. +// Payload parsing taken from noir/noir-repo/tooling/noir_js/src/witness_generation.ts. // TODO: import this in isolation without having to import noir_js in its entirety. -export function parseErrorPayload(abi: Abi, originalError: ExecutionError): Error { +export function enrichNoirError(artifact: NoirCompiledCircuit, originalError: ExecutionError): DecodedError { const payload = originalError.rawAssertionPayload; if (!payload) { return originalError; } - const enrichedError = originalError as ErrorWithPayload; + const enrichedError = originalError as DecodedError; try { // Decode the payload - const decodedPayload = abiDecodeError(abi, payload); + const decodedPayload = abiDecodeError(artifact.abi, payload); if (typeof decodedPayload === 'string') { // If it's a string, just add it to the error message @@ -40,5 +41,26 @@ export function parseErrorPayload(abi: Abi, originalError: ExecutionError): Erro // Ignore errors decoding the payload } + try { + // Decode the callstack + const callStack = extractCallStack(originalError, { + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/5813) + // We only support handling debug info for the circuit entry point. + // So for now we simply index into the first debug info. + debugSymbols: parseDebugSymbols(artifact.debug_symbols)[0], + files: artifact.file_map, + }); + + enrichedError.noirCallStack = callStack?.map(errorLocation => { + if (typeof errorLocation === 'string') { + return `at opcode ${errorLocation}`; + } else { + return `at ${errorLocation.locationText} (${errorLocation.filePath}:${errorLocation.line}:${errorLocation.column})`; + } + }); + } catch (_errorResolving) { + // Ignore errors resolving the callstack + } + return enrichedError; } diff --git a/yarn-project/stdlib/src/abi/abi.ts b/yarn-project/stdlib/src/abi/abi.ts index 4e3199f9be03..ecec5fd16e5e 100644 --- a/yarn-project/stdlib/src/abi/abi.ts +++ b/yarn-project/stdlib/src/abi/abi.ts @@ -419,6 +419,10 @@ export async function getFunctionArtifact( return { ...functionArtifact, debug: debugMetadata }; } +export function parseDebugSymbols(debugSymbols: string): DebugInfo[] { + return JSON.parse(inflate(Buffer.from(debugSymbols, 'base64'), { to: 'string', raw: true })).debug_infos; +} + /** * Gets the debug metadata of a given function from the contract artifact * @param artifact - The contract build artifact @@ -432,14 +436,12 @@ export function getFunctionDebugMetadata( try { if (functionArtifact.debugSymbols && contractArtifact.fileMap) { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/10546) investigate why debugMetadata is so big for some tests. - const programDebugSymbols = JSON.parse( - inflate(Buffer.from(functionArtifact.debugSymbols, 'base64'), { to: 'string', raw: true }), - ); + const programDebugSymbols = parseDebugSymbols(functionArtifact.debugSymbols); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/5813) // We only support handling debug info for the contract function entry point. // So for now we simply index into the first debug info. return { - debugSymbols: programDebugSymbols.debug_infos[0], + debugSymbols: programDebugSymbols[0], files: contractArtifact.fileMap, }; }