diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9b5dd3d..d1ffe66 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: "true" - run: rustup show - run: rustup component add rustfmt - uses: Swatinem/rust-cache@v2 @@ -20,6 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: "true" - run: rustup show - uses: Swatinem/rust-cache@v2 - run: cargo clippy --all -- -D warnings @@ -32,24 +36,20 @@ jobs: submodules: "true" - run: rustup show - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - # - name: "Unit tests" - # run: | - # RUSTFLAGS="-D warnings" cargo test - - name: "Test examples" + - name: Run Rust tests run: | - RUSTFLAGS="-D warnings" cargo run --example run_program + RUSTFLAGS="-D warnings" cargo test udeps: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: "true" - run: rustup toolchain install nightly --profile minimal - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - run: cargo install cargo-udeps --locked - # - run: cargo +nightly udeps --all-targets - - run: cargo +nightly udeps + - name: Run udeps + run: cargo +nightly udeps --all-targets diff --git a/.gitmodules b/.gitmodules index c0ee744..7724ea1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "dependencies/cairo-lang"] path = dependencies/cairo-lang url = https://github.com/starkware-libs/cairo-lang +[submodule "test-programs"] + path = dependencies/test-programs + url = https://github.com/Moonsong-Labs/cairo-programs diff --git a/dependencies/cairo-lang b/dependencies/cairo-lang index caba294..efa9648 160000 --- a/dependencies/cairo-lang +++ b/dependencies/cairo-lang @@ -1 +1 @@ -Subproject commit caba294d82eeeccc3d86a158adb8ba209bf2d8fc +Subproject commit efa9648f57568aad8f8a13fbf027d2de7c63c2c0 diff --git a/dependencies/test-programs b/dependencies/test-programs new file mode 160000 index 0000000..b3a7981 --- /dev/null +++ b/dependencies/test-programs @@ -0,0 +1 @@ +Subproject commit b3a798100623919afefb3a2fa5a6bfe45f1ca9d7 diff --git a/src/hints/bootloader_hints.rs b/src/hints/bootloader_hints.rs index 4728b05..f71f2ea 100644 --- a/src/hints/bootloader_hints.rs +++ b/src/hints/bootloader_hints.rs @@ -22,6 +22,7 @@ use crate::hints::types::{BootloaderInput, CompositePackedOutput, PackedOutput}; use crate::hints::vars; /// Implements +/// ```no-run /// %{ /// from starkware.cairo.bootloaders.bootloader.objects import BootloaderInput /// bootloader_input = BootloaderInput.Schema().load(program_input) @@ -33,6 +34,7 @@ use crate::hints::vars; /// output_builtin_state = output_builtin.get_state() /// output_builtin.new_state(base=ids.simple_bootloader_output_start) /// %} +/// ``` pub fn prepare_simple_bootloader_output_segment( vm: &mut VirtualMachine, exec_scopes: &mut ExecutionScopes, @@ -433,16 +435,24 @@ pub fn assert_program_address( mod tests { use num_traits::ToPrimitive; + use crate::hints::codes::{ + BOOTLOADER_GUESS_PRE_IMAGE_OF_SUBTASKS_OUTPUT_HASH, BOOTLOADER_SAVE_OUTPUT_POINTER, + BOOTLOADER_SAVE_PACKED_OUTPUTS, BOOTLOADER_SET_PACKED_OUTPUT_TO_SUBTASKS, + EXECUTE_TASK_ASSERT_PROGRAM_ADDRESS, + }; use crate::hints::types::{BootloaderConfig, SimpleBootloaderInput}; - use assert_matches::assert_matches; - use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::{ - BuiltinHintProcessor, HintProcessorData, + use crate::{ + add_segments, define_segments, ids_data, run_hint, vm, MinimalBootloaderHintProcessor, }; + use assert_matches::assert_matches; + use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_maybe_relocatable_from_var_name; use cairo_vm::hint_processor::hint_processor_definition::HintProcessorLogic; use cairo_vm::serde::deserialize_program::OffsetValue; - use cairo_vm::vm::runners::builtin_runner::{BuiltinRunner, OutputBuiltinState}; - use cairo_vm::vm::runners::cairo_pie::PublicMemoryPage; + use cairo_vm::vm::runners::builtin_runner::{ + BuiltinRunner, OutputBuiltinRunner, OutputBuiltinState, + }; + use cairo_vm::vm::runners::cairo_pie::{BuiltinAdditionalData, PublicMemoryPage}; use cairo_vm::{any_box, relocatable, Felt252}; use rstest::{fixture, rstest}; @@ -469,9 +479,10 @@ mod tests { #[rstest] fn test_prepare_simple_bootloader_output_segment(bootloader_input: BootloaderInput) { - let mut vm = vm!(); - vm.segments.add(); - vm.run_context.fp = 1; + let mut vm = VirtualMachine::new(false); + vm.add_memory_segment(); + vm.add_memory_segment(); + vm.set_fp(1); let mut output_builtin = OutputBuiltinRunner::new(true); output_builtin.initialize_segments(&mut vm.segments); @@ -479,7 +490,10 @@ mod tests { .push(BuiltinRunner::Output(output_builtin.clone())); let mut exec_scopes = ExecutionScopes::new(); - let ids_data = ids_data!["simple_bootloader_output_start"]; + let ids_data = HashMap::from([( + "simple_bootloader_output_start".to_string(), + HintReference::new_simple(-1), + )]); let ap_tracking = ApTracking::new(); exec_scopes.insert_value(vars::BOOTLOADER_INPUT, bootloader_input); @@ -492,7 +506,7 @@ mod tests { .expect("Hint failed unexpectedly"); let current_output_builtin = vm - .get_output_builtin() + .get_output_builtin_mut() .expect("The VM should have an output builtin") .clone(); let stored_output_builtin: OutputBuiltinState = exec_scopes @@ -500,10 +514,8 @@ mod tests { .expect("The output builtin is not stored in the execution scope as expected"); // Check the content of the stored output builtin - assert_ne!(current_output_builtin.base(), stored_output_builtin.base()); - assert_eq!(stored_output_builtin.base(), output_builtin.base()); - assert_eq!(stored_output_builtin.stop_ptr, output_builtin.stop_ptr); - assert_eq!(stored_output_builtin.included, output_builtin.included); + assert_ne!(current_output_builtin.base(), stored_output_builtin.base); + assert_eq!(stored_output_builtin.base, output_builtin.base()); let simple_bootloader_output_start = get_maybe_relocatable_from_var_name( "simple_bootloader_output_start", @@ -557,12 +569,18 @@ mod tests { let mut vm: VirtualMachine = vm!(); // The VM must have an existing output segment let output_segment = vm.add_memory_segment(); - vm.builtin_runners = vec![OutputBuiltinRunner::from_segment(&output_segment, true).into()]; + let output_builtin = { + let mut builtin = OutputBuiltinRunner::new(true); + builtin.new_state(output_segment.segment_index.try_into().unwrap(), true); + builtin + }; + + vm.builtin_runners = vec![output_builtin.into()]; let mut exec_scopes = ExecutionScopes::new(); let new_segment = vm.add_memory_segment(); let output_builtin_state = OutputBuiltinState { - base: new_segment.segment_index, + base: new_segment.segment_index.try_into().unwrap(), pages: Default::default(), attributes: Default::default(), }; @@ -573,7 +591,7 @@ mod tests { assert_eq!(vm.builtin_runners.len(), 1); match &vm.builtin_runners[0] { BuiltinRunner::Output(output_builtin) => { - assert_eq!(output_builtin.base(), output_builtin_state.base()); + assert_eq!(output_builtin.base(), output_builtin_state.base); } other => panic!("Expected an output builtin, found {:?}", other), } @@ -585,7 +603,7 @@ mod tests { let mut vm = vm!(); add_segments!(vm, 2); - vm.run_context.fp = 2; + vm.set_fp(2); let mut exec_scopes = ExecutionScopes::new(); let ids_data = ids_data!["bootloader_config"]; @@ -598,10 +616,7 @@ mod tests { let bootloader_config_segment = get_ptr_from_var_name("bootloader_config", &mut vm, &ids_data, &ap_tracking).unwrap(); - let config_segment = vm - .segments - .memory .get_continuous_range(bootloader_config_segment, 3) .unwrap(); @@ -622,8 +637,6 @@ mod tests { match programs_segment { MaybeRelocatable::RelocatableValue(relocatable) => { let program_hashes: Vec = vm - .segments - .memory .get_integer_range(relocatable.clone(), expected_nb_programs) .unwrap() .iter() @@ -643,6 +656,7 @@ mod tests { #[rstest] fn test_gen_arg() { + use std::ops::Add; let mut vm = vm!(); let mut nested_args = Vec::>::new(); @@ -657,8 +671,6 @@ mod tests { let args_base: Relocatable = gen_arg(&mut vm, &args).expect("gen_args failed unexpectedly"); let values = vm - .segments - .memory .get_integer_range(args_base, 2) .expect("Loading values failed"); @@ -667,14 +679,10 @@ mod tests { let nested_args_address: Relocatable = args_base.add(2i32).unwrap(); let nested_args_base = vm - .segments - .memory .get_relocatable(nested_args_address) .expect("Nested vector should be here"); let nested_values = vm - .segments - .memory .get_integer_range(nested_args_base, 2) .expect("Loading nested values failed"); @@ -686,10 +694,9 @@ mod tests { fn test_enter_packed_output_scope() { let mut vm = vm!(); // Set n_subtasks to 2 - vm.run_context.fp = 1; - vm.segments = segments![((1, 0), 2)]; - let ids_data = ids_data!["n_subtasks"]; - + vm.set_fp(1); + define_segments!(vm, 2, [((1, 0), 2)]); + let ids_data = HashMap::from([("n_subtasks".to_string(), HintReference::new_simple(-1))]); let ap_tracking = ApTracking::default(); let mut exec_scopes = ExecutionScopes::new(); @@ -729,11 +736,7 @@ mod tests { ) -> bool { exec_scopes.insert_value(vars::PACKED_OUTPUT, packed_output); is_plain_packed_output(vm, exec_scopes).expect("Hint failed unexpectedly"); - let result = vm - .segments - .memory - .get_integer(vm.run_context.get_ap()) - .unwrap(); + let result = vm.get_integer(vm.get_ap()).unwrap(); result.into_owned() != Felt252::from(0) } @@ -744,7 +747,8 @@ mod tests { assert!(is_plain(&mut vm, &mut exec_scopes, plain_packed_output)); // Increment AP to avoid an inconsistent memory error writing in the same slot - vm.run_context.ap += 1; + let new_ap = &vm.get_ap().offset + 1usize; + vm.set_ap(new_ap); assert!(!is_plain( &mut vm, &mut exec_scopes, @@ -755,18 +759,16 @@ mod tests { #[test] fn test_save_output_pointer() { let mut vm = vm!(); - vm.segments = segments![((1, 0), (0, 0))]; + define_segments!(vm, 2, [((1, 0), (0, 0))]); let mut hint_ref = HintReference::new(0, 0, true, false); hint_ref.offset2 = OffsetValue::Value(2); let ids_data = HashMap::from([("output_ptr".to_string(), hint_ref)]); let mut exec_scopes = ExecutionScopes::new(); - let hint_data = HintProcessorData::new_default( - String::from(hint_code::BOOTLOADER_SAVE_OUTPUT_POINTER), - ids_data, - ); - let mut hint_processor = BuiltinHintProcessor::new_empty(); + let hint_data = + HintProcessorData::new_default(String::from(BOOTLOADER_SAVE_OUTPUT_POINTER), ids_data); + let mut hint_processor = MinimalBootloaderHintProcessor::new(); assert_matches!( hint_processor.execute_hint( &mut vm, @@ -811,10 +813,10 @@ mod tests { exec_scopes.insert_box("bootloader_input", Box::new(bootloader_input.clone())); let hint_data = HintProcessorData::new_default( - String::from(hint_code::BOOTLOADER_SAVE_PACKED_OUTPUTS), + String::from(BOOTLOADER_SAVE_PACKED_OUTPUTS), HashMap::new(), ); - let mut hint_processor = BuiltinHintProcessor::new_empty(); + let mut hint_processor = MinimalBootloaderHintProcessor::new(); assert_matches!( hint_processor.execute_hint( &mut vm, @@ -877,8 +879,13 @@ mod tests { offset: 18 } ); + let pages = match vm.get_output_builtin_mut().unwrap().get_additional_data() { + BuiltinAdditionalData::Output(o) => o.pages, + _ => unreachable!("Type should be Output"), + }; + assert_eq!( - vm.get_output_builtin().unwrap().pages, + pages, HashMap::from([ (1, PublicMemoryPage { start: 2, size: 3 }), (2, PublicMemoryPage { start: 5, size: 1 }), @@ -905,10 +912,10 @@ mod tests { ); let hint_data = HintProcessorData::new_default( - String::from(hint_code::BOOTLOADER_SET_PACKED_OUTPUT_TO_SUBTASKS), + String::from(BOOTLOADER_SET_PACKED_OUTPUT_TO_SUBTASKS), HashMap::new(), ); - let mut hint_processor = BuiltinHintProcessor::new_empty(); + let mut hint_processor = MinimalBootloaderHintProcessor::new(); assert_matches!( hint_processor.execute_hint( &mut vm, @@ -927,7 +934,7 @@ mod tests { fn test_guess_pre_image_of_subtasks_output_hash() { let mut vm = vm!(); add_segments!(vm, 2); - vm.run_context.fp = 2; + vm.set_fp(2); let ids_data = ids_data!["nested_subtasks_output_len", "nested_subtasks_output"]; @@ -947,7 +954,7 @@ mod tests { run_hint!( vm, ids_data.clone(), - hint_code::BOOTLOADER_GUESS_PRE_IMAGE_OF_SUBTASKS_OUTPUT_HASH, + BOOTLOADER_GUESS_PRE_IMAGE_OF_SUBTASKS_OUTPUT_HASH, &mut exec_scopes ), Ok(()) @@ -955,7 +962,7 @@ mod tests { let nested_subtasks_output_len = get_integer_from_var_name("nested_subtasks_output_len", &vm, &ids_data, &ap_tracking) .expect("nested_subtasks_output_len should be set") - .into_owned(); + .to_owned(); assert_eq!(nested_subtasks_output_len, 1.into()); let nested_subtasks_output = @@ -988,7 +995,7 @@ mod tests { let mut vm = vm!(); add_segments!(vm, 2); - vm.run_context.fp = 2; + vm.set_fp(2); let ids_data = ids_data!(vars::PROGRAM_ADDRESS); let ap_tracking = ApTracking::new(); @@ -1019,7 +1026,7 @@ mod tests { let result = run_hint!( vm, ids_data.clone(), - hint_code::EXECUTE_TASK_ASSERT_PROGRAM_ADDRESS, + EXECUTE_TASK_ASSERT_PROGRAM_ADDRESS, &mut exec_scopes ); diff --git a/src/hints/execute_task_hints.rs b/src/hints/execute_task_hints.rs index 63423a2..c1becaa 100644 --- a/src/hints/execute_task_hints.rs +++ b/src/hints/execute_task_hints.rs @@ -314,9 +314,13 @@ pub fn write_return_builtins_hint( Ok(()) } -fn get_bootloader_program(exec_scopes: &ExecutionScopes) -> Result<&ProgramIdentifiers, HintError> { - if let Some(boxed_program) = exec_scopes.data[0].get(vars::BOOTLOADER_PROGRAM_IDENTIFIERS) { - if let Some(program) = boxed_program.downcast_ref::() { +fn get_bootloader_identifiers( + exec_scopes: &ExecutionScopes, +) -> Result<&ProgramIdentifiers, HintError> { + if let Some(bootloader_identifiers) = + exec_scopes.data[0].get(vars::BOOTLOADER_PROGRAM_IDENTIFIERS) + { + if let Some(program) = bootloader_identifiers.downcast_ref::() { return Ok(program); } } @@ -413,7 +417,7 @@ pub fn call_task( let program_address: Relocatable = exec_scopes.get("program_address")?; // ret_pc = ids.ret_pc_label.instruction_offset_ - ids.call_task.instruction_offset_ + pc - let bootloader_identifiers = get_bootloader_program(exec_scopes)?; + let bootloader_identifiers = get_bootloader_identifiers(exec_scopes)?; let ret_pc_label = get_identifier(bootloader_identifiers, "starkware.cairo.bootloaders.simple_bootloader.execute_task.execute_task.ret_pc_label")?; let call_task = get_identifier( bootloader_identifiers, @@ -484,23 +488,22 @@ mod util { #[cfg(test)] mod tests { - use std::path::Path; - use std::sync::Arc; - use assert_matches::assert_matches; - use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::{ - BuiltinHintProcessor, HintProcessorData, - }; - use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_maybe_relocatable_from_var_name; + use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; + + use cairo_vm::any_box; use cairo_vm::hint_processor::hint_processor_definition::HintProcessorLogic; - use cairo_vm::serde::deserialize_program::OffsetValue; + use cairo_vm::types::errors::math_errors::MathError; + use cairo_vm::types::program::Program; + use cairo_vm::types::relocatable::MaybeRelocatable; use cairo_vm::vm::runners::builtin_runner::BuiltinRunner; - use cairo_vm::vm::runners::cairo_pie::PublicMemoryPage; - use cairo_vm::{any_box, relocatable, Felt252}; - use num_traits::ToPrimitive; + use cairo_vm::vm::runners::cairo_pie::{BuiltinAdditionalData, PublicMemoryPage}; + use rstest::{fixture, rstest}; - use crate::hints::types::{BootloaderConfig, SimpleBootloaderInput}; + use crate::hints::codes::EXECUTE_TASK_CALL_TASK; + + use crate::{add_segments, define_segments, ids_data, non_continuous_ids_data, run_hint, vm}; use super::*; @@ -508,7 +511,7 @@ mod tests { fn test_allocate_program_data_segment() { let mut vm = vm!(); // Allocate space for program_data_ptr - vm.run_context.fp = 1; + vm.set_fp(1); add_segments!(vm, 2); let ids_data = ids_data!["program_data_ptr"]; let expected_program_data_segment_index = vm.segments.num_segments(); @@ -545,7 +548,8 @@ mod tests { #[fixture] fn fibonacci() -> Program { let program_content = - include_bytes!("../../../../../cairo_programs/fibonacci.json").to_vec(); + include_bytes!("../../dependencies/test-programs/cairo0/fibonacci/fibonacci.json") + .to_vec(); Program::from_bytes(&program_content, Some("main")) .expect("Loading example program failed unexpectedly") @@ -553,15 +557,18 @@ mod tests { #[fixture] fn fibonacci_pie() -> CairoPie { - let pie_file = - Path::new("../cairo_programs/manually_compiled/fibonacci_cairo_pie/fibonacci_pie.zip"); - CairoPie::from_file(pie_file).expect("Failed to load the program PIE") + let pie_content = include_bytes!( + "../../dependencies/test-programs/bootloader/pies/fibonacci/cairo_pie.zip" + ); + CairoPie::from_bytes(pie_content).expect("Failed to load the program PIE") } #[fixture] fn field_arithmetic_program() -> Program { - let program_content = - include_bytes!("../../../../../cairo_programs/field_arithmetic.json").to_vec(); + let program_content = include_bytes!( + "../../dependencies/test-programs/cairo0/field-arithmetic/field_arithmetic.json" + ) + .to_vec(); Program::from_bytes(&program_content, Some("main")) .expect("Loading example program failed unexpectedly") @@ -572,9 +579,9 @@ mod tests { let task = Task::Program(fibonacci.clone()); let mut vm = vm!(); - vm.run_context.fp = 1; + vm.set_fp(1); // Set program_header_ptr to (2, 0) - vm.segments = segments![((1, 0), (2, 0))]; + define_segments!(vm, 2, [((1, 0), (2, 0))]); let program_header_ptr = Relocatable::from((2, 0)); add_segments!(vm, 1); @@ -593,13 +600,14 @@ mod tests { // The Fibonacci program has no builtins -> the header size is 4 let header_size = 4; - let expected_code_address = &program_header_ptr + header_size; + let expected_code_address: Result = + program_header_ptr + header_size; let program_address: Relocatable = exec_scopes.get(vars::PROGRAM_ADDRESS).unwrap(); - assert_eq!(program_address, expected_code_address); + assert_eq!(program_address, expected_code_address.unwrap()); // Check that the segment was finalized - let expected_program_size = header_size + fibonacci.shared_program_data.data.len(); + let expected_program_size = header_size + fibonacci.data_len(); assert_eq!( vm.segments.segment_sizes[&(program_address.segment_index as usize)], expected_program_size @@ -612,8 +620,8 @@ mod tests { // Allocate space for pre-execution (8 felts), which mimics the `BuiltinData` struct in the // Bootloader's Cairo code. Our code only uses the first felt (`output` field in the struct) - vm.segments = segments![((1, 0), (2, 0))]; - vm.run_context.fp = 8; + define_segments!(vm, 2, [((1, 0), (2, 0))]); + vm.set_fp(8); add_segments!(vm, 1); let ids_data = non_continuous_ids_data![(vars::PRE_EXECUTION_BUILTIN_PTRS, -8)]; @@ -632,7 +640,7 @@ mod tests { run_hint!( vm, ids_data.clone(), - hint_code::EXECUTE_TASK_CALL_TASK, + EXECUTE_TASK_CALL_TASK, &mut exec_scopes ), Ok(()) @@ -644,7 +652,7 @@ mod tests { /// * a `HasIdentifiers` trait cannot be used as exec_scopes requires to cast to `Box`, /// making casting back to the trait impossible. /// * using an enum requires defining test-only variants. - fn mock_program_with_identifiers(symbols: HashMap) -> Program { + fn mock_program_identifiers(symbols: HashMap) -> ProgramIdentifiers { let identifiers = symbols .into_iter() .map(|(name, pc)| { @@ -662,17 +670,7 @@ mod tests { }) .collect(); - let shared_program_data = SharedProgramData { - identifiers, - ..Default::default() - }; - let program = Program { - shared_program_data: Arc::new(shared_program_data), - constants: Default::default(), - builtins: vec![], - }; - - program + identifiers } #[rstest] @@ -684,10 +682,13 @@ mod tests { // the Bootloader Cairo code. Our code only uses the first felt (`output` field in the // struct). Finally, we put the mocked output of `select_input_builtins` in the next // memory address and increase the AP register accordingly. - vm.segments = segments![((1, 0), (2, 0)), ((1, 1), (4, 0)), ((1, 9), (4, 42))]; - vm.run_context.ap = 10; - vm.run_context.fp = 9; - add_segments!(vm, 3); + define_segments!( + vm, + 4, + [((1, 0), (2, 0)), ((1, 1), (4, 0)), ((1, 9), (4, 42))] + ); + vm.set_ap(10); + vm.set_fp(9); let program_header_ptr = Relocatable::from((2, 0)); let ids_data = non_continuous_ids_data![ @@ -711,9 +712,9 @@ mod tests { ("starkware.cairo.bootloaders.simple_bootloader.execute_task.execute_task.call_task".to_string(), 8usize) ] ); - let bootloader_program = mock_program_with_identifiers(bootloader_identifiers); + let program_identifiers = mock_program_identifiers(bootloader_identifiers); exec_scopes.insert_value(vars::PROGRAM_DATA_BASE, program_header_ptr.clone()); - exec_scopes.insert_value(vars::BOOTLOADER_PROGRAM_IDENTIFIERS, bootloader_program); + exec_scopes.insert_value(vars::BOOTLOADER_PROGRAM_IDENTIFIERS, program_identifiers); // Load the program in memory load_program_hint(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking) @@ -734,18 +735,18 @@ mod tests { // The pre-execution struct starts at (1, 0) and the return struct at (1, 8). // We only set the output values to (2, 0) and (2, 10), respectively, to get an output size // of 10. - vm.segments = segments![((1, 0), (2, 0)), ((1, 8), (2, 10)),]; - vm.run_context.fp = 16; + define_segments!(vm, 2, [((1, 0), (2, 0)), ((1, 8), (2, 10)),]); + vm.set_fp(16); add_segments!(vm, 1); let tree_structure = vec![1, 2, 3, 4]; - let program_output_data = OutputBuiltinAdditionalData { - base: 0, + let program_output_data = OutputBuiltinState { pages: HashMap::from([ (1, PublicMemoryPage { start: 0, size: 7 }), (2, PublicMemoryPage { start: 7, size: 3 }), ]), attributes: HashMap::from([("gps_fact_topology".to_string(), tree_structure.clone())]), + base: 0, }; let mut output_builtin = OutputBuiltinRunner::new(true); output_builtin.set_state(program_output_data.clone()); @@ -762,10 +763,10 @@ mod tests { let mut exec_scopes = ExecutionScopes::new(); - let output_runner_data = OutputBuiltinAdditionalData { - base: 0, + let output_runner_data = OutputBuiltinState { pages: HashMap::new(), attributes: HashMap::new(), + base: 0, }; exec_scopes.insert_value(vars::OUTPUT_RUNNER_DATA, Some(output_runner_data.clone())); exec_scopes.insert_value(vars::TASK, task); @@ -783,10 +784,11 @@ mod tests { assert_eq!(fact_topology.tree_structure, tree_structure); // Check that the output builtin was updated - let output_builtin_additional_data = vm.get_output_builtin().unwrap().get_additional_data(); + let output_builtin_additional_data = + vm.get_output_builtin_mut().unwrap().get_additional_data(); assert!(matches!( output_builtin_additional_data, - BuiltinAdditionalData::Output(data) if data == output_runner_data, + BuiltinAdditionalData::Output(data) if data.pages == output_runner_data.pages && data.attributes == output_runner_data.attributes, )); } @@ -802,20 +804,24 @@ mod tests { // Initialize the used builtins to {range_check: 30, bitwise: 50} as these two // are used by the field arithmetic program. Note that the used builtins list // does not contain empty elements (i.e. offsets are 8 and 9 instead of 10 and 12). - vm.segments = segments![ - ((1, 0), (2, 1)), - ((1, 1), (2, 2)), - ((1, 2), (2, 3)), - ((1, 3), (2, 4)), - ((1, 4), (2, 5)), - ((1, 5), (2, 6)), - ((1, 6), (2, 7)), - ((1, 7), (2, 8)), - ((1, 8), (2, 30)), - ((1, 9), (2, 50)), - ((1, 24), (1, 8)), - ]; - vm.run_context.fp = 25; + define_segments!( + vm, + 2, + [ + ((1, 0), (2, 1)), + ((1, 1), (2, 2)), + ((1, 2), (2, 3)), + ((1, 3), (2, 4)), + ((1, 4), (2, 5)), + ((1, 5), (2, 6)), + ((1, 6), (2, 7)), + ((1, 7), (2, 8)), + ((1, 8), (2, 30)), + ((1, 9), (2, 50)), + ((1, 24), (1, 8)), + ] + ); + vm.set_fp(25); add_segments!(vm, 1); // Note that used_builtins_addr is a pointer to the used builtins list at (1, 8) @@ -827,7 +833,7 @@ mod tests { let ap_tracking = ApTracking::new(); let mut exec_scopes = ExecutionScopes::new(); - let n_builtins = field_arithmetic_program.builtins.len(); + let n_builtins = field_arithmetic_program.builtins_len(); exec_scopes.insert_value(vars::N_BUILTINS, n_builtins); exec_scopes.insert_value(vars::TASK, task); @@ -836,8 +842,6 @@ mod tests { // Check that the return builtins were written correctly let return_builtins = vm - .segments - .memory .get_continuous_range(Relocatable::from((1, 16)), 8) .expect("Return builtin was not properly written to memory."); diff --git a/src/hints/fact_topologies.rs b/src/hints/fact_topologies.rs index d3b851a..1ad70ff 100644 --- a/src/hints/fact_topologies.rs +++ b/src/hints/fact_topologies.rs @@ -154,6 +154,7 @@ pub fn compute_fact_topologies<'a>( /// * `output_start`: Start of the output range for this fact topology. /// /// Reimplements the following Python code: +/// ```no-run /// offset = 0 /// for i, page_size in enumerate(fact_topology.page_sizes): /// output_builtin.add_page( @@ -162,6 +163,7 @@ pub fn compute_fact_topologies<'a>( /// offset += page_size /// /// return len(fact_topology.page_sizes) +/// ``` fn add_consecutive_output_pages( fact_topology: &FactTopology, output_builtin: &mut OutputBuiltinRunner, diff --git a/src/hints/inner_select_builtins.rs b/src/hints/inner_select_builtins.rs index e6936fa..d1fc033 100644 --- a/src/hints/inner_select_builtins.rs +++ b/src/hints/inner_select_builtins.rs @@ -65,6 +65,8 @@ mod tests { use cairo_vm::Felt252; use rstest::rstest; + use crate::{add_segments, define_segments, ids_data, vm}; + use super::*; #[rstest] @@ -81,14 +83,18 @@ mod tests { builtin_value + 1 }; - vm.segments = segments![ - ((1, 0), (2, 0)), - ((1, 1), (2, 1)), - ((2, 0), builtin_value), - ((2, 1), expected_value) - ]; + define_segments!( + vm, + 3, + [ + ((1, 0), (2, 0)), + ((1, 1), (2, 1)), + ((2, 0), builtin_value), + ((2, 1), expected_value) + ] + ); // Allocate space for program_data_ptr - vm.run_context.fp = 3; + vm.set_fp(3); add_segments!(vm, 2); let ids_data = ids_data!["selected_encodings", "all_encodings", "select_builtin"]; let ap_tracking = ApTracking::new(); @@ -102,7 +108,7 @@ mod tests { let select_builtin = get_integer_from_var_name("select_builtin", &vm, &ids_data, &ap_tracking) .unwrap() - .into_owned(); + .to_owned(); let n_selected_builtins: usize = exec_scopes.get(vars::N_SELECTED_BUILTINS).unwrap(); if (n_builtins != 0) && should_select_builtin { diff --git a/src/hints/program_hash.rs b/src/hints/program_hash.rs index ac50c60..d8a01ec 100644 --- a/src/hints/program_hash.rs +++ b/src/hints/program_hash.rs @@ -84,10 +84,10 @@ fn maybe_relocatable_to_field_element( felt_to_field_element(felt) } -#[allow(dead_code)] // TODO: remove /// Computes the Pedersen hash of a program. /// /// Reimplements this Python function: +/// ```no-run /// def compute_program_hash_chain(program: ProgramBase, bootloader_version=0): /// builtin_list = [from_bytes(builtin.encode("ascii")) for builtin in program.builtins] /// # The program header below is missing the data length, which is later added to the data_chain. @@ -95,6 +95,7 @@ fn maybe_relocatable_to_field_element( /// data_chain = program_header + program.data /// /// return compute_hash_chain([len(data_chain)] + data_chain) +/// ``` pub fn compute_program_hash_chain( program: &StrippedProgram, bootloader_version: usize, @@ -168,16 +169,16 @@ mod tests { #[rstest] // Expected hashes generated with `cairo-hash-program` #[case::fibonacci( - "../cairo_programs/fibonacci.json", - "0x43b17e9592f33142246af4c06cd2b574b460dd1f718d76b51341175a62b220f" + "./dependencies/test-programs/cairo0/fibonacci/fibonacci.json", + "0x6fc56a47599a5cc20bb3c6d4c5397f872bb6269f036e383f4c13986d4020952" )] #[case::field_arithmetic( - "../cairo_programs/field_arithmetic.json", - "0x1031772ca86e618b058101af9c9a3277bac90712b750bcea1cc69d6c7cad8a7" + "./dependencies/test-programs/cairo0/field-arithmetic/field_arithmetic.json", + "0xdc5a7432daec36bb707aa9f8cbcd60a2c5a4f5b16dbe7a4b6d96d5bfdd2a43" )] #[case::keccak_copy_inputs( - "../cairo_programs/keccak_copy_inputs.json", - "0x49484fdc8e7a85061f9f21b7e21fe276d8a88c8e96681101a2518809e686c6c" + "./dependencies/test-programs/cairo0/keccak-copy-inputs/keccak_copy_inputs.json", + "0x79e69539b9bbcc863519fb17f864c3439277cd851146f30d1ce0232fb358632" )] fn test_compute_program_hash_chain( #[case] program_path: PathBuf, diff --git a/src/hints/program_loader.rs b/src/hints/program_loader.rs index e36e178..bbdadd7 100644 --- a/src/hints/program_loader.rs +++ b/src/hints/program_loader.rs @@ -165,19 +165,15 @@ impl<'vm> ProgramLoader<'vm> { #[cfg(test)] mod tests { - use std::any::Any; - use cairo_vm::types::builtin_name::BuiltinName; use cairo_vm::types::program::Program; use cairo_vm::types::relocatable::Relocatable; use cairo_vm::vm::runners::cairo_pie::StrippedProgram; use cairo_vm::vm::vm_memory::memory_segments::MemorySegmentManager; use cairo_vm::Felt252; - use num_traits::ToPrimitive; use rstest::{fixture, rstest}; - use serde::Serialize; - use crate::hints::types::BootloaderVersion; + use crate::{add_segments, hints::types::BootloaderVersion}; use super::*; @@ -242,9 +238,9 @@ mod tests { #[fixture] fn fibonacci() -> Program { let program_content = - include_bytes!("../../../../../cairo_programs/fibonacci.json").to_vec(); + include_bytes!("../../dependencies/test-programs/cairo0/fibonacci/fibonacci.json"); - Program::from_bytes(&program_content, Some("main")) + Program::from_bytes(program_content, Some("main")) .expect("Loading example program failed unexpectedly") } @@ -282,6 +278,7 @@ mod tests { let program = fibonacci.get_stripped_program().unwrap(); let mut vm = VirtualMachine::new(false); + add_segments!(vm, 2); let mut segments = MemorySegmentManager::new(); let base_address = segments.add(); @@ -295,7 +292,7 @@ mod tests { check_loaded_header(&vm, base_address.clone(), &program, bootloader_version); - let builtin_list_ptr = (base_address + builtins_offset)?; + let builtin_list_ptr = (base_address + builtins_offset).unwrap(); check_loaded_builtins(&vm, &vec![], builtin_list_ptr); } @@ -318,6 +315,7 @@ mod tests { let program = fibonacci.get_stripped_program().unwrap(); let mut vm = VirtualMachine::new(false); + add_segments!(vm, 2); let mut segments = MemorySegmentManager::new(); let base_address = segments.add(); diff --git a/src/hints/select_builtins.rs b/src/hints/select_builtins.rs index dcb2666..bc97828 100644 --- a/src/hints/select_builtins.rs +++ b/src/hints/select_builtins.rs @@ -39,17 +39,13 @@ pub fn select_builtins_enter_scope( #[cfg(test)] mod tests { - use std::any::Any; - use std::collections::HashMap; - use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_integer_from_var_name; - use cairo_vm::hint_processor::hint_processor_definition::HintReference; use cairo_vm::serde::deserialize_program::ApTracking; use cairo_vm::types::exec_scope::ExecutionScopes; - use cairo_vm::vm::errors::hint_errors::HintError; use cairo_vm::vm::vm_core::VirtualMachine; - use num_traits::ToPrimitive; - use serde::Serialize; + use std::collections::HashMap; + + use crate::{define_segments, ids_data, vm}; use super::*; @@ -57,8 +53,8 @@ mod tests { fn test_select_builtins_enter_scope() { let mut vm = vm!(); // Set n_selected_builtins to 7 - vm.run_context.fp = 1; - vm.segments = segments![((1, 0), 7)]; + vm.set_fp(1); + define_segments!(vm, 2, [((1, 0), 7)]); let ids_data = ids_data![vars::N_SELECTED_BUILTINS]; let n_selected_builtins = 7usize; diff --git a/src/hints/simple_bootloader_hints.rs b/src/hints/simple_bootloader_hints.rs index c473226..3c67f94 100644 --- a/src/hints/simple_bootloader_hints.rs +++ b/src/hints/simple_bootloader_hints.rs @@ -131,45 +131,36 @@ pub fn set_current_task( #[cfg(test)] mod tests { - use std::any::Any; - use std::collections::HashMap; use std::collections::HashMap; use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::{ - get_ptr_from_var_name, get_relocatable_from_var_name, insert_value_from_var_name, + get_ptr_from_var_name, insert_value_from_var_name, }; - use cairo_vm::hint_processor::hint_processor_definition::HintReference; - use cairo_vm::serde::deserialize_program::{ApTracking, BuiltinName, Identifier}; + + use cairo_vm::serde::deserialize_program::ApTracking; use cairo_vm::types::exec_scope::ExecutionScopes; use cairo_vm::types::program::Program; - use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable}; - use cairo_vm::vm::errors::hint_errors::HintError; - use cairo_vm::vm::errors::memory_errors::MemoryError; - use cairo_vm::vm::runners::builtin_runner::OutputBuiltinRunner; - use cairo_vm::vm::runners::cairo_pie::{ - CairoPie, OutputBuiltinAdditionalData, StrippedProgram, - }; + use cairo_vm::types::relocatable::Relocatable; + use cairo_vm::vm::vm_core::VirtualMachine; - use cairo_vm::vm::vm_memory::memory::Memory; - use cairo_vm::{any_box, Felt252}; - use num_traits::ToPrimitive; + + use cairo_vm::Felt252; use num_traits::ToPrimitive; use rstest::{fixture, rstest}; - use starknet_crypto::FieldElement; - use crate::hints::fact_topologies::{get_task_fact_topology, FactTopology}; - use crate::hints::load_cairo_pie::load_cairo_pie; - use crate::hints::program_hash::compute_program_hash_chain; - use crate::hints::program_loader::ProgramLoader; - use crate::hints::types::{BootloaderVersion, Task, TaskSpec}; + use crate::hints::fact_topologies::FactTopology; + + use crate::hints::types::{Task, TaskSpec}; use crate::hints::vars; + use crate::{add_segments, define_segments, ids_data, vm}; use super::*; #[fixture] fn fibonacci() -> Program { let program_content = - include_bytes!("../../../../../cairo_programs/fibonacci.json").to_vec(); + include_bytes!("../../dependencies/test-programs/cairo0/fibonacci/fibonacci.json") + .to_vec(); Program::from_bytes(&program_content, Some("main")) .expect("Loading example program failed unexpectedly") @@ -194,8 +185,8 @@ mod tests { #[rstest] fn test_prepare_task_range_checks(simple_bootloader_input: SimpleBootloaderInput) { let mut vm = vm!(); - vm.run_context.fp = 3; - vm.segments = segments![((1, 0), (2, 0)), ((1, 1), (2, 2))]; + vm.set_fp(3); + define_segments!(vm, 2, [((1, 0), (2, 0)), ((1, 1), (2, 2))]); let ids_data = ids_data!["output_ptr", "range_check_ptr", "task_range_check_ptr"]; vm.add_memory_segment(); @@ -216,8 +207,6 @@ mod tests { // Assert *output_ptr == n_tasks let output = vm - .segments - .memory .get_integer(Relocatable { segment_index: 2, offset: 0, @@ -265,9 +254,9 @@ mod tests { let expected_num_felt = Felt252::from(expected); let mut vm = vm!(); + vm.set_ap(1); + vm.set_fp(1); add_segments!(vm, 2); - vm.run_context.ap = 1; - vm.run_context.fp = 1; let ids_data = ids_data!["num"]; let ap_tracking = ApTracking::new(); @@ -276,11 +265,7 @@ mod tests { divide_num_by_2(&mut vm, &ids_data, &ap_tracking).expect("Hint failed unexpectedly"); - let divided_num = vm - .segments - .memory - .get_integer(vm.run_context.get_ap()) - .unwrap(); + let divided_num = vm.get_integer(vm.get_ap()).unwrap(); assert_eq!(divided_num.into_owned(), expected_num_felt); } @@ -291,12 +276,7 @@ mod tests { set_ap_to_zero(&mut vm).expect("Hint failed unexpectedly"); - let ap_value = vm - .segments - .memory - .get_integer(vm.run_context.get_ap()) - .unwrap() - .into_owned(); + let ap_value = vm.get_integer(vm.get_ap()).unwrap().into_owned(); assert_eq!(ap_value, Felt252::from(0)); } @@ -305,15 +285,14 @@ mod tests { fn test_set_current_task(simple_bootloader_input: SimpleBootloaderInput) { // Set n_tasks to 1 let mut vm = vm!(); - vm.run_context.fp = 2; - vm.segments = segments![((1, 0), 1)]; + vm.set_fp(2); + define_segments!(vm, 2, [((1, 0), 1)]); let mut exec_scopes = ExecutionScopes::new(); exec_scopes.insert_value(vars::SIMPLE_BOOTLOADER_INPUT, simple_bootloader_input); let ids_data = ids_data!["n_tasks", "task"]; let ap_tracking = ApTracking::new(); - set_current_task(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking) .expect("Hint failed unexpectedly"); diff --git a/src/hints/vars.rs b/src/hints/vars.rs index dd25c27..63b7dbc 100644 --- a/src/hints/vars.rs +++ b/src/hints/vars.rs @@ -2,7 +2,7 @@ pub const BOOTLOADER_INPUT: &str = "bootloader_input"; /// The bootloader program, as a Program object. -pub const BOOTLOADER_PROGRAM_IDENTIFIERS: &str = "bootloader_program"; +pub const BOOTLOADER_PROGRAM_IDENTIFIERS: &str = "bootloader_program_identifiers"; /// Saved state of the output builtin. pub const OUTPUT_BUILTIN_STATE: &str = "output_builtin_state"; diff --git a/src/lib.rs b/src/lib.rs index 5a49bee..11523f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ use cairo_vm::types::exec_scope::ExecutionScopes; - pub use hints::*; pub mod bootloaders; mod hints; pub mod tasks; +#[cfg(test)] +pub mod macros; + /// Inserts the bootloader input in the execution scopes. pub fn insert_bootloader_input( exec_scopes: &mut ExecutionScopes, diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..f057a27 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,115 @@ +#[macro_export] +macro_rules! vm { + () => { + VirtualMachine::new(false) // Default to false if no argument is provided + }; +} + +#[macro_export] +macro_rules! add_segments { + ($vm:expr, $n:expr) => { + for _ in 0..$n { + $vm.add_memory_segment(); + } + }; +} + +#[macro_export] +macro_rules! ids_data { + ( $( $key:expr ),* ) => {{ + let mut map = HashMap::::new(); + let ids_names = vec![$( $key ),*]; + let ids_len = ids_names.len() as i32; + let mut _value = -ids_len; + $( + map.insert($key.to_string(), cairo_vm::hint_processor::hint_processor_definition::HintReference::new_simple(_value)); + _value += 1; + )* + map + }}; +} + +#[macro_export] +macro_rules! non_continuous_ids_data { + ( $( ($name: expr, $offset:expr) ),* $(,)? ) => { + { + let mut ids_data = cairo_vm::stdlib::collections::HashMap::::new(); + $( + ids_data.insert(cairo_vm::stdlib::string::String::from($name), HintReference::new_simple($offset)); + )* + ids_data + } + }; +} + +#[macro_export] +macro_rules! insert_value_inner { + ($vm:expr, ($si:expr, $off:expr), ($sival:expr, $offval: expr)) => { + let (k, v) = ( + ($si, $off).into(), + &$crate::mayberelocatable!($sival, $offval), + ); + $vm.insert_value(k, v).unwrap(); + }; + ($vm:expr, ($si:expr, $off:expr), $val:expr) => { + let (k, v) = (($si, $off).into(), &$crate::mayberelocatable!($val)); + $vm.insert_value(k, v).unwrap(); + }; +} + +#[macro_export] +macro_rules! mayberelocatable { + ($val1 : expr, $val2 : expr) => { + cairo_vm::types::relocatable::MaybeRelocatable::from(($val1, $val2)) + }; + ($val1 : expr) => { + cairo_vm::types::relocatable::MaybeRelocatable::from(cairo_vm::Felt252::from($val1 as i128)) + }; +} + +#[macro_export] +macro_rules! define_segments { + ($vm:ident, $count:expr, [$( (($seg1:expr, $off1:expr), $val:tt) ),* $(,)?]) => { + for _ in 0..$count { + $vm.add_memory_segment(); + } + $( + $crate::insert_value_inner!($vm, ($seg1, $off1), $val); + )* + }; +} + +#[macro_export] +macro_rules! run_hint { + ($vm:expr, $ids_data:expr, $hint_code:expr, $exec_scopes:expr, $constants:expr) => {{ + let hint_data = HintProcessorData::new_default($hint_code.to_string(), $ids_data); + let mut hint_processor = $crate::MinimalBootloaderHintProcessor::new(); + hint_processor.execute_hint(&mut $vm, $exec_scopes, &any_box!(hint_data), $constants) + }}; + ($vm:expr, $ids_data:expr, $hint_code:expr, $exec_scopes:expr) => {{ + let hint_data = HintProcessorData::new_default( + cairo_vm::stdlib::string::ToString::to_string($hint_code), + $ids_data, + ); + let mut hint_processor = $crate::MinimalBootloaderHintProcessor::new(); + hint_processor.execute_hint( + &mut $vm, + $exec_scopes, + &any_box!(hint_data), + &cairo_vm::stdlib::collections::HashMap::new(), + ) + }}; + ($vm:expr, $ids_data:expr, $hint_code:expr) => {{ + let hint_data = HintProcessorData::new_default( + $crate::stdlib::string::ToString::to_string($hint_code), + $ids_data, + ); + let mut hint_processor = $crate::MinimalBootloaderHintProcessor::new(); + hint_processor.execute_hint( + &mut $vm, + exec_scopes_ref!(), + &any_box!(hint_data), + &$crate::stdlib::collections::HashMap::new(), + ) + }}; +}