Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, HintReference>,
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}"
);
}
}
}
2 changes: 1 addition & 1 deletion crates/cairo-program-runner-lib/src/hints/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ pub struct MockCairoVerifierInput {
pub program_output: Vec<Felt252>,
}

#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FlexibleBuiltinUsageInput {
#[serde(default)]
pub n_output: usize,
Expand Down