diff --git a/crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs b/crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs index 02f068f6..20267ae8 100644 --- a/crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs +++ b/crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs @@ -214,3 +214,335 @@ pub fn flexible_builtin_usage_from_input( ap_tracking, ) } +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::prepare_ids_data_for_test; + use cairo_vm::serde::deserialize_program::OffsetValue; + use cairo_vm::types::relocatable::MaybeRelocatable; + use cairo_vm::types::relocatable::Relocatable; + use cairo_vm::vm::runners::builtin_runner::{ + BuiltinRunner, OutputBuiltinRunner, OutputBuiltinState, SignatureBuiltinRunner, + }; + use cairo_vm::vm::runners::cairo_pie::PublicMemoryPage; + use cairo_vm::vm::vm_core::VirtualMachine; + use rstest::rstest; + use std::borrow::Cow; + + fn prepare_vm_for_flexible_builtin_usage_test( + input: &FlexibleBuiltinUsageInput, + ) -> ( + VirtualMachine, + ExecutionScopes, + HashMap, + ApTracking, + ) { + // Prepare a VirtualMachine instance, ids_data, exec_scopes, and ap_tracking for testing. + // Should mimic the vm's state after running the following Cairo0 code: + // alloc_locals; + // local n_output; + // local n_pedersen; + // local n_range_check; + // local n_ecdsa; + // local n_bitwise; + // local n_ec_op; + // local n_keccak; + // local n_poseidon; + // local n_range_check96; + // local n_add_mod; + // local n_mul_mod; + // local n_memory_holes; + // local n_blake2s; + let mut vm = VirtualMachine::new(false, false); + let ids_data = prepare_ids_data_for_test(&[ + "n_output", + "n_pedersen", + "n_range_check", + "n_ecdsa", + "n_bitwise", + "n_ec_op", + "n_keccak", + "n_poseidon", + "n_range_check96", + "n_add_mod", + "n_mul_mod", + "n_memory_holes", + "n_blake2s", + ]); + let mut exec_scopes = ExecutionScopes::new(); + exec_scopes.insert_value(PROGRAM_INPUT, serde_json::to_string(input).unwrap()); + let ap_tracking = ApTracking::default(); + vm.set_fp(13); + vm.set_ap(13); + vm.segments.add(); + vm.segments.add(); + (vm, exec_scopes, ids_data, ap_tracking) + } + + /// Test the builtin_usage_add_other_segment function with finalize set to true and false. + /// Checks that other_segment is added correctly and segment is finalized based on the flag. + #[rstest(finalize, case(true), case(false))] + fn test_builtin_usage_add_other_segment(finalize: bool) { + let mut vm = VirtualMachine::new(false, false); + let ids_data = prepare_ids_data_for_test(&["other_segment"]); + vm.segments.add(); + vm.segments.add(); + vm.set_fp(1); + let ap_tracking = ApTracking::default(); + let result = builtin_usage_add_other_segment(&mut vm, &ids_data, &ap_tracking, finalize); + assert!(result.is_ok()); + + // Check that other_segment was added correctly. + let other_segment = vm + .segments + .memory + .get_relocatable(Relocatable::from((1, 0))) + .expect("Failed to get other_segment"); + assert_eq!(other_segment, Relocatable::from((2, 0))); + + // Check that the segment is finalized based on the finalize flag. + if finalize { + assert_eq!(vm.segments.get_segment_size(2), Some(1)); + } else { + assert_eq!(vm.segments.get_segment_size(2), None); + } + } + + /// Test the builtin_usage_add_signature hint with a sample signature. + /// Checks that the signature is added correctly to the VM's state. + #[test] + fn test_builtin_usage_add_signature() { + let mut vm = VirtualMachine::new(false, false); + let ids_data = prepare_ids_data_for_test(&["ecdsa_ptr"]); + let ap_tracking = ApTracking::default(); + vm.segments.add(); + vm.segments.add(); + vm.set_fp(1); + vm.set_ap(1); + + // Initialize the SignatureBuiltinRunner with a base of 2. + let mut ecdsa_builtin = SignatureBuiltinRunner::new(Some(512), true); + ecdsa_builtin.initialize_segments(&mut vm.segments); + assert_eq!(ecdsa_builtin.base, 2); + vm.builtin_runners = vec![ecdsa_builtin.into()]; + + // Load the ecdsa_ptr (2,0) into the VM's memory + let _ = vm + .load_data( + Relocatable::from((1, 0)), + &[MaybeRelocatable::from(Relocatable::from((2, 0)))], + ) + .unwrap(); + + builtin_usage_add_signature(&mut vm, &ids_data, &ap_tracking) + .expect("Failed to add signature"); + + // Check that the signature was added correctly. + if let BuiltinRunner::Signature(sig_runner) = &vm.builtin_runners[0] { + let signatures = sig_runner.signatures.try_borrow_mut().unwrap(); + let signature = signatures + .get(&Relocatable::from((2, 0))) + .expect("Signature not found at ecdsa_ptr"); + let expected_signature = ( + Felt252::from_dec_str( + "3086480810278599376317923499561306189851900463386393948998357832163236918254", + ) + .unwrap(), + Felt252::from_dec_str( + "598673427589502599949712887611119751108407514580626464031881322743364689811", + ) + .unwrap(), + ); + assert_eq!(signature.r, expected_signature.0); + assert_eq!(signature.s, expected_signature.1); + } else { + panic!("Not a SignatureBuiltinRunner"); + } + } + + /// Test the builtin_usage_5_to_ap function - checks that the value 5 is inserted into the AP. + /// Checks that Ap indeed has the value 5 and not 6. + #[test] + fn test_builtin_usage_5_to_ap() { + let mut vm = VirtualMachine::new(false, false); + vm.segments.add(); + vm.segments.add(); + vm.set_fp(1); + vm.set_ap(1); + let result = builtin_usage_5_to_ap(&mut vm); + assert!(result.is_ok()); + // Check that the value 5 was inserted into the AP. + let ap = vm.get_ap(); + let value = vm + .segments + .memory + .get_integer(ap) + .expect("Failed to get AP value"); + assert_eq!(value, Cow::Borrowed(&Felt252::from(5))); + + //Check that 6 is not in ap. + assert_ne!(value, Cow::Borrowed(&Felt252::from(6))); + + //Also check that 5 is still in ap. + assert_eq!(value, Cow::Borrowed(&Felt252::from(5))); + } + + /// Test the builtin_usage_set_pages_and_fact_topology function. + /// Checks the assertion of the pedersen hash and the addition of pages and fact topology to the + /// output builtin. + #[rstest( + left_pedersen_hash, + case::hash_matches(123_usize), + case::hash_mismatch(122_usize) + )] + fn test_builtin_usage_set_pages_and_fact_topology(left_pedersen_hash: usize) { + let mut vm = VirtualMachine::new(false, false); + let ids_data = prepare_ids_data_for_test(&["output_ptr"]); + let ap_tracking = ApTracking::default(); + vm.segments.add(); + vm.segments.add(); + vm.set_fp(1); + vm.set_ap(1); + // Load the output_ptr (2,0) into the VM's memory + let _ = vm + .load_data( + Relocatable::from((1, 0)), + &[MaybeRelocatable::from(Relocatable::from((2, 0)))], + ) + .unwrap(); + + // Initialize the OutputBuiltinRunner with a base of 2. + let output_segment = vm.add_memory_segment(); + let mut output_builtin_runner = OutputBuiltinRunner::new(true); + let temp_output_builtin_state = OutputBuiltinState { + base: output_segment.segment_index as usize, + base_offset: 0, + pages: Default::default(), + attributes: Default::default(), + }; + output_builtin_runner.set_state(temp_output_builtin_state); + vm.builtin_runners = vec![output_builtin_runner.into()]; + + // Load pedersen hash into output builtin_ptr + let ped_hash = pedersen_hash( + &FieldElement::from(left_pedersen_hash), + &FieldElement::from(456_usize), + ); + let ped_hash_felt = field_element_to_felt(ped_hash); + let _ = vm + .load_data( + Relocatable::from((2, 0)), + &[MaybeRelocatable::from(ped_hash_felt)], + ) + .unwrap(); + + let result = builtin_usage_set_pages_and_fact_topology(&mut vm, &ids_data, &ap_tracking); + + // Hint should succeed if the pedersen hash matches the expected value. + if left_pedersen_hash == 123_usize { + assert!(result.is_ok()); + } else { + assert!(result.is_err()); + return; + } + + // Assert that OutputBuiltinRunner has the expected pages and attributes. + if let BuiltinRunner::Output(output_runner) = &mut vm.builtin_runners[0] { + let output_builtin_state = output_runner.get_state(); + let expected_output_builtin_state = OutputBuiltinState { + base: 2, + base_offset: 0, + pages: HashMap::from([ + (1, PublicMemoryPage { start: 1, size: 2 }), + (2, PublicMemoryPage { start: 3, size: 2 }), + ]), + attributes: HashMap::from([(GPS_FACT_TOPOLOGY.into(), vec![3, 2, 0, 1, 0, 2])]), + }; + assert_eq!(output_builtin_state, expected_output_builtin_state); + } else { + panic!("Not an OutputBuiltinRunner"); + } + } + + /// Test the flexible_builtin_usage_from_input function with a sample input. + /// Asserts that the hint puts all values into the VM's locals as expected. + #[test] + fn test_flexible_builtin_usage_from_input() { + let input = FlexibleBuiltinUsageInput { + n_output: 1, + n_pedersen: 2, + n_range_check: 3, + n_ecdsa: 4, + n_bitwise: 5, + n_ec_op: 6, + n_keccak: 7, + n_poseidon: 8, + n_range_check96: 9, + n_add_mod: 10, + n_mul_mod: 11, + n_memory_holes: 12, + n_blake2s: 13, + }; + let (mut vm, mut exec_scopes, ids_data, ap_tracking) = + prepare_vm_for_flexible_builtin_usage_test(&input); + + let result = + flexible_builtin_usage_from_input(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking); + assert!(result.is_ok()); + + // Check that the VM state was modified as expected. + let local_names = [ + "n_output", + "n_pedersen", + "n_range_check", + "n_ecdsa", + "n_bitwise", + "n_ec_op", + "n_keccak", + "n_poseidon", + "n_range_check96", + "n_add_mod", + "n_mul_mod", + "n_memory_holes", + "n_blake2s", + ]; + let expected_values = [ + input.n_output, + input.n_pedersen, + input.n_range_check, + input.n_ecdsa, + input.n_bitwise, + input.n_ec_op, + input.n_keccak, + input.n_poseidon, + input.n_range_check96, + input.n_add_mod, + input.n_mul_mod, + input.n_memory_holes, + input.n_blake2s, + ]; + + // Check that the VM's locals match the expected values. + // The locals are stored at FP + offset2, where offset2 is the offset in the HintReference. + // The offset2 is always a Value, so we can safely use it + // to calculate the address of the local variable. + for (i, (&name, &expected)) in local_names.iter().zip(expected_values.iter()).enumerate() { + let hint_ref = ids_data.get(name).expect("Missing HintReference"); + // Calculate address: FP + offset2 + let offset: i32 = match hint_ref.offset1 { + OffsetValue::Immediate(_) => panic!("Unexpected Immediate in offset2 for {name}"), + OffsetValue::Value(_) => panic!("Unexpected Value in offset2 for {name}"), + OffsetValue::Reference(_, offset, _, _) => offset, + }; + let fp = vm.get_fp(); + let addr = Relocatable::from((fp.segment_index, (fp.offset as i32 + offset) as usize)); + let value_owned = vm.segments.memory.get_integer(addr).unwrap(); + let value = value_owned.as_ref(); + assert_eq!( + value, + &Felt252::from(expected), + "Mismatch for {name} at local {i}" + ); + } + } +} diff --git a/crates/cairo-program-runner-lib/src/hints/types.rs b/crates/cairo-program-runner-lib/src/hints/types.rs index 35cb0100..305e4166 100644 --- a/crates/cairo-program-runner-lib/src/hints/types.rs +++ b/crates/cairo-program-runner-lib/src/hints/types.rs @@ -532,7 +532,7 @@ pub struct MockCairoVerifierInput { pub program_output: Vec, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct FlexibleBuiltinUsageInput { #[serde(default)] pub n_output: usize,