cairo_vm/
cairo_run.rs

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_no_std::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    /// The `dynamic_layout_params` argument should only be used with dynamic layout.
33    /// It is ignored otherwise.
34    pub dynamic_layout_params: Option<CairoLayoutParams>,
35    pub proof_mode: bool,
36    pub secure_run: Option<bool>,
37    /// Disable padding of the trace.
38    /// By default, the trace is padded to accommodate the expected builtins-n_steps relationships
39    /// according to the layout.
40    /// When the padding is disabled:
41    /// - It doesn't modify/pad n_steps.
42    /// - It still pads each builtin segment to the next power of 2 (w.r.t the number of used
43    ///   instances of the builtin) compared to their sizes at the end of the execution.
44    pub disable_trace_padding: bool,
45    pub allow_missing_builtins: Option<bool>,
46}
47
48impl<'a> Default for CairoRunConfig<'a> {
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/// Runs a program with a customized execution scope.
65pub fn cairo_run_program_with_initial_scope(
66    program: &Program,
67    cairo_run_config: &CairoRunConfig,
68    hint_processor: &mut dyn HintProcessor,
69    exec_scopes: ExecutionScopes,
70) -> Result<CairoRunner, CairoRunError> {
71    let secure_run = cairo_run_config
72        .secure_run
73        .unwrap_or(!cairo_run_config.proof_mode);
74
75    let allow_missing_builtins = cairo_run_config
76        .allow_missing_builtins
77        .unwrap_or(cairo_run_config.proof_mode);
78
79    let mut cairo_runner = CairoRunner::new(
80        program,
81        cairo_run_config.layout,
82        cairo_run_config.dynamic_layout_params.clone(),
83        cairo_run_config.proof_mode,
84        cairo_run_config.trace_enabled,
85        cairo_run_config.disable_trace_padding,
86    )?;
87
88    cairo_runner.exec_scopes = exec_scopes;
89
90    let end = cairo_runner.initialize(allow_missing_builtins)?;
91    // check step calculation
92
93    cairo_runner
94        .run_until_pc(end, hint_processor)
95        .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
96
97    if cairo_run_config.proof_mode {
98        cairo_runner.run_for_steps(1, hint_processor)?;
99    }
100    cairo_runner.end_run(
101        cairo_run_config.disable_trace_padding,
102        false,
103        hint_processor,
104    )?;
105
106    cairo_runner.vm.verify_auto_deductions()?;
107    cairo_runner.read_return_values(allow_missing_builtins)?;
108    if cairo_run_config.proof_mode {
109        cairo_runner.finalize_segments()?;
110    }
111    if secure_run {
112        verify_secure_runner(&cairo_runner, true, None)?;
113    }
114    cairo_runner.relocate(cairo_run_config.relocate_mem)?;
115
116    Ok(cairo_runner)
117}
118
119pub fn cairo_run_program(
120    program: &Program,
121    cairo_run_config: &CairoRunConfig,
122    hint_processor: &mut dyn HintProcessor,
123) -> Result<CairoRunner, CairoRunError> {
124    cairo_run_program_with_initial_scope(
125        program,
126        cairo_run_config,
127        hint_processor,
128        ExecutionScopes::new(),
129    )
130}
131
132pub fn cairo_run(
133    program_content: &[u8],
134    cairo_run_config: &CairoRunConfig,
135    hint_processor: &mut dyn HintProcessor,
136) -> Result<CairoRunner, CairoRunError> {
137    let program = Program::from_bytes(program_content, Some(cairo_run_config.entrypoint))?;
138
139    cairo_run_program(&program, cairo_run_config, hint_processor)
140}
141/// Runs a Cairo PIE generated by a previous cairo execution
142/// To generate a cairo pie use the runner's method `get_cairo_pie`
143/// Note: Cairo PIEs cannot be ran in proof_mode
144/// WARNING: As the RunResources are part of the HintProcessor trait, the caller should make sure that
145/// the number of steps in the `RunResources` matches that of the `ExecutionResources` in the `CairoPie`.
146/// An error will be returned if this doesn't hold.
147pub fn cairo_run_pie(
148    pie: &CairoPie,
149    cairo_run_config: &CairoRunConfig,
150    hint_processor: &mut dyn HintProcessor,
151) -> Result<CairoRunner, CairoRunError> {
152    if cairo_run_config.proof_mode {
153        return Err(RunnerError::CairoPieProofMode.into());
154    }
155    if !hint_processor
156        .get_n_steps()
157        .is_some_and(|steps| steps == pie.execution_resources.n_steps)
158    {
159        return Err(RunnerError::PieNStepsVsRunResourcesNStepsMismatch.into());
160    }
161    pie.run_validity_checks()?;
162    let secure_run = cairo_run_config.secure_run.unwrap_or(true);
163
164    let allow_missing_builtins = cairo_run_config.allow_missing_builtins.unwrap_or_default();
165
166    let program = Program::from_stripped_program(&pie.metadata.program);
167    let mut cairo_runner = CairoRunner::new(
168        &program,
169        cairo_run_config.layout,
170        cairo_run_config.dynamic_layout_params.clone(),
171        false,
172        cairo_run_config.trace_enabled,
173        cairo_run_config.disable_trace_padding,
174    )?;
175
176    let end = cairo_runner.initialize(allow_missing_builtins)?;
177    cairo_runner.vm.finalize_segments_by_cairo_pie(pie);
178    // Load builtin additional data
179    for (name, data) in pie.additional_data.0.iter() {
180        // Data is not trusted in secure_run, therefore we skip extending the hash builtin's data
181        if matches!(name, BuiltinName::pedersen) && secure_run {
182            continue;
183        }
184        if let Some(builtin) = cairo_runner
185            .vm
186            .builtin_runners
187            .iter_mut()
188            .find(|b| b.name() == *name)
189        {
190            builtin.extend_additional_data(data)?;
191        }
192    }
193    // Load previous execution memory
194    let has_zero_segment = cairo_runner.vm.segments.has_zero_segment() as usize;
195    let n_extra_segments = pie.metadata.extra_segments.len() - has_zero_segment;
196    cairo_runner
197        .vm
198        .segments
199        .load_pie_memory(&pie.memory, n_extra_segments)?;
200
201    cairo_runner
202        .run_until_pc(end, hint_processor)
203        .map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
204
205    cairo_runner.end_run(
206        cairo_run_config.disable_trace_padding,
207        false,
208        hint_processor,
209    )?;
210
211    cairo_runner.vm.verify_auto_deductions()?;
212    cairo_runner.read_return_values(allow_missing_builtins)?;
213
214    if secure_run {
215        verify_secure_runner(&cairo_runner, true, None)?;
216        // Check that the Cairo PIE produced by this run is compatible with the Cairo PIE received
217        cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?;
218    }
219    cairo_runner.relocate(cairo_run_config.relocate_mem)?;
220
221    Ok(cairo_runner)
222}
223
224#[cfg(feature = "test_utils")]
225pub fn cairo_run_fuzzed_program(
226    program: Program,
227    cairo_run_config: &CairoRunConfig,
228    hint_processor: &mut dyn HintProcessor,
229    steps_limit: usize,
230) -> Result<CairoRunner, CairoRunError> {
231    use crate::vm::errors::vm_errors::VirtualMachineError;
232
233    let secure_run = cairo_run_config
234        .secure_run
235        .unwrap_or(!cairo_run_config.proof_mode);
236
237    let allow_missing_builtins = cairo_run_config
238        .allow_missing_builtins
239        .unwrap_or(cairo_run_config.proof_mode);
240
241    let mut cairo_runner = CairoRunner::new(
242        &program,
243        cairo_run_config.layout,
244        cairo_run_config.dynamic_layout_params.clone(),
245        cairo_run_config.proof_mode,
246        cairo_run_config.trace_enabled,
247        cairo_run_config.disable_trace_padding,
248    )?;
249
250    let _end = cairo_runner.initialize(allow_missing_builtins)?;
251
252    let res = match cairo_runner.run_until_steps(steps_limit, hint_processor) {
253        Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), // program ran OK but ended before steps limit
254        res => res,
255    };
256
257    res.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
258
259    cairo_runner.end_run(false, false, hint_processor)?;
260
261    cairo_runner.vm.verify_auto_deductions()?;
262    cairo_runner.read_return_values(allow_missing_builtins)?;
263    if cairo_run_config.proof_mode {
264        cairo_runner.finalize_segments()?;
265    }
266    if secure_run {
267        verify_secure_runner(&cairo_runner, true, None)?;
268    }
269    cairo_runner.relocate(cairo_run_config.relocate_mem)?;
270
271    Ok(cairo_runner)
272}
273
274#[derive(Debug, Error)]
275#[error("Failed to encode trace at position {0}, serialize error: {1}")]
276pub struct EncodeTraceError(usize, bincode::error::EncodeError);
277
278/// Writes the trace binary representation.
279///
280/// Bincode encodes to little endian by default and each trace entry is composed of
281/// 3 usize values that are padded to always reach 64 bit size.
282pub fn write_encoded_trace(
283    relocated_trace: &[crate::vm::trace::trace_entry::RelocatedTraceEntry],
284    dest: &mut impl Writer,
285) -> Result<(), EncodeTraceError> {
286    for (i, entry) in relocated_trace.iter().enumerate() {
287        dest.write(&((entry.ap as u64).to_le_bytes()))
288            .map_err(|e| EncodeTraceError(i, e))?;
289        dest.write(&((entry.fp as u64).to_le_bytes()))
290            .map_err(|e| EncodeTraceError(i, e))?;
291        dest.write(&((entry.pc as u64).to_le_bytes()))
292            .map_err(|e| EncodeTraceError(i, e))?;
293    }
294
295    Ok(())
296}
297
298/// Writes a binary representation of the relocated memory.
299///
300/// The memory pairs (address, value) are encoded and concatenated:
301/// * address -> 8-byte encoded
302/// * value -> 32-byte encoded
303pub fn write_encoded_memory(
304    relocated_memory: &[Option<Felt252>],
305    dest: &mut impl Writer,
306) -> Result<(), EncodeTraceError> {
307    for (i, memory_cell) in relocated_memory.iter().enumerate() {
308        match memory_cell {
309            None => continue,
310            Some(unwrapped_memory_cell) => {
311                dest.write(&(i as u64).to_le_bytes())
312                    .map_err(|e| EncodeTraceError(i, e))?;
313                dest.write(&unwrapped_memory_cell.to_bytes_le())
314                    .map_err(|e| EncodeTraceError(i, e))?;
315            }
316        }
317    }
318
319    Ok(())
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use crate::stdlib::prelude::*;
326    use crate::vm::runners::cairo_runner::RunResources;
327    use crate::Felt252;
328    use crate::{
329        hint_processor::{
330            builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
331            hint_processor_definition::HintProcessor,
332        },
333        utils::test_utils::*,
334    };
335    use bincode::enc::write::SliceWriter;
336
337    use rstest::rstest;
338    #[cfg(target_arch = "wasm32")]
339    use wasm_bindgen_test::*;
340
341    fn run_test_program(
342        program_content: &[u8],
343        hint_processor: &mut dyn HintProcessor,
344    ) -> Result<CairoRunner, CairoRunError> {
345        let program = Program::from_bytes(program_content, Some("main")).unwrap();
346        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false, true);
347        let end = cairo_runner
348            .initialize(false)
349            .map_err(CairoRunError::Runner)?;
350
351        assert!(cairo_runner.run_until_pc(end, hint_processor).is_ok());
352
353        Ok(cairo_runner)
354    }
355
356    #[test]
357    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
358    fn cairo_run_custom_entry_point() {
359        let program = Program::from_bytes(
360            include_bytes!("../../cairo_programs/not_main.json"),
361            Some("not_main"),
362        )
363        .unwrap();
364        let mut hint_processor = BuiltinHintProcessor::new_empty();
365        let mut cairo_runner = cairo_runner!(program);
366
367        let end = cairo_runner.initialize(false).unwrap();
368        assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
369        assert!(cairo_runner.relocate(true).is_ok());
370        // `main` returns without doing nothing, but `not_main` sets `[ap]` to `1`
371        // Memory location was found empirically and simply hardcoded
372        assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123)));
373    }
374
375    #[test]
376    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
377    fn cairo_run_with_no_data_program() {
378        // a compiled program with no `data` key.
379        // it should fail when the program is loaded.
380        let mut hint_processor = BuiltinHintProcessor::new_empty();
381        let no_data_program_path =
382            include_bytes!("../../cairo_programs/manually_compiled/no_data_program.json");
383        let cairo_run_config = CairoRunConfig::default();
384        assert!(cairo_run(no_data_program_path, &cairo_run_config, &mut hint_processor,).is_err());
385    }
386
387    #[test]
388    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
389    fn cairo_run_with_no_main_program() {
390        // a compiled program with no main scope
391        // it should fail when trying to run initialize_main_entrypoint.
392        let mut hint_processor = BuiltinHintProcessor::new_empty();
393        let no_main_program =
394            include_bytes!("../../cairo_programs/manually_compiled/no_main_program.json");
395        let cairo_run_config = CairoRunConfig::default();
396        assert!(cairo_run(no_main_program, &cairo_run_config, &mut hint_processor,).is_err());
397    }
398
399    #[test]
400    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
401    fn cairo_run_with_invalid_memory() {
402        // the program invalid_memory.json has an invalid memory cell and errors when trying to
403        // decode the instruction.
404        let mut hint_processor = BuiltinHintProcessor::new_empty();
405        let invalid_memory =
406            include_bytes!("../../cairo_programs/manually_compiled/invalid_memory.json");
407        let cairo_run_config = CairoRunConfig::default();
408        assert!(cairo_run(invalid_memory, &cairo_run_config, &mut hint_processor,).is_err());
409    }
410
411    #[test]
412    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
413    fn write_output_program() {
414        let program_content = include_bytes!("../../cairo_programs/bitwise_output.json");
415        let mut hint_processor = BuiltinHintProcessor::new_empty();
416        let mut runner = run_test_program(program_content, &mut hint_processor)
417            .expect("Couldn't initialize cairo runner");
418
419        let mut output_buffer = String::new();
420        runner.vm.write_output(&mut output_buffer).unwrap();
421        assert_eq!(&output_buffer, "0\n");
422    }
423
424    #[test]
425    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
426    fn write_binary_trace_file() {
427        let program_content = include_bytes!("../../cairo_programs/struct.json");
428        let expected_encoded_trace =
429            include_bytes!("../../cairo_programs/trace_memory/cairo_trace_struct");
430
431        // run test program until the end
432        let mut hint_processor = BuiltinHintProcessor::new_empty();
433        let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
434
435        assert!(cairo_runner.relocate(false).is_ok());
436
437        let trace_entries = cairo_runner.relocated_trace.unwrap();
438        let mut buffer = [0; 24];
439        let mut buff_writer = SliceWriter::new(&mut buffer);
440        // write cairo_rs vm trace file
441        write_encoded_trace(&trace_entries, &mut buff_writer).unwrap();
442
443        // compare that the original cairo vm trace file and cairo_rs vm trace files are equal
444        assert_eq!(buffer, *expected_encoded_trace);
445    }
446
447    #[test]
448    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
449    fn write_binary_memory_file() {
450        let program_content = include_bytes!("../../cairo_programs/struct.json");
451        let expected_encoded_memory =
452            include_bytes!("../../cairo_programs/trace_memory/cairo_memory_struct");
453
454        // run test program until the end
455        let mut hint_processor = BuiltinHintProcessor::new_empty();
456        let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap();
457
458        // relocate memory so we can dump it to file
459        assert!(cairo_runner.relocate(true).is_ok());
460
461        let mut buffer = [0; 120];
462        let mut buff_writer = SliceWriter::new(&mut buffer);
463        // write cairo_rs vm memory file
464        write_encoded_memory(&cairo_runner.relocated_memory, &mut buff_writer).unwrap();
465
466        // compare that the original cairo vm memory file and cairo_rs vm memory files are equal
467        assert_eq!(*expected_encoded_memory, buffer);
468    }
469
470    #[test]
471    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
472    fn run_with_no_trace() {
473        let program = Program::from_bytes(
474            include_bytes!("../../cairo_programs/struct.json"),
475            Some("main"),
476        )
477        .unwrap();
478
479        let mut hint_processor = BuiltinHintProcessor::new_empty();
480        let mut cairo_runner = cairo_runner!(program);
481        let end = cairo_runner.initialize(false).unwrap();
482        assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok());
483        assert!(cairo_runner.relocate(false).is_ok());
484        assert!(cairo_runner.relocated_trace.is_none());
485    }
486
487    #[rstest]
488    #[case(include_bytes!("../../cairo_programs/fibonacci.json"))]
489    #[case(include_bytes!("../../cairo_programs/integration.json"))]
490    #[case(include_bytes!("../../cairo_programs/common_signature.json"))]
491    #[case(include_bytes!("../../cairo_programs/relocate_segments.json"))]
492    #[case(include_bytes!("../../cairo_programs/ec_op.json"))]
493    #[case(include_bytes!("../../cairo_programs/bitwise_output.json"))]
494    #[case(include_bytes!("../../cairo_programs/value_beyond_segment.json"))]
495    fn get_and_run_cairo_pie(#[case] program_content: &[u8]) {
496        let cairo_run_config = CairoRunConfig {
497            layout: LayoutName::starknet_with_keccak,
498            ..Default::default()
499        };
500        // First run program to get Cairo PIE
501        let cairo_pie = {
502            let runner = cairo_run(
503                program_content,
504                &cairo_run_config,
505                &mut BuiltinHintProcessor::new_empty(),
506            )
507            .unwrap();
508            runner.get_cairo_pie().unwrap()
509        };
510        let mut hint_processor = BuiltinHintProcessor::new(
511            Default::default(),
512            RunResources::new(cairo_pie.execution_resources.n_steps),
513        );
514        // Default config runs with secure_run, which checks that the Cairo PIE produced by this run is compatible with the one received
515        assert!(cairo_run_pie(&cairo_pie, &cairo_run_config, &mut hint_processor).is_ok());
516    }
517
518    #[test]
519    fn cairo_run_pie_n_steps_not_set() {
520        // First run program to get Cairo PIE
521        let cairo_pie = {
522            let runner = cairo_run(
523                include_bytes!("../../cairo_programs/fibonacci.json"),
524                &CairoRunConfig::default(),
525                &mut BuiltinHintProcessor::new_empty(),
526            )
527            .unwrap();
528            runner.get_cairo_pie().unwrap()
529        };
530        // Run Cairo PIE
531        let res = cairo_run_pie(
532            &cairo_pie,
533            &CairoRunConfig::default(),
534            &mut BuiltinHintProcessor::new_empty(),
535        );
536        assert!(res.is_err_and(|err| matches!(
537            err,
538            CairoRunError::Runner(RunnerError::PieNStepsVsRunResourcesNStepsMismatch)
539        )));
540    }
541}