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