Skip to content
New issue

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

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

Already on GitHub? # to your account

feat(acir-simulator): advanced debug formatting for noir + acir simulator #775

Merged
merged 8 commits into from
Jun 8, 2023
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
74 changes: 74 additions & 0 deletions yarn-project/acir-simulator/src/client/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ACVMField } from '../acvm/index.js';

/**
* Convert an array of ACVMFields to a string.
*
* @param msg - array of ACVMFields where each represents a single ascii character
* @returns string representation of the message
*/
function acvmFieldMessageToString(msg: ACVMField[]): string {
let msgStr = '';
for (const msgChar of msg) {
const asciiCode = Number(msgChar);
const asciiChar = String.fromCharCode(asciiCode);
msgStr = msgStr.concat(asciiChar);
}
// cut off string in case of preemptive null termination
const nullCharIndex = msgStr.indexOf('\\0');
if (nullCharIndex >= 0) {
msgStr = msgStr.substring(0, nullCharIndex);
}
return msgStr.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
}

/**
* Format a debug string for Noir filling in `'{0}'` entries with their
* corresponding values from the args array.
*
* @param formatStr - str of form `'this is a string with some entries like {0} and {1}'`
* @param args - array of fields to fill in the string format entries with
* @returns formatted string
*/
function applyStringFormatting(formatStr: string, args: ACVMField[]): string {
const matches = formatStr.match(/{\d+}/g);
if (matches == null) {
return formatStr;
}
// Get the numeric values within the curly braces, convert them to numbers,
// and find the maximum value.
const maxIndex = Math.max(...matches.map(match => Number(match.slice(1, -1))));
const argsPadded = args.concat(Array.from({ length: Math.max(0, maxIndex - args.length) }, () => '0xBAD'));

return formatStr.replace(/{(\d+)}/g, function (match, index) {
return typeof args[index] != 'undefined' ? argsPadded[index] : match;
});
}

/**
* Convert an array of ACVMFields from ACVM to a formatted string.
*
* @param fields - either a single field to be printed, or a string to be formatted.
* When it is a string to be formatted:
* The last entry in `fields` is `numArgs` (the number of formatting
* args). The `formatArgs` occupy the end of the `fields` array,
* excluding that last entry (`numArgs`). The message string `msg`
* takes up the remaining entries at the start of the `fields` array.
*
* @returns formatted string
*/
export function fieldsToFormattedStr(fields: ACVMField[]): string {
if (fields.length === 1) {
return `${fields[0]}`;
} else {
const numArgs = Number(fields[fields.length - 1]);
const msgLen = fields.length - 1 - numArgs;

const msgFields = fields.slice(0, msgLen);
const formatArgs = fields.slice(msgLen, fields.length - 1);

const msg = acvmFieldMessageToString(msgFields);
const formattedMsg = applyStringFormatting(msg, formatArgs);

return formattedMsg;
}
}
6 changes: 3 additions & 3 deletions yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
toAcvmEnqueuePublicFunctionResult,
} from '../acvm/index.js';
import { sizeOfType } from '../index.js';
import { fieldsToFormattedStr } from './debug.js';
import { ClientTxExecutionContext } from './client_execution_context.js';
import { Tuple, assertLength } from '@aztec/foundation/serialize';

Expand Down Expand Up @@ -173,9 +174,8 @@ export class PrivateFunctionExecution {
},
getL1ToL2Message: ([msgKey]: ACVMField[]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),

debugLog: ([data]: ACVMField[]) => {
// eslint-disable-next-line
console.log(data);
debugLog: (fields: ACVMField[]) => {
this.log(fieldsToFormattedStr(fields));
return Promise.resolve([ZERO_ACVM_FIELD]);
},
enqueuePublicFunctionCall: async ([acvmContractAddress, acvmFunctionSelector, ...acvmArgs]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ClientTxExecutionContext } from './client_execution_context.js';
import { select_return_flattened as selectReturnFlattened } from '@noir-lang/noir_util_wasm';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { fieldsToFormattedStr } from './debug.js';

const notAvailable = () => {
return Promise.reject(new Error(`Not available for unconstrained function execution`));
Expand Down Expand Up @@ -55,9 +56,8 @@ export class UnconstrainedFunctionExecution {
frToNumber(fromACVMField(acvmLimit)),
frToNumber(fromACVMField(acvmOffset)),
),
debugLog: ([data]: ACVMField[]) => {
// eslint-disable-next-line
console.log(data);
debugLog: (fields: ACVMField[]) => {
this.log(fieldsToFormattedStr(fields));
return Promise.resolve([ZERO_ACVM_FIELD]);
},
getL1ToL2Message: ([msgKey]: ACVMField[]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@

// Utility function to console.log data in the acir simulator
// WARNING: sometimes when using debug logs the ACVM errors with: `thrown: "solver opcode resolution error: cannot solve opcode: expression has too many unknowns x155"`

#[oracle(debugLog)]
fn debug_log_oracle(_msg: Field) -> Field {}
fn debug_log_oracle<T, N>(_msg: T, _num_args: Field) -> Field {}
#[oracle(debugLog)]
fn debug_log_format_oracle<T, N>(_msg: T, _args: [Field; N], _num_args: Field) -> Field {}
#[oracle(debugLog)]
fn debug_log_field_oracle(_field: Field) -> Field {}

/// NOTE: call this with a str<N> msg of length > 1
/// Example:
/// `debug_log("blah blah this is a debug string");`
unconstrained fn debug_log<T>(msg: T) {
constrain debug_log_oracle(msg, 0) == 0;
}

/// NOTE: call this with a str<N> msg of form
/// "some string with {0} and {1} ... {N}"
/// and an array of N field which will be formatted
/// into the string in the simulator.
/// Example:
/// `debug_log_format("get_2(slot:{0}) =>\n\t0:{1}\n\t1:{2}", [storage_slot, notes.0.value, notes.1.value]);`
unconstrained fn debug_log_format<T, N>(msg: T, args: [Field; N]) {
constrain debug_log_format_oracle(msg, args, args.len()) == 0;
}

unconstrained fn debug_log(msg: Field) -> Field {
debug_log_oracle(msg)
}
/// Example:
/// `debug_log_field(my_field);`
unconstrained fn debug_log_field(field: Field) {
constrain debug_log_field_oracle(field) == 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl Set {
constrain note_getter_data.1.root == inputs.old_private_data_tree_root;

let notes = (note_getter_data.0.note, note_getter_data.1.note);

(context, notes)
}

Expand Down