tasm_lib/structure/
tasm_object.rs

1use std::collections::HashMap;
2use std::error::Error;
3
4use itertools::Itertools;
5use num_traits::ConstZero;
6use num_traits::Zero;
7pub use tasm_object_derive::TasmObject;
8use triton_vm::prelude::*;
9
10use crate::prelude::*;
11
12pub(super) type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
13
14pub const DEFAULT_MAX_DYN_FIELD_SIZE: u32 = 1u32 << 28;
15
16/// This trait defines methods for dealing with primitive types and
17/// custom-defined struct types from within the VM, assuming they live in memory
18/// as they are encoded with [`BFieldCodec`].
19///
20/// ### Dyn-Compatibility
21///
22/// This trait is _not_ [dyn-compatible] (previously known as “object safe”).
23///
24/// [dyn-compatible]: https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility
25pub trait TasmObject: BFieldCodec {
26    /// Maximum jump distance for encoded size and length indicators.
27    /// The field getters will compare any length or size indicator read
28    /// from memory against this value and crash the VM if the indicator
29    /// is larger or equal.
30    const MAX_OFFSET: u32 = DEFAULT_MAX_DYN_FIELD_SIZE;
31
32    fn label_friendly_name() -> String;
33
34    /// Return the size of `self` and crash if any contained size-indicator
35    /// is not valid.
36    ///
37    /// ```text
38    /// BEFORE: _ *object
39    /// AFTER:  _ calculated_size
40    /// ```
41    fn compute_size_and_assert_valid_size_indicator(
42        library: &mut Library,
43    ) -> Vec<LabelledInstruction>;
44
45    /// Decode as [`Self`].
46    fn decode_iter<Itr: Iterator<Item = BFieldElement>>(iterator: &mut Itr) -> Result<Box<Self>>;
47
48    fn decode_from_memory(
49        memory: &HashMap<BFieldElement, BFieldElement>,
50        address: BFieldElement,
51    ) -> Result<Box<Self>> {
52        let mut iterator = MemoryIter::new(memory, address);
53        Self::decode_iter(&mut iterator)
54    }
55}
56
57/// This trait defines methods for dealing with custom-defined struct types from
58/// within the VM, assuming they live in memory as they are encoded with
59/// [`BFieldCodec`].
60///
61/// The arguments referring to fields are strings. For structs with unnamed fields, the
62/// nth field name is implicitly `field_n`.
63///
64/// ### Dyn-Compatibility
65///
66/// This trait is _not_ [dyn-compatible] (previously known as “object safe”).
67///
68/// [dyn-compatible]: https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility
69pub trait TasmStruct: TasmObject {
70    /// Tasm code that returns a pointer to the field of the object, assuming:
71    ///  - that a pointer to the said object lives on top of the stack;
72    ///  - said object has a type that implements the [`TasmObject`] trait;
73    ///  - said object lives in memory encoded as [`BFieldCodec`] specifies.
74    ///
75    /// ```text
76    /// BEFORE: _ *object
77    /// AFTER:  _ *field
78    /// ```
79    fn get_field(field_name: &str) -> Vec<LabelledInstruction>;
80
81    /// Tasm code that returns a pointer to the field of the object, along with
82    /// the size of that field in number of [`BFieldElement`]s, assuming:
83    ///  - that a pointer to the said object lives on top of the stack;
84    ///  - said object has a type that implements the [`TasmObject`] trait;
85    ///  - said object lives in memory encoded as [`BFieldCodec`] specifies.
86    ///
87    /// ```text
88    /// BEFORE: _ *object
89    /// AFTER:  _ *field field_size
90    ///```
91    ///
92    /// See also: `get_field` if you just want the field without the size.
93    fn get_field_with_size(field_name: &str) -> Vec<LabelledInstruction>;
94
95    /// Destructure a struct into the pointers to its fields.
96    ///
97    /// ```text
98    /// BEFORE: _ *struct
99    /// AFTER:  _ [pointers to all fields]
100    /// ```
101    ///
102    /// # Example
103    ///
104    /// The example below defines a struct `Foo` and encodes an instance of it into
105    /// memory. It then creates a Triton VM program to read and destructure the
106    /// `Foo` instance, extracting and outputting the `bar` field. Finally, it runs
107    /// the program and asserts that the extracted value matches the original `bar`
108    /// value.
109    ///
110    /// ```no_compile
111    /// #  // ^^^^^^^ derive macro `BFieldCodec` does not behave nicely; todo
112    /// # use tasm_lib::prelude::*;
113    /// # use tasm_lib::triton_vm::prelude::*;
114    /// # use tasm_lib::memory::encode_to_memory;
115    /// #[derive(BFieldCodec, TasmObject)]
116    /// struct Foo {
117    ///     bar: u32,
118    ///     baz: XFieldElement,
119    /// }
120    ///
121    /// let foo = Foo { bar: 13, baz: xfe!(0) };
122    /// let foo_ptr = bfe!(42);
123    /// let mut non_determinism = NonDeterminism::default();
124    /// encode_to_memory(&mut non_determinism.ram, foo_ptr, &foo);
125    ///
126    /// let program = triton_program! {
127    ///     read_io 1               // _ *foo
128    ///     {&Foo::destructure()}   // _ *baz *bar
129    ///     read_mem 1              // _ *baz bar (*bar - 1)
130    ///     pop 1                   // _ *baz bar
131    ///     write_io 1              // _ *baz
132    ///     halt
133    /// };
134    ///
135    /// let output = VM::run(program, PublicInput::new(vec![foo_ptr]), non_determinism).unwrap();
136    /// let [bar] = output[..] else { panic!() };
137    /// assert_eq!(bfe!(foo.bar), bar);
138    /// ```
139    fn destructure() -> Vec<LabelledInstruction>;
140}
141
142pub fn decode_from_memory_with_size<T: BFieldCodec>(
143    memory: &HashMap<BFieldElement, BFieldElement>,
144    address: BFieldElement,
145    size: usize,
146) -> Result<Box<T>> {
147    let sequence = (0..size)
148        .map(|i| address + bfe!(i as u64))
149        .map(|b| memory.get(&b).copied().unwrap_or(BFieldElement::ZERO))
150        .collect_vec();
151    T::decode(&sequence).map_err(|e| e.into())
152}
153
154/// Convenience struct for converting between string literals and field name identifiers.
155pub trait TasmStructFieldName {
156    fn tasm_struct_field_name(&self) -> String;
157}
158
159impl TasmStructFieldName for &str {
160    fn tasm_struct_field_name(&self) -> String {
161        self.to_string()
162    }
163}
164
165impl TasmStructFieldName for i32 {
166    fn tasm_struct_field_name(&self) -> String {
167        format!("field_{self}")
168    }
169}
170
171/// Convenience macro, so that we don't have to write
172/// ```ignore
173/// let field_f = <StructWithNamedFields as TasmStruct>::get_field!("f");
174/// let field_0 = <StructWithUnnamedFields as TasmStruct>::get_field!("field_0");
175/// ```
176/// but instead
177/// ```ignore
178/// let field_f = field!(StructWithNamedFields::f);
179/// let field_0 = field!(StructWithUnnamedFields::0);
180/// ```
181/// .
182///
183/// **Limitations** The type descriptor cannot have generic type arguments. To get around
184/// this, define a new type via `type Custom = Generic<T>` and use that instead.
185#[macro_export]
186macro_rules! field {
187    ($o:ident::$e:ident) => {
188        <$o as $crate::structure::tasm_object::TasmStruct>::get_field(
189            &$crate::structure::tasm_object::TasmStructFieldName::tasm_struct_field_name(
190                &stringify!($e),
191            ),
192        )
193    };
194    ($o:ident::$e:expr) => {
195        <$o as $crate::structure::tasm_object::TasmStruct>::get_field(
196            &$crate::structure::tasm_object::TasmStructFieldName::tasm_struct_field_name(&$e),
197        )
198    };
199}
200
201/// Convenience macro, so that we don't have to write
202/// ```ignore
203/// let field_f = <StructWithNamedFields as TasmStruct>::get_field_with_size!("f");
204/// let field_0 = <StructWithUnnamedFields as TasmStruct>::get_field_with_size!("field_0");
205/// ```
206/// but instead
207/// ```ignore
208/// let field_f = field_with_size!(StructWithNamedFields::f);
209/// let field_0 = field_with_size!(StructWithUnnamedFields::0);
210/// ```
211/// and for numbered fields.
212///
213/// **Limitations** The type descriptor cannot have generic type arguments. To get around
214/// this, define a new type via `type Custom = Generic<T>` and use that instead.
215#[macro_export]
216macro_rules! field_with_size {
217    ($o:ident::$e:ident) => {
218        <$o as $crate::structure::tasm_object::TasmStruct>
219            ::get_field_with_size(
220                &$crate::structure::tasm_object::TasmStructFieldName::tasm_struct_field_name(
221                    &stringify!($e)
222                )
223            )
224    };
225    ($o:ident::$e:expr) => {
226        <$o as $crate::structure::tasm_object::TasmStruct>
227            ::get_field_with_size(
228                &$crate::structure::tasm_object::TasmStructFieldName::tasm_object_field_name(&$e)
229            )
230    };
231}
232
233/// Turns a memory, represented as a `HashMap` from `BFieldElement`s to `BFieldElement`s,
234/// along with a starting address, into an iterator over `BFieldElement`s.
235#[derive(Debug, Clone, Copy, Eq, PartialEq)]
236struct MemoryIter<'a> {
237    memory: &'a HashMap<BFieldElement, BFieldElement>,
238    address: BFieldElement,
239}
240
241impl<'a> MemoryIter<'a> {
242    fn new(memory: &'a HashMap<BFieldElement, BFieldElement>, address: BFieldElement) -> Self {
243        Self { memory, address }
244    }
245}
246
247impl Iterator for MemoryIter<'_> {
248    type Item = BFieldElement;
249
250    fn next(&mut self) -> Option<Self::Item> {
251        let element = self
252            .memory
253            .get(&self.address)
254            .copied()
255            .unwrap_or(BFieldElement::zero());
256        self.address.increment();
257        Some(element)
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use arbitrary::Arbitrary;
264    use arbitrary::Unstructured;
265    use triton_vm::proof_item::FriResponse;
266
267    use super::*;
268    use crate::empty_stack;
269    use crate::execute_with_terminal_state;
270    use crate::list::length::Length;
271    use crate::memory::FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
272    use crate::test_prelude::*;
273
274    #[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, TasmObject, Arbitrary)]
275    struct InnerStruct(XFieldElement, u32);
276
277    #[test]
278    fn test_load_and_decode_from_memory() {
279        #[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, TasmObject)]
280        struct OuterStruct {
281            o: InnerStruct,
282            a: Vec<Option<bool>>,
283            b: InnerStruct,
284            p: Vec<Digest>,
285            c: BFieldElement,
286            l: Vec<Digest>,
287        }
288
289        fn pseudorandom_object(seed: [u8; 32]) -> OuterStruct {
290            let mut rng = StdRng::from_seed(seed);
291            let a = (0..19)
292                .map(|_| {
293                    if rng.random() {
294                        Some(rng.random())
295                    } else {
296                        None
297                    }
298                })
299                .collect_vec();
300            let b0: XFieldElement = rng.random();
301            let b1: u32 = rng.random();
302            let b2: XFieldElement = rng.random();
303            let b3: u32 = rng.random();
304            let c: BFieldElement = rng.random();
305            let digests_len_p = rng.random_range(0..5);
306            let digests_p = (0..digests_len_p).map(|_| rng.random()).collect_vec();
307            let digests_len_l = rng.random_range(0..5);
308            let digests_l = (0..digests_len_l).map(|_| rng.random()).collect_vec();
309
310            OuterStruct {
311                o: InnerStruct(b0, b1),
312                a,
313                b: InnerStruct(b2, b3),
314                p: digests_p,
315                c,
316                l: digests_l,
317            }
318        }
319
320        let mut rng = rand::rng();
321        let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
322
323        let object = pseudorandom_object(rng.random());
324        let address = rng.random();
325        encode_to_memory(&mut memory, address, &object);
326        let object_again: OuterStruct = *OuterStruct::decode_from_memory(&memory, address).unwrap();
327        assert_eq!(object, object_again);
328    }
329
330    /// Test derivation of field getters and manual derivations of the `field!` macro
331    mod derive_tests {
332        use num_traits::ConstZero;
333        use twenty_first::math::x_field_element::EXTENSION_DEGREE;
334
335        use super::*;
336        use crate::maybe_write_debuggable_vm_state_to_disk;
337
338        #[test]
339        fn load_and_decode_struct_with_named_fields_from_memory() {
340            #[derive(BFieldCodec, TasmObject, PartialEq, Eq, Clone, Debug, Arbitrary)]
341            struct NamedFields {
342                a: Digest,
343                b: BFieldElement,
344                c: u128,
345                d: Vec<Digest>,
346                e: XFieldElement,
347                f: Vec<u32>,
348            }
349
350            let mut randomness = [0u8; 100000];
351            rand::rng().fill_bytes(&mut randomness);
352            let mut unstructured = Unstructured::new(&randomness);
353            let random_object = NamedFields::arbitrary(&mut unstructured).unwrap();
354            let random_address: u64 = rand::rng().random_range(0..(1 << 30));
355            let address = random_address.into();
356            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
357
358            encode_to_memory(&mut memory, address, &random_object);
359            let object_again: NamedFields =
360                *NamedFields::decode_from_memory(&memory, address).unwrap();
361            assert_eq!(random_object, object_again);
362
363            let mut library = Library::new();
364            let length_d = library.import(Box::new(Length));
365            let length_f = library.import(Box::new(Length));
366            let code = triton_asm! {
367                    // _ *obj
368                    dup 0 {&field!(NamedFields::d)}
369
370                    // _ *obj *d
371                    swap 1
372                   {&field!(NamedFields::f)}
373                    // _ *d *f
374
375                    call {length_f}
376                    // _ *d f_length
377
378                    swap 1
379                    call {length_d}
380                    // _ f_length d_length
381            };
382
383            let mut stack = get_final_stack(&random_object, library, code);
384            let extracted_d_length = stack.pop().unwrap().value() as usize;
385            let extracted_f_length = stack.pop().unwrap().value() as usize;
386
387            assert_eq!(random_object.d.len(), extracted_d_length);
388            assert_eq!(random_object.f.len(), extracted_f_length);
389        }
390
391        #[derive(BFieldCodec, TasmObject, PartialEq, Eq, Clone, Debug, Arbitrary)]
392        struct TupleStruct(
393            Vec<XFieldElement>,
394            InnerStruct,
395            u32,
396            Vec<Digest>,
397            Digest,
398            Vec<BFieldElement>,
399            Digest,
400        );
401
402        fn prepare_random_tuple_struct(seed: [u8; 32]) -> TupleStruct {
403            let mut rng = StdRng::from_seed(seed);
404            let mut randomness = [0u8; 100000];
405            rng.fill_bytes(&mut randomness);
406            let mut unstructured = Unstructured::new(&randomness);
407            TupleStruct::arbitrary(&mut unstructured).unwrap()
408        }
409
410        /// Verify correct field-macro behavior when the size-indicators have
411        /// illegal values.
412        fn prop_negative_test_messed_up_size_indicators<T: BFieldCodec>(
413            program: Program,
414            tuple_struct: &T,
415            obj_pointer: BFieldElement,
416            offset_for_manipulated_si: BFieldElement,
417            expected_stack: &[BFieldElement],
418            also_run_negative_tests_for_correct_size_indicators: bool,
419        ) {
420            // No-messed works
421            let mut no_messed_memory = HashMap::new();
422            encode_to_memory(&mut no_messed_memory, obj_pointer, tuple_struct);
423            let no_messed_nd = NonDeterminism::default().with_ram(no_messed_memory.clone());
424            let mut vm_state_pass = VMState::new(
425                program.clone(),
426                PublicInput::default(),
427                no_messed_nd.clone(),
428            );
429            maybe_write_debuggable_vm_state_to_disk(&vm_state_pass);
430            vm_state_pass.run().unwrap();
431
432            let expected_output_len = expected_stack.len();
433            let actual_stack = (0..expected_output_len)
434                .map(|i| vm_state_pass.op_stack[i])
435                .collect_vec();
436            assert_eq!(expected_stack, actual_stack);
437
438            // Messed-up encoding fails: Too big but still u32
439            let mut messed_up_memory = no_messed_memory.clone();
440            messed_up_memory.insert(
441                obj_pointer + offset_for_manipulated_si,
442                bfe!(TupleStruct::MAX_OFFSET + 1),
443            );
444            let messed_up_nd_0 = NonDeterminism::default().with_ram(messed_up_memory.clone());
445            let mut vm_state_fail0 = VMState::new(
446                program.clone(),
447                PublicInput::default(),
448                messed_up_nd_0.clone(),
449            );
450            maybe_write_debuggable_vm_state_to_disk(&vm_state_fail0);
451            let instruction_error0 = vm_state_fail0.run().unwrap_err();
452            assert!(matches!(
453                instruction_error0,
454                InstructionError::AssertionFailed(_)
455            ));
456
457            // Messed-up encoding fails: Negative sizes banned
458            let negative_number = bfe!(-42);
459            messed_up_memory = no_messed_memory.clone();
460            messed_up_memory.insert(obj_pointer + offset_for_manipulated_si, negative_number);
461            let messed_up_nd_1 = NonDeterminism::default().with_ram(messed_up_memory.clone());
462            let mut vm_state_fail1 = VMState::new(
463                program.clone(),
464                PublicInput::default(),
465                messed_up_nd_1.clone(),
466            );
467            maybe_write_debuggable_vm_state_to_disk(&vm_state_fail1);
468            let instruction_error1 = vm_state_fail1.run().unwrap_err();
469            let expected_err =
470                InstructionError::OpStackError(OpStackError::FailedU32Conversion(negative_number));
471            assert_eq!(expected_err, instruction_error1);
472
473            // Messed-up encoding fails: Size-indicator is within allowed
474            // range but does not have the correct value. This is not checked
475            // by all the `TasmObject` trait functions, so these checks are run
476            // conditionally.
477            if also_run_negative_tests_for_correct_size_indicators {
478                // Messed-up encoding fails: Size-indicator is *one* too big
479                messed_up_memory = no_messed_memory.clone();
480                let address_for_manipulated_si = obj_pointer + offset_for_manipulated_si;
481                messed_up_memory.insert(
482                    address_for_manipulated_si,
483                    messed_up_memory[&address_for_manipulated_si] + bfe!(1),
484                );
485                let messed_up_nd_2 = NonDeterminism::default().with_ram(messed_up_memory.clone());
486                let mut vm_state_fail2 = VMState::new(
487                    program.clone(),
488                    PublicInput::default(),
489                    messed_up_nd_2.clone(),
490                );
491                maybe_write_debuggable_vm_state_to_disk(&vm_state_fail2);
492                let instruction_error2 = vm_state_fail2.run().unwrap_err();
493                assert!(matches!(
494                    instruction_error2,
495                    InstructionError::AssertionFailed(_)
496                ));
497
498                // Messed-up encoding fails: Size-indicator is *one* too small
499                messed_up_memory = no_messed_memory.clone();
500                messed_up_memory.insert(
501                    address_for_manipulated_si,
502                    messed_up_memory[&address_for_manipulated_si] - bfe!(1),
503                );
504                let messed_up_nd_3 = NonDeterminism::default().with_ram(messed_up_memory.clone());
505                let mut vm_state_fail3 =
506                    VMState::new(program, PublicInput::default(), messed_up_nd_3.clone());
507                maybe_write_debuggable_vm_state_to_disk(&vm_state_fail3);
508                let instruction_error3 = vm_state_fail3.run().unwrap_err();
509                assert!(matches!(
510                    instruction_error3,
511                    InstructionError::AssertionFailed(_)
512                ));
513            }
514        }
515
516        #[test]
517        fn mess_with_size_indicator_field_getter_named_fields_negative_test() {
518            #[derive(BFieldCodec, TasmObject, PartialEq, Eq, Clone, Debug, Arbitrary)]
519            struct WithNamedFields {
520                a: Vec<Digest>,
521                b: Vec<BFieldElement>,
522                c: Digest,
523                d: Vec<XFieldElement>,
524            }
525
526            fn prepare_random_object(seed: [u8; 32]) -> WithNamedFields {
527                let mut rng = StdRng::from_seed(seed);
528                let mut randomness = [0u8; 100000];
529                rng.fill_bytes(&mut randomness);
530                let mut unstructured = Unstructured::new(&randomness);
531                WithNamedFields::arbitrary(&mut unstructured).unwrap()
532            }
533
534            const START_OF_OBJ: BFieldElement = BFieldElement::new(800);
535            let random_object = prepare_random_object(rand::random());
536            let third_to_last_field = field!(WithNamedFields::c);
537            let code_using_field_getter = triton_asm!(
538                // _
539
540                push {START_OF_OBJ}
541                // _ *with_named_fields
542
543                {&third_to_last_field}
544                // _ *digest
545
546                addi {Digest::LEN - 1}
547                read_mem {Digest::LEN}
548                pop 1
549                // _ [digest]
550
551                halt
552            );
553
554            let expected_stack_benign = random_object.c.values();
555            let offset_for_manipulated_si = bfe!(0);
556            prop_negative_test_messed_up_size_indicators(
557                Program::new(&code_using_field_getter),
558                &random_object,
559                START_OF_OBJ,
560                offset_for_manipulated_si,
561                &expected_stack_benign,
562                false,
563            );
564        }
565
566        #[test]
567        fn mess_with_size_indicators_field_and_size_getter_negative_test() {
568            const START_OF_OBJ: BFieldElement = BFieldElement::ZERO;
569            let random_object = prepare_random_tuple_struct(rand::random());
570            let fourth_to_last_field = field_with_size!(TupleStruct::field_3);
571            let code_using_field_and_size_getter = triton_asm!(
572                // _
573                push { START_OF_OBJ }
574                // _ *tuple_struct
575
576                {&fourth_to_last_field}
577                // _ *digests digests_size
578
579                swap 1
580                // _ digests_size *digests
581
582                read_mem 1
583                pop 1
584                // _ digests_size digests_len
585
586                halt
587            );
588
589            let expected_field_size = bfe!(random_object.3.len() as u64 * Digest::LEN as u64 + 1);
590            let expected_list_len = bfe!(random_object.3.len() as u64);
591            let expected_stack_benign_nd = [expected_list_len, expected_field_size];
592            prop_negative_test_messed_up_size_indicators(
593                Program::new(&code_using_field_and_size_getter),
594                &random_object,
595                START_OF_OBJ,
596                bfe!(Digest::LEN as u64),
597                &expected_stack_benign_nd,
598                false,
599            );
600        }
601
602        #[test]
603        fn mess_with_size_indicators_field_getter_negative_test() {
604            const START_OF_OBJ: BFieldElement = BFieldElement::ZERO;
605            let random_object = prepare_random_tuple_struct(rand::random());
606            let third_to_last_field = field!(TupleStruct::field_4);
607            let code_using_field_getter = triton_asm!(
608                // _
609
610                push {START_OF_OBJ}
611                // _ *tuple_struct
612
613                {&third_to_last_field}
614                // _ *digest
615
616                addi {Digest::LEN - 1}
617                read_mem {Digest::LEN}
618                pop 1
619                // _ [digest]
620
621                halt
622            );
623
624            let expected_output_benign_nd = random_object.4.values();
625            prop_negative_test_messed_up_size_indicators(
626                Program::new(&code_using_field_getter),
627                &random_object,
628                START_OF_OBJ,
629                bfe!(Digest::LEN as u64),
630                &expected_output_benign_nd,
631                false,
632            );
633        }
634
635        #[test]
636        fn mess_with_size_indicators_size_indicator_validity_check_negative_test() {
637            let mut library = Library::default();
638            const OBJ_POINTER: BFieldElement = BFieldElement::new(422);
639            let random_object = prepare_random_tuple_struct(rand::random());
640            let assert_size_indicator_validity =
641                TupleStruct::compute_size_and_assert_valid_size_indicator(&mut library);
642            let code_using_size_integrity_code = triton_asm!(
643                // _
644
645                push {OBJ_POINTER}
646                // _ *tuple_struct
647
648                {&assert_size_indicator_validity}
649                // _ encoding_length
650
651                halt
652            );
653
654            let expected_stack_benign = [bfe!(random_object.encode().len() as u64)];
655
656            // mess up size-indicator of all size-indicators
657            const SIZE_OF_SIZE_INDICATOR: usize = 1;
658            let size_indicator_for_bfe_vec = Digest::LEN;
659            let size_indicator_for_digest_vec_ptr = size_indicator_for_bfe_vec
660                + random_object.5.encode().len()
661                + SIZE_OF_SIZE_INDICATOR
662                + Digest::LEN;
663            let size_indicator_for_xfe_vec = size_indicator_for_digest_vec_ptr
664                + random_object.3.encode().len()
665                + SIZE_OF_SIZE_INDICATOR
666                + 1
667                + 4;
668
669            for size_indicator_offset in [
670                size_indicator_for_bfe_vec,
671                size_indicator_for_digest_vec_ptr,
672                size_indicator_for_xfe_vec,
673            ] {
674                prop_negative_test_messed_up_size_indicators(
675                    Program::new(&code_using_size_integrity_code),
676                    &random_object,
677                    OBJ_POINTER,
678                    bfe!(size_indicator_offset as u64),
679                    &expected_stack_benign,
680                    true,
681                );
682            }
683        }
684
685        #[test]
686        fn mess_with_size_indicators_checked_size_list_w_dyn_sized_elems_negative_test() {
687            #[derive(BFieldCodec, TasmObject, Debug, Clone, Arbitrary)]
688            struct ListDynSizedElements {
689                a: Vec<Digest>,
690                b: Vec<Vec<Digest>>,
691                c: Vec<XFieldElement>,
692            }
693
694            fn random_struct(seed: [u8; 32]) -> ListDynSizedElements {
695                let mut rng = StdRng::from_seed(seed);
696                let mut randomness = [0u8; 100000];
697                rng.fill_bytes(&mut randomness);
698                let mut unstructured = Unstructured::new(&randomness);
699                ListDynSizedElements::arbitrary(&mut unstructured).unwrap()
700            }
701
702            const OBJ_POINTER: BFieldElement = BFieldElement::new(423);
703            let mut library = Library::default();
704            let assert_size_indicator_validity =
705                ListDynSizedElements::compute_size_and_assert_valid_size_indicator(&mut library);
706
707            let imports = library.all_imports();
708            let code_using_size_integrity_code = triton_asm!(
709                // _
710
711                push {OBJ_POINTER}
712                // _ *list_dyn_sized_elems
713
714                {&assert_size_indicator_validity}
715                // _ encoding_length
716
717                halt
718
719                {&imports}
720            );
721
722            let random_obj = random_struct(rand::random());
723            let expected_stack_benign = [bfe!(random_obj.encode().len() as u64)];
724
725            const SIZE_OF_SIZE_INDICATOR: usize = 1;
726            const SIZE_OF_LENGTH_INDICATOR: usize = 1;
727            let offset_for_vec_vec_digest_size_indicator = random_obj.c.len() * EXTENSION_DEGREE
728                + SIZE_OF_SIZE_INDICATOR
729                + SIZE_OF_LENGTH_INDICATOR;
730            prop_negative_test_messed_up_size_indicators(
731                Program::new(&code_using_size_integrity_code),
732                &random_obj,
733                OBJ_POINTER,
734                bfe!(offset_for_vec_vec_digest_size_indicator as u64),
735                &expected_stack_benign,
736                true,
737            )
738        }
739
740        #[test]
741        fn validate_total_size_statically_sized_struct() {
742            #[derive(BFieldCodec, TasmObject, Debug, Clone, Copy)]
743            struct StaticallySizedStruct {
744                a: Digest,
745                b: Digest,
746                c: XFieldElement,
747            }
748            const OBJ_POINTER: BFieldElement = BFieldElement::new(422);
749            let random_object = StaticallySizedStruct {
750                a: rand::random(),
751                b: rand::random(),
752                c: rand::random(),
753            };
754
755            let mut library = Library::default();
756            let assert_size_indicator_validity =
757                StaticallySizedStruct::compute_size_and_assert_valid_size_indicator(&mut library);
758            let code_using_size_integrity_code = triton_asm!(
759                // _
760
761                push {OBJ_POINTER}
762                // _ *tuple_struct
763
764                {&assert_size_indicator_validity}
765                // _ encoding_length
766
767                halt
768            );
769
770            let program = Program::new(&code_using_size_integrity_code);
771            let mut no_messed_memory = HashMap::new();
772            encode_to_memory(&mut no_messed_memory, OBJ_POINTER, &random_object);
773            let no_messed_nd = NonDeterminism::default().with_ram(no_messed_memory.clone());
774            let mut vm_state = VMState::new(program, PublicInput::default(), no_messed_nd.clone());
775            maybe_write_debuggable_vm_state_to_disk(&vm_state);
776            vm_state.run().unwrap();
777
778            let expected_stack = vec![bfe!(random_object.encode().len() as u64)];
779            let actual_stack = vec![vm_state.op_stack[0]];
780            assert_eq!(expected_stack, actual_stack);
781        }
782
783        #[test]
784        fn load_and_decode_tuple_structs_from_memory() {
785            let random_object = prepare_random_tuple_struct(rand::random());
786            let random_address: u64 = rand::rng().random_range(0..(1 << 30));
787            let address = random_address.into();
788
789            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
790            encode_to_memory(&mut memory, address, &random_object);
791            let object_again: TupleStruct =
792                *TupleStruct::decode_from_memory(&memory, address).unwrap();
793            assert_eq!(random_object, object_again);
794
795            // code snippet to access object's fields
796            let mut library = Library::new();
797            let list_length = library.import(Box::new(Length));
798            let code_for_list_lengths = triton_asm! {
799                // _ *obj
800
801                dup 0
802                {&field!(TupleStruct::3)} // _ *obj *digests
803                swap 1                    // _ *digests *obj
804
805                dup 0
806                {&field!(TupleStruct::5)} // _ *digests *obj *bfes
807                swap 1                    // _ *digests *bfes *obj
808
809                {&field!(TupleStruct::0)} // _ *digests *bfes *xfes
810                call {list_length}        // _ *digests *bfes xfe_count
811                swap 2                    // _ xfe_count *bfes *digests
812                call {list_length}        // _ xfe_count *bfes digest_count
813                swap 1
814                call {list_length}        // _ xfe_count digest_count bfe_count
815            };
816
817            // extract list lengths
818            let mut stack = get_final_stack(&random_object, library, code_for_list_lengths);
819            let extracted_bfe_count = stack.pop().unwrap().value() as usize;
820            let extracted_digest_count = stack.pop().unwrap().value() as usize;
821            let extracted_xfe_count = stack.pop().unwrap().value() as usize;
822
823            // assert correct lengths
824            assert_eq!(random_object.3.len(), extracted_digest_count);
825            assert_eq!(random_object.5.len(), extracted_bfe_count);
826            assert_eq!(random_object.0.len(), extracted_xfe_count);
827        }
828
829        #[test]
830        fn test_fri_response() {
831            let mut rng = rand::rng();
832            let num_digests = 50;
833            let num_leafs = 20;
834
835            // generate object
836            let authentication_structure = (0..num_digests)
837                .map(|_| rng.random::<Digest>())
838                .collect_vec();
839            let revealed_leafs = (0..num_leafs)
840                .map(|_| rng.random::<XFieldElement>())
841                .collect_vec();
842            let fri_response = FriResponse {
843                auth_structure: authentication_structure,
844                revealed_leaves: revealed_leafs,
845            };
846
847            // code snippet to access object's fields
848            let mut library = Library::new();
849            let get_authentication_structure = field!(FriResponse::auth_structure);
850            let list_length = library.import(Box::new(Length));
851            let get_revealed_leafs = field!(FriResponse::revealed_leaves);
852            let code = triton_asm! {
853                // _ *fri_response
854                dup 0 // _ *fri_response *fri_response
855
856                {&get_authentication_structure} // _ *fri_response *authentication_structure
857                swap 1                          // _ *authentication_structure *fri_response
858                {&get_revealed_leafs}           // _ *authentication_structure *revealed_leafs
859
860                swap 1                          // _ *revealed_leafs *authentication_structure
861                call {list_length}              // _ *revealed_leafs num_digests
862                swap 1                          // _ num_digests *revealed_leafs
863                call {list_length}              // _ num_digests num_leafs
864
865            };
866
867            // extract list lengths
868            let mut stack = get_final_stack(&fri_response, library, code);
869            let extracted_xfes_length = stack.pop().unwrap().value() as usize;
870            let extracted_digests_length = stack.pop().unwrap().value() as usize;
871
872            // assert correct lengths
873            assert_eq!(num_digests, extracted_digests_length);
874            assert_eq!(num_leafs, extracted_xfes_length);
875        }
876
877        /// Helper function for testing field getters. Only returns the final stack.
878        fn get_final_stack<T: BFieldCodec + Clone>(
879            obj: &T,
880            library: Library,
881            code: Vec<LabelledInstruction>,
882        ) -> Vec<BFieldElement> {
883            // initialize memory and stack
884            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
885            let random_address: u64 = rand::rng().random_range(0..(1 << 30));
886            let address = random_address.into();
887
888            encode_to_memory(&mut memory, address, obj);
889            let stack = [empty_stack(), vec![address]].concat();
890
891            // link by hand
892            let entrypoint = "entrypoint";
893            let library_code = library.all_imports();
894            let instructions = triton_asm!(
895                call {entrypoint}
896                halt
897
898                {entrypoint}:
899                    {&code}
900                    return
901
902                {&library_code}
903            );
904
905            let program = Program::new(&instructions);
906            let nondeterminism = NonDeterminism::new(vec![]).with_ram(memory);
907            let final_state =
908                execute_with_terminal_state(program, &[], &stack, &nondeterminism, None).unwrap();
909            final_state.op_stack.stack
910        }
911    }
912
913    #[cfg(test)]
914    mod destructure {
915        use super::*;
916        use crate::neptune::neptune_like_types_for_tests::MmrSuccessorProofLookalike;
917        use crate::neptune::neptune_like_types_for_tests::TransactionKernelLookalike;
918        use crate::neptune::neptune_like_types_for_tests::UpdateWitnessLookalike;
919        use crate::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
920
921        #[test]
922        fn unit_struct() {
923            #[derive(BFieldCodec, TasmObject)]
924            struct Empty {}
925
926            let sentinel = bfe!(0xdead_face_u64);
927            let program = triton_program! {
928                push {sentinel}         // _ s
929                push 0                  // _ s 0
930                {&Empty::destructure()} // _ s
931                push {sentinel}         // _ s s
932                eq                      // _ (s == s)
933                assert                  // _
934                halt
935            };
936            VM::run(program, PublicInput::default(), NonDeterminism::default()).unwrap();
937        }
938
939        mod one_field {
940            use super::*;
941
942            #[derive(Debug, Copy, Clone, BFieldCodec, TasmObject, Arbitrary)]
943            struct TupleStatic(u32);
944
945            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
946            struct TupleDynamic(Vec<u32>);
947
948            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
949            struct TupleNested(Vec<Vec<u32>>);
950
951            #[derive(Debug, Copy, Clone, BFieldCodec, TasmObject, Arbitrary)]
952            struct NamedStatic {
953                field: u32,
954            }
955
956            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
957            struct NamedDynamic {
958                field: Vec<u32>,
959            }
960
961            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
962            struct NamedNested {
963                field: Vec<Vec<u32>>,
964            }
965
966            // This macro is a little bit cursed due to the `$post_process`. Since it is
967            // very limited in scope, I say it's better than duplicating essentially the
968            // same code six times. If you want to extend the scope of this macro, please
969            // re-design it.
970            macro_rules! one_field_test_case {
971                (fn $test_name:ident for $ty:ident: $f_name:tt $($post_process:tt)*) => {
972                    #[proptest]
973                    fn $test_name(
974                        #[strategy(arb())] foo: $ty,
975                        #[strategy(arb())] ptr: BFieldElement,
976                    ) {
977                        let program = triton_program! {
978                            push {ptr}
979                            {&$ty::destructure()}
980                            read_mem 1 pop 1 write_io 1
981                            halt
982                        };
983
984                        let mut non_determinism = NonDeterminism::default();
985                        encode_to_memory(&mut non_determinism.ram, ptr, &foo);
986
987                        let output = VM::run(program, PublicInput::default(), non_determinism)?;
988                        let [output] = output[..] else {
989                            return Err(TestCaseError::Fail("unexpected output".into()));
990                        };
991
992                        let $ty { $f_name: the_field } = foo;
993                        let expected = the_field$($post_process)*;
994                        prop_assert_eq!(bfe!(expected), output);
995                    }
996                };
997            }
998
999            one_field_test_case!( fn tuple_static  for TupleStatic:  0 );
1000            one_field_test_case!( fn tuple_dynamic for TupleDynamic: 0.len() );
1001            one_field_test_case!( fn tuple_nested  for TupleNested:  0.len() );
1002            one_field_test_case!( fn named_static  for NamedStatic:  field );
1003            one_field_test_case!( fn named_dynamic for NamedDynamic: field.len() );
1004            one_field_test_case!( fn named_nested  for NamedNested:  field.len() );
1005        }
1006
1007        mod two_fields {
1008            use super::*;
1009
1010            #[derive(Debug, Copy, Clone, BFieldCodec, TasmObject, Arbitrary)]
1011            struct TupleStatStat(u32, u32);
1012
1013            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1014            struct TupleStatDyn(u32, Vec<u32>);
1015
1016            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1017            struct TupleDynStat(Vec<u32>, u32);
1018
1019            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1020            struct TupleDynDyn(Vec<u32>, Vec<u32>);
1021
1022            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1023            struct TupleStatNest(u32, Vec<Vec<u32>>);
1024
1025            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1026            struct TupleNestStat(Vec<Vec<u32>>, u32);
1027
1028            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1029            struct TupleNestNest(Vec<Vec<u32>>, Vec<Vec<u32>>);
1030
1031            #[derive(Debug, Copy, Clone, BFieldCodec, TasmObject, Arbitrary)]
1032            struct NamedStatStat {
1033                a: u32,
1034                b: u32,
1035            }
1036
1037            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1038            struct NamedStatDyn {
1039                a: u32,
1040                b: Vec<u32>,
1041            }
1042
1043            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1044            struct NamedDynStat {
1045                a: Vec<u32>,
1046                b: u32,
1047            }
1048
1049            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1050            struct NamedDynDyn {
1051                a: Vec<u32>,
1052                b: Vec<u32>,
1053            }
1054
1055            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1056            struct NamedStatNest {
1057                a: u32,
1058                b: Vec<Vec<u32>>,
1059            }
1060
1061            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1062            struct NamedNestStat {
1063                a: Vec<Vec<u32>>,
1064                b: u32,
1065            }
1066
1067            #[derive(Debug, Clone, BFieldCodec, TasmObject, Arbitrary)]
1068            struct NamedNestNest {
1069                a: Vec<Vec<u32>>,
1070                b: Vec<Vec<u32>>,
1071            }
1072
1073            // This macro is a little bit cursed due to the `$post_process`es. Since it is
1074            // very limited in scope, I say it's better than duplicating essentially the
1075            // same code 14 times. If you want to extend the scope of this macro, please
1076            // re-design it.
1077            macro_rules! two_fields_test_case {
1078                (fn $test_name:ident for $ty:ident:
1079                    ($f_name_0:tt $($post_process_0:tt)*)
1080                    ($f_name_1:tt $($post_process_1:tt)*)
1081                ) => {
1082                    #[proptest]
1083                    fn $test_name(
1084                        #[strategy(arb())] foo: $ty,
1085                        #[strategy(arb())] ptr: BFieldElement,
1086                    ) {
1087                        let program = triton_program! {
1088                            push {ptr}
1089                            {&$ty::destructure()}
1090                            read_mem 1 pop 1 write_io 1
1091                            read_mem 1 pop 1 write_io 1
1092                            halt
1093                        };
1094
1095                        let mut non_determinism = NonDeterminism::default();
1096                        encode_to_memory(&mut non_determinism.ram, ptr, &foo);
1097
1098                        let output = VM::run(program, PublicInput::default(), non_determinism)?;
1099                        let [output_0, output_1] = output[..] else {
1100                            return Err(TestCaseError::Fail("unexpected output".into()));
1101                        };
1102
1103                        let $ty { $f_name_0: field_0, $f_name_1: field_1 } = foo;
1104                        let expected_0 = field_0$($post_process_0)*;
1105                        let expected_1 = field_1$($post_process_1)*;
1106                        prop_assert_eq!(bfe!(expected_0), output_0);
1107                        prop_assert_eq!(bfe!(expected_1), output_1);
1108                    }
1109                };
1110            }
1111
1112            two_fields_test_case!( fn tuple_stat_stat for TupleStatStat: (0) (1) );
1113            two_fields_test_case!( fn tuple_stat_dyn  for TupleStatDyn:  (0) (1.len()) );
1114            two_fields_test_case!( fn tuple_dyn_stat  for TupleDynStat:  (0.len()) (1) );
1115            two_fields_test_case!( fn tuple_dyn_dyn   for TupleDynDyn:   (0.len()) (1.len()) );
1116            two_fields_test_case!( fn tuple_stat_nest for TupleStatNest: (0) (1.len()) );
1117            two_fields_test_case!( fn tuple_nest_stat for TupleNestStat: (0.len()) (1) );
1118            two_fields_test_case!( fn tuple_nest_nest for TupleNestNest: (0.len()) (1.len()) );
1119            two_fields_test_case!( fn named_stat_stat for NamedStatStat: (a) (b) );
1120            two_fields_test_case!( fn named_stat_dyn  for NamedStatDyn:  (a) (b.len()) );
1121            two_fields_test_case!( fn named_dyn_stat  for NamedDynStat:  (a.len()) (b) );
1122            two_fields_test_case!( fn named_dyn_dyn   for NamedDynDyn:   (a.len()) (b.len()) );
1123            two_fields_test_case!( fn named_stat_nest for NamedStatNest: (a) (b.len()) );
1124            two_fields_test_case!( fn named_nest_stat for NamedNestStat: (a.len()) (b) );
1125            two_fields_test_case!( fn named_nest_nest for NamedNestNest: (a.len()) (b.len()) );
1126        }
1127
1128        #[test]
1129        fn all_static_dynamic_neighbor_combinations() {
1130            /// A struct where all neighbor combinations of fields with
1131            /// {static, dynamic}×{static, dynamic} sizes occur.
1132            #[derive(Debug, BFieldCodec, TasmObject, Eq, PartialEq)]
1133            struct Foo {
1134                a: XFieldElement,
1135                b: Vec<Digest>,
1136                c: Vec<Vec<XFieldElement>>,
1137                d: u128,
1138                e: u64,
1139            }
1140
1141            let foo = Foo {
1142                a: xfe!([42, 43, 44]),
1143                b: vec![Digest::new(bfe_array![45, 46, 47, 48, 49])],
1144                c: vec![vec![], xfe_vec![[50, 51, 52]]],
1145                d: 53 + (54 << 32) + (55 << 64) + (56 << 96),
1146                e: 57 + (58 << 32),
1147            };
1148
1149            let foo_encoding = bfe_vec![
1150                /* e: 00..=01 */ 57, 58, //
1151                /* d: 02..=05 */ 53, 54, 55, 56, //
1152                /* c: 06..=14 */ 8, 2, 1, 0, 4, 1, 50, 51, 52, //
1153                /* b: 15..=21 */ 6, 1, 45, 46, 47, 48, 49, //
1154                /* a: 22..=24 */ 42, 43, 44 //
1155            ];
1156            debug_assert_eq!(foo_encoding, foo.encode(),);
1157
1158            let foo_ptr = bfe!(100);
1159            let mut non_determinism = NonDeterminism::default();
1160            encode_to_memory(&mut non_determinism.ram, foo_ptr, &foo);
1161
1162            let program = triton_program! {
1163                read_io 1               // _ *foo
1164                {&Foo::destructure()}   // _ *e *d *c *b *a
1165                write_io 5              // _
1166                halt
1167            };
1168
1169            let input = PublicInput::new(vec![foo_ptr]);
1170            let output = VM::run(program, input, non_determinism.clone()).unwrap();
1171            let [a_ptr, b_ptr, c_ptr, d_ptr, e_ptr] = output[..] else {
1172                panic!("expected 5 pointers");
1173            };
1174
1175            assert_eq!(foo_ptr + bfe!(22), a_ptr);
1176            assert_eq!(foo_ptr + bfe!(16), b_ptr);
1177            assert_eq!(foo_ptr + bfe!(7), c_ptr);
1178            assert_eq!(foo_ptr + bfe!(2), d_ptr);
1179            assert_eq!(foo_ptr + bfe!(0), e_ptr);
1180
1181            let a = *XFieldElement::decode_from_memory(&non_determinism.ram, a_ptr).unwrap();
1182            let b = *Vec::decode_from_memory(&non_determinism.ram, b_ptr).unwrap();
1183            let c = *Vec::decode_from_memory(&non_determinism.ram, c_ptr).unwrap();
1184            let d = *u128::decode_from_memory(&non_determinism.ram, d_ptr).unwrap();
1185            let e = *u64::decode_from_memory(&non_determinism.ram, e_ptr).unwrap();
1186            let foo_again = Foo { a, b, c, d, e };
1187            assert_eq!(foo, foo_again);
1188        }
1189
1190        #[proptest]
1191        fn destructure_update_witness(
1192            #[strategy(arb())] witness: UpdateWitnessLookalike,
1193            #[strategy(arb())] witness_ptr: BFieldElement,
1194        ) {
1195            let mut non_determinism = NonDeterminism::default();
1196            encode_to_memory(&mut non_determinism.ram, witness_ptr, &witness);
1197
1198            let program = triton_program! {
1199                read_io 1
1200                {&UpdateWitnessLookalike::destructure()}
1201                write_io 5 write_io 5 write_io 4
1202                halt
1203            };
1204
1205            let input = PublicInput::new(vec![witness_ptr]);
1206            let output = VM::run(program, input, non_determinism.clone())?;
1207            let mut output = output.into_iter();
1208            let mut next_ptr = || output.next().unwrap();
1209            let ram = &non_determinism.ram;
1210
1211            let old_kernel =
1212                *TransactionKernelLookalike::decode_from_memory(ram, next_ptr()).unwrap();
1213            let new_kernel =
1214                *TransactionKernelLookalike::decode_from_memory(ram, next_ptr()).unwrap();
1215            let old_kernel_mast_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1216            let new_kernel_mast_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1217            let old_proof = *Proof::decode_from_memory(ram, next_ptr()).unwrap();
1218            let new_swbfi_bagged = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1219            let new_aocl = *MmrAccumulator::decode_from_memory(ram, next_ptr()).unwrap();
1220            let new_swbfa_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1221            let old_swbfi_bagged = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1222            let old_aocl = *MmrAccumulator::decode_from_memory(ram, next_ptr()).unwrap();
1223            let old_swbfa_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1224            let aocl_successor_proof =
1225                *MmrSuccessorProofLookalike::decode_from_memory(ram, next_ptr()).unwrap();
1226            let outputs_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1227            let public_announcements_hash = *Digest::decode_from_memory(ram, next_ptr()).unwrap();
1228
1229            let witness_again = UpdateWitnessLookalike {
1230                old_kernel,
1231                new_kernel,
1232                old_kernel_mast_hash,
1233                new_kernel_mast_hash,
1234                old_proof,
1235                new_swbfi_bagged,
1236                new_aocl,
1237                new_swbfa_hash,
1238                old_swbfi_bagged,
1239                old_aocl,
1240                old_swbfa_hash,
1241                aocl_successor_proof,
1242                outputs_hash,
1243                public_announcements_hash,
1244            };
1245            prop_assert_eq!(witness, witness_again);
1246        }
1247    }
1248
1249    #[test]
1250    fn test_option() {
1251        let mut rng = rand::rng();
1252        let n = rng.random_range(0..5);
1253        let v = (0..n).map(|_| rng.random::<Digest>()).collect_vec();
1254        let mut ram: HashMap<BFieldElement, BFieldElement> = HashMap::new();
1255        let some_address = FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
1256        let none_address = encode_to_memory(&mut ram, some_address, &Some(v.clone()));
1257        encode_to_memory(&mut ram, none_address, &Option::<Vec<Digest>>::None);
1258
1259        let some_decoded = *Option::<Vec<Digest>>::decode_from_memory(&ram, some_address).unwrap();
1260        assert!(some_decoded.is_some());
1261        assert_eq!(some_decoded.unwrap(), v);
1262
1263        let none_decoded = *Option::<Vec<Digest>>::decode_from_memory(&ram, none_address).unwrap();
1264        assert!(none_decoded.is_none());
1265    }
1266
1267    #[proptest]
1268    fn iter_decoding_too_short_sequence_does_not_panic(
1269        #[strategy(arb())] object: Vec<Vec<Digest>>,
1270        num_elements_to_drop: usize,
1271    ) {
1272        let encoding = object.encode();
1273        let encoding_len = encoding.len();
1274        let num_elements_to_drop = num_elements_to_drop % encoding_len;
1275        prop_assume!(num_elements_to_drop != 0);
1276
1277        let mut object_iter = encoding.into_iter().dropping_back(num_elements_to_drop);
1278        prop_assert!(<Vec<Vec<Digest>>>::decode_iter(&mut object_iter).is_err());
1279    }
1280}