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
16pub trait TasmObject: BFieldCodec {
26 const MAX_OFFSET: u32 = DEFAULT_MAX_DYN_FIELD_SIZE;
31
32 fn label_friendly_name() -> String;
33
34 fn compute_size_and_assert_valid_size_indicator(
42 library: &mut Library,
43 ) -> Vec<LabelledInstruction>;
44
45 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
57pub trait TasmStruct: TasmObject {
70 fn get_field(field_name: &str) -> Vec<LabelledInstruction>;
80
81 fn get_field_with_size(field_name: &str) -> Vec<LabelledInstruction>;
94
95 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
154pub 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#[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#[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#[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 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 dup 0 {&field!(NamedFields::d)}
369
370 swap 1
372 {&field!(NamedFields::f)}
373 call {length_f}
376 swap 1
379 call {length_d}
380 };
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 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 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 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 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 if also_run_negative_tests_for_correct_size_indicators {
478 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_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 push {START_OF_OBJ}
541 {&third_to_last_field}
544 addi {Digest::LEN - 1}
547 read_mem {Digest::LEN}
548 pop 1
549 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 push { START_OF_OBJ }
574 {&fourth_to_last_field}
577 swap 1
580 read_mem 1
583 pop 1
584 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 push {START_OF_OBJ}
611 {&third_to_last_field}
614 addi {Digest::LEN - 1}
617 read_mem {Digest::LEN}
618 pop 1
619 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 push {OBJ_POINTER}
646 {&assert_size_indicator_validity}
649 halt
652 );
653
654 let expected_stack_benign = [bfe!(random_object.encode().len() as u64)];
655
656 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 push {OBJ_POINTER}
712 {&assert_size_indicator_validity}
715 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 push {OBJ_POINTER}
762 {&assert_size_indicator_validity}
765 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 let mut library = Library::new();
797 let list_length = library.import(Box::new(Length));
798 let code_for_list_lengths = triton_asm! {
799 dup 0
802 {&field!(TupleStruct::3)} swap 1 dup 0
806 {&field!(TupleStruct::5)} swap 1 {&field!(TupleStruct::0)} call {list_length} swap 2 call {list_length} swap 1
814 call {list_length} };
816
817 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_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 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 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 dup 0 {&get_authentication_structure} swap 1 {&get_revealed_leafs} swap 1 call {list_length} swap 1 call {list_length} };
866
867 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_eq!(num_digests, extracted_digests_length);
874 assert_eq!(num_leafs, extracted_xfes_length);
875 }
876
877 fn get_final_stack<T: BFieldCodec + Clone>(
879 obj: &T,
880 library: Library,
881 code: Vec<LabelledInstruction>,
882 ) -> Vec<BFieldElement> {
883 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 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} push 0 {&Empty::destructure()} push {sentinel} eq assert 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 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 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 #[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 57, 58, 53, 54, 55, 56, 8, 2, 1, 0, 4, 1, 50, 51, 52, 6, 1, 45, 46, 47, 48, 49, 42, 43, 44 ];
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::destructure()} write_io 5 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}