Skip to content

Commit 852ab3c

Browse files
committed
Add option to cleanly output HDP module debug prints
1 parent 5a64e65 commit 852ab3c

5 files changed

Lines changed: 140 additions & 12 deletions

File tree

crates/dry_hint_processor/src/lib.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub mod syscall_handler;
1010
use std::{any::Any, collections::HashMap};
1111

1212
use ::syscall_handler::SyscallHandlerWrapper;
13-
use cairo_lang_casm::hints::{Hint, StarknetHint};
13+
use cairo_lang_casm::hints::{CoreHint, CoreHintBase, Hint, StarknetHint};
1414
use cairo_vm::{
1515
hint_processor::{
1616
builtin_hint_processor::builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData},
@@ -44,16 +44,18 @@ pub struct CustomHintProcessor {
4444
cairo1_builtin_hint_proc: Cairo1HintProcessor,
4545
hints: HashMap<String, HintImpl>,
4646
extensive_hints: HashMap<String, ExtensiveHintImpl>,
47+
pretty_output: bool,
4748
}
4849

4950
impl CustomHintProcessor {
50-
pub fn new(inputs: HDPDryRunInput) -> Self {
51+
pub fn new(inputs: HDPDryRunInput, pretty_output: bool) -> Self {
5152
Self {
5253
inputs,
5354
builtin_hint_proc: BuiltinHintProcessor::new_empty(),
5455
cairo1_builtin_hint_proc: Cairo1HintProcessor::new(Default::default(), Default::default(), true),
5556
hints: Self::hints(),
5657
extensive_hints: Self::extensive_hints(),
58+
pretty_output,
5759
}
5860
}
5961

@@ -139,6 +141,29 @@ impl HintProcessorLogic for CustomHintProcessor {
139141
.map(|_| HintExtension::default())
140142
})
141143
});
144+
} else if self.pretty_output {
145+
if let Hint::Core(CoreHintBase::Core(CoreHint::DebugPrint { start, end })) = hint {
146+
let start_ptr = get_ptr_from_res_operand(vm, start)?;
147+
let end_ptr = get_ptr_from_res_operand(vm, end)?;
148+
let len = (end_ptr - start_ptr)
149+
.map_err(|_| HintError::CustomHint("DebugPrint: invalid range".into()))?;
150+
if len > 0 {
151+
let felts: Vec<Felt252> = vm
152+
.get_integer_range(start_ptr, len)?
153+
.into_iter()
154+
.map(|f| (*f.as_ref()))
155+
.collect();
156+
let text = pretty_debug_text(&felts);
157+
if !text.is_empty() {
158+
println!("{}", text);
159+
}
160+
}
161+
return Ok(HintExtension::default());
162+
}
163+
return self
164+
.cairo1_builtin_hint_proc
165+
.execute(vm, exec_scopes, hint)
166+
.map(|_| HintExtension::default());
142167
} else {
143168
return self
144169
.cairo1_builtin_hint_proc
@@ -152,3 +177,33 @@ impl HintProcessorLogic for CustomHintProcessor {
152177
}
153178

154179
impl ResourceTracker for CustomHintProcessor {}
180+
181+
/// Reconstruct a clean debug message from a DebugPrint felt range.
182+
/// Each felt is tried as a Cairo short string (up to 31 ASCII bytes);
183+
/// printable fragments are concatenated into a single line.
184+
fn pretty_debug_text(felts: &[Felt252]) -> String {
185+
let mut text = String::new();
186+
for value in felts {
187+
if let Some(s) = felt_as_short_string(value) {
188+
text.push_str(&s);
189+
}
190+
}
191+
text
192+
}
193+
194+
/// Decode a Felt252 as a Cairo short string (same logic as cairo-vm's
195+
/// `as_cairo_short_string`). Returns None if any byte is non-ASCII.
196+
fn felt_as_short_string(value: &Felt252) -> Option<String> {
197+
let mut result = String::new();
198+
let mut ended = false;
199+
for byte in value.to_bytes_be().into_iter().skip_while(|b| *b == 0) {
200+
if byte == 0 {
201+
ended = true;
202+
} else if ended || !byte.is_ascii() {
203+
return None;
204+
} else {
205+
result.push(byte as char);
206+
}
207+
}
208+
Some(result)
209+
}

crates/dry_run/src/lib.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,23 @@ pub struct Args {
5454
help = "Print program output to stdout [default: false]"
5555
)]
5656
pub print_output: bool,
57+
#[arg(
58+
long = "print_output_pretty",
59+
default_value_t = false,
60+
help = "Print clean debug messages (text only, no raw felt values) [default: false]"
61+
)]
62+
pub print_output_pretty: bool,
5763
#[structopt(long = "allow_missing_builtins")]
5864
pub allow_missing_builtins: Option<bool>,
5965
}
6066

