Skip to content

Commit db67973

Browse files
adding builtin usage tests
1 parent 1a731f2 commit db67973

File tree

2 files changed

+332
-1
lines changed

2 files changed

+332
-1
lines changed

crates/cairo-program-runner-lib/src/hints/builtin_usage_hints.rs

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,334 @@ pub fn flexible_builtin_usage_from_input(
214214
ap_tracking,
215215
)
216216
}
217+
#[cfg(test)]
218+
mod tests {
219+
use super::*;
220+
use crate::test_utils::prepare_ids_data_for_test;
221+
use cairo_vm::serde::deserialize_program::OffsetValue;
222+
use cairo_vm::types::relocatable::MaybeRelocatable;
223+
use cairo_vm::types::relocatable::Relocatable;
224+
use cairo_vm::vm::runners::builtin_runner::{
225+
BuiltinRunner, OutputBuiltinRunner, OutputBuiltinState, SignatureBuiltinRunner,
226+
};
227+
use cairo_vm::vm::runners::cairo_pie::PublicMemoryPage;
228+
use cairo_vm::vm::vm_core::VirtualMachine;
229+
use rstest::rstest;
230+
use std::borrow::Cow;
231+
232+
fn prepare_vm_for_flexible_builtin_usage_test(
233+
input: &FlexibleBuiltinUsageInput,
234+
) -> (
235+
VirtualMachine,
236+
ExecutionScopes,
237+
HashMap<String, HintReference>,
238+
ApTracking,
239+
) {
240+
// Prepare a VirtualMachine instance, ids_data, exec_scopes, and ap_tracking for testing.
241+
// Should mimic the vm's state after running the following Cairo0 code:
242+
// alloc_locals;
243+
// local n_output;
244+
// local n_pedersen;
245+
// local n_range_check;
246+
// local n_ecdsa;
247+
// local n_bitwise;
248+
// local n_ec_op;
249+
// local n_keccak;
250+
// local n_poseidon;
251+
// local n_range_check96;
252+
// local n_add_mod;
253+
// local n_mul_mod;
254+
// local n_memory_holes;
255+
// local n_blake2s;
256+
let mut vm = VirtualMachine::new(false, false);
257+
let ids_data = prepare_ids_data_for_test(&[
258+
"n_output",
259+
"n_pedersen",
260+
"n_range_check",
261+
"n_ecdsa",
262+
"n_bitwise",
263+
"n_ec_op",
264+
"n_keccak",
265+
"n_poseidon",
266+
"n_range_check96",
267+
"n_add_mod",
268+
"n_mul_mod",
269+
"n_memory_holes",
270+
"n_blake2s",
271+
]);
272+
let mut exec_scopes = ExecutionScopes::new();
273+
exec_scopes.insert_value(PROGRAM_INPUT, serde_json::to_string(input).unwrap());
274+
let ap_tracking = ApTracking::default();
275+
vm.set_fp(13);
276+
vm.set_ap(13);
277+
vm.segments.add();
278+
vm.segments.add();
279+
(vm, exec_scopes, ids_data, ap_tracking)
280+
}
281+
282+
#[rstest(finalize, case(true), case(false))]
283+
fn test_builtin_usage_add_other_segment(finalize: bool) {
284+
// Test the builtin_usage_add_other_segment function with finalize set to true.
285+
let mut vm = VirtualMachine::new(false, false);
286+
let ids_data = prepare_ids_data_for_test(&["other_segment"]);
287+
vm.segments.add();
288+
vm.segments.add();
289+
vm.set_fp(1);
290+
let ap_tracking = ApTracking::default();
291+
let result = builtin_usage_add_other_segment(&mut vm, &ids_data, &ap_tracking, finalize);
292+
assert!(result.is_ok());
293+
294+
// Check that other_segment was added correctly.
295+
let other_segment = vm
296+
.segments
297+
.memory
298+
.get_relocatable(Relocatable::from((1, 0)))
299+
.expect("Failed to get other_segment");
300+
assert_eq!(other_segment, Relocatable::from((2, 0)));
301+
302+
// Check that the segment is finalized based on the finalize flag.
303+
if finalize {
304+
assert_eq!(vm.segments.get_segment_size(2), Some(1));
305+
} else {
306+
assert_eq!(vm.segments.get_segment_size(2), None);
307+
}
308+
}
309+
310+
#[test]
311+
fn test_builtin_usage_add_signature() {
312+
// Test the builtin_usage_add_signature hint with a sample signature.
313+
// This test checks that the signature is added correctly to the VM's state.
314+
let mut vm = VirtualMachine::new(false, false);
315+
let ids_data = prepare_ids_data_for_test(&["ecdsa_ptr"]);
316+
let ap_tracking = ApTracking::default();
317+
vm.segments.add();
318+
vm.segments.add();
319+
vm.set_fp(1);
320+
vm.set_ap(1);
321+
322+
// Initialize the SignatureBuiltinRunner with a base of 2.
323+
let mut ecdsa_builtin = SignatureBuiltinRunner::new(Some(512), true);
324+
ecdsa_builtin.initialize_segments(&mut vm.segments);
325+
assert_eq!(ecdsa_builtin.base, 2);
326+
vm.builtin_runners = vec![ecdsa_builtin.into()];
327+
328+
// Load the ecdsa_ptr (2,0) into the VM's memory
329+
let _ = vm
330+
.load_data(
331+
Relocatable::from((1, 0)),
332+
&[MaybeRelocatable::from(Relocatable::from((2, 0)))],
333+
)
334+
.unwrap();
335+
336+
builtin_usage_add_signature(&mut vm, &ids_data, &ap_tracking)
337+
.expect("Failed to add signature");
338+
339+
// Check that the signature was added correctly.
340+
if let BuiltinRunner::Signature(sig_runner) = &vm.builtin_runners[0] {
341+
let signatures = sig_runner.signatures.try_borrow_mut().unwrap();
342+
let signature = signatures
343+
.get(&Relocatable::from((2, 0)))
344+
.expect("Signature not found at ecdsa_ptr");
345+
let expected_signature = (
346+
Felt252::from_dec_str(
347+
"3086480810278599376317923499561306189851900463386393948998357832163236918254",
348+
)
349+
.unwrap(),
350+
Felt252::from_dec_str(
351+
"598673427589502599949712887611119751108407514580626464031881322743364689811",
352+
)
353+
.unwrap(),
354+
);
355+
assert_eq!(signature.r, expected_signature.0);
356+
assert_eq!(signature.s, expected_signature.1);
357+
} else {
358+
panic!("Not a SignatureBuiltinRunner");
359+
}
360+
}
361+
362+
#[test]
363+
fn test_builtin_usage_5_to_ap() {
364+
// Test the builtin_usage_5_to_ap function - checks that the value 5 is inserted into the AP
365+
// Check that Ap indeed has the value 5 and not 6.
366+
let mut vm = VirtualMachine::new(false, false);
367+
vm.segments.add();
368+
vm.segments.add();
369+
vm.set_fp(1);
370+
vm.set_ap(1);
371+
let result = builtin_usage_5_to_ap(&mut vm);
372+
assert!(result.is_ok());
373+
// Check that the value 5 was inserted into the AP.
374+
let ap = vm.get_ap();
375+
let value = vm
376+
.segments
377+
.memory
378+
.get_integer(ap)
379+
.expect("Failed to get AP value");
380+
assert_eq!(value, Cow::Borrowed(&Felt252::from(5)));
381+
382+
//Check that 6 is not in ap.
383+
assert_ne!(value, Cow::Borrowed(&Felt252::from(6)));
384+
385+
//Also check that 5 is still in ap.
386+
assert_eq!(value, Cow::Borrowed(&Felt252::from(5)));
387+
}
388+
389+
#[rstest(
390+
left_pedersen_hash,
391+
case::hash_matches(123_usize),
392+
case::hash_mismatch(122_usize)
393+
)]
394+
fn test_builtin_usage_set_pages_and_fact_topology(left_pedersen_hash: usize) {
395+
// Test the builtin_usage_set_pages_and_fact_topology function.
396+
// This test checks the assertion of the pedersen hash and the addition of pages and fact
397+
// topology to the output builtin.
398+
let mut vm = VirtualMachine::new(false, false);
399+
let ids_data = prepare_ids_data_for_test(&["output_ptr"]);
400+
let ap_tracking = ApTracking::default();
401+
vm.segments.add();
402+
vm.segments.add();
403+
vm.set_fp(1);
404+
vm.set_ap(1);
405+
// Load the output_ptr (2,0) into the VM's memory
406+
let _ = vm
407+
.load_data(
408+
Relocatable::from((1, 0)),
409+
&[MaybeRelocatable::from(Relocatable::from((2, 0)))],
410+
)
411+
.unwrap();
412+
413+
// Initialize the OutputBuiltinRunner with a base of 2.
414+
let output_segment = vm.add_memory_segment();
415+
let mut output_builtin_runner = OutputBuiltinRunner::new(true);
416+
let temp_output_builtin_state = OutputBuiltinState {
417+
base: output_segment.segment_index as usize,
418+
base_offset: 0,
419+
pages: Default::default(),
420+
attributes: Default::default(),
421+
};
422+
output_builtin_runner.set_state(temp_output_builtin_state);
423+
vm.builtin_runners = vec![output_builtin_runner.into()];
424+
425+
// Load pedersen hash into output builtin_ptr
426+
let ped_hash = pedersen_hash(
427+
&FieldElement::from(left_pedersen_hash),
428+
&FieldElement::from(456_usize),
429+
);
430+
let ped_hash_felt = field_element_to_felt(ped_hash);
431+
let _ = vm
432+
.load_data(
433+
Relocatable::from((2, 0)),
434+
&[MaybeRelocatable::from(ped_hash_felt)],
435+
)
436+
.unwrap();
437+
438+
let result = builtin_usage_set_pages_and_fact_topology(&mut vm, &ids_data, &ap_tracking);
439+
440+
// Hint should succeed if the pedersen hash matches the expected value.
441+
if left_pedersen_hash == 123_usize {
442+
assert!(result.is_ok());
443+
} else {
444+
assert!(result.is_err());
445+
return;
446+
}
447+
448+
// Assert that OutputBuiltinRunner has the expected pages and attributes.
449+
if let BuiltinRunner::Output(output_runner) = &mut vm.builtin_runners[0] {
450+
let output_builtin_state = output_runner.get_state();
451+
let expected_output_builtin_state = OutputBuiltinState {
452+
base: 2,
453+
base_offset: 0,
454+
pages: HashMap::from([
455+
(1, PublicMemoryPage { start: 1, size: 2 }),
456+
(2, PublicMemoryPage { start: 3, size: 2 }),
457+
]),
458+
attributes: HashMap::from([(GPS_FACT_TOPOLOGY.into(), vec![3, 2, 0, 1, 0, 2])]),
459+
};
460+
assert_eq!(output_builtin_state, expected_output_builtin_state);
461+
} else {
462+
panic!("Not an OutputBuiltinRunner");
463+
}
464+
}
465+
466+
#[test]
467+
fn test_flexible_builtin_usage_from_input() {
468+
// Test the flexible_builtin_usage_from_input function with a sample input.
469+
// Asserts that the hint puts all values into the VM's locals as expected.
470+
let input = FlexibleBuiltinUsageInput {
471+
n_output: 1,
472+
n_pedersen: 2,
473+
n_range_check: 3,
474+
n_ecdsa: 4,
475+
n_bitwise: 5,
476+
n_ec_op: 6,
477+
n_keccak: 7,
478+
n_poseidon: 8,
479+
n_range_check96: 9,
480+
n_add_mod: 10,
481+
n_mul_mod: 11,
482+
n_memory_holes: 12,
483+
n_blake2s: 13,
484+
};
485+
let (mut vm, mut exec_scopes, ids_data, ap_tracking) =
486+
prepare_vm_for_flexible_builtin_usage_test(&input);
487+
488+
let result =
489+
flexible_builtin_usage_from_input(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking);
490+
assert!(result.is_ok());
491+
492+
// Check that the VM state was modified as expected.
493+
let local_names = [
494+
"n_output",
495+
"n_pedersen",
496+
"n_range_check",
497+
"n_ecdsa",
498+
"n_bitwise",
499+
"n_ec_op",
500+
"n_keccak",
501+
"n_poseidon",
502+
"n_range_check96",
503+
"n_add_mod",
504+
"n_mul_mod",
505+
"n_memory_holes",
506+
"n_blake2s",
507+
];
508+
let expected_values = [
509+
input.n_output,
510+
input.n_pedersen,
511+
input.n_range_check,
512+
input.n_ecdsa,
513+
input.n_bitwise,
514+
input.n_ec_op,
515+
input.n_keccak,
516+
input.n_poseidon,
517+
input.n_range_check96,
518+
input.n_add_mod,
519+
input.n_mul_mod,
520+
input.n_memory_holes,
521+
input.n_blake2s,
522+
];
523+
524+
// Check that the VM's locals match the expected values.
525+
// The locals are stored at FP + offset2, where offset2 is the offset in the HintReference.
526+
// The offset2 is always a Value, so we can safely use it
527+
// to calculate the address of the local variable.
528+
for (i, (&name, &expected)) in local_names.iter().zip(expected_values.iter()).enumerate() {
529+
let hint_ref = ids_data.get(name).expect("Missing HintReference");
530+
// Calculate address: FP + offset2
531+
let offset: i32 = match hint_ref.offset1 {
532+
OffsetValue::Immediate(_) => panic!("Unexpected Immediate in offset2 for {name}"),
533+
OffsetValue::Value(_) => panic!("Unexpected Value in offset2 for {name}"),
534+
OffsetValue::Reference(_, offset, _, _) => offset,
535+
};
536+
let fp = vm.get_fp();
537+
let addr = Relocatable::from((fp.segment_index, (fp.offset as i32 + offset) as usize));
538+
let value_owned = vm.segments.memory.get_integer(addr).unwrap();
539+
let value = value_owned.as_ref();
540+
assert_eq!(
541+
value,
542+
&Felt252::from(expected),
543+
"Mismatch for {name} at local {i}"
544+
);
545+
}
546+
}
547+
}

crates/cairo-program-runner-lib/src/hints/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ pub struct MockCairoVerifierInput {
532532
pub program_output: Vec<Felt252>,
533533
}
534534

535-
#[derive(Debug, Clone, Deserialize)]
535+
#[derive(Debug, Clone, Deserialize, Serialize)]
536536
pub struct FlexibleBuiltinUsageInput {
537537
#[serde(default)]
538538
pub n_output: usize,

0 commit comments

Comments
 (0)