diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 98318a8e3c..78c54e0454 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -737,7 +737,6 @@ jobs: path: lcov-test-no_std#4-cairo-0-secp-hints.info key: codecov-cache-test-no_std#4-cairo-0-secp-hints-${{ github.sha }} fail-on-cache-miss: true - - name: Upload coverage to codecov.io uses: codecov/codecov-action@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index dab70f72f6..7fe623c736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ #### Upcoming Changes +* feat: Use BTreeMap in PIE additional data [#2162](https://github.com/lambdaclass/cairo-vm/pull/2162) + +* feat: Remove prover input info struct and add getters instead [#2149](https://github.com/lambdaclass/cairo-vm/pull/2149) + +* feat: Added support for large files in PIE [#2136](https://github.com/lambdaclass/cairo-vm/pull/2136) + +* feat: Disable relocate trace with flag [#2133](https://github.com/lambdaclass/cairo-vm/pull/2133) + +* feat: Enable using secure run in proof mode [#2113](https://github.com/lambdaclass/cairo-vm/pull/2113) + +* [BREAKING] Compute missing builtin cells only in proof mode [#2088](https://github.com/lambdaclass/cairo-vm/pull/2088) + +* test: Add test for filling holes in builtin segments [#2087](https://github.com/lambdaclass/cairo-vm/pull/2087) + +* fix: Removed memory comparison test with Python VM in proof mode, since the new hole-filling logic causes divergence.[#2086](https://github.com/lambdaclass/cairo-vm/pull/2086) + +* refactor: Use BTreeMap for deterministic order of PIE keys [#2085](https://github.com/lambdaclass/cairo-vm/pull/2085) + +* fix: Fix zero offset output base assumption [#2068](https://github.com/lambdaclass/cairo-vm/pull/2068) + +* feat: Add perpetual and dex with bitwise layouts [#2067](https://github.com/lambdaclass/cairo-vm/pull/2067) + +* feat: Fill holes in builtins segments to save computation in the prover [#2036](https://github.com/lambdaclass/cairo-vm/pull/2036) + +* feat: Added hints felt unpacking for blake [#2032](https://github.com/lambdaclass/cairo-vm/pull/2032) + * dev: make `VirtualMachine::get_traceback_entries` pub * chore: Pin types-rs version to the one set in lockfile [#2140](https://github.com/lambdaclass/cairo-vm/pull/2140) diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index daaf1e1712..49d51a5489 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -180,12 +180,13 @@ fn run(args: impl Iterator) -> Result<(), Error> { entrypoint: &args.entrypoint, trace_enabled, relocate_mem: args.memory_file.is_some() || args.air_public_input.is_some(), + relocate_trace: trace_enabled, layout: args.layout, proof_mode: args.proof_mode, secure_run: args.secure_run, allow_missing_builtins: args.allow_missing_builtins, dynamic_layout_params: cairo_layout_params, - ..Default::default() + disable_trace_padding: false, }; let mut cairo_runner = match if args.run_from_cairo_pie { diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index fbe0760d94..51418afbc3 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -267,7 +267,12 @@ pub fn cairo_run_program( runner.run_for_steps(1, &mut hint_processor)?; } - runner.end_run(false, false, &mut hint_processor)?; + runner.end_run( + false, + false, + &mut hint_processor, + cairo_run_config.proof_mode, + )?; let result_inner_type_size = result_inner_type_size(return_type_id, &sierra_program_registry, &type_sizes); @@ -336,7 +341,7 @@ pub fn cairo_run_program( } } - runner.relocate(true)?; + runner.relocate(true, true)?; Ok((runner, return_values, serialized_output)) } diff --git a/cairo_programs/poseidon_builtin_hole.cairo b/cairo_programs/poseidon_builtin_hole.cairo new file mode 100644 index 0000000000..7835eae950 --- /dev/null +++ b/cairo_programs/poseidon_builtin_hole.cairo @@ -0,0 +1,13 @@ +%builtins poseidon +from starkware.cairo.common.cairo_builtins import PoseidonBuiltin +from starkware.cairo.common.poseidon_state import PoseidonBuiltinState + +func main{poseidon_ptr: PoseidonBuiltin*}() { + assert poseidon_ptr[0].input = PoseidonBuiltinState(1, 2, 3); + let result = poseidon_ptr[0].output; + let poseidon_ptr = poseidon_ptr + PoseidonBuiltin.SIZE; + // Note that we are not accesing result.s1. + assert result.s0 = 442682200349489646213731521593476982257703159825582578145778919623645026501; + assert result.s2 = 2512222140811166287287541003826449032093371832913959128171347018667852712082; + return (); +} diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index eb63334e4c..b07f078de7 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -27,7 +27,10 @@ pub struct CairoRunConfig<'a> { #[cfg_attr(feature = "test_utils", arbitrary(value = "main"))] pub entrypoint: &'a str, pub trace_enabled: bool, + /// Relocate memory if `true`, otherwise memory is not relocated. pub relocate_mem: bool, + // When `relocate_trace` is set to `false`, the trace will not be relocated even if `trace_enabled` is `true`. + pub relocate_trace: bool, pub layout: LayoutName, /// The `dynamic_layout_params` argument should only be used with dynamic layout. /// It is ignored otherwise. @@ -51,6 +54,8 @@ impl Default for CairoRunConfig<'_> { entrypoint: "main", trace_enabled: false, relocate_mem: false, + // Set to true to match expected behavior: trace is relocated only if trace_enabled is true. + relocate_trace: true, layout: LayoutName::plain, proof_mode: false, secure_run: None, @@ -104,9 +109,9 @@ pub fn cairo_run_program_with_initial_scope( cairo_run_config.disable_trace_padding, false, hint_processor, + cairo_run_config.proof_mode, )?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments()?; @@ -114,7 +119,10 @@ pub fn cairo_run_program_with_initial_scope( if secure_run { verify_secure_runner(&cairo_runner, true, None)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -213,9 +221,9 @@ pub fn cairo_run_pie( cairo_run_config.disable_trace_padding, false, hint_processor, + cairo_run_config.proof_mode, )?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if secure_run { @@ -223,7 +231,10 @@ pub fn cairo_run_pie( // Check that the Cairo PIE produced by this run is compatible with the Cairo PIE received cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -264,9 +275,8 @@ pub fn cairo_run_fuzzed_program( res.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?; - cairo_runner.end_run(false, false, hint_processor)?; + cairo_runner.end_run(false, false, hint_processor, cairo_run_config.proof_mode)?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments()?; @@ -274,7 +284,10 @@ pub fn cairo_run_fuzzed_program( if secure_run { verify_secure_runner(&cairo_runner, true, None)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -375,7 +388,7 @@ mod tests { let end = cairo_runner.initialize(false).unwrap(); assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok()); - assert!(cairo_runner.relocate(true).is_ok()); + assert!(cairo_runner.relocate(true, true).is_ok()); // `main` returns without doing nothing, but `not_main` sets `[ap]` to `1` // Memory location was found empirically and simply hardcoded assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123))); @@ -441,7 +454,7 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap(); - assert!(cairo_runner.relocate(false).is_ok()); + assert!(cairo_runner.relocate(false, true).is_ok()); let trace_entries = cairo_runner.relocated_trace.unwrap(); let mut buffer = [0; 24]; @@ -465,7 +478,7 @@ mod tests { let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap(); // relocate memory so we can dump it to file - assert!(cairo_runner.relocate(true).is_ok()); + assert!(cairo_runner.relocate(true, true).is_ok()); let mut buffer = [0; 120]; let mut buff_writer = SliceWriter::new(&mut buffer); @@ -489,7 +502,7 @@ mod tests { let mut cairo_runner = cairo_runner!(program); let end = cairo_runner.initialize(false).unwrap(); assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok()); - assert!(cairo_runner.relocate(false).is_ok()); + assert!(cairo_runner.relocate(false, false).is_ok()); assert!(cairo_runner.relocated_trace.is_none()); } diff --git a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs index 3251cff0e8..8dfcedf030 100644 --- a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs @@ -1,3 +1,4 @@ +use crate::hint_processor::hint_processor_utils::felt_to_usize; use crate::stdlib::{borrow::Cow, collections::HashMap, prelude::*}; use crate::types::errors::math_errors::MathError; @@ -17,9 +18,11 @@ use crate::{ vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; +use num_bigint::BigUint; +use num_integer::Integer; use num_traits::ToPrimitive; -use super::hint_utils::get_integer_from_var_name; +use super::hint_utils::{get_integer_from_var_name, insert_value_into_ap}; fn get_fixed_size_u32_array( h_range: &Vec>, @@ -242,6 +245,84 @@ pub fn blake2s_add_uint256_bigend( Ok(()) } +/* Implements Hint: +memory[ap] = (ids.end != ids.packed_values) and (memory[ids.packed_values] < 2**63) +*/ +pub fn is_less_than_63_bits_and_not_end( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let end = get_ptr_from_var_name("end", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + + if end == packed_values { + insert_value_into_ap(vm, 0)? + } else { + let val = vm.get_integer(packed_values)?; + insert_value_into_ap( + vm, + (val.to_biguint() < (BigUint::from(1_u32) << 63)) as usize, + )? + } + Ok(()) +} + +/* Implements Hint: +offset = 0 +for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len +*/ +pub fn blake2s_unpack_felts( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let packed_values_len = + get_integer_from_var_name("packed_values_len", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + let unpacked_u32s = get_ptr_from_var_name("unpacked_u32s", vm, ids_data, ap_tracking)?; + + let vals = vm.get_integer_range(packed_values, felt_to_usize(&packed_values_len)?)?; + let pow2_32 = BigUint::from(1_u32) << 32; + let pow2_63 = BigUint::from(1_u32) << 63; + let pow2_255 = BigUint::from(1_u32) << 255; + + // Split value into either 2 or 8 32-bit limbs. + let out: Vec = vals + .into_iter() + .map(|val| val.to_biguint()) + .flat_map(|val| { + if val < pow2_63 { + let (high, low) = val.div_rem(&pow2_32); + vec![high, low] + } else { + let mut limbs = vec![BigUint::from(0_u32); 8]; + let mut val: BigUint = val + &pow2_255; + for limb in limbs.iter_mut().rev() { + let (q, r) = val.div_rem(&pow2_32); + *limb = r; + val = q; + } + limbs + } + }) + .map(Felt252::from) + .map(MaybeRelocatable::from) + .collect(); + + vm.load_data(unpacked_u32s, &out) + .map_err(HintError::Memory)?; + Ok(()) +} + /* Implements Hint: %{ from starkware.cairo.common.cairo_blake2s.blake2s_utils import IV, blake2s_compress @@ -604,6 +685,103 @@ mod tests { .is_none()); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_ends() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![((1, 0), (1, 2)), ((1, 1), (1, 2)), ((1, 2), 123)]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_small() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![((1, 0), (1, 3)), ((1, 1), (1, 2)), ((1, 2), 123)]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_big() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![ + ((1, 0), (1, 3)), + ((1, 1), (1, 2)), + ((1, 2), 0x10000000000000000) + ]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn blake2s_unpack_felts() { + let hint_code = hint_code::BLAKE2S_UNPACK_FELTS; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![ + ((1, 0), 2), + ((1, 1), (1, 3)), + ((1, 2), (2, 0)), + ((1, 3), 0x123456781234), + ((1, 4), 0x1234abcd5678efab1234abcd) + ]; + vm.set_fp(5); + vm.set_ap(5); + let ids_data = ids_data![ + "packed_values_len", + "packed_values", + "unpacked_u32s", + "small_value", + "big_value" + ]; + vm.segments.add(); + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![ + vm.segments.memory, + ((2, 0), 0x1234), + ((2, 1), 0x56781234), + ((2, 2), 0x80000000), + ((2, 3), 0), + ((2, 4), 0), + ((2, 5), 0), + ((2, 6), 0), + ((2, 7), 0x1234abcd), + ((2, 8), 0x5678efab), + ((2, 9), 0x1234abcd) + ]; + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn blake2s_add_uint256_bigend_valid_non_zero() { diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index e96e1de372..ea4cfc272e 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -1,6 +1,6 @@ use super::blake2s_utils::example_blake2s_compress; use super::{ - blake2s_utils::finalize_blake2s_v3, + blake2s_utils::{blake2s_unpack_felts, finalize_blake2s_v3, is_less_than_63_bits_and_not_end}, ec_recover::{ ec_recover_divmod_n_packed, ec_recover_product_div_m, ec_recover_product_mod, ec_recover_sub_a_b, @@ -362,6 +362,12 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::BLAKE2S_ADD_UINT256_BIGEND => { blake2s_add_uint256_bigend(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END => { + is_less_than_63_bits_and_not_end(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } + hint_code::BLAKE2S_UNPACK_FELTS => { + blake2s_unpack_felts(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::UNSAFE_KECCAK => { unsafe_keccak(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) } diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs index addb4e007f..5d0919e0c8 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -426,6 +426,17 @@ segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4) MASK = 2 ** 32 - 1 segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)]) segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)])"#}), +(IS_LESS_THAN_63_BITS_AND_NOT_END, indoc! {r#"memory[ap] = to_felt_or_relocatable((ids.end != ids.packed_values) and (memory[ids.packed_values] < 2**63))"#}), +(BLAKE2S_UNPACK_FELTS, indoc! {r#"offset = 0 +for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len"#}), (EXAMPLE_BLAKE2S_COMPRESS, indoc! {r#"from starkware.cairo.common.cairo_blake2s.blake2s_utils import IV, blake2s_compress _blake2s_input_chunk_size_felts = int(ids.BLAKE2S_INPUT_CHUNK_SIZE_FELTS) diff --git a/vm/src/tests/cairo_pie_test.rs b/vm/src/tests/cairo_pie_test.rs index e6de72ea28..33f3b22993 100644 --- a/vm/src/tests/cairo_pie_test.rs +++ b/vm/src/tests/cairo_pie_test.rs @@ -9,7 +9,7 @@ use wasm_bindgen_test::*; use crate::{ cairo_run::{cairo_run, CairoRunConfig}, hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, - stdlib::collections::HashMap, + stdlib::collections::BTreeMap, types::relocatable::Relocatable, vm::runners::{ cairo_pie::{ @@ -45,7 +45,7 @@ fn pedersen_test() { // ret_pc_segment assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((6, 0))); // builtin_segments - let expected_builtin_segments = HashMap::from([ + let expected_builtin_segments = BTreeMap::from([ (BuiltinName::output, SegmentInfo::from((2, 1))), (BuiltinName::pedersen, SegmentInfo::from((3, 3))), (BuiltinName::range_check, SegmentInfo::from((4, 0))), @@ -71,7 +71,7 @@ fn pedersen_test() { let expected_execution_resources = ExecutionResources { n_steps: 14, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::range_check, 0), (BuiltinName::output, 1), (BuiltinName::pedersen, 1), @@ -79,12 +79,12 @@ fn pedersen_test() { }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data - let expected_additional_data = HashMap::from([ + let expected_additional_data = BTreeMap::from([ ( BuiltinName::output, BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { - pages: HashMap::new(), - attributes: HashMap::new(), + pages: BTreeMap::new(), + attributes: BTreeMap::new(), }), ), ( @@ -128,7 +128,7 @@ fn common_signature() { assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((4, 0))); // builtin_segments let expected_builtin_segments = - HashMap::from([(BuiltinName::ecdsa, SegmentInfo::from((2, 2)))]); + BTreeMap::from([(BuiltinName::ecdsa, SegmentInfo::from((2, 2)))]); assert_eq!(pie_metadata.builtin_segments, expected_builtin_segments); // program_segment assert_eq!(pie_metadata.program_segment, SegmentInfo::from((0, 21))); @@ -150,13 +150,13 @@ fn common_signature() { let expected_execution_resources = ExecutionResources { n_steps: 11, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::ecdsa, 1)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::ecdsa, 1)]), }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data - let expected_additional_data = HashMap::from([( + let expected_additional_data = BTreeMap::from([( BuiltinName::ecdsa, - BuiltinAdditionalData::Signature(HashMap::from([( + BuiltinAdditionalData::Signature(BTreeMap::from([( Relocatable::from((2, 0)), ( felt_str!( @@ -223,7 +223,7 @@ fn relocate_segments() { let expected_execution_resources = ExecutionResources { n_steps: 22, n_memory_holes: 0, - builtin_instance_counter: HashMap::default(), + builtin_instance_counter: BTreeMap::default(), }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index 1eaa469f9a..b33a08535f 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1241,6 +1241,7 @@ fn run_program_with_custom_mod_builtin_params( cairo_run_config.disable_trace_padding, false, &mut hint_processor, + cairo_run_config.proof_mode, ) .unwrap(); diff --git a/vm/src/tests/compare_factorial_outputs_all_layouts.sh b/vm/src/tests/compare_factorial_outputs_all_layouts.sh index e634fbb9d4..c6c784069a 100755 --- a/vm/src/tests/compare_factorial_outputs_all_layouts.sh +++ b/vm/src/tests/compare_factorial_outputs_all_layouts.sh @@ -21,15 +21,6 @@ for layout in "plain" "small" "dex" "recursive" "starknet" "starknet_with_keccak else passed_tests=$((passed_tests + 1)) fi - # Compare memory - echo "Running memory comparison for layout $layout" - if ! ./vm/src/tests/memory_comparator.py factorial_rs.memory factorial_py.memory; then - echo "Memory differs for layout $layout" - exit_code=1 - failed_tests=$((failed_tests + 1)) - else - passed_tests=$((passed_tests + 1)) - fi # Compare air public input echo "Running air public input comparison for layout $layout" if ! ./vm/src/tests/air_public_input_comparator.py factorial_rs.air_public_input factorial_py.air_public_input; then diff --git a/vm/src/tests/compare_outputs_dynamic_layouts.sh b/vm/src/tests/compare_outputs_dynamic_layouts.sh index 41c7bf0f17..8346d4ba39 100755 --- a/vm/src/tests/compare_outputs_dynamic_layouts.sh +++ b/vm/src/tests/compare_outputs_dynamic_layouts.sh @@ -207,15 +207,6 @@ for case in "${CASES[@]}"; do passed_tests=$((passed_tests + 1)) fi - # Compare memory - echo "Running memory comparison for case: $case" - if ! ./vm/src/tests/memory_comparator.py program_rs.memory program_py.memory; then - echo "Memory differs for case: $case" - exit_code=1 - failed_tests=$((failed_tests + 1)) - else - passed_tests=$((passed_tests + 1)) - fi # Compare air public input echo "Running air public input comparison for case: $case" diff --git a/vm/src/tests/compare_vm_state.sh b/vm/src/tests/compare_vm_state.sh index 34c7486663..bac9aa8cb6 100755 --- a/vm/src/tests/compare_vm_state.sh +++ b/vm/src/tests/compare_vm_state.sh @@ -65,7 +65,7 @@ for file in $(ls $tests_path | grep .cairo$ | sed -E 's/\.cairo$//'); do fi fi - if $memory; then + if $memory && -z $proof_tests_path; then if ! ./memory_comparator.py $path_file.memory $path_file.rs.memory; then echo "Memory differs for $file" exit_code=1 diff --git a/vm/src/types/builtin_name.rs b/vm/src/types/builtin_name.rs index 5490f439a1..4f11488c5b 100644 --- a/vm/src/types/builtin_name.rs +++ b/vm/src/types/builtin_name.rs @@ -32,7 +32,7 @@ const MUL_MOD_BUILTIN_NAME_WITH_SUFFIX: &str = "mul_mod_builtin"; /// Enum representing the name of a cairo builtin #[cfg_attr(feature = "test_utils", derive(Arbitrary))] -#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash, Ord, PartialOrd)] #[allow(non_camel_case_types)] pub enum BuiltinName { output, @@ -179,11 +179,11 @@ impl core::fmt::Display for BuiltinName { // Implementation of custom serialization & deserialization for maps using builtin names with suffixes as keys pub(crate) mod serde_generic_map_impl { use super::BuiltinName; - use crate::stdlib::{collections::HashMap, string::String}; + use crate::stdlib::{collections::BTreeMap, string::String}; use serde::{de::Error, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; pub fn serialize( - values: &HashMap, + values: &BTreeMap, serializer: S, ) -> Result where @@ -199,13 +199,13 @@ pub(crate) mod serde_generic_map_impl { pub fn deserialize<'de, D: Deserializer<'de>, V: Deserialize<'de>>( d: D, - ) -> Result, D::Error> { + ) -> Result, D::Error> { // First deserialize keys into String - let map = HashMap::::deserialize(d)?; + let map = BTreeMap::::deserialize(d)?; // Then match keys to BuiltinName and handle invalid names map.into_iter() .map(|(k, v)| BuiltinName::from_str_with_suffix(&k).map(|k| (k, v))) - .collect::>>() + .collect::>>() .ok_or(D::Error::custom("Invalid builtin name")) } } diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index d98b3b3a3f..ab04a3272d 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -282,6 +282,38 @@ impl BuiltinsInstanceDef { mul_mod, } } + + pub(crate) fn perpetual() -> BuiltinsInstanceDef { + BuiltinsInstanceDef { + output: true, + pedersen: Some(PedersenInstanceDef::new(Some(32))), + range_check: Some(RangeCheckInstanceDef::new(Some(16))), + ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), + bitwise: None, + ec_op: None, + keccak: None, + poseidon: None, + range_check96: None, + add_mod: None, + mul_mod: None, + } + } + + pub(crate) fn dex_with_bitwise() -> BuiltinsInstanceDef { + BuiltinsInstanceDef { + output: true, + pedersen: Some(PedersenInstanceDef::default()), + range_check: Some(RangeCheckInstanceDef::default()), + ecdsa: Some(EcdsaInstanceDef::default()), + bitwise: Some(BitwiseInstanceDef::new(Some(64))), + ec_op: None, + keccak: None, + poseidon: None, + range_check96: None, + add_mod: None, + mul_mod: None, + } + } } #[cfg(test)] diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index 1c7fe138a8..30d920ef36 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -169,6 +169,30 @@ impl CairoLayout { builtins: BuiltinsInstanceDef::dynamic(params), } } + + pub(crate) fn perpetual_instance() -> CairoLayout { + CairoLayout { + name: LayoutName::perpetual, + rc_units: 4, + cpu_component_step: DEFAULT_CPU_COMPONENT_STEP, + memory_units_per_step: DEFAULT_MEMORY_UNITS_PER_STEP, + builtins: BuiltinsInstanceDef::perpetual(), + public_memory_fraction: 4, + diluted_pool_instance_def: None, + } + } + + pub(crate) fn dex_with_bitwise_instance() -> CairoLayout { + CairoLayout { + name: LayoutName::dex_with_bitwise, + rc_units: 4, + cpu_component_step: DEFAULT_CPU_COMPONENT_STEP, + memory_units_per_step: DEFAULT_MEMORY_UNITS_PER_STEP, + builtins: BuiltinsInstanceDef::dex_with_bitwise(), + public_memory_fraction: 4, + diluted_pool_instance_def: Some(DilutedPoolInstanceDef::new(2, 4, 16)), + } + } } #[cfg(feature = "test_utils")] @@ -496,6 +520,31 @@ mod tests { ); } + #[test] + fn get_perpetual_instance() { + let layout = CairoLayout::perpetual_instance(); + let builtins = BuiltinsInstanceDef::perpetual(); + assert_eq!(layout.name, LayoutName::perpetual); + assert_eq!(layout.rc_units, 4); + assert_eq!(layout.builtins, builtins); + assert_eq!(layout.public_memory_fraction, 4); + assert_eq!(layout.diluted_pool_instance_def, None); + } + + #[test] + fn get_dex_with_bitwise_instance() { + let layout = CairoLayout::dex_with_bitwise_instance(); + let builtins = BuiltinsInstanceDef::dex_with_bitwise(); + assert_eq!(layout.name, LayoutName::dex_with_bitwise); + assert_eq!(layout.rc_units, 4); + assert_eq!(layout.builtins, builtins); + assert_eq!(layout.public_memory_fraction, 4); + assert_eq!( + layout.diluted_pool_instance_def, + Some(DilutedPoolInstanceDef::new(2, 4, 16)) + ); + } + #[test] fn get_dynamic_instance() { // dummy cairo layout params diff --git a/vm/src/types/layout_name.rs b/vm/src/types/layout_name.rs index 0d3e13c021..24addc4507 100644 --- a/vm/src/types/layout_name.rs +++ b/vm/src/types/layout_name.rs @@ -22,6 +22,8 @@ pub enum LayoutName { all_cairo, dynamic, all_cairo_stwo, + perpetual, + dex_with_bitwise, } impl LayoutName { @@ -39,6 +41,8 @@ impl LayoutName { LayoutName::all_cairo => "all_cairo", LayoutName::dynamic => "dynamic", LayoutName::all_cairo_stwo => "all_cairo_stwo", + LayoutName::perpetual => "perpetual", + LayoutName::dex_with_bitwise => "dex_with_bitwise", } } } @@ -65,6 +69,8 @@ impl ValueEnum for LayoutName { Self::all_cairo, Self::dynamic, Self::all_cairo_stwo, + Self::perpetual, + Self::dex_with_bitwise, ] } diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index a8cb0a6d06..77e6de07da 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -401,7 +401,7 @@ impl BuiltinRunner { } } - fn cells_per_instance(&self) -> u32 { + pub fn cells_per_instance(&self) -> u32 { match self { BuiltinRunner::Bitwise(_) => CELLS_PER_BITWISE, BuiltinRunner::EcOp(_) => CELLS_PER_EC_OP, diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 85a8fe8070..6d0d711945 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -821,15 +821,25 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let program = Program::from_bytes(program_data, Some("main")).unwrap(); - let mut runner = - CairoRunner::new(&program, LayoutName::all_cairo, None, true, false, false).unwrap(); + let proof_mode = true; + let mut runner = CairoRunner::new( + &program, + LayoutName::all_cairo, + None, + proof_mode, + false, + false, + ) + .unwrap(); let end = runner.initialize(false).unwrap(); // Modify add_mod & mul_mod params runner.run_until_pc(end, &mut hint_processor).unwrap(); runner.run_for_steps(1, &mut hint_processor).unwrap(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, proof_mode) + .unwrap(); runner.read_return_values(false).unwrap(); runner.finalize_segments().unwrap(); diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index ae54908565..8b84f38fa8 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -1,4 +1,4 @@ -use crate::stdlib::{collections::HashMap, prelude::*}; +use crate::stdlib::{collections::BTreeMap, prelude::*}; use crate::types::builtin_name::BuiltinName; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; @@ -12,6 +12,7 @@ use crate::vm::vm_memory::memory_segments::MemorySegmentManager; #[derive(Debug, Clone, PartialEq)] pub struct OutputBuiltinState { pub base: usize, + pub base_offset: usize, pub pages: Pages, pub attributes: Attributes, } @@ -19,6 +20,7 @@ pub struct OutputBuiltinState { #[derive(Debug, Clone)] pub struct OutputBuiltinRunner { base: usize, + pub base_offset: usize, pub(crate) pages: Pages, pub(crate) attributes: Attributes, pub(crate) stop_ptr: Option, @@ -29,17 +31,19 @@ impl OutputBuiltinRunner { pub fn new(included: bool) -> OutputBuiltinRunner { OutputBuiltinRunner { base: 0, - pages: HashMap::default(), - attributes: HashMap::default(), + base_offset: 0, + pages: BTreeMap::default(), + attributes: BTreeMap::default(), stop_ptr: None, included, } } - pub fn new_state(&mut self, base: usize, included: bool) { + pub fn new_state(&mut self, base: usize, base_offset: usize, included: bool) { self.base = base; - self.pages = HashMap::default(); - self.attributes = HashMap::default(); + self.base_offset = base_offset; + self.pages = BTreeMap::default(); + self.attributes = BTreeMap::default(); self.stop_ptr = None; self.included = included; } @@ -143,6 +147,7 @@ impl OutputBuiltinRunner { pub fn set_state(&mut self, new_state: OutputBuiltinState) { self.base = new_state.base; + self.base_offset = new_state.base_offset; self.pages = new_state.pages; self.attributes = new_state.attributes; } @@ -150,6 +155,7 @@ impl OutputBuiltinRunner { pub fn get_state(&mut self) -> OutputBuiltinState { OutputBuiltinState { base: self.base, + base_offset: self.base_offset, pages: self.pages.clone(), attributes: self.attributes.clone(), } @@ -168,7 +174,7 @@ impl OutputBuiltinRunner { self.pages.insert( page_id, PublicMemoryPage { - start: page_start.offset, + start: page_start.offset - self.base_offset, size: page_size, }, ); @@ -203,7 +209,6 @@ impl Default for OutputBuiltinRunner { mod tests { use super::*; use crate::relocatable; - use crate::stdlib::collections::HashMap; use crate::{ utils::test_utils::*, @@ -471,8 +476,8 @@ mod tests { assert_eq!( builtin.get_additional_data(), BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { - pages: HashMap::default(), - attributes: HashMap::default() + pages: BTreeMap::default(), + attributes: BTreeMap::default() }) ) } @@ -493,8 +498,9 @@ mod tests { let new_state = OutputBuiltinState { base: 10, - pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), - attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), + base_offset: 0, + pages: BTreeMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), + attributes: BTreeMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), }; builtin.set_state(new_state.clone()); @@ -510,17 +516,19 @@ mod tests { fn new_state() { let mut builtin = OutputBuiltinRunner { base: 10, - pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), - attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), + base_offset: 0, + pages: BTreeMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), + attributes: BTreeMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), stop_ptr: Some(10), included: true, }; let new_base = 11; let new_included = false; - builtin.new_state(new_base, new_included); + builtin.new_state(new_base, 2, new_included); assert_eq!(builtin.base, new_base); + assert_eq!(builtin.base_offset, 2); assert!(builtin.pages.is_empty()); assert!(builtin.attributes.is_empty()); assert_eq!(builtin.stop_ptr, None); @@ -544,7 +552,7 @@ mod tests { assert_eq!( builtin.pages, - HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 }),]) + BTreeMap::from([(1, PublicMemoryPage { start: 0, size: 3 }),]) ) } @@ -571,7 +579,7 @@ mod tests { let values = vec![0, 12, 30]; builtin.add_attribute(name.clone(), values.clone()); - assert_eq!(builtin.attributes, HashMap::from([(name, values)])); + assert_eq!(builtin.attributes, BTreeMap::from([(name, values)])); } #[test] @@ -614,14 +622,16 @@ mod tests { fn get_and_extend_additional_data() { let builtin_a = OutputBuiltinRunner { base: 0, - pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), - attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), + base_offset: 0, + pages: BTreeMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), + attributes: BTreeMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), stop_ptr: None, included: true, }; let additional_data = builtin_a.get_additional_data(); let mut builtin_b = OutputBuiltinRunner { base: 0, + base_offset: 0, pages: Default::default(), attributes: Default::default(), stop_ptr: None, diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index 8f2bda7510..bdd7a95a0c 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -1,6 +1,11 @@ use crate::air_private_input::{PrivateInput, PrivateInputSignature, SignatureInput}; use crate::math_utils::div_mod; -use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*, rc::Rc}; +use crate::stdlib::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, + prelude::*, + rc::Rc, +}; use crate::types::builtin_name::BuiltinName; use crate::types::instance_definitions::ecdsa_instance_def::CELLS_PER_SIGNATURE; @@ -162,7 +167,7 @@ impl SignatureBuiltinRunner { pub fn get_additional_data(&self) -> BuiltinAdditionalData { // Convert signatures to Felt tuple - let signatures: HashMap = self + let signatures: BTreeMap = self .signatures .borrow() .iter() @@ -523,7 +528,7 @@ mod tests { }, )]); builtin.signatures = Rc::new(RefCell::new(signatures)); - let signatures = HashMap::from([( + let signatures = BTreeMap::from([( Relocatable::from((4, 0)), (felt_str!("45678"), felt_str!("1239")), )]); diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index b839be06f3..a545ef2237 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -3,7 +3,10 @@ use crate::stdlib::prelude::{String, Vec}; use crate::types::builtin_name::BuiltinName; use crate::vm::errors::cairo_pie_errors::CairoPieValidationError; use crate::{ - stdlib::{collections::HashMap, prelude::*}, + stdlib::{ + collections::{BTreeMap, HashMap}, + prelude::*, + }, types::relocatable::{MaybeRelocatable, Relocatable}, Felt252, }; @@ -72,8 +75,8 @@ impl From<&Vec> for PublicMemoryPage { } // HashMap value based on starknet/core/os/output.cairo usage -pub type Attributes = HashMap>; -pub type Pages = HashMap; +pub type Attributes = BTreeMap>; +pub type Pages = BTreeMap; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct OutputBuiltinAdditionalData { @@ -93,7 +96,7 @@ pub enum BuiltinAdditionalData { Output(OutputBuiltinAdditionalData), // Signatures are composed of (r, s) tuples #[serde(with = "serde_impl::signature_additional_data")] - Signature(HashMap), + Signature(BTreeMap), None, } @@ -125,7 +128,7 @@ impl PartialEq for BuiltinAdditionalData { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct CairoPieAdditionalData( #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")] - pub HashMap, + pub BTreeMap, ); #[derive(Serialize, Clone, Debug, PartialEq, Eq)] @@ -145,7 +148,7 @@ pub struct CairoPieMetadata { pub ret_fp_segment: SegmentInfo, pub ret_pc_segment: SegmentInfo, #[serde(serialize_with = "serde_impl::serialize_builtin_segments")] - pub builtin_segments: HashMap, + pub builtin_segments: BTreeMap, pub extra_segments: Vec, } @@ -321,8 +324,9 @@ impl CairoPie { let file = File::create(file_path)?; let mut zip_writer = ZipWriter::new(file); - let options = - zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + let options = zip::write::FileOptions::default() + .compression_method(zip::CompressionMethod::Deflated) + .large_file(true); zip_writer.start_file("version.json", options)?; serde_json::to_writer(&mut zip_writer, &self.version)?; @@ -435,7 +439,7 @@ impl CairoPie { } pub(super) mod serde_impl { - use crate::stdlib::collections::HashMap; + use crate::stdlib::collections::{BTreeMap, HashMap}; use crate::types::builtin_name::BuiltinName; use num_traits::Num; @@ -761,7 +765,7 @@ pub(super) mod serde_impl { use super::*; pub fn serialize( - values: &HashMap, + values: &BTreeMap, serializer: S, ) -> Result where @@ -783,30 +787,30 @@ pub(super) mod serde_impl { pub fn deserialize<'de, D>( d: D, - ) -> Result, D::Error> + ) -> Result, D::Error> where D: Deserializer<'de>, { let number_map = Vec::<((Number, Number), (Number, Number))>::deserialize(d)?; - let mut res = HashMap::with_capacity(number_map.len()); - for ((index, offset), (r, s)) in number_map.into_iter() { - let addr = Relocatable::from(( - index + number_map + .into_iter() + .map(|((index, offset), (r, s))| { + let idx = index .as_u64() .ok_or_else(|| D::Error::custom("Invalid address"))? - as isize, - offset + as isize; + let off = offset .as_u64() .ok_or_else(|| D::Error::custom("Invalid address"))? - as usize, - )); - let r = Felt252::from_dec_str(r.as_str()) - .map_err(|_| D::Error::custom("Invalid Felt252 value"))?; - let s = Felt252::from_dec_str(s.as_str()) - .map_err(|_| D::Error::custom("Invalid Felt252 value"))?; - res.insert(addr, (r, s)); - } - Ok(res) + as usize; + let addr = Relocatable::from((idx, off)); + let r = Felt252::from_dec_str(r.as_str()) + .map_err(|_| D::Error::custom("Invalid Felt252 value"))?; + let s = Felt252::from_dec_str(s.as_str()) + .map_err(|_| D::Error::custom("Invalid Felt252 value"))?; + Ok((addr, (r, s))) + }) + .collect::, D::Error>>() } } @@ -840,7 +844,7 @@ pub(super) mod serde_impl { } pub fn serialize_builtin_segments( - values: &HashMap, + values: &BTreeMap, serializer: S, ) -> Result where diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index a54cff333d..b9ee5701a4 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1,3 +1,4 @@ +use crate::vm::trace::trace_entry::TraceEntry; use crate::{ air_private_input::AirPrivateInput, air_public_input::{PublicInput, PublicInputError}, @@ -11,7 +12,7 @@ use crate::{ types::{builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName}, vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, - trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry, TraceEntry}, + trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry}, }, Felt252, }; @@ -190,6 +191,8 @@ impl CairoRunner { LayoutName::all_cairo => CairoLayout::all_cairo_instance(), LayoutName::all_cairo_stwo => CairoLayout::all_cairo_stwo_instance(), LayoutName::all_solidity => CairoLayout::all_solidity_instance(), + LayoutName::perpetual => CairoLayout::perpetual_instance(), + LayoutName::dex_with_bitwise => CairoLayout::dex_with_bitwise_instance(), LayoutName::dynamic => { let params = dynamic_layout_params.ok_or(RunnerError::MissingDynamicLayoutParams)?; @@ -889,13 +892,14 @@ impl CairoRunner { disable_trace_padding: bool, disable_finalize_all: bool, hint_processor: &mut dyn HintProcessor, + proof_mode: bool, ) -> Result<(), VirtualMachineError> { if self.run_ended { return Err(RunnerError::EndRunCalledTwice.into()); } self.vm.segments.memory.relocate_memory()?; - self.vm.end_run(&self.exec_scopes)?; + self.vm.end_run(&self.exec_scopes, proof_mode)?; if disable_finalize_all { return Ok(()); @@ -981,9 +985,9 @@ impl CairoRunner { Ok(()) } - pub fn relocate(&mut self, relocate_mem: bool) -> Result<(), TraceError> { + pub fn relocate(&mut self, relocate_mem: bool, relocate_trace: bool) -> Result<(), TraceError> { self.vm.segments.compute_effective_sizes(); - if !relocate_mem && self.vm.trace.is_none() { + if !relocate_mem && (self.vm.trace.is_none() || !relocate_trace) { return Ok(()); } // relocate_segments can fail if compute_effective_sizes is not called before. @@ -999,25 +1003,39 @@ impl CairoRunner { return Err(TraceError::MemoryError(memory_error)); } } - if self.vm.trace.is_some() { + if self.vm.trace.is_some() && relocate_trace { self.relocate_trace(&relocation_table)?; } self.vm.relocation_table = Some(relocation_table); Ok(()) } - // Returns a map from builtin base's segment index to stop_ptr offset - // Aka the builtin's segment number and its maximum offset + /// Returns a tuple of builtin base's segment index and stop_ptr offset + /// Aka the builtin's segment number and its maximum offset pub fn get_builtin_segments_info(&self) -> Result, RunnerError> { + let proof_mode = self.is_proof_mode(); let mut builtin_segment_info = Vec::new(); for builtin in &self.vm.builtin_runners { let (index, stop_ptr) = builtin.get_memory_segment_addresses(); - builtin_segment_info.push(( - index, - stop_ptr.ok_or_else(|| RunnerError::NoStopPointer(Box::new(builtin.name())))?, - )); + match (proof_mode, stop_ptr) { + // Segment present (same handling in both modes). + (_, Some(sp)) => builtin_segment_info.push((index, sp)), + + // If non proof-mode, only builtins in the program are present and they must + // point to a segment (so `stop_ptr` must be set). Throw an error if not. + (false, None) => { + return Err(RunnerError::NoStopPointer(Box::new( + builtin.name().to_owned(), + ))); + } + + // In proof‐mode there are builtin runners for all builtins in the layout, but only + // the ones that are in the program point to a segment (so `stop_ptr` is set). + // Only collect those and silently ignore the rest. + (true, None) => {} + } } Ok(builtin_segment_info) @@ -1055,7 +1073,7 @@ impl CairoRunner { .unwrap_or(self.vm.current_step); let n_memory_holes = self.get_memory_holes()?; - let mut builtin_instance_counter = HashMap::new(); + let mut builtin_instance_counter = BTreeMap::new(); for builtin_runner in &self.vm.builtin_runners { builtin_instance_counter.insert( builtin_runner.name(), @@ -1154,7 +1172,7 @@ impl CairoRunner { self.run_until_pc(end, hint_processor) .map_err(|err| VmException::from_vm_error(self, err))?; - self.end_run(true, false, hint_processor)?; + self.end_run(true, false, hint_processor, self.is_proof_mode())?; if verify_secure { verify_secure_runner(self, false, program_segment_size)?; @@ -1414,7 +1432,7 @@ impl CairoRunner { execution_segment: (execution_base.segment_index, execution_size).into(), ret_fp_segment: (return_fp.segment_index, 0).into(), ret_pc_segment: (return_pc.segment_index, 0).into(), - builtin_segments, + builtin_segments: builtin_segments.into_iter().collect(), extra_segments, }; @@ -1488,79 +1506,38 @@ impl CairoRunner { .collect() } - /// Collects relevant information for the prover from the runner, including the - /// relocatable form of the trace, memory, public memory, and built-ins. - pub fn get_prover_input_info(&self) -> Result { - let relocatable_trace = self - .vm + /// Returns a reference to the relocatable trace. + pub fn get_relocatable_trace(&self) -> Result<&[TraceEntry], RunnerError> { + self.vm .trace - .as_ref() - .ok_or(RunnerError::Trace(TraceError::TraceNotEnabled))? - .clone(); + .as_deref() + .ok_or(RunnerError::Trace(TraceError::TraceNotEnabled)) + } - let relocatable_memory = self - .vm + /// Returns a vector of segments, where each segment is a vector of Option values, representing the relocatble memory values. + pub fn get_relocatable_memory(&self) -> Vec>> { + self.vm .segments .memory .data .iter() .map(|segment| segment.iter().map(|cell| cell.get_value()).collect()) - .collect(); - - let public_memory_offsets = self - .vm - .segments - .public_memory_offsets - .iter() - .map(|(segment, offset_page)| { - let offsets: Vec = offset_page.iter().map(|(offset, _)| *offset).collect(); - (*segment, offsets) - }) - .collect(); + .collect() + } - let builtins_segments: BTreeMap = self - .vm + /// Returns a map from the builtin segment index into its name. + pub fn get_builtin_segments(&self) -> BTreeMap { + self.vm .builtin_runners .iter() - .filter(|builtin| { - // Those segments are not treated as builtins by the prover. - !matches!( - builtin, - BuiltinRunner::SegmentArena(_) | BuiltinRunner::Output(_) - ) - }) .map(|builtin| { let (index, _) = builtin.get_memory_segment_addresses(); (index, builtin.name()) }) - .collect(); - - Ok(ProverInputInfo { - relocatable_trace, - relocatable_memory, - public_memory_offsets, - builtins_segments, - }) + .collect() } } -//* ---------------------- -//* ProverInputInfo -//* ---------------------- -/// This struct contains all relevant data for the prover. -/// All addresses are relocatable. -#[derive(Deserialize, Serialize)] -pub struct ProverInputInfo { - /// A vector of trace entries, i.e. pc, ap, fp, where pc is relocatable. - pub relocatable_trace: Vec, - /// A vector of segments, where each segment is a vector of maybe relocatable values or holes (`None`). - pub relocatable_memory: Vec>>, - /// A map from segment index to a vector of offsets within the segment, representing the public memory addresses. - pub public_memory_offsets: BTreeMap>, - /// A map from the builtin segment index into its name. - pub builtins_segments: BTreeMap, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct SegmentInfo { pub index: isize, @@ -1576,7 +1553,7 @@ pub struct ExecutionResources { pub n_steps: usize, pub n_memory_holes: usize, #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")] - pub builtin_instance_counter: HashMap, + pub builtin_instance_counter: BTreeMap, } /// Returns a copy of the execution resources where all the builtins with a usage counter @@ -1665,6 +1642,8 @@ mod tests { use crate::air_private_input::{PrivateInput, PrivateInputSignature, SignatureInput}; use crate::cairo_run::{cairo_run, CairoRunConfig}; use crate::stdlib::collections::{HashMap, HashSet}; + use crate::types::instance_definitions::bitwise_instance_def::CELLS_PER_BITWISE; + use crate::types::instance_definitions::keccak_instance_def::CELLS_PER_KECCAK; use crate::vm::vm_memory::memory::MemoryCell; use crate::felt_hex; @@ -3851,7 +3830,7 @@ mod tests { cairo_runner.run_ended = true; assert_matches!( - cairo_runner.end_run(true, false, &mut hint_processor), + cairo_runner.end_run(true, false, &mut hint_processor, false), Err(VirtualMachineError::RunnerError( RunnerError::EndRunCalledTwice )) @@ -3867,14 +3846,14 @@ mod tests { let mut cairo_runner = cairo_runner!(program); assert_matches!( - cairo_runner.end_run(true, false, &mut hint_processor), + cairo_runner.end_run(true, false, &mut hint_processor, false), Ok(()) ); cairo_runner.run_ended = false; cairo_runner.relocated_memory.clear(); assert_matches!( - cairo_runner.end_run(true, true, &mut hint_processor), + cairo_runner.end_run(true, true, &mut hint_processor, false), Ok(()) ); assert!(!cairo_runner.run_ended); @@ -3888,16 +3867,16 @@ mod tests { Some("main"), ) .unwrap(); - + let proof_mode = true; let mut hint_processor = BuiltinHintProcessor::new_empty(); - let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, true, true); + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, proof_mode, true); let end = cairo_runner.initialize(false).unwrap(); cairo_runner .run_until_pc(end, &mut hint_processor) .expect("Call to `CairoRunner::run_until_pc()` failed."); assert_matches!( - cairo_runner.end_run(false, false, &mut hint_processor), + cairo_runner.end_run(false, false, &mut hint_processor, proof_mode), Ok(()) ); } @@ -3927,6 +3906,49 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_builtin_segments_info_non_proof_mode() { + let program_data = + include_bytes!("../../../../cairo_programs/proof_programs/assert_nn.json"); + let cairo_run_config = CairoRunConfig { + entrypoint: "main", + trace_enabled: false, + relocate_mem: false, + layout: LayoutName::small, + proof_mode: false, + secure_run: Some(true), + ..Default::default() + }; + let mut hint_executor = BuiltinHintProcessor::new_empty(); + let runner = cairo_run(program_data, &cairo_run_config, &mut hint_executor).unwrap(); + // Only the range_check builtin is used in this program, and in non-proof mode, it will + // be the first and only builtin segment initialized (and used). + assert_eq!(runner.get_builtin_segments_info(), Ok(vec![(2, 6)])); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_builtin_segments_info_proof_mode() { + let program_data = + include_bytes!("../../../../cairo_programs/proof_programs/assert_nn.json"); + let cairo_run_config = CairoRunConfig { + entrypoint: "main", + trace_enabled: false, + relocate_mem: false, + layout: LayoutName::small, + proof_mode: true, + secure_run: Some(true), + ..Default::default() + }; + let mut hint_executor = BuiltinHintProcessor::new_empty(); + let runner = cairo_run(program_data, &cairo_run_config, &mut hint_executor).unwrap(); + // Only the range_check builtin is used in this program, and in proof mode, it will + // be the first and only builtin segment used, but initialized after the other builtins + // in the layout. + assert_eq!(runner.get_builtin_segments_info(), Ok(vec![(4, 6)])); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_execution_resources_trace_not_enabled() { @@ -3941,7 +3963,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }), ); } @@ -3996,7 +4018,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }), ); } @@ -4021,7 +4043,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::output, 4)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::output, 4)]), }), ); } @@ -4969,7 +4991,7 @@ mod tests { } fn setup_execution_resources() -> (ExecutionResources, ExecutionResources) { - let mut builtin_instance_counter: HashMap = HashMap::new(); + let mut builtin_instance_counter: BTreeMap = BTreeMap::new(); builtin_instance_counter.insert(BuiltinName::output, 8); let execution_resources_1 = ExecutionResources { @@ -5160,7 +5182,7 @@ mod tests { let execution_resources_1 = ExecutionResources { n_steps: 800, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::pedersen, 7), (BuiltinName::range_check, 16), ]), @@ -5171,7 +5193,7 @@ mod tests { ExecutionResources { n_steps: 1600, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::pedersen, 14), (BuiltinName::range_check, 32) ]) @@ -5181,7 +5203,7 @@ mod tests { let execution_resources_2 = ExecutionResources { n_steps: 545, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 17)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::range_check, 17)]), }; assert_eq!( @@ -5189,14 +5211,14 @@ mod tests { ExecutionResources { n_steps: 4360, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 136)]) + builtin_instance_counter: BTreeMap::from([(BuiltinName::range_check, 136)]) } ); let execution_resources_3 = ExecutionResources { n_steps: 42, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }; assert_eq!( @@ -5204,7 +5226,7 @@ mod tests { ExecutionResources { n_steps: 756, n_memory_holes: 0, - builtin_instance_counter: HashMap::new() + builtin_instance_counter: BTreeMap::new() } ); } @@ -5520,7 +5542,7 @@ mod tests { } #[test] - fn get_prover_input_info() { + fn test_get_relocatable_trace() { let program_content = include_bytes!("../../../../cairo_programs/proof_programs/common_signature.json"); let runner = crate::cairo_run::cairo_run( @@ -5533,7 +5555,7 @@ mod tests { &mut BuiltinHintProcessor::new_empty(), ) .unwrap(); - let prover_info = runner.get_prover_input_info().unwrap(); + let relocatable_trace = runner.get_relocatable_trace().unwrap(); let expected_trace = vec![ TraceEntry { pc: (0, 15).into(), @@ -5591,46 +5613,117 @@ mod tests { fp: 3, }, ]; + assert_eq!(relocatable_trace, expected_trace); + } + + #[test] + fn test_get_relocatable_memory() { + let program_content = + include_bytes!("../../../../cairo_programs/proof_programs/common_signature.json"); + let runner = crate::cairo_run::cairo_run( + program_content, + &CairoRunConfig { + layout: LayoutName::all_cairo, + ..Default::default() + }, + &mut BuiltinHintProcessor::new_empty(), + ) + .unwrap(); + let relocatable_memory = runner.get_relocatable_memory(); + let expected_in_memory_0_3 = MaybeRelocatable::Int(13.into()); let expected_in_memory_1_0 = MaybeRelocatable::RelocatableValue(Relocatable { segment_index: 2, offset: 0, }); - assert_eq!(prover_info.relocatable_trace, expected_trace); - assert_eq!( - prover_info.relocatable_memory[0][3], - Some(expected_in_memory_0_3) - ); - assert_eq!( - prover_info.relocatable_memory[1][0], - Some(expected_in_memory_1_0) - ); - assert!(prover_info.public_memory_offsets.is_empty()); - assert_eq!( - prover_info.builtins_segments, - BTreeMap::from([(2, BuiltinName::ecdsa)]) - ); + + assert_eq!(relocatable_memory[0][3], Some(expected_in_memory_0_3)); + assert_eq!(relocatable_memory[1][0], Some(expected_in_memory_1_0)); } #[test] - fn test_output_not_builtin_segment() { + fn test_get_builtin_segments() { let program_content = - include_bytes!("../../../../cairo_programs/proof_programs/split_felt.json"); + include_bytes!("../../../../cairo_programs/proof_programs/bitwise_builtin_test.json"); let runner = crate::cairo_run::cairo_run( program_content, &CairoRunConfig { - trace_enabled: true, layout: LayoutName::all_cairo, ..Default::default() }, &mut BuiltinHintProcessor::new_empty(), ) .unwrap(); - let prover_info = runner.get_prover_input_info().unwrap(); + let builtin_segments = runner.get_builtin_segments(); + + assert_eq!(builtin_segments[&2], BuiltinName::bitwise); + } + + #[test] + fn end_run_fill_builtins() { + let program = Program::from_bytes( + include_bytes!("../../../../cairo_programs/proof_programs/keccak_uint256.json"), + Some("main"), + ) + .unwrap(); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let proof_mode = true; + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, proof_mode, true); + + let end = cairo_runner.initialize(false).unwrap(); + cairo_runner + .run_until_pc(end, &mut hint_processor) + .expect("Call to `CairoRunner::run_until_pc()` failed."); + + // Before end run + assert!(cairo_runner.vm.segments.memory.data[6].len() as u32 % CELLS_PER_BITWISE != 0); + assert!(cairo_runner.vm.segments.memory.data[8].len() as u32 % CELLS_PER_KECCAK != 0); + + assert_matches!( + cairo_runner.end_run(false, false, &mut hint_processor, proof_mode), + Ok(()) + ); + + // After end run + assert!(cairo_runner.vm.segments.memory.data[6].len() as u32 % CELLS_PER_BITWISE == 0); + assert!(cairo_runner.vm.segments.memory.data[8].len() as u32 % CELLS_PER_KECCAK == 0); + assert!(cairo_runner.vm.segments.memory.data[6].last().is_some()); + assert!(cairo_runner.vm.segments.memory.data[8].last().is_some()); + + let builtin_segments = cairo_runner.get_builtin_segments(); + assert!(builtin_segments.get(&6) == Some(&BuiltinName::bitwise)); + assert!(builtin_segments.get(&8) == Some(&BuiltinName::keccak)); + } + + #[test] + fn end_run_fill_middle_holes() { + let program = Program::from_bytes( + include_bytes!("../../../../cairo_programs/proof_programs/poseidon_builtin_hole.json"), + Some("main"), + ) + .unwrap(); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, true, true); + + let end = cairo_runner.initialize(false).unwrap(); + cairo_runner + .run_until_pc(end, &mut hint_processor) + .expect("Call to `CairoRunner::run_until_pc()` failed."); + + // Before end run + // In 'poseidon_builtin_hole.cairo' we are not accessing result.s1, so there is a hole in that builtin segment. + assert!(cairo_runner.vm.segments.memory.data[9][4].is_none()); + assert_matches!( + cairo_runner.end_run(false, false, &mut hint_processor, true), + Ok(()) + ); + + // After end run + assert!(!cairo_runner.vm.segments.memory.data[9][4].is_none()); - assert!(!prover_info - .builtins_segments - .values() - .any(|v| *v == BuiltinName::output)); + let builtin_segments = cairo_runner.get_builtin_segments(); + assert!(builtin_segments.get(&9) == Some(&BuiltinName::poseidon)); } } diff --git a/vm/src/vm/security.rs b/vm/src/vm/security.rs index 0dfdda8f23..08abc03198 100644 --- a/vm/src/vm/security.rs +++ b/vm/src/vm/security.rs @@ -150,7 +150,9 @@ mod test { runner.initialize(false).unwrap(); // runner.vm.segments.compute_effective_sizes(); let mut hint_processor = BuiltinHintProcessor::new_empty(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, false) + .unwrap(); // At the end of the run, the ret_fp should be the base of the new ret_fp segment we added // to the stack at the start of the run. runner.vm.run_context.fp = 0; @@ -215,7 +217,9 @@ mod test { runner.initialize(false).unwrap(); let mut hint_processor = BuiltinHintProcessor::new_empty(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, false) + .unwrap(); runner.vm.builtin_runners[0].set_stop_ptr(1); // Adding ((1, 1), (3, 0)) to the memory segment to simulate the ret_fp_segment. runner.vm.segments.memory = memory![((2, 0), 1), ((1, 1), (3, 0))]; diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 2adcf47b09..fcd8f9dac9 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -3,6 +3,7 @@ use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; +use crate::vm::vm_memory::memory::MemoryCell; use crate::{ hint_processor::{ builtin_hint_processor::blake2s_hash::blake2s_compress, @@ -748,6 +749,66 @@ impl VirtualMachine { )) } + /// Updates the memory with missing built-in deductions and verifies the existing ones. + pub fn complete_builtin_auto_deductions(&mut self) -> Result<(), VirtualMachineError> { + for builtin in self.builtin_runners.iter() { + let builtin_index: usize = builtin.base(); + + // Output and SegmentArena do not need to be auto-deduced in the memory. + if matches!( + builtin, + BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) + ) { + continue; + } + + // Extend the segment size to a multiple of the number of cells per instance. + let current_builtin_segment = &mut self.segments.memory.data[builtin_index]; + let len = current_builtin_segment.len(); + let cells_per_instance = builtin.cells_per_instance() as usize; + current_builtin_segment.resize( + len.div_ceil(cells_per_instance) * cells_per_instance, + MemoryCell::NONE, + ); + + let mut missing_values: Vec<(usize, MemoryCell)> = vec![]; + + for (offset, cell) in self.segments.memory.data[builtin_index].iter().enumerate() { + if let Some(deduced_memory_cell) = builtin + .deduce_memory_cell( + Relocatable::from((builtin_index as isize, offset)), + &self.segments.memory, + ) + .map_err(VirtualMachineError::RunnerError)? + { + match cell.get_value() { + Some(memory_value) => { + // Checks that the value in the memory is correct. + if memory_value != deduced_memory_cell { + return Err(VirtualMachineError::InconsistentAutoDeduction( + Box::new(( + builtin.name(), + deduced_memory_cell, + Some(memory_value), + )), + )); + } + } + None => { + // Collect value to be stored in memory. + missing_values.push((offset, MemoryCell::new(deduced_memory_cell))); + } + } + } + } + + for (offset, value) in missing_values { + self.segments.memory.data[builtin_index][offset] = value; + } + } + Ok(()) + } + ///Makes sure that all assigned memory cells are consistent with their auto deduction rules. pub fn verify_auto_deductions(&self) -> Result<(), VirtualMachineError> { for builtin in self.builtin_runners.iter() { @@ -798,8 +859,16 @@ impl VirtualMachine { Ok(()) } - pub fn end_run(&mut self, exec_scopes: &ExecutionScopes) -> Result<(), VirtualMachineError> { - self.verify_auto_deductions()?; + pub fn end_run( + &mut self, + exec_scopes: &ExecutionScopes, + proof_mode: bool, + ) -> Result<(), VirtualMachineError> { + if proof_mode { + self.complete_builtin_auto_deductions()?; + } else { + self.verify_auto_deductions()?; + } self.run_finished = true; match exec_scopes.data.len() { 1 => Ok(()), @@ -4560,7 +4629,7 @@ mod tests { scopes.enter_scope(HashMap::new()); assert_matches!( - vm.end_run(scopes), + vm.end_run(scopes, false), Err(VirtualMachineError::MainScopeError( ExecScopeError::NoScopeError ))