6167
#[allow(clippy::type_complexity)]
6268
#[instrument(skip(input), fields(program = %program_path.display()))]
63-
pub fn run(program_path: PathBuf, input: HDPDryRunInput) -> Result<(DryRunSyscallHandler, HDPDryRunOutput), Error> {
69+
pub fn run(
70+
program_path: PathBuf,
71+
input: HDPDryRunInput,
72+
pretty_output: bool,
73+
) -> Result<(DryRunSyscallHandler, HDPDryRunOutput), Error> {
6474
info!("Starting dry run execution");
6575
debug!(params_count = input.params.len(), "Input parameters loaded");
6676
let cairo_run_config = cairo_run::CairoRunConfig {
@@ -77,7 +87,7 @@ pub fn run(program_path: PathBuf, input: HDPDryRunInput) -> Result<(DryRunSyscal
7787
})?;
7888
let program = Program::from_bytes(&program_file, Some(cairo_run_config.entrypoint))?;
7989

80-
let mut hint_processor = CustomHintProcessor::new(input);
90+
let mut hint_processor = CustomHintProcessor::new(input, pretty_output);
8191
let mut cairo_runner = cairo_run_program(&program, &cairo_run_config, &mut hint_processor).map_err(Box::new)?;
8292
let resources = cairo_runner
8393
.get_execution_resources()
@@ -160,9 +170,10 @@ pub async fn run_with_args(args: Args) -> Result<(), Error> {
160170
params,
161171
injected_state,
162172
},
173+
args.print_output_pretty,
163174
)?;
164175

165-
if args.print_output {
176+
if args.print_output || args.print_output_pretty {
166177
println!("{:#?}", output);
167178
}
168179

crates/sound_hint_processor/src/lib.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub mod syscall_handler;
1010
use std::{any::Any, collections::HashMap};
1111

1212
use ::syscall_handler::SyscallHandlerWrapper;
13-
use cairo_lang_casm::hints::{Hint, StarknetHint};
13+
use cairo_lang_casm::hints::{CoreHint, CoreHintBase, Hint, StarknetHint};
1414
use cairo_vm::{
1515
hint_processor::{
1616
builtin_hint_processor::builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData},
@@ -44,16 +44,18 @@ pub struct CustomHintProcessor {
4444
cairo1_builtin_hint_proc: Cairo1HintProcessor,
4545
hints: HashMap<String, HintImpl>,
4646
extensive_hints: HashMap<String, ExtensiveHintImpl>,
47+
pretty_output: bool,
4748
}
4849

4950
impl CustomHintProcessor {
50-
pub fn new(inputs: HDPInput) -> Self {
51+
pub fn new(inputs: HDPInput, pretty_output: bool) -> Self {
5152
Self {
5253
inputs,
5354
builtin_hint_proc: BuiltinHintProcessor::new_empty(),
5455
cairo1_builtin_hint_proc: Cairo1HintProcessor::new(Default::default(), Default::default(), true),
5556
hints: Self::hints(),
5657
extensive_hints: Self::extensive_hints(),
58+
pretty_output,
5759
}
5860
}
5961

@@ -139,6 +141,29 @@ impl HintProcessorLogic for CustomHintProcessor {
139141
.map(|_| HintExtension::default())
140142
})
141143
});
144+
} else if self.pretty_output {
145+
if let Hint::Core(CoreHintBase::Core(CoreHint::DebugPrint { start, end })) = hint {
146+
let start_ptr = get_ptr_from_res_operand(vm, start)?;
147+
let end_ptr = get_ptr_from_res_operand(vm, end)?;
148+
let len = (end_ptr - start_ptr)
149+
.map_err(|_| HintError::CustomHint("DebugPrint: invalid range".into()))?;
150+
if len > 0 {
151+
let felts: Vec<Felt252> = vm
152+
.get_integer_range(start_ptr, len)?
153+
.into_iter()
154+
.map(|f| (*f.as_ref()))
155+
.collect();
156+
let text = pretty_debug_text(&felts);
157+
if !text.is_empty() {
158+
println!("{}", text);
159+
}
160+
}
161+
return Ok(HintExtension::default());
162+
}
163+
return self
164+
.cairo1_builtin_hint_proc
165+
.execute(vm, exec_scopes, hint)
166+
.map(|_| HintExtension::default());
142167
} else {
143168
return self
144169
.cairo1_builtin_hint_proc
@@ -152,3 +177,28 @@ impl HintProcessorLogic for CustomHintProcessor {
152177
}
153178

154179
impl ResourceTracker for CustomHintProcessor {}
180+
181+
fn pretty_debug_text(felts: &[Felt252]) -> String {
182+
let mut text = String::new();
183+
for value in felts {
184+
if let Some(s) = felt_as_short_string(value) {
185+
text.push_str(&s);
186+
}
187+
}
188+
text
189+
}
190+
191+
fn felt_as_short_string(value: &Felt252) -> Option<String> {
192+
let mut result = String::new();
193+
let mut ended = false;
194+
for byte in value.to_bytes_be().into_iter().skip_while(|b| *b == 0) {
195+
if byte == 0 {
196+
ended = true;
197+
} else if ended || !byte.is_ascii() {
198+
return None;
199+
} else {
200+
result.push(byte as char);
201+
}
202+
}
203+
Some(result)
204+
}

crates/sound_run/src/lib.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ pub struct Args {
5353
help = "Print program output to stdout [default: false]"
5454
)]
5555
pub print_output: bool,
56+
#[arg(
57+
long = "print_output_pretty",
58+
default_value_t = false,
59+
help = "Print clean debug messages (text only, no raw felt values) [default: false]"
60+
)]
61+
pub print_output_pretty: bool,
5662
#[arg(long = "proof_mode", conflicts_with = "cairo_pie", help = "Configure runner in proof mode")]
5763
pub proof_mode: bool,
5864

@@ -75,7 +81,12 @@ pub struct Args {
7581
}
7682

7783
#[instrument(skip(input, cairo_run_config), fields(program = %program_path.display()))]
78-
pub fn run(program_path: PathBuf, cairo_run_config: CairoRunConfig, input: HDPInput) -> Result<(CairoRunner, HDPOutput), Error> {
84+
pub fn run(
85+
program_path: PathBuf,
86+
cairo_run_config: CairoRunConfig,
87+
input: HDPInput,
88+
pretty_output: bool,
89+
) -> Result<(CairoRunner, HDPOutput), Error> {
7990
debug!(
8091
chain_proofs = input.chain_proofs.len(),
8192
state_proofs = input.state_proofs.len(),
@@ -90,7 +101,7 @@ pub fn run(program_path: PathBuf, cairo_run_config: CairoRunConfig, input: HDPIn
90101
})?;
91102
let program = Program::from_bytes(&program_file, Some(cairo_run_config.entrypoint))?;
92103

93-
let mut hint_processor = CustomHintProcessor::new(input);
104+
let mut hint_processor = CustomHintProcessor::new(input, pretty_output);
94105
let mut cairo_runner = cairo_run_program(&program, &cairo_run_config, &mut hint_processor).map_err(Box::new)?;
95106
let resources = cairo_runner
96107
.get_execution_resources()
@@ -184,9 +195,10 @@ pub async fn run_with_args(args: Args) -> Result<(), Error> {
184195
injected_state,
185196
unconstrained: proofs_data.unconstrained,
186197
},
198+
args.print_output_pretty,
187199
)?;
188200

189-
if args.print_output {
201+
if args.print_output || args.print_output_pretty {
190202
println!("{:#?}", output);
191203
}
192204

tests/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ mod test_utils {
145145
.context("failed to initialize CairoRunner (dry_run)")?;
146146

147147
// Run the Cairo VM
148-
let mut hint_processor = dry_hint_processor::CustomHintProcessor::new(program_inputs);
148+
let mut hint_processor = dry_hint_processor::CustomHintProcessor::new(program_inputs, false);
149149
cairo_runner
150150
.run_until_pc(end, &mut hint_processor)
151151
.context("dry_run failed: Cairo VM execution failed")?;
@@ -229,7 +229,7 @@ mod test_utils {
229229
.context("failed to initialize CairoRunner (sound_run)")?;
230230

231231
// Run the Cairo VM
232-
let mut hint_processor = sound_hint_processor::CustomHintProcessor::new(program_inputs);
232+
let mut hint_processor = sound_hint_processor::CustomHintProcessor::new(program_inputs, false);
233233
cairo_runner
234234
.run_until_pc(end, &mut hint_processor)
235235
.context("sound_run failed: Cairo VM execution failed")?;

0 commit comments

Comments
 (0)