1use crate::{
2 hint_processor::hint_processor_definition::HintProcessor,
3 types::{
4 builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName,
5 program::Program,
6 },
7 vm::{
8 errors::{
9 cairo_run_errors::CairoRunError, runner_errors::RunnerError, vm_exception::VmException,
10 },
11 runners::{cairo_pie::CairoPie, cairo_runner::CairoRunner},
12 security::verify_secure_runner,
13 trace::trace_entry::RelocatedTraceEntry,
14 },
15 Felt252,
16};
17
18use crate::types::exec_scope::ExecutionScopes;
19#[cfg(feature = "test_utils")]
20use arbitrary::{self, Arbitrary};
21use core::fmt;
22use tracing::{info, span, Level};
23
24#[cfg_attr(feature = "test_utils", derive(Arbitrary))]
25pub struct CairoRunConfig<'a> {
26 #[cfg_attr(feature = "test_utils", arbitrary(value = "main"))]
27 pub entrypoint: &'a str,
28 pub trace_enabled: bool,
29 pub relocate_mem: bool,
31 pub relocate_trace: bool,
33 pub layout: LayoutName,
34 pub dynamic_layout_params: Option<CairoLayoutParams>,
37 pub proof_mode: bool,
38 pub fill_holes: bool,
39 pub secure_run: Option<bool>,
40 pub disable_trace_padding: bool,
48 pub allow_missing_builtins: Option<bool>,
49}
50
51impl Default for CairoRunConfig<'_> {
52 fn default() -> Self {
53 CairoRunConfig {
54 entrypoint: "main",
55 trace_enabled: false,
56 relocate_mem: false,
57 relocate_trace: true,
59 layout: LayoutName::plain,
60 proof_mode: false,
61 fill_holes: false,
62 secure_run: None,
63 disable_trace_padding: false,
64 allow_missing_builtins: None,
65 dynamic_layout_params: None,
66 }
67 }
68}
69
70#[allow(clippy::result_large_err)]
71pub fn cairo_run_program_with_initial_scope(
73 program: &Program,
74 cairo_run_config: &CairoRunConfig,
75 hint_processor: &mut dyn HintProcessor,
76 exec_scopes: ExecutionScopes,
77) -> Result<CairoRunner, CairoRunError> {
78 let _span = span!(Level::INFO, "cairo run").entered();
79 let secure_run = cairo_run_config
80 .secure_run
81 .unwrap_or(!cairo_run_config.proof_mode);
82
83 let allow_missing_builtins = cairo_run_config
84 .allow_missing_builtins
85 .unwrap_or(cairo_run_config.proof_mode);
86
87 let mut cairo_runner = CairoRunner::new(
88 program,
89 cairo_run_config.layout,
90 cairo_run_config.dynamic_layout_params.clone(),
91 cairo_run_config.proof_mode,
92 cairo_run_config.trace_enabled,
93 cairo_run_config.disable_trace_padding,
94 )?;
95
96 cairo_runner.exec_scopes = exec_scopes;
97
98 info!(layout = ?cairo_run_config.layout, proof_mode = ?cairo_run_config.proof_mode, trace_enabled = ?cairo_run_config.trace_enabled, disable_trace_padding = ?cairo_run_config.disable_trace_padding, allow_missing_builtins = ?allow_missing_builtins, "Initializing Cairo runner.");
99 let end = cairo_runner.initialize(allow_missing_builtins)?;
100 info!("Running until PC.");
103 cairo_runner
104 .run_until_pc(end, hint_processor)
105 .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
106
107 if cairo_run_config.proof_mode {
108 cairo_runner.run_for_steps(1, hint_processor)?;
111 }
112
113 info!(disable_trace_padding = ?cairo_run_config.disable_trace_padding, fill_holes = ?cairo_run_config.fill_holes, "Ending run.");
114 cairo_runner.end_run(
115 cairo_run_config.disable_trace_padding,
116 false,
117 hint_processor,
118 cairo_run_config.fill_holes,
119 )?;
120
121 info!("Reading return values.");
122 cairo_runner.read_return_values(allow_missing_builtins)?;
123 if cairo_run_config.proof_mode {
124 info!("In proof mode, finalizing segments.");
125 cairo_runner.finalize_segments()?;
126 }
127
128 if secure_run {
129 info!("In secure run, verifying secure runner.");
130 verify_secure_runner(&cairo_runner, true, None)?;
131 }
132
133 info!(relocate_mem = ?cairo_run_config.relocate_mem, relocate_trace = ?cairo_run_config.relocate_trace, "Relocating.");
134 cairo_runner.relocate(
135 cairo_run_config.relocate_mem,
136 cairo_run_config.relocate_trace,
137 )?;
138
139 Ok(cairo_runner)
140}
141
142#[allow(clippy::result_large_err)]
143pub fn cairo_run_program(
144 program: &Program,
145 cairo_run_config: &CairoRunConfig,
146 hint_processor: &mut dyn HintProcessor,
147) -> Result<CairoRunner, CairoRunError> {
148 cairo_run_program_with_initial_scope(
149 program,
150 cairo_run_config,
151 hint_processor,
152 ExecutionScopes::new(),
153 )
154}
155
156#[allow(clippy::result_large_err)]
157pub fn cairo_run(
158 program_content: &[u8],
159 cairo_run_config: &CairoRunConfig,
160 hint_processor: &mut dyn HintProcessor,
161) -> Result<CairoRunner, CairoRunError> {
162 let program = Program::from_bytes(program_content, Some(cairo_run_config.entrypoint))?;
163
164 cairo_run_program(&program, cairo_run_config, hint_processor)
165}
166
167#[allow(clippy::result_large_err)]
168pub fn cairo_run_pie(
175 pie: &CairoPie,
176 cairo_run_config: &CairoRunConfig,
177 hint_processor: &mut dyn HintProcessor,
178) -> Result<CairoRunner, CairoRunError> {
179 if cairo_run_config.proof_mode {
180 return Err(RunnerError::CairoPieProofMode.into());
181 }
182 if hint_processor
183 .get_n_steps()
184 .is_none_or(|steps| steps != pie.execution_resources.n_steps)
185 {
186 return Err(RunnerError::PieNStepsVsRunResourcesNStepsMismatch.into());
187 }
188 pie.run_validity_checks()?;
189 let secure_run = cairo_run_config.secure_run.unwrap_or(true);
190
191 let allow_missing_builtins = cairo_run_config.allow_missing_builtins.unwrap_or_default();
192
193 let program = Program::from_stripped_program(&pie.metadata.program);
194 let mut cairo_runner = CairoRunner::new(
195 &program,
196 cairo_run_config.layout,
197 cairo_run_config.dynamic_layout_params.clone(),
198 false,
199 cairo_run_config.trace_enabled,
200 cairo_run_config.disable_trace_padding,
201 )?;
202
203 let end = cairo_runner.initialize(allow_missing_builtins)?;
204 cairo_runner.vm.finalize_segments_by_cairo_pie(pie);
205 for (name, data) in pie.additional_data.0.iter() {
207 if matches!(name, BuiltinName::pedersen) && secure_run {
209 continue;
210 }
211 if let Some(builtin) = cairo_runner
212 .vm
213 .builtin_runners
214 .iter_mut()
215 .find(|b| b.name() == *name)
216 {
217 builtin.extend_additional_data(data)?;
218 }
219 }
220 let has_zero_segment = cairo_runner.vm.segments.has_zero_segment() as usize;
222 let n_extra_segments = pie.metadata.extra_segments.len() - has_zero_segment;
223 cairo_runner
224 .vm
225 .segments
226 .load_pie_memory(&pie.memory, n_extra_segments)?;
227
228 cairo_runner
229 .run_until_pc(end, hint_processor)
230 .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
231
232 cairo_runner.end_run(
233 cairo_run_config.disable_trace_padding,
234 false,
235 hint_processor,
236 cairo_run_config.fill_holes,
237 )?;
238
239 cairo_runner.read_return_values(allow_missing_builtins)?;
240
241 if secure_run {
242 verify_secure_runner(&cairo_runner, true, None)?;
243 cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?;
245 }
246 cairo_runner.relocate(
247 cairo_run_config.relocate_mem,
248 cairo_run_config.relocate_trace,
249 )?;
250
251 Ok(cairo_runner)
252}
253
254#[cfg(feature = "test_utils")]
255#[allow(clippy::result_large_err)]
256pub fn cairo_run_fuzzed_program(
257 program: Program,
258 cairo_run_config: &CairoRunConfig,
259 hint_processor: &mut dyn HintProcessor,
260 steps_limit: usize,
261) -> Result<CairoRunner, CairoRunError> {
262 use crate::vm::errors::vm_errors::VirtualMachineError;
263
264 let secure_run = cairo_run_config
265 .secure_run
266 .unwrap_or(!cairo_run_config.proof_mode);
267
268 let allow_missing_builtins = cairo_run_config
269 .allow_missing_builtins
270 .unwrap_or(cairo_run_config.proof_mode);
271
272 let mut cairo_runner = CairoRunner::new(
273 &program,
274 cairo_run_config.layout,
275 cairo_run_config.dynamic_layout_params.clone(),
276 cairo_run_config.proof_mode,
277 cairo_run_config.trace_enabled,
278 cairo_run_config.disable_trace_padding,
279 )?;
280
281 let _end = cairo_runner.initialize(allow_missing_builtins)?;
282
283 let res = match cairo_runner.run_until_steps(steps_limit, hint_processor) {
284 Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), res => res,
286 };
287
288 res.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
289
290 cairo_runner.end_run(false, false, hint_processor, cairo_run_config.fill_holes)?;
291
292 cairo_runner.read_return_values(allow_missing_builtins)?;
293 if cairo_run_config.proof_mode {
294 cairo_runner.finalize_segments()?;
295 }
296 if secure_run {
297 verify_secure_runner(&cairo_runner, true, None)?;
298 }
299 cairo_runner.relocate(
300 cairo_run_config.relocate_mem,
301 cairo_run_config.relocate_trace,
302 )?;
303
304 Ok(cairo_runner)
305}
306
307#[derive(Debug)]
309pub struct WriteError;
310
311impl fmt::Display for WriteError {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 write!(f, "Failed to write bytes")
314 }
315}
316
317impl std::error::Error for WriteError {}
318
319pub trait BinaryWrite {
324 fn write_all(&mut self, bytes: &[u8]) -> Result<(), WriteError>;
328}
329
330impl<W: std::io::Write> BinaryWrite for W {
331 fn write_all(&mut self, bytes: &[u8]) -> Result<(), WriteError> {
332 std::io::Write::write_all(self, bytes).map_err(|_| WriteError)
333 }
334}
335
336#[derive(Debug)]
338pub struct EncodeTraceError(usize, WriteError);
339
340impl fmt::Display for EncodeTraceError {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "Failed to encode trace at position {}", self.0)
343 }
344}
345
346impl std::error::Error for EncodeTraceError {}
347
348pub fn write_encoded_trace(
355 relocated_trace: &[RelocatedTraceEntry],
356 dest: &mut impl BinaryWrite,
357) -> Result<(), EncodeTraceError> {
358 for (i, entry) in relocated_trace.iter().enumerate() {
359 dest.write_all(&(entry.ap as u64).to_le_bytes())
360 .map_err(|e| EncodeTraceError(i, e))?;
361 dest.write_all(&(entry.fp as u64).to_le_bytes())
362 .map_err(|e| EncodeTraceError(i, e))?;
363 dest.write_all(&(entry.pc as u64).to_le_bytes())
364 .map_err(|e| EncodeTraceError(i, e))?;
365 }
366
367 Ok(())
368}
369
370pub fn write_encoded_memory(
376 relocated_memory: &[Option<Felt252>],
377 dest: &mut impl BinaryWrite,
378) -> Result<(), EncodeTraceError> {
379 for (i, memory_cell) in relocated_memory.iter().enumerate() {
380 if let Some(value) = memory_cell {
381 dest.write_all(&(i as u64).to_le_bytes())
382 .map_err(|e| EncodeTraceError(i, e))?;
383 dest.write_all(&value.to_bytes_le())
384 .map_err(|e| EncodeTraceError(i, e))?;
385 }
386 }
387
388 Ok(())
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use crate::vm::runners::cairo_runner::RunResources;
395 use crate::Felt252;
396 use crate::{
397 hint_processor::{
398 builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
399 hint_processor_definition::HintProcessor,
400 },
401 utils::test_utils::*,
402 };
403
404 use rstest::rstest;
405
406 #[allow(clippy::result_large_err)]
407 fn run_test_program(
408 program_content: &[u8],
409 hint_processor: &mut dyn HintProcessor,
410 ) -> Result<CairoRunner, CairoRunError> {
411 let program = Program::from_bytes(program_content, Some("main")).unwrap();
412 let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false, true);
413 let end = cairo_runner
414 .initialize(false)
415 .map_err(CairoRunError::Runner)?;
416
417 assert!(cairo_runner.run_until_pc(end, hint_processor).is_ok());
418
419 Ok(cairo_runner)
420 }
421
422 #[test]
423 fn cairo_run_custom_entry_point() {
424 let program = Program::from_bytes(
425 include_bytes!("../../cairo_programs/not_main.json"),
426 Some("not_main"),
427 )
428 .unwrap();
429 let mut hint_processor = BuiltinHintProcessor::new_empty();
430 let mut cairo_runner = cairo_runner!(program);
431
432 let end = cairo_runner.initialize(false).unwrap();
433 assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
434 assert!(cairo_runner.relocate(true, true).is_ok());
435 assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123)));
438 }
439
440 #[test]
441 fn cairo_run_with_no_data_program() {
442 let mut hint_processor = BuiltinHintProcessor::new_empty();
445 let no_data_program_path =
446 include_bytes!("../../cairo_programs/manually_compiled/no_data_program.json");
447 let cairo_run_config = CairoRunConfig::default();
448 assert!(cairo_run(no_data_program_path, &cairo_run_config, &mut hint_processor,).is_err());
449 }
450
451 #[test]
452 fn cairo_run_with_no_main_program() {
453 let mut hint_processor = BuiltinHintProcessor::new_empty();
456 let no_main_program =
457 include_bytes!("../../cairo_programs/manually_compiled/no_main_program.json");
458 let cairo_run_config = CairoRunConfig::default();
459 assert!(cairo_run(no_main_program, &cairo_run_config, &mut hint_processor,).is_err());
460 }
461
462 #[test]
463 fn cairo_run_with_invalid_memory() {
464 let mut hint_processor = BuiltinHintProcessor::new_empty();
467 let invalid_memory =
468 include_bytes!("../../cairo_programs/manually_compiled/invalid_memory.json");
469 let cairo_run_config = CairoRunConfig::default();
470 assert!(cairo_run(invalid_memory, &cairo_run_config, &mut hint_processor,).is_err());
471 }
472
473 #[test]
474 fn write_output_program() {
475 let program_content = include_bytes!("../../cairo_programs/bitwise_output.json");
476 let mut hint_processor = BuiltinHintProcessor::new_empty();
477 let mut runner = run_test_program(program_content, &mut hint_processor)
478 .expect("Couldn't initialize cairo runner");
479
480 let mut output_buffer = String::new();
481 runner.vm.write_output(&mut output_buffer).unwrap();
482 assert_eq!(&output_buffer, "0\n");
483 }
484
485 #[test]
486 fn run_with_no_trace() {
487 let program = Program::from_bytes(
488 include_bytes!("../../cairo_programs/struct.json"),
489 Some("main"),
490 )
491 .unwrap();
492
493 let mut hint_processor = BuiltinHintProcessor::new_empty();
494 let mut cairo_runner = cairo_runner!(program);
495 let end = cairo_runner.initialize(false).unwrap();
496 assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
497 assert!(cairo_runner.relocate(false, false).is_ok());
498 assert!(cairo_runner.relocated_trace.is_none());
499 }
500
501 #[rstest]
502 #[case(include_bytes!("../../cairo_programs/fibonacci.json"))]
503 #[case(include_bytes!("../../cairo_programs/integration.json"))]
504 #[case(include_bytes!("../../cairo_programs/common_signature.json"))]
505 #[case(include_bytes!("../../cairo_programs/relocate_segments.json"))]
506 #[case(include_bytes!("../../cairo_programs/ec_op.json"))]
507 #[case(include_bytes!("../../cairo_programs/bitwise_output.json"))]
508 #[case(include_bytes!("../../cairo_programs/value_beyond_segment.json"))]
509 fn get_and_run_cairo_pie(#[case] program_content: &[u8]) {
510 let cairo_run_config = CairoRunConfig {
511 layout: LayoutName::starknet_with_keccak,
512 ..Default::default()
513 };
514 let cairo_pie = {
516 let runner = cairo_run(
517 program_content,
518 &cairo_run_config,
519 &mut BuiltinHintProcessor::new_empty(),
520 )
521 .unwrap();
522 runner.get_cairo_pie().unwrap()
523 };
524 let mut hint_processor = BuiltinHintProcessor::new(
525 Default::default(),
526 RunResources::new(cairo_pie.execution_resources.n_steps),
527 );
528 assert!(cairo_run_pie(&cairo_pie, &cairo_run_config, &mut hint_processor).is_ok());
530 }
531
532 #[test]
533 fn cairo_run_pie_n_steps_not_set() {
534 let cairo_pie = {
536 let runner = cairo_run(
537 include_bytes!("../../cairo_programs/fibonacci.json"),
538 &CairoRunConfig::default(),
539 &mut BuiltinHintProcessor::new_empty(),
540 )
541 .unwrap();
542 runner.get_cairo_pie().unwrap()
543 };
544 let res = cairo_run_pie(
546 &cairo_pie,
547 &CairoRunConfig::default(),
548 &mut BuiltinHintProcessor::new_empty(),
549 );
550 assert!(res.is_err_and(|err| matches!(
551 err,
552 CairoRunError::Runner(RunnerError::PieNStepsVsRunResourcesNStepsMismatch)
553 )));
554 }
555
556 struct SliceWriter<'a> {
558 buf: &'a mut [u8],
559 pos: usize,
560 }
561
562 impl<'a> SliceWriter<'a> {
563 fn new(buf: &'a mut [u8]) -> Self {
564 Self { buf, pos: 0 }
565 }
566 }
567
568 impl BinaryWrite for SliceWriter<'_> {
569 fn write_all(&mut self, bytes: &[u8]) -> Result<(), WriteError> {
570 if self.pos + bytes.len() > self.buf.len() {
571 return Err(WriteError);
572 }
573 self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
574 self.pos += bytes.len();
575 Ok(())
576 }
577 }
578
579 #[test]
580 fn write_binary_trace_file() {
581 let program_content = include_bytes!("../../cairo_programs/struct.json");
582 let expected_encoded_trace =
583 include_bytes!("../../cairo_programs/trace_memory/cairo_trace_struct");
584
585 let mut hint_processor = BuiltinHintProcessor::new_empty();
586 let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
587
588 assert!(cairo_runner.relocate(false, true).is_ok());
589
590 let trace_entries = cairo_runner.relocated_trace.unwrap();
591 let mut buffer = [0u8; 24];
592 let mut writer = SliceWriter::new(&mut buffer);
593 write_encoded_trace(&trace_entries, &mut writer).unwrap();
594
595 assert_eq!(buffer, *expected_encoded_trace);
596 }
597
598 #[test]
599 fn write_binary_memory_file() {
600 let program_content = include_bytes!("../../cairo_programs/struct.json");
601 let expected_encoded_memory =
602 include_bytes!("../../cairo_programs/trace_memory/cairo_memory_struct");
603
604 let mut hint_processor = BuiltinHintProcessor::new_empty();
605 let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
606
607 assert!(cairo_runner.relocate(true, true).is_ok());
608
609 let mut buffer = [0u8; 120];
610 let mut writer = SliceWriter::new(&mut buffer);
611 write_encoded_memory(&cairo_runner.relocated_memory, &mut writer).unwrap();
612
613 assert_eq!(*expected_encoded_memory, buffer);
614 }
615
616 #[test]
617 fn write_encoded_trace_error_on_small_buffer() {
618 let trace = vec![RelocatedTraceEntry {
619 ap: 1,
620 fp: 2,
621 pc: 3,
622 }];
623 let mut buffer = [0u8; 10]; let mut writer = SliceWriter::new(&mut buffer);
625 let err = write_encoded_trace(&trace, &mut writer).unwrap_err();
626 assert_eq!(err.to_string(), "Failed to encode trace at position 0");
627 }
628
629 #[test]
630 fn write_encoded_memory_error_on_small_buffer() {
631 let memory = vec![Some(Felt252::from(1u64))];
632 let mut buffer = [0u8; 10]; let mut writer = SliceWriter::new(&mut buffer);
634 let err = write_encoded_memory(&memory, &mut writer).unwrap_err();
635 assert_eq!(err.to_string(), "Failed to encode trace at position 0");
636 }
637
638 #[test]
639 fn write_encoded_trace_with_std_io_writer() {
640 let trace = vec![RelocatedTraceEntry {
641 ap: 1,
642 fp: 2,
643 pc: 3,
644 }];
645 let mut buf = Vec::new();
646 write_encoded_trace(&trace, &mut buf).unwrap();
647 assert_eq!(buf.len(), 24);
648 assert_eq!(&buf[0..8], &1u64.to_le_bytes());
649 assert_eq!(&buf[8..16], &2u64.to_le_bytes());
650 assert_eq!(&buf[16..24], &3u64.to_le_bytes());
651 }
652
653 #[test]
654 fn write_encoded_memory_with_std_io_writer() {
655 let memory = vec![None, Some(Felt252::from(42u64))];
656 let mut buf = Vec::new();
657 write_encoded_memory(&memory, &mut buf).unwrap();
658 assert_eq!(buf.len(), 40); assert_eq!(&buf[0..8], &1u64.to_le_bytes()); }
661}