Skip to main content

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        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    /// Relocate memory if `true`, otherwise memory is not relocated.
30    pub relocate_mem: bool,
31    // When `relocate_trace` is set to `false`, the trace will not be relocated even if `trace_enabled` is `true`.
32    pub relocate_trace: bool,
33    pub layout: LayoutName,
34    /// The `dynamic_layout_params` argument should only be used with dynamic layout.
35    /// It is ignored otherwise.
36    pub dynamic_layout_params: Option<CairoLayoutParams>,
37    pub proof_mode: bool,
38    pub fill_holes: bool,
39    pub secure_run: Option<bool>,
40    /// Disable padding of the trace.
41    /// By default, the trace is padded to accommodate the expected builtins-n_steps relationships
42    /// according to the layout.
43    /// When the padding is disabled:
44    /// - It doesn't modify/pad n_steps.
45    /// - It still pads each builtin segment to the next power of 2 (w.r.t the number of used
46    ///   instances of the builtin) compared to their sizes at the end of the execution.
47    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            // Set to true to match expected behavior: trace is relocated only if trace_enabled is true.
58            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)]
71/// Runs a program with a customized execution scope.
72pub 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    // check step calculation
101
102    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        // we run an additional step to ensure that `end` is the last step execute,
109        // rather than the one after it.
110        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)]
168/// Runs a Cairo PIE generated by a previous cairo execution
169/// To generate a cairo pie use the runner's method `get_cairo_pie`
170/// Note: Cairo PIEs cannot be ran in proof_mode
171/// WARNING: As the RunResources are part of the HintProcessor trait, the caller should make sure that
172/// the number of steps in the `RunResources` matches that of the `ExecutionResources` in the `CairoPie`.
173/// An error will be returned if this doesn't hold.
174pub 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    // Load builtin additional data
206    for (name, data) in pie.additional_data.0.iter() {
207        // Data is not trusted in secure_run, therefore we skip extending the hash builtin's data
208        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    // Load previous execution memory
221    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        // Check that the Cairo PIE produced by this run is compatible with the Cairo PIE received
244        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(()), // program ran OK but ended before steps limit
285        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/// Error returned by [`BinaryWrite::write_all`].
308#[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
319/// A minimal binary write trait that works in both std and no_std environments.
320///
321/// This trait provides a simple interface for writing bytes, similar to `std::io::Write`,
322/// but without requiring the standard library.
323pub trait BinaryWrite {
324    /// Writes all bytes from the buffer to the writer.
325    ///
326    /// This method must write the entire buffer or return an error.
327    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/// Error returned when encoding trace or memory fails.
337#[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
348/// Writes the trace binary representation.
349///
350/// The trace entries (ap, fp, pc) are little-endian encoded and concatenated:
351/// - ap: 8-byte
352/// - fp: 8-byte
353/// - pc: 8-byte
354pub 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
370/// Writes the relocated memory binary representation.
371///
372/// The memory pairs (address, value) are little-endian encoded and concatenated:
373/// - address: 8-byte
374/// - value: 32-byte
375pub 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        // `main` returns without doing nothing, but `not_main` sets `[ap]` to `1`
436        // Memory location was found empirically and simply hardcoded
437        assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123)));
438    }
439
440    #[test]
441    fn cairo_run_with_no_data_program() {
442        // a compiled program with no `data` key.
443        // it should fail when the program is loaded.
444        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        // a compiled program with no main scope
454        // it should fail when trying to run initialize_main_entrypoint.
455        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        // the program invalid_memory.json has an invalid memory cell and errors when trying to
465        // decode the instruction.
466        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        // First run program to get Cairo PIE
515        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        // Default config runs with secure_run, which checks that the Cairo PIE produced by this run is compatible with the one received
529        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        // First run program to get Cairo PIE
535        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        // Run Cairo PIE
545        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    /// A simple slice writer for testing BinaryWrite in no_std-like conditions.
557    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]; // Too small (needs 24 bytes)
624        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]; // Too small (needs 40 bytes: 8 + 32)
633        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); // 8 (addr) + 32 (value)
659        assert_eq!(&buf[0..8], &1u64.to_le_bytes()); // address = 1 (index of Some)
660    }
661}