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 },
14};
15
16use crate::Felt252;
17use bincode::enc::write::Writer;
18
19use thiserror::Error;
20
21use crate::types::exec_scope::ExecutionScopes;
22#[cfg(feature = "test_utils")]
23use arbitrary::{self, Arbitrary};
24
25#[cfg_attr(feature = "test_utils", derive(Arbitrary))]
26pub struct CairoRunConfig<'a> {
27 #[cfg_attr(feature = "test_utils", arbitrary(value = "main"))]
28 pub entrypoint: &'a str,
29 pub trace_enabled: bool,
30 pub relocate_mem: bool,
32 pub relocate_trace: bool,
34 pub layout: LayoutName,
35 pub dynamic_layout_params: Option<CairoLayoutParams>,
38 pub proof_mode: bool,
39 pub fill_holes: bool,
40 pub secure_run: Option<bool>,
41 pub disable_trace_padding: bool,
49 pub allow_missing_builtins: Option<bool>,
50}
51
52impl Default for CairoRunConfig<'_> {
53 fn default() -> Self {
54 CairoRunConfig {
55 entrypoint: "main",
56 trace_enabled: false,
57 relocate_mem: false,
58 relocate_trace: true,
60 layout: LayoutName::plain,
61 proof_mode: false,
62 fill_holes: false,
63 secure_run: None,
64 disable_trace_padding: false,
65 allow_missing_builtins: None,
66 dynamic_layout_params: None,
67 }
68 }
69}
70
71#[allow(clippy::result_large_err)]
72pub fn cairo_run_program_with_initial_scope(
74 program: &Program,
75 cairo_run_config: &CairoRunConfig,
76 hint_processor: &mut dyn HintProcessor,
77 exec_scopes: ExecutionScopes,
78) -> Result<CairoRunner, CairoRunError> {
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 let end = cairo_runner.initialize(allow_missing_builtins)?;
99 cairo_runner
102 .run_until_pc(end, hint_processor)
103 .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
104
105 if cairo_run_config.proof_mode {
106 cairo_runner.run_for_steps(1, hint_processor)?;
109 }
110 cairo_runner.end_run(
111 cairo_run_config.disable_trace_padding,
112 false,
113 hint_processor,
114 cairo_run_config.fill_holes,
115 )?;
116
117 cairo_runner.read_return_values(allow_missing_builtins)?;
118 if cairo_run_config.proof_mode {
119 cairo_runner.finalize_segments()?;
120 }
121 if secure_run {
122 verify_secure_runner(&cairo_runner, true, None)?;
123 }
124 cairo_runner.relocate(
125 cairo_run_config.relocate_mem,
126 cairo_run_config.relocate_trace,
127 )?;
128
129 Ok(cairo_runner)
130}
131
132#[allow(clippy::result_large_err)]
133pub fn cairo_run_program(
134 program: &Program,
135 cairo_run_config: &CairoRunConfig,
136 hint_processor: &mut dyn HintProcessor,
137) -> Result<CairoRunner, CairoRunError> {
138 cairo_run_program_with_initial_scope(
139 program,
140 cairo_run_config,
141 hint_processor,
142 ExecutionScopes::new(),
143 )
144}
145
146#[allow(clippy::result_large_err)]
147pub fn cairo_run(
148 program_content: &[u8],
149 cairo_run_config: &CairoRunConfig,
150 hint_processor: &mut dyn HintProcessor,
151) -> Result<CairoRunner, CairoRunError> {
152 let program = Program::from_bytes(program_content, Some(cairo_run_config.entrypoint))?;
153
154 cairo_run_program(&program, cairo_run_config, hint_processor)
155}
156
157#[allow(clippy::result_large_err)]
158pub fn cairo_run_pie(
165 pie: &CairoPie,
166 cairo_run_config: &CairoRunConfig,
167 hint_processor: &mut dyn HintProcessor,
168) -> Result<CairoRunner, CairoRunError> {
169 if cairo_run_config.proof_mode {
170 return Err(RunnerError::CairoPieProofMode.into());
171 }
172 if hint_processor
173 .get_n_steps()
174 .is_none_or(|steps| steps != pie.execution_resources.n_steps)
175 {
176 return Err(RunnerError::PieNStepsVsRunResourcesNStepsMismatch.into());
177 }
178 pie.run_validity_checks()?;
179 let secure_run = cairo_run_config.secure_run.unwrap_or(true);
180
181 let allow_missing_builtins = cairo_run_config.allow_missing_builtins.unwrap_or_default();
182
183 let program = Program::from_stripped_program(&pie.metadata.program);
184 let mut cairo_runner = CairoRunner::new(
185 &program,
186 cairo_run_config.layout,
187 cairo_run_config.dynamic_layout_params.clone(),
188 false,
189 cairo_run_config.trace_enabled,
190 cairo_run_config.disable_trace_padding,
191 )?;
192
193 let end = cairo_runner.initialize(allow_missing_builtins)?;
194 cairo_runner.vm.finalize_segments_by_cairo_pie(pie);
195 for (name, data) in pie.additional_data.0.iter() {
197 if matches!(name, BuiltinName::pedersen) && secure_run {
199 continue;
200 }
201 if let Some(builtin) = cairo_runner
202 .vm
203 .builtin_runners
204 .iter_mut()
205 .find(|b| b.name() == *name)
206 {
207 builtin.extend_additional_data(data)?;
208 }
209 }
210 let has_zero_segment = cairo_runner.vm.segments.has_zero_segment() as usize;
212 let n_extra_segments = pie.metadata.extra_segments.len() - has_zero_segment;
213 cairo_runner
214 .vm
215 .segments
216 .load_pie_memory(&pie.memory, n_extra_segments)?;
217
218 cairo_runner
219 .run_until_pc(end, hint_processor)
220 .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
221
222 cairo_runner.end_run(
223 cairo_run_config.disable_trace_padding,
224 false,
225 hint_processor,
226 cairo_run_config.fill_holes,
227 )?;
228
229 cairo_runner.read_return_values(allow_missing_builtins)?;
230
231 if secure_run {
232 verify_secure_runner(&cairo_runner, true, None)?;
233 cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?;
235 }
236 cairo_runner.relocate(
237 cairo_run_config.relocate_mem,
238 cairo_run_config.relocate_trace,
239 )?;
240
241 Ok(cairo_runner)
242}
243
244#[cfg(feature = "test_utils")]
245#[allow(clippy::result_large_err)]
246pub fn cairo_run_fuzzed_program(
247 program: Program,
248 cairo_run_config: &CairoRunConfig,
249 hint_processor: &mut dyn HintProcessor,
250 steps_limit: usize,
251) -> Result<CairoRunner, CairoRunError> {
252 use crate::vm::errors::vm_errors::VirtualMachineError;
253
254 let secure_run = cairo_run_config
255 .secure_run
256 .unwrap_or(!cairo_run_config.proof_mode);
257
258 let allow_missing_builtins = cairo_run_config
259 .allow_missing_builtins
260 .unwrap_or(cairo_run_config.proof_mode);
261
262 let mut cairo_runner = CairoRunner::new(
263 &program,
264 cairo_run_config.layout,
265 cairo_run_config.dynamic_layout_params.clone(),
266 cairo_run_config.proof_mode,
267 cairo_run_config.trace_enabled,
268 cairo_run_config.disable_trace_padding,
269 )?;
270
271 let _end = cairo_runner.initialize(allow_missing_builtins)?;
272
273 let res = match cairo_runner.run_until_steps(steps_limit, hint_processor) {
274 Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), res => res,
276 };
277
278 res.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
279
280 cairo_runner.end_run(false, false, hint_processor, cairo_run_config.fill_holes)?;
281
282 cairo_runner.read_return_values(allow_missing_builtins)?;
283 if cairo_run_config.proof_mode {
284 cairo_runner.finalize_segments()?;
285 }
286 if secure_run {
287 verify_secure_runner(&cairo_runner, true, None)?;
288 }
289 cairo_runner.relocate(
290 cairo_run_config.relocate_mem,
291 cairo_run_config.relocate_trace,
292 )?;
293
294 Ok(cairo_runner)
295}
296
297#[derive(Debug, Error)]
298#[error("Failed to encode trace at position {0}, serialize error: {1}")]
299pub struct EncodeTraceError(usize, bincode::error::EncodeError);
300
301pub fn write_encoded_trace(
306 relocated_trace: &[crate::vm::trace::trace_entry::RelocatedTraceEntry],
307 dest: &mut impl Writer,
308) -> Result<(), EncodeTraceError> {
309 for (i, entry) in relocated_trace.iter().enumerate() {
310 dest.write(&((entry.ap as u64).to_le_bytes()))
311 .map_err(|e| EncodeTraceError(i, e))?;
312 dest.write(&((entry.fp as u64).to_le_bytes()))
313 .map_err(|e| EncodeTraceError(i, e))?;
314 dest.write(&((entry.pc as u64).to_le_bytes()))
315 .map_err(|e| EncodeTraceError(i, e))?;
316 }
317
318 Ok(())
319}
320
321pub fn write_encoded_memory(
327 relocated_memory: &[Option<Felt252>],
328 dest: &mut impl Writer,
329) -> Result<(), EncodeTraceError> {
330 for (i, memory_cell) in relocated_memory.iter().enumerate() {
331 match memory_cell {
332 None => continue,
333 Some(unwrapped_memory_cell) => {
334 dest.write(&(i as u64).to_le_bytes())
335 .map_err(|e| EncodeTraceError(i, e))?;
336 dest.write(&unwrapped_memory_cell.to_bytes_le())
337 .map_err(|e| EncodeTraceError(i, e))?;
338 }
339 }
340 }
341
342 Ok(())
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::stdlib::prelude::*;
349 use crate::vm::runners::cairo_runner::RunResources;
350 use crate::Felt252;
351 use crate::{
352 hint_processor::{
353 builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
354 hint_processor_definition::HintProcessor,
355 },
356 utils::test_utils::*,
357 };
358 use bincode::enc::write::SliceWriter;
359
360 use rstest::rstest;
361 #[cfg(target_arch = "wasm32")]
362 use wasm_bindgen_test::*;
363
364 #[allow(clippy::result_large_err)]
365 fn run_test_program(
366 program_content: &[u8],
367 hint_processor: &mut dyn HintProcessor,
368 ) -> Result<CairoRunner, CairoRunError> {
369 let program = Program::from_bytes(program_content, Some("main")).unwrap();
370 let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false, true);
371 let end = cairo_runner
372 .initialize(false)
373 .map_err(CairoRunError::Runner)?;
374
375 assert!(cairo_runner.run_until_pc(end, hint_processor).is_ok());
376
377 Ok(cairo_runner)
378 }
379
380 #[test]
381 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
382 fn cairo_run_custom_entry_point() {
383 let program = Program::from_bytes(
384 include_bytes!("../../cairo_programs/not_main.json"),
385 Some("not_main"),
386 )
387 .unwrap();
388 let mut hint_processor = BuiltinHintProcessor::new_empty();
389 let mut cairo_runner = cairo_runner!(program);
390
391 let end = cairo_runner.initialize(false).unwrap();
392 assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
393 assert!(cairo_runner.relocate(true, true).is_ok());
394 assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123)));
397 }
398
399 #[test]
400 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
401 fn cairo_run_with_no_data_program() {
402 let mut hint_processor = BuiltinHintProcessor::new_empty();
405 let no_data_program_path =
406 include_bytes!("../../cairo_programs/manually_compiled/no_data_program.json");
407 let cairo_run_config = CairoRunConfig::default();
408 assert!(cairo_run(no_data_program_path, &cairo_run_config, &mut hint_processor,).is_err());
409 }
410
411 #[test]
412 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
413 fn cairo_run_with_no_main_program() {
414 let mut hint_processor = BuiltinHintProcessor::new_empty();
417 let no_main_program =
418 include_bytes!("../../cairo_programs/manually_compiled/no_main_program.json");
419 let cairo_run_config = CairoRunConfig::default();
420 assert!(cairo_run(no_main_program, &cairo_run_config, &mut hint_processor,).is_err());
421 }
422
423 #[test]
424 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
425 fn cairo_run_with_invalid_memory() {
426 let mut hint_processor = BuiltinHintProcessor::new_empty();
429 let invalid_memory =
430 include_bytes!("../../cairo_programs/manually_compiled/invalid_memory.json");
431 let cairo_run_config = CairoRunConfig::default();
432 assert!(cairo_run(invalid_memory, &cairo_run_config, &mut hint_processor,).is_err());
433 }
434
435 #[test]
436 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
437 fn write_output_program() {
438 let program_content = include_bytes!("../../cairo_programs/bitwise_output.json");
439 let mut hint_processor = BuiltinHintProcessor::new_empty();
440 let mut runner = run_test_program(program_content, &mut hint_processor)
441 .expect("Couldn't initialize cairo runner");
442
443 let mut output_buffer = String::new();
444 runner.vm.write_output(&mut output_buffer).unwrap();
445 assert_eq!(&output_buffer, "0\n");
446 }
447
448 #[test]
449 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
450 fn write_binary_trace_file() {
451 let program_content = include_bytes!("../../cairo_programs/struct.json");
452 let expected_encoded_trace =
453 include_bytes!("../../cairo_programs/trace_memory/cairo_trace_struct");
454
455 let mut hint_processor = BuiltinHintProcessor::new_empty();
457 let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
458
459 assert!(cairo_runner.relocate(false, true).is_ok());
460
461 let trace_entries = cairo_runner.relocated_trace.unwrap();
462 let mut buffer = [0; 24];
463 let mut buff_writer = SliceWriter::new(&mut buffer);
464 write_encoded_trace(&trace_entries, &mut buff_writer).unwrap();
466
467 assert_eq!(buffer, *expected_encoded_trace);
469 }
470
471 #[test]
472 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
473 fn write_binary_memory_file() {
474 let program_content = include_bytes!("../../cairo_programs/struct.json");
475 let expected_encoded_memory =
476 include_bytes!("../../cairo_programs/trace_memory/cairo_memory_struct");
477
478 let mut hint_processor = BuiltinHintProcessor::new_empty();
480 let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
481
482 assert!(cairo_runner.relocate(true, true).is_ok());
484
485 let mut buffer = [0; 120];
486 let mut buff_writer = SliceWriter::new(&mut buffer);
487 write_encoded_memory(&cairo_runner.relocated_memory, &mut buff_writer).unwrap();
489
490 assert_eq!(*expected_encoded_memory, buffer);
492 }
493
494 #[test]
495 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
496 fn run_with_no_trace() {
497 let program = Program::from_bytes(
498 include_bytes!("../../cairo_programs/struct.json"),
499 Some("main"),
500 )
501 .unwrap();
502
503 let mut hint_processor = BuiltinHintProcessor::new_empty();
504 let mut cairo_runner = cairo_runner!(program);
505 let end = cairo_runner.initialize(false).unwrap();
506 assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
507 assert!(cairo_runner.relocate(false, false).is_ok());
508 assert!(cairo_runner.relocated_trace.is_none());
509 }
510
511 #[rstest]
512 #[case(include_bytes!("../../cairo_programs/fibonacci.json"))]
513 #[case(include_bytes!("../../cairo_programs/integration.json"))]
514 #[case(include_bytes!("../../cairo_programs/common_signature.json"))]
515 #[case(include_bytes!("../../cairo_programs/relocate_segments.json"))]
516 #[case(include_bytes!("../../cairo_programs/ec_op.json"))]
517 #[case(include_bytes!("../../cairo_programs/bitwise_output.json"))]
518 #[case(include_bytes!("../../cairo_programs/value_beyond_segment.json"))]
519 fn get_and_run_cairo_pie(#[case] program_content: &[u8]) {
520 let cairo_run_config = CairoRunConfig {
521 layout: LayoutName::starknet_with_keccak,
522 ..Default::default()
523 };
524 let cairo_pie = {
526 let runner = cairo_run(
527 program_content,
528 &cairo_run_config,
529 &mut BuiltinHintProcessor::new_empty(),
530 )
531 .unwrap();
532 runner.get_cairo_pie().unwrap()
533 };
534 let mut hint_processor = BuiltinHintProcessor::new(
535 Default::default(),
536 RunResources::new(cairo_pie.execution_resources.n_steps),
537 );
538 assert!(cairo_run_pie(&cairo_pie, &cairo_run_config, &mut hint_processor).is_ok());
540 }
541
542 #[test]
543 fn cairo_run_pie_n_steps_not_set() {
544 let cairo_pie = {
546 let runner = cairo_run(
547 include_bytes!("../../cairo_programs/fibonacci.json"),
548 &CairoRunConfig::default(),
549 &mut BuiltinHintProcessor::new_empty(),
550 )
551 .unwrap();
552 runner.get_cairo_pie().unwrap()
553 };
554 let res = cairo_run_pie(
556 &cairo_pie,
557 &CairoRunConfig::default(),
558 &mut BuiltinHintProcessor::new_empty(),
559 );
560 assert!(res.is_err_and(|err| matches!(
561 err,
562 CairoRunError::Runner(RunnerError::PieNStepsVsRunResourcesNStepsMismatch)
563 )));
564 }
565}