From 4c08af0bee6f0106649b8efc24b8e2ca459895ba Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Fri, 1 Mar 2024 18:03:35 +0100 Subject: [PATCH 01/36] Output builtin features for bootloader support (#1580) * Output builtin features for bootloader support This commit introduces the following features/changes: * Paging: pages can now be added to the output builtin. These pages are reflected in the public memory of the VM when exporting the public input. * The state of the output builtin can now be modified using the new `set_state` method. * The output builtin can now handle attributes. These are used to generate the fact topologies of the bootloader. * clippy + coverage fixes * revert pub(crate) for base field * remove from_segment * changelog * Fix: use dedicated struct to store the output builtin state The `get_state` and `set_state` methods now rely on the new `OutputBuiltinState` struct. Rolled back the introduction of the base field in `OutputBuiltinAdditionalData`. * fix coverage --- CHANGELOG.md | 2 + vm/src/vm/errors/runner_errors.rs | 2 + vm/src/vm/runners/builtin_runner/output.rs | 188 ++++++++++++++++++++- vm/src/vm/runners/cairo_runner.rs | 6 +- 4 files changed, 192 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b020db2a92..5f2aa00a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * feat: Add cairo1-run output pretty-printing for felts, arrays/spans and dicts [#1630](https://github.com/lambdaclass/cairo-vm/pull/1630) +* feat: output builtin features for bootloader support [#1580](https://github.com/lambdaclass/cairo-vm/pull/1580) + #### [1.0.0-rc1] - 2024-02-23 * Bump `starknet-types-core` dependency version to 0.0.9 [#1628](https://github.com/lambdaclass/cairo-vm/pull/1628) diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index dc1533f509..01634ecb2c 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -102,6 +102,8 @@ pub enum RunnerError { Trace(#[from] TraceError), #[error("EcOp builtin: Invalid Point")] InvalidPoint, + #[error("Page ({0}) is not on the expected segment {1}")] + PageNotOnSegment(Relocatable, usize), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index c774bb330d..bf9eb7c7d8 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -2,16 +2,27 @@ use crate::stdlib::{collections::HashMap, prelude::*}; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::errors::runner_errors::RunnerError; -use crate::vm::runners::cairo_pie::{BuiltinAdditionalData, OutputBuiltinAdditionalData}; +use crate::vm::runners::cairo_pie::{ + Attributes, BuiltinAdditionalData, OutputBuiltinAdditionalData, Pages, PublicMemoryPage, +}; use crate::vm::vm_core::VirtualMachine; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use super::OUTPUT_BUILTIN_NAME; +#[derive(Debug, Clone, PartialEq)] +pub struct OutputBuiltinState { + pub base: usize, + pub pages: Pages, + pub attributes: Attributes, +} + #[derive(Debug, Clone)] pub struct OutputBuiltinRunner { base: usize, + pub(crate) pages: Pages, + pub(crate) attributes: Attributes, pub(crate) stop_ptr: Option, pub(crate) included: bool, } @@ -20,11 +31,21 @@ impl OutputBuiltinRunner { pub fn new(included: bool) -> OutputBuiltinRunner { OutputBuiltinRunner { base: 0, + pages: HashMap::default(), + attributes: HashMap::default(), stop_ptr: None, included, } } + pub fn new_state(&mut self, base: usize, included: bool) { + self.base = base; + self.pages = HashMap::default(); + self.attributes = HashMap::default(); + self.stop_ptr = None; + self.included = included; + } + pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) { self.base = segments.add().segment_index as usize // segments.add() always returns a positive index } @@ -110,14 +131,64 @@ impl OutputBuiltinRunner { pub fn get_additional_data(&self) -> BuiltinAdditionalData { BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { - pages: HashMap::default(), - attributes: HashMap::default(), + pages: self.pages.clone(), + attributes: self.attributes.clone(), }) } pub(crate) fn set_stop_ptr_offset(&mut self, offset: usize) { self.stop_ptr = Some(offset) } + + pub fn set_state(&mut self, new_state: OutputBuiltinState) { + self.base = new_state.base; + self.pages = new_state.pages; + self.attributes = new_state.attributes; + } + + pub fn get_state(&mut self) -> OutputBuiltinState { + OutputBuiltinState { + base: self.base, + pages: self.pages.clone(), + attributes: self.attributes.clone(), + } + } + + pub fn add_page( + &mut self, + page_id: usize, + page_start: Relocatable, + page_size: usize, + ) -> Result<(), RunnerError> { + if page_start.segment_index as usize != self.base { + return Err(RunnerError::PageNotOnSegment(page_start, self.base)); + } + + self.pages.insert( + page_id, + PublicMemoryPage { + start: page_start.offset, + size: page_size, + }, + ); + + Ok(()) + } + + pub fn get_public_memory(&self) -> Result, RunnerError> { + let size = self + .stop_ptr + .ok_or(RunnerError::NoStopPointer(Box::new(OUTPUT_BUILTIN_NAME)))?; + + let mut public_memory: Vec<(usize, usize)> = (0..size).map(|i| (i, 0)).collect(); + for (page_id, page) in self.pages.iter() { + for index in 0..page.size { + public_memory[page.start + index].1 = *page_id; + } + } + + Ok(public_memory) + } } impl Default for OutputBuiltinRunner { @@ -463,4 +534,115 @@ mod tests { let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; assert!(builtin.air_private_input(&memory).is_empty()); } + + #[test] + fn set_state() { + let mut builtin = OutputBuiltinRunner::new(true); + assert_eq!(builtin.base, 0); + + 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])]), + }; + builtin.set_state(new_state.clone()); + + assert_eq!(builtin.base, new_state.base); + assert_eq!(builtin.pages, new_state.pages); + assert_eq!(builtin.attributes, new_state.attributes); + + let state = builtin.get_state(); + assert_eq!(state, new_state); + } + + #[test] + 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])]), + stop_ptr: Some(10), + included: true, + }; + + let new_base = 11; + let new_included = false; + builtin.new_state(new_base, new_included); + + assert_eq!(builtin.base, new_base); + assert!(builtin.pages.is_empty()); + assert!(builtin.attributes.is_empty()); + assert_eq!(builtin.stop_ptr, None); + assert_eq!(builtin.included, new_included); + } + + #[test] + fn add_page() { + let mut builtin = OutputBuiltinRunner::new(true); + assert_eq!( + builtin.add_page( + 1, + Relocatable { + segment_index: builtin.base() as isize, + offset: 0 + }, + 3 + ), + Ok(()) + ); + + assert_eq!( + builtin.pages, + HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 }),]) + ) + } + + #[test] + fn add_page_wrong_segment() { + let mut builtin = OutputBuiltinRunner::new(true); + let page_start = Relocatable { + segment_index: 18, + offset: 0, + }; + + let result = builtin.add_page(1, page_start, 3); + assert!( + matches!(result, Err(RunnerError::PageNotOnSegment(relocatable, base)) if relocatable == page_start && base == builtin.base()) + ) + } + + #[test] + fn get_public_memory() { + let mut builtin = OutputBuiltinRunner::new(true); + + builtin + .add_page( + 1, + Relocatable { + segment_index: builtin.base() as isize, + offset: 2, + }, + 2, + ) + .unwrap(); + + builtin + .add_page( + 2, + Relocatable { + segment_index: builtin.base() as isize, + offset: 4, + }, + 3, + ) + .unwrap(); + + builtin.stop_ptr = Some(7); + + let public_memory = builtin.get_public_memory().unwrap(); + assert_eq!( + public_memory, + vec![(0, 0), (1, 0), (2, 1), (3, 1), (4, 2), (5, 2), (6, 2)] + ); + } } diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index cadc1d02ba..c117ccda1c 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -55,7 +55,7 @@ use num_traits::{ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; use super::{ - builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner, OUTPUT_BUILTIN_NAME}, + builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}, cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion}, }; @@ -1090,8 +1090,8 @@ impl CairoRunner { let (_, size) = builtin_runner .get_used_cells_and_allocated_size(vm) .map_err(RunnerError::FinalizeSegements)?; - if builtin_runner.name() == OUTPUT_BUILTIN_NAME { - let public_memory = (0..size).map(|i| (i, 0)).collect(); + if let BuiltinRunner::Output(output_builtin) = builtin_runner { + let public_memory = output_builtin.get_public_memory()?; vm.segments .finalize(Some(size), builtin_runner.base(), Some(&public_memory)) } else { From 3ecc47e2b0ca2fde18e2dafa04f9397ec8a36512 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:57:26 -0300 Subject: [PATCH 02/36] [Cairo 1] Add flag to append return values to output segment when not running in proof_mode (#1646) * Add flag * Use flag when creating entry code & builtins * Use flag when writing output segment size * Handle output builtin * Add header for non-proof_mode * Extend test * Fix offset * Add ret statement * Fix offset * Simplify args * Update Doc * Add cli flag + fix * Better fix * Add check that cairo_pie can be generated when running without proof_mode * Extend doc * Update changelog * Remove ghost entry * Clippy * Update compression method --- CHANGELOG.md | 4 ++ cairo1-run/README.md | 2 + cairo1-run/src/cairo_run.rs | 81 +++++++++++++++++++++++++++------- cairo1-run/src/main.rs | 8 ++++ vm/src/vm/runners/cairo_pie.rs | 2 +- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f2aa00a4e..2d3fcdd449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ #### Upcoming Changes +* feat: Add flag to append return values to output segment when not running in proof_mode [#1646](https://github.com/lambdaclass/cairo-vm/pull/1646) + * Adds the flag `append_return_values` to both the CLI and `Cairo1RunConfig` struct. + * Enabling flag will add the output builtin and the necessary instructions to append the return values to the output builtin's memory segment. + * feat: Add cairo1-run output pretty-printing for felts, arrays/spans and dicts [#1630](https://github.com/lambdaclass/cairo-vm/pull/1630) * feat: output builtin features for bootloader support [#1580](https://github.com/lambdaclass/cairo-vm/pull/1580) diff --git a/cairo1-run/README.md b/cairo1-run/README.md index 2f0cc5a8b3..8b7244314f 100644 --- a/cairo1-run/README.md +++ b/cairo1-run/README.md @@ -66,3 +66,5 @@ The cairo1-run cli supports the following optional arguments: * `--air_private_input `: Receives the name of a file and outputs the AIR private inputs into it. Can only be used if proof_mode, trace_file & memory_file are also enabled. * `--cairo_pie_output `: Receives the name of a file and outputs the Cairo PIE into it. Can only be used if proof_mode, is not enabled. + +* `--append_return_values`: Adds extra instructions to the program in order to append the return values to the output builtin's segment. This is the default behaviour for proof_mode. diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 5dbda6c672..348bb8f9a5 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -58,6 +58,8 @@ pub struct Cairo1RunConfig<'a> { // Should be true if either air_public_input or cairo_pie_output are needed // Sets builtins stop_ptr by calling `final_stack` on each builtin pub finalize_builtins: bool, + // Appends return values to the output segment. This is performed by default when running in proof_mode + pub append_return_values: bool, } impl Default for Cairo1RunConfig<'_> { @@ -69,6 +71,7 @@ impl Default for Cairo1RunConfig<'_> { layout: "plain", proof_mode: false, finalize_builtins: false, + append_return_values: false, } } } @@ -98,7 +101,7 @@ pub fn cairo_run_program( &type_sizes, main_func, initial_gas, - cairo_run_config.proof_mode, + cairo_run_config.proof_mode || cairo_run_config.append_return_values, cairo_run_config.args, )?; @@ -120,6 +123,8 @@ pub fn cairo_run_program( // Also appends return values to output segment let proof_mode_header = if cairo_run_config.proof_mode { create_proof_mode_header(builtins.len() as i16, return_type_size) + } else if cairo_run_config.append_return_values { + create_append_return_values_header(builtins.len() as i16, return_type_size) } else { casm! {}.instructions }; @@ -190,7 +195,7 @@ pub fn cairo_run_program( // Run it until the end / infinite loop in proof_mode runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - if cairo_run_config.proof_mode { + if cairo_run_config.proof_mode || cairo_run_config.append_return_values { // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size vm.segments @@ -205,17 +210,19 @@ pub fn cairo_run_program( // Set stop pointers for builtins so we can obtain the air public input if cairo_run_config.finalize_builtins { finalize_builtins( - cairo_run_config.proof_mode, + cairo_run_config.proof_mode || cairo_run_config.append_return_values, &main_func.signature.ret_types, &type_sizes, &mut vm, )?; - // Build execution public memory - if cairo_run_config.proof_mode { + if cairo_run_config.proof_mode || cairo_run_config.append_return_values { // As the output builtin is not used by the program we need to compute it's stop ptr manually vm.set_output_stop_ptr_offset(return_type_size as usize); + } + // Build execution public memory + if cairo_run_config.proof_mode { runner.finalize_segments(&mut vm)?; } } @@ -334,6 +341,39 @@ fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec Vec { + // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty + // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases + let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments + + // The pc offset where the original program should start + // Without this header it should start at 0, but we add 2 for the call and 1 for the return instruction + // and also 1 for each instruction added to copy each return value into the output segment + let program_start_offset: i16 = 3 + return_type_size; + + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel program_start_offset; // Begin program execution by calling the first instruction in the original program + }; + // Append each return value to the output segment + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + casm_extend! {ctx, + ret; + }; + ctx.instructions +} + /// Returns the instructions to add to the beginning of the code to successfully call the main /// function, as well as the builtins required to execute the program. fn create_entry_code( @@ -342,12 +382,12 @@ fn create_entry_code( type_sizes: &UnorderedHashMap, func: &Function, initial_gas: usize, - proof_mode: bool, + append_output: bool, args: &[FuncArg], ) -> Result<(Vec, Vec), Error> { let mut ctx = casm! {}; // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); + let (builtins, builtin_offset) = get_function_builtins(func, append_output); // Load all vecs to memory. // Load all array args content to memory. @@ -401,7 +441,7 @@ fn create_entry_code( let generic_ty = &info.long_id.generic_id; if let Some(offset) = builtin_offset.get(generic_ty) { let mut offset = *offset; - if proof_mode { + if append_output { // Everything is off by 2 due to the proof mode header offset += 2; } @@ -534,7 +574,7 @@ impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { fn get_function_builtins( func: &Function, - proof_mode: bool, + append_output: bool, ) -> ( Vec, HashMap, @@ -585,7 +625,7 @@ fn get_function_builtins( current_offset += 1; } // Force an output builtin so that we can write the program output into it's segment - if proof_mode { + if append_output { builtins.push(BuiltinName::output); builtin_offset.insert(OutputType::ID, current_offset); } @@ -639,7 +679,7 @@ fn fetch_return_values( // Calculates builtins' final_stack setting each stop_ptr // Calling this function is a must if either air_public_input or cairo_pie are needed fn finalize_builtins( - proof_mode: bool, + skip_output: bool, main_ret_types: &[ConcreteTypeId], type_sizes: &UnorderedHashMap, vm: &mut VirtualMachine, @@ -678,7 +718,7 @@ fn finalize_builtins( } // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, skip_output)?; Ok(()) } @@ -732,17 +772,23 @@ mod tests { #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] #[case("../cairo_programs/cairo-1-programs/simple.cairo")] #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] - fn check_append_ret_values_to_output_segment(#[case] filename: &str) { + fn check_append_ret_values_to_output_segment( + #[case] filename: &str, + #[values(true, false)] proof_mode: bool, + ) { // Compile to sierra let sierra_program = compile_to_sierra(filename); // Set proof_mode let cairo_run_config = Cairo1RunConfig { - proof_mode: true, + proof_mode, layout: "all_cairo", + append_return_values: !proof_mode, // This is so we can test appending return values when not running in proof_mode + finalize_builtins: true, ..Default::default() }; // Run program - let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); + let (runner, vm, return_values) = + cairo_run_program(&sierra_program, cairo_run_config).unwrap(); // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values // And handle the panics returning an error, so we need to add it here let return_values = if main_hash_panic_result(&sierra_program) { @@ -762,5 +808,10 @@ mod tests { assert!(vm .get_maybe(&Relocatable::from((2_isize, return_values.len()))) .is_none()); + + // Check that cairo_pie can be outputted when not running in proof_mode + if !proof_mode { + assert!(runner.get_cairo_pie(&vm).is_ok()) + } } } diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index c970d113cb..9dc3367e0c 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -58,6 +58,13 @@ struct Args { args: FuncArgs, #[clap(long = "print_output", value_parser)] print_output: bool, + #[clap( + long = "append_return_values", + // We need to add these air_private_input & air_public_input or else + // passing cairo_pie_output + either of these without proof_mode will not fail + conflicts_with_all = ["proof_mode", "air_private_input", "air_public_input"] + )] + append_return_values: bool, } #[derive(Debug, Clone)] @@ -211,6 +218,7 @@ fn run(args: impl Iterator) -> Result, Error> { trace_enabled: args.trace_file.is_some() || args.air_public_input.is_some(), args: &args.args.0, finalize_builtins: args.air_private_input.is_some() || args.cairo_pie_output.is_some(), + append_return_values: args.append_return_values, }; let compiler_config = CompilerConfig { diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 5e3aba9571..e6e797fe2c 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -113,7 +113,7 @@ 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::Stored); + zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); zip_writer.start_file("version.json", options)?; zip_writer.write_all(serde_json::to_string(&self.version)?.as_bytes())?; zip_writer.start_file("metadata.json", options)?; From 136296ab41ac228ba38f8e7475e59a90f56f20a0 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:59:53 -0300 Subject: [PATCH 03/36] Fix output serialization for cairo 1 (#1645) * Revert "Add cairo1-run pretty printing (#1630)" This reverts commit 061ba872d94a0e49027132f2215b4d4ad4ee95d6. * First iteration progress * Copy tests from reverted PR * Fix expected test value * Format fixes + add continue * Fmt + better commentary * Update test file * Update commentary * Add test for reported bug * Update Changelog * Fix * Dereference references upon output serialization + Add test * Add comment to serialize_output function * Expand CHANGELOG description with new behaviour * fmt * Use doc commnt --- CHANGELOG.md | 5 + cairo1-run/src/main.rs | 149 ++++++++++++++- cairo1-run/src/serialize_output.rs | 169 ------------------ .../array_integer_tuple.cairo | 9 + .../cairo-1-programs/nullable_box_vec.cairo | 19 ++ 5 files changed, 173 insertions(+), 178 deletions(-) delete mode 100644 cairo1-run/src/serialize_output.rs create mode 100644 cairo_programs/cairo-1-programs/array_integer_tuple.cairo create mode 100644 cairo_programs/cairo-1-programs/nullable_box_vec.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3fcdd449..5e61dd2dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ #### Upcoming Changes +* feat: Fix output serialization for cairo 1 [#1645](https://github.com/lambdaclass/cairo-vm/pull/1645) + * Reverts changes added by #1630 + * Extends the serialization of Arrays added by the `print_output` flag to Spans and Dictionaries + * Now dereferences references upon serialization + * feat: Add flag to append return values to output segment when not running in proof_mode [#1646](https://github.com/lambdaclass/cairo-vm/pull/1646) * Adds the flag `append_return_values` to both the CLI and `Cairo1RunConfig` struct. * Enabling flag will add the output builtin and the necessary instructions to append the return values to the output builtin's memory segment. diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index 9dc3367e0c..e9875babb7 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -6,24 +6,27 @@ use cairo_run::Cairo1RunConfig; use cairo_vm::{ air_public_input::PublicInputError, cairo_run::EncodeTraceError, - types::errors::program_errors::ProgramError, - vm::errors::{ - memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, - vm_errors::VirtualMachineError, + types::{errors::program_errors::ProgramError, relocatable::MaybeRelocatable}, + vm::{ + errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, + vm_errors::VirtualMachineError, + }, + vm_core::VirtualMachine, }, Felt252, }; use clap::{Parser, ValueHint}; use itertools::Itertools; -use serialize_output::serialize_output; use std::{ io::{self, Write}, + iter::Peekable, path::PathBuf, + slice::Iter, }; use thiserror::Error; pub mod cairo_run; -pub mod serialize_output; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -302,7 +305,7 @@ fn main() -> Result<(), Error> { Err(Error::Cli(err)) => err.exit(), Ok(output) => { if let Some(output_string) = output { - println!("{}", output_string); + println!("Program Output : {}", output_string); } Ok(()) } @@ -328,6 +331,116 @@ fn main() -> Result<(), Error> { } } +/// Serializes the return values in a user-friendly format +/// Displays Arrays using brackets ([]) and Dictionaries using ({}) +/// Recursively dereferences referenced values (such as Span & Box) +pub fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { + let mut output_string = String::new(); + let mut return_values_iter: Peekable> = return_values.iter().peekable(); + serialize_output_inner(&mut return_values_iter, &mut output_string, vm); + fn serialize_output_inner( + iter: &mut Peekable>, + output_string: &mut String, + vm: &VirtualMachine, + ) { + while let Some(val) = iter.next() { + if let MaybeRelocatable::RelocatableValue(x) = val { + // Check if the next value is a relocatable of the same index + if let Some(MaybeRelocatable::RelocatableValue(y)) = iter.peek() { + // Check if the two relocatable values represent a valid array in memory + if x.segment_index == y.segment_index && x.offset <= y.offset { + // Fetch the y value from the iterator so we don't serialize it twice + iter.next(); + // Fetch array + maybe_add_whitespace(output_string); + output_string.push('['); + let array = vm.get_continuous_range(*x, y.offset - x.offset).unwrap(); + let mut array_iter: Peekable> = + array.iter().peekable(); + serialize_output_inner(&mut array_iter, output_string, vm); + output_string.push(']'); + continue; + } + } + + // Check if the single relocatable value represents a span + // In this case, the reloacatable will point us to the (start, end) pair in memory + // For example, the relocatable value may be 14:0, with the segment 14 containing [13:0 13:4] which is a valid array + if let (Ok(x), Ok(y)) = (vm.get_relocatable(*x), vm.get_relocatable(x + 1)) { + if x.segment_index == y.segment_index && y.offset >= x.offset { + // Fetch array + maybe_add_whitespace(output_string); + output_string.push('['); + let array = vm.get_continuous_range(x, y.offset - x.offset).unwrap(); + let mut array_iter: Peekable> = + array.iter().peekable(); + serialize_output_inner(&mut array_iter, output_string, vm); + output_string.push(']'); + continue; + } + } + + // Check if the relocatable value represents a dictionary + // To do so we can check if the relocatable consists of the last dict_ptr, which should be a pointer to the next empty cell in the dictionary's segment + // We can check that the dict_ptr's offset is consistent with the length of the segment and that the segment is made up of tuples of three elements (key, prev_val, val) + if x.offset + == vm + .get_segment_size(x.segment_index as usize) + .unwrap_or_default() + && x.offset % 3 == 0 + { + // Fetch the dictionary's memory + let dict_mem = vm + .get_continuous_range((x.segment_index, 0).into(), x.offset) + .expect("Malformed dictionary memory"); + // Serialize the dictionary + output_string.push('{'); + // The dictionary's memory is made up of (key, prev_value, next_value) tuples + // The prev value is not relevant to the user so we can skip over it for calrity + for (key, _, value) in dict_mem.iter().tuples() { + maybe_add_whitespace(output_string); + // Serialize the key wich should always be a Felt value + output_string.push_str(&key.to_string()); + output_string.push(':'); + // Serialize the value + // We create a peekable array here in order to use the serialize_output_inner as the value could be a span + let value_vec = vec![value.clone()]; + let mut value_iter: Peekable> = + value_vec.iter().peekable(); + serialize_output_inner(&mut value_iter, output_string, vm); + } + output_string.push('}'); + continue; + } + + // Finally, if the relocatable is neither the start of an array, a span, or a dictionary, it should be a reference (Such as Box) + // In this case we show the referenced value (if it exists) + // As this reference can also hold a reference we use the serialize_output_inner function to handle it recursively + if let Some(val) = vm.get_maybe(x) { + maybe_add_whitespace(output_string); + let array = vec![val.clone()]; + let mut array_iter: Peekable> = array.iter().peekable(); + serialize_output_inner(&mut array_iter, output_string, vm); + continue; + } + } + maybe_add_whitespace(output_string); + output_string.push_str(&val.to_string()); + } + } + + fn maybe_add_whitespace(string: &mut String) { + if !string.is_empty() + && !string.ends_with('[') + && !string.ends_with(':') + && !string.ends_with('{') + { + string.push(' '); + } + } + output_string +} + #[cfg(test)] mod tests { use super::*; @@ -531,7 +644,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_felt_dict(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "{\n\t0x10473: [0x8,0x9,0xa,0xb,],\n\t0x10474: [0x1,0x2,0x3,],\n}\n"; + let expected_output = "{66675:[8 9 10 11] 66676:[1 2 3]}"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } @@ -540,7 +653,25 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_span.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_felt_span(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "[0x8,0x9,0xa,0xb,]"; + let expected_output = "[8 9 10 11]"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/array_integer_tuple.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/array_integer_tuple.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_array_integer_tuple(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "[1] 1"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/nullable_box_vec.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/nullable_box_vec.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_nullable_box_vec(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "{0:10 1:20 2:30} 3"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } } diff --git a/cairo1-run/src/serialize_output.rs b/cairo1-run/src/serialize_output.rs deleted file mode 100644 index 562f2dc832..0000000000 --- a/cairo1-run/src/serialize_output.rs +++ /dev/null @@ -1,169 +0,0 @@ -use cairo_vm::{ - types::relocatable::{MaybeRelocatable, Relocatable}, - vm::{errors::memory_errors::MemoryError, vm_core::VirtualMachine}, - Felt252, -}; -use itertools::Itertools; -use std::{collections::HashMap, iter::Peekable, slice::Iter}; -use thiserror::Error; - -#[derive(Debug)] -pub(crate) enum Output { - Felt(Felt252), - FeltSpan(Vec), - FeltDict(HashMap), -} - -#[derive(Debug, Error)] -pub struct FormatError; - -impl std::fmt::Display for FormatError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Format error") - } -} - -impl Output { - pub fn from_memory( - vm: &VirtualMachine, - relocatable: &Relocatable, - ) -> Result { - match vm.get_relocatable(*relocatable) { - Ok(relocatable_value) => { - let segment_size = vm - .get_segment_size(relocatable_value.segment_index as usize) - .ok_or(FormatError)?; - let segment_data = vm - .get_continuous_range(relocatable_value, segment_size) - .map_err(|_| FormatError)?; - - // check if the segment data is a valid array of felts - if segment_data - .iter() - .all(|v| matches!(v, MaybeRelocatable::Int(_))) - { - let span_segment: Vec = segment_data - .iter() - .map(|v| Output::Felt(v.get_int().unwrap())) - .collect(); - Ok(Output::FeltSpan(span_segment)) - } else { - Err(FormatError) - } - } - Err(MemoryError::UnknownMemoryCell(relocatable_value)) => { - // here we assume that the value is a dictionary - let mut felt252dict: HashMap = HashMap::new(); - - let segment_size = vm - .get_segment_size(relocatable_value.segment_index as usize) - .ok_or(FormatError)?; - let mut segment_start = relocatable_value.clone(); - segment_start.offset = 0; - let segment_data = vm - .get_continuous_range(*segment_start, segment_size) - .map_err(|_| FormatError)?; - - for (dict_key, _, value_relocatable) in segment_data.iter().tuples() { - let key = dict_key.get_int().ok_or(FormatError)?; - let value_segment = value_relocatable.get_relocatable().ok_or(FormatError)?; - let value = Output::from_memory(vm, &value_segment)?; - felt252dict.insert(key, value); - } - Ok(Output::FeltDict(felt252dict)) - } - _ => Err(FormatError), - } - } -} - -impl std::fmt::Display for Output { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Output::Felt(felt) => write!(f, "{}", felt.to_hex_string()), - Output::FeltSpan(span) => { - write!(f, "[")?; - for elem in span { - write!(f, "{}", elem)?; - write!(f, ",")?; - } - write!(f, "]")?; - Ok(()) - } - Output::FeltDict(felt_dict) => { - let mut keys: Vec<_> = felt_dict.keys().collect(); - keys.sort(); - writeln!(f, "{{")?; - for key in keys { - writeln!(f, "\t{}: {},", key.to_hex_string(), felt_dict[key])?; - } - writeln!(f, "}}")?; - Ok(()) - } - } - } -} - -pub(crate) fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { - let mut output_string = String::new(); - let mut return_values_iter: Peekable> = return_values.iter().peekable(); - let result = serialize_output_inner(&mut return_values_iter, &mut output_string, vm); - if result.is_err() { - return result.err().unwrap().to_string(); - } - - output_string -} - -fn maybe_add_whitespace(string: &mut String) { - if !string.is_empty() && !string.ends_with('[') { - string.push(' '); - } -} - -fn serialize_output_inner( - iter: &mut Peekable>, - output_string: &mut String, - vm: &VirtualMachine, -) -> Result<(), FormatError> { - while let Some(val) = iter.next() { - match val { - MaybeRelocatable::Int(x) => { - maybe_add_whitespace(output_string); - output_string.push_str(&x.to_string()); - continue; - } - MaybeRelocatable::RelocatableValue(x) if ((iter.len() + 1) % 2) == 0 /* felt array */ => { - // Check if the next value is a relocatable of the same index - let y = iter.next().unwrap().get_relocatable().ok_or(FormatError)?; - // Check if the two relocatable values represent a valid array in memory - if x.segment_index == y.segment_index && x.offset <= y.offset { - // Fetch array - maybe_add_whitespace(output_string); - output_string.push('['); - let array = vm.get_continuous_range(*x, y.offset - x.offset).map_err(|_| FormatError)?; - let mut array_iter: Peekable> = - array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm)?; - output_string.push(']'); - continue; - } - }, - MaybeRelocatable::RelocatableValue(x) if iter.len() > 1 => { - let mut segment_start = *x; - segment_start.offset = 0; - for elem in iter.into_iter() { - let output_value = Output::from_memory(vm, &elem.get_relocatable().ok_or(FormatError)?)?; - output_string.push_str(output_value.to_string().as_str()) - } - } - MaybeRelocatable::RelocatableValue(x) => { - match Output::from_memory(vm, x) { - Ok(output_value) => output_string.push_str(format!("{}", output_value).as_str()), - Err(_) => output_string.push_str("The output could not be formatted"), - } - } - } - } - Ok(()) -} diff --git a/cairo_programs/cairo-1-programs/array_integer_tuple.cairo b/cairo_programs/cairo-1-programs/array_integer_tuple.cairo new file mode 100644 index 0000000000..cb018f3a4a --- /dev/null +++ b/cairo_programs/cairo-1-programs/array_integer_tuple.cairo @@ -0,0 +1,9 @@ +use core::array::ArrayTrait; + + +fn main() -> (Array, u32) { + let mut numbers = ArrayTrait::new(); + numbers.append(1); + + (numbers, 1) +} diff --git a/cairo_programs/cairo-1-programs/nullable_box_vec.cairo b/cairo_programs/cairo-1-programs/nullable_box_vec.cairo new file mode 100644 index 0000000000..37b2b8042a --- /dev/null +++ b/cairo_programs/cairo-1-programs/nullable_box_vec.cairo @@ -0,0 +1,19 @@ +struct NullableVec { + items: Felt252Dict>>, + len: usize, +} + +fn main() -> NullableVec { + let mut d: Felt252Dict>> = Default::default(); + + // Populate the dictionary + d.insert(0, nullable_from_box(BoxTrait::new(BoxTrait::new(10)))); + d.insert(1, nullable_from_box(BoxTrait::new(BoxTrait::new(20)))); + d.insert(2, nullable_from_box(BoxTrait::new(BoxTrait::new(30)))); + + // Return NullableVec + NullableVec { + items: d, + len: 3, + } +} From 7566aa57c15ed913d54f6516db90c18669a62853 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:42:06 -0300 Subject: [PATCH 04/36] Sort builtin segment info upon serialization for Cairo PIE (#1654) * Custom serialization * Add changelog entry * Clippy --- CHANGELOG.md | 2 ++ vm/src/vm/runners/cairo_pie.rs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e61dd2dee..e2488d713f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Sort builtin segment info upon serialization for Cairo PIE [#1654](https://github.com/lambdaclass/cairo-vm/pull/1654) + * feat: Fix output serialization for cairo 1 [#1645](https://github.com/lambdaclass/cairo-vm/pull/1645) * Reverts changes added by #1630 * Extends the serialization of Arrays added by the `print_output` flag to Spans and Dictionaries diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index e6e797fe2c..248a15828f 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -84,6 +84,7 @@ pub struct CairoPieMetadata { pub execution_segment: SegmentInfo, pub ret_fp_segment: SegmentInfo, pub ret_pc_segment: SegmentInfo, + #[serde(serialize_with = "serde_impl::serialize_builtin_segments")] pub builtin_segments: HashMap, pub extra_segments: Vec, } @@ -132,8 +133,9 @@ impl CairoPie { mod serde_impl { use crate::stdlib::collections::HashMap; use num_traits::Num; + use serde::ser::SerializeMap; - use super::{CairoPieMemory, CAIRO_PIE_VERSION}; + use super::{CairoPieMemory, SegmentInfo, CAIRO_PIE_VERSION}; use crate::stdlib::prelude::{String, Vec}; use crate::{ types::relocatable::{MaybeRelocatable, Relocatable}, @@ -321,6 +323,33 @@ mod serde_impl { seq_serializer.end() } + + pub fn serialize_builtin_segments( + values: &HashMap, + serializer: S, + ) -> Result + where + S: Serializer, + { + let mut map_serializer = serializer.serialize_map(Some(values.len()))?; + const BUILTIN_ORDERED_LIST: &[&str] = &[ + "output", + "pedersen", + "range_check", + "ecdsa", + "bitwise", + "ec_op", + "keccak", + "poseidon", + ]; + + for name in BUILTIN_ORDERED_LIST { + if let Some(info) = values.get(*name) { + map_serializer.serialize_entry(name, info)? + } + } + map_serializer.end() + } } #[cfg(test)] From 1fc537cec94b414afe51969e06516844b15f0165 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:31:42 -0300 Subject: [PATCH 05/36] Add workflow to compare vm outputs for factorial on all layouts (#1653) * Show only layout builtins in air private input * Add test * Add changelog entry * Fix wasm import * Run for extra step in proof mode * Add recursive layout * Add script * Add workflow * Add workflow * Add workflow * Update file permissions * Fix * Fix script * Fix workflow * Fix workflow * Fix typo * Fix workflow * Fix workflow * Fix workflow * Fix workflow * Clean files after script --- .github/workflows/rust.yml | 39 +++++ CHANGELOG.md | 2 + cairo-vm-cli/src/main.rs | 1 + cairo1-run/src/cairo_run.rs | 4 + vm/src/air_private_input.rs | 152 ++++++++++-------- vm/src/cairo_run.rs | 4 + .../compare_factorial_outputs_all_layouts.sh | 66 ++++++++ vm/src/vm/runners/cairo_runner.rs | 1 + 8 files changed, 205 insertions(+), 64 deletions(-) create mode 100755 vm/src/tests/compare_factorial_outputs_all_layouts.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3e13f56f9b..8af32e5197 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -613,3 +613,42 @@ jobs: cairo-compile cairo_programs/array_sum.cairo --no_debug_info --output cairo_programs/array_sum.json cd examples/wasm-demo wasm-pack build --target=web + + compare-factorial-outputs-all-layouts: + name: Compare factorial outputs for all layouts + needs: [ build-programs, build-release ] + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Python3 Build + uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install cairo-lang and deps + run: pip install -r requirements.txt + + - name: Fetch release binary + uses: actions/cache/restore@v3 + with: + key: cli-bin-rel-${{ github.sha }} + path: target/release/cairo-vm-cli + fail-on-cache-miss: true + + - uses: actions/download-artifact@master + with: + name: proof_programs + path: cairo_programs/proof_programs/ + + - name: Fetch programs + uses: actions/cache/restore@v3 + with: + path: ${{ env.CAIRO_PROGRAMS_PATH }} + key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'examples/wasm-demo/src/array_sum.cairo') }} + fail-on-cache-miss: true + + - name: Run script + run: ./vm/src/tests/compare_factorial_outputs_all_layouts.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index e2488d713f..413fa67059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Show only layout builtins in air private input [#1651](https://github.com/lambdaclass/cairo-vm/pull/1651) + * feat: Sort builtin segment info upon serialization for Cairo PIE [#1654](https://github.com/lambdaclass/cairo-vm/pull/1654) * feat: Fix output serialization for cairo 1 [#1645](https://github.com/lambdaclass/cairo-vm/pull/1645) diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 276dd6b70b..4d75179c6b 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -61,6 +61,7 @@ fn validate_layout(value: &str) -> Result { "plain" | "small" | "dex" + | "recursive" | "starknet" | "starknet_with_keccak" | "recursive_large_output" diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 348bb8f9a5..8376727918 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -195,6 +195,10 @@ pub fn cairo_run_program( // Run it until the end / infinite loop in proof_mode runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + if cairo_run_config.proof_mode { + runner.run_for_steps(1, &mut vm, &mut hint_processor)?; + } + if cairo_run_config.proof_mode || cairo_run_config.append_return_values { // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size diff --git a/vm/src/air_private_input.rs b/vm/src/air_private_input.rs index 6429da0673..52e3ac4cae 100644 --- a/vm/src/air_private_input.rs +++ b/vm/src/air_private_input.rs @@ -17,13 +17,20 @@ use crate::Felt252; pub struct AirPrivateInputSerializable { trace_path: String, memory_path: String, - pedersen: Vec, - range_check: Vec, - ecdsa: Vec, - bitwise: Vec, - ec_op: Vec, - keccak: Vec, - poseidon: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pedersen: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + range_check: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + ecdsa: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bitwise: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + ec_op: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + keccak: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + poseidon: Option>, } // Contains only builtin public inputs, useful for library users @@ -108,44 +115,34 @@ impl AirPrivateInput { AirPrivateInputSerializable { trace_path, memory_path, - pedersen: self.0.get(HASH_BUILTIN_NAME).cloned().unwrap_or_default(), - range_check: self - .0 - .get(RANGE_CHECK_BUILTIN_NAME) - .cloned() - .unwrap_or_default(), - ecdsa: self - .0 - .get(SIGNATURE_BUILTIN_NAME) - .cloned() - .unwrap_or_default(), - bitwise: self - .0 - .get(BITWISE_BUILTIN_NAME) - .cloned() - .unwrap_or_default(), - ec_op: self.0.get(EC_OP_BUILTIN_NAME).cloned().unwrap_or_default(), - keccak: self.0.get(KECCAK_BUILTIN_NAME).cloned().unwrap_or_default(), - poseidon: self - .0 - .get(POSEIDON_BUILTIN_NAME) - .cloned() - .unwrap_or_default(), + pedersen: self.0.get(HASH_BUILTIN_NAME).cloned(), + range_check: self.0.get(RANGE_CHECK_BUILTIN_NAME).cloned(), + ecdsa: self.0.get(SIGNATURE_BUILTIN_NAME).cloned(), + bitwise: self.0.get(BITWISE_BUILTIN_NAME).cloned(), + ec_op: self.0.get(EC_OP_BUILTIN_NAME).cloned(), + keccak: self.0.get(KECCAK_BUILTIN_NAME).cloned(), + poseidon: self.0.get(POSEIDON_BUILTIN_NAME).cloned(), } } } impl From for AirPrivateInput { fn from(private_input: AirPrivateInputSerializable) -> Self { - Self(HashMap::from([ - (HASH_BUILTIN_NAME, private_input.pedersen), - (RANGE_CHECK_BUILTIN_NAME, private_input.range_check), - (SIGNATURE_BUILTIN_NAME, private_input.ecdsa), - (BITWISE_BUILTIN_NAME, private_input.bitwise), - (EC_OP_BUILTIN_NAME, private_input.ec_op), - (KECCAK_BUILTIN_NAME, private_input.keccak), - (POSEIDON_BUILTIN_NAME, private_input.poseidon), - ])) + let mut inputs = HashMap::new(); + let mut insert_input = |input_name, input| { + if let Some(input) = input { + inputs.insert(input_name, input); + } + }; + insert_input(HASH_BUILTIN_NAME, private_input.pedersen); + insert_input(RANGE_CHECK_BUILTIN_NAME, private_input.range_check); + insert_input(SIGNATURE_BUILTIN_NAME, private_input.ecdsa); + insert_input(BITWISE_BUILTIN_NAME, private_input.bitwise); + insert_input(EC_OP_BUILTIN_NAME, private_input.ec_op); + insert_input(KECCAK_BUILTIN_NAME, private_input.keccak); + insert_input(POSEIDON_BUILTIN_NAME, private_input.poseidon); + + Self(inputs) } } @@ -168,22 +165,25 @@ mod tests { assert_matches::assert_matches, }; + #[cfg(any(target_arch = "wasm32", no_std, not(feature = "std")))] + use crate::alloc::string::ToString; + #[cfg(feature = "std")] #[test] fn test_from_serializable() { let serializable_private_input = AirPrivateInputSerializable { trace_path: "trace.bin".to_string(), memory_path: "memory.bin".to_string(), - pedersen: vec![PrivateInput::Pair(PrivateInputPair { + pedersen: Some(vec![PrivateInput::Pair(PrivateInputPair { index: 0, x: Felt252::from(100), y: Felt252::from(200), - })], - range_check: vec![PrivateInput::Value(PrivateInputValue { + })]), + range_check: Some(vec![PrivateInput::Value(PrivateInputValue { index: 10000, value: Felt252::from(8000), - })], - ecdsa: vec![PrivateInput::Signature(PrivateInputSignature { + })]), + ecdsa: Some(vec![PrivateInput::Signature(PrivateInputSignature { index: 0, pubkey: Felt252::from(123), msg: Felt252::from(456), @@ -191,21 +191,21 @@ mod tests { r: Felt252::from(654), w: Felt252::from(321), }, - })], - bitwise: vec![PrivateInput::Pair(PrivateInputPair { + })]), + bitwise: Some(vec![PrivateInput::Pair(PrivateInputPair { index: 4, x: Felt252::from(7), y: Felt252::from(8), - })], - ec_op: vec![PrivateInput::EcOp(PrivateInputEcOp { + })]), + ec_op: Some(vec![PrivateInput::EcOp(PrivateInputEcOp { index: 1, p_x: Felt252::from(10), p_y: Felt252::from(10), m: Felt252::from(100), q_x: Felt252::from(11), q_y: Felt252::from(14), - })], - keccak: vec![PrivateInput::KeccakState(PrivateInputKeccakState { + })]), + keccak: Some(vec![PrivateInput::KeccakState(PrivateInputKeccakState { index: 0, input_s0: Felt252::from(0), input_s1: Felt252::from(1), @@ -215,23 +215,47 @@ mod tests { input_s5: Felt252::from(5), input_s6: Felt252::from(6), input_s7: Felt252::from(7), - })], - poseidon: vec![PrivateInput::PoseidonState(PrivateInputPoseidonState { - index: 42, - input_s0: Felt252::from(1), - input_s1: Felt252::from(2), - input_s2: Felt252::from(3), - })], + })]), + poseidon: Some(vec![PrivateInput::PoseidonState( + PrivateInputPoseidonState { + index: 42, + input_s0: Felt252::from(1), + input_s1: Felt252::from(2), + input_s2: Felt252::from(3), + }, + )]), }; let private_input = AirPrivateInput::from(serializable_private_input.clone()); - assert_matches!(private_input.0.get(HASH_BUILTIN_NAME), Some(data) if *data == serializable_private_input.pedersen); - assert_matches!(private_input.0.get(RANGE_CHECK_BUILTIN_NAME), Some(data) if *data == serializable_private_input.range_check); - assert_matches!(private_input.0.get(SIGNATURE_BUILTIN_NAME), Some(data) if *data == serializable_private_input.ecdsa); - assert_matches!(private_input.0.get(BITWISE_BUILTIN_NAME), Some(data) if *data == serializable_private_input.bitwise); - assert_matches!(private_input.0.get(EC_OP_BUILTIN_NAME), Some(data) if *data == serializable_private_input.ec_op); - assert_matches!(private_input.0.get(KECCAK_BUILTIN_NAME), Some(data) if *data == serializable_private_input.keccak); - assert_matches!(private_input.0.get(POSEIDON_BUILTIN_NAME), Some(data) if *data == serializable_private_input.poseidon); + assert_matches!(private_input.0.get(HASH_BUILTIN_NAME), data if data == serializable_private_input.pedersen.as_ref()); + assert_matches!(private_input.0.get(RANGE_CHECK_BUILTIN_NAME), data if data == serializable_private_input.range_check.as_ref()); + assert_matches!(private_input.0.get(SIGNATURE_BUILTIN_NAME), data if data == serializable_private_input.ecdsa.as_ref()); + assert_matches!(private_input.0.get(BITWISE_BUILTIN_NAME), data if data == serializable_private_input.bitwise.as_ref()); + assert_matches!(private_input.0.get(EC_OP_BUILTIN_NAME), data if data == serializable_private_input.ec_op.as_ref()); + assert_matches!(private_input.0.get(KECCAK_BUILTIN_NAME), data if data == serializable_private_input.keccak.as_ref()); + assert_matches!(private_input.0.get(POSEIDON_BUILTIN_NAME), data if data == serializable_private_input.poseidon.as_ref()); + } + + #[test] + fn serialize_air_private_input_small_layout_only_builtins() { + let config = crate::cairo_run::CairoRunConfig { + proof_mode: true, + relocate_mem: true, + trace_enabled: true, + layout: "small", + ..Default::default() + }; + let (runner, vm) = crate::cairo_run::cairo_run(include_bytes!("../../cairo_programs/proof_programs/fibonacci.json"), &config, &mut crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor::new_empty()).unwrap(); + let public_input = runner.get_air_private_input(&vm); + let serialized_public_input = + public_input.to_serializable("/dev/null".to_string(), "/dev/null".to_string()); + assert!(serialized_public_input.pedersen.is_some()); + assert!(serialized_public_input.range_check.is_some()); + assert!(serialized_public_input.ecdsa.is_some()); + assert!(serialized_public_input.bitwise.is_none()); + assert!(serialized_public_input.ec_op.is_none()); + assert!(serialized_public_input.keccak.is_none()); + assert!(serialized_public_input.poseidon.is_none()); } } diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index 8293bc9625..b0b4a53c60 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -88,6 +88,10 @@ pub fn cairo_run_program( cairo_runner .run_until_pc(end, &mut vm, hint_executor) .map_err(|err| VmException::from_vm_error(&cairo_runner, &vm, err))?; + + if cairo_run_config.proof_mode { + cairo_runner.run_for_steps(1, &mut vm, hint_executor)?; + } cairo_runner.end_run( cairo_run_config.disable_trace_padding, false, diff --git a/vm/src/tests/compare_factorial_outputs_all_layouts.sh b/vm/src/tests/compare_factorial_outputs_all_layouts.sh new file mode 100755 index 0000000000..1a2067e4c8 --- /dev/null +++ b/vm/src/tests/compare_factorial_outputs_all_layouts.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env sh + +factorial_compiled="cairo_programs/proof_programs/factorial.json" +passed_tests=0 +failed_tests=0 +exit_code=0 + +for layout in "plain" "small" "dex" "recursive" "starknet" "starknet_with_keccak" "recursive_large_output" "all_solidity" "starknet_with_keccak"; do + # Run cairo_vm + echo "Running cairo-vm with layout $layout" + cargo run -p cairo-vm-cli --release -- --layout $layout --proof_mode $factorial_compiled --trace_file factorial_rs.trace --memory_file factorial_rs.memory --air_public_input factorial_rs.air_public_input --air_private_input factorial_rs.air_private_input + # Run cairo_lang + echo "Running cairo_lang with layout $layout" + cairo-run --layout $layout --proof_mode --program $factorial_compiled --trace_file factorial_py.trace --memory_file factorial_py.memory --air_public_input factorial_py.air_public_input --air_private_input factorial_py.air_private_input + # Compare trace + echo "Running trace comparison for layout $layout" + if ! diff -q factorial_rs.trace factorial_py.trace; then + echo "Trace differs for layout $layout" + exit_code=1 + failed_tests=$((failed_tests + 1)) + 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 + echo "Air public input differs for layout $layout" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + # Compare air private input + echo "Running air private input comparison for layout $layout" + if ! ./vm/src/tests/air_private_input_comparator.py factorial_rs.air_private_input factorial_py.air_private_input; then + echo "Air private input differs for layout $layout" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + # Clean files generated by the script + echo "Cleaning files" + rm factorial_rs.* + rm factorial_py.* +done + +if test $failed_tests != 0; then + echo "Comparisons: $failed_tests failed, $passed_tests passed, $((failed_tests + passed_tests)) total" +elif test $passed_tests = 0; then + echo "No tests ran!" + exit_code=2 +else + echo "All $passed_tests tests passed; no discrepancies found" +fi + +exit "${exit_code}" diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index c117ccda1c..4dcd533cc9 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -179,6 +179,7 @@ impl CairoRunner { "plain" => CairoLayout::plain_instance(), "small" => CairoLayout::small_instance(), "dex" => CairoLayout::dex_instance(), + "recursive" => CairoLayout::recursive_instance(), "starknet" => CairoLayout::starknet_instance(), "starknet_with_keccak" => CairoLayout::starknet_with_keccak_instance(), "recursive_large_output" => CairoLayout::recursive_large_output_instance(), From 59a97c75394037687d7064a7f8641dc679c5842f Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:32:23 -0300 Subject: [PATCH 06/36] Show only layout builtins in air private input (#1651) * Show only layout builtins in air private input * Add test * Add changelog entry * Fix wasm import From 6ae7b32b0cb8cf758e52fbf795d6689b81a7ca0e Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:56:01 -0300 Subject: [PATCH 07/36] Make air public inputs deserializable (#1648) * Derive deserialize for AirPublicInput * Add test * Add more test cases * Add Changelog entry * Clippy * Fix import * Fix import * fmt --- CHANGELOG.md | 2 + vm/src/air_public_input.rs | 95 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 413fa67059..ed1c5da691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Make air public inputs deserializable [#1648](https://github.com/lambdaclass/cairo-vm/pull/1648) + * feat: Show only layout builtins in air private input [#1651](https://github.com/lambdaclass/cairo-vm/pull/1651) * feat: Sort builtin segment info upon serialization for Cairo PIE [#1654](https://github.com/lambdaclass/cairo-vm/pull/1654) diff --git a/vm/src/air_public_input.rs b/vm/src/air_public_input.rs index 692f391f2a..3a2bf8bc37 100644 --- a/vm/src/air_public_input.rs +++ b/vm/src/air_public_input.rs @@ -1,5 +1,5 @@ use crate::Felt252; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use thiserror_no_std::Error; use crate::{ @@ -14,18 +14,21 @@ use crate::{ }, }; -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct PublicMemoryEntry { pub address: usize, #[serde(serialize_with = "mem_value_serde::serialize")] + #[serde(deserialize_with = "mem_value_serde::deserialize")] pub value: Option, pub page: usize, } mod mem_value_serde { + use core::fmt; + use super::*; - use serde::Serializer; + use serde::{de, Deserializer, Serializer}; pub(crate) fn serialize( value: &Option, @@ -37,9 +40,41 @@ mod mem_value_serde { serializer.serialize_none() } } + + pub(crate) fn deserialize<'de, D: Deserializer<'de>>( + d: D, + ) -> Result, D::Error> { + d.deserialize_str(Felt252OptionVisitor) + } + + struct Felt252OptionVisitor; + + impl<'de> de::Visitor<'de> for Felt252OptionVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Could not deserialize hexadecimal string") + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Felt252::from_hex(value) + .map_err(de::Error::custom) + .map(Some) + } + } } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct MemorySegmentAddresses { pub begin_addr: usize, pub stop_ptr: usize, @@ -55,7 +90,7 @@ impl From<(usize, usize)> for MemorySegmentAddresses { } } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct PublicInput<'a> { pub layout: &'a str, pub rc_min: isize, @@ -64,6 +99,7 @@ pub struct PublicInput<'a> { pub memory_segments: HashMap<&'a str, MemorySegmentAddresses>, pub public_memory: Vec, #[serde(rename = "dynamic_params")] + #[serde(skip_deserializing)] // This is set to None by default so we can skip it layout_params: Option<&'a CairoLayout>, } @@ -139,3 +175,52 @@ pub enum PublicInputError { #[error(transparent)] Trace(#[from] TraceError), } +#[cfg(test)] +mod tests { + #[cfg(feature = "std")] + use super::*; + #[cfg(feature = "std")] + use rstest::rstest; + + #[cfg(feature = "std")] + #[rstest] + #[case(include_bytes!("../../cairo_programs/proof_programs/fibonacci.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/bitwise_output.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/keccak_builtin.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/poseidon_builtin.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/relocate_temporary_segment_append.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/pedersen_test.json"))] + #[case(include_bytes!("../../cairo_programs/proof_programs/ec_op.json"))] + fn serialize_and_deserialize_air_public_input(#[case] program_content: &[u8]) { + let config = crate::cairo_run::CairoRunConfig { + proof_mode: true, + relocate_mem: true, + trace_enabled: true, + layout: "all_cairo", + ..Default::default() + }; + let (runner, vm) = crate::cairo_run::cairo_run(program_content, &config, &mut crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor::new_empty()).unwrap(); + let public_input = runner.get_air_public_input(&vm).unwrap(); + // We already know serialization works as expected due to the comparison against python VM + let serialized_public_input = public_input.serialize_json().unwrap(); + let deserialized_public_input: PublicInput = + serde_json::from_str(&serialized_public_input).unwrap(); + // Check that the deserialized public input is equal to the one we obtained from the vm first + assert_eq!(public_input.layout, deserialized_public_input.layout); + assert_eq!(public_input.rc_max, deserialized_public_input.rc_max); + assert_eq!(public_input.rc_min, deserialized_public_input.rc_min); + assert_eq!(public_input.n_steps, deserialized_public_input.n_steps); + assert_eq!( + public_input.memory_segments, + deserialized_public_input.memory_segments + ); + assert_eq!( + public_input.public_memory, + deserialized_public_input.public_memory + ); + assert!( + public_input.layout_params.is_none() + && deserialized_public_input.layout_params.is_none() + ); + } +} From 105ca3a78fc4e3fb3057a926b49d3a2fbc1d0963 Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:21:59 +0530 Subject: [PATCH 08/36] New tracer pr (#1643) * add tracer cairo 0 * fix after rebase * Update CHANGELOG.md Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> * Update docs/tracer/README.md Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> * remove option for tracer * remove .idea * Fix clippy --------- Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> Co-authored-by: Pedro Fontana Co-authored-by: Pedro Fontana --- CHANGELOG.md | 1 + Cargo.lock | 650 +++++++++++++++++- Cargo.toml | 4 +- README.md | 36 +- cairo-vm-cli/Cargo.toml | 2 + cairo-vm-cli/src/main.rs | 48 +- cairo-vm-tracer/Cargo.toml | 28 + cairo-vm-tracer/src/error/mod.rs | 1 + .../src/error/trace_data_errors.rs | 24 + cairo-vm-tracer/src/lib.rs | 4 + cairo-vm-tracer/src/tracer.rs | 134 ++++ cairo-vm-tracer/src/tracer_data.rs | 221 ++++++ cairo-vm-tracer/src/types/memory_access.rs | 9 + cairo-vm-tracer/src/types/mod.rs | 1 + cairo-vm-tracer/static/index.html | 56 ++ cairo-vm-tracer/static/tracer.css | 81 +++ cairo-vm-tracer/static/tracer.js | 387 +++++++++++ docs/README.md | 1 + docs/tracer/README.md | 39 ++ docs/tracer/tracer.png | Bin 0 -> 739142 bytes felt/src/lib_bigint_felt.rs | 0 vm/Cargo.toml | 2 + vm/src/serde/deserialize_program.rs | 22 + vm/src/types/program.rs | 73 +- vm/src/vm/context/run_context.rs | 4 + vm/src/vm/trace/mod.rs | 2 +- vm/src/vm/vm_core.rs | 5 + 27 files changed, 1817 insertions(+), 18 deletions(-) create mode 100644 cairo-vm-tracer/Cargo.toml create mode 100644 cairo-vm-tracer/src/error/mod.rs create mode 100644 cairo-vm-tracer/src/error/trace_data_errors.rs create mode 100644 cairo-vm-tracer/src/lib.rs create mode 100644 cairo-vm-tracer/src/tracer.rs create mode 100644 cairo-vm-tracer/src/tracer_data.rs create mode 100644 cairo-vm-tracer/src/types/memory_access.rs create mode 100644 cairo-vm-tracer/src/types/mod.rs create mode 100644 cairo-vm-tracer/static/index.html create mode 100644 cairo-vm-tracer/static/tracer.css create mode 100644 cairo-vm-tracer/static/tracer.js create mode 100644 docs/tracer/README.md create mode 100644 docs/tracer/tracer.png create mode 100644 felt/src/lib_bigint_felt.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1c5da691..1547001753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Cairo-VM Changelog #### Upcoming Changes +* feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memory registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) * feat: Make air public inputs deserializable [#1648](https://github.com/lambdaclass/cairo-vm/pull/1648) diff --git a/Cargo.lock b/Cargo.lock index 6fc683d8c4..783fb44464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -51,6 +60,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -211,12 +235,109 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-compression" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd 0.13.0", + "zstd-safe 7.0.0", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64ct" version = "1.6.0" @@ -280,6 +401,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.15.3" @@ -298,6 +440,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + [[package]] name = "bzip2" version = "0.4.4" @@ -780,6 +928,7 @@ dependencies = [ "assert_matches", "bincode", "cairo-vm", + "cairo-vm-tracer", "clap", "mimalloc", "nom", @@ -787,6 +936,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cairo-vm-tracer" +version = "1.0.0-rc1" +dependencies = [ + "axum", + "cairo-vm", + "include_dir", + "mime_guess", + "num-bigint", + "num-traits 0.2.18", + "serde", + "thiserror-no-std", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", +] + [[package]] name = "cairo1-run" version = "1.0.0-rc1" @@ -1214,6 +1382,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" @@ -1360,6 +1537,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "good_lp" version = "1.7.0" @@ -1445,6 +1628,69 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "iai-callgrind" version = "0.3.1" @@ -1468,6 +1714,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indent" version = "0.1.1" @@ -1520,6 +1785,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -1700,6 +1975,12 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matrixmultiply" version = "0.2.4" @@ -1724,6 +2005,22 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minilp" version = "0.2.2" @@ -1749,6 +2046,17 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -1778,6 +2086,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -1862,6 +2180,25 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1874,6 +2211,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -1983,6 +2326,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "petgraph" version = "0.6.4" @@ -2008,6 +2357,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -2302,6 +2671,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2477,6 +2852,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -2486,6 +2871,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2518,6 +2915,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -2548,6 +2954,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -2681,6 +3097,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tap" version = "1.0.1" @@ -2750,6 +3172,16 @@ dependencies = [ "thiserror-impl-no-std", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.34" @@ -2788,6 +3220,47 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.10" @@ -2833,6 +3306,128 @@ dependencies = [ "winnow 0.6.2", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "async-compression", + "base64", + "bitflags 2.4.2", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -2854,6 +3449,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -2878,6 +3482,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" @@ -2903,6 +3522,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3283,7 +3911,7 @@ dependencies = [ "pbkdf2", "sha1", "time", - "zstd", + "zstd 0.11.2+zstd.1.5.2", ] [[package]] @@ -3292,7 +3920,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe 7.0.0", ] [[package]] @@ -3305,6 +3942,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.9+zstd.1.5.5" diff --git a/Cargo.toml b/Cargo.toml index 45b7340838..707673bde6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "vm", "hint_accountant", "examples/wasm-demo", - "cairo1-run" + "cairo1-run", + "cairo-vm-tracer" ] default-members = [ "cairo-vm-cli", @@ -27,6 +28,7 @@ keywords = ["starknet", "cairo", "vm", "wasm", "no_std"] [workspace.dependencies] cairo-vm = { path = "./vm", version = "1.0.0-rc1", default-features = false } +cairo-vm-tracer = { path = "./cairo-vm-tracer", version = "1.0.0-rc1", default-features = false } mimalloc = { version = "0.1.37", default-features = false } num-bigint = { version = "0.4", default-features = false, features = [ "serde", diff --git a/README.md b/README.md index dbbea5dd42..43b4b074ff 100644 --- a/README.md +++ b/README.md @@ -21,29 +21,37 @@ A faster and safer implementation of the Cairo VM in Rust ## Table of Contents -- [Disclaimer](#%EF%B8%8F-disclaimer) -- [About](#-about) +- [Table of Contents](#table-of-contents) +- [⚠️ Disclaimer](#️-disclaimer) +- [📖 About](#-about) - [The Cairo language](#the-cairo-language) -- [Getting Started](#-getting-started) +- [🌅 Getting Started](#-getting-started) - [Dependencies](#dependencies) -- [Usage](#-usage) + - [Required](#required) + - [Optional](#optional) + - [Installation script](#installation-script) +- [🚀 Usage](#-usage) - [Adding cairo-vm as a dependency](#adding-cairo-vm-as-a-dependency) - - [Running cairo-vm from the CLI](#running-cairo-vm-from-cli) + - [Running cairo-vm from CLI](#running-cairo-vm-from-cli) - [Using hints](#using-hints) - [Running a function in a Cairo program with arguments](#running-a-function-in-a-cairo-program-with-arguments) - [WebAssembly Demo](#webassembly-demo) - [Testing](#testing) -- [Benchmarks](#-benchmarks) -- [Changelog](#-changelog) -- [Contributing](#-contributing) -- [Related Projects](#-related-projects) -- [Documentation](#-documentation) + - [Tracer](#tracer) +- [📊 Benchmarks](#-benchmarks) +- [📜 Changelog](#-changelog) +- [🛠 Contributing](#-contributing) +- [🌞 Related Projects](#-related-projects) +- [📚 Documentation](#-documentation) - [Cairo](#cairo) - [Original Cairo VM Internals](#original-cairo-vm-internals) - [Compilers and Interpreters](#compilers-and-interpreters) - [StarkNet](#starknet) - - [Computational Integrity and Zero-Knowledge Proofs](#computational-integrity-and-zero-knowledge-proofs) -- [License](#%EF%B8%8F-license) + - [Computational Integrity and Zero Knowledge Proofs](#computational-integrity-and-zero-knowledge-proofs) + - [Basics](#basics) + - [ZK SNARKs](#zk-snarks) + - [STARKs](#starks) +- [⚖️ License](#️-license) ## ⚠️ Disclaimer @@ -260,6 +268,10 @@ Now that you have the dependencies necessary to run the test suite you can run: make test ``` +### Tracer + +Cairo-vm offers a tracer which gives you a visualization of how your memory and registers change line after line as the VM executes the code. You can read more about it [here](./docs/tracer/README.md) + ## 📊 Benchmarks Running a [Cairo program](./cairo_programs/benchmarks/big_fibonacci.cairo) that gets the 1.5 millionth Fibonacci number we got the following benchmarks: diff --git a/cairo-vm-cli/Cargo.toml b/cairo-vm-cli/Cargo.toml index 572c4db8af..783e896156 100644 --- a/cairo-vm-cli/Cargo.toml +++ b/cairo-vm-cli/Cargo.toml @@ -9,6 +9,7 @@ keywords.workspace = true [dependencies] cairo-vm = { workspace = true, features = ["std"] } +cairo-vm-tracer = { workspace = true, optional = true } clap = { version = "4.3.10", features = ["derive"] } mimalloc = { version = "0.1.37", default-features = false, optional = true } nom = "7" @@ -22,3 +23,4 @@ rstest = "0.17.0" [features] default = ["with_mimalloc"] with_mimalloc = ["cairo-vm/with_mimalloc", "dep:mimalloc"] +with_tracer = ["cairo-vm/with_tracer", "cairo-vm-tracer"] diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 4d75179c6b..f8fc6d0d62 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -4,9 +4,19 @@ use bincode::enc::write::Writer; use cairo_vm::air_public_input::PublicInputError; use cairo_vm::cairo_run::{self, EncodeTraceError}; use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; +#[cfg(feature = "with_tracer")] +use cairo_vm::serde::deserialize_program::DebugInfo; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; use cairo_vm::vm::errors::trace_errors::TraceError; use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +#[cfg(feature = "with_tracer")] +use cairo_vm::vm::runners::cairo_runner::CairoRunner; +#[cfg(feature = "with_tracer")] +use cairo_vm::vm::vm_core::VirtualMachine; +#[cfg(feature = "with_tracer")] +use cairo_vm_tracer::error::trace_data_errors::TraceDataError; +#[cfg(feature = "with_tracer")] +use cairo_vm_tracer::tracer::run_tracer; use clap::{Parser, ValueHint}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -42,7 +52,7 @@ struct Args { air_public_input: Option, #[clap( long = "air_private_input", - requires_all = ["proof_mode", "trace_file", "memory_file"] + requires_all = ["proof_mode", "trace_file", "memory_file"] )] air_private_input: Option, #[clap( @@ -54,6 +64,9 @@ struct Args { cairo_pie_output: Option, #[structopt(long = "allow_missing_builtins")] allow_missing_builtins: Option, + #[structopt(long = "tracer")] + #[cfg(feature = "with_tracer")] + tracer: bool, } fn validate_layout(value: &str) -> Result { @@ -88,6 +101,9 @@ enum Error { Trace(#[from] TraceError), #[error(transparent)] PublicInput(#[from] PublicInputError), + #[error(transparent)] + #[cfg(feature = "with_tracer")] + TraceDataError(#[from] TraceDataError), } struct FileWriter { @@ -123,6 +139,31 @@ impl FileWriter { } } +#[cfg(feature = "with_tracer")] +fn start_tracer(cairo_runner: &CairoRunner, vm: &VirtualMachine) -> Result<(), TraceDataError> { + let relocation_table = vm + .relocate_segments() + .map_err(TraceDataError::FailedToGetRelocationTable)?; + let instruction_locations = cairo_runner + .get_program() + .get_relocated_instruction_locations(relocation_table.as_ref()); + let debug_info = instruction_locations.map(DebugInfo::new); + + let relocated_trace = cairo_runner + .relocated_trace + .clone() + .ok_or(TraceDataError::FailedToGetRelocatedTrace)?; + + run_tracer( + cairo_runner.get_program().clone(), + cairo_runner.relocated_memory.clone(), + relocated_trace.clone(), + 1, + debug_info, + )?; + Ok(()) +} + fn run(args: impl Iterator) -> Result<(), Error> { let args = Args::try_parse_from(args)?; @@ -184,6 +225,11 @@ fn run(args: impl Iterator) -> Result<(), Error> { std::fs::write(file_path, json)?; } + #[cfg(feature = "with_tracer")] + if args.tracer { + start_tracer(&cairo_runner, &vm)?; + } + if let (Some(file_path), Some(ref trace_file), Some(ref memory_file)) = (args.air_private_input, args.trace_file, args.memory_file) { diff --git a/cairo-vm-tracer/Cargo.toml b/cairo-vm-tracer/Cargo.toml new file mode 100644 index 0000000000..0ce1ebdea2 --- /dev/null +++ b/cairo-vm-tracer/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "cairo-vm-tracer" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + +[features] +default = ["std"] +std = [] +alloc = [] +tracer = [] + +[dependencies] +cairo-vm = { workspace = true, features = ["arbitrary", "std"] } +thiserror-no-std = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +axum = "0.6.18" +tokio = {version = "1.28.2", features = ["rt", "macros","rt-multi-thread"]} +serde = { workspace = true } +tower = { version = "0.4.13", features = ["util"] } +tower-http = { version = "0.4.0", features = ["full"] } +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +include_dir = "0.7.3" +mime_guess = "2.0.4" diff --git a/cairo-vm-tracer/src/error/mod.rs b/cairo-vm-tracer/src/error/mod.rs new file mode 100644 index 0000000000..67535674a0 --- /dev/null +++ b/cairo-vm-tracer/src/error/mod.rs @@ -0,0 +1 @@ +pub mod trace_data_errors; diff --git a/cairo-vm-tracer/src/error/trace_data_errors.rs b/cairo-vm-tracer/src/error/trace_data_errors.rs new file mode 100644 index 0000000000..7af3f540cf --- /dev/null +++ b/cairo-vm-tracer/src/error/trace_data_errors.rs @@ -0,0 +1,24 @@ +use cairo_vm::vm::errors::{memory_errors::MemoryError, vm_errors::VirtualMachineError}; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum TraceDataError { + #[error("Instruction is None at pc {0} when encoding")] + InstructionIsNone(String), + #[error(transparent)] + InstructionDecodeError(#[from] VirtualMachineError), + #[error(transparent)] + FailedToGetRelocationTable(#[from] MemoryError), + #[error("Failed to get relocated trace")] + FailedToGetRelocatedTrace, + #[error("Failed to read file {0}")] + FailedToReadFile(String), + #[error("Input file is None {0}")] + InputFileIsNone(String), + #[error("Instruction encoding must be convertible to a u64")] + FailedToConvertInstructionEncoding, + #[error("Offset must be convertible to a usize")] + FailedToConvertOffset, + #[error("Imm address {0} must be convertible to a usize")] + FailedToImmAddress(String), +} diff --git a/cairo-vm-tracer/src/lib.rs b/cairo-vm-tracer/src/lib.rs new file mode 100644 index 0000000000..a17ac87737 --- /dev/null +++ b/cairo-vm-tracer/src/lib.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod tracer; +mod tracer_data; +mod types; diff --git a/cairo-vm-tracer/src/tracer.rs b/cairo-vm-tracer/src/tracer.rs new file mode 100644 index 0000000000..0edf2f9fe1 --- /dev/null +++ b/cairo-vm-tracer/src/tracer.rs @@ -0,0 +1,134 @@ +use std::{collections::HashMap, net::SocketAddr}; + +use axum::{ + body::{self, Empty, Full}, + extract::{Path, State}, + http::{header, HeaderValue, Response, StatusCode}, + response::IntoResponse, + routing::get, + Json, Router, +}; +use cairo_vm::utils::PRIME_STR; +use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry; +use cairo_vm::{serde::deserialize_program::DebugInfo, types::program::Program, Felt252}; +use include_dir::{include_dir, Dir}; +use num_bigint::BigInt; +use num_traits::{One, Signed}; +use serde::Serialize; +use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; +use tracing::Level; + +use crate::{ + error::trace_data_errors::TraceDataError, tracer_data::TracerData, + types::memory_access::MemoryAccess, +}; + +static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static"); + +#[tokio::main] +pub async fn run_tracer( + program: Program, + memory: Vec>, + trace: Vec, + program_base: u64, + debug_info: Option, +) -> Result<(), TraceDataError> { + let tracer_data = TracerData::new(program, memory, trace, program_base, debug_info)?; + + tracing_subscriber::fmt::init(); + let app = Router::new() + .route("/static/data.json", get(get_data)) + .route("/static/eval.json", get(get_eval)) + .route("/static/*path", get(static_path)) + .with_state(tracer_data) + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)), + ); + + let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); + tracing::info!("listening on http://{}/static/index.html", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + Ok(()) +} + +async fn get_data(tracer_data: State) -> Json { + let data_response = DataReponse { + code: tracer_data + .input_files + .iter() + .map(|(k, v)| (k.clone(), v.to_html())) + .collect(), + trace: tracer_data.trace.clone(), + memory: tracer_data + .memory + .iter() + .filter_map(|x| x.as_ref().map(|_| (*x).unwrap())) + .map(|x| { + field_element_repr( + &x.to_bigint(), + &BigInt::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(), + ) + }) + .enumerate() + .map(|(i, v)| (i + 1, v)) + .collect(), + memory_accesses: tracer_data.memory_accesses.clone(), + public_memory: vec![], + }; + + // filter a vector of options to remove none values + + Json(data_response) +} + +async fn get_eval(_tracer_data: State) {} + +async fn static_path(Path(path): Path) -> impl IntoResponse { + let path = path.trim_start_matches('/'); + let mime_type = mime_guess::from_path(path).first_or_text_plain(); + + match STATIC_DIR.get_file(path) { + None => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(body::boxed(Empty::new())) + .unwrap(), + Some(file) => Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + HeaderValue::from_str(mime_type.as_ref()).unwrap(), + ) + .body(body::boxed(Full::from(file.contents()))) + .unwrap(), + } +} + +fn field_element_repr(val: &BigInt, prime: &BigInt) -> String { + // Shift val to the range (-prime / 2, prime / 2). + let shifted_val: BigInt = (val.clone() + prime.clone() / 2) % prime.clone() - prime.clone() / 2; + // If shifted_val is small, use decimal representation. + let two_pow_40: BigInt = BigInt::one() << 40; + if shifted_val.abs() < two_pow_40 { + return shifted_val.to_string(); + } + // Otherwise, use hex representation (allowing a sign if the number is close to prime). + let two_pow_100: BigInt = BigInt::one() << 100; + if shifted_val.abs() < two_pow_100 { + return format!("0x{:x}", shifted_val); + } + format!("0x{:x}", val) +} + +#[derive(Serialize)] +struct DataReponse { + code: HashMap, + trace: Vec, + memory: HashMap, + public_memory: Vec, + memory_accesses: Vec, +} diff --git a/cairo-vm-tracer/src/tracer_data.rs b/cairo-vm-tracer/src/tracer_data.rs new file mode 100644 index 0000000000..7c5268152e --- /dev/null +++ b/cairo-vm-tracer/src/tracer_data.rs @@ -0,0 +1,221 @@ +use std::collections::{BTreeMap, HashMap}; + +use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry; +use cairo_vm::{ + serde::deserialize_program::{DebugInfo, InstructionLocation}, + types::{ + instruction::Op1Addr, + program::Program, + relocatable::{MaybeRelocatable, Relocatable}, + }, + vm::{context::run_context::RunContext, decoding::decoder::decode_instruction}, + Felt252, +}; +use num_bigint::BigUint; +use num_traits::ToPrimitive; + +use crate::{error::trace_data_errors::TraceDataError, types::memory_access::MemoryAccess}; + +#[derive(Clone)] +pub struct InputCodeFile { + content: String, + lines: Vec, + tags: Vec<(usize, isize, String)>, +} + +impl InputCodeFile { + fn new(content: &str) -> Self { + let lines: Vec = content.lines().map(|line| line.to_string()).collect(); + InputCodeFile { + content: content.to_string(), + lines, + tags: Vec::new(), + } + } + + fn mark_text( + &mut self, + line_start: usize, + col_start: usize, + line_end: usize, + col_end: usize, + classes: &[&str], + ) { + let offset_start = self + .lines + .iter() + .take(line_start - 1) + .map(|line| line.len()) + .sum::() + + line_start + + col_start + - 2; + + let offset_end = self + .lines + .iter() + .take(line_end - 1) + .map(|line| line.len()) + .sum::() + + line_end + + col_end + - 2; + + self.tags.push(( + offset_start, + -(offset_end as isize), + format!("", classes.join(" ")), + )); + self.tags + .push((offset_end, -std::isize::MAX, "".to_string())); + } + + pub fn to_html(&self) -> String { + let mut res = self.content.replace(' ', "\0"); + let mut sorted_tags = self.tags.clone(); + sorted_tags.sort_by_key(|&(key, _, _)| key); + for &(pos, _, ref tag_content) in sorted_tags.iter().rev() { + res.insert_str(pos, tag_content); + } + res.replace('\0', " ").replace('\n', "
\n") + } +} + +// TODO: add support for taking air_public_input as an argument +#[derive(Clone)] +pub struct TracerData { + pub(crate) _program: Program, + pub(crate) memory: Vec>, + pub(crate) trace: Vec, + pub(crate) _program_base: u64, // TODO: adjust size based on maximum instructions possible + pub(crate) _debug_info: Option, + pub(crate) memory_accesses: Vec, + pub(crate) input_files: HashMap, +} + +impl TracerData { + pub fn new( + program: Program, + memory: Vec>, + trace: Vec, + program_base: u64, + debug_info: Option, + ) -> Result { + let mut input_files = HashMap::::new(); + + if let Some(debug_info) = debug_info.clone() { + // loop over debug_info + + //sort hashmap by key + let instruction_locations: BTreeMap = + debug_info.get_instruction_locations().into_iter().collect(); + for (pc_offset, instruction_location) in instruction_locations.iter() { + let loc = &instruction_location.inst; + let filename = &loc.input_file.filename; + let content = loc + .input_file + .get_content() + .map_err(|_| TraceDataError::FailedToReadFile(filename.clone()))?; + if !input_files.contains_key(filename) { + input_files.insert(filename.clone(), InputCodeFile::new(content.as_str())); + } + let input_file = input_files.get_mut(filename); + if input_file.is_none() { + return Err(TraceDataError::InputFileIsNone(filename.clone())); + } + let input_file = input_file.unwrap(); + + input_file.mark_text( + loc.start_line as usize, + loc.start_col as usize, + loc.end_line as usize, + loc.end_col as usize, + &[format!("inst{}", pc_offset).as_str(), "instruction"], + ); + } + } + + let mut memory_accesses: Vec = vec![]; + //loop of trace + for entry in trace.iter() { + let run_context = RunContext::new(Relocatable::from((0, entry.pc)), entry.ap, entry.fp); + + let (instruction_encoding, _) = + get_instruction_encoding(entry.pc, &memory, program.prime())?; + + let instruction_encoding = instruction_encoding.to_u64(); + if instruction_encoding.is_none() { + return Err(TraceDataError::FailedToConvertInstructionEncoding); + } + let instruction_encoding = instruction_encoding.unwrap(); + let instruction = decode_instruction(instruction_encoding)?; + + // get dst_addr + let dst_addr = run_context.compute_dst_addr(&instruction)?.offset; + + // get op0_addr + let op0_addr = run_context.compute_op0_addr(&instruction)?.offset; + + // get op1_addr + let mut op0: Result, TraceDataError> = Ok(None); + if instruction.op1_addr == Op1Addr::Op0 { + let op0_memory = &memory[op0_addr]; + op0 = match op0_memory { + None => Ok(None), + Some(felt) => { + let offset = felt.clone().to_usize(); + if offset.is_none() { + return Err(TraceDataError::FailedToConvertOffset); + } + let offset = offset.unwrap(); + Ok(Some(MaybeRelocatable::RelocatableValue(Relocatable { + segment_index: 1_isize, + offset, + }))) + } + }; + } + let op0 = op0?; + let op1_addr = run_context + .compute_op1_addr(&instruction, op0.as_ref())? + .offset; + + // add to memory access + memory_accesses.push(MemoryAccess { + dst: dst_addr, + op0: op0_addr, + op1: op1_addr, + }); + } + + Ok(TracerData { + _program: program, + memory, + trace, + _program_base: program_base, + _debug_info: debug_info, + memory_accesses, + input_files, + }) + } +} + +// Returns the encoded instruction (the value at pc) and the immediate value (the value at +// pc + 1, if it exists in the memory). +pub fn get_instruction_encoding( + pc: usize, + memory: &[Option], + prime: &str, +) -> Result<(Felt252, Option), TraceDataError> { + if memory[pc].is_none() { + return Err(TraceDataError::InstructionIsNone(pc.to_string())); + } + let instruction_encoding = memory[pc].unwrap(); + let prime = BigUint::parse_bytes(prime[2..].as_bytes(), 16).unwrap(); + + let imm_addr = BigUint::from(pc + 1) % prime; + let imm_addr = usize::try_from(imm_addr.clone()) + .map_err(|_| TraceDataError::FailedToImmAddress(imm_addr.to_string()))?; + let optional_imm = memory[imm_addr]; + Ok((instruction_encoding, optional_imm)) +} diff --git a/cairo-vm-tracer/src/types/memory_access.rs b/cairo-vm-tracer/src/types/memory_access.rs new file mode 100644 index 0000000000..21507b313f --- /dev/null +++ b/cairo-vm-tracer/src/types/memory_access.rs @@ -0,0 +1,9 @@ +use serde::Serialize; + +// TODO: check if the sizes are corect +#[derive(Serialize, Clone)] +pub struct MemoryAccess { + pub(crate) dst: usize, + pub(crate) op0: usize, + pub(crate) op1: usize, +} diff --git a/cairo-vm-tracer/src/types/mod.rs b/cairo-vm-tracer/src/types/mod.rs new file mode 100644 index 0000000000..8b7ec4be21 --- /dev/null +++ b/cairo-vm-tracer/src/types/mod.rs @@ -0,0 +1 @@ +pub mod memory_access; diff --git a/cairo-vm-tracer/static/index.html b/cairo-vm-tracer/static/index.html new file mode 100644 index 0000000000..9606f87125 --- /dev/null +++ b/cairo-vm-tracer/static/index.html @@ -0,0 +1,56 @@ + + + + + + Cairo Tracer + + + + + + + +
+
+
+
+
+
+ Follow: + +
+
+
+
+ (s) Step
+ (S) Previous step
+ (n) Step over
+ (N) Previous step over
+ (o) Step out
+ (b) Run until next breakpoint
+ (B) Run until previous breakpoint
+
+ pc =
+ ap =
+ fp =
+
+ dst_addr =
+ op0_addr =
+ op1_addr =
+
+ Stack trace: +
+
+ + +
+ + + diff --git a/cairo-vm-tracer/static/tracer.css b/cairo-vm-tracer/static/tracer.css new file mode 100644 index 0000000000..c4ec672eed --- /dev/null +++ b/cairo-vm-tracer/static/tracer.css @@ -0,0 +1,81 @@ +html, body { + margin: 0px; + padding: 0px; + height: 100%; +} + +#code_div, #info_td, #memory_div_parent { + font-family: monospace; +} + +.current_instruction, .current_pc_mem { + background: yellow !important; +} + +.highlight_instruction { + background: lightblue; +} + +.breakpoint { + background: #99ccff; +} + +#code_div_parent, #memory_div_parent { + flex-flow: column; + height: 100%; + vertical-align: top; +} + +#code_div_parent { + background: rgb(238, 255, 255); + overflow-y: scroll; +} + +#memory_div_parent { + background: rgb(255, 255, 250); + display: flex; +} + +#memory_div { + overflow-y: scroll; + flex-flow: row; +} + +.mem_info { + display: inline-block; + width: 40px; + text-align: right; + font-size: 70%; +} + +#main_table { + height: 100%; +} + +#code_div { + margin: 10px; +} + +#memory_div>table { + height: 100%; + margin: 10px; +} + +.table_with_border { + border-collapse: collapse; +} + +.table_with_border>tr>td, .table_with_border>tr>th { + border: 1px black solid; +} + +.filename { + font-weight: bold; + text-decoration: underline; + margin-top: 10px; + margin-bottom: 5px; +} + +.public_memory { + background-color: #f0fff8; +} diff --git a/cairo-vm-tracer/static/tracer.js b/cairo-vm-tracer/static/tracer.js new file mode 100644 index 0000000000..61b97a60b5 --- /dev/null +++ b/cairo-vm-tracer/static/tracer.js @@ -0,0 +1,387 @@ +/* + The number of the current step being viewed in the tracer. +*/ +var current_step; + +var trace; +var memory; +var memory_accesses; + +/* + A list of objects {watch_expr: ..., watch_result: ...} where watch_expr is the element + and watch_result is the result element. +*/ +var watch_exprs = []; + +/* + The maximum number of entries shown in the stack trace. +*/ +const MAX_STACK_TRACE = 100; + +function load_json() { + $.getJSON("data.json", function (data) { + trace = data.trace; + memory = data.memory; + memory_accesses = data.memory_accesses; + for (const filename in data.code) { + $("#code_div") + .append($("
").addClass("filename").text(filename)) + .append($("
").html(data.code[filename])); + } + $("#slider_div").append(create_slider()); + $("#memory_div").append(create_memory_table()); + $("#watch_table").append(create_watch_row()); + mark_public_memory(data.public_memory); + goto_step(0); + + $(".instruction").dblclick(toggle_breakpoint); + $(".mem_row").dblclick(toggle_breakpoint); + }); +} + +/* + Adds a slider that tracks the progress of the program. +*/ +function create_slider() { + const slider = $("").attr({ + id: "slider", + type: "range", + min: 0, + max: trace.length - 1, + value: 0, + }); + + slider[0].oninput = function () { + goto_step(parseInt($("#slider").val())); + }; + + return slider; +} + +function create_memory_table() { + const table = $("").addClass("table_with_border"); + for (const addr in memory) { + table.append( + $("") + .attr({ id: "mem_row" + addr }) + .addClass("mem_row") + .append( + $("") + .append($("").append(fp_cell).append(pc_cell); + + pc_cell.mouseenter(function () { + $(".inst" + pc).addClass("highlight_instruction"); + }); + pc_cell.mouseleave(function () { + $(".inst" + pc).removeClass("highlight_instruction"); + }); + + return row; +} + +function create_watch_row() { + const watch_expr = $("").attr({ type: "text" }); + const watch_result = $(""); + + watch_exprs.push({ watch_expr: watch_expr, watch_result: watch_result }); + + watch_expr.keyup(function (event) { + if (event.key == "Enter") { + update_watch(); + } + }); + + const n_watches = watch_exprs.length; + watch_expr.change(function (event) { + // Add new watch if needed. + if (watch_expr.val() != "" && watch_exprs.length == n_watches) { + $("#watch_table").append(create_watch_row()); + } + + update_watch(); + }); + + // Make sure navigation keys (s, n, ...) will not work when watch_expr is focused. + watch_expr.keypress(function (event) { + event.stopPropagation(); + }); + + return $("") + .append($("
").append( + $("") + .attr({ id: "mem_info" + addr }) + .addClass("mem_info") + ) + ) + .append($("").text(addr)) + .append($("").text(memory[addr])) + ); + } + return table; +} + +function mark_public_memory(public_memory) { + for (const addr of public_memory) { + $("#mem_row" + addr).addClass("public_memory"); + } +} + +function update_current_instruction_view() { + const entry = trace[current_step]; + const pc = entry.pc; + const ap = entry.ap; + const fp = entry.fp; + + $(".instruction").removeClass("current_instruction"); + $(".inst" + pc).addClass("current_instruction"); + $("#pc").text(pc); + $("#ap").text(ap); + $("#fp").text(fp); + + const mem_accesses = memory_accesses[current_step]; + $("#dst_addr").text(mem_accesses.dst); + $("#op0_addr").text(mem_accesses.op0); + $("#op1_addr").text(mem_accesses.op1); + + $(".mem_row").removeClass("current_pc_mem"); + $("#mem_row" + pc).addClass("current_pc_mem"); + + $(".mem_info").text(""); + $("#mem_info" + ap).append("ap "); + $("#mem_info" + fp).append("fp "); + + update_stack_trace_view(); + + if ($("#slider").val() != current_step) { + $("#slider").val(current_step); + } + + scrollIntoViewIfNeeded($(".inst" + pc)[0]); + + if ($("#memory_follow").val() == "pc") { + scrollIntoViewIfNeeded($("#mem_row" + pc)[0]); + } else if ($("#memory_follow").val() == "ap") { + scrollIntoViewIfNeeded($("#mem_row" + ap)[0]); + } else if ($("#memory_follow").val() == "fp") { + scrollIntoViewIfNeeded($("#mem_row" + fp)[0]); + } + + update_watch(); +} + +function scrollIntoViewIfNeeded(target) { + if (target === undefined) { + return; + } + + const rect = target.getBoundingClientRect(); + const scrollParent = getScrollParent(target); + const scrollParentRect = scrollParent.getBoundingClientRect(); + if (rect.bottom > scrollParentRect.bottom) { + // Scroll down. + const scrollMargin = 64; + const targetBottom = scrollParent.scrollTop + rect.bottom; + scrollParent.scrollTo({ + top: targetBottom - scrollParent.clientHeight + scrollMargin, + behavior: "smooth", + }); + } else if (rect.top < scrollParentRect.top) { + // Scroll up. + const scrollMargin = 32; + const targetTop = scrollParent.scrollTop + rect.top; + scrollParent.scrollTo({ + top: targetTop - scrollMargin, + behavior: "smooth", + }); + } +} + +function getScrollParent(node) { + node = node.parentNode; + while (getComputedStyle(node)["overflow-y"] != "scroll") { + node = node.parentNode; + } + return node; +} + +function update_stack_trace_view() { + const entry = trace[current_step]; + const initial_fp = trace[0].fp; + + $(".mem_row").css("border-top", ""); + + const stack_trace = $("#stack_trace"); + stack_trace + .empty() + .append( + $("
").append("fp")) + .append($("").append("return pc")) + ); + + var fp = entry.fp; + for (var i = 0; i < MAX_STACK_TRACE && fp != initial_fp; i++) { + const pc = memory[fp - 1]; + stack_trace.append(create_stack_trace_row(fp, pc)); + $("#mem_row" + fp).css("border-top", "2px black solid"); + fp = memory[fp - 2]; + } +} + +function create_stack_trace_row(fp, pc) { + const fp_cell = $("").append(fp); + const pc_cell = $("").append(pc); + const row = $("
").append(watch_expr)) + .append($("").append(watch_result)); +} + +var update_watch_ajax = { abort: function () {} }; + +function update_watch() { + // REMOVE RETURN TO EVALUATION EXPRESSION + return; + var query_str = "eval.json?step=" + encodeURIComponent(current_step); + for (const entry of watch_exprs) { + const expr_txt = entry.watch_expr.val(); + query_str += + "&expr=" + encodeURIComponent(expr_txt == "" ? "null" : expr_txt); + } + // Abort previous AJAX request if exists. + update_watch_ajax.abort(); + update_watch_ajax = $.getJSON(query_str, function (data) { + for (var idx = 0; idx < data.length; ++idx) { + watch_exprs[idx].watch_result.text(data[idx]); + } + }); +} + +/* + Clears the text selection in the window. + This function was copied from + https://stackoverflow.com/questions/880512/prevent-text-selection-after-double-click. +*/ +function clearSelection() { + if (document.selection && document.selection.empty) { + document.selection.empty(); + } else if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + } +} + +function toggle_breakpoint(event) { + $(this).toggleClass("breakpoint"); + clearSelection(); + event.stopPropagation(); +} + +function has_breakpoint(step) { + return ( + $("#mem_row" + trace[step].pc).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].dst).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].op0).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].op1).hasClass("breakpoint") + ); +} + +function goto_step(i) { + // Update global variable. + current_step = i; + update_current_instruction_view(); +} + +function step() { + if (current_step < trace.length - 1) { + goto_step(current_step + 1); + } +} + +function previous_step() { + if (current_step > 0) { + goto_step(current_step - 1); + } +} + +function step_over() { + const current_fp = trace[current_step].fp; + for (var i = current_step + 1; i < trace.length; i++) { + if (trace[i].fp == current_fp || has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +function previous_step_over() { + const current_fp = trace[current_step].fp; + for (var i = current_step - 1; i >= 0; i--) { + if (trace[i].fp == current_fp) { + goto_step(i); + return; + } + } +} + +function step_out() { + const current_fp = trace[current_step].fp; + const previous_fp = memory[current_fp - 2]; + for (var i = current_step + 1; i < trace.length; i++) { + if (trace[i].fp == previous_fp) { + goto_step(i); + return; + } + } +} + +function next_breakpoint() { + for (var i = current_step + 1; i < trace.length; i++) { + if (has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +function previous_breakpoint() { + const current_fp = trace[current_step].fp; + for (var i = current_step - 1; i >= 0; i--) { + if (has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +$(document).ready(function () { + load_json(); +}); + +$(document).keypress(function (event) { + if (event.key == "s") { + step(); + event.stopPropagation(); + } + if (event.key == "S") { + previous_step(); + event.stopPropagation(); + } + if (event.key == "n") { + step_over(); + event.stopPropagation(); + } + if (event.key == "N") { + previous_step_over(); + event.stopPropagation(); + } + if (event.key == "o") { + step_out(); + event.stopPropagation(); + } + if (event.key == "b") { + next_breakpoint(); + event.stopPropagation(); + } + if (event.key == "B") { + previous_breakpoint(); + event.stopPropagation(); + } +}); diff --git a/docs/README.md b/docs/README.md index dd1d40c420..f25a1c762a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,3 +4,4 @@ * [Custom Hint Processor](./hint_processor/) * [How to run a cairo program with custom hints](./hint_processor/builtin_hint_processor) * [References parsing](./references_parsing/) +* [Tracer](./tracer/) diff --git a/docs/tracer/README.md b/docs/tracer/README.md new file mode 100644 index 0000000000..5e5e3b6404 --- /dev/null +++ b/docs/tracer/README.md @@ -0,0 +1,39 @@ +# Cairo Tracer + +Cairo-vm offers a tracer which gives you a visualization of how your memory and registers change line after line as the VM executes the code. + +## Setting up the tracer + +To use the tracer, you need to build your the VM using the `with_tracer` feature flag. + +```bash +cargo build --release --features with_tracer +``` + +## Running the tracer + +Once the build in the above step is complete, you can use the `cairo-vm-cli` as you do normally with an additional argument `--tracer true`. This tells the VM to start a server where you can visualize the exection of your cairo program. + +```bash +target/release/cairo-vm-cli --layout --memory_file --trace_file --tracer true +``` + +### NOTE +> The `--memory_file` and `--trace_file` arguments are compulsory at the moment as the current code relocates the memory and trace only when these arguments are supplied. However, edits can be made in future versions to relocate if only the `--tracer` option is specified. + + +## Using the tracer + +![tracer](tracer.png) + +You can go to [http://127.0.0.1:8100/static/index.html](http://127.0.0.1:8100/static/index.html) to see the tracer. + +Use the following commands to visualize the code execution + +- `s` to go to the next step +- `Shift + s` to go to the previous step +- `n` to step over +- `N` to previous step over +- `o` to step out +- `b` to run until you reach the next breakpoint +- `B` to run until you reach the previous breakpoint diff --git a/docs/tracer/tracer.png b/docs/tracer/tracer.png new file mode 100644 index 0000000000000000000000000000000000000000..9cb29af0d9ecc5cf51dd2dd94ece1e905cb289b8 GIT binary patch literal 739142 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

7`%<>!=D%dE% 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%0AZ& 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

=5Ad&w9G?+KX2L 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

$c-L?~X7M;^DKztx#9K zb^8=hM*<7#dOA(U$8=oO>;5z!zst56{jG8GSlLeTW%*oM{Y9OTerJi6WCr?v?dO?g zp57r1nju)b?=f+E-BCa#9G8BpaO~Ml>PT;8hii}C-B2aYsCaVjcXQoeMd92DD*Gwf zyJU&Aa)-Kg32>vz#X=c3y)GpJmCUNX=cE%x=|b5Pzt&0S@fe+Ax161F%#GlSP|JFu z2zQKLUz|+FE&XV%DLH0YH@R%TuH_K&b3liYGY%B*iiB@w!;>!eD>wbFh0)G$gzY&A zxK|FxjP1W65{e%CkyHI|k?5~-(XUdFJlD)$ss2~;Jhin>yTN{%6;L2#?_ets045ts z6L~^~K_=+_p?VBNdZPhkvFEP0wvf@QV*|~F6GzSV5YZWK(CTY4nYWrs$X);eQ)8Pv zXj&?_Z8I9o*hBZhEPsXEu9Ns^zYp{*Fq;ABV%kxXS|iPaj2H-ez%-hi%AsWUT3y@j zb#A9sRz$IF09%pw?QVFbRk=W^f7;l#MSjA*AvMAOh{3g&-RN5}?<7t3Y)yr%&8H#YWT|+WN^|^O*uFAuz7FhKy$0zplLE&$*$-VcOS*$#A|Y$ zo$^PVxkKjq>FNl2eG!z?yRi1FoLA8Yq8ex%qmVxOdCvZM>eijwVOkPsc}b}ow7z7b zy297}*^d3ycg$ornauTjMH9XSG}4OmjG)B!0V2a;yq3*)jAt)r$y}w0h1$+@WvGor zxL1at*>)`>bwo(>$%Tw$ONjv=m%W$hzN-RXwfDZ8=hwAC&MvdzXuZtfw6zUG;ci>6 zI|owhsuM_*uUZq|&^9=dLERs{Tqt%+#@ysv>X0+DDW-L_0F&&6_z0_I7-m7! z>L&G4?!_@_zykLWsHfYsRO;`68sj7hi*b?aN<10tYs+gPfwNEb*K!tP>oYB_CJh=c zk(9r=BOd?vi<1D6Q%|9_(Z6x&rnz~`W`8V;P9tvE=mrJczF+U7*92*_1`5o z0W-}-(oPKY0q=Cu6!Yb#`h8-bYNv_zf;v-6V!es?>^a zoG{BXBQ-ixoSOarp?}+yzQXvr)?{jgH|@IAViQhVEP6@0^OQ7@PR2Z6ehOKYl)k3s zU~(=PF&I4*9~+UTOl7A~@cWuuL>@o|RcmPBNy5uSqQD+w;ur~SO^~UuV?M*xaUcwI zB|nP?qw?Ftr>9D?1TB)XwV_|geM~b84_3~~tsK-`v}%jpLI~h!qCUWVOflAC1x?k6 zsvVR)tY6kB(tOo0ic;Vh_@Djy*=#L{j+kStgJ?cD7N5l}riFL!7)sPzN8heM+c$39 ziYSV!dyI>}Q|=?TFV-^0K}M-VV{n#}GOxcNeB zAMY?+CJ};S?|L7NZkSebedkEjf=_j%-DsJVNHs!aw}PrQ9RFNEcE!j)Gj zGj2S?`NN^eFL>>FY`oV{$A_D<(;k0jQiDvyc01^_xJr-D7+h|wZ$4Y{mx#Ym79iek zPqnUF5)EhPX=T>fvL!PApFYxG(@z*cf5msEMKzym;bX~Mb22CnVxQG1!rR$=L_cRa z9xsKg(;9CZKD@=ARcpf3r#TK^_{Ez9g&=Km`qBEwxODdjwAxRiB$&puVALvg0*Ut@UB=9u{>nVcrF|JZuxasL)+?+RcpYaJbCE z`oi4bXD&Cd)Vg0-&}ui#p4wZ8=I7ohLr8VoO*JX&vgomtEghYyi1*)v14I=N9E43C zmi`qS&}!;MXhD8+}xfIQ3lOn~i3G`WMZTZCRd_gFio#NNGMJQ0!b z@pDO0RtczAdHMkYp$>EO9qCy`xndHOSF^>zFdXhYZ1V`nlywa_)IV%xsaIPw2WkDI{+_4!0lpD(L# zZgf-xHPFC*>Mmarm<~`JsJ=TlNgKP8&&mUBRZ6=zhffNK4mzm9Q^2G_EvC+cSpIm^ z2QVg({zrhI)ao8gsmy~x25^&yMpDX(^=mtM{n1iUT@pgRJBOx4U-eTdvr`7h1BPZX zWm(f7xCc%+W1H&i*s2f0_U1WJG^Hr)q0$ler#yuQeoHXp-Wrez9nlw#{nb#{*x0z= zspSpl=;%mQ*GPZB=mqDfZr@+1@pJg>%FsAc;_8NYe8XXPcPBLiTB`8^P`ExShitaG zAAdW5tz^{Mu19FB`2v9vimZ(@Y#jj?VNPpJs^bnXth&YslSqme49@m<`ewJ+Q6?2> zf|Vw(NzKzA_?^$TULJE{K`1C+24({AvNFTr-lNeN-dd{&Cof^rOlu9Ohs}OvjE2pP z{LWH?t>`nw92x13T`yp+az_mlb4{=$w-ROxt`G9Ab7pZOOJYxXz1wydB7Yx@8;a%P6shsb@f*fVZ~kHb4@;MaD--)k zoZHt?xi6@!G;PDFGG>q0d_SyYaIah?Zk(KC63vuE9Bs;A8xJ8yyroO=Jv)`&jvLK@ z3~Q}wua2%Itu+#RgnfeXIZQ&m89g*QZ<|9~UEYNeCybk%vE1CXD=eTtCHr}bH`>SG z#oKKcY(3#f^U-n_8eAN;;aG~`bxEV`eMJGcqvBz$Fz-dam@w_vw7KteI3g5c$bzG{ zJwIKberN@xFi@$nd3(M!mgH-b8b-|hxr+a;t0y#Rya|GASaHN73nq)IcOLp<_r?!< z=dW7VF8_!`M>JmDk(-PVD`kn1%-2}*BCYbF68<)@WTi%ULXIJW#I-wjnx(ZaSg3<{ zC_fQ+={)d~ZzKvslukYi3S`v2hmVFsaUamqACjzUEatX=#EO~Qy|fI(F*ka{Q54n- ziY9qd2`pw`goGYJ0v)Y3!vHxg@K2v)VD44Q#A3v&_s;>C3|To(PFT?bfCiMR&6L@t zof>ihgRIb2(F=_8%L6@$NsHAtDZWCQq2RHZSnmxt(sWAB1-!OGNP`zZOO691mr>+V ziHu^7)Yvcw0VBw_?=Ll~ZX$;tRDnW3^&aT(`G7b-#nsANtHPSD!h+qGCMbwhdQ$NY zP%(lCc1SzS1-pYz*Dv!T-+UYgZ5#H1PP?;@vXBA-omZ6B;2k?^|)4yYFEt1ugwH?GzEDP-P{y6{pvH$l?80DkY zrmP^I+-5R;nO;az1)ivFQr{lllVwQzD&o7_mE+7%5 z-5t&Hn+|4-Y!XN4tV{EkCd@{m6ST-M9((=~yQDqSwCpt^4O_XXz3?5QP8Oe0qV5W3BSZd_FhQVmxOQix;ns-2A zlD*G*vF)=%V3==i*9K z%TJFm2O_9y^DVM{{_$~OTYRzqd}1i_wdl~c+Uo80L~j2AZ?5mp^7R$AuY2g>&BpKZ7*`UA|GJo#$U3f}qiKpwPRB*B2A9VH!LEeH zyx3+Lm^W}~h?q{eFW73DcE$fn7%WqD`iS7751{8>!f!9KM?}85l~)T3N3?l6<4#b? zB+rsBB*Mefmk0tN_Z`rrGP?s3Ukbo-P1gNr0}mPvSflY^Kt+vVz=^x2-;)J}o=++( zqAiIppYXK+YWg;1{_02y?*w4|PuB52HupQFcQ%ndduz}$Y2P~(pYp>8{=Dg@5C1$} zKshA)_v%0GTMRhztG@EhoK2TJ8plLdj*-Ogl_uzs+TwaGGFNGm3rOM$0C*PK630pDM9OL$V$qu{ zOz)J&VM!RnpfUaV?R}^&Ad3S1KtVu%h!rNd=+XulH5!|69J*Lx0jegK%P`pGp)7#N za`c&u9%;5F*r4U^3a25v!_I<$;WUy9w+zB%AUxljQ?x7vYBp8I!-U(m_uvRA{IVL- zuJ%SeiU*7`Ss;j}6a^kkn%x~HE^38r#By<0(%z%h&Z!e(-De@jq!PhH zJERNlHmcQP&34<%$5#s|D=jmU^KM?bEzRRU{!8s9U37&T+ z3yZ+lJ+7DWd$gTyu(M2IAJ@q*l5O!?=wt%vK>UO5J$)+B4-^7@KI|H39gs?=??_Js zNgf-Fl8Osm{jp5evI`9z*H7S||GdAuu{2|(fEz8n!YQ|s-X`L+8J?Pgj$6ekG8&qds}p#wrjMppm19UsqpT)_R78jp|9 z`RoIu#X3BZ>+q|0fx1=BABJ9Mk-WzI`)Y_pfYY3TNT&7weg6MrnLX*SJZ+Y7O21`D z|7DWFxAX>K3Ip%nax)z&(PrVcCT3CP%yklHsp$!q{!j_v#wzYk@1qPPN^Z@RqF-N~ zamFxA;P6TzT@9;d)e( za~y9Iam~4(*6ZLXd@*@^fj^4iG6k*2zQ{)ZLbIk}6Lsmo1jV)=zn{m1dVW&GWPn zR}SNu5CYbGqd|^_?je5!lr3?9Y+~1V_RrnD>$nEzQW502q!Oocq=^zimAT*b%~Nj^ zzjw3zss&0x)1^ArUmgL6B-{DC#SJ%!-q7UPW<<&(`M;f5aw)L#0#e!>_^5xsegCgd z1m2Il2H)m%mZxIluWtjkNb36J?u|4%W%A+5${$Tl`qN^Z5kZ#=B`2k2^YfzzSmC4k z`uZdW^wlPHzCJjwv@lPG(RUiZZa>esuet)`ytPE5UDJXT4UDeS(X}l?Shmk@ZZi+iRC4YF-0io(}(4F}A zb?6C9MOtT50iqgSKy+J%MlmP---j$%>sgHD2XHQ*sZl?FBKY{R z3BQYbmAU#)E2LAPmzt;3>P{J4ZMBpLWN-CZboxDislUYhItmDyIRNfTNFo%1|F+BI z5YB8eFJjD9{Ea>-_cx09$Nzn35I@3+yyx;*+j@J_>XuY6U1G#qcnKgCbaC`QTEF3D z7}6-rYLemn?JNEBk3-ntKgvg@+JElBX-$ zu~2Ir*xN1sLvjkBt~)0)M0r7=fcHKS!%M#>WY>NndHR4~x(jPk5AZTL1}bMNVC-<PnvFR+Fqu}Ge3Y0YOB2hv$?Zuv5u{%j7*d`UZ*%>OwUvm&}HBU)^3m7cqzc)6F z%88%<@o$)(j5uF_!w&3^xSZ;;@@WS(F*%0jlX&s=Hv!Ind%pA%gaVPF2%yFNH3E80 zS8A%gwo^)N_ipbg)hM-}hHJmeKytlOt!-A>5NS;#zL#CD%miq<-<}LwkEXH1EiV+7 z|Kn^0t0UQI`aNn5Z2Gc|MY0IcDOycJ&9ECeZ(pktebPN(pKLJx^3enZ=jKt%uh1{+ zecBRDH}9K;c*-2n@9fSHr}7lzzHAJd=de}$^Qa}i2hWp9go8!npOx#l{V1e8&v-*s3;ADd|zHvACAwFlOUD6C#Qf8P7TJHuI0&BIjoC0pIntc*WaJERLV9) zVh;nS$a5X3NT>cAnBNfr30qs&b;?gg@+HKU3sn-E+NgE1Nc>m{)Io+Wh)vG-kv$>! zVjz9>1N_RDcG#Z?fT0j(_b|XGPT`2$@Imq+jWw~~UXlI7fVDjVR}90WFA;md){3q+ zy_R-8`TaJ9%QkYk#Z8=m)ma|YTd4KB0x262X&R*nJyF1-rA7X6*d!YT-zcHLa>+eY zklnuxh6w*{#ayW{GCQvO4SEzxM^d&NSNGR@Is^XX2@9FJZaf}-Od?@Rj@^9+{pTs4 z5yY=3m;t6JQNZ@qKE^_yabNt{w$EZY1X-C*L+m1|dm%|OnnpC>Vn3@B0j;lU`xH;E z_}O`i6#@?;saLT(QNFR?37h4zYk{TVvFOn!cCA9iBn~#09VH*&`4f=~i*UVaW0DGn z4u51y70k^7;q&jl2vJviuwGdW%Cw4R$60&*g{q=O50D9ypCL3+8_=<;>r=~l8mg`k z6aa z_PD6sim*@q$FyaRu_RUpLWdWmJL>ixa}T85A=raCFlyqjh}4<2bHQ>Kv8tsf>6ynn*;OMtMrD_)z?~cW~`pKPzM$ zquT$lk(hwBuiNf*k8FA@$$Y@)hpnM&I47^{mBi}bn+Gy;UpBJ+0l2Efa-mOU_4dRs zIIsJ%+v?Wst*wGAlqj6^?~gut@*iKpLR0Pi5YnIUS-T7iPN=OOF8NX_XPS~9^Obx3RK{2y@Nnn*@?A0WWZ(x+E6VB^tVt#~|) zb$Z^|1My{;YzV*lz>8=Kb(YG7RQLPagckQZTJiRUEU|CdZeHsiHyle~DxT#bA!e#3Y!r6wuU zsYzf#6}>E$Sy~{60t1(-!tA3{Pw36gTOE=P5S}SK;pCKzV8QABMzoNfz^CdoLM;wr zEjHw*%dH-=x#4N)EABU;O%;Y5@v^S6Cr!WB0c-PRNh#nM!;(i*1>Q?2QshSGEAAR| z^9}L72)Q^%eY@Ip|42XR`1J8=lR&}|9u)d}iPv#!nZ)B8<3Z?e8U+QB@OQ=tW}}sJ z%QurndK|_rE;q4Q!G7khzWz2X9+wAfR$Yj59-de9-;TAqn6Ra8^$w{Wt(GzaF3CQ$ zyRA{At|k6R&(kkQ7F?MKX6PQ?cRd9c?)Z6G=0h^uSh3V5^tB)u+2~14=ozrRbGg4i zsZ0`#o5+?H4~lg9oI2IB;}=&&`$H1`BUfpoT%~dV8k_6w)sZ*cKhlz37*yc)41A5Q z5-@@t^lXs0IsxB0(2=q;C z_X(@_XCqvDL&}j36O9J*ZTCM_j3~dPuBA|_%D2U%ZJM%veT}vyYa*M%QZ_(mXPn4o zkaN~ikjCBPNPT^F@Iyj?s>>Us;Xi+9b+6Jt*00|J#jtX5bV71Idphy!%=bS`9NwhP zPDC-TG>W6c?Y1m6j^hdn((ovq#91F@DQpbV$Ooi>Ga_iT$F(z;pGDl+>3GQYN)lJ< zK7u)8@y)7-NYys0MSN$knXC_e?BQ|i!`O5v(T`A=r2}s2Y-8{)8K}%iPye~86$@S@ znin{k3x3^VMkQmD`-T%E*)-UB92`eej$v_9?$|3+pq5z|$uK%_@CX@zjT00p%{Ohr z7VAv`i8ZksKXKrz7z9F1%~J^7fvK-Dl4`E<`QX2`!9d+!k!7i;|4Jt((Aq&lfr9w=dn{S%kLUgLtK2C# zBl9Ndh4V%$d2&R+XiOn-9nL*nZ7S4pnFU3L|Hi=jk7>5wk|pv+IwBXsUit;)uQwrhn=7F<&WB9!fZSJ z6(3Uh*(l5NPWsn-%|8A6PB2S;%9PejUizY+t>ap9s!;KK{{M0I7Eo2LTmPscU?2h_ zAfO;fr*wA-vIyx`knWB}h=@o_2vUn)lytW)LdivUcQ3l*KJ2~!=bX>)+=vJ zi2Hq?XU_Sn>6~rzwLQK>u36?zz!j}zw&%ulc|to8WA3{)4lF*@G)@K^KNh2!iq=&79qVjsmm7UHoDDUcZI4^(JjhU`ov`6 zx0KFjwSG@pN)AY~o&0xAWSkBc=o!8VB``H5n@RzLG1zT-ZK~xI_t4)jvKwSbw;s$< z4dl)@C~TWw_dZ;LK;pd@0CyG5kec9K(tCWPcqKo5iX+iAfL^UA<#e>;5W4(ZEVpO3x)x5q?sKzuiV74OtTgM7xktz>&P1hCZk1zIYaDPG@4z%6QfDio#-jEb zUaA=`v>TJJ8DYU=_L3M_kA=ovdxjvSIbK=a`>`-P?f!4r5$hU-eSjV17wZ21cfw~^!*4v(-8 zh*R1u>05Pkw4F~cj;77$Po14+H?&B&ZJtm=V7kml2kRP!6xK=YU%6mkwW_9by>2rw z6SfQb>_)2xoe)yBhv8xANBfglJpXw|@V=+$rK^plMM(g(;Zw`2SUAXXG1~=9@z6*< zSRX_+#M<~fX2@nzT1qa)PASiw$x=S^ZwiqM*#>@C}+Kt$!U_>LveR~&KQ0O2|p9zBz>*Twqvks%SumL z^>+l;r=?(jW%z^3^Ti~YW4;l%tWk*?ug}G$M7izO{7#gNs`lr(uPqST0^L-R=8 zWw}vt{{hpZ&pY_p_IWW38s3-A7US9k`{4=vkR`^0rtwj-*}APTwsS9|aU{)GGBeV<_7}&i{g5zG zyd4MMPhAQ9zXS2P5JFJ|QmujMzp}G&{TY6JB<7GJ?TXHBLe>ORezz7Z#x2xua7B*I z5q8i|9bRW|MwuYF=|nONM6P}NY9D0%+Ec$3dCpSKVchZ5t=m+DwMCJXgOw&;wa#DN znOJmL31!|PlRC__i~6eTjoywemGb(Vfmh)*fNyj81>}( z2iGz?vfGaM-nFNnLyPTFzPJrJ2u#TQ$kV_Scxpz1-Gm4IxF@&~mamhCrE{Jv?7EUx z`yzw@{wI9-9s}$U3*#{;PtO^_U{kg zFyG9=TDDZn7S%aF&we*rQf4?WZMhk_s6Z|v=I^ldWmf3dB?SugHi6`>gnzQIhxndp zS4Pr!Mc-43-*x1;&;0FSvXR+_0apag>cnqmx zxx|5+PbEq2>k5#e#B-1I*E%u6k@Z>~#K+gX?OySATSD{>l`{Q)g4Nf1ns?r7R=mK&GAS|`zRYAryg@B;lPUZGR+uXlsv z3&M?1B+mpACr%soG#xaeeMY>~9IN$p3pNabbrk8+Dz)87@?olRv_IXs$Dzsbob8gW z*UjPh^wSl9ipD=|uZLghn&50to*kwy8D?S0kY6{#OgXRHOx$@w@bGCgH$J%9#)&w> znSLP;Me0kH6RYg$)i*|VPrm)FQDI6KuzzehK{zWoJWV0F)9Z5VGobfHYi@k2_@u1QUw2;j?VYd6R8ugR)3=XJt@8Kzl)CH zEm8H zEd*|2J+4{SLX7bbD%aL$bH95fVO$lx7v5ady+*Y8>AvqZ*1*c~!L8*Upu@o9WJH^p znaTZ(W($*9B zqC+2%=M7|5&aXpnTN8AzSERAVAB|TBgVLR*BZ}BzRz_$wlZJ_L_Osi_6teGB` zC+utcoy1CRE#Rg^+p;4D_1~)zDBKqR$WU&KHlNd@1z75GCsqooJx>?{vDyiNj|IhuQ7%Sb)~Q^RAVRA^3{U1U5^vk@7eD3rpeF}R*{J~L^H(DvFuSKx^Ag~ zQd8|IQR+O20jbLq5BI*Hx4lGrB%uGq5N9$LX^_%dZrKZFg71gC0F1ZRaEcqb$S`ig zEa_Vqbi{p0*7P0uYd0ar1PWFGAqsG*(z-f5cqE6O87 z_uXuP$EpR&O^LGC(lZAlf#7{&!EYn;jrG62zKW%b9TEFE_MGX?va8LgoLWb8jKnT6 zf5_!zFb$*Unkol z&==zW`wu7kzwiIJchhw%QX}YH~sN5M}%9@ zJ8Qx=+z0@O^$g^%^Ojm!Bq7DwUTQzf1oI2h!P?nSs^sgNcbj zy(U^aX89@dj24HycU$b1`D`K|J2Kwi{U=^`sa>OIG372_0s+8`Z;0NIl@wF$dTRam zV8VFjdAUHGZ%`j37h9Ne(z!ZP!_^hX4e71~uu)qH#R1s-%UWhVPjh;{8l|QOKWGAj_Gw{}?!X1qwghk!0)Mx9pvTi*WuNJg?`7_rJ{%C&GF)J~|`{B69 z(jkv~EvCY^L^3$1F*+S%m@;z_>9%gdPA%iHC`_d}Dzf-l{aOeAd&`~lLVXgvg&ymej=X6<9g^V*zET4rFX>SAU2Q$3s`A>pwg9f(1!jb(yVA7{~P5M&-N5(`bhD*C~#Ggjs#GlAd!v-Vxt=74fwyb!BF)n_nj z6voWzacfdphb1;6h~Ma2J__upQ##Bb+w|NAq(v#fV9)+GGDPX0~YqL^zf z7%NOTYhk*cMlp^fc8%NSUA|CBqV3fsr{|2Q+{UWxvh8%mSX412sY+?~pNg0vwaBOe zxA8iCeLY{zZ0Ttio3VwGbG;5)suul8DP`8%J_t0dOoJV~&|a^zwb1X$eL}v1wiD$E z?C(0Mn$Bt43zg}lJHG*i-1gR{&gnMlHvbNiEmyfAvD5Ho;TE1ytF1bgj`KPOtuDKd zH^0Zq7pd7dc4ORtW}I&=bDI*3a~5ZMeJt^rh5Qa`52M8R>)d`CkQ~P6>x8PQ^=p2I zQMp(h@Y}b3h5B+s`_xIxaFx*gbwh|^A2BOOQc8!t6pU>Rd#rF9ELhUfWESKmqI>-f zuzI=hoe#cXSz9<95-<836FY4#^Hu-8JJx{bgJytZuGIfBT7)KsV{+wnG-%!!%XY_# zYxU85^JMw#13Fi)tnb@fo@Uv|8x})w>`<8CsBHbYS&WdzHNB$wrtNJ@sfa;Gw;2E9s-$~d=$;dt27HrLL_TwCAYZzLExE|& z-X87r7rK!162LB8dr=))5AUv55JXP2Vv{OdKo7r6x}t`|*8)xbp=pv%iwLQBF6SVV z9Wk7p>OnCk&EA6vTU6;N>`XN_?l}T=brJi}`=|=lX`0ZG|JS^roc4PGWS;d6ppX_9 z4mCU1TRSe^--zS1n4_wGCO3-bE7YqdblPi{^AVK(A#3AmfE3p#XUIw^<4E$g1Jm!&M2d<~@Nuxe~Y6hH4AXbp!DP;Jrv{qg$xS>e7Wy_jAv=<><^Na8xo6BXIuLb$Cr-yqypF z|94c3JOd)bx6|T(bcM3q+o>#gGaUKWR=*MMB%XM9*hOmCtJ%+;&#ZQL?}X_GGxbN8 zU*Tk2e1wFas~&DFI>nUCT9si=^*iz3&xMTgaO;-nzZj^RmNEs3%X~#gx!v88P_48E~%W^y!{|jBc{{jm)p(;U;CLU zVYz&;%cj{vbD+gUudg*JPfqZYkn%1xMJe9g1(bmIhs#b_D1Vum*q&*(Y9s`PMZ~JG zUST`DP^xr}9LV8vR8|qCWfv8^&F@Y0be&2-&~3dHo8-CR8IR?pbQp5>=?a%-x-Q>X zg?W^IE_XY!fzj931OtX`IgsKEmMasm0zDzwbp4yWK!rQoT<;W*G@2Ejwg22Z&n=2Y z#yrqhja0DJxNqdxqADQNg62y<-)de`NiHZ^y}LM=5f}84s#5d{8qEl#$=;0%r-Okg~rhv6HHkGeV z{(Ew9ASGU9z`$paY0PXm2Ok?xV19+H)rGUCYZzy_J2t%HE?cdG)#!{&IfJ@VnhEk0 z;m+cze&?j)5lcp#<7x7hTzcIXn}ZnU*P4YH{)>A|J4uC(wH1T`cKZknIn*^PjT&%V zJGR%_TFp{?1X%fC=MAq0t&JNPf8uV>LjG2cy|{)oNE`pZ;I?*EcCdQlEEm_jClQToT*e5q_WU78_0+?___u zv-bf_n1s_>4&SsZ64qO4`Z5b{$*?Y|iCgP^_Mjam5UaotL4C90^*8^KtO9-P%HjE% z$2$IoEu4mqgysd0&>`a0`_-aY~QS=RWR%It7NMc_PLY?(0>a0-N_JR9eF6| zm8HUWNG}-b-s?a=67mpkoJ}`CaL`aHb?7^R7^4xCv((k)!(CGjb&M}#RCC^OTm&4y z&e9j^;%;*c7Ljoe!1gZK>%JLICX@kWS4x!O%?`z%nfJP_Ocgp8Ce5}^&Vn)Yd>=7u zbINVZ%7Q5~|5Dc>&75U=fB!E2B7&XUDOU45tXr1oUJY9H4t6qE10!oK@jYXW<3Q*u8&GkH+zl=d$4GSjR^E!CLSAV$C}N#%MDp zamsGYyonk~H#rWsHV;lc( zj@s$}$ITUFH#OF+`~V$s%NKZZl&3^Wf2V>m*Kdj0fQ;;uKO1j^=!M0gS<~8JNx<`E zqzc$=ku;)nY)&$PK$e#|Q&rkdWr#GQ+Kb+8@-yhiX0$j<_D8ba(RJ8tXI0YHTZ2Bw17>;`MCFg{{R5V_LnUE z+p{0FR0FxX5#2C;sQ_dmbsdeokGTUkI_AeAdKq%a^aRVso#cw}&lHzmaWm};eNd=4ME1eavx7^?do)xf>H#iKS zom$P%1K`L}+M1~DI$X}~Oi6iG&2XLJ766G*;x>M zEFe;4+d|A4CD=0?X@UoAu9UivSkYlGi5+-YdDVe=(i&EtC$p35O_OSDKxy%pQTu4*dhnM z)R2|42jG`fJICc!^VA_hgv{|Rmq7L>HC~B&RDq(f30}5ni)^8{nQ$Xqcen|yviE{6 zjRQ^LVm;WiOAtiNLF4XjoD$EKr!v<}Hc{5n1Uuv8tkT-sDCRXw$9R^=6NC}?=No(t zJ7HeKPMEAD@-7gxs|KhHz$Ux6LjBZOWM+U)?HY7BK`w?kzV-Xj<DA|?rnE>5t-`>sF#!Ws5!C34)Ww?nwE zD0_G2X?xng=B)yupM}7+XbT9wbM1|M%MFJcfo+Slqc0;?aa|4dy=$f7HW zO~T2Y&0{fOw>D87C03LEkj0=uK3zIA6__cmjaq{yU{$FkwOq8)JRI%kVosUs)&yUB zawBfN`<}FH=opF5KTI_A(wpj-Vtb0({!>Byg_ydU?5O2uXWJi*t(I%^v~zlnCKzI?94J@8~aERnftq4qvggOgbXV158vlElhi)GdWBlQ{lF7{k5%(U zEC|mt?SVgkI96sZqmre-W7bXr{u04zi9>!Eg@g%xysM*Q`X}u5e(*k-&mvm-V8HwW zHj{H;TXDn`nfIH39pq{8gJtGM!1nfuWEe+2i9Zy4GS;a6j1#{qL8x{zmt`1kb8_se zA0{>~*#>l7!z>REH%Vs$ zMPeD^3stfRzq07bZE?PU9_o&g8w8h{Dk^~m-uF*UfBH0dVyv$88nG9u!1&T_uA|A&7)nty*VGk1LgcCgU*{%nN*B(gKE4`yGp-*7T@M0WDD z^jy-p!(uyyR2eYU(pY}n15Sacsa)VZkRV`)=6Ruj$8mQ0APpu2eY}uRn)l`>0-Hnfk{3mZFV z^6|PIf6nYK)ZDuL0DgJl#rqP(p5V^yl(BzL@IKmNmW!kcl;=4Ja9!NrZI@@_XMLSf z^v*<|=M61DSw9oMVI+s-fmn8-I*|g+BvPdIO&8ysk(2B8c7}BUEh&7-;O^PF4 z*emzGh?||ki{Eq@D@-qw`JGF8DaIYUVpt8t$}I-JZj$jih<08~SdardNv6{(d;9T9 z@AGVxtS}4M!;xB7Rst4XUFldRt(QRQ9ytqa)3U7lPqb>BB39Dj^21j~I6O9tReKN( zClRn+x%dt|*kTMET6*vTIDu{Rb0)LBX;*r2#Aw|XSd3>itofj$X33O$YGGSJ4aIkDACxtR* zPIedf#61(cQ$px#om$!xcx-W}8eio(1G@}lYKqU;bGkuKwdjo)SNJV4F|mChuHs?_ z`fiJFx0o8PfUbFf^TLx$f;MHuJl{?~i(wQIWxcokl19i2tiKKgI_xy=7I*vk8d=y< zT>~RQv^TiC7zv3psK}GW`uytX!}kGmbtoI>^h7kYCFlfFx=8P~p zGbl4fD)`kLI`rErRc{S3dG;OEI0PUD1)c@YskIVazK7q>uy>Vla_GIwfRpL2` z-#N{<9w2Uc5L0(7jK)}U8-gxvsy2Jnfa)2%kcC2_v41WpR(Z9Vt$w^jo!kBsX;p@gcE1c z0(Y%#+RxwH={vUJ{i$O3>xlGR7oi9D(f^e0{BZ%^K>vJgL@xb77?I74&~Z%H_}J6h z+$<-g;sQ*Ke$ru2Am9jP`rSUFZy7A5d_R12L)dc2iat>TE9eH+GOI2xNx&{QEY#u8yXSe$$LHdQ>|Qn2%R%O4sK)Qb4Vdku3?pipTnK z%hqz4=^h*5=nM`V^huH*ZmQ7bnAE$!PlUa~@_3}AC0y<+L*^=~C3xA8 z_J(U*h0!lU2K9~cIke0kVCtY#fq!SW=u~N}pNRI3E@eJqHPA37@^bd`M{HF+|$bcqZ=-KB# z#M*z(%Ri6`HxMU9Y`6Y75L~ALIOIxclCs8!{#d5JJ32bdlvP~l5vU-7j{X{F%lm5; zwpTomcupy|cSuEW&-N}f0{6@1Wi394snvi3cgSnE!mH%_BHrgOKYy#wd|dUe?O~bK zC}q%7fiQSO@#nvuObx=v=))B7|0#!(lxGH#3)f|W-h|{EbmqrR2F;?+kA_Q$><`Oq z0ndEAGp|}^COc^4(+>}&*Q+Tjb6Rx(-Bj6zC9o%h9$U)Kd$U{*uQi;MM_ae?0p{jE z&Knk~1EzvXrxAxPAoP`kByeZB?-Bks;V|u%tfPDWVp|xJ$g2e>VAv-DCUUXTvzw;C zZt35bg{1086m*w&Gd+rVyNJ9v^F>5K9f&7OO#`;a3|_WHnnJ71N$~M>P}8HO%Sxpt z%*GsbzcaK-X&m%JNw}oDZz(F`j8!|5WimR5D)6CR zMdh2Hy1538Ft~NtLR(aF7@3eX2vkbRQE3bu#lE*ng}xjQ65agN3-$B*+bf?1FS%}v z9&+Vwl_3q4FcbDgR?8K|=7@LK|= z1~JeG5$^3I$ar0#9ZqDWc0uBRCSu({t@*#+PLDukXQZ;j{HN4op&tUqsh<{-AxTEY zY0*b@x)^J~G$0CKrGJ%{|M=h|+uS#o6dULMQ>oahd*YE1kqN;uX!8)>DCc8F97cAG zmS(A@R+b}9bSiC>57x?6QM;WyKmxd{?T}T6YuNOuT)IAd+(>}c>GNYy^3FlC`xyg2 z6uSV8_0F5ZqpLH6+x8F{QlN+$@#umpCR0eBSQ~Q)YK*L(AFT5%Y@sLen)TdR8_c7< zrU&~6fsN2V`)b_q1EU7Z<05yENiZ__%=ddKxR`Twt0UnJLKB?WUg0AiS7gGTnj@-u zPid#g!R(w4zP=8^(Wz=j)7RfMi?M4;|2a9JWY3gP{_R!%qj<~+Tx*|BGDpI?bj(%} z7z@`T{=K7EJzCkA5X=Xp3K&6;T3fDLHs2Sr&oYv2nMpZQz&=iMO{uVl{@izLC4C-` zDyXU8;sxMltEHk@@9afcPr7Y?en{oy__Edv9bHuP)ipH72|eYkaoC}p#G58J00Asu z8*dt+^|*4!gUxdP&5*8mLH|$K`)p>$$6)DpxeK%3{x-}Ga1q+YPIrN;AoL*?$_0c$ za~#j|)^Yqe{?D@vK5qC68}CHdkxZ+&XM1orQNT3}po26~$Rq)&ICkSc*0fg0=-_uP ztXXq!7BJuhaob)V!XWunXw2GWX`nu+fQW>QWw%Me3%j#JpFG&I7Vaj>#}DKR!etcz<9X-e?ZQ zHx!d18E+XkpK79{QvTT-CI5})}L(usuqzd>|EFxx4J!AdfN6G^qM1LZ!X%^%3j}@ zBFxt^l3~oY2oyCFoG!U*e~wM@Za@Tx#6{hk37dzW0G{mO_*8kJxvkPrSt(HL)4%JA z>`I8teEAmg)lL~MUF}e2-A}_8lXvroKJ`~6IjOF@og$eqdw(2 zv6;B-(K5G&H&64$bT@;=+ESCp2Ju^YV7N0pgQq1i40YL+5c|A!tlSG4PvP>VN8}Wn zOgssC)ThWzGF|2J^yye*c+KIOggt&Uhjy!OTHZuft^cpa_Dneo`Fg_R_Y?TE0~r)( z|C1%$cEtV5rek{jg5I$W4b|VlL{7C8>es*f{yE;Hy}=WpNWC9*MRWJP~S!SS$>*ilo#r57YZA-8Zew~QBgTd$caum08 zC<#Qb_V5{~qTpr=Yjx7|t)OXo+5EWD6*bj|YWx+E32e{uT$V#U0~4jD;ke_mU(9`d zT|vN;M3im#<^%{#Ik}$mn-K?WM$+bECO4rAzkA<}A^i86=X`78J&o{m3Pyq7wx#=y za9{dm=Bs5ctcO*bAY-5(^n(68SL&SZyOI!BKzp-Z9O^K|RoF~g)-zMr0GUF~zUIWFUWk0447AQsF^U$go`1aY45emgxn!o#}t zx~D7QQzPGqWAZ0%Fw%(f7FI8O{;%a@1s7<*rfd&Lay9aEHwvs;f>iVLy@CjZ_=T!` zlF@E$Df>3wQ@%v_J9UcT0yR4s8q-9cP9}S%LewXPdx~tli89YPVYY<^b!k7&ZthPn zvpT#vDY>La4&7T$7IBxRpP1|5KfFirkDjDW#%=M$;K-)?)}|pd5`X-(`x|>45GB0q zUqWhBO1iLda4pq&9)5^yxmq%}tUUH*0K&w0PmQ1_KrPmtBn-*y&`4>PEfeYShbPq; zDNF$mcK*ojS$n-GP!#cUcHJHJ5_v?qSvGwF@A%?-Ih-#*ZReP7s+imdtV(nd0k;P^ zN-`^jlt2!^%cEOpE0YP8C2<9a^o!YBw|@gEB{Do1u|6eU?3Rx>LN!#3EHE6I|Kqdh z+Xpzm^0f(~DvU@#r}bmWA8pm!&zaHT;JV&>n|L6nW*&hTj z+hXq%sRupUvvmc}4uJ5?#XX4i8#8H=kaG}CS3Kk(f!m(UtZV}pZ4+#?#sGWmup+_} zMHKNUCGB3;hS_9z_NHC?9!ISJ*_LgcrLbUZ(qSueWcyucDm7ITCg=p1>N%XnR% zfuX#CLSiPTHm&&3G~Xrs z^h4u64U%Qja#UymFflEd!Fb~7y*<;gO_?lDldoGHlDo=$I!rK>@UU>)s21=gcC+{0 z-Q9DRoVO;_+)~MD3o{$9nDeG|#vHoFDzAX)BA!y}446w_m>J*=0Wj`Eh0R4pf3`*9 zo&C-6WK5hxYM$AVpVL=xhOlMVPkfF+uAdBJ<&5+%zjANP!pl;xFG!7iZOCBWaHLx3 zCS?la_p9Tbr(h0{T9t-v)+)?(nMd8WhSA?CR9UFsSCs zVQJ*|tq!?ugPe_h;7F-fGt787_VsL|>!X3>4j!AKWV*ihS9Yd5Ay4>9m^!B3B0+`| z6Q0tQR==L6ex4SrXN}5|lWn}XtSy@z+>3q0BPPgEzS%KnFMPjB z5#-OOT{Txl1?E4DqIzXgDpQ|WAs6?E zR>09+NP1p7ih(=v0Q#}s^;dG*)gdXhVnpOa531Mk?7Tjw`?C6^)XxFpC#NK|K4xNN zWg#YjMlS66%w+$?c0(Dh?C!g0PIre>s#eE?KI2mkD7PO`Zjjz--N^mP_0(jfpnJh3 zr6ls4eYf`_z-g<~Jcvj{*!ZxUVfnDpFtsgbo^~a;ik98@q(9evyb6Hnk1mJ=214<^0!1-%r&#hjKwAF0wlkmFqXPV?dydpp{(I}{-!oKb!&I7*Wp}>~fAuH7*6yR-sTz+H_Ld;xakweL zbgke~KHu?PK(aIN-_(^*y<3KE!5SMq!}f%tXsr3=s02%NESp+Nm+4T+(}J?h4@Kgm zhxg2k+bg~(h`4W%TTG`_?@#3K>y=n(t@UIwF0$@-?L|kDy;0m%m9^erd?aLxB4xj!eAzS<8cJLuMgWw zCUAT0{Vo_#-3H1mg;4lL$Fqv{*}lwRkj_WBt0xKO zE=YrGX+Ty1k?mHYdVc(F{ab)EnkTq5l1S9yxF5d|b(^d_pZYZITL&V}JiP+?M~dgA zbhylhzOU|&+cq*v3?`~%%1K;PG-wAxP?y(hDAcCbb;_F>KZ|$x%zRTorS!6&P{p1t zZd+7V8kh`r2^)Q{0fMn=9o`-~?XrvwS1=&hch$5PU6Ml!z(x*6^n{uc*(rKoA~m{3 zAAmCj01AnI4`74R0Av_K#XHrvR$2~!Y7Hl6TyCBDDbd`GIZbH#|F zB~+V+Up}@Dw)Cm2$Y+uJuK;t^!dN%z8#~W4zQcYu`Hw_=8Ipf2R_= z0+o>2cDC|bhfK15y;)~Zszuzq`0sBtdi=zuKTVqAxdsFuJRvl61MK=>nK7ct6r@ND z94Au0TAjJ{%KZhUp7#;goq*1W-a_4#yL>w9U;V1qrOYVUg;Bn{P_0oo36C`z*pej2 zamdniPnc|pbIL5c-1Bf#P9LnW*7L5%941qS^AWExw8L|Ya>eEFeK3RLJ< z5~m001i?i8K-2-5MaR4JiNYJ58W62e01i5k^|C&R)dIJ`)Vp^*L5b>JSoe$)KP2 zwi<0SsyUnU_auheA@v#ZjG$YdCo3zd5*8XyP*auUYJ@r+Yh;#{HtCdz`M{j{gJ6wl zs;$acn4n@7tLB^B?0B+?lL`&D-5lVN9(!=xgjHXB@MFM?|D4obCw&x`BX5A)+q%se zu><3>=SvBFe8I?TlL6FqGeg&X_jOLmBAv+-iEw^7cPR3wSHtQd>55Odhtn2aJ@Go+ zaYRRu_7@~cTC2o;-IO>~hcU6yb0@7T8EsKKg&RWBQ9&c0EOr;)5+$o|6(QgwoK9)e z(j=&HEycD^@NCooS`dX~-qfix{AR~ou&h;j6Fa{-7}4Z0U7%+^TwyDMxi?B*Pu*y=nJlmP)Y!C+#OB%^R!^Qjb@gC@`ti{3? z3eK@%4IZseC!-v4pFv%+{y8(3j$I3rUU4wNm>tMp;+JPmCOCKjkd`aB>{#kN$BGL6ho zEtXT;eRwrQ3tReZ}i$ z7jRu2>LTsy*V8H9YrWRPgGd3@jH<)Ec%on1uG0GN%~zF{bj&s${6y{p@0Kh+&*qc_ zh@vie$FloJtBT+QR9{mo1~=_Pp}#t5(^2A|*I7N)ajqUroZ(Lee9`+VZEZ<-?cPiA zE^X9MU*g2mD&C}zT>hjf-vz5Zd9!o;(+}!jb@dA+lBl09&8%JRz;%1DslVS3L_ab4 zD1PI%<_CdJ3SILzDi3CU>Ve(5Ro$0`m0k%4vem=9kI;j)=R}EiiObT_bP5q7c?>eQu41Uvw;!F{4+x>e5y-CnsmB4t%^(8HCn|3{XU&R1hD zKjQh--3XO0k8{^N0Swbh(l@leujh#E4Z~j_;Ee z15BG>zZN3z| za6es*8dCr%q@0UY(3`-UN&uNa@#IB>u}+}~Z-fCw2K-hFGR zG_yWjGO_tf(Qkw>(_oQFav|PjV^+kY?_IgqRxMbWBPPB=-uyOGL>S9H7p_P#Gq50X zxh>XJ*k6=N6TZ&qIQf`y(p%m?E=yQs(mwpLh}fp%=qYwCbB(c6(_tr1V^Zxv!y{;% zVirsYyYH(fS!G{i{T{?T0C|wW*}G3KsFHWF662`KTZQDw^qEc4GXmsLLvl0XF{B1J zS%>R^KkGC~j{I~vDInZzOnvPe|GF^Lc|2FE5cbn6bs96==x5MZ8tYzb)(pfEtnq%I z7un9aWlx#;7tIXAA1%=kV@7pOx(@4jhQb7v=0Z&8gKGQ40HrKmDyTX1@b1}sH}m8an~)Y z;>H=^*});px!Bh9Ki1n^Z}GVzo`3?isPf*4Kqw2jWfMfZ{PlQG<5vGNYfo95BQmb; zgrixz_Mq{qcr&a);~08lyx(>kH!1|?b!k=JHKAU3Q?m0fATPZy@h@HQrzdCY!mteO3>q;@SxXNwE>I-gX?$xwP zf$yc(9e9`iItWb7woc?f2p=s8dMZaQr+dYOUMnVkTu{-m=ugMD0-ROStNad4(s zq7Ta{AoK6{E*4QY$8Vy!jg;ZSWb-l0!CZeaz83Yx&P@<2qpuRa>57izv4!0xVAb#Z zfm!VKGeyM9q-F;VW0$n-=OKt`oq8uAo=8U3e2RJStrmA8;d|D;W#J~s8GW%L08oB0 zAP#sj4wPdd);=I4LJ0t)r2AcUBwQA_``fcZr5BE*fQaVGW|iC%1JMITnmkn#8%efS z;{!QSA;Px+5K8c_tKMqeE4Pzi@d?R9^qFSyPkU4Q9Q*%@IDN!IdvL^yoQgi$nxd|( z)-8Tlyf#_Ub;Td#N+1&U2r+K{Gfm2y>T?f}n6zyU8yLqoXzcJ58uSmd8sxkQv+66| zL43{27BA1mJizjRz6#$?{0Wl_2ql)e>z}oE?G{T0x&5qg@z3xb5-2K6WRXyxdSvef zxp;^|Imtlm)NrAGog`xnOR9ZSK!@NZhxyRer~uY>b{NZj@|oNBm!MLq>GXo{Y6${nUQY<2V}BG6uElVr8$yVfMna()r3sVNwQW2|374%-VyNks9;|jc zF-}n*QJXC!F zB;!3qjX?w|Y*JZ-pvrOg76*3~$%Rt6yAmR5w^a=q?eQ6Oztgqg%LMLfKNj)GSUb4- z5%{d#pz4cu*z*9X1ekHh@3L+oXy50%_)fc{>(MrBJmy%Y*Z;szE?!UD|CNRDU=k zm@A+Ikzm)(ne#4jIoG)#UgmvZVc0GHSz)!$G=*@EPVEr0E7j%Vw9oRqhJaCa2lw*V zU|uv^C;oTNKCtjJtVB(4Ul^Axchtzo?PttEtC1&2w&%h-(tOXWpmuj7qS`CAK($;Q z;&FI|3R5AG*uVqYkL{-Iio=oP$fynmX5#>b9HLs$&CD=;WvzU=Q!av3M#C(^N zlHZQ0pdVjajaEhQhl>}za>rWW8TWZ5*xd-CkN7Jr=)=gp>FfD!-^OU;ABDP}+cpm7 zh-mFDa7sT}x9m(PHT+E$7_g6G9WIP)rDcyMK%yUNqO8QAlrlxzhDm&u>gB-{mIqzgjy&Ew6;^lkBoiRwkyz94MU9Lb!%Tt1Q8W9~c} zijy-cuh)W3G|-kc7u;aSZPvP`+afMeDJEm|eKKx1SJq>kS_#y& zT_Yt^$Gy1ZJe4F*>D{rG`rym0v$|cVkbV>S64q}zDEFAeH+TyTYv8^YwL%(16^l9D_l1cigO@rZy0-5Uj(y>^H_Uxq`sviG!#%7@gXw@5x$dXnXbWlVqyG-?gWF*QH$8#>#!bSzyMHS)}u%|y(5 zx_$iezpPXMd@d0Gv4wEo{BipZ#eRrv)aRDr4j!u>leiE@aV4+Wy4Rw3cS!NOOCdX8 zhM5>GZBFaan_H3fj>!AiBL04`Tu;Y6hqd6qW<-hLgUutfoe}~@x|RVXo4CA)!{@>} zmydS()-PERteFmX@d$$j>iw&{+ie?Hwa#60w&$Y>t;c$JDUO3ldA58oO$=#Uuo`#8 z_Y2Sozn8yo6wEU;-P!}MYP81Z-@Kg$6^UzH=2ma?k^L$`GFs^F`k$ZUS5vE7Yepy2 z8?WFM;hT15sDs#k_X=qkU=4^PX}=E2$MXeyBDiVI){{7_`46_n{9VZIWq>})taK#= zmphh4RS9nHzZZmurBfOVA$?MYaBtX}*@ScuvBJqNPJ2NHAa9^LlfL+7z0U8d?R`0F zf4XD?-P#M*edEOKCoDYFFK=~`d-oxC`VSPFJ+Bku-?hYPsTd4{HBt{r>F~Q+vKIu?fF2(R(TTaY z%=m~=TI^R?1aFHU82u^MO;c^bvI_ITzRca4MilNE(cxs8(|ew-~2mg zZhDM&CoQ@r#lQLW5};XwU1F!CTOnLV>mCVQ?)MJXdnWg_4>lF^&Gnx#RXO)ce0H_0kSdFPNTZCE6x3=xbr6N=1c{o>Hqj`fHU~)KC zfM}ws!|Qy!Wi1pr6dRS3&s)R^rQ-{~PvJ{) zJsM%5Ud;BEmY*a-?mqoCPnI)Y45yed?|pX=*qulPYQ1Vj6n=BB^4_W;zfLT@>jQvM zPEkmQ`^Y1r>s4y7FRuiF1dPY!I;it*+Vi6j&y=dpQeVP{yFJ8B&(-xEr0aqvRe&wIYnMvnIWy7 zJyxp})phoW$yW*Mkc>-9&igjp$Sw7d5}8e1rNJ)lkf#4ij{zFRoiE2ok|qP1z)Q&l zmL#rJ&pWI00ev+Ofb+&`Ut%V0%L}KYlua}%I-Nnc3v7Dh-u-0_0+dG!BJS=8-POl3 zRyGU=VtDuy!;Sz^u9!4+yWUbQw_Fx??j`(w#)DHzd+T3b0MBqDU85}|1G0Lfs-A?h z9+<1uNr#L+bUjYi`qUXZ9}l@a4I3*~lfn=Qm2bkR`OsK(ZB1L+!smRw;p)Wt%C~T{ zmk^x2E4&7+DP!K;#b5Y!6WC58?_3v+pmeBj6Hx-2 zV!=x5YqOc`DPR>fvZgCw0%&fG?7?g=*DklGvAHoSj)))peH@N<1e8s?xV9T?VL29y zj$@EX7Me&v7P5A@9pZWnerwA%^xEt1+W@vDJTu|b3alYTH}a+f1-TFW`crrn!k^tb zd`U8PP12)&dR0dfNmf`_UeT7%?s|)rB4f;?rz>dDFI1MCJ9MQZ?~@|%cM5$0#7bKF zuVN(?e0u1@^`9qY>UOk=F*W-WYk?6Hm=7`MY9>@#&QRTP!SOY^g4ZL1L#}&~El!dj z$9TcI|A3R8uSrY2$k80ik773VO!ZEg&daN2-PYGBxnN-7 zJpS%Iz7RK4a%{!bXvcO16w_wTXZ=NveRjwcMN6=VUBl!2*xh->o!q9p&VC>k3zdpsHmwW4SyeSivul&L!{j~~hqH@<>cB3(L=1NT(zaJkTGlQPc z(rwzyTQN=2q|8B6ttyNBU{xXkxoTHF{?jf`nQuW4*Uh-LBApCjki_$wBrh+D?X&AW zoNOk*xMDS+_Tuh!+=3=CzO%rWAKE@T+nMDQ$Kq`)2w@F0pQxan$+OV30*}Lt^^U2` z+vnto%6Gf68=r_K4s6+qmQ;*qR{#g*8QL)V#2eg`Tt;o=_M75bGwGRmDRo-FI^|!e zw)ux!EZ~_ik9jTv4n^>s>V?5{jw~*i-%ld3?ZAu+%lf&`|6ybRPQqv)KhydE$mLel z8WP(on6Uy*C$B?K=;95n&Z6>M!>KTz@h{S9G#^^zvOSkTypmYDc}aSJ$NjGMWLrSM<0^{cJ-*o1 z*%Y0e@0vcWxj34Tv(FJ;m5IU z&aPciiD`fTq+_N~VJ)r0>2PxFSNh#9wk-4YLU5I3VSm2=O2kzU^?Dy%=9AKZuJLM_ zs0SN>j|S1G){7Eg8KtsV!1y~|z1jZHqhtBMKRObX*qXLZjuacDEs|_LeK{=;z>v6= zbQ52pSf-_!qk@9+hVW@PM#8F3xbt1dz2^Op$oVOPyN+;%#F!xUOI)J;913E(M>>>}n zeYUOCRGGG^H{3h#eQTsVleHZnzlQE-`$8@r9%oy33bo7PAxP672f*0Hxz?w$9>PW1 z;_N*(r)DBx@OmjV?es)Ytu&z<;_D zpPf`ACig8@R+4(JID#Vg>b3rL$ogOv5wq27*SAB| zq^>QeRBq~)p^KTZqb$T#+odj{Fa1d`9`$P2)FKp*(@mBRf=1A8ACz^}-1{&ikuIeI z;)BRtV>9l)1MWBC=1(T+SQ4C^GBP(RHf2pinK-uDH(CIkn>NnX0!ZlY05<9w#+Lx_uQKgBpi!6BwF6MqXzK}z6VegJn_&S zjT&l(db+d(Vy>eiwc5xPdQD;SuE?=#egr4(oqtVN98Y1-^pmz}1DZ(Xz^^iMjqWD<6D5p0 zqQKxx5*hIt-z6{t$;~$g0z2pEOMMT1TsgStE~B{2#>-h!EAOnTxIj_F6O1=p8%1ky zk=>qX&=uZptgT|_#`F!XhFnU#=3trGH2sMhZK9FPL~aX9Rl&rkFfOxE#XNalB7Z5u z8~6`hZrU)?T01d3K8N0kEMxPn%njtvcRbI$C&@pX=a#Zkh~PGXHJ@ zgp0W}(PRDNTC91h5tUX@x(Gu5BeY~c7mLYabY-7KFzuTiD%1AJqfBlM;jkAp>7K|Y z6=skH@prs7EwKU8@r9dG7fH4v=3An%Pr}rAQx@ba>`a^=dZqgwGd1eEq^%qiugOlU@tFJv0*WCS?UWTbD7LlfAU=Aw1?@D`>8`HI!Ac zB`qjTK1hU6^vk$L14ia5dhQnjl>ICaqwN@rbbb>6IT%rXtA@#$`*=>)eyb-JZzgGR z#(q-9_Qq$IcwhUIXVbC9|yHZNsJdn=+%Pow@$__`-|T0amHlo zp3Sr9h6^8rMOkzXqqZ}O5HW@Z*p++Y(UcuQ8Iq5OQN-&;x2bU5CiA^e?(@vjpnhkS z2d_y7bU2~HT#Q;FJ1fW|Dc!luaKk^|`Gw%PHstvxdOZvmG(3BgsTyCSlX{3*NHOvJ z4WN5;MLbV02y1xUuU)Aja(TV5Zo4s&M{Y4&E$6~{eb9jdNGFX`-Y_cuOv@OmE5eF% zzD5JC`}y|(L+K<*eLoZhi%@?Bw261)T*xNs%qFA_n~ocU-j^NkQ$ZqQ#Mt8H@&~l2 zhy@!+D%xI|lahz+?I7w1Ukf_pp|m@_YUQF9<0@@qM;#}}z!CfPXfNJab~7A;@%QiO zOS4&pQ}`!gyXyJ?l*}sJO=80$jxywleeRtFKU%WLcXKTeW-V~C*cvVN&EyL4}jLNCPqZ#3PKD)KT8vPt2eYLru< z5V2|{S6hpjyRefa3tT1wDoqtr6ZuMPipTMT)$7ZMz||`&0X|FgRSb&A)wqV~VauZL zCUeQ}0x#&j*mQm1iVcn-+RBNF&7Whljjy$wfeBR4Dz7@LMP!RSbOgg`qddMZOwcl5 zbQhCcV3S?#hDSG{*&89Jrn=af=NO+m50lKYO7ipD{qhIWiER25&PZ09^#T67&OCz) z#yZ4>8XLvQfu9Mx&e-*zQe~HZf62A*VCaU`N`fnqJ^J)%H*J?~HSzUs1VxhHd$SuwT4iaT z=?as1i*Ex{!cY3o=u2LBohXLm*+f58ZzH=^-u1&=wH?6_L_aQDCg+M9`R zyrT!FF`nF%v)vwyqij$_@lI%p*-D!B>dN4HODHIpf0X7>@lt!^bdapvX!U_}*Zl;w zQaP&99R;Y5LY~eo_Oz>oHm}>dX3x$2PydEU-a_+#2h-&Si#h)HmzV=ipQFWeGoo>sqd1M=iK{A=U_PqC+|@dilp-@A$MD-@EOWr zZtKXMAY#swEum51==@Cg^JW>POm5x^ruS$cgmJMkszD83NsM^U&~D|B8DOZLKR*%J&*;jWlyRTu!b@Stl1yH3PCX18K5Rl} z3x>1GQ>${Xn;d3WiB04&7eKZ$p8jN}Y^+Op-kl(Nsv67Uy;H_sT02oEffN72z1AE$ zBZe-1W3FnYfxtV?)g75i=J$a5Rs0n`t9`r%5v1t>$Q;~#8?>O>nK1nh1vh6!)AhTk z|L{%L?h8ZN;rAAg))`prdlR`-E9U)khn?)+lRD}4I(nB#aaYdJcIBc*J0!7cT`Sgv zdz}$c%E-mF_}(9?K)NKrayn-+=t)#fD8LBqxi|zie386nN|Pyt7sm=>dzUYNUGGqM zeDcIlsN!>%AK<)K@yl=BF)|L8AOegNPUzpR0{=$f&`jXBo+c{SC%j`#n>yMYUKq^} zjxXUWm#He^(0hsfL^_to8(5wdx(FJ6rgq&oEI!x@gXTlHlg`;v+EccY?0mkzD?0k4 z?;Uw}bY|hj$!4VPbNiGX%~!=mQ<6Kj)6z+~KEg8vy;bCp8%uw*2nQzP7$MN`Nl{e2 z7RxR0r@h^^Qm8gq?tI|ijnx$;AjLY0fNf?mQ=#q>Gd8s~Q~K(Nu~=$6w9Ii?hs1Q; zMMK&AZ3tn6o(^`33ETnhbBFrF$_-H1#WM@|`z4%}D;8Iv7sk5xJ@hbL8WTKnwPJs;KYVFk3M(?YV)f(N;mM!oBb}QjBC{#xZ>>JaCc)iISuj0$TY>AZ_QVWgB6e@4FX5W0= z_2|rAc^o8Y*I=U=B@vyW?}v()uSD4!`)HVOQ)(J%a*n-9^cf zZa9aH-O6s8TQn5}yN>3IB#aeCw5z~F#JQ9sBX|o6yC1q50QQ&pe4!>gv!HrqBik*# z@BZk(KK8B!<7|y{P#4yjoY7)vh!^yZRfsA8-Q&AR*WQs@td13$#+M-S!qpz1`<|aa zVCjSaS5lP612U_J58C~^nbETCn2d)a10$&{t#N(zvfcKPC@fl%a}^A4grVEloaFo^ zpR=EO%>nC&=mp4<&Q@HOnbhlU7ln;`=vrg04jX*atX4|lOO(8G`S6Wv^V`CU%Oob1emB^$jk`YMhT@#LK0fVPe29tp0i zt;hK(61muh@Vbwqo<2XltE2Co-YTovNUbqjND4rSW}y~S>5Z`B%5t{)<;kwE?9kFz ze<4?N^j4bN--}6dmTk(|J_u0E3ta)H8F|qjXb3y3@lzx;p;9VP*`Jz80r|+?dp8Gr z7zSU%G%Z)28CmUe6;AXz0(Ch1$0i}E>J67+q8V@36KaEaMMLiG&6t2KGpL;nAY!?6 z^gm){eS@<}4{#*y3iaaTrpSIg)~bw1{_jDZ+-6dKXY655#U8%;$l*GuXF8Ks!~9Cc zqPu?|R29_PpKrgx7>)1?rCx$+x*YA3*8~{7nzCDO>?)hx2-(E?w%Z0m;*sWWxC3z* z2s3R?K4Ru7yrYdCR~pW6u&7}V@*PlamDV3zA+r?qtT_lEynJ8LCeg$_jyIPAg6Xq& zA5wfgeEdS~Q1dn0?P>YsfC$ckflc%9O>#FYirI?l!>vrnP$>2~h%6z1<((MK8b1#(oT<>W^gz*1}Wq zW}ALOr$om=axcqYrX4?E-dne_vHI{1F_Gkw|3Kb7xQ9A|k4>jd(qJia_Hx?B6X*{! z+ih3H!=Nc|Gt-3z9JV;}0x978?xrZoXy`m&e_Uci9P<5k=r^a^YA?-swY4{RU;8iO z1(VVnb9<&><83&lW8Fq%d&~EFE*oZ4`9wG2&XsU**3Q*AhhA*XuZWZH55y_r|A06F zL;6U}Eqs1Y?g|fVtsM5l?XlqKo)iY(IS)jc?>37a_M!=L74pYg*8;I0ZO_h86bl^+ zb2t>=cMBo)B=LAdfZXpGGSs8GG@RNH6w-E7%MIdiaA&GQw@%4zO2ueSMv7SdMK?;u zHr=ubxnXvVVIvq!cp%E9)m^PngZ&;gvkpUr?Sgf~QkacXXvww8%*hTGUlBZb2r~x zKPoT~+dcRjD2fkMGtLNJ6}A3qPSmQPJXn+Hf|3h znj=oa7%Nl}n&s2TaICfj<3y_1y?`I*M4#YA8D|0Cj|fKhfnyP>-$+w$jcuN zQOBMA51PDRGFU$afYY93{=(FkfbwC51!CV6!?dvP5LktM(y(&?aW%<(XOQ>r)Q2B5 zh)=Mr@#yfkNyBJPH`mpBE~?8^UY?ECYT{^o)HwYK%A z_|eD(R4P6>-(N<)cb|(N0rjqIkp}zM={nzbxS0yG7&=!FVlE#0jAXelDF~OPK*nUK zI-#I-2ohI7ocP|;^N*>j-9M5*$~2=)n;C-wani~#94#;CCZRT1E_A$Or(Zj8?1!X(?k zf(3N)5Y5Kk=d2di!?J&BLU2!Pf( z#&B^l;vV+N&$}J0K$mu#B%>MBEAKN~OvPFP-#puu9uTHY`1SDW!ck$e)Bt7b**F87 zKJ7!ug~fvkt2wb~Dvjz7hi)Dh#{}^l_O+7wR_8{2@WVIP2x0kHD!TV>%f0hVhcN&| z;>h26{gpECug@>pcAJHsS#?NGRXSsg796~At}@$fj!sh={f1q!f|34P+nGNXi8c3+ z<(KG7`Ea)08M!3pm{jPV1iD)jq$!-d#kp_|vNa*s(tW`|^8u)eF<)Wcep!{b&Gk9~MDk24T`AH&SLYepUosQ$;z^obH|w{5Xu8+%o< zhXgMS=~QVF0rbu3>@pTf;Bm<&h&UFpd0%g)dT%6K#zXuAF(8&5Nm7O~6s&`sS_~u{ z2Ca-l_JhtD;QAm7{zp4HKw=dGi$9B7_joDq8Wfx?i=rYE-a_~(Fw&T<_@IA&&nXf!b4437Sq#L7UxZrfp;%#EQc z$t6{PX=ud+mG=4m$Z&;utAy>}KY&(%Bo;`~tWelVJ3EFEJaBphj!P&OT^ze6q) z)zU|w`lChq>$&|upREag>`)D`?ka@Y`XAWO%*YP_6RTX?)et6=A^7vT8-Cw(ST;S_ zzeC`t$?>>pWO}n?4W}}8*!}TSfY0K>NpzZ!MArOo zW_PAE3U?&u>4o*0;zjc;yv+Ha24 z5vu$*{^K3ed~~nc^Kglu+-}xCEiuk!&_^O!kLmmVp77X5JZ4%u>0Z)0Ju`R(sr7(x_J! z#D^k+^?gYE&EH6diXCw^{mZ=;s(*ehR9f~y-AcLxG%H%7V`!V8glA=hy%#o97I zLBc|6Q`YIBJIF(zwWgugqO+Q9noDz;0}-dNEE|Lqet-6i-Vez`2AzCFylx*QfCvE9 z_g?%Vl_f1Jw)xG)ZjnCOM8ODA>EOMP8j~>}-dF(%_BW!d1DOc3 zHWgLZ43wQ=zpVFoB6sfLWL;6-55=q$!OQvGasB5>r2#X^FM6&&<}V2H=wEmND;0(A z=t2~oCukv}MOqES@JG}r`)r@Vb}wueOdHF0+l4j=&Z!N6N1Y2iVKFi7see@NJLA~AVuHxb<$KApF+!d(1gnNidFxY%z*zA|=9fAR~;<-VA4y^HVnVeaX$YzD*gY|`&8o$)kL^{9Y+A^}MkNho|O z>%W;FMN4ZDU8E_(ym4(l28G5YsaKHR+(CvlVsC;c>L4S(Bgy=^kBdY>@jD*NYzAJz zz12{Bc7C9N&lb^ba>jhB;h)UoE)EmYT5pWxN&y8&reMHByk^F+Vg%G3WYnXJ`{q*Q zk-%GLC{rRk;tH5F(8#6TPr~VQ>G1{F1m1Q`4Pz&W(6V;F-I2_F{20sU;CLQHmHcv* zfiH>o!Iri9!tQi(G8ZX=IDG`B!rRlsz#^=tGQ%m3)eJM*nSAM1rl`eCP!RcYYnh3B z9Sdxh77svT|M1NF+~*^THO?;#VS(DO9p2bzn32oKIOA^&3i#Qn{21H&Oqy(#`c6lK zP|jTR=Z%#R@4)HNgQD7si94l-BLYK{jG zr;nJrS~CgA{jy$ait5tsg-y-ik@zA{?096lbYRS;p;#tWbVluqqxV>z`d`?^Z; zdmNDW8!k#wCOwSAY_SU+!TD1&O?dpiZEK43WcvrE~YuBrc+3qh0b;Wbny-W%b zr+ON)xh(ZCD*sjbsCp$^-s2JEU)dr5bFV%X`mqh4!nPseDk>qDD3BX|K--zowW;-c z@F7*eFQT}Z$e=4StVr_(7y6h=(+VO2^Y+x^n_#}|{ZBz80(XO0?mq68A65}0;)S>^ zob2#k%6{lezEU`>yT3ixBvSDbICBx7qy2cbYyXc|i_rHHrlJ862G~fe6|;{9$^Zn< z^fiM@)y!=7G|W>#u|m>BbsLsPtRK2#nBWA=^$kRm6{mjC-8srxKc$dy^`88j?G8(a zRB7D?q$I_Bc_Fy|70OV8m~}d+k<})r@5zPTW5zY6(R2X>wP&uCk_pc}ZZRL2J zt4fdjjLC#i+KDe7!qc8uo8Cy}#R!di6m8 zx~EwkdR)ud&)U3{bIYs2u%ULU=yC9>WI{AQke(e+K?@8dx}v!e$i)+5A5x0(Q_E*% znAg0K-!UYcMy|N~Pi5Jseu`xYE~QukH|V3u6W1MA#^#Irq^~)yHc$ z;BO!qZ8lzE3g9oA(%E6YJqNQ@g?&uwwT)K44#@&(*OMKB2B-6xg*S8Lmmv?INajp6 z0gt1Jh>* zDkSs&>?+hq&Kq1=siklo35eBnul!K0sKL0O;|vsY**fq~0V(rkrbJA7o#U=tz{7Un zsSXFsyx${^_`qrk5MlNMkL8D#6c-5%y2VCJ=wi8&XWjfw7)-x1nlB*`fdOG}`C6^2T7W;K*M0Sp( z?!nb)aj|j0!}1r;WO>wuYW|ioX35ICYbjFIDxg;l-yIwe$J{=nd1KJh~KcTH1 zUBV-JtZ6^>TqHRCmNOIERk?$)Ldi%z#{UQr{%B(BIua8w0)Y9qppy;*#tt8FJQe~* ztSp}txJGP+Z}0Im*sX^jF0@MkZ*6~50Oe^%Y9{XF5%nBN%#FpH9s77J2%>R2efMDl+pGO4>sNJ-b|6e~tmMA{;y&D3OZ^HMyHBC6ne$QRj8qrTH>WJcnPx?3Y~49r?fi3vrO!hsGq z8J>b<_$NdX)9*1@i=^i!B?fp?cvRIA7urLul&v-dwGHs6K zqOe0QQPjWSw12z~kck7Mq#F(-iXL8`9kg7yh^;?{&KQG}Rvb9lGCk9!d}8Z690sy5 z@J8emFDahct}u0_%$-^xuACbTeDFmA3I;OPsz{D8x;3NFQ<$~Y>#yOPOY^X<`yBm8 z*G=kkFlfcB+xG8M=wGx_IhbqrgTzUH#O4}K@CLMNbx7<@m&tMqrYnyK zsS?nnJSerK5wz5dcnfA-BSq_6RqQjf>m_U*tc)zO z6ZX$?{*lA*KfS>gtTEwivD0}4@cirXb#PG(0t4vvsfx{^12=#NQ{}T2vKaQn-aKA! z#~&>Q5emZ~lfr;^f9Wil@pY$8C6UW)3pwb~F)6?07@>0YiR}4$&;JGCL4eGdBpVT#Dw(btx27@##yR5;=VkzWgsfWa)DE+-vHH z=~3HiHD{HjwN$=5`P#_(cxu5zDRr04gf)%1M(2oqAEfk%pmf<)26br|Th>BFXSU>( z#8j;fnayM9Vgej8ti@tBNq*bkuxHJX&!)U$S8%8518^-0wDXR z0BlQ^Ebp&P$=^SZQ~m(?Qhz^o3;kbMrr;Gzi23SQy$Rq2Y$z`L{F~B?vvc_y5`ht) zB8%yvg&NyYGlj5)O!2{E&p#9&!>S;f~_r%rP0LhGe!USy4z=z2Nd zA~Ey;sjWbBp>&{RJ|UW1VVXCNUlKLltPM#u!Jt?hG1=hPX6tvjKERO$L=C&Z*XVlZ zAc1N#V^8QOPJl9$Zq_0-FV)Cjq9^~P|NcG})yF>8VlPcSjCYxY=T~>{e+)<$7K`K* zssSoaH|Bz_yMKtxscT#C86*DYp89h=Z}GtvGvDRCS8QP9s=oSPk6f`rW?_x%dcS>3 z$vK1T#DvXA#R-FH%cG`LCLnWlv7nK!gSe~JyHwmK znrGdf64+TEHkB7hhY>c@z=M(O{%~g>Tv*T0kGX6XFYFw3t zJ}2Ldn@@@Z{}}n<9wDdRW$_s4vCjtmb2S2Be)?Rwt;iYR6XBFxpy5|8_>U(*b2|*g z!^K$pt?VQAx^QGN?&I%Nkbm5+1s(JFvKQ@39bo@9(vctcS~uWi7c}DH(~|twHy|&9 zy4K@(DOVDtLP+Ft4aouzuce9?H_o8Movn5VPUeR2i_Oe*Y94X}{zl)7LG~IR@*IO^ zH6P9m2PG@cQqKW5kUk7AG$x?{GtYb2ACAxQ#OaFK?zVn*KOcy+SzMw&CevR&y~&++X0_;!khORdG>K_f4ilI8HXi_7=XN7E}G|K=eNs1Rrr1ka^`}J()$@Q}UpMTjP_J&F~)uTZ(l2yrk7{l;mVi!T2_m|cks_%i(srxmXMX`xbJO@Ml!oPh_D)!UV~ZO zV4zUYI-vF*f^uI0Mn-m8m#5ecyY(N@2Dv29bb#z9;dT5845FREH@cm@UX=#V#pq+s z?*`$dFgQ2t9h4c@&}s$-)>t*3bue?h*Q&+z0=PEJetd~HW`m96KVuwnuvB+Bx3|x^ zV1=cB3v9;P5)qX8&nwx}GZ;2kMJEpY%?kVLn&VR>`$jcif7Sk8F1X%)F#w&clJ1+i z1~^{F27Q^qX39s0Z6;u;U_t+}>7Vr!u}&cPp0YZLf7fFCd!>PiG4+qx|HA*@Xa5fW zJ&7BwPhI?;!*8D5zlu`WZ=p&3fJ`v9e|uX0uln9UDN}!a*uVKq91r?%C^N;>|81{R z|H@eV_1NNLf<>>GNtXY%+98$w?o99oskJOwqXTu1m$_E=4LC zaPbYQklg!^8?p$NVlf`e`hPqwbXG^;D+}g`@ANbv+ydpwzY2nlVUMc?B zQIaCAmfTB&_gXmHAFL@e`s&fE228jtBOeWq6Se`CM~--|JgnlCP%2JUJKA*Lep)T) zx@FS%{iG(tn8!H~b0k9u+2!=xt-`~Tn5&chz0~!C)dYg>pzMBXcScr|vfT+A8kHrH zvg6Kwayx#0Lte#Fz<9V=ic4=>mF6L!`jDQVQob0%H3lg>a0 z5wPp`vwJ6BY5w^9Y?Wi4WpC-LzFLVM7GTeK!R9KerLR*L+JodOOp{@Fly&!uF9e(p zT{WWA(l3BfapI}bq)8TlU$Q{p&hX>;8NkTljC^4v4f6lsU*#!M&p1qelccNq<6m9? zGc~p#uE(&}dY*J|1|S0h$DAJ5m)KENnY%O4>#GZ)8`J^NAPF`}$S-$AQ2{c_1G5Vd zvtxI-_8hP&f+krYrz8wDb*`}$^F1K(WdNjG!GAe;bDu%GF%1@Cu-Zoe^pkyt-Qcf{ ztF)SXDG_}dRLZ(ORyaKb`dvFF!yZu(SeH1tkKPO864jqDfB;A&42lf!t;w~%JAy%U zL3!oNJnn$GvprQIQl#2Aeo9!ZQA1i&qq_13E$L=&xKXGO9P6{U*3dtYU{sx7|7#Y4 zm4#^-ewLdLsbaY~H5=UGR{U{70E2MdaG{Iz_D>SxS{*zR47$W*(BcZ}5@Jn%4iPX} zLepm7idYb`RVq1VGal}rtCs@RZI_*bM{T*Kl!Q<74}J*<_M--UWtL>vFD3?mIq08?pA|3RyGi$GTN)6??Q zahL3{Hze55Vzr9*Acn-GG^DKMmh9JX1C*}uTm|(=P0M{I1#AKkTjqOyOrTS~bAGfr z1ibs#fW7RC`2GO6wtxnZ1o=F^zh(Dp1SRaUe!pU?CwS3kpvnYobQg)|93NPha?q!19C-Wg1fc{HvVaYXZYd%7x9wakzzMws_YXbNA0>&c7wF9D&1v*}A;6(04j zO2L7VVPe{T0&6JRG6G(2kTGe{SOU{tTA_LsUg6tu2`4;b^BNhzy%h!=pww^Q zhB>y4z%=6l!4y%EHE)_NA+$^+`-PI`BaboFS9jk37Mo9e3m&@kbCjal0FUc45f_Si zxlgQ7fLye8f9)*7$Zo~%S!Q>v7b8+X?Mh3x5yRR>RQDmUgB4F9fivifj)LJuMgBcO z{?=OSRV6$J5;MRw?rUxEGA)4~!?yT0BE)irs57B8U*4NEfO*P$0R>4%YNUbIOb7UK zt)s%sZhIf=D~;_~0fmR1>{R*q>LNlYmBhwC%o!Yrl7@QURZ~0y0Z;4E-|ruM)AHZ) zn7Y4CofKOY`nH>V-eT2{{|*ZLd{_06vyFF8;-u|Bzfm%mQI{JR(9;{;ut|wUkf#Hm zdWnmsxkfC)1DPS-G!V=H`3TIVzt(OY`AzrF??{IdaivU^1PU-u>2L+QaFxM0i|5ETAeEdAxPAPXpG$XxHznau$p3))b(G#@tjkyOI> z0A$o<0mJdGeH7dgz|{$q=i?(T)NXp2RWji+RZe~P$o6dw1bFiQm^Z$;(^jV`)1NnV zah@vR#c1(0+;`~+3xCPSX9h#7<3%USnT%75^KrfAN4~Z7R3slb5OJX4Wr9UMBfF>j0au1HpFndFcrn+*|QF)I3I94k!w6+`SVLuc+emI2d zyy2oZx)O6WA=(}Uf@C5g(W845UR#r8NS0=6z{HagMB%=8EA{j-$VHTxPEKadh;dvY zpp-Vh%4<)Yi72P1V5k$ZcHsm5rBxT zXsw;qo;XUu>(JMuVC}F>7SW$j-}EV@37T+FNY5-cn?-mWsMb$t7kOxTdtBOdWx!FY zuguJpi8xTScz_!E83*R?t~p8=^(sQjKi_`!=h!XD5@=Cj9#)b7{}=!qpf$J}1IStVeI< zShH?O2FpJ5NiYE?3l_S_7s$n|N3xOwFaky~RaD+g;IZOy4&Y$>2gof^lQ$}-<3>%7EWfc#+AOLvjkPg_7`>h_KJZ4l}nXR_*g~6fEoNTXXG;0&Tvk_4F z46##FtvzbT>9d`9F1Fm2JXFW+n$hTT6n?%l@$n5Oxs&UKD9NtO!t6n#`$a2OpX1s< zfMI_!8T>Hy+Cn=owb(<}3QAy;i=k(1CP!%GT0HFk0n4x!bqc5sv7pia{WUY|du9(YbXaO?m{E=Jw zgK1Dx-1~gZuX;V4?I=ueZhT>EF*kYOoF&Ct zR@l`m&F_>v(@vo+>kcQcTYC7&i4UE&E(4#eFMPTJ9;FusLlLP zz}-hZUN5H4`P!JZ_dNlpxKr1SP+^pZ08) z+xqN{$LFH30vEto8bapQ2G?mMFdEIVkPn__TwO`#HibhE1QE3)=2Y;%Dfy*g$Tnpn zrt*e-Gt^x%o!>d>X=nS?gooKk*IOnCY!*5o5B}{xA zA7J05<$r@MXN9q_?sf;cQ}6O2cP?6+I_};3d+8wQ@YTWWbfrRrY4Sz|%MEa$k)J3s z3`9iU{~|efl$h?HjK#ULc#T1sqtqT~+7!igP4{zvczMf{dx;j1e1jp0n_LVmh)>=~ z6r>nbnL)(j$qmG*J&le<`Vx_{-M2Ne`=h-B5Xj-l=c3Z?kHz#yxYyIeMPOXRtS5kKw z2isDHTk;{3MD`v|NVV2pz&%bk4LIi(t<+tfRM*8qUo^u`Z4+p zr^=D+DQr)_zXaKQ)f|G5>_o=QIycIhbP?8gHk*3DY?cJ&RHj5E*_V?c^XW<{5NG`= z6VZgb7El0U#ABWouGska_JQvZsjJRlIo^ZyTZBZ^$CToXgH@2w>LXiwzeLnyW8^D{ zk}b6MJpg&$QF{7U!SEVHRp9F43K>ddFN3ceV=TIycXK>B92$0Pg18%xK9$$l{EC1OAKT|qMn7%vApe0TYL@BFn2E`vEI3U>Kf1sV$M$Bl^ufKgKB?v$<7dN(oS!MZDqxCget~pAUgQl|;-A zTSUBPH1BvmN*JSN*wkbN;YkU+<%mNKTr zdwtMM=+j@`}rZx^1}f@L4q-Wqc0-6U7agFf~kEF$`jwUwt)1+jJv*fTtCk> z@!GZe#Z!}37O^Vy6I$S*2jLZ^`j2|c{}!JJgY|fw$zD_gJVZ68O7#V1^4TFF2oBha zRl7q`d4VzI{e--!%(Th_IJ%rRV{~Wyp?-}b_}Xg9_^n-D+{OLaEhfkNy$3MB1OCTjCs#p&Y=T7%wxC+lB6k*PAw)yYr~XM|T? zmX`lC!u@Ji_MI^0sry~i!5lA+n~Kfx?73*VRC`(_d6AUpQhemq7lwV3Zu~Ee=6m0* zjt$9bTs~U_Tq@H>ryZ`1smkDb2=$6AuQ{4Xcct+omzg{V^K*x;Bm2RsOI_&I*=t{R zFlr!wImgKxvkt9dI147U5VM*1PbIA_1@ASIWJTr$=lm&IHXko8B`MZE7G8IR^+ahSM$UN|T6prg2Ga@eL3Ph<-_ ze7ikAFYUN9!}Lk{VV+{HFsH9Kwmx+5tm-XD4-_4yS7*5=1HED2nQv2A@EPLw4Fr+E zp6+keb7VCs{CNXK&Hxu4c^X5!R48Q%1>yQS$j84iSDGD?ebdW2_mxRstMOm-u(R_P z2kG*PU6AXBt+i7HZ2#tf1jStB%yt>fZlmdA>My5eYX2m7XTTcJ3^9zzr@1LOz7d(| zWWq?OdmhN4N2_ZfErhXHI%z5oMFh|{M9D-RyqFkh&gQ1ms`ugXxaM~~Wzr~da1LHi z5&rO8|1K^sq@a)b4-E!{;~6MmomOlQD2BgvJcjb>wmgYDyuQL46+@>qW)%P|e*yEU z@3Mhi{kjX73!lBOI!9cB*4(?I4&3oy7~)U%eV>OMZWhD6);KEzhsFLL(p6GB`eyG) znTQGRLJA=ICK8DMljrN{X$uRbu32f#bXAaXBABDEPqx-UzYILWqmFVlEz|F!gzpVp zJ;GS+&rfr&=VB4( z1;G&RbNH`Oxv6at@|^3Hjn?HJ9WLAkd5;*KpH%Vcz+@ge_aR+?%3D3c5Rz zZ>I}fU%Iq(!boMc_gZ*5lK1PEGsiXGo^M~QlCGLFr>vg~J)7XRGx3eH#iu2fQ6~M1 ziAT!`uIQFL={KE+ZJzJ7A}_-)fA+@iwV|=hmZLPrztVBMswg}X)_LgQtryQJrr7U! z?fdS}5_({F>u8!ST2nYBW#(r6;q(77c#{6(!%I*navq>;G$F~jPqhKk@9lzHVv%v& zFuyk47$jqw1Ot5oAM2lkrc5VgXmU}&W-7;d+^`%zFtJ!A2$_qhOe>X!^ryIAB?xmeMItom7yg&29Sfkv*Axv^CBJZ=K?S%p?(;vZhFy+_*QO#S>( zeYIj!@8%J}$dbJGyH7(#;r}Z90q*Po=#5l;FMfVuMm)Y>_sPb>Seo>kHqxx>#R~+Q zYY*d>AJWgig`)Ck@D=%U`*~kc98DgG+I1Xz^?rNc(Q(%y)bYJ_l|Ht&i#Nm!5c}*l z7UFKEiGAAMzDe~|B>dXvsIwu|YduzGLxT>5HlwqjF5kX5kdx0zV z(26};^8?>~85V;_IYCc;!X6j-dG^gcmYeV2Ijp@8G?`cP9Cg+YQ9HKz^1$eYc&8E1 z&E>t*tvdHC@6C->=bj`|2&C~R;8?Z3kFJait2PM(ox`=1-JwV&$LLAKhB?Jfg@x5A z34XC}QxtOz-eBB0GU4`p;4{;IDD4H$!(zpliR+dNytj@$SQF)lNxo%S&ixuomP&b- zBuYL6$ynW2mI*5TQ;e=#n;mu&d-Wv*KUj}moIq#_?yh&UV3U)1iY*rhI}?O#lW=&D zxn6B_*j?bLtRryWXF&I!&xm@)VLDjY#VhXGWYw2{#ke`v=kygNg?B+rpU<+Ysgi74 z)A!{){=t*>mG0{n;%g=m%`LNERQehkerV`nw}y$LHBR=5VL-ZX7=T7 zeQ{SQPaUusRy04^L5C92rZHY%7hR1y-G(`5(R*}_$qu_CEf>r7${aaNKaF@4g}S*+ zYP+?a;lWs4Z#(gV@S5+l1ir_r>z2z7s+;oUugKv)vBVlW61hZnyB_)IcY&wF1XF!? zK?;o*0yzu#z~Kp3Dtl|vd~HFSn_>#=?^5u+h3V%3lMQ^56TLII6@gZPHT1?LuKzQu0g z!GuIEA75tD`rugy!}`ky?wE;;fPjWL&mGoV9S4n+M#}A zQkj~ZtS}v+@bM2edr@c=N`@&}RKX=hnD^H_R;P|U-YLJ>v+KFBB&wf+_Z;6G%1v7@ z(sX#Y@P0x5Z~*7aw@C{Fx3vo}BJTh%DGYFDalIMrUe@Rk5XDKpLU0pDM$EK0phmAR zHJH&+%((Tf3ANjr?1U4kdiZSTMQbMu~8 z&DKd#R*Lz+tUcq866^0Ko@-4M{iq=yCW|GXPFz3@e@?^iwOAP}YSS4 zu%cqA#v`Yh3o}|>zwATl5wpiV*sVPXq1meCcOMNsNMI{9@pkmU z*WSjedjV$JpAs%$8+rpl_`cthQ-|*vQ#WSxSpIHFP#~Mdv!Q4NxmFK0W&dK_w!xI4 zgF80)_`lxcKW^E3_V7tTdRi#wsACv9XAiWEsW<9FI`M?(6*{rI_Uje7%EwK@ym+CI z!tXB5%qr`?)& z{C8T^kJE|P9PozWiFH|uWW&%*d;jN{;+Mz11*1A8JCfo=yN`mVg$9eJ*=1q}J#;LfrqGa@s8Tptn0kzushO?n-5KD?Eo5_9p)0ML`WCj!d)s;S?57|W z<%m7YaUOV;GUH`o^Ej7zD?get(PG!;@*EMedWtx?r=k7|ki}jj!mg^{KuK7`XCW5uQlIjWI8KrsGATJe3hrQSxlh-AD88;c?y0K_vCgyzu#~L&f+f^`QO=lZf&P3?7#fqst>vd@^CP=oI zm0;+Glpd=ySc~5=KX&uqHt0!7h64u=M^n@Wt;Tb)mV2+6^(0DUhO(QT5K!LP4&-uo ziF=ddIqOlNX~CMD|DzOBJvs7>E7EIS`j&CbVEPc{iMHPh3AS*mhe_i#ZrgcYV>rOl2Yx4?F z)9FUaB7^I7X_usvb>3|JT#OzR9^+h}@4Ee(;=)5Yc7Dsi7xODMmH*hH|F&)4TI_{~ zmT+vdWOw$!yE`n_`WSa;N@epZ{>Ug_mP!dJjSiMjo8356-V~B~wYhif5YTvxU=d?a z4NS{LwZ2)hfT-Rk*c6YFeXojtP~w@MeIUv{`^3-ibqo_4s2D| z`nAHpU=Y}FchOHpXlhzI=c(%jAA#_>3*}1o6`|3!8P11&*jlpeULo36ZB@sO}Erq%8&LaZ?}3j{QrC#G!6Aw6Q%Ee(&T zSCgzpIGkbes4MhbYYltmYt_F&KP!aW7%?1@{YZYGt^TdA)L{m%J^TLUpO^7_yw@%{ zsnYdaxh-J5)3b6WkXUBgI&nu}e55z+p&!k2Nn?2`{M?hL!}+hw3Xu7ff@295I&%_k zr8?D)85%*a#ykjdC!)EvC5czF=@!!s!g}awww+rec{Elj*{*8m8{66;CA@a+iRCZT z>BpmbGf{_AB(irNPDGy?5whNSvMJ;&>^w!0)OV|TyHH2JjTp#ECv1NwFjejmUxagY zIKQH>1BMG+dU&wZ$ef_}$#Uf*rQ?w{`i?v3-0G>&ChJKI`iOnOYuiIRhBAJ4mlPse zmZ|1D%RVzFLc{~SFj79VOuGYWr>4aIlL&>GEkO*0FM znYNuzmJPbY1M@Wp5JVYgR7{Bm!`6)w9|+5Ld?V8o*3^>Rww~vMavqUDz3=$A){jZHBe z+B{p^Yww=V_psW3^c|U@y8bw+&LT4w4<8=cZMEd=aG;PjHBB9^3)L?5513vd&AFe@2&i*F0O>@Q~P# z0*WRxC)=?_6}p94xe=QQCB_j^ch`NH@QsCSxzqxd4c*GA*W$E^NpGQ-`0o3&tcvGR zQ8!=^@V$}fYaPyMq{rdnxpOJ`EvEcy<|E}Q==H`buCX+ZSQL zY4c5n_?@>`>c!{=rzN-=Llvp7pybQ07+)kXvSPhJYTxV-@*}~x!lBD5T#WIZC6XX$ zzna9^p@S^3=u;4<7f{mW{eUj+u8s-b<~9;A+-;^pjd|kqB6tm7iyH~qZ{;766gbGe z8zV-`6`_`TgXlmfXmr%PmLuNn;7XV~_P#)V;T9LUT~mGsE;b^Mo{>(5}Hz%DuNX7}T9s;AwHTZX3vF>dNe zjh|YsLaDBtQE!0hI6 z{#*?k>^xDP#AfP?9TUh271qK8;@~w!micQMvaBF zi&I;eIkqK;S&f>9j1U!d2+;qMq!6*1vb4R`&6YM0d(Q;6WEaAnC8TzA4Uyew_X%+# zvV^CVf>!vh@tBnF=Wspg23IsP8q60o zQzeo5rPeA1qs10O4a*bK*-u;HK`CLf9BvrxX$1ud6*QdUa(9{JqT>$*{R2zGHJvhG^F`+bXZ0R8$m9< zQGzF=P0R1D7NR(~*VIEp>KylfG~m?HJoNFx#9>S1-a!fT0S+_G>>IU%_pX!=k z@^8u(-@f+@>8_PO)xb4WVje;fiLlD=M|;?-AHq(sv6tFD+>O)9Z8AXa1k!u9zIH)jbaSdepsp-%Px%O#k z$wVRMnAF~%eZDg+q?VfNiZXPxGur_>08gk}R9sy6IZDe6IpY%^ljWB5nZtZc$_UeXU5s}%%B4ksuT*#9Yg@~^F$3IkcqBP#VEscMH zYYumxEe~LPBOAq^oo$kf(b4Go45v)Q2B}zho<5coW8gBH8vEXD6^%4#4}t?ySU^7B zEZ3m=vqFrp-g=p^lXaZM5g&Lff%Ehnc9LrdY+3% z`CGgxgT__n6uy@nt*H3tE3Fuf4L$>q5j+4vmf2 zz4@auxcom?1{|_v7(~*GEKe~9j;1TP1qsPPm&Wz3N)Pu4KKl&A6SN1S?-?gZWb=Hl zK+JG#Dm7{4Og-R~AeKt&H^#MRkCxqRudqRK$pYcy2Lm2F!lJ2o!?RZXu_jJ;dVF;eh|S3Djg83s?5LVC~k|ic}D)SFlXp6v>YpPg{-@FOIOEsy6tvntRr9#0?vgsLVi(_ZPs=k>i<95u3t=3SPzuO_si{Q;y;)+OFhZS3ESg zG^(zeuRs-1{rFV`Z^rJ4%zD&Jb7XI4_M7sp&w61E^tH4h=D(iezook;y&%ioJ3dnX zx%0^Bs`K7!>^)ZD(EWWwraybA9uybK{xWC_Jz+zauB67+a9~y&2$#SVI3_TkL>+|*{;hyqd_0|gV$%%{{Ih#Bi44ejr^#^iYrHt6kDn3ZT) zNIh{wc!|Yw0HDCgBa2DliSk#F9zG4ZExsy1+K zTWjW)NUy=txX7*Hp~7N2O1x{!1>p0{^}R|AgyIduDiFd5V2xgQ2TBqZ86i^>brERv z?_F9X!qJ9ix+xpP%!>>}FoTI_Vv}+iry^N6dU9(nbtK=o`H)UrE&~5R3>zOxH95z+ zugx0^Mxm+S!J3fYGd`}{a>V8C7=qYpI={J+=r`^Zb3a|u`)j@}iYrbzfL6=wdR4rJZ?vEk`=^?K!>4tK!5IZ!1_F5>%Qs13^);m2nwNW1?tX%3^ zuk^GxDUZ`yr{ugAmGo%ZPKtWwR%m?-PPoFY+Xd4YApM4>J6Y9x(MQJPG8qmNChkbj zo9XjS%e?=p(DUiw=?~{b@>@g>Irn$0EvQSiwNWAMO(IXvhez*k!QN}0#TB5gXr-WN zz5B}*F|G;ZeYmqPbB6Xi3z2=*%6On*;<4u2K9_@**fU&piUBf7W}R~M9`|8Ab!)eg z*T;3E6ts733?Js|^Qap8I(p2t?9e;RUH-8)V^6bN0b?nEvrM`_D%z&!+jtRsxBcaj z)3S&KZCc{_Y<d zeFxeI?35M61oG2FQXSSVk^5fhC9m5i3(vzEhQ^4s+noc&SazuwFIW2w10s6dY2U>f z;@2a(DxKN870K4=gbTG!U%}&KGukHIvzwbFa|rZ;%OA+^wqJ%$|G?q0qv0IA#?1)k z=1@+D*fw+Y!%?Vo8-M-?pc=@|)0APquN5X@w|V#A(gwdWXVIWo71|sP#9>GzpIH>c zm=Brfb?>!UJipbbYMmdgRb(iKimXSIjQEHqb%`a>o7^PMe9-QiXYGrFBPrHxKgC=( z6w)=&oNG6_0hMS1d`;n+qtm8ri2d#uOX-84V0Nv}_0H*wd>NVX&IBurf!lSN6ve0s zzN3&fn1&kGe^v0p4;E!IO^&d%&&T6IK`gRwbr+A$s7GG{q~PHkD%MpWY(TC!uTF(x zstp*G#kV9t^66WNf2M8w7SIDNR92OJX)#W6V^K8V#9ieW;h3^IQ1d0UOyp{W(u z&s-?fFu;DFUm9Vwde&LH^NO7E`Dn7C$S2z+bVYrh&jkxeR2caWUHW$ng5l%~*iCQ7 zWu3#rl6bA@#h%~a*&$dtGduIh2JvTe&JNR4i*3wKe4BYAM_WVOob)H~DzwF(2vcKG zzT2dpekJ+MM~sK!s+YsFk)(GDQ5Uvm2bLvvKXz@0HyI@uQ1$quh2{M{=Dpgsa}1t# zC54=A!UV|BT{kw^os+mqLvb_+-0kG*vb05a7%*mT<|Q#g&JP@RO7+0&;j;75bIXJW z1%1Q~YO98y2amQW?veTWEzz!|jxSwDYo;yw>3iC8`yhW;Tydn6%r79|Z*RwA0l@WC zzThMNE|;P6k5uLK^hIWc3z;uo>?-zrCYwDr-`&&Ahr>w3zZg_*MjCjc!%@>=9~wZl z(qmQol*T@X8qsI3K#T<-==zzOtopf!&tkkERNFBx`vPh4cP8m`kqzq zHPrnEj3EA}WVKf#0;EmxxOH;7vA$?{enY#Gn)Ou`; zm)j!uxS-~ta1^#ovCeTE;nK`XE01`XZB|5H?B6oD)Hkj=u~@0C9Nsa4HT~?glJI=x z=aqrNrv-+GkWtDs$Dc7W$El%K^vdn{1m(!#DZNzanw}Ok8##oyX*=FKnz) zGL-{M8`4>p#&~ULNKWkFX!#*wbe{002H9&z^hTa2J`Ka5YJl#trBzpc#;I4H#rUZ& z(sSkK!9Z>UalOK-QP1iKh45vo6w8q`UX<17cGZ!OxU*APJ_Yt0jxicz-SVaj@cqPC zWpUty&yS&teI2ea2o-J!=z_|M;AEE>OlLvko5nq%Ob7polq}t{CjQax)}xwlx|Iet zvxTiNkh+ImzWv_GypL)W+j{WL3EJW!^csG^G)@jX-)oM|c@>^D;_L^_Y?_OSDYhZA zQ52zEdRLXju!Y&9MJHzwShpBq`v57Q*WnDL5$1QcZoGiYXHe6!RdD^t&axWsuW&43 zQV4J8>#J}ynCoQZsVL8`O^%OaxMnp<6E@h8fS8byn0^FBh{dAtNqw@ok|&)lE9-l7 z$TT`4c7WYV;@NU^Ae^|Wo)#~J0&SQBtqHpf&h{B~7I|oyZIwO_8Uzv*5NEq>>3SXg z*B&I4ED}VxU2Hq4|59*0R)gj5?=c-a{PL4z<{e)n{8VG0wDIb|dbjdSUc{xs%H3gA zaVpVU<&Fyv{m}7Ex^b7W)&442`!8>L>4R5|Tc4E|J{i))Q_wGEidg&$6#LH&aQORO zxWmwBQd{3? z;^0>GEc)@v$TEl{6`3TSg*6lli0p!QRnB*8v~~9qwy1N94=WN&%bvj z*0(6`kKi#52JCu^sC{(Z)@5t;iv9j(e%q=!FCvrdGxNMWr1UmUM>7`^}tefO{0Nl&zj3ozr+`Ws=PHd<%TYU-zKoM2Y zxqfl)ftz{^PGe%*tsFh=Vrc1Kj=LHISQcVP0{wsZ$UiPa@eV36in@0ve!CI+|HT{m zC2#oiE!-hswGX3b#}B?cA$FH4o zdUEguZbZ6n>kT*r*ZlH4?%2YW$0pSVt7Rw#%G@iA+9EadJrgs|vg$9;VZ7&leFp!0 znh)o|9n@m0vMlZ>KKXT2vO9e_Ui|6!L)5QwGrQ7tii zYRn($EbbV!#_9ohf<`?l|21v?^Y=YTfM2J6M`+pfVC~V;pKdJWp4Pn11C4R z5C49fyI--j*Z(|Zet#8DVyV0AuNkhQy7E<|c?s{{eh9oKo4~O6?&&W}@aGMlEG;3e zhEDs#)^nuzph@$;@*Jl>*%;dT=D@1`#fz#4z5J*V1dlgN<3;{HXyh>h#Jx}|ETGj-x&uk@ zqL3x!_AjyP&$r{FkbrqwiTUv9EWfz5U&rQuDe5N-vzSS>X(%l4ThheY_5=AsmrSJla!)93$EBA z;?{UvH*^O|tg1=OIgd%pRX~M9-&3;Rh3vx3uNrbHP+Q3WI&c|i&d~2Lw6-mpy9t>6 z-|F%>!|b~7(nX+JzJ6!mo7IgTucvy}h zCW}v?-iPTov8Jn~+T^U3Lg%L?m4M}og^>znTGw#hayxC+6opHmXbyvN&4tELqb_s^ z?!#!ZuJt*f*s>s^#1ET>m5gRwN>@!T4xL#OV@5{tO-wh38U1*#kq=U~&Nx0I73g|C zGAZJNdw-x@zaU)QmU_%4t17svmR=nCOC&4hChf0)GP{Yd`R-`Vf;X$s%?{ZLlWFX* z)G(vk-^ciqrLd#DsYm^(+zzHeC#sUL-7E*_Y;dK);_fbT$Q*iC^8z;ePmWL}PBn$F zYlb-IKENK25WyWiV{K_xs?OJt8w1{I{wjPE6iyAUq z=DvHJmDzG)!c^TFS71q4SGW0jD?CYdQp{@`)O`U!AvOc?Aa-0pVDG|cRkvYOPR}8{ z%j!m?#I70qu=yo3Pk_25eEvuy#MWd3J%s^zhV`2Z0(P_4o{k}ckm9Je7*&BiOl$pg z)Rw|eh>IoYCUA)p%X!)qEUTJ3>Pk11)fBF4)z=Ti{3j@K8{1+kve?#QAvquT^2j3C zn^22i%Hw2y0GhYNAGb|^U6q^n;c;S!jUH={4GZ<9ZUT;()^%>M!oLvH*Q;XkAE}|= z4mIz4XCQm*X?u&pZj#^Qz9)GFr!p{%&Aj*d#p+B{@6<{?eV9t(9TFt1NL(1;P1ejV zX(>i~3J=+CEC?xnassi!R26PMBWrqsZPq<>ZZKX-EJD;d0ewwvqbLVTHMv*h0kxgZ z>iM6^DVw6qH>ml|Zy3TOaM>P>n6w#n)pmYhEUp}b7pf0dIoO7783Iw*HR2hjy9~a) zW1jkMD+eP4Fzi9zdII3=`M`oZP=?O=`}N$S08!^>G97L|-cL^h7!n?D66362Z6~<` z=G#-AyYdf@=T+7BSPT_g%x|CPvz;@vbDzta>+?Nzo;x-ST$C#uY=Wz|W^~f^t5R6C z(p4LyQ#V)QK7kc`TGR=pj3~1AnVwf@hru+9`#OdKtSt zt}hN8(R=fwRQL1EE4KHzJum#j3&3(6OUaJ_1Dy%xQI=dRY(Ew&sn;aGR>RX!cgZ}GaU4+euUNe~OAW1W5;5n$Il zm4%`67g?(atZ}YRpMCp})a{R>Uh?fxK;6Edv4if-bd9on8X>!eyf7s(m%FG~Fv9wOqzasb(oJ-Wi5Vm()Z8ZW6yrC0-`1$M<>seW|)?_VBa6v*H_zxc<}>lOW9$B z0FQ{c_4xV5z$+gH9CCISOAG^njZ?#H$K~2na_g%aM~|kxSS?7e*~vZ)?zv^~VZod_ zQs6RYwyyz9v9CfxXcM~{YlX~2)*M(3Wz*5j2g8A zL2rrOT$fPBrM)F+Y+EFs4h-2?bcmUkB!a@MAlxCoGD6x^ zp8LU#)jJ1r_v>CWc(=gsy*}oW4;Q#Fz4O(*C*T_6B>*b zc+-0;Dud_RL z>(8|1w^t0`4?sTm^9XCEhu}~Kok+UD?e~;HIqrBlsSv4KrW)fgJ^OExeaSQujhtax zhu|{UI}eW5DpWt)o5pMRkL+-DCdsNr2-@UFEP%pdb-la2pSjO_0gf!*h;$MAc?Rg; zwk<>#C&>myv~+?fG&o03!l6)`dbH2}-W8grNbJGxR=&H#LWu-CHZ$32SNe-_Y zk&<}8BgqcuKT|9YP)#I_ZEF% zTO+C-N_7+@9+Q;xO<11d*9u?M~2YJ$r%8mZM#E+guih93`LlMmJ_TB^b&{L3`^&BO zR=INL#*Inl2oQyKs&_w_ZXipnxVJVZxr*JDBX~3}-gsg@4_j<7b~v8h2RmY@bL+nn zrBcGIk7WFtl%qe=c3ZhzGP&x@{ic{IiS-|;*T7`*X?0P z?*NO>Hsq;q%P0-9W{nO{gQ0T!OP|n-{t}ma5J6EwFdrsETqoILZFjYqd-dmsGQ|;u zyK9dkfEVQ}nL$U>@YkV(piw!V>s+qNZ3n$~5OrM9RP9YUCa#}#OTMkMov z&06@whcT^gZP5{IL2grO-Z3oiKnRqNHs#sdO%heSICwO}Yo*uYU>m7=XbN#1G0H&N zg7vSyzI#zHIbw`y%tK^sp^i@tv4)5#soud;lMbfFhq-9`LpTUi4Q9DjGsCTUv~Qyf zPg77seHjeqPMOq~NRkeLl@94!WH>lc@7P`2$bzXYz-$GIx$+xZQLwdtx>QA8!)+WT zZAx#DZR2F4NzGn0g*j0XN$arnTA=Prx z-he1WZu zRhSjRjUri;Xi&q7dZzDs6`6A@MwU=3tufKQFi1roE5DUB2PJoIrz0Kx`4ynd77kQ`RO(GJmAO7Vy@IHAKN`kvA zKLDy2p%}VS(&SZp^q0T+^Q||T6l@c+366J8NAKZ^@ub7q9FrS913*8p^VC1cZ#mF` ze&1kjv?>hwaUB{#0(p#^qAsy0y-csc+-Un&m6K`I*u}hDF6HCGcF!%r;v<*c@1jr^ z;lqU4PHD?UVI2>s#EsWy+ZBc&=}3C?4G+aM6|_E@cJ9T&WkR8nuJG-p1feoRoQLq8 zXx>2rwP0?&L4|{2S8BV_zIFS7t=>#6ilUvIHVyp}s1Er;=61%Y_uDyauT0kW@2ig? zBb%QQ4J)cLLvm5}&_t}y{Vw(F(F{W392Uh@2bv{TTqb>x-We0PvG&YKdE@eh;Vqx3 zHrfdL;h}Kg#^n|VG2tdwRMXh4hPdiaM|O|{v)p>+@i|q3qbYVhIs@Z6Mb9pQ5tLnz z>(Ko#<08(JzVj&q-M&ntb=p{5ck5uPN1QOAHMwIAWliR0nT#?UneC27CQir`-nv$h zZEDpn2WZbgI*}&cygSP98JE~T5Twk^QxPrZn$nYj4Yk1izl#MiZ1)jUBOGfLC+I_u zQSlyns;uzgZ?nIeDj*AXMqLh>6+?>=W~4%k{5+RQz9PD2AkK623W@nCE&Q`>0&ZCy zG~(tka>liba}V;jUXCaES*}N4F7>2;ICk;IccUB7DeYfhG{-SR@6kVcz#4r5e?LdJ zEMZ^TT4RPrGt;B54e`3jTCjHu&@Eeyod5FW8{68pt;x_>jdRQjLc1W8lzH$fnZ82` z95dHYrtRad^5aH?U&^i4?+kV)rWPcF4p8dg7IzqUvW4fkRJPT`2v|*BjHw?xDuCKr z-;klV|MckFo$#pRx&?%ajiM$T@19{7yw@lWcy%V@rn7xK<{0Sm&{|wmh)I>S&@t8M zP(E=tlw*~7FEncC5zwnX7X)*y0@EK9y`E&IAa0dfPrRTJIcIW0Wi0Euanmi*88mc> z?0J>NNO`YY`>ZSl&E^lzL}If1d5zE=(-v{knO0OMU{0XubM&`hxz6@q6cA){NiExM z?+c&w*R*(C{+Ym}@N?_UAj4wpe?em3<^kYxJ*6Udf7QXX3a-b{EC~{Kl`{`>hKkJx z;+tOSRtPnLsxuJ8GZCwcLjuG3PVJG{SCwmRt|`R6?CL2{LfCxlg;c#aP326;Q;+vx zw5Jrxq5Xt9@hCJXguwACKaigr(XwJm8_gEIKhwU0WW5JvSdx<1LKOItX?QQ%Cb?iU z*aSVj5^cV{_GM;wBw+???JP}DBlHMg6dBC+g>HB`0P+-7$QmGvYBt|pk$KtVvWsvd zEV`-f{NA6Stn)3wZ7k35%~bPwXRb7hAPhk_NQT~5m;o{2~-tY?S4K+C)&}<`U%a+!J%Lr(QZC7TyCFumg9a0|1QLi zUPbnR#yU}Vii4nIDSmr_G6A9V_y(uXUux%Z1Qgqz{*6c&tZZxA8Z5+nnfK*y5Pmub z*MIJp`mNMgG1FfK$yBK0z8E$*NWDx*R(AG~Y61Ub; zHBMR@y?&B8+41>Xk~=|(ZPMkLkj+o6%)yl<3%!vF9Dr?j=hmwRfLUml+eMJy%Havo0u zae{fFMYQd?MdvjWdEbjxgM*1|>Y19~PB~%b?!0oHHp%`bKi{#q(MgY19yCI=>46SRjf!_hEzUi=j*QdV;}2zg z!S)%JuPs5RvJ5*hfQ5$8iMgcj!L2`EpS^@)E}(6|IxG(IIj=T07(M=78-GhY;_F}| z=<{0?Ma$h5`Y%C)VpXrvC@C@iAKIR9%Sp4xXqD?fm`1Ivahu3vE3{?IkDvX^qW-hS zKCD6lMApBTay09BmG{E1!Wd?+4S+FIo_%{=#P$}E*1LcuHUuyRva0cS@APWfYJ_e) zD@Np2Y3gg{^weULjeopPF6P%;F3y1mBVu8xynU{_{t9>-x^!2PU~#~VZwx^-9ikK? zJZh0wJy$afU~d$|21$Ig2cOULOx=jVHCJCO$rYEgvDZ<)&AXpYWH{TrBR;n2y+mi< z^v2>4Tjq{oiskyOvU=U>3@Aw(LfHKm2G$A+Ek{(vw~Sswo%|ktZ+Q@|Duls_(=h2T zVTtzO9*$IpFu`MRbvK|8F<#gS$jC2YLiA#+^sb8>1 z)?MUdMT-EPReDhL?6Y_jqlGO6jo{sp@zYyWSi5YpKOQk4pifc%tey_D#)VQ;dTts@X=KG)`r z`a>!#G*iTHx5^lkK8h`JSah*BlvF$u?>=7B9w=yYnsSrAz96Aacvz{nlvwNsV*6@Krz{az4p!u7LyHX67)uvpH$mRlO_O z8TaPxTiXQQUc#D7$39dwe%=h*hFt{&)mS~&BifS-Grj_)T#4RWhkBqNz0L3h+=^PR&-T!PB7rN(dh2(N_}<)XKv!? zpmm$@KZKoS5dOg(3Q&bJZZvcrs>bj9t&ZDNy6S{2-K+MHj(}OfbFSkx+-wYDADYBH z<({Ls9_FiuaS|dyeA9nDtmw0kGjt%ad^j&-Qd&~?U{Ui+4wvKXasc`8A}6eVQKT5c zEEQ}W<)98${DgtVYHaM07(_tgQw83OE8 zx?xp({;G_==^!`Q$!ear<-BtD@vtURMeo~Og5 z>2nGDJF~+OwKvC(P2gYd$rR%sUPpk`JV$(&s+EULlY_g!M3IEpN3HtNiX_4RHB<2! zx+LfO&qcZ*clVWz{)@mAWP?3tE?u^IHjr=ZbCzvNXzM=II=cSXU5tS^LEX0c2h{bC z2mdC>ake_d1hS6(Sij=+#qwTMbJFdhPZ=IFlspMJSuP~MXkoZaO)mJVg>4g5#O1HQniK6l!+h_2ySd9H3v-(FP-JC(T%T5e zp5|t#oRPc17RU zwGz*p$GXNNb;7vxjBC84Oqw9drLUdVBGD*Ki2PRc2`F!_i}SZ`& zyq6qq-aqm8p(2kHLWOzU-rWrhr=&Y<6gEbepJZ@=}Y0KrL6V{6wuk~TGE(0~CCK5e92<8NL!Mi`p(hnwQS}~tt-j15k z6_{$-N-+P2q7rH|{?381Dq5XfefK2O_EA6g`_zbh1X^+#Y!);2EE;P%)>pL6kLBp; zI4|VYlb*yPGo(7h+DTwCa>zF%%r!(t?O$(m=?M$xzYw{o%b$Rr;n6krs7~XVmoUtb zAlfArnh$&}`k+(hh(W;QVWUv%k~^v7F^Zm}l|o8<36{EooZMBxV>X7~*`4X}x!;VN z&&dW{z8(FHQNUi)gLaQNux=&%VmaVKH4{gd$vfui3d#GA=>1|dUtbldi2FW-DsR3n zplLM4wg*iuXqQ5GdE|`H{Q4d93Q}*}{OdQe^K(aIEClFgi{_%3?GEh%MTSm5YQ-a? zjF&3+)82r-<@^51EY?fsfs#VG-?ECT*_w6!i;B{_1By*?yY}Pk|K+gOgpAmqa^nf+ zM#diO6XjXsnz`;&BLHyOG94bX-i!A_X3h3ld-mA_mNroBSxqiH$^w4hq389TYE8!r z_#CfRg4u=o!5Vtiz5S;Wc;ii*Lwh+VOH1r`pz>(^LQm(R8S5$TGpn#88zerRU7_P1 zZ%HafA!5q`U6df|Zzm8)343X8+f(9vXeJy+X|= zKgc}ybaaOneyorP12{(TDnDunW=%9|3U0J2k<2Y=4&zRzzF++A#1wKbK{k-d_nai) z`w2yRWK8#=XwUwoX=&LD%7|UBILT1||5CA^gNj}M!y!{~&wV{U1*T&KAZ>EXjnq7F z(y8M&Db@J4kB7wZinH$tH8COrn3d5Z(be_w3cqdx$*dy) zahRY%d{ymzEy*KUZ1`$L{|X?*9K%XZ*zT#Vt_Ovx0kbh+c(9AQb)0 zN4cjy0dye?N8Pq?gNWl&1NcyrI(>k9a!uCCT?z#2*f>L?HlTGCMqt&M?@sU!WYA|!E~@}I=keV+;FK);_we(^%>2OIUztE z$=<&oewJNRy(!dPF&4%PT&7wzgTY*zV{ru~v#(@z5*BB_H}TV*c^!rZ&W)d*UKl|IsMyw?Fc}e-dKrxemiG z@&-}~0Bi&SfRI_!H%Di>)Sa4Yw}{WH=hsX3X@WK#E+{es0NoJ6gYVvbeb+@knSt)t zx%P8AP~-(Wj%8!`?PfcqRwv#p4$osWe!Mt12JCICdA(t43@_>f5BP78)IJ*18#z94 zK;Ce{y*Zc>b_jd`g@x->Y1lxIhcjWZJl|+$yBVcR4YdC6!X(ACkh6nz;WScU)4)ZMgnIC+$N^9}4Tz{_$YPbn5I>LG<~^Ml+e;?} zP|n*Ue)AReW6r~bQ-T=Fo5%nCSlP3Wf+U)!m~l4j{WY2SQ^#?Dq`i;t9PNJRe(>G7 ztM~eiY>^@y(4bVgWx_{tmND*0>m*t06!?MJja>EvD`#CbFqhJ}w&DaGIQvmhKe17= z-8v+LR=`lQ1&3kL)ml{f0+H^DapJe<``qg-s=YMvI`3rfeeT)k9QGaK{&B2*iS_EXQ+l=74hw%J_p$R@lfC&ba9&=qd{#K8 z%>EKTDL@S}#&tWr+iL*FagkmYg~Pi zpUQV1;ZVnIor0Sf;jllGLiqvS(|&#E9gt^PvTUh8=!+YsE^y)+E-ncJqtb#LdLFWt z8#e*Bt0R@DBEG7T5^YGO^Em^hiGA|RdNbwHdb_c>1x@WSOJF-PZl^kdCKW|9*;4AT zq%@joz%&$q$3S^5@&!=Z^?=o?4gGIw;f$X|rk@sIjTI96F2pi1;i%nRWvuygyMn=9>j+BTA`AHKf<1z`deB$iQW z&M_w>qkrlb{t`BY)6|n&u312+$vVx&!66ZiC%IQw)3=f%ouoLnE_DNFp>ppnhZY*G zfHMG#hyu4TY(+4^*E_u#0BuvyEKvlAMoLEHM1HGfKTsq|@w)yd1H5lvShJQ~cV-+n zR{gmE>e)!Otks&$rNH_x1YkDMD30l?tC&i68XpIW2Vn?cXrMfoltJ*(B(xiFvH`?M zU~?KNpbe}(vq^R92vY5nLI)6(2+)6-9NW=|1*P^#@pDJ!P9IbgrLoH{Ld?XA)2Wc| zsiL!wi)_xv&tNv5@F>k;JyADJs%`PZ^;Fq^j7Ta@XwU0!z@j&s2gB`T30l=3Auiq0s8ll`c5L}zOo{Ao4uw1FViNCY1`G~fh2z1B%@}ruoueg zu;L0IV0_)5YfPs_e9(!FO~$>uBlu%(DLs!cF%2ldyf1_=Lo_r@xrpyIXk;?f>PguT zl<#&scYRBvE^Vqn-e_^*Yz4601v(2Y;$5;(o{zS$b2r$klt1A(?@~R{2R*$M4$DNY zW*;r+FI3=lzX9)CQa+#(Rni;}ttc_cfSt`YIr}aHo2?3jSV^7~m7r{9V?v+5k2|$x zJ0H$*hpihXX}b;!1A5a?K!mzo|9rS=)A8fR>UQELA?u+O>v>1q)rGrqN@rnlZ&g$g z-zu3 zwqulop@HvKo{r9cpX|SVd0PlLSG+S-GO#MPHVei*THmn zYHJsGXd1Qp@R#ue)EAh zj|3oatDDz{`Ckh9qE*AQ-kd>|&ek$QtN?)AT8rIu>T^dV=%mxBUuA^yE$;jH06tIk zMBlds%BH#i@zLi7R>)V!mcDyD7y^v)XLsV}o+Q=rfe2#Eiz`TYcW;8Q*XL@J3G}p@)v`vX*Q5j`>cKg2(;vYP=f(ZzjLaYQ#eRQ@h`5g z5T{nh;IHb&1uj{Q8`sCOX$p6#;Gnz*vi|?&wC}-zEl@}L2mf4zVw8fK*L(?S@~z}o zq1*M4ThKPVwK=muE{L-So2P}h_Bx*?_!6HhQ7L=#E<&>ndb9a2m76x8fs)ITOVeH7RT1)(@Hu$ z5l<$KAK0RxpeV>ByrOz3BjU!ePJ08|;1OW>q+UqKaEJz`GPmrZRom*I>j;i;)-dXUaHonL2>w37k$bWcZbb%svD$?l2?E$PVx90z3;fr+5m)8SnO}> zKT&VD!@X$-b8k>LCHyu^YSKhp+I)NqPqN=MgYN)AS+XdjO5Hh{c2Y{V0WE+0$5C;~ zzufSs6F&fgeFKhMQqPFk46YWe!W1#pGXUz|@>(?8hXd-UTwRUEqtqb3dSMIut_8w( z0ogAH0EeZ{J$+tK06BTaY1ISDJSBt+#)In%DV}3q%SErkTR7a_&0lahN_EHS>f_Xd z$M{19_b%nDIc+F!03N*u6fl4&KJb=SZ+I?G0nzdL>~Tmlz+5P>(?_zh3vf`6ijV+$ z$4*=4>r42aU8zBVNlZ-WGj>LflLoLYK&w_M1SC_aK@#-}n^KagAE2?Dm zWoguvhwy>50np`sBmv+q&upE@FxXv#qh9Ujlf7{R>MpDGBjdhqNu1VdU<^Zrf=kPy zCo^7j#4G%}k#zBYJC%DRpx21_B>%sgK>xl^4zXZi5&$*aY&?YksJyVespTUYy-e~9 zAUzT(`zoKo_0dGz-w?1BGTegxCx6?1zXAlCya>$=QrXnkXHa6Wz2ZZLp<<9b9=D;X4W6ke9rojJ z4sN)paPO}*q*`g6o<1}>7}zOJQp=cotUF&^@i)MmsT~c&`@N*beGyv);)pL>fB0$Gk01bySF1XI!=o2q zzXNSFoz+P5oB$xX^_eFtb~dwsP?x64M0D=68Sw;rw^x{@Z%2BIrAN_BcZ*L#4m1%{4hoSsVc> zVae2E2>)(4*zP!M!u|8qXjJ5)8MM=w(#ONVw4r{xK_Q2p$?z9O#GnhHv^3kUe`~OG zealDuMLotgwU#gY@(h+2w0#R^6RPLxiAm0}-9&#TaQd9fCG(i~`x3|V(wi=_(75V9 z3*DpHb0AFc#%^bZ0uZLC6`97WH#8fi8IKZBI@g$uY&PA(%M{{vjwBGb0MuJfFHGih z&%lkU2MmWMT1E9&i?x~6XY+|tfdIVFzSCx_6?rPn`pS&{;f}xNhJHC)*U9Ib^%IZp zq3g~MbXs3ua@u;nU6z6aQBM(nK!Stac_zf{UhO8iN8yzg!T^wN=$!emxSfYoyg7DD zjc3kl`F7VHuwqhkyFF@JKPKR|Ycx(V*ti8GnpK{itiuCBE5Mif`G5FQzkJsOuet93 zfuDKvof6nrFqtmOHG==nbd3xs-HhS-j(&WeJh<6Pf_K0X#q&XL4^+qE`9UgXnd9(< zq`j#<;HUP?Y|`S+4h9LweXPtUbU#QRhX0nGSx8~-HnS@sKUo#&WrqyUSUMWmMQAl! zuf~eSlp1UHyst)rPFI;Q@boieZHyKS?#!BrECI3CFwjkqq^DQPf;wb8iho?xH73un zDQ=D1UjaRjV}8KL?UF15#~a4Hrbl)77j1iuZl%nRbhtFv$4H8A_In zlgd7c4t}|RHCkDR1EgaE-o zFD7#^UmF3abj`C{#V-Tc8}z-CoikaV$M`lYKvE(bAU%;FlhQVuW44<3FC)tYvD!?a zuYq7L0f)sGIeu{;<-%PkRB(NG6Phu?d|4^nXjTBVQSTTl7OcZhwT6bU01hzIu*p+* z0gjTHRe)`>tGC-OpR6Ll70R~I;)L?}f~BFS(!w$NWO#^(hy=V&!;p4TyW{*fKT%m0-|3u@Y4XL9*zP)KBX9gG04M< zk!7DT2f!9@qFRmF%T9Epy)4jS%(DhN49~AlKgq`tv750R?as^44T}KDJ&EGts_vgl zNM%U1YOHwijGv+lnLf6?Cg8Fut*)oPUg^dASaK{8O&f7WJD?{617Uo4lsU?9ww?|s zzPt@Fdd0)%cF7S%e^v2$b;Z_8Jc>GN^CLhywW7GDew&R<9q%`NIa;i#Qti0+n`m;H zdu@v5>qp!(^T~2Km2C1`+2-~D_>cxbweus_M_%_3;0>+8Kio6cn(E+?=cA$-{ZHI| zCmW0bSU<|Z0Q~X+fGbJh&?vLiC~CS%;tf>D2?MgYbII;h% z+4`q}2%&=$6Ag7mGQ}nJ7ph8LjUa#bmi7rf5T2e|0pR!U=?kV&Kz;6Zx`47E~NZ!Rw{XGT+D;*MJYD@@74itX+Na?z| z-*86)0`)37(aKgpJ2dwc`3z@)XPlKH;AU^Z^Yyn}a)-o4YF)!f5@t^~!;=qVh5Yal z15=9_ON^Qv{2=;)7{Id++uNEXv)vWcsC*T)-s-qL$?JY?PAyCF%YmfQ*4k0`5fqR1 z{i``-4=8{DS1rad?%G}he83?n9`iLg_b3td=z5$VwK{qu@!csO`1n?MQ;ZyLc-IRr zGT3Bn2ogmtuaB*L>ce(8zlb8EO-!6W&$8D$`fDTO+5c!(|6k>qKR&d+1xH;f8+ggK zDwGba31@M4E7KvKnA`3N4!wpL`wxJR9c~Z2`8DsJ@l1e3NpbZ)%5=ul90Txhj_rsn5j)JV%4j}LR0O6rihC%2U*lI|h zDEm~f()TqEsDB3n6l?k_4d$`(?pkcr<>h4_SkVU3;iLeJ!}y%b#*bRbDr^+c#?kQf zp2)@LrZhJ-;e-IT1E6og?qz@NVRLqXqsj+b?HjWVF>2$lL8l(ec)ZxNui5#8O|LI@urxsK z{0HDZ2KZ{m9gFp%7V)T_@D%wlRED97ob0Xp=44sKZi zPs(4u!*ZDZ5|96+Oz*R)wpz(1=y%{DJgKN^r(AMbop}EV=0QPKWuQV@?>a5&ln1Kq zBDEBABQXFzjvo_g?b!w_;)|M!lDxgQu@t;fF~M*kK|Kgyj%}#OT8q(sqZ_c_+pZpf zXCecQc&I_N=t(vb&wwv7A%Vx=nR6c zu3ZaK$1-y_;g07eH&}X>>!MtK9nF`1k#`XG0MY2WFfNkK)hV9GB)XfSpUj9ljO(#` z8oSl+H}%xT=UeQ$uW;~G62V0M4wb2YJzMhj*?kkN)-MLZg8uNYv<|ItsiK}li)Q`) zseG|nMec!5_v>3)O2VLK;Ciy%D%poq^oob^BpjDFpH zFaXTc@8>dB1(Q9cFoqJ(rgOm9@s@5}1gtLs3NBAwTu6~d9n;}%3qx1$X02NPucsf{ z1ELP6%}qxRe!T#X#z?=661hk-RmtvySoL911PuFQCxZjsKi=kk1i1{9iZ)^f=0FyN zjAl~dXU^J{%v(bb(fB&4RQO&_<#X>ueTdK^fMvD*&WP`uYnh+zQmE6p0+@ISLDZiv z3hM?CBJul8)qT-*(>+q$=xlCa!B6)000JJU9>;MB+b6{+6)0aCk$hA%{Kg0Z;eIkQ zi-=sn=M}r-EmceBw|b*%z888w_ybQ+Z#A>KW?gf3e0;vm@%n5RNeFV7KW7+0xc?sk zg^4<``CnWBw{pTBYZAceh)pFJgrkSK3U=t1B zFrBlbSi|JGz&1ua27SX?TODode3c0|4i(lZZ-aacKex@A5U7=h^pP!H`9P(Z`=lcQ zYqLD}3dAg1(n5N_pU5C2_hNZ>8d{&Vy}MgxgzCYa1Hm@)bR9 z&@yQriB5_|#jIU9zIttfXeoEl19Tp#wWpGJDTX*D%Fn*^GLrukwqoe0mij(CjZn{<5s zbTnV5Y!i7eVg~Vt$CMUvs1+&EL&?O4wxB`y!@270O+Nzccq&kZJvhx~R=XoQhq`q4 z7qdh(_f(DM8uKngAt>{9si3X?u@gynr*-NWx!pHEo;YoiB)BK z72OxetFB#VCF~c%pa5ppugvEfMP9t0*2lCg0$^A2#|Q(mZ@#-yYt&-s4J1x3{s1E= zsxj9nGmtI{&w%kHw$@~YdebpT)CZfK-G&J57u(*^65W7yQ5($O&Mx~QGS>SU`fL@K z^F!pb)0s3t^YKVu_2an}Z8HCS^w#z^9V+}|bF0$Yh*$ZiXE1|WZdcL zX>PC6!uJO|$kSFpe3X#A67D|@@z1v_dTV?W5%!CZetkwmlRxFtKX|e+Rgj~UF_6p^ zj1a))?~Ui3fKDXaO6*>l@2ab!#pZsWE|xjrz>lF0N~dkMY(!ZO#a3Nvaykp2F#OVD zu>*XxQl1=dxlc&g(<MKz*qsalrMO;m@q!*2Vf^=pSuzPn+To$35wX?LSq0y~f2b zt?WGpzI`z1OYIycpWez_abGSBOD zaLSlfR*}&cA|2U7&#H+%o<%7UpZIJjhjn@dU^4tml$G8B#-~?c*;kyYO~$NqyWbnY zqwTyFyRVY6J>h)85ij5 zL+vP$OiI)}Vcd9Ev9H2(m?MmKHg}S^m!ZxoiJ>;juZlT>fvI-aPxa+K84w+HTw?Bl zdOzSNybLn@33~}yYptt@?CuUL*X_-wV+!DRzKk@RZ6v+uji=+zvMgoJ_k8>x;p6WL z^hh*7Rr04ez16gEqS#2atQb?&eZZ@cb~%)A@wp zs5|*UyTwVku1!PQ(U~yHS|rfvsLOOySyOcoi(>7&KXyHjg1^qy#W0YnTz;C}6V(VB z*K$B>FDmo=Y-d|No<;w$6h60dzrqXJ>l7Yq+^eD7v~Zl_l$^#8g;A!g;PaV!0|q6V zJmbic{tx@dBV|j58(#aQ#jc~PbczL9nx+Z~i%IG=*`vMDd9KfREW>>cw*@;xpDVx| zAUOlcg2C$5varWwHRf}f9=y)s4G9T4m{C;P$&}JJ5s4_v<55=^hn$*CR&iRF7YzL= ztP08O#nVkH=9;{0d=>*SDMQFVefQAhZ0C56iBq|h3f7`49F7Q#e-%;{X*7D_4#Y{} z4J0e|o}Eo@uD=V=d4N~C=hogT%wl)?+7ZQDO;yMNa{ib=GRgT;hrDZiOz@uz4_Z=i z5EPMMF|f^F$W&e3CMm|W;H}ARULZ}fIL8Yr%9Ezf%R3Y^zsuihri)WQK6?LD^E@Y| z>ta#Zc({yADuH`EUu(v`RxX08N-sfz&)$zWOjO9>>=HhTPTp^4rkSq6fYds=P<8!$ zw7_PlSAu;RC0NwaRG>oJElqQ}#-t(>x{p$0wtgT<5x~toJlY#0g|mJ@t(fWuJ40R z8QH;bilB{RweraNO4E#0=96c2R^7}$MR3KRvRY!|pHPHteUP86ujn-=GH*gUdg8zsnO)Y7#Rab1Vv_R~PZu5A;?mrLiJ?d1Lbu!*Z7h z37T;+_sQ)J@BV@6R;rf*$Hz$xeb~Tz{In2+y-|skaEkTA?ub81gW?XsTBW4-vT;qx zp-)+4$5;|`ayl{3H65O#@4+I{ZEj*1`j5NdT-b0Y>RbtJ&4#Cp7cQqvr}Jzkt&DHB zG#dm9w3X7T(w-*uYI=8RV|K}0S|e2LOb8rRUwyyfJK7q0hjS>C!YvbB@W4x985BE* z3){|<>UHVfS@*v#XrTI@?=3u-q!1&iaL0Lj92bb2r}W-|Pk&7|p4qj&FM-K~t!i^f zl@2}BuVt6FXDRmGw(+rh3)dD?*HjD&v3-)n_CQ=;7z09ttBP)K4}^0BJTjl#nB z>qzN|C6`$Q)I6!25OL*NSL)WYTn_Wb9rhA!1?C?|YiWWT@v)ye&1UoJUq?C~pCX%T z)I>M?*DH9JNdf$zUei1QYq}{Rdym7dq@wipKv%bD$HmU5%F5TW_-nM4| z%YKJ*y!|2x?X6@vN>!IRw&e@C=`k!#sNQt`F9Ps4_1J#3_UK-)LDyYI{yUs=>1~XR z4u2%rqz3r!EVmd?35O0)2@5wC+8AXP-Zd3|w$^Ycv>%9pydYF|1N7R#8qXT#xXRPh;(QU^gMxktb`#*I+!(ZE3*VR7TzMI! zTMQ)HC_H`VMVaV+y^PP9{f@#nna!a1oyRHMLU5zZiCLveqBoYFxG$8&zbVE54NKF0 zdorTK?`qt~u0LV8gVjWGN5^(y;ih&k0J6aFgVkVXW-6P>ZJyyvz;ZW*_kM_K`M10T z#B-ZX5fuIY7lJ?UXAHD*l5r)n7eQT{Np-fzwwJ&06-`DtJz9D&9)P3E8Mq3i)if^O z@$-0~w0o46BoOhO1p6P(=HA1vNVk?4^7ap(>Hh!iGkbcRpwc%|D}gvsMQADKfsf$Z z>9(`qZBH%ltcuVV7gy>)r6m%)^X7b%3bIrj%{1~qt6JqPi066x@qt~D1LjX1l9BWZ z2vi(3LN;ba7IPP^!u6Kh1fzv2F^g^fWWuoL-Z8GYX4X$RTpk_~vMF{{@jqE$h0@`? zaJs0>_U;UX_Gk5Pj&*0#{?;e87lN1YhVi)V1mP>mT+?}ly$MOId!>4?9LXd6sJx;k z=b59Gg!vNS4MaOxH}-}RxhG|R6%JhM_?7IBw}^Y6qLlQjV7d^~WRVI30k2~@Xq$3c zA3PzL$*JjUp>MpI&awVADtF9K?hPAUXryXAg9E&K)7PGnw0c(034gY=g=H8@{$jSc zSokEa7c*5FAlc%45>oD8@v{ck2Ezv#+o12YDtg=B8K;k822TQ+L-FbgGs7hqCevZtgG3508F+p7mXAp>ZgLe{#4{nT6kF zi(`W^wWT_MphULtX%erinv^=$IOuc)m{lr(g@W&}nI7jfUt+%Lzd~lnumC z0^xVzl&tCuwD4*RmlP@7k%NpC#R(C~TFdg)L4CuIpj8N@z2oMo+61g4Rl}4a#JnP- zp6_>g9Q=9^93@pYgrY38@Kg9RHf~=nRfWDn1AS7J0u#QA;1J96*#t~!1 z3D1UqY@Z@W3^mgLg-5zs&TA$Re04F9+)lnPx#;e7JiX(JVqo6}?fVU51rm5_ECy(t zP>75c6ruoJAuUn+?trG1-J`?nCcA07e7T(JcGI6~VcyqTtrf1Yv7$9!Vz+OzE*HmA zPD^h4TiR3gmJg10#*|i1@Oj=ly+JoBEJcE%3in0m_hlXne6pI%24I0ujL%R8McL=| zqB+u)x^q0@pA1CViTM;^8B8C+?K>LMQ_K{y?1|&9;aUN3yP*sF-7e^{>BdNY6m>Sb z!%+Z`Y%d(EV8v2ixCFquSGJ7~R;Ol7PJ?UR5&hD7*{@xm0>h?lu^JB@(vvJH;7%1q z2Qiv!G+#YN*>p0UCU8wiF@@d48#de#oU`x%QH+)^G+DNXB~S8&)5`&AHfLv}(;;OZ zVug0+F2z$2^&NqL7!e}6?0$GwrktQdGK0dW7kAYRe}qN-+qfE0ESolve=TisAmV2ex%h_Fh>=<51Tte3t$ zS2ZQZ!~{OKhx1(`IdW2yQ_|Kj=Tj*I*p%%^wyHv$`*{yePZ&*%TvDmNml6kLNr^!v zB!rlo(sz|PALp6|R=11yM)+Vk9L~&IYpC<_b8&3Z;BW-WP6iFx`1};wA%oD%#vM?E z=ATuIr2llRQ0CZmobc43QT`@jPXCyC6@)shpQYDVM(1A0<+Pb6GBtMFhTa{$?ni1> zhO;5hDKCU5?|6x~zT^AJyFYp`yFXsYAwfTNgy=+rYmfPk}Ixld_c}?&@I1ye{QjZO>rc^aN1@|jLdtF z?cGpjx#;m1H^W`NeC>>SNttBMK)2l~VfLwulil_H-f`o(;^)(O)4SW`4^0{AwpK?ccw{1;%LLGjU1QW>rRU;bXhhA7Ow93DS#3>j+(xSJg zU)mgAin;b7y@$Q64c{;cIZ`CRsw4mU9LD6ASPi{EuORF9wL zj~7#z)BH|&6hg!!G|OEX^Q?B@FSi@A(MoOE(NJkh2J4VY5(!SI`DV@Gx_A#H!42Pz=Iu@uZPgNHU5`UHi6NcybBH}08H}8`QZ$whYj5q5^sI?eJ z(VDzi%y=oC4SKsw{p^B4L~LHTeeo2-Sv}DdHG;lD$*lCcC5LE~1L1k<_jL_U|IvMF zg02gn{%2=kt)~}jYp-EzTqSGY5v)NM!H)y($8FI<7Q*E8UoGlCI%Ba?;F?*?w5Tv> z%hQv_8k8{v)=q;4qtdQIXGjL7CxSR*`N?d<6Trz@Fy&iyaKmMjs)vHR7GF`z^J%qD z-eF*KK{i#El*?u#QaZ?47->*qxxc`K5a^h$4^^u8O-7}%;>sTMxe`2r*3EeXURDwB zgYdec5Z=TJdzYWzi8k3A8YFa@bX4CDC3!)2S$kvDXKXH-ukt&cTe$7JC#fom>6}v- z6f0jfot|hbtwC_c%%-JTH%7)rt0)GujTELD&60`(#p&rQ^&muXD*12jA2d72smw_~ zWtWWORqt1CvcU(Hl$w;am-#yfi<@RM2A@FY*-vlWHF+t1blj(3>B;p$7jIrLFswm- zI)+a5TnQSC`pB&(x_`NjC9#B4pEjgKDJy*EbEyZKFKB305zhHOzd(~c@53MEG4+DB zLTKlgQy=mPZe%(-G-c2g^0^c5YZW`5lL*sk;n#WI3*VEQO5@nOyXm6^ddU)TOt-xWl{xz^3<7d9`CmTM?B?<}{MWDR?6>w^zB&*c;8EwA zethCi*$7(6uJ-Kgn)rlfBjz(+8|IR(dD2ae^$ZV|uNJXpio9LpzM!eWB1 z4vJ$E-f(HH$Hm2&haKUxy1R?Vz9!9*Nb%Sjn({4p&sS*8JR0H+eO8Vnvz)@`8a~&R z!s|#4W76#sL&G1EyKP@`aoxzSUhiE$5T_UjR2VBfc&5q>1sO@t5lmH*;W5Gs*rhtN zjOlDHpZVw4Ru{^@@p~dC!$6=k%rTI=^tf@I1Ug#paYyflP25hlc75;h40HxHq2c<2 zi_C2IxZ)x3J7U1^2m!wn&U}8S-;w;o?=*dBaI-n6#T(7n4qfaBHnD{?)a;Vvd{(8Z zsi+t=9sK*tz3X#pASl8Ygqc2GApi07vdlqlU{#VN7?;vs7JQUjc&kWvB%N{s;OV)k zA&hsN;ol!aLU4PY1m+?7#(%_N*7q3j_c2bTksr2TlP2Cshu(`uz2n!X-TGriq1^vB zX6hc?JRYcyK6SVu1`73~Kq1LjG17?+;?RLB2_XEO*60`^zdg0yHOXm|8|ZSh!Mr^Q z`$DhfBA`;-2n4O>(a3%Pw``xA!-c0U>Q&EuNGIh(tr{!w{gBsS7AMc%xp04Sh~o zP~F=m99-#T0Eiz(&HBTyB0(0E+zqF==m6LA^5D?rt!M~O8joYRY=LqJZp{(Q`DGjt z=Rxe&HnyMbbT^pOT>Mz%t4$Au5EAfABrxeqilGwfr?z@4cumbsUr=mMV3Hgk;JBV0 z1Y1F+-xe1e<$d_EoL0T^sJbtyliTGKpc4+o2AV!kjw~;Jlu@mqtTz0te!R8RSyHLv znc{*FtG?RDFMoa6o%=2#2n?RLWII6yfW19^BHMSxXsO+3z;hBwwd6e&O*7FNJUwkS z=P>627+YP1-$ispL0rK*c%@nIHDjCmBO1y=T?^j=hA8N^)^hjns7RX?fJb%DAq>^~ z$13g5>g@|B_{Ib>>?VMbNbhJp>kQ?Woo~MRRjy|*DeW}!%7C`cd zD?^kxXrpw^;inH)B(nil2GanNk(NfYRTjh5`KA%b9+tb$@>EKm)i>@*mY%vFEN@m? zLtSUGXkEv6kPlPM|=lL}AVOOuTI*@l2TC0k?Wq}I|b zV#!Cj{2}lU_m= zcMwpSWWPQvGwKLR$oF&Do?0B*tX_bd>;=}5Yn4-C_d8`ld(yjMRHRJw7WtjV0E(L%Djbh$P;ll3-iAHRCX{Kot8#huDb$0 zZLGmUBi2;4ZMtw^9}3j5ztIkj(~3N;+Vpu=&XI7S%d#n4B5^pWcab$Q%NkbjbHZ0s z24wfyY9~q;h-RCwe0(X*ck{mnuM=W?o~#%z;YX3VL>z_96{=EN7C&E>dJu?*7;x&y zK(CSe77Yz~S)-Ew{hoIHAt{&RL0sR?1S3kjKkNaWwwveie6ly?%vN^o2mQ5X4og>$?x=tw~e^ajQ>2O zJ5Di`3`{xDL@T8p1_}jx!;X*5&YZqcG=Xw~xN##)6j(!zJyEc4FC1~jQ}{If z-7Xo~T(2*KH8es7(*#5Pcpc{(RW%)oRGd&+WN?{)PONM|CCU>qdZ8P2U1&5Cv@Shi z+LdviXm?r}G2 zSfwIIi7B^ED{CO=sMZ{=+lveOTwhra)>=A_7T411^=!+(AqpS2&+=fRcV00d^TZ^h zM?pVW>{esDL)&9u$>(bT zF$TJv`?`tld)x4{^+_@7F%0fuJ_N>Tael1M9R?1Urv>4|Me3u$PRj}C6BS&}>Do>8 zv2u>R(e8u!O7?k%gHH~}wWDlSjy94~i`1{OI_-{U%$|N;(Vs{u-;JKJNIk+e-#ANV zStmH!z6f3JpF#DcgHWB|M9oCP_O}VWU_KWCEO*oeU*3>_)WH(#4k!DcZr27h9LE>@ zs{KEN<(66SWxg|k@8#ng?H**56C1VFqy2ER;48%hN&GN9)0i#OBEajU@y&F;F^{IH zpC-w=1r5zZ>)_(3m+B$pEb_%i$O71o776H$k_7pK&5{VKcsvU|tC>qU+vh!nfMtUW z5qzNrta_fGUQ7@_+<_EMn1<8Y>V7B@os3@ZMlc-MtFGMpuuJn8F?=|VISMr=FMIOB zsUxUCcWS%_flk%xrQ*V+FS@o8}QYzd{Hh0?q)k zYnLtHX|bzG$#zvZrG zC!!-kZPh~_-gN|Dd_XiKcsb=_u0oNTmX(X3=ly|1_FXpH4V$q- zL<_ye{)8K0ld+jBQbDWXuCM_BonDdkhRrB1{A_hOUIojy&n79Hg+X2salGSdI8?x; zQf|8g|7a^G5KkK^&5nq1oX1LIJ2dlM%{xy&c$7zhymmvlaY5=822<*f9|t2;B0SjH z(aTk1MVxg2QTPeaO8Vw_aT1f!6SZx#5<}kv1Xqr`c@&b*O;@rBCCNc=*u8lkMWd&yz_08#l*`A7Ad*8E#Gdo<@&k{CS>eb@A6xM9sFnyz?kXfi#-tv95qa>BP{ijG3gS=AKkRLGS3x&TrXEHBf=_VcYZtr zIJ(#IyJeqbay2;7sfrC47PpM%YC=K4^I4D?Xs`@G+ny!JYp+AKm zzf8`j<%T<+6PDdaqs@o9J*CvK_Ds9!JirP%N3*#{cki=JD(e0FyIwC(-t6A^qTkRq z9_I|=MikB1B>8)R0fAXBFX zNPYarsXr7T4 zpi2K*?Z5WXd{@Tp91)8BGFmQfjEpx$m@i^#4`>02M;aFNF&*kd;;Jo%Lo(Wma0aZo z*`6BCo||W(95KHO=dgk4+d2}BN=mhXtS-Nf3i_bcvES##o!cdUW^0K5xt6kbg}GN} z8`u&0Q~-ddv$~n}!(6$pj+W<%F?*FCn?+l`rTbg5;1T<0m)iWti@{&su(>$~f8m}j z)@1LBn@fc#CWGt2bZ@r{k&!0n(hp7M^NFnw?D&9!M}L7@)u&4PT|uf;ebOqOwfP(3 zbyaSeIzgnPNTr3_Vw(vd$miGh zhVAU2=Zc`paBlmeo8TkILkF6KlekmJR;?D^;wY_|g2de%$#7!NYeO^nLZ^&_kkA6q zFC%YH$n|8NSgNlM_@I@2G7tY$n)Z-T@+krv(ua~0_scNNEu;HCKW0=`IkUN6n{G_( z7jF4N9yuogj&Rof?T61qf~~SW9#{{fBOH^xl;Mkjpp4f!jkf#s2jZo)6(LO5#Y*Cb zO|(kc8Snj6Ti_uCEg)wN&6oVdV8t`CM%ZHf^I^G-5vF)_->GBS3TLpfSS1=wl2d8Mu=xIK%bUv%_(JY#YdU z4AD%B4A2`-D_`jyK0Jf9$NaWzwdOd4|Lb?(aKbU9w4`EdB`@0^BCYf#`Nr3n9i^ye z)WP@1u_YlKZjlD6n9jYe+8AY3!8s#<`3ZgRLgx$1m7xF66&Kh|W-OCFwUc8jb^u$N8kkOIpIe zTH&q`B7h6xs@GQ=czHPoB(BcDg5NWr*@BhNem@ZKZ~f{P*n36s4Dc-PCE_g~o+xvq z68JjNMs(R3+5+-Xhh8T3njysseYM$Ne0=6!F^=|e_6NGc)LI%hji=|$6r!E8hf+bDWN9PXfBp=si@r*P!1a7NvWD$xh+eNX!6--!?=Lhi z6u-DL)}?}VJuOm9v=mo>M1`-=$3J4RIefaoYM3hL=3sCM;BZpUm;(Z0XoYPFd@d;* z9YRg>TLKLr?*za44;#oLbV_FDlQ1MAB_1BWh$-*5=}NU}(cfgVy2=U(zue*fxuj|| z-dN`{21Y|G4N&BJBp;|eeD9+hZu#jbHtEylD;H#E^~<6^e3AHFLua|xbXbIIeI+YI zsZt1;izH~UIp(!Vk0QZHLyX2w04iZd`+PW~2XC$L9KCm^H5WiLJlZ?SNc+?QV7PR! ze0)cmHnD$)Z;Y#3ILOs;+epw?;q=uTsV_|dF;v#?0}SgzGemn8Cd>1rYz-niiGDWN za*JB8&V=i2WjS4)V}aG0($eYNu&&qaCBXZ8`|i>VS=UpA9&Fhrc?0$)uEBI+Zz)?k zm{!ZRJ zM5cjq(;Xq(g9Mno?~>f@I2r!*Y%oEkM6blP-accgGek}o8ES7nFCgX3!3E%UHmg%r z78-F!nrG*yvUzauYexktHb=?)S^cRuZwJqb249fFd0Ve$CXAOTERw%7{Jn?SDq&{U zeOyk-}7OkJ<(AR zRG~VZtW(#@rFnHF@hHG*mOR)k5!1$>OrVYTJeeG>ZJw%*S?~46fipI!NRIH1)@JQgg~AOjODQoo_QKtalVXEco9^kX#$(DnWbW^4-b)q z0E@F)3M1Js!fQWFM=x2f(5?A!;vOC{H4b;!s!30>YiQz7wiE?u-yPPkD-}5f9q+oo zYCAfvG}l(uoP%F9EyzhpptFX0IJ7vrI#);pb zdZWWdW{-AU!?Gpo%&g2bCgFa>^Vs9&NNHGe+_!89B^ZwKby+8_xI}2>kSx7 zVEh)!NWZPtIg~ez_-=E$z2A`A9LV;VcI)Wa?Ol~|7Pe28adi+)lWXFZ<(bclV&&u2BNbZNKFqpSc$4#xK&qtx)$Z(6MiHB91Xq zAZo1p(tf1jE4;cTe6ide0Hi_Q5-_hmLM7Uwn?@r_RClPdkKX^~_6incxWEGu+;1E} zK-#L(Mn)%Yeu?Ot#AGzE$wi0)bo_*k(%nMvt(6|NB(V09ALo-$NEvT^`3UT;-r8D>C^1J> zdIk}pXb*T!&L+vYvZB4-g=;4Mf0%pAs4Ca!UsO>*x{*%lk_IX1?rxF(}cbaySfyViZ#d!Oz9j=S#}s!6f#eyM_T#&(cPiXH<73bfvM;(vRGxpabglFf?(#X3Lk%n>f&xn0kPrd6E zK`eug#5AAx6BPH`K3hc|ZCYj?lRm-E`G?21(WZ`Ti<$$Qn-ZE8#uM1M-NZL5dC8n5 z6Tq=dBYkBx-pP=DKiUCMKtgOeTvv%nn)gf{hh!272zRq)uPx(CN99G~lP}5eh(Pe^ zOkkPBozpDw$#M^uq!Vy@hU~3?@<^uI$^t*;>ao1}!9gF5w3<{LOa0uXdAe$e(G4%4 zBmFTFV#*NddUX4McjV)mR!fCKJVBVgFdogGj_W;*{%+J`LJ>Q&i7LhsKr@9?l7Cmz zSXEAIldH#C|MR|)@z&i$n zSsL$m9T0ZbtW#!(Rv~T4bZ)cKB&puaq-=6Jw1vJw=VY7_LL5QNn`tFz4I4oq>J^B{ zp*ZNMCc|%=t8S#pnBEHO{c@9hWdH5y$Z$$`2#d)G0h#5KZpZy#`=r~J3nj!l5|Z~V zW}ZLXhHT!U9eSESuVKVW=H%GVaUBK=Ig%n}M$YZ-298;d3)Cw8_;@r1SdDIZa93tU zKQwO5$I`{nn}0d271cUKce3B|$}md$z$2BoM%9{rllqzQ#QJ5ND_~}{0=3;!%|^UP zPl5Hg2ejBv)A?xD3w4T5t=%C$Pvtw`#RQ-d(EfDPBFNAD%b?2QEMO#m+2M9YcC=Hc zeaR-^Y^6u5!==Fn+PKNXxZC|4KD1QUm9#7_Xz zH_UI~rgh53Rc;4<{MUebNAlsmO&zzk(qY3&tFeYlwZw|51#<5T*o4xxT*u9c2`sll zglv~RBUjR8Q{DY~!W*S-rP-~g#0kYS>8y42!}C8WTl#k9<;7fp%V4N>=rO>4bA z@{+Jx1Pt#O)|>=RJh-#k*>SDvPL5lN%@1Y^*{tW0=4OBruiK?4w^bXIG?i+z;!O?> zgbzYw<|-)Sn#=QBYm@(XqtrPkMp^`-alwpT{IToKpCc9 zngKJMG(ONRZMK#&=eTyDS)Ianb#!?#8N#P&)=RVidyQs^dd`U`EEb+y((I9l$>y-| z+pu?R5^iJak<$vatD0P^Q0(qBl^N`+MhXA&B;m907LK|<7~s9Gdp1B*gCWes>NIN>AhuSD4Bwc1R^pLrk9Ul}h} znOoFPyN0luGHA�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

aAPK?yFa7Mv?I;Ix* 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

+Gzs2VSeVq^ynOa=O|kOa-!1K)Y*i0ZN=WC z?$eId{naVGDT#|gw4U|ymr}nSCXd29;Ql;3d9nQuQTspl=d{+1#uH0f%4~`?bUz!9 z!w%r-e3)CGD-b8-#wQ7iB(bLxI;cQRFG?pHo62oDH6RBuNCDd%fr+ucO00%b@3c!D z2XXfV(U-pL3|}mWd`yS?lD!i|CaKoo1cKqydBw}~lhw2B`YYPjLy);t*{y!ft>~G) zf61!f{Z(1AZWSD%ePl0`9-N5jg$gJg$YpU_VbK=Tbt2bhsQ?60*ju2i zI_kB2%VV=ZUr2`9n^$YxIMotsU0gmH61m&N29nF<^-;^Vb%==1emIyqF*zDjJi1!$ zFlf7t-rjX;b9*_j`Yr44TlwM|ZLPQUq5M5&{pA5_F@o?V1#L2$&%f`-H!7BL*jbtK z1A*h=(SJwFar=eU)XFmDh1Y-AcOa;(02V{1fB^C0w;b~?Z%+Lg6pLHd$=bwt_FKn+ z2)5;*d#2#yh!a~))}l5-QxaVEv3~;)64gKjQmbf$`o9I#&kz3qbo?B&Ukz2xbDPx1 z@-yaQ-b8z#-C%!2d3!k!w$%^$eGlmXG~qxp3E1fVbLD#d(}y4bAUEg`_Ky^ngQhbt zY_>ALa{+S1?&j#E3)q9Fl@eFK|9fY!Jx7^7Is6tb{_-_E(XK0Fmsq3GPoi=Bu>bS0 zuOD^=BM;S!W2>k?9vn{4Er6_H&DYZKP_-ofBL8in9!`LLjm_=6@!+?O^Oq%kD-9Mp z5USJ1^v51RQwKpIB(U$+Z~q2I0W4c^rXgi3*gXID_xJZlGubn)zkEr|$%Su6Q!pqh zLsP|VIZf)R+eUBsqI;q-%~ZKyGxkXQVk5*N|NH-PX$v;%)v_!D7TVwc>aY7~CKeo* zo)fp6(mxIi+L!-xVE)g6`RfMx|3P#*@D`;KbbjTFO_c_q^Z_8508%-zCJ=1;#}+JA{}qC6l#PR@sq-}htHsqv2CGH1p93D7gB^GMot5eaI}Re&+u zTmnU)C{Ar@b7K$%e-Ho?X>uxC{qV=%L*LG5^MY3M8oP~O?*bMNPYwFtEPtVsqg49g z-99t|z=KTvcD((KF?Ld3n=j7Np!!SQUmeWZETNH!aq9p2q;nw`?+GjkzzFif4dyX| zF06vi`>%SYiw)@_=oMa8leRp%ag!U@s=1bKr4KjOF|6Q575Z;q7EJ*}b{xOH|1Ax9 zh*Nl-3k4(1*&aIs#*dW!134Jm)17+n3}oT-78BlkH_Y!$tL467U@8X1c|1UUO+5Pn zXdUxRj|ddF*Z8Ae;e<{-GwFJ4V{6zmJ=L)orcSV;a@xqgBoXBjBXr(LfG_M?c^E`o zR6C-yH_-%|u{-ahfr<&+{^DqRS_z;8D?dI3{K%#@AKUY-!k_^;))`vG+!VguKH#Q$ z%Wnw&zGA+%#8Lav8^d`& z4zg`o6ieso=9ZeXIeM*{`^ZVhc1~=jOV&lvI<*xM*{t4=AqB!$aa#+oOeOFM9kdt-Xns}}$32&|7Hk zk2=|zMUnja_>)#sXAU~c8ysaB-D>pwH&kpo)anfNfGYtL+Ijr(p3q}-t}nOkvV#Ni z8Ei$FCf_q7wPqgh`Dy?B{It3^?ZET@TiC)$zW)4pNjLx3&;S09&%dl``Dz~&Gea4@ z+a;#No;6JW_5v8ywVQ8|fm(ILT>zLLMi1b=D(u5Xud{FtW6810KjFNb^|j4!!x19S zl(G_gbAYVkqs>y6u{{$KG>=BIjJFy1;XT2d?`Ck`U*Q1bTu3rOLvOW%c?Qrls%ulR zgR`!8zhZ03Yo?~KSHSuA+`sQSs==5tH0#`tf<~BiU(|=Qie(L?T06O1YhFFz+*uA@ zr&t96guh4izdVbZ(dybpGUE}SckF%~uYl4<^8` z5=}TwL)G9TTSNBygugBLzcb(l_m`lCI^qco@)pG==Me_zXyouP3d(i|%5}khpQvll z?K@DuJOZ?2`?w1ZCi$=FCFhaT=HWHRO@z5E1@caF0C7qFq#R$v z0Dzbd*bPM>qzG>&O1K7(ia%(BH4W7Qvs!eVPxrGlKGg%>@rNE(vmgHGd+lqE=?mN2 z%_)4YT5&5OGx9>h-_E}9F}QZG0(%<49A5^R5>m}L$DC*XSUypucW@*}Dsz$(I6C ziWCk~i0f@PfWJHYZ~MkKT3+4EK9;gBHAcmirsT4ScgG#OHs&5(hM`z(b$k;-Q+=PJ5Z0!FdX5CifsOh7b=dh@CN=ipxn+`EVQ zy7kH0-qvWs4Kla?SdDccJ_@AyhqXX+zg~+*UZ<-qiZr>eqIai#fn_sAm7}du!C;GA zBJ6$K6&NlvN+}>OvWaE>Q*s^%KBNuh;)>opx1FU1PJVu|^=3dWI9(GhMkeuclZeUp zX^PVNh~3125PYoTu9Z|Ha2wRh zR)+_V?-9H*0u2b6)c zs53%yeRMEG&gf{Xwu4sW{Cx4-dsU{kUSi7w^Zw*gtpt&Ca8^=7ULq%)0HrpRqo!Lq z1+tXIjKc&#tI2~)axoY1YVvbGPhq+RlXpS>=ea}p-C-LYPWgz3iYw^Xz5o7HAQ^mk z4pQ-}yUTAczeEp}y77~^)<3ZFeSh^p;z$PqJAXB%n4a1?_pKkr=TtuUvVL8MJJn}h zEkAkMamD{^BX zcKZ)FwWtPQb;}oEYWaN|z%W5)UVQrfw|7OssMO2}?KII7kyn9a+12rZj^?9a zv~sN9ObsP7N~!fytnbk~Gd%2cj$JUUjF@ z5;WH;>y^LUSl2Hx04Z4WPA>3u>&7S11cj)l{8$A#Jydi~hovQ(_{Zbct2ZR6EFS3n;&MD_dESgnzB%Br-PvDDfp>YnI2yt|L zT17Q}r=5plet6g20GT~X;sDl{uwNhf76usdBD8T+uLCMbU7AsUGDFVXb)JX+?-JyH z&B|j<8&uvD1>o@c4!Rs}%U$?l*&BHw{I~PbVh^__qCd#SOAnijrh_R7h|Td0pc636 z2vV-M){7~1DnvvE?}F^yhEFl|6;Zu3>~etwxLf)wroWak9?Vc19Sm~4IE?@bp42$8 z{uBxB=rV3xqDM_2(;fs@=jzA-83E+Qt&Wgq_{~xerl&08be5z)BLsMQVZ`TG>0oL> zXC0kf#7niS`r6Wh{#bwBC+*TCF6YN=p;cx zpp(i|IIsA1;k8UvqU&g`TQ6|5*F8Pw&6Er&UF@g|rI8?t2u~gWgK<|8i%j9qq&`S~ zd*n6IJC7Q?(v=lPz%NVQ5z4O?RXh2@1*a$0cGIE4<^s6yFP45|b-ODWwQzOClWI;J zIsc9%ww}sKPT-4e5Z+_yS0v932h1;63X~T4^)}CbVsoxmO6)p*t%nQjP|=?RJ0Kp- zEXhDO7uzD>c#mZ>we_F(905n9t%1Px(FGLQRnaFbu={>AC{)R;3g!-Go5jLc#acc?GH}C-OT@E#S%aMnf zAKMLZ$vLoZjgM3KVj>N@8JV*Ft@qbK%Co@v&H3!NcgPHg&UhIaJCN66Ig|NeZ=MDo zgF`JzQZ3wC_h3p6g9)BBh(7%Rd7p0CWVJ1^3Y(g05YttcW?AgoIl?2d3od7yE|*fA zA;ejB%m>2t*d)nu2G1#-*2^$LZ9)#qiMkh0 z&H3$<6%9J0OXfrDJi+7}->ZvW{krFk!{5*Bwk6$jH8Z*tA;^VJ3H)X<6r0Ofi8x{+ z8m-zZ*o=0S`!;2%<&?CA4H%gm{#af90BS&We%IrpSOAXit8UaMJiV>46Et@)SB3{u zUk`)%oO`*vTuoPCvT@3@CZGO_*(RBhc|Y&DUG<+2Zb5v!-0Vqjw#v|<(sEBq%Ulhz0b78eJ5Dn{gCpvx+g(n}QB_&lYbn*i zW(srik7obSgkze3=XR@;@VVvyITV*8#SiTs8&uT}y!%8q>rvc~bV9XVQoX{9{QB^YMCx^+L^3kEAQFlZib z5Ftwmm&-RH!WUJJVU`8*q&CxrpGTS0!05whgWZ$7fIbMP5Sb#hYgWTbYSBmz))r2Z&URZEiv-j+bsXHq@ zgN{Y3R5ETU^m|2pv>DECtr2h0K>CGgP5N@fJ1@j2k$x$RTC5LluxYN}9TTD4+Oxgr zGzo&7{6xhIF{(dR{$`e8Hqm*oPZh~=cY$;_3AA;L8Sh>N)WI+ri}tQ;KehleQ^xJ| zcI_B<C){y>^-`0JWQA{0>X!EWVO9NlIBKmcs|?*8x8#9M7p zkFB%5d;Tq_hNwM4>TVD41eL=7&}C&*T}S=-O}#T`Wltr%oL+2A87r?Kqmb+P^5yM? zijEBp+|TCyJ8G3xy&z{QlyKyo%2Z`R)VxEnep}|S({$tP3L&E+mPWeS;JFTz$^%Zt zH!=r4v^I)7Z_Agq^?>K>!BLktqpdx7>d=0!Q=DQRrh|?%4GwN>JzLIq`9ha7mu_K+ zGy1qYW{B*x=F_BTYv7&3s8kWMrBaSI|2|Dpql6n;qipY7P_u6Ts$!Q_0YWutfLhofbKYMd{>0z+|;a#4>cRIH?QnQuY)T2=Z*mO6)u zS|T0ak#f4(N|j%qzed=0tS@zb71M5^ITvosd>yt><2M)l$m?{UOFiH4$CRUd@2MpG zJJ0fU{a~`#5#=4i%hMf^!=_!4xFPA2#l4L%$NQo}?DJlKVC5MtHi37EpUYelD$-ZE zq!H?}eip}TiYvZ!IBOl3uUYstCIHnV+;bbV!%;Ol<6>i~fC`fUJK4y$f9_GryAMYc zFsrvieUD0j{f@bk{kb1n6|zZ4AePr!5w=`<)DjEb>+2+DlRo`{-Ofxf%Ii*C(j=&m zR3{rDNqh8s(*|k1*+d&56`2Y~oMg)!Qx-1&dX}wImg_t%PwXEGY*7ZC6|$sBjy(f@ zK6_6ewl_EtQV!i}VL5&FLSE(j4tSp)Rzhr?ou)S(4HsQeyu~#VyCQ-ePnEUEfaQ-o z?Q62w<>+8<&pp+T4mvs|uKm=dEWui}M;>@jWSEzE&)D;9;anwFhi3L?u9g)Ji~f9h z6tnaFLYY4-5wEBwuq|4#iB7hr+05H+O^unvgoJDonmh*%~^HLtD*tNbJ1<}h6fEwU_*tLKa zD3R?b5**+Ud;wZOGvn_f*Vi9?c{#}=@g@E*sP(^J&SZkm7Ua*{r$Z(Qd2-8)yv;$M zUMVvqm*C8FFr7nh_N9dC@MI*)5U=RNaMUzv7@StdXz}Y48%p0SUgma@*;rnK8%Q?X z^ByLcXD$YLt_vuKE*O8n5%-#bn~Zs@tg?dl73i^kX{wr^SdhHww0px74@0G_F{!b< zV5+VCvevn84vM5t?XzLS{j$`tC_sNx4(o$+sx-qg!_`lm+3qd3Gliu0ViD?lVN7(( z-7)H_V~YmGItO&0=~u>{Ty>S6SClu+pQ(nVww^no181gb# z3NXyH)xC!!JkD2Mhh?dcbD2cg`e8*{&V+GeGMinbfR0XDS zZ&?$OkV@J!cTt5GXUQb*An&im7BQ|3+(Wm9`ft5K#?(@y|cuX;L(e6$hVLBU=^ zsej_pU8+)283?tC&q*y`{pBfCb0%!s?3U#FV7U=d%UYAKw1ut9lqM<$HE*_qMWspx z#>^4+ShrnukwEhprW4cyh?Lx9BAgA#TrH(Yt|a1>Q7^H|EUw51*|hzSb(*l6CBsvJ zA#DfO5_Z&%i1cQaoodtWbp_2j=gGX7hxxD4@kKVh&S2wIic5!Vu>qS_4IOmyMiD9cO zqRjrj{l-Wf?3v5wY*60siz+W2otGvFlpXuw;ZTiMsW4?p?TkM>jycQIRI7Bmq!TDm znz~ZAPGE~aGNgg?|HvaQ+F`Kqw0zmzrg zLY~|19R#TD&&;}=(2`hKiA|W|(@9Z}??HMlrCq?J;54bPuO=yj_lb@^o!_~Sd-dYD zVM=f>@0p|*{+wqDMq|XF(}_1O&0=XjW{<6A-QjdCjA>e<0h=HSb-8KoGJPNYk0y`5 z0_m)pPC@WL-AH$yHVXu$71?NU8IH3MVLlIPtnzlz!c$vEIgUK0Hw9XSa( z=>MI}2e2z(Qg#GXShO|nwwtS5l$Ec8zBGUTnPz=_h`W17q+oUJQj$)3o=vsX?R?wi zPN}T9W*RJbJs7+0^SE~!<6-a+0|Rv9m(6KarY!uSb)TtvDSy6F7TzX7grU<6&apox zJoj#0>G~V8?a4~^Zi3nI5YW+S?@lxe!P@TuOxl{?OlkZJGJ$0){gkC{X4Puem&;I5 zdu>V+|Cv2tX2gu(VfPYQix&9WE^{1kZzD@|(XG~Nz#XeNz7iPbo7@V^2%^aNdS|aw zCU2^y`SEd9UaEOcvVd=&)Mwkah%V@vC-p+FB@J3Y5-1Ix*R*4+#@fMbK<77~;Qgup&=Mu1H&fu}Gxw#EVI@_;2> zrR3R%ZX@*_F9z(NUKr2bw>&n#v$#ALEcx!F^=n@&(u+D)llM3eg*+yq?yaXy1#QAq z@>=CyI_f6;ku)jqv%=Q`S_40eS)U+x5(6B!R;Ipk@mOxwKvlD-QKrssv|WbL$&}@| z_Ch~6X=QYvNY`AgCgVXxaAQWWwDPK*>Jo;11&?7Qqs)(cH|LG=6Rtf-qZpKiOcoHE z4D%^9zI{85UJI$E7kjsmzHdt`X~$~ad`X=W_#7!YN0jB4?JG&$>de#^1mmcjco;8t zjLpH&2>J+CWqEA^BFpI+d3V)ak3qrRDl0bJ`~(iu$SOBV^xpN7WG8&zX-||QNbAoF zwy-fcvyFJd<`g(ak|>)8mXxt-h#f;CSo*FTzu(eqPSadtQ7+^n zbuzcTHjoN7R%IAm<=XP|^-zW>ahN}H?d7v%t*fO*s5#l@F7GM3Of|)qzYDQEo&j2t zy9J7tMGVAOJ|%`{oj@aXR@K$JT=_pB$4jiz#W~B$Ga@S+=6hqP%%ezmpba02#^ht#R}Dr~9~SbW4f*6RU&9 z8{t{X<&6d5j4uR8lDP_QmX_t+lWBYW!UWos%rsC|X%RjhQ-X>FFvwx&cvsU^w~ zy@%BD?o!?e-W3X>;nq@qYg+xx+y3j9r%b@aM!H&EU7Key7}6o13pG+jk3`51kA9UR8|NcOg4TuvvkYyD`)+dq?44&T??@gV95q6T2kpm*I ztK3fqP1HQ~ua(CMOgqAy9_IRh-PteVGXRFe7!X^0 z&K}UJ97i-@S6i(_<$Tn1v1&l@1$8NXEbh6z9IRXOip0x2!i_DK6V4YPc{P$>c%697 zxUTLk8W6gt^&)z;>&7!3cDx=Swj;{|@nV+~k41O6P)w)I&CfdK2*=K8G4rL}Unzes zAC8|!lV?1}e0Ve;_E!_ZFtm8uCVqdLv z%my-0MqApi3{-y`t}~_ju0TmN=L5QT7Jo?pEn~yg_=EGgxEAs`o{MjOvz7Q%3V5}Y zKY6wOUcu9U;MK07MI8pzap-W41et0<`dpf1|5E*>jnU@)h2#lE5nxgtT7ga7rWh;M}|qpEeoo#$gYG0 zSqg}((o$C%EsN%xE;YlI2Pd}MLX3M-))Xuv7C&Z(_~txuup04~?@ zEi&*Ab-ebcDHU{2?!ipG%3|C*eK3u?_ZEjLptdo5mhlK3ej9nMQBV-{5yo;uxDlIg z$RN&b!5xGw@6AKo1th$X`8Beo*flJGB{KP3&h(4){E#ZqzwziNi~DB!`$kLer;=8e z1x*t4mOChfORe7>mszuQ@^fQrUwS01>k-gY@5n3P_B%b}6j>zsMqa`?i3; zX*j>g-O3gp^YJX@6C12tnV~v$c8xNtM~h&@p96)^MqgEuv4G8Y?!X;NY0TQ1mDqYn zoS?n`t$2ZyZTHVF{Pcc?z+*XVBXiNT|AcafCm%fGftdEc&z?>#9KJL*taC&9e7ytu zqMzDq)j&%FSXy|Q6S&=PnoJgYT?=E3ypTbv_Sohi^4zkmH+6PKY{J69)DG*fkB6pP zec#`9C*H_Uw0(pc=;&vv*V%$O=T2tu$<8!K!$q^noq?y|D3%GkoCh3VZpa1%IzVcg zxy;ZAHI10vG~c&qpy-IYEa!k2|1oyIrp}a`Y2T;|Z7!5_Z%UYBOESXP>ODlrBH!PiVZE;I6UnYiOob>k~7A z#6ZpnKz2gLnnVs0P>z~f12e8oHMGFfg9{ho!erQf%v^1T>aTBMuH!(IcNDRsXY7Ied2Sv}h*~AesN-c1rzXjWfwYB%5kZ$MBR|~*r#Gm}2UFAG zu-V@T|1WglEbsRCl~Pu+J~lD8JY6O!)jbWq!2+UyBf-Bkw~psRiM@ZL7eE4M3Nj~6 zC2Jdl81^V%0`$InHy-bsv?^qe@}< zaWin3Er|hyJ+iwV2_RX8`2L0=cYVg+xYl+7bHN* z3V9kO(5Aa4gM~DK@d9J|(9@rOi;k{?L2=5-W+atn;b?r=R05y1)Y~=2{lH~{P9r`r z19Myakb9>$ViO=J!MlJ^7)sXWCuQ3bdBs5u5W&G;hP6nBi2pa|c`)|IPXn&u`XbrcGdcM3!CB$AwqTQ1hqbBVM;I ze!`yv10rFti`;>W1m5j>g;c$xtp#?6TuwY*OMj=3WcM91G}bAsRDFi8ohOKyb9-xd zxe%VDcCH>jZY+ZKIH{g$n0Yl?-@kRSRNMJ~vi?UrHLdOcm-1G=@$0#8&uQftH31I$(S$p* zz_vT_!}X9ShWVEEw}@xBx39LUx5+gi9D${cQabK^-)Dzdcv5qeqg2cI4cis>%!cHn zIZiNoXKL^)HXr_oYVuAvX?QVL4TwMnDI$9XhjNhS znTK!DMm*a0r89;cj36>j9;z5yn7s0*e68$M0cjZf&;2|1!$r7G$cHkpWym=td+v8% zwCd}r(gHF~Y`8NbLdxvUaY2A71FYsY0rzF_8c{5IkMEIv_Jro~+epCZsd!P5%VP!Q zt*Zc7FKp%>T?2J_>={+XIRVblVCx64~6iRi(msCC>b^r~NisCcOj6Y5D4 zr|7m&r&4Ov**i@=sa#`fozMyC72lM(93C?qY)(a}vZ_;OnBPf*zBBrW>?C4NJ?w_J zo12O(+V~tVgJHrykLc>y$QXEfFLK`0@SITaJgn}3c$Grf7o>NCD}i0U+TA)-gVzx+ zWiKD)f79&xS&?C23fn|D_A6I{1<0;tmN`VZ9$5|W2yWchPt!)ib+HyUd z#z-*_{%x0{k%0oXsil8d)B5@jjVCMB?V%tX@d3CL>$S%FBT^i4^ml$7Fww#VV0SyG z*_DwA8Q}!ty_?aeq})I1h7&yTztl|?aNql)NcV;9NS}#5tC$Rnb8zdM+5`>bgXQ)F zISVh5BueW`h(LMg_R~fDVc!VP93n8ah9gT&O_hyuh5bv7#vK*Dr4ucc^VZwDPD5)A zHyeVtA=#aP?U0dL>diP!MqKO^Ug;_b0JfBX?U#f(xxhgBYkZPhi{EFuJ_Te-H{aXe zpVi}Y{uS(CJ}ur!%<`3G)Z!wnLp7Vem3Ia%q1?0RiQ;|j2KAMipz92JwiJl*g_7@k z0^qP2a>Dr)t<^1*mm!jb`(V!fSAa!_t0@Q9fe928{0kEBk%BOl45-r0AL{5dwr|Va zia+d`-;mKPO|~9P2bO*c!-6(Wg(Yan_@kq5<7OwCF8;VQK3bPRJbSvhoG_fYeGEM5 z3(-a-@FOC5$=eA-u>-iVaUs=Nz-M&1trJ zcJhb4VQf^JybjWn3t|f0H8XOM_EmgpjyP74yJ6*+xw@5-cO6bhs~=NZDb!)td~`?v za5dx24~uUR4r`(j*{Uhq%?CekT3POf;MQk(0dV_8g~lQLTft&cNn=E7`FSb4=rUZp zXWl*8jjDUWDdNudR9)K3xx6Rj)Ju(;HTH?xqpW*p?SmTwVp@>1X6RC*>$HFz+1q7f zSf^+#u$KpB>;hR>p!eGq`+R2$B@T>p7syc!HMjiilI`%BWF7UOE;-I!d7>KRuGEGq zdFo-#2UNo9w)zD!zpT3Fdl5rBIOR|^aaylbLZZ0Rbvg26u zf6s<0!h?gLjcf`9Fb0<{XBOY zx+f_Q7&G1dmk)RqdAfO|Cl*u8)ix^1LvrC9 zh2+aP?^qSmw9#=8>+5f=&9u)B3yq2tm0ws4miRThYOQnn4=n5> z(O+65Kq%iDq#Z7xB0p?u0DI#G29<{Z38;mmEyCm~9t;jS%+r)XT+clv%F|(4Hh3NS zBlSw4*rguEVfE{&ecVpXb&=17o4$mqV20@`T`YFTaj`BB^L$)GBD!9traxJ}Fu5c3 zw0)ETBAR>~3B9SCL2feIGarKdYQHW`8Lz-qWIs;}g~;bK83^g>mI|!*CkoXXIKioc z6z*ys;TTjNBI{P9x~O1%J=93aW7_*(bY0ur=QEO1=XWry<6;R z=O$P3EYO9(d#s%AH9|Ac3;+Aquk$sJYnL|cCj4i5bC1_(7;o7MpCPvY-G9i0~d|HM`6$rTC+RBt!nSQmVpj+e%W<5}8y6BE-qh^A)OtF#I$CG)U;<;>S3EdbFjYssQ1Q znkW&I%AQOVGH45b9Y-}n!$icA9x779@^PJ0P%!I5T$OU6Gbd*SY<7Z|QM2OvYmcyc z+iEPTbmp<5P);Q*KPrC5#?`w;w_j_B27aq?-xsy3s5$a%{(MrL)+|k1m zQe$*TPSP=aHv`H`Wf}|yC%Bd8n>cBzvoS*|vAvv+A}#s*4m~$b01U&q?i}OYP+ew9 zH;qW#6bU0@G3uD8v{41Lm5O0nU}=b%lXixr6>Mz+8ct`a6T@rp9E>s6I*bnHwe-9- z^gWAMh4cRD6n2dSwD<#8rEJ9fAZ!mh-!Xljj&6zqdHT#IlUpW%r~myk-(KG!Qkmhk zp|UsWagD?4y=PZ4S|s~K1bZUx2p+-i8e@FA_aZp9Me*o(@(&Ep6|3&bRip9h9x?3cWsqFXGnqxN5 z8cOA3SSS-lTcW5;en`R|$#=N2)fZXdzw>UYE82kP0@Mx!Qpz5!IDDQ!YJ{ieatVxn z48$xhhuE!t?Jgax?{DnlpFYzOklYHLgQFhk2)e&6N3l==g(5mu$Ugwk?@eYI?tmnD;(_N2uJa1V{rMe5brSC_xe>(Lfs)Y=8c>#CKTJnp!J-5}7ZnFLQUxoB4 zF&z7oj?pl6eSa;*rJL*51G@BNzCLL%(sl_7PxMOk@ z&KAq)NGSlbY=?f`Vc?IflD$Ftd*kwiFwmbp6?lEE|HQ2qDcXDLdz*sda$+Af?RA2z zDyE~Ib_sZshc`UG6Mw&ss)BCgEH)4L)lT<|@SuR?GX~Q(r^`@&X-3`ub-%~=r04sK zzkNcS&p6i%Rjcv;McP|HRk^Kg!zu=XB8`HigtQCQzjx)!;pe=hev=ilcX_V<48`NnXMfiSkZo;ja6@B6;$|3$Ve`t*AKo>uicA;3)K z3`$^)Bzyt?(nc!A`w+MO2kcJnSa^|zJz`;U=Ork~!+K_?Fnr+Wd)c^QO> z_S4IbeRAtJn3L*~BX&I^(2tThUq2F!bRkI<3%QV<(V~EOd;AsELtZPh0#v^_z%u_2 z+GRmctg%Yx`|Noz6S!jqKorCzqYA60o}$;go(R)=oP{CXCd(wdC(F}W6f($E&6V2R z3>_>6y+O3Cd0F#i0A2voRJFAAd}~?tW29!}-=1$dcylYL^uC|`o`J{zFK6JTzh&+p zQJ)mr>|&dOLaTd0^C*)!GF$b>vdt65hkv%+oAO{b_SB^(ekj=*hYlbU+@w#o==+Kc zB-MB=6Ngb|XKOD(XqSN8jodDl|CrH{y+fKLsX;l=fN)tm@(q27S-I+_X zn{5=Tb~}Ax&=da^SWF87$MhllP#%Yk*9~H1t(>{2gRlG=>~JWU0>tws_;_&FG+o*4{gqei=Z(oC27_azMaYB?I_BCPP_sipBb`YbGlp z+f3cD>?*D_A-?t~D;MW?BA)5oSQRiExd#Moiy!u;Yd$LGYfu56Z+3OTeWDRx_#0I~ z?jvAM>^eU?-Bt-dS;ncdSxR2);QM8yE|mma<_vq{rD46=z(FHitKL-%n3APn5Kkxo zraQxgnUQC-x^cd?c|$^#jCP&#Yw#3Pfx#4}wwY@Xp#4Pw7HGk=SNCq82EFqZ-wTK_ zM5rAqS(B?r{vY7#wHw~ZrFxcmgZShBNkjWk1Z;7=c%M*boqsBUYwjV`CS}@!8~VS! z#(D2u$F+}GF9j$wo%O|nc!zJpjx%d+Y^5DGpxUXuBp zY2m962Jx3R%iJG7gVwX04@}GPbKxiC!Qfw(JIGsXF>M-fhxGVVz z`1h5Rizje60DN-1B!t_Xe{ZEZB;XRyeA7^VtG>q^4$9w(^X)<}6pKFEiinaO2DIiG3 zi|kZ>dnz#lYtSEYK*P+@nPMf|TfGkiOtCzWdcr9nL?kRT8Jt)Pj$_-!zJ6q2^T?-( z=*4@o+yD?{5DkPYl(6mb5(PyxW}Tw?I`a7Vos;0}=uJJK>jBP5$s6Mysl&OWooE}i$?wNmbzRZj zj;0}N4v|>-i3#jx7$!-;*xcHJFo167f#Sk)cb)M}EtdrR*(onD2h3X3R35yY zS?&CMjaiE~gVTCGmP0NRkYNPN%v?HGf%SDK9GEEH1C;oB#}%?St_1$-=loBJ+9PP` zHLGkQ$9gASl2IFs@K4>J(D%Y2;V2tGoTwZ-dAaW+`3EaY zT?Dus7Q$Q!K)A^5uu)ho^{C2)H@8TJCBC zq2Tp%AkGHf2zV1U5YnP22gAA1@44r4%{ZMc2EKj8-HT%0rYIl^v#OVla~P;g5*s66 zzIY9cg+{9jfx{^|g(U@WuO>|ab_B*wbk7`j-Vk#3DZB(5OA=^5BXryl-EpI|z%4tc z(}fG1nUTdty<)(yEfd5vsFs4mmktCsq~M-!PZ+pCt}DG}nLREs3R#@WuJ!F<@a~D| zu#*My>=bJu8+iX^xJVK> z2Qz5bi%vlVI_%&c2qTK(UJ1u-68O3(6ULnDqGJl%zeH#Jk=??{jLav8Sn#X$UL2oO zx-s)d!TZZ?jR;wEgi8+%dP-N>YzkNPiY|oHsAW>QxA`+x2bZ{>9aeku9v{*m$92yy9@K< zlUKp0Df>tA-vpfYM03>2yj7ou3RPx6`v{(U!WTPe2+&I{p-~x9U-jD;%-a0)Y`_WV z;%${?sS}n&-mEh4Xu3I#Cg>rHR2QteBRn%oW=h2SUaI>!!qjQxhR5YOM^TrPpx`ws zshBsxcjRT)rgZpQzgaR?(OINJi zVi@c0etsqc+{8}aN0mkXkXhz!Llk+^#KRn7m}?T^NIU(K2!wCmftFTFe#m(aQbX4I zQc7x}%}v62<(ZvoJRZgt!Tb%uyqUbfTG=BV(DnUpQAVNL;-b&A>O$-J@GBkbA3G_( zuXABw!d<=DXMU!QEF;H&k_Uihg1-I~@96U$Xr2y(V>rVr+&p=czn0m2(u2-JMH zOVex`Ak4A)XV;sQqkH4I842D6CUSmdHM~g5)4lZDrv(qjO0(vWdL38i;_d zv(I;q(6*?{S%uC{m!hdl9e3s9c&ny>0m`E89S3m^0o8VXr@fmXn&`8Wa^>#qEE!A1 zSA(yip>dx)xC8b;{esnV7O(>3qNPS574ayB23t8vRFZ~-JPxHgnIC|Gp*&EZuJk0l z;VJ+YGhv%5Is0ip@-DzJn%0xRBM;6-#{Av@s^j|cVuQG;><{QWZf#BL*HIAuRSd5| znMiIH495>z#pbqK5d}tm(W40)Tw zvrp*ByjMs^cR!3Hrx?pb_w)kxmR1TJNBF!-rThH(7|F>^i>j` z`ZkG^kk5q>aFyg&ynX%Ol25^JX(+DE)#6Bdm)I?JEVM0ZmC+TUye_yv$&>o2&us{>!8`88^T zxZu1-!M%;?xEBT5mK9d~jnE{C45dMC``P zGg~(h&{Gs8VAP{Q8$XII`oRkhRyYG(h~YLxBe0T2C*T2$pnRlkZcPdr0a@@TmPv9J zXtY&@fgln2o_MaZa-aJ|O!4z~I**%uAhf{5R*mEiIFBWUbH3gjT}Hpn>49{~%#e18 z!?@@qPwL8f0&1Rb1pU$AX7$|Ux!+{3G&17!X{@!vY?P$MzOD}PmeDXMZ}=0%?%}4O zL^yR`)FzNdK;2YS1AGX8Ejgcbtp3#X6Hur%P0u9s#Ged;{NT^+s4%A#k+q@KC5mfi z=#P$d2U{#UHYXv)&YEyhv(bx~smE^aX^42%=sORZS}@Q|C_(R15nz=2>H6q+i4m>k z%njDfvyKFY+CRS>cpk9*(0#Jl&R&3XZ0-+5^v*FHaN zmIbu>FS;mD{^pjub~9BNgx6=iIo+|}!1F7P8E#H1u)C+CyKdMW+XtLN?)5+sSicf@ zLexr)slrvi%>aiSf6(Ar;gJ?p1(&>0XC_lQFh7vwnjX!P3wrThHPheBXz7MU_uZuF zE*=nO1`K7Rmp}#cWNMwK%gT6#nEWT>)7G;M!+PCT>^aILDKnK@O-);XC^&9#>E`B^ zrCOvb{A|lS+UjC!`b~~TRgPT_>~LyIW7UVW6MgqwN3CIIUvpxLR$EiW?||##aFagr zdxA>9cC9H(a{z%Xl@X)DO{C9b5FtCc1HkH#E*Q~p{HKA&-VaD=J&#HZc5!He(TrS9 zJ=ZkIWfNWPF3xVrc@UPF4$GJ8T!a+NZPrDr2G)TRXrkfr+EPz~WFd!t=i3(0uR4j? zJCH)l&3U{$J^O$wZ7aq72Vb}6`oqB+nydW6Vybd+P3<+)r#F7Cb-Jw*rnTU-lQ>wf z>AHn81^bA?$4=mwuP0rbKfx*hvQ6bU+eRbN@tT^1%f!eLHlVQ&V>&{%_c%1}nd2p} z_V>&MUlF?V{E~F|Vv1!kB_XZS_B~;+(J)v<0>T#fwbxjK!9Wp@pwKXt6UNA(t#rhfF$BS1> zN}r9w9EN@{jewzqoLo)M28jpIHlo#Hk+mgqNh(Xk{jf3X-60I{w*pdsi_?-`{t`sW z;E26buI$AJv(3Vayh$N)yMs;xXKMab!|p`s>S_8rBxaj0*`=W>X0~|~Zt&%xDR#kD z^Ptad#1tA61==-W3{I0#0HAGVoUi2(O7`8o&LFYsUGI5@W%xGH#Sby!FszCP&alK@alKUSqkR%AXp>!r0A59<#att@Ti{kej;L{Z)hu#nYAB z>9R>bLM<5w%FG}|r;U`wWjjCAh$DZ_Un*t~bgg77v}Frk4_~kBIUH^;o%Bo1B(zLsLX}VtJiX>)+*V}EKfjXSryMV05dUJxx+VY}C@Dq;H3^uhC1soIu6!;=%l+9~Nf1J0b z0&7O#+4o?u3;k!^-&lnz$azTuLxLPb4Jr@R*dY00xeTK2h($lC20|Mj`E+S>xn<_v zBI93$I&2olRspq&P2C1a0{&oKqS(yEv`f~{5WM@Vwt!YyEx?i7+JyDf48gX?V=vLg zOc5{cfH)Qh^%}3kG3vQ$b%ehu?10BX-f6A}yPrj;MO^J!Tfo@e$+Il2OpG80vMgTW_D#(B@9@{h^_fp#`&B}Upq@H;C{{3MZbx(7yq>>fa zrFp6$+4lx!c|jNRdXA@C(<=eY^@YaI>Fx#rp9VmKB+7}xw|K06wE6s70)e`wGQ(T> z8kpkpE*wS+t1)LL_2T;`Cvs=7^e&EKxkFFXlRHkqkHwT)mCKjgPAj36(uNIy1-gpc zyTKX|l8GdY0GZstMz+FI65|$vzpAP-6c{SB^tKxmsd^E(dO}2(xR%7Qylj?+97imt zVhkD8D4ClDh9dAuk9!li(lpDXFw2-#tK@&W9d6Qa6m{ouI|kPKJjl&@Y>TXwl$Rvb z2=7ohAG)?sT<3_kDen2gZkDZbKnnhq=gop?uBwZhTE;snep=7RF>cRY}gMRtgFppI4-W?52TsYyyxi zsAWFl)0t+ydIgDmWB00G%Qmwt_;0EKH0Qt`g<3AZ9>b^C*Rt{iPVlAU;zGp}?rQj` zT!phY_{e;sG!saaJDoQ!dTv0drk33ct?EpbIKCEKayail1cgb6^xWCu=47@+8vJ!-2=2%=POWOSL=fpkd5A(3z_29~$o5rb5(bS%F*}lc~zP`rXso zkt_lp;@6Ku1gZxtc28A=bog!OJqv~{FM%Nx0PCoI++4iiwYGw5Lm6T>MjrQexBc3U zc?$FpC-#e62L}h~*i@b3fuIy98w;E7PxEO?7l`}?WaVX!Z4W(sZ1&$49c+aW*sYLy z!ZGGV4F0n^#|pfZ?W3D`ly%trz|Lx0OT+|U7cPu?ZhgL{L>J0e!~fdb%`2H- zC6RZ(ZJQ=LUmfrBm_WlA6Fh8;PD#LW*mOAcR<81+pPMV^KYP|T)k~Ah=#^FXWvL2L z6I=JO?!rJ*Ds6!FwftlQDYhWyQD)bHKaPa$Z*9KmjM~zMefwh2*?0K)4#F!x&J${4``IVMu&WF@46z+#BnHWbuDZ(i%PqzW9LLOLT%(#h&H zs99K6z~=Z}Wp(F!gRSap$k9R?Yyq80tf2_d7SWwMprezsKlo-DnA(1b$=TCLoh8b$%Ygem=KSqPsqw*6?zu3A5)_5RvJ} z-x0U}l4JYJz;7*iqk1-n1#eIr3c2CnmgUs<)y1aGR=o^GrvAl{-T4BK-i z-73oCv7;O%9H!ma;!~^B<*LIoXzPs;0tH+@hY+(?ZE{0WL#T;C0PdF?rY@fFBa!WX z%Lca8drfcCwLT|hi~G$ZrHg3TO$S9!dDE5Sp&p=~aFyHb?@f?6i6(%#JOJL= z^uUDwXp5Ykj8OA!AQf;FH9#X6y16IP7MT7bfyac@$8x4pD52G{4zd+-Fvi~C(ooO( z{esJ;VU>WsaHT;ZBym4-%z=Y*Cu)jV2R7p2anDbj6I!D8Dwf^Aequ| z5Od9V|GQ zPFLrT&VE~vXji0N<9e&id?X_Ln-zc8sCHLjnTL6cTgCf1;jag$tpO2yMlxot32O|Mq=;a;wZN`rs`qm|~*bzL!ca;yrE`Q6QgAXbueT zQTyWt;<-u~CzS4jJ|zVN4h-lN#pQE@&Puq>g{T3Z%gYavY~I>VjAq99jKAi8w&U%@ zT2-l-e6CB=2l8z0!j7`NUF!l4LH0b-)YtoruSqpaS4`!r!;wXyYQ#I6DP=E^UHFR6 z*%7rTYSa+6BcEO$EjI4vHrsg7L{%T5LD>k+=tXOeM;s6VM+Ozo&$VvBa762a3X2m3 zFhx^RY2x#EI>+mw5Q0Nfzaa^HO^v@D=XH#v+&nx)gQ4Tm(LuC~C(GG^{0rgsLwS;p zFXJwZ*v*~TNhT>^x(tV!Z4*(b)2x<5?w&LR$YR7ex=w@J*)B)7(jEm`JHW$>dLJ7_wu|1J--&~_6 zis^0zHhyo;XRqRJcMGz#k;hA13(UJI+3{A%Mn&|abq6b9N=0_lbV_VKE)L(d>)oHE zcfYgwC^uRH(-23sD$uHzIz)vvn}rbEU&QGdcm0JIOluDfdS1&dht-pNC@iw|=5XJ{ zV2EVytnfOD@qQ>dn^QT#IYJ$M_+B?&@Dq^S7`aPIrV zN*iEW^1X%V5a4k8Fm1b-rQw@8!6C2?1r1?+1!9Sy>R1>$b6PEMU`}!Gig^t3mBO_B zefupl_LY*AoH0!{mqQI!bKs@ZbWmSTe%VH+km+%0U@P_pK*zvLViR?9lhIug>$ce8V-{)7?G^a?ift^l^ldb}8Z^jC zpA2st&V64HhC+p38IuU=2JOF zKIL@k>vQ8xf4t&+Xqj2dD2qTuD3`~NPo+*398|9phiZK_cR?`-9BKpsFpvrLQV>lZ zGu+KTCi{8gQln`c;aN~nyZme}W}seu6t&=z+zSrzL_bYT;AziuzAq6=@pU6ki%q+u zI|x5t%beU_<%UUqqs4(hwQ>*D6kA^t`(;&uzEP>@|0rEwVcgFpUc(<0)L@21@5Xz zN7AMfE#0D*Ne~{aiCn~?gRpRS;V(q1||ub z*VvUS4TZYPFwD<-@q!ku3szcUb-ul80l6h2B>Hz9mo6iicj)~b@}a?Y+aU|Yrjenz z3H=GBbkqm@4lxfTHPcP zd4m{U*e@`zTZJxBw93*jDDs^{s}mc7YR=aSk5=(ohCrC94hm=f^iPKj#!NG%=P4-97g z2<~cBT8%Ub8vf2M+mbB~<^y*VS78}yW8ZbnMNl|`>|a{^XTPFVKtjg-VOOK4->RDf z1RQ;C)Ds*RI*{fTuT520dJyuvQc__zXnT2i5eB*`J~D&FP{%{|PllmS;C1lCA!wI-^WVd{efHNeUKh)t>O$cFL7qy*y|Yt3fJgCc#T_)=k8*NP&_+rhz)w z7SKu`#4DhafjFARI^B5M7a?w;^o%#(dz2ux}g+)U4 zr)TdvXzII)eG|pt(y^tX!F{})zuOM~2=D86P(R22`dZS#1{ebX6~Ur~%y?TET00&D1MeO5oB~7K5e)e25B}NePDPk$)v~^s$S4ZclW737Rs)i%Au0*H^Qq}+Ux5}%~9^Vv4%RFR(1-d-&I0>d)1@-KafG|N}p^L4H#*g4vlG|ne` z>2_PtD%)sWTac6oL}=U7de$l8RG=rO)p&YU*&rhYHp>wwmnF15{mGpN{XkV|oQJ7V zvNJrv;yj>lnvTPz0>Tn(4GNaFEb{!L4o_f73bCao+0ecee5MtRr^UjCc{(K@Kx?Vz z6VexwF4-}$VYAKhjx3Tv^JR5KiA$H4K(GAZ?Ss{|rLM(|rW3Bg426or%|+Fm(QFZ0 zuSOmKOc?4*^{1Sl4nAbL<2v+RMb>@i`I72o5`&Y?8ki&)bQ>a4emK*fDyT4N2{U?_ zNOhXjEr$po=N#E~RW<|C?sJ_=5o%L3`MTD(n8#*`(T2jvt#zI0G%P8e#2cx&^@crB z!kOSIK2qQ8jO=iM$;`VfB_CV2j_l?-V!S}jyxj4GbF{pIpJ{u5glA|aOShUnBBfP1 zj*buDt)q|^bG*GzdTB@n6mvw^TOMfrpRjcR{a?7f3A}jm-cQuh4@v`KzOZ%fC>Lmv zPjxavh?e}Ff4o4C1EImvAgn93c^-1zJ|4*8}IS4A0e$&0S^Djf92MY_dR+n%k2b3t&OZ)0YFlD(-mdD|JvJX zqr)3iz9muy2 zT1giyRn`KJo!qP36%!TmGlPLUZ@(`VbqPzoz{1ZUkqV1j9UD7S%T?fVo> zLOQ+uX=-1YnrNMY2qQwmm9yFz792$qDX8!Dt2|$)z{)+sN{*D@IC(_Tg|?9B?kV@f!1*{XnAL5bjYWJ64oK^( z3$W$Sei=yVI*&L)J{SO+6{MG9tKGIUh<1**gr9oK_16RDqCpYJuAZk_UvjVI;J3Ss z>Xo5LpI0|t>0mnk)5ESKVMSutXOz!RM=x1ByM|6rjABddvM;{Od7n)0%Wu$;J(%0a z1~%DNT~BbXXhM-i8JPnZ8u1(cf;dw2Bl$%>Ff3tT_c39+1Re-eEVum^t6+|E3mx;) zv-@FQU3@NwmWfJHTw9*NY2ZrS^y0Fg2|*Z;8{9_8empoG5s`t4_&=<9Au8f z@||k?*fND833xV($+$n&ak$_Q$IqvyIyBg~R=GqgV;(5WZT`yN0uWPKC4ZyvPB3vz z-{EEyH8q$Rrhp`9)#+B{G7w~rlcPDA;1B>M-fshuGoEQq=x$%gH`doB0aqkKW*rd- zf?xUo(e{>4+EDV3hSXChc&(?NBaWNgBlrE46ssYQ^YUjPZ`Jg&N2)zFnMr47Hl?X! zW?x!8`aRZSJ@E2x3MIkDmI6qB3)vb6t|NiYk5h1$f{>dp_~ZQi{QK(#-S=)dF%5m| zU}m$P=PVMW%ysbC)Aa=AXWzHjX6O1}`F6Cmw<6h#m}UQzW>U;vR{+zz*iArP5O-BEO~vQ-L$V zJFRs;kEX&ze0(+McpNsFZXLLW7~sy^Zo%-k>lBx#Q}5RYs%Ss}^>|5PXDCngmM=RBFSPZyIhG=N%}dt@yu2H5`qw8P^Esm4C*l;O_5xh88{-4t z!fu>?67%n?UuZk%R=uBl%0kym%aNu14C~=xU!k}+MAeE>U77XtP1DIBz{+jJ0@{lX zNjQCZTPW~dFdU|t&$>BmXi$|dY~G*(;g7PI^;Pto-(c+0Z#anK10RJPexDY8O%viI zAuM(7{dj#wL90dLh+$-o^5%l=TmT3gxJve(*0r4x>LReRFyN>XS5B}aopDP-?G9Z{P7u0 zhsmdF`FqnE8U$|LxuXE+VP1M}7&Wjr&=D{i_H8hNVKrF0?zR#j7&0+H1s0dDRY&c4 z`MUM|%ku#}7%|G(x#b(1A`z>R2k*W2S$@1+IvGA1?T78}F1*sc-o-M18ci@oDH){! zb@iNU=chlcb)l%u8om+LaH_`WEqY~jn@RJ#xu&Qit8~nkXH(#mG{PcH9Jb!<#LLmF zVx7j!-$0@iRO=(z49n3IHy_kFT$}8=sKUF}-_7Lsc zkm>yKgV+!d)YU)pjEqR>xYKQ`Kd-OSbL+M9nU+%2)Au zazq5}O>a1rsTnl`Y_0^C{co3UkzYSq-1;)QZ{qEeVO?U#=Deqm0vUe5pWBLZxHU2h zE}79m^kL0hrL-5sJaQe|UGnb)uN~kVmB*UReEcwdHhkWRjungk>KI8{xP_9K_Oiq1 z^){!++q1Eh#2^jbev!g4iLVJ4PUJx+pK!O(Nhe=eq~2yXYv1_}tSBy+gA6!RJybm7 zaL{4HKaMV9%_kLcjjr5K$ZwJ%2LRHTZWMBDKexZ1+q_rIx_^UgcH?I1_B6^|Z3)uV z?20q+5F2njwdAoP)Js6(hvjieiXsRzp_QGfwcF|yNJmvRNNlhUXRXE2%ViTF-^-tu z4^HHFO8w&7S|Ks@oyuQud~eAl4>PLdlq;aLp)0D}l*^46;{hT6r|Mdqs&#mTSuHimwK1We2R)l=AFXbzLpHwcMY=~&sRqV# zNaCYRTIF1V*GV&$~cD9@L3ZIwLZN1(t zLh0A=DYwN;C3#!HL=EH3-CZ2YDvqLdb>_abM*xcqh-N<*8;k`;TRY0U-k&-vY4rx`MB+p#}zU= zcTC8SLwpO;dTB!%I**`|J{|!*s;?hDcS2jj-@eAN`#z4e_Lyp8#?1z=$;JNrM6DN{ zlKm5^c-a1${aW8VyO|0^GUSkgwA%K0zdsZNVfd zh5{UYv2oO*H9@iLk-O@;SVBdvP&L%_vL?fic`iqCs}Kr(e3oct$zWMyK;ms{_ zd7vS#&u=}#u>+Tuf92cRdfh?l@vIqX*N)gS#XK4FTml!|!_b!)yWduEXw(){OA3>PXP`M?3r#g5TXfYkaPkxKq0rC9s8)s$6%kg&!=Anzp*HiE;vbn zvNVpI=M}*D69;D-$<`+F@0&ww-$F|@V!o(UuEIavw3kZaNZRvnePr-U^pZCNnCQsO zi@wgX+OjNlF$y@+qEndha<*R+eeN~LPulHydg=M-xnlUl*X_%x%hMzc&ZeWolSaYj zo*QaH^dF;whz2{UB=s?#L)~|hBjv?!T(PXb@7V6&cWepifBF79M4XCL>RxW={z9I( z&P-Dc=LOoCcaEA@x|kcj-2-FaoYL$~V=v!?ArzDwo@6h?{qqKm+QqX6(~B_8Cx^%G z(Cq_u723{GW@z#|i7+}UgRZd8{Fl6kuA;hF@Xch*YCK1U9&K-_N3@#W_hJ=X?4(6@ zy4CGkV3?5%3Q+?_oynTZp{nn8j(7EJfICIg>2h}*2QEd+?8nq?Fbha4C^6t*VCo|b z^1jB?rOoXjbhryR(dl~b`b^Ee5-IUqOTp+&c=Q9_nl%*PFX|H&W~^tuFYOVm))WYd z4cDDV#*EEYhHglV!icy#LD(6 zNqneim&dH(F%h2W=WD2(JRetFj#?6-ya{f>{Q_5<&sGrtqzc9sbpuna5Q%rgU()y0 z7hV=W$SlxE0SrF3p$pfnfCrn6i(um;*t!is5Mb0S`7VgS_Vrp^e8t0BV$($mb@(HIPZ;u(3T=LgNGmm=JgX zaGJzuC<|SR&VveCu$zr(65P6iW9l`-J4c&AHj*dx^n#n!4t_T0mo{Pr*@Iaw zK8K~3$9L)8eB1GG${H#&SKs4vjls`X6C1Z(4%}A-(`jksysB5A&1XiuS>)Cge;1O< zNOdz+l9N<&M}x$mnge!5Tp$-4OGcRuhT zKkwy5Yv5tva)bJExa@rU4=y4Vqs>u|Bbml_@Nr`V_v>6{IYW5vUD1tS(1Q(hS@4VU z=T$A=Uq9Zq9}+yitIx)PgM4?mxBk~v6Y|~pI}Xq9?-zN3zk)?CHBFXsZR|LqOglIT ztqi;}@d?O}Ko$=<1R3d9EMOxNJeZV3Te{+Be`QsYMS(dTzoC@fzMm*DQA5dLSCL%G z3)BxEN^pGZArCy^J^a9g?TR%TlLX7@@;c@56+inc8>R)MeFDvbU+{ldYtTkqRfqg# zqkmLIa^quI`ul0H?cbmEOfa{-^qK$##wqD@YHoyRYH$$r?dj53)*GUmf{o*MMWHo!nw*T1T=pMav5z*WfUu<2FcdMF%J=E zW823d=5r3jAl^gj)dx7Bt|Ce~iWxu@{))35&@%>O+09nRiWcnFM;Phe%<=#=K4m1M z`im(*Em{YPc_269NZ{P%S?Wy+aX(I})pSEUY6|=13aU2ywIQ$h7C(`tPB+;yvr+#- zFQ7vi*ju7s1^Bn!<%Js%@DqVNsZUc=Iemcaj;(WEVF~rGhJid=6uZL>uD$)og4qh0 zeuX3ivnP{4>3MRJkkZ&F*cnMr05sJ8?q|DgR_jSXhi?I`$FcZ&yxWkxx76jM?)@BE z>m)J>-0y&!a(Kh#eKgcc+I821;Y(wn!(W`A$F?B)@k59W@U5+75&qTJaj|XUfP$a) z1SI={8s_Z$ZUia6D_H@^Xq~h=1;ovhry|~gb5V0r0s;cDT(*GX1knndP68@vsnvn> zbif*w_rcg~&butC+?*&2aCHst0}2VC9%})UXbV8BaE{=2^R_z8Wd-aCQUQ0-8i&oW zBL)Gpvp|&`#jr0lvMI3-j(%;&6Z1F>03H!7NXt&G4>&&kIR7)qtR#_dnx!cj> z$e=P>i2AB`Sg;hRfdeg3(rz1;8){wOV+C6X7jjsED^IK=~jFXT2D;TGJRPhUJ-@)a7Fgf^9lS= zxUj!k*;?Onw|bsZCfH_Z)+e;NPVtV%J34fl)$!eDz%oHTh;Z0^`PGZh+n0VgG_O?i z?AN2%^nR9Fbp;`_y7zYq-%_)&i8EXiH)p@pv(oEF^a^lf05nT|69>vH9eZZVE=$F| z7timA`4W##F#}LQa+epmfC5SvtG6E2>(r&%ey((L~;?sybfPa_(rW};XApv z{Bo(H0MP3OGAm1}Cl2pmX}YawO+jwxZinMWtofIt<>oHrJ&7RGQwNY1-4Od}SWjMd z>`nE8O8)Lgm)jGCI%(8_SyooQs&-bAl|Q5LJeErLmOsXG*$x6$n(s-`pgS1)`I-qt_P|{&_Z}ks z9<&%ccfqOj8ge$2t$-fA$B7SyJ$RIZi2dDUon~*<#az$O(0Anf7ZNXlY>Ep1**+#Z zrF=g~v|9ZcL^RLeDJUKHLavpuhxGF#a|0U|x?)sk{Z48yAjY;&Uk6%^G{&j13d@S_ z6PraY1IW32dgqsdZ5AiDM3tG>@8+6X_q4iz{nYtx+iQQ&lHQ`1d6#(tQsO&`c_9rK z6RO~os}sqV7)l#&X)x#7H@h6E=DmYZc)}ghgoUm+rmbwi1!QUv??P&<4TevWypx(|!#X>}IK# z-M97<6Ge>sl7F!FLNC#P$;w-$!JhwLXYX0`A3FP0i{9WN;1f{1>RF)K@R(=^yFfLb5EbZ8(XgCw9svvm6^~Y4v`gVSKFap^0%Kc( zfQLI`9zC3=maoZ)>Jt;VPr&~k3{F7uID;ZC+sQhYkU;ggkX)4l`NNGW5EQ<}W;%pA zOuSk9SKa+|f(D-QJ!GI-j#;lV-4#tT~hZp}3dT;0aIR zGmK_bm#KE2PI7fUL{EXxiq!KL136jI=_eDGJ2mOiOy+97$;>!f$0D}qXw_8s$}y@T zBz(f0OOVm(qfu=SIk417=GNl!GM$6q*texs6ri~V5G4&kw107xK^H!{!-2ur)q^qHx?02paqnF|Gqx-1Nv^+LJLb35UTQt6q@N|nK7n590w@( z(KaC3ppNK01;Tmn zeMj>(Ww$urWXdqAl^8~nGpG9gQ%6scquXxu^ZNDcE$G5yb!Tyw^()+i5w2ahufJJKkxqDteSp!6co%%5 zulIQ+TQu18j4=)lHaH`c@1MQ_bjgYIX4~va)TTy2kZ-z~jDOXg-q=PBfP=Qg>l&n2 z6s&*U6Y+td?~r*x{-PR3Da^RpJIG|b7*8=n%7oj|DoJdegv8C0=j7qni?v~GeBn7^ zFaH5hS(%Si3>SbKQZAZ_`pC?-%?AG`P>#u=8M+*-2|am*F^Xu-Utw7E*jx0A`uPb) zuhOG1-|~7TGl{S-N6D;|7o)OfG(D0&nrMKtX&#X(CfR;b@U}$0a+@ZMX$*_2M;=*EoN%PIN{8x0P&D$b4~o;YTOC&jkAJzJuc$G!7kM7 z6QcMUjNm?ktTwqiXZtcX7Zk7suf;m@6?oUhnM(myiBewK7w} zPU_cRRQJt&TR(XNS^MZ{v_! zw);+x{nl(d?N7Ka_6I8;e@hYJ%Ld`vfE3@I)bJse{Z#tppDOs4a2i>(S7-CAm$N_W z=85$PeWWlihqlnm5&4Pqs4zU1_$#Z2U&vXn%uVYnu9Uy_0=|D@bAtL}sSJcbT2zM_ z$q|b3n9upzf+}7me_^V*SNrTJDTQj{vQQdUUCH(lZ?j`^|~ZozW0-%WX~kllXW46rUFn38^to_V$L< zpXL|hKVa@F(sKcRQlCrdg-;{IIFMFfAj^J7}@^Rg9-9Q>+%X-(?YL zc@l7clS$BGYAK}dS*{+;kVYomdyNK0RXA+Y{m72Ohc&h>W3iWri0BT>;?LAL`tkYM99ok1=Tes=hu!x|p*jcKAfSETOK~j?=CmKxyiUv^%l7P)?hvcjHmYme+jQt(g|`n2W-5 z8=9NCe+2D)YM~L{Tq;pI%)Df$o%WI)RC5V*`3aHemFzUG4$Rv_+4S8BJlav9oe8um z*k))>Nr8AitVqU1DTzDr$CLC7cV*4f#6v}|kp;kO^e2-)WjdIFE|XO84K5rJbSn9D z32Rk+fVzj6^Vt)2^@tfMfRZumY(*60n{9r7$VCwk)bSp?mcZ1R*9ZHLVB1ng{bc1wHP*l3RQM!>y=N@zE^DY-_t>@jx@qIt{9}iHR z-1ikD&+{BYFR{wsKSIdHXcii`FqntuM}%FI6Xf+ce1=agm#CVpsdGJ1muQI% zSRe=3ToDom*c`#)+k~|&jRLHb?oM|uFA5?oh8%IQvExDi;-L>KV8c86^h|mh*A6EB zhhA(d8^@44OMKbGZ``tqY%!jt1GCz$p!5d4u!JD86;|~ zJ0OF;?}x}nZ^uBeNL^4>X*}=ay~mV}@2r;m1Jp*-Y6}Dk9Gsn$jObj6CpbN@sFX z#ns||AhVBf-neA(tqpkjH%c>S8Z~ulUtT5O;qT;-Klx9GrG^JA!%8&rneIPSU;n2H z2Vc~PQ-ik=zeAahJH$vJ;I>*A*EtfbEJw%RZ6aQxuuGGF3>j!VG+Az2S75>}& zV=uwuGI#Z}Ry!e6Cci5xY`(?V0zto`@{3Oy?HBtsn26uqbryF4s-*T2AQVGr?>W|P z^TgoEwRzss7Zy_b?057=LHQ+;X>x|gh|AIO!O?{O7)FW5m)Y|UA|7(+7$fi3=h#J+ z#Y(##lPpgK*Y|#UwR#DZ_j2&RriP1w_L?jP&O7Zoiyn=<2ecff?GnaAwjnb+*AK)qPx=q1H=euVncb^_!yoo0$W3JB!YH;_C+lIE7QS6zs8lq4d7k-BR4>tXEJgSHjPx#6b zS`x=gNWcG$ussT>R9=r5bEKvw5P%WA_V-Nc%!sv+9VkSO$Dwv+46oB#(gl<{(&vYQ z*ML#+R5q61GSDhhFO;#Oqcx_YJ`8`bHW3fhAMWx-#_>J=nxG@APlB`@ah5pARi}a8 z?8c;+^ufA)RK%MwO0+CGRS2BN9TW*y@~lwG-;?tx_#6vN`}(%t`PvBq{!UsY zv%KMn=+TA5G^`m)0q5%@D?LTv=JmH6RH^~-Dmn30hRS^TFvE8Tc!D)O%cq845iy1- zmfM@PR8UW_8#fc|(51SW-?l(XRgP0RJnXu9(%f%mdWwgF5a_n-qS=}og<=%@uEW4O z?)D=DRf-F32ohS!pza6t5X2)82;~G((t&C4MK{Qnc1EH4@{@KuYamm*0c65ERn8lE zptZ8%Wi!)y1NTSu|Bu#LL|HV!<6wiN2tdTJ_B5DI+p8xTH1d;rPGX)uO@NyPRWbT6 zb31EO$*@-KfQz2{>^+vQkQnzez%^b>N9*8WM?$i7WSm@1PN>;XaSQQ=X7M8#U0pkX z{Zb~|Rf?U|d8Vu0lCbggX=Vsua&@o`fQ$9@c0c0Te0MtS=N;<8<~y%klNh=9wA-R) z#6(0y9{5(9?J(TD|4H;)>FAxi8dPH-NkHoU8}W0y%H>K3Or%q3nbu%^lVyDfNv3kL zlmoBp_UlJ3^WBo&IXYTVeR*LY`EWow`EKhRjI#aw>}hZB8;vA4iEp5WPCF@J+~I9a zaE@#>CEuvYOX(RPok^dmiIrY|!u_8+@nmJ60SP1TF;Eo^uig2wXV#-nG0y+PeLHcI9Ll~9H}U_8 zJoA^IhufAidCvYY6*codTW)j2-x?Lsj3w;vZ1>Or-fi7uR ziD#rE*vw`3h$G6;9fR=BRL0+9#Q@lQJ4lMYsILsg*OTLzT+k_0%hr_LnD613)bhE0 z7kHrs5^-Y4Ml<)teuYnN)tN=pwK82ksa7Lvsb@&pytH(U++lOP36hI!$}3*A1%FvF z>q<$Ezk}K2qP)>JSSBBzU_0}&*g~tT&`c?dC{M!2?9v38wL0M$plnLERR1hsPn_lb2W=_JlHjA45Ib9;_*`( zBU8GE4ilepOcmhXON~VWu9ynM0PBwduIIg|Sw< z?Bpq!n&p67cIFqitg4AMR{h<=w5raVe!IzfX7u$?(>253fRQdcsE z@cF6^)t?uPi0~^v!2f+3;Ek0om|Be$@(|&*o0gmZLmTPVNBJ>LQPzAlZc1XuIB=tl z=GHDCsgnrKO4FS8Q{m~empb1NWu_^oqu63ppVl&JUR`J{G3)X}slQed*2M6MWZ<5Q zfyh!0CB13!#APK3s^LRDy3&NL{-DS1yt#Oz&R9km;e;g4(5(tA#biCVI<}oe&-H$NwetppQCsZ2lx)cH7V#B^0>JI& z6uNNvEHJj`=nO%IqtF&wWTSP#Px%Md zvZzwnOy#>R2L8b76~WhJqeZb0LhQMBf03Nn+?EDalq-rQMa>o3neIc70m~C5y);(M z0w=IO>jnM8OugaM$%d}_+w3N?s95pg2A9Pk({Q@XRSk35ZtZ|C8`@qk3l_PT78a;S zIzbf+@XlvLEH)PU#7hTV9xi3nK>gu$%zTT#+uk`Wo1BUZrh1jz*Jx!TrC(bKv9jC+ z#mJPV@Lc)5O$)Qe5J@jrVk9KdfwBBfw;_f8$#ly8nd2^+wlNQ1lig>I0%8cNj?ous zlmnfB^LCn&tz}{NY2U}4((*Zxr5r3tsmLwlQXF0?G}yRsck;UjDB6^J90{tV>Dy|Q z^c;aCu}=*4UBv`Z@9SVH`_o5bsey6Zm!-kzD3HeTiui*4^Ul;Z{fjBO>M(8wV)@}x zFV_PBg3y^lk0Z}d2Og!aDQIXpX5Y6_Ja4eYNfRDVDx}iHm@B(#K;o_lF;Zry#cvW} z=!(*KO;fl7HKJ}ijBk}!pRxZ_yz!I=%wvl;)w%zr{g8*cq;UB@&=LR{jE0C=TN_8M z!eN04{fm%1Xdov21Iy>zxot=%A#5giI8^+csg8L(oFy)iMlwh<+ayy@yVR^}x={b) zRgIPJUkYvq#444xVJdi{i5bN)sO&0W?UiU2Ha!^q`ZlV)H=8cpOht`I`GJn#*5ZH; z)?-XuwF7R3q4@}%;i{^wzzUaB4fCDJ1&Z8O+15kFOJ-kvHQ3CDie+vmEi=>tQ6HZf z8Pm{Yp5t5at{OzOh-E6>_QKRpv{@dJ$;J%Q7=xab_q(_?#m_DL+1{IsYL1D}Dr(?d z=*grOMYfz-VgbcMVrK0};kXA-BNjbS%|HA@Ym_b+f1v%n#^mM_{)4^)FyS4QPPj=! zTcC_ws2JzIOP?$iIyiszEaEXrnRmHhFGR@WipHxQ=)1{fw%y(o^_H$UnJBstVX5ZR63o15f>L{-6SLp-Ja0fkC^S3qbWgwON-+^H~ZEZQbf>}I&}BG zXv?yLk60C`^Wgv*lSuVJuO{*RE9JaTa)8f}gI%jq&g*f;(sk+S(-V!+JW-2YQBjPL z@k_X~I>jfEpofFp!3@9dMU z9@y?}-pIQnhNdFM_iAgo14oQrf9$Hyb&j`E|4{JQAU$3n*?=WOY8MOw2jZ<0U# z5J(I&rT~>F=En!S4h#wjzI4MZ&_bbeH5SPN^;Y_|zLL3R-8kUMWXc8kq12JyZ~vI+ z0!mt%Ow^{At0g1NBpt2vcJdV+v0tIAyu#A(FBv9cHBYD@V=FkiXOD@!xj-yCIyXDp zVW6q`BZRzz9aoQpNl1BrYS{L*S0_v|TrQ9OtFKv?-WqHqOsP6sCDO|P%w<0#xs1k71 zDin05h;8t8GSeKH^+sP7Ta7*V7Mp|f#Kpx211HqTertK@e0dj(M!qt0xBNye->a7+ zfoRTd5&jKt_gEixo35?SGFE(UyFHMfJol|u+;&+-sD&gh&0bJ^il?Vv2AD3V ztqs-;bscWR)G5qJ?N2I^MeKavKnnd+0MSoUhNzahetox?dViHc z9dV{xH6+0oo&t4HPp$UCq%%l&26*sWIwDFOo;+DU|6q-^GkgE=fbabB-4AYij#ox= zEHim-2tZ@Eb50PnP(d_Gkh26_h7Sgw-REW8Dvpn3^HeO6`j|&(7&vJ(nKSi*snc(- zPw(0jw?H$!c77TC4-vlj!J-i}5K7uL{6X>V%J7la#m@D>*P{Zv`GL5=*Wl5;4%p@2 zyIX&J-HHOO%*0$?|0mqf?{zLg7)diDgtIB3*G&<49oH0WinP1CNs=kIR&nm!0<=$G z7qqbKw+S6#T|vs?;Nk}7%sV07@0oQbaj+P(Tgp{K8H|x^i3O|U$Dfn~oHz^JMQTIs z3P%~XDi0yw{*wY~IaW+$bwamG7Rx80sx=D(2%y7HvHRVJWieyo%**ZAGgw&oe)}S} z;U|f+kOHK!>bhfAqpUS!N2h7!Vzrw!RNZ<#c=sn849e_211w?+2}Nek##n)h5zx9s zVFPH0Kaf$o!BpWmx|~6a9aI zqyFvU|85HMUI^P`&lq3}p83he8>c3CvbWjeg5U)(AL;eTe*m0$`rm~+;O|92@{b9~ zZ<)pn4S>GvT5+8*{@8kaR-m27jY4t{(Hc@8i%UGdRR)aQvJ8J-xhM&I!Phc zaoT!cjGj|i*;$+yhY~zGRn;3DMN#25&))0DdtaCn6BXlqc0mMR?9IBaXdpwCq%VU2c>$O$FiZZ#mU3wYE059y(ePE|0pAoJ2<>q_P&gSzY>&yppP;W|K;} zj;LR^93!>u-pE&v3AFf1H)}w0a=!#a`Pd#lRsB~I+PJwOs zIi9CEMysUZJ>Fh@!1*|-lt&r~Ij-Y^6)neYa*|r?FOP#SO!e?2+Dzi6 zq`Y}s+}@r1RHtU&otg%vAkI7G=p2Xgq>uWosvEjq;xvRdg&U3i2o4fd@-ltZo9gnI zRM7f8zgjgyI+90exojrZbV&Z>@2~pXn`5cKFS)Z(&+X6nL?oeg=*MGv<+`aAcX`T_ z=mcTHt2qMbt=F>HJv*)*d%JO+r!`J1oYT)=9`PymXzOJYIwCg zk`te`?6K)tjztx)ivH~J(iLmLZ!u?mFCKrdHZ=5B7e{Y}IZz0l9P>Eh zuvn?u(v^(v-??)kreS~8^SR=x$IGd_!t^Rj7}gPG%g@x@-Qzad8m!i7(s~_QYiomd zb6=Z{?+YF68|?NBbxst^ZMv-$>h|?a^qib=?lh2&yp!q=U|wpA@rKRVPhoipc~g&gKu>~7!VOIX{Mx7s^$iE zwuK!RKNj9s@Yt5YGh;(3a^$$5IE`Y;IO0h+{5o#qZ*c-d{%S^uH!Zlm4V`HJkJ3YU^- zUK>T(m#^+ssA;|1i1Q=AHpgaYoe{+^hC{EBQm7>S_$2|Yw4;NLg4M+MT$dl!hGh(I zL)v+2GB^GZ{xY%(>yPxM?i=}2iX~D;;^YGCHoIzT4<_!iIeaH!bMKzXH&{@u+x<>^ zuQ6z2L58y8@nQ&rQByq}^_E>fNZgbTsS9csd$9MZ^< z(R2z=a8|wT%s*UW)S1K&hm%#8?ATmF1ZL%(zhudo9vh5ewZgk6JmpESMvGq6?8pmk z`r;iwk$SN5TPh5Z&UXYC84p}G>@$jKZ{5)-dK95lRcaH1O;gY9clnn zIIo@iyxZpj3v-_e=eEpcPIZ~2`&}t)j*S=U)}|@BB$HF`@q{HtX?%E)9Fnd6QK5x` zI~TW2w_>_}f3(=iZfCxUh1_AI`QvI=5S^Q*_<&}Q-jY-sZ`g+Kxm7$E3|^~adFBJd z4V`L{ScZc_vf*mIH&ZRdem_>CF0xYX5haPwuzyj(w0N*SO_@FmTzp%oT<`Z;VU#n=$>(!U$6dqQlX{b2us?t)x;o zs(WB{K4T)^KhOyszZCiNDj&A;Y4ub8_5XWTs64INQRiRdwcediW(=C@P?oJCzOF7m zSm<)6Q9w(<-g=XZ-9%zwjcBrni|?ItxlYU46#2=uJAymPnw5u24$aaXWL2aLbMg35HzME}(LXJk~>v{_1 z!Ycu#aslf@upyyM9=btYCbE*=`#UllryerpM2tBI#IRP`R5|B6PDy2I1s5G`oC9U= za)-(NvN5CL!4)i($J?_VewRqu3m+mGYQJhOj5c5-T= z_)+LHF&w^K|D}jlD+IlP&|b5>ImXsr#o4`<)76VZ*UA9GQtV@w+9oAC)Pkk@JaOhQ zK-pj5!Hu;y|KxDs?>0&C2#ivD{-m)Lp?X;Mat~+!c<;pA1mkxxG8Jz+-Cpl zYJ+o>h2^Fq3kvZj;BmI-EiXEw^06OgE7*mUwKhkvD(PAb*I%eG4k?*GJm}L?)WxV> z87dIB=iDV^kQWTJ;46G4t5 z)$-eXf5D_%DGK5ljL=O8HHpi9e<-jZaX69wmSqCQXGOsro7||NVE$d5 zl2A_EV5r$@Uh=De^Lj{g1pB>%T^5OsgupRme@1D`@@SP7_$w9wq6cfuy95A!SPF)* zC0XAe3nIo~bF8XT$y99s1x7LdDi~9xSoDy{2|(r}U+2=P0t_5=1Wr2l0)Y#F1Vu}U zpqNZduf8g2*1t+y217svELzhSJCl9#LFAreQzc=qa|T4ULZ<@7DI79pEUw^ljhIl) z3_&5GrL0_z)dOn)1N3yA(-DUC0UbluF^#93;=aC$V(FtuC0}>)tZ|Aq|O8Jic7su*GX{ZKxfW^lY{(Wout#<1=fb5gfXd9lzRF zv|4^-vN{8&h9}sq5%Pgo**petBzNj-Whaou`WUThxjI_Ltx+?oH{y2NxwPGG3e8TO z?<}6svRp98q)Oeb(ZkH0fe5a6af~w2(qmvfm|u%w?fHRctx%X_4DW7t+g3q!V609> zy@EsS^HKM)D|eE;8oS$G9DDTteEazo+^rw+%SW6C*aSTyS%8EIFDMBvm5$m>zGc?Q z-S~ESxD3VIXMu8>V}@GpsNLE|pB_&v^FpEG4NF?D446P&R)t z&Sr+80DK>gf$MI~J&OSOq3gh9@OEU6OS^U&`by%QkNRiRwDKpF)+YQH$eR>eK6iI1 z#X&bX@FgyFFewabQ3OcvM0#Xmmdh3eoAtPUUA|ii)Ir!De6}vz8g*IvB8&9&)e$P& z-sl@@*#1Env)hep-j|~+vTiienEg!He*mO6CSQ_Vss|8%HZyr{1e;L^^zbAhCL%M+ zVJBgZ4wnkeXi(h{HQNiJ7I)Mru~aEMIqO-9VT+jCa)CxS`OIs#MQ8WUBX*A>2T`Pe1a|X1FZJu3#vKh0Lbl%%3XDj^YwmYcV^( zW5AV~Gil@reJM4*FHS8Ku4+|icS%bbS9QFywe@JO)W%XJP6=Wu&0^_&Y|+OHG?CA$ zj}Emu1y-M?tHj$HMP#yBe>UWGTuA+` zqY*?>R4V3DS~7Dv!@UXXTpm&OloF?3>+4NBAHkg>5%}pLqP$@kVX0%dx&zZRlFYio z1vW36-iJ<#(fVwj<;XSxjC+~HZU|{OkgP_WZpHjQ$2oVIYOQTa208rxz@SGp%|uPGg8FmD77$*>tkGo_+@hTS5Tlg5O* z!=rfr-G?I({wr7~EC!7M0N*Q4q!jcl|!V&68yNzb$ibWfhNXi~n`dOtJ7n0xDS zrCL+C$K4T{E{RW{{L(Ko(Q#W|(kOZu5yGN3x)`0kpZ=S z`d-VO4QDZr@fblO61n2TommC zRt@|G7(fN&EvNNiE#97FuCCje&j_~){cv7j*G7iRps6~#3Wo&gLboW%JK$- zJa62i(Mm}Qc}WWZh|;TM_6EQdJMY#>x7qeX)41t{av8la2P!1+j+%=>sFr4HWGldI z`rdeD4!6f48hdYQEQ958f%wP6lb>KVt9~=E_D}RJ->QCsbxfO;08iV9}8~M2xEMnAV-|fAuwcc$p;v^?9XphzHJyf#=Jw z&X8V*JT1q-ls8a0?}1!|`BOnkVkT=3g2Eg!up@#a3FVvI_z!N~8F%&VH$85gg|SfS z;*00st;Suig+}cAuo}=5?bhtJN3gR}bd+_O(3Z;B>?8pMlz|xznvF#5~xS?N;%0SXd*8o-db3 zCk(ChWwX}B%r|L%*JGHnJXll!N_k_f?p4cy;+^lFnpkNxR=sVa$l9;PLn?2s%=@{6 zULT#DVHf!FpZUSg1B#ku_2pqk)zkhY=iS zWiIteJ59>jerWyP)+};vS$Bddppv2U=f#hxdkB5`>}O5GSaf#p1YPr&mYz%-zyCGP zpf`JHkIl3l-;Vw3w_40$=S+pfVkXT(b$hy9u#e^0R)d8R;4ed=Zy@b{xFepr(I%v) zhuE{sY3x7r1zDzHvlv~h?v|oE9<5w1#oKl>pL$x@TF3UzlJiQ#kdTzh;HKnzGdf&T z_DY`BD@r38uH_mt;-aFHTNajlBok<0wxz}I91r_$OaBq!;(MH$x#mxP&8muGC;nSF z!`>H2YF)^CAmN$Zr}U86X$&2n--j4CX!#?i%#Lr_*uA&qf&RQjYTW}94%UuP0V4bI ztxi?~krhaKfBJ?NbDOerZ=P)1`h2I@-7};Q8kf6koT}_VbT8D-q)jUD`xm30Hle74 zjjg)kmTC!*vXLpUta85BH(KR74=U;gic~JD;v>;zc1^^gOE0kh%^l%^JDRvNa%8bf zK*MV_>YA>#R_B+^(7vVN&1~%s3U>^Q4u*~(*{voukRRyT(~G6&lw8(Os+%Nn zAEvV$AnmOtF0pLt7W3s7R^(k zaR!$!%ksrLGK)WIC=>NrEA)K3z{Tw5IlmJVY_4B1D*xUfS80YPl*cW;!LRzk7e#Zm z=7o>XF=-z!?;E>+e72Gd!*6951a55GHG_mVkmKyPE^+I{-=+;!aes?*)kAFri?!X% zZr8PPCbk}398_Hcx5ieM3nBDqSewtxmeII59L2j8^k>@9JO3sm8hO zZ3f2*?4?~+;ji{E*cI$Ks4IzbaL4DiBDc^z|JRZrzHklXpiXLm7nv9EC~~zcA8F2% z#^kwud=naUp~0F{80qft2OdHgU-=)f+NjZDL+(o46j? z95_etSY#~rm-lq`tXNlo&%8#~kL_%*ZOu zjJm9F7(q0jt1a&~Hy0W{-{ic}-xzzadk@)bAsumO(~n8D)6P+&L*hrut_s=K3Wq^a ziH*bZu!`5Y0|}{ccg@~hy@}KM7&R5fCjeQW02|KL?uo2$vdecx(sWUtor)Qp2xlQ^c-!lOexfskSQ z^n!%G43$3qw>3(t4m$Mhc_a+VqK?b^Di{y95R73r4M5_VQQ{xI?NR8GG@`{&W+8&-w82Us4~d?08LY;wT4eA^-3v+!Xt}A zoybG<)`f|O0z?TAvakK9qm=wUGhsHs$CB+n?bJ563UN89fV zqK_y)UHlP+fbHjO%d$5_My&~AbRR|?rotz#)kj%eW!EmvDF}10-uEKsBclwOTN7cp z?xr6%sv3G-FS9S_2K!74y9V`PID2XxYq?`PjP;%C&dyZILtJ@p|Luq;bnay`Fgcax z?;y(xjPDhMUnKvQpq(ql<6mJSoH)4lxV{TN(3(vP% zD&KI#8C6M?)2ZBSWMs3OS614(vSHfn#$fK78pY%~@B6Q{uV{?&|=(F}`^iu#> z6a!2f9i9+b1b=-xVK|0)^v842c-=~PE+gd*iaFY1b_LH`uja5X?Ooyq47st>=0Jv@ z?*U8$4XAWN*87s`HK}eS83@3PX|A>5rO(1C-cQOeAYt9eru_EpnhM?4BV(JM3?l|}4<|71XS-X{*^9>IfcwIyK>O%n z&pb!hTSP;WN~T)No_+s^Z`1rRojMUU!gmeY!Snqd=xc&+t+*EUSM5jgR}znqf!Ou? zR^qtFeK0;22P&!h_BxvPJoA^e83!Eat?uk|uMgel*n%-eyLN?I51Fef9*5EsUMtV; zwYYrC?CRM|T&yBfygn5rW?qfXyi zpTkg+lbw?|#`AC`(jQQ(QPV6AX`_?Axsee@Z;=0yk7Mn_&BcPi&hmrHvgV)M_he$3 zmFqilbt5$0o^Tl~Zz)zPOKNP^UUll2%8TALe)f`4RT5l&^zNItFRe`6Z``=VaZAVP zl9l~XyMJ7B9Ielb%=eG0C|&$0AKPtqzp@Nx)ZyiMyqTqu&A#w>uBS^$rSWg~^Jg~U z$$cIIL)mwyMkSO*67Ju>A9O>@@Yy?%-oNLGhS9v~iXFW&R1(*SKPqltvlPL8ZRBg2 z>yB-@c3C++kW;xDAc0gOx0=Y1j}OYyt-7o0R#b+wNWKCGcYSd{Yj<;rg~!$~97Hus zv->>Tt*Uw&(EAob0lly6*>NN3_RB`P%(8Q(dX-P&weSnI#7l6qep$yDkxBrp8B;Dr z47i<*?yIN}7uYBj_Pk9&-LDk9Ea|vE6%&5 z$OttoBvL;g7-1N|d0^YD>ze1;9D$6<4zJfBzf}!`pi@PaRB2uA?$*BA%Cuv9 zRn%B{2xXGB@#pj{mFAo-Iu~C@&2>+`s`f;d?@Tu6Ndf;v+nK-)`6d32OV1u3TOz{z zB`S;jWd$iz!v5~O3814FgcYxLaB|jW!_%?U*?#_DYm9}I%{b`RT$hyc$F*25kGW4` zJj9O50)9UfB!jQS)Zra!59t7E!d`fQ^7X}ebOc%?Acwt2zu)=DKZ z)^-2KwG<|pHfOIP&{Fc`#{-7vn-7A$M-+G>AyxRpO4+T?vUcX^U{2&4)T!2O2|)fp zsW9f!ojcYZsH3|_CmY?5{@pwQutyD*PGe5Zf)w@r8*k5Tjb7fl^$f}#>6hs=OUci> z-Fe#TJeD^RHUG|ivUs$pX3T&Sh*x#&6~rS168DlrWp=X8k-b=n{4AMEx7$qH9wU0g^Zvnw((n@ztIUx-o_YD;* zh>5`}gM;ypr;_YuwuXBvh#{JH-!6agDnJ`Ae3>f`uA2F+1 z%7HW`m95DU!?_V9#TH5}ZmCpk*5&LppPm}sI}|s&3U!Ah(C3w1I0Z#K=^NXG+#Y~# zbXv@_pY|e%QStcdn?8RjVAS5MdTy#}Yg9qb!um*-Az;hQwL)VC(-WCvg5UJF?x;TW zRjnjf-#jJwU=rhzsC13=rfaDjvs$ip;4mp$JyB>+ior~4r^%{*+~Ay)MI>ivZmJCX z#um8{{T#c|YS|5XJ=y`h5Em^B1$InR5_@_0;mvedQXumhvur%MpxYm9r z(Aym%6lRvH?wa%{Xc?sI?=hq-PBtVmNC+i%?FxU*K?&vfJ{nvPo~_w{IoLcK+10_4 zL6h!E({udx8LloLA;T}R5}F$*@NY<~HfhVX z)sG=5kCumd6iX~835m0$gbvn&al%*(hye~d?ue9pZ`tdZrRIT#LVr^OQ<+W1pYlki zN{(G)Cq;#&in`x>2h7qnuya&3Y(z2J^ez zdqiKWy61mt{l`B4^D|Ub{q!QH&J$dMBFv?`T)rT=o*_NO6O#c1YzY{7W73G&nKpO& z_HrhsR}F}%VK=%yZiE`nb&AO@tUoKUk%`?;1}u1EHy$2d@cP`*JsRZJmj;dU0Rekr7(rNwF7J^Q$BzZV(^xjz0-rHMgq6@C7y8YgA z6R}2&m@7K4XAOJ=iZBfbAKOS_PTUc^jGVJm?yEc`j5yU7L zYr0UOT52=lsxhO|U^1S%v7 z1{=5NDtw%eCH$x!@#*rdCklu|XRpLK@Wc0jiPe%{!t`zQs}UX=CHu6UlMHQAUJ9_! zGqXkmPwvLDJy~$(E!FMo5ZU7$I!KXOpf&g^-3gPV=Q$&l$rnK^V_?~mkE z7w;;*oLfPS(2=&?P&lCJBWhN_-!;y5P}7RNsXsHf`6gBNWhT>_hD-;jqECGx(fg3= zh?BKNTnXMdoLPrqH@d3}I4#M-o@jnc)yP&C2HXQIJEq!Ce)R%d!xQQ=7N{Cse61Fn z?c{aBLV(pWmmFr->;h=JK1j)l?mt=p-@o9FaPELnPSUGB8AK;@2^*Jh5zDV{B@?u3 z8lcFR;L3ZO$};Eqk89S4QC8Cuz&?>7iH*IgkC$~c6PFou%S3T>S1_Swi zFFt|ZOxBr6XFkvu3gTxVm3gu)3Eu5R!}8LN*BY2ePK!ZpVrJiFYpbH7ujUvF^FTZp zQ#sj2DFtYate$C(t5+UO|YWL29GLgP1+ADg=QbaP(qC)5?RY{SAJVgb>h2OwU zOAmdk6$*sD+P#m@&z#!j{)7dRR7reTp{?VL3w5{PsfmcPRGp`w4;nr9wU$ zpa_Rb3aUM8v@{rM9L=bf69O&khhMp#te$Xa9%TpXg2rR4=OJ)!;1;XS=JJTrh<3+_ zYj`3q-LDF7)@5G%fXcmPKI75D+E@O5E{=?T)yk|FPE0;sc208 zflM50s{R_Z=D5*TVi<;q?HzvvU#peIugX>jzV-FwdqM^*YurMX!=`q3o-H&A^Xu#} zD5t$%9Nx(^&vQk#^sRlu4b<3MV&qD@HXTNxWi1nq%K1?9*UwS# zPxd1aKL9&)cA7umsxNY7wCYuv$ZeJdJ8H8|nrXTdRm1s4xDK_SxCIt$a6aE?N47M| z=VGRe;Z8M%DsX9OhXBT&_v^ef(%ax=q<6z!_oyIrbjvsU>t>GlJ&}h|9caJbNyu|O z+b=*)d&?g;@%F!5TtKRgq5wf$&efcY<^i)iNRPBL-DPAVE4_E^si5jCB>B}hBI|0Z zT1@=m&dlX{px4GUR$;u-3Qf4&cX+U;;V@Y_*c5lj>4IOt-WO|`yIVzK9ilfOkV`)H%WsHx4;6A7WW zLd2J*Qc_;tY5_u(6VB2CIwcA@+8EH@t}@SKQeUmYxsdx!H3!$;>#>{NN4Uf>z2;ls z6p$CKt#U8U_E#Tl@)f;3y~zxc2@e?3+z|Y`vj(zLn=gg^B9_I4?!T9dbEjjkuF?}2 ztK4B^GnMy{1QzfDL+TV%o|?u#-ic|nZ{LSG7UZOhF{*>a59WzycT(67KqXqlXl-R0 zW^Oa>gz!v*dO`8qZnxgCt#os|AK!>apnd9#U?JpQsUYGw?kllSq0pVZ?VG%RE5ZCv zk^8$gG(@~jtcjrF6Lc)vT$761-MA~7lq~Wcn5s<|UfWFma zL$FrtlkeC7D|OL!E@vk>*zmpa66Q|TgQ1ZA>Oig@!#*<+1SAGi|5Ud<=qy@!KwE-< z!d(gm99)Hw7e$ZByLZ5IX1a~Bz%%=KWOW>5w*HdSil zb?QUiCNvx5U^*Tnn31GWlf6lx3(+B6bF_JA)pE22tNT!qLlFvhLfOx+e8qb76pq;O z_?|`r7K3o|zCru(fB7@SLzjca7$5j*+y;o#vyVdJd2?zu-}D0TZyr!nn_y|?>tcUg zIp)B|Gr z$Bf+5Tu31I7{mnr=VOlDvyUmB#z*K*aGGhYgQLgFR6KL_w_?6~(p2&Fj zMN-P+Ruut#(v{2$02lqY+gW-KE~nf@OkRKkdoFpT`7wT|O5i}ldQtup4E$<0&&2ik zDsD62ht%w#VA28ZZnQQ*G>ZG_L7iC+j84~`Z1w6y|I2IOzZQr0m^eS?FN4V<(d{qt zB<)hh#M({eH~7Ni=Lmk{D*0w_-B&yI%*(c(@6lWRDSmj{FLif_L~(hz4}36qMI+n% z3g_Gf<6}Sl+qYN>sGG4~Q+fskd1k)6X2%MU=NU_Ra)3O)L%pSIT}+KEhL_LrO;JPr zMwF<_+Lu{YHm7~=ff5t{f$c(`F{!1Q1t)SW#@~L;@fD`Jg^FF@PT-GRN1^gY!V-01 z)806DXNjHgeDQ13un&gy_m8ixI+_S{HF#kVXZZ!dPh$(&+~#$hyXtoOY`*G`f5QNx z4)zrW{H&TC{1F|mm#9~<#>5sC9ezDk1=#5@k-2lnj+EyULac}5m7|%biyk%vy~Irh zWE$JU&ZH+p3Y`BIIir3H0&?e-D8iyY-u*V}7Pa%y`C=XaboujG&f~5Hr_UNLFAgYH zIa);USm~xMa!njxsNYW}3))AVX#WHKyDWUx;TNt+g?wH#ODhA+^Kwwvvk}qp93s>K z@kKsflKkR#3_&t%gJwg_tyg3$6H%o9ksP!B-0{o%=Uui=g_nrC>1Vq7O!U;ykvzGY zzKfVSIzE=ePk=t&hC$o*@y%UTp_=tXE@>79%mrV&H-F1)67U8HKOg7g8~CS@p~9C# zRfY&F`awP25|DDAX!He!Y6~Sj-e9I-> z(?FW{sk>FbjbtE0a-#3QK{!!o1?w#8K2LCqa=n4qyjwDr)(1v5fr7&v5U=sSjO=Y8 zu8Ts)S1L?68od>7aYPFyS-X582%!V)X!iT98Z;MnDN(Z zc64Fi~PL;`{xS3@B8vpFKzeWg~Ca> z^@~+<%kO65Z|m`gHKMXcy&IQ#yYO^t>ixG6w^7TLB`Ei!71<~RH@PCRsGEn3`v3l z<)o@Cto93p@P{0Mt8i2gJ4{~uX%T*YwM~a_;)(1r?+c4SPVaCsJp6aV2uT)-a$>da zZqW~~@he~@KM*G|9L>>jAr)}WNN1=KEJWS79;{oFmdshwj zHuaL^<8S*J^+n*4wB9Q)&TRxO(v}P|p8U+kz3e2YQ!t$J&_p`J8&z-`034Ix=~JOT z(IdxKHBLvBX%N)>qanJ><5hB@|`G((an{`%}`9|KNx2L^G`q!WP%YVE0N3`TU z4jISK9w73QRuA5b93(@PTiI)0G^a-$C?V+A#}_y{ zj&!K4M?Kgz|2*zLzrv416FD1Q4aKPoFy8r7&#O%G3j098=ciw$p#7K3>BLDC`uNG2CO*4>ua6sb<*xPNg+gfDsg#gJ5Hx@Zah^i;hAf-+8rp4_=qQHxVBW zNXk=|zR&NLNIj9Pc8NsC_dWg^DjxNRA&z)1l`H*2l{?6$-K8NA&7$#R`!As(SOM@} zcr6p(c{2c-0#F*Oh8VuMhR=vc16R{QpCyDI*-TEv?J?D8M9MZvJvUTR{cYz*or7Y& zp`;qa4zdk5h(YC&lA!J6wax@FyMaol0*31A7`TG;phFuh>P=8qln6G0z)#p79)ATJ z9={}!_HU0*i+cQ#>O+P*yPKgQctT8a{2KC+#jYyL9^; z{M7&EOP>m%5}ZUL4~Ik?g0r}|N*djEksm>SUI*vGb^YlTc>h3vd!Hlc3^r3>D`#q$yC7 zyM0j`-v=-wh$V;m*@$u}vAo#?Wxd-*86IuTpa-S{4f~0-I^drHEi^61+j9xS6Y05wBCh@= z^7L#+!3OO8FsMa-b2oqeAu1Y_7x%8kU!tnS@P-bn+S-hNb0xz4;GtwVb4+6HOk6Gt z-{V6DO85SIFo2!u>}3QEEac;?ElvXqa|r(dhec%7BVDG#b@jVf;wWectKfUM)Y;mz zgC`DvR)m;ZOO}uWgEmx+jW!tpkzxzQFlhL^iXMc@lW4i4+gOOh+*<6JOSfvw+W*0T(f`XHg`oDxfxGhDSFM2A z^*OzUCKfK_U@PLme0M{JC80}Tos+p1>)B{ZF3ZZwO-(+3Hy4J*k&4vy`3=uh6^bUo zBiE2ZtD`WD5{HF0#Zv40sm|QUq3z;w1$oExX&sB1y6My2yc0{?2wsa^&^ z2~v4tsrS+E$?)Z|+XI@VE>QO`I|O_2+5?r<9$mbN_Sen(eVcAeL9P_QOQwrCaYMo$ zH1ZV7Y#nl!78%b`3^Rf>Qz3~{bV)H$SIrJ|xph=?w3qU%s&=@{3>QvA{v_wqK4Pq#qq&#bcW+UaHD7s+pgh#>o^XF}Ue2=q5PRn#4 z%{mQlF#^v#=T!K#k}xe7NDhkrTRz}1Pm>vOjJwxc~tgH7*IWEn^8ul4S5*0E@AUtZzcC#>k3rWFJc0$u<)y3aa;F z`2lP+ZbB%(lAh4|Nw4@3#y}ZTtpH@2T&Eq{VymWAO7MQ-3%;lG7(ub@JKD$6P0?jG zxR>s;q!hk&3Omuiwaejpl=A&ifT|nlqv}A;90fcYux*%X3XgG=#za_+>;^-C z(2%apQV4}yc+A%9VO{wT@G9$)s$<6o&Hb z0uqogm5k)HU>@@j8q;N>y(gHw8+R4ZBi0NiK#(W!dzuST=hkb8I$KPScY%dXQSp3a z-AjezanYa71tO&X!`^!aHQD%SqmK%LAOa%21Vlwasx&E~ixp65N)u2NDFNw(fE1+@ z3j#`$-aCZetMnd7AVH8$h!BzpA>qAw_P_7G^X%?9XFr@TXU4 z7%%BB`E1X<0b~%U*D?V1bmqaoxfO7r>f3z(xv~8QG$>6h1w^0088H z|7SoR@XwY#n$-DkJIjL9ardzmSo@zr3df`0|BsUXf9}f-1{9ds%K=+gj^A@lHKxDu z#S#u>(7<=ke`4DIzDYX&C(nle-HZNlJ&LdfdeT~K{y$&Ews~>Qg~NIOU$c<^{a63b z#`;p*;Hc)#M1kih^;R z$6E8re_=(FaP_5smXkI!V3L4+$^1|4;Xj5CpGcr&oLSQR`^%*Q7~B7ItA+m;zT5~x z+de=`7|52jzif4|_h_}7`x3y4mUk(&s$7>tCK3ktUT{roRJ$Q!W_U%=4 z>Yi%skwq`&rhu9HJX9TmqgQ zwRTGVEYM93Zn7nA04^_ywv~Wx+~@C04Bar+Hjk6cv z7M@EzEVH{4Bb5%_OtXk(?gHz^IC-b}{?*uHLE@%qncR_75sX)E@$sExk<<~K{nLjG zyQNPX?svq|uH+e{#ScEIy1T9BxBM=V?{1XwHTy}Nhe=^y;fsO$XcDAVlB#iV^%iyR zPHaL!vNt7gRHwr*MW!(ai_~z%USrkxwqbx8@|{f#^K6;F);jU0)vGCG;{-&02LDC# z$N{9W>%mKy&3m&mqLE7Snt3U@Tz{IF+HW2K3+4qxLC9pK8-gzfKI(W2fCVW08T!EE z+-3;8^;Ej(ybapvNGa;Yp6ACV>mI9Rm1a~M4FI}* zqzkh?{N&%@0+4Q+5X2Xj0DAQqpbGY_09v{XZTjWemlAh@)w`L*YawCRU^qBnAB&|Km9<>RcN7u18;N+81IPDUD=*>}yrP< z4UjIgm9dtsft|&a-GRtQCEt$D6d>|P+zivfOVh2*3p5J;a}=ipDq3y$h=Omed^KhV zfK!F2qpb5>-h-0^-xQ+H3aM!VB#(uPR1+Qmbnb~u71k(Brge67`~nn7pHztUk6I3I z6b4jZGT2hroW;5vdw5r+1|7wgE8=&EK}Ql-d2cxE)(uA+eQ9R`(;@9sBA+|t&c zfYib}09z>=0K-Uyt^``#e9ft#xji2+AX@p{N;^Q;@aHW9))9BKh|RX16E;EA%RmZ9 z;xQhvvm`d?ZunL1-LHW}LxA(QurpUf=8Z^h2HC8@>Pdgb;ZQ;RY7W%u?mu@~P2qU@ zMR~Wty$Hi>r@XixQLk_Em6RFTI|Q|L=)2sifB)O1v#8cR`uvf@EUUupF@7e-5~Aebd(f@zVN25dl@~)EgH^PIOvjy=*A;89*GqI2rp`aW3!1Nm}Hj z;C`@a6t*268mh6sNKPuY%x<|ZI9^tE$>hXZ*3^(fMxcEp^{E36@4&hD5j_CK6m~jI zo^M?pjRf>jl(dZ(r8$2J<53VLwZLC-Oe^bOA z8&6gsZG*}_6;?Y=Oh3e0S3GMMdof+i`$>OZ9`6|CY6+07KATxP{+tG+LhZZ5IBcY= zgnfqa2iSNrvVEki6=To?PhV5~1~AtOg%2uSmra97fbT%Tb<4Uw@i+ddK;n&jD@-)C z#;zkQa=d{Ugk8U%qHJIdo0=ag#IF+pf|+<7x^|@Wm@O<gxASf+i#26U7JzA-f7#mEuKh~iqsq2j@kdQ{)d9^V zOzDUk zr>{RY{WnTL+Qh-k)BLBoG0u-{d^Zc=4g25i6Nf9Nyo?Tb_s87)j^=$*4gkl9NYPu& zz$4%N%1wxG{!Hsc=0R&sxK-{|UAo1^=JRXVtun>empmMXJvo32Ozfqr^d1g_@n-x= zaG%G-!-qLyWNmP7stD!LI;zMdDZjEA1vlIWdNg_fBACk7V+<^ zKPYP&w*=B=Tus-vHh^1|+$^d$HhH1$rtPs#kKpg8?;)5UWUd~WYg*FqW}!_h^3KAc zp$0WuZa+~L=FtBuZOZ!P1Ee8lVj?{#?Q{IKW(|ekXh`d4ZtGXifc#Snv_Kw%P@PRj zr{UN8zktr`!Pi>witql0LUqW0YI^K2Tb+M zz6MZ%eX=dET<${A^EwT!pxgMK&=h!K z>GN!r>d8ufB1jnGgI}?XyiDiHZ21rr6JEgTtEXNGu=hFN)XlL36ULg&0*`%#id+|V zyzVl#hqi1-H)HTGhkx*XNqvRr??2r6-X|TtcM!&mbo0ME}yphdZ!QQj&wl}2PT5x}Xfwc9-VWVPcT6dx8 z@=qG7@D9gU>Xca=r^*CWfwR2Xc8KrTB_*pV#ea@`$SEN(P@zEhm%?_;Q2uUaWx7TC zS_>dF&J*G^h36q3{}|5^3|{cRnG{TTX_)%a5Aj&SPJw0s z<@+8ncHc4$BFpzZ{FYA#zB-=%IB7f?pGtJHWda12RIMnaUS=^p^>`DWza z(T|sa-f8cMyT1@?a)7^fY$+$)a!e?}i!Pu^FqZT4d9MB=iTFF5TgkVCpj!`Tv%usC zakpdLzj&3O6K7Y7<%=^mqK4r8YWBKv83UPER8a}e>`60i)H(b`q{3mAqT?7qEA z$_iotG8J?HT>Y{NGbuxdVo$x67`**?m?vFs+1{ZQUF~=oi0@D&s3-$TkYe|L*Vhg% z1F^tHU+1cl4~|uoUk9)Xgo9qdAx^S8O8rILouO}2FWOI%`4{eed8AZUG*Vy>xcS_M zkv>Y8f!F8ut-O}HG<&^EzLrgq-2iIsM<5Lv_z`TM(J%Ht03vjh0BqAecWq-TdSzx7 zsjpCWs^rGYIQca!uvVYjC&NtcBTCTs)4@7>EG%D5PM)G2lz{CP6?Nw03({SG12V|FsVYbFf7ufO z*C*%~+o0lxGudUH(JSkZ;2LRY9uwoBtWxue$&HRYq~?48D&k%az}J>Cp;}&Y!x+jG z{-=vt<;i3CwKYI8qvdziNv8(V#^rf`viEmjmbN;bU%8VWv>jit7i*B34NV8zoy3v1 zJ_f4$_Z{L}JgO&VQyYu!?|m^*JWt84^|?>~SGo$XRN_=t3CQ5f-O3juBJi9ClJ~f2 z!tfjemr5X~v~GCwx{yl#;u=K%{@bIadXKN}b<n-sP*V%%y}qb7_QVI!xn6r`(sKrOw&{V&QbyYG;S6xrD&r;p3}ske z`ziX#1lCqUHYp>#XaF`v0n;asr3#i3uHADWN=J{ll-PT3nJ4X+D?;OXFE5K?Ze*D+ zjrHCKsj6Q#Y4-+Pwkl13mGG4{hJRS5XUhgo%x7>gw|y}!m@MOALRVrc%!RU(O0ByCP>a%*owZZ{qU{>YEB zpoyiGH@tyP_1;Oh@E^Z|x&;Ap@Zvx$p9SjQeDjzC3)LDJYu!iS3P4s|^H4oF4(@DGbKY+rkRDJ4HZQmer<5AIwp_dcE%ymYVoY-sG4kYLT<@q;J7L8sJd3r1H}N z!-QTyNub2V-)q^5B=Qs!iq@m0`>fBEmp-yiK6fb-rF#3VNW2vZav+A7=vvCunw)k-{h{E zm3Y5c2Hh^S9X%5uiLG)fY-!nJO z4LK?|dZ&v5(>FF}BHDGb&5%;L7B(kWz4WMLKnqlMN7(q2|DK?E&uY=DHV@Sg?GQpT32DYMpMxxk;@Y}vLk zEVZ9(UD}%f?O_-6q7!9~$Q6(YFoX&wtsbe6NYhQNtI4k1efa96h9^rk=}HZQMDk~o z5#khPIGr$519Cb<2|FRSK1td8BgMz0Ha&!aEd{!%$?!`E71TBt|5@vW_8`gkoZs(^ z@JVcD-Xyk)k{#dAvxbA*pU*3^uf<3ld@C%eP9;L`9|he#ve7%DVVenNgj%b#fc;e0 zzABnS4aWZ9XcX#$|1>yT8P(!wZFm5A)hxY!AY8nY~G+&~@ zKapangFb6e--a^jFJu2h3tst;yGb1)5rzFYG7jY2H^rMb~;^sedKYb zKjaBz;h?cIkoLq&t+{6!PfxWb5|8rIdb9xQ(p>%DxUc=D(}!2b=kmydqZPm<%B&sv z*;wYV=PKY$k^sckVYpKrY3q6E-W{X7h^m+HrZdft^JhwB2B$#l3 zKe1V5Pw%ROm8M1p3rsmBE1*%DMnHsBoj(31UY~4XS5jk6uxw^pm)3352w1fmy~$V zK(};t0h2jFVlm>t1n&5=-$V%gAeH(ctU1W4)uIf;&h0uM_2<>ERrZZ&>pZZR%_?ob zcPx=fE!K$l=lnrA0zxct<#MZz+^Acp?I{+M1XX=VsBJjE(#1*cpX}oIin3a!hW%Y8 zT3}tlpYu`<{2+VPP*QjMwqz0=|Y4us3WFe zTdIL_nE=UKsJE27{#n*ubPxO#U1eP>q`ZK5!1>H!W5}lEqPB&F{b<<%PyRw-1VIUM z%Q)Y1)-kmvup6FyaTxF%o;EEr3l1|_GE$P$1<*-U35cRd49F|Bn(rwk+fEA1@7(rK0gEJ=xgD315i~KssSW|5TCcR29Sq{otzt zx@0~)G+a3qbRagU~EZCbxz)94@oCn zz^k~SC-%6~1Ib~J%x@H18ScqQgY>87bo&T8*f8#Mp)Ht&Hg!Yqj z?VpU;sBvnH)q2qn9~Cl9$5?9!DWD2Uq+RR)gD88(y`)X25{%N9b(C#(2}6LqS_ciP zPe0s>>ZY7ZzMZP@73XeD|yg8ABp zHm(UeTrZ);;w{F$UwtU=`}o=r;nJNOeqYXCj=Yi)!w#U3fbv1_xxpvx_g8iL)J6#R z9gWDXfypl|UZ%o^)mOJX>i?vUtOBfe1+~q__st`-twiVeARzRTeYwu7DwuoU|F@^th115ww3fQ|Oj5$Tlw-Qiy!~EA*y?pRjy1!;L}h z&Io&D>q6(*m?>3nI+SeC)VH(HwDChX$$&hs#sO_gYinXOx%`y zXDf)`oi;0Oa&Z{#K2Z_7Q%aig@XGXadKz|1IXwrl$oJ<>EFW)OZw*d|AML$HjT97n z#Sd#U2#3D=?yLOXJ)yr@Y&$eC3EbJpT7ego1!bsFH;<-%$LKjB84^Ga#o7KxxdOR^ z$=vOXWN&4&V%^AyrG*G5y5XPx8RSJpY&$k4rV#s>Q{vY0DFYQu$6PCK8SSB8=jlD} zLEh3>Eqlo+yh{JbxzFp6+uSSPxZd;k8xX&7AzCxGyjrOV5AnF3hq%y|FvU&*w#~+Z zA&XiLs%y!PECU+Lj04Be3aPnIi5m~8H=m$)s765gdL80ti`GEb&8pw(7tamm*?{zWKNrP8gHx1rfvDLya-fSq2yz^pYg9KOG;G9e62W7 zq75M~e}Q_dHqR&gX>J2^R|}G8EUl@YYvT%qETiXMaYF-fH+*mBlIUi6@aBc~^yGzQ z36rP|WI2Bj1Oy=1zr*}c_t-nwC@0M!_>D&u(WQp1Nq@`Uf9uheFc#btXrusN(P z;@nWo=N66B)$y-lgoA!xf@tWKO2eM*|R7}Pc20{hHe=DFT`armcN zT=$Y)`n_y}%lEda+pp9h`pb>a+67t)vseI7Sel(_F*0+=+(P(O&A zx)EapUiujh+C>W+rp;l%aqC#8vgS`G;=FkQ)2|^J>p#uPDEz2mchvk+p3cFe9HAi@ zh+@R;=zxxR7c_gd6<~~9w(a14+qgagqPxs%2!1kMRrRd>Bv`DZ?EUh9#+M3*q1}A7 zjv1i@3nLg2Sgl}94bQ-%_O3IunQK{XL;6MkX7``ONFP1Eu_+KKNZ z`YI=`JcTf3Zh5jl`{;SeFcwP)LpAXLbyF%3v^e#Bu}|4u6*tftY_l9 zxaISXx&}wMh~F~EDb^hqxZAOBPL#FY7fqTjHj~ak6k0bT($688EbrNU`MA+6ruz#O zF30RX&nm7lRT0wks5$7dYDgGir$?CXW1ZNULW!=8Z_?V=Cz+zc?NbM}5#?lv7Lsm{ z>`OMQgP8DhEDP;f=@ZI3pC4ExIXyLZEx$SC^mK-Hy1hkG06wK5qwj!FzBem&FweL1 zdB5d7{`u3>9mdYRNf9Q!gK>tb8XC*h+g`F$P{Tvhk6%+s`{w6PILFNxcot_b(T1eZ zII@4)i{8ju7n{Z#9K+$wL7hIPz0?ar0%3E-&WCILLF|Drk(fO zi3NT-nvJ%ducd{;n*1$w1gZ!TwW+sLnYdd9C9(i{Tkw8i`B5h;7On!PXZ9BgaoH+T z2QFKC+-IBi9VCzH()9B?{4Cl^h}E6`#0|RKT0&*5MXl@l)U{W#Z+n>SoDv-&SyJE5 z{R9pKnW*kK2&8rI(NskBZBVW=`kW77ot_#9eI^nD6{7TX`gb6JoHQkIxC|nEnadcY zp3AJsr{v1>;>O}k^zALu8Pu(y$pD4311>*4(bH1i$;rBoYT({+D_fV&aXfs{dAjB? z?vBl?sHJea+i(BVXQ5p9!*Yk8@2d&WH8#{D3t8))`Ol`!63!4|M#&Z-RjK|{5^?Bd zc(RIQhrtXJ+^1#(3IBb&*em#VmjSDLMcO_2uv*PuCkz8gI*t(%?_&-XPhko*_?53} z7l$%rdDmAUTp+D%(B7!y$uU61ovl>mD7*QJuDrI{)J^mul+9ps*yu-B-!^TrD zgmxF|ut#(GI#f;A@@q@aKyHjFvwd>(XLQ@Edw=`X3HJ|eXVP?CD*igKGvfVbW@JvR zK{lz$5daAb*f&4%CZhoE(3ZbxN@tKhIF$NV)7FF`#cR5uQ?)Qv2*#PlHzsX`wlo}W zy>7+#ae7$n6`FH6>l^}>@09vJwy9+UA*S2=`i3^6R7iiDtg2zGeXY`X_%$@56Jy{| z+{H00YUrD0jArjBYD}QVN0M$j+ZXF()3LWew{cvO&Gi!baK(VUuZhx492?03C@-3f zIBPZzc-)@Ru1a6U>(Z6{h;ZwOn~-ri2ab$dY47A54j0Trk-U>$1%>{eNe!|UrPD(^ z8{SqztS-sSO2=4JFuSw0-st`$d)>k7C)k`}*q7oWKR|y<6kF((SMub8m@0tKb)(78B3T}{k) zs1^Aj4Bw;0T zkcU=`9s~+99$Ricw-W18EpZU$65i8}mw)|oMrP1lYu1km7f((#zl`g&Y z!WLuO+rFcRazH(*m&)Fy%J&5dsaDcWI^#vFnkU2*v)#b+*Kvn?C|giftw@|+%3?QO z!K|#=CFA6zvDz@84Vfi-p8MIbsbD=2@q(LfN1413dKm_lXVE5}3?vLG>7|BqZ%1*a z@07K&!JJUUO|$()58oTu2CvW{gQYGej*`zzSb#CWnN%Bv0z_17zAg6>hg)#Z7~rUg zqJ6vIPC%k@a-z`9x?cM^*nh7t#&IIB|Ie?_uI$?OTfK!vPyy@SkE;wI&zP?c-BFi$ z&4q)znQQ00i?~7S<&Y!bIvA0!CfxC!A+XD7w(_gZkrF1K_aj0d4yWp|A!3iap?fXE zxx@q8yRawJ=^Lir(_qhNWb z>AkDT3fq9m-yuoMMvt$_tF2V^fRJ+YM%FH4=TM;N>3(Nq>4P3n(BVeni4j_oYV|oS zs_BTM)i^ibjXxqM&I|vikMecK=k;!wzN>IKwhK)gU zuGpCk-^{84Wqk~oWG{k{1Xsor2%1b7n;nNzTr+D1YZ%k-u;NURIxBv{+q&~sY+Rqy z&~PgIDmyo8q(ZY3^DvyYBZgPwlArOj=U067G~$DJ%WrL60So6J?P zYdd~6M{0{1AUa8ZP5u}jB@D-9@`gqmR5Bp+$^+4^>!nD8>)xZ4$ZZs-trE&=M8tEV z!=g+wKDbxi*C0&L<}5}0V#!J#BX_mg-)iA1JkWAl16R$9Y^F<>(Y4d z!8Y;Q%8E74eCw~qQ5)wS)9YH{E1@#H>;@`5j)gAU{(86O-2~g3-my{o*)k+j411Mp z>Mu`VWn5j*GTd#S8&|^X`Jywolt}_%z6Eb4o^4>#>%DrwT$YYU zTFK9LjEvL-m_6{3^^8azpTJ&q?znZ(c5P_s(p8Nu^D`btD@>~*m0s9yZ{G!x)Gs@~ zC%Y_1vr&{?J&=@(jV$e*+xP-ZYsU3M+p+r}+Rh_cEGHgn%vEI?h;QjPcm^OJ^0T3c z!^N#ROW(K>_?>}uF5z{Mj_M5TH1bU~{7sh{4wJ5KkrNeUy)9x%el{NkS|k|PQ-w^JoIMklq#D0muT8I zMPjW2E3xeEN6vY@iY;(kfu+E1aN_JqubFtD=pA~_wO)&UzCEiDD~QU(`*!RDQ{SK1 z_LV3XiqDC1pVmC>cvtkd4eyzXkT*Lp0jzz++H@M;eVDlBDjneIa;w!FtNi(^n~-1j z8!JWvb+RUz0xcW)rFkyg19Urm=0nH6XH_*}!l?juzcx|Q;+Kt+%SY$Jj(v4#Ae^n_ z9AF7-`H_SuWjSzH14VNy}Z{m`~{FrI-vwH6z8omHd zNW#{zQzEC=M>NPsJ^c5QN*77MA&MRqw=1k^rU+18mAa0!6c>|LV&wsU#<-|j7$x^C zXS-;bq{Xy;w>#nenzaiMK>Q?k=Ob;(%!V|s*|fEiI7{n^bvoQN@^;vnC)oCT6COc+ zPZc&NeGm*ijq^!cUis1&$b;^DQ-yQF29|le6*k=QR)bnlICZy3JGzL^%kSSYp~UR{ z;(Py?CXXU3`q~hB6X@RYDwn9A|Lug8)3JX>!;l=b(8kI+11Af&2i_LC)by<};Dv4= zn>>2IKJ$8fWO%c$?>WPtY`mhZsw@bRN7JB3Y}#8)8F0$W=zHuVTse+urQ8NvjTd3D zMIvDp-uuAr#H2gSmfQwSmsl9vbN$nn1wYu=@?1@0MfK{S3_w1FFh4dGEz483?{VrZ z-d>yYViz^Y!4kSEuC&rbcVAvT0gIXUO@P0)h`7rKvO)x-ZP^=b#tN>ka@Se53+Nq7 z+v|%ujJCa6L3hFw=o}F;pv<$9SQ0H(dHd8GMw2Z$FMcX*Ozwpo#&Q12sf(X|wQuf8 z%$#$d?##Idbvcc$kkKLNM07K}=5|Jm*am1`|GvtOT0Gaqa(-A;D9nM)FH_C;WEW_w zoV$F6S+M#n@^Nl651m&f?(xXRH`7kzf$2SfQe_JCWgU52bQ#g7TezZUnJ`tTmu*Jk z`TR{9#ZN*r3wyRycUL5pRi`mp;k$Ws{4tz*=uGig`|!vpxAB_>j~zYCl$TrD+cDr4M$Z&g z21yT;*L!u_rhdy`!f3y}IVDxE-Zxc-t37H?yXZ213!?A#p6vnd4Mnt+-Txf12Hne* z>?qJ^sFk1J%l>}Zff+R4R3qS{wy{&phzranxXCTgT(1}v{aS6is0}oZEw|J(c_-6c zMa?xw>}R_9$Fqo>P0WNV^!lHwfj(zd;04A2r3NVTgAcruTP?G0JyUmcsa~?J;M*>8 zwk^o5DMKr4Ovm;~f1xNP=1@;F%w&_9UDf7&fGHB|_2FeDbto<98AKde?dkSdsK9dK z(K=+??%j8MN9lL9X#pnm=Y3Zk29jm?nu=p1pFx%3@MmqEdoMZ;abyItR>Ida&rvXp zr&bHlW3XG(^YYE`r#v+hSM;Ap|M0dOckpD4JNFv-AT=Soyt9t zivO5qqgI-87Y8%_-N8JPT+~BD+}2ZObuaV})&HvC)FSjTFI59!KT8k!t;~Jzf<=)j z3tla(!Zodd)xtO;rb#$W&%AnVikIz!Q`2C_^9{Q8mgxc~B8b$7eW zea@&J)Zeks!f#4RS*XF5*pCKQo^(@!hyEBs=y4}?`AdWdn$?u*=3!&F=+t=K7wIu!2MQ5Y6++Dt=g#iDGJ1+6G~(_&Tx!%#dU7w3 zekErISN@~1EHS*}6gMLc z_%!D%wzsSlWNf~h=CscU+zyHQ{>J_K?eaAegW1_<%tz&~baC1|2xrwYv#kr6R0c63mm{da$vqyAS4QrOAkNlw(I1qS|n@(__sEk)vE0-&1^< z71=Z%{ynZFDb}vmm=k0V@+-Z*z7xz-TzYYLP=yk$x5yy#2iwqNQiGE=SKsPeT$1@8|t2n*uz8NKtg}0*hQ)RMYJ%i6QuA#&pjF$`d)+3|hB$}OxL6lT%bh2Vui!PehAe!^ zqu%~F2&tyK1%8)EdoX|;<+^=(e!x+UWn$lo!SBihcHI5JJ#MAtkNpagv$Xf`WW2Ev zK9WA%FMIh~e+1OJ&X&S-_kGb{r?D)uSHOZYqc&jz)7}=!5E~9>p@Y+M6pYGkWjG?F znPd^>HqiX|2i+>#s3Ed$16VE?-Pd7DzJi=UmcR??+7Hg^9F>{JCY zX#0_pc`cL6^VuLzv{ma@AI-AfFs)X*e!pQJD0eMi^Hu;Ax{?w;tsW58bYxE$l z(?)(s(SbkD(7Dd6yu{UL9el-pEYpvc9`%x6;q`~H>UA_Pz`m9- z%c6Gp`|zLi@Lid%6n;wGESqXFan})2f@Cs1Dt9$m3Bu<^gKGp5>1n?kTD@^h8pCEbxS-FkV)>@1ORpug zIfq**u7oy1M5tUmw8&J_u1CkxcN9s_02p<^`H_gFXH0FK@h->=``%{AOI_-X&E9X@ zZfwINNwQOT(6JY1-4JWGn^3e(HLDn(wq+SE%xGfv|7L=C(Xr1P%}voTSkRBM&FX!A z@N9xKoZrDgIT0)2xvsi5kgxVri$|!D82-^bL$X>($?K_cK*V)>0HUS)DWis(CE236 zh8d>XW6LOPy+6GOrTRU>6p=Smp012LF}cA;)9U!`P94RK0!KFMfc2cTSBxRz7T<$C z{;mT{?Nt`1Q9p)3A&On@pmLoHYq{MtCrAtLMb^G?_HviE)fy-%q?z0ifF&@*BQ-UPZuTbEEbjBVmt`#c`K0;{@whm zaJ63@@P52NFJDqM{V>f%rkfh#e6Xgr6lH1`Yuogs!+|;NDsnsP)n*1Zkei*=vHVpx zS8wBD07c*+YUm^cSw8R}(G4uPBtNC-qi0xZWWEW%skJF2FZ4(-?_2A80ML!doxBpp zWB<)C)3$KVZaF=wZ`(Us0N5e*zgKZwP1Bpzr(^x_{~W$1QHI61kx7Dcm!qe z^1X`(?<7-v%BHl^s5s?qU$;%(u72>!n@jYVo@MZgq-QOi3U_#sjx$$7f{HzX_wK%)a(Y}vh{`PT)};CvMTtxadsC$Y zQ~fk>F8Y+bQ0Yz`ejLVPC0izSmIc^fP`1pMT%>;jgMi_xPwI1u#f;NamHKON<5I_h zJtaz@8qk+r4fnFXcN_i)to810t2n5ZbMKTvfbRcnf~wzJR?UR6u2(_}7p>X3QQ~QC z;Ms~0AaRA7OcA78`r)$aOYzpY4P4L$$^P+h%E?q6_ACC>=$FvU)s8q$a;#=Kh1De zD5^fLKa589pK@`Xm~ppr$$KisS*Fv5TVXkYK(Tju3?@OS1PL>#*t@K(3%6NW_gpbE z$jYTNi!S`%qpcTb`W*U{eSiUt+L1CO8zq$I)IQ3TP{o+&N6p6{zV|1@d2o8J!jrDwo{dP{9 z$u}?z2ABn2VUu1u(TMwEMlgXy--bj#bQ21gqrHOSsy5HUQ@@F6$1jt-d{e5IW;*=L z4#Rxec3(BKIb1QnZvs5ql~cpUJxt>$*evJph(*21>$LT$ptrp}R^LXI92_X$vLT(!53-)9=MI ztDV+{54{UJ{Gb@UAoq76+x)DSKLgBNOaI($J}qJB<9@EI$A6ebc3T+JaroZ-Z3}m` z(b$8Rc(mGFJt~gd37rP<5{lzQf?EK%6VDex`D+fkq0kn$D(vmy2$!$ml z&dw!L?OT?*syF+fh@wt`v3~^L_AGM8$sErB>U>?<-n53;n|Es2bM1?T(~a?1;GP5c zBE>z60JF(spaB>pJRE9$C?dth`nt7(TWg`kXcM$T8(Il>v3MouiQn!yE&#@VA9R2M zkofNfK!9pLX!u#Q=j4_<{Z7S7@$*)TMlyOM$BK7GWX1e?kG96xlC5(s`mDVsVNy24 z2=BB7b!zsWfyj>GH6#5~SfThc7P8W4M6P&>p?C4~ZfY_VF#go2ejvv@7TA9gs2E$+ zWY|&Yu3Kuv3v|eoQ}g~!Yb!Tw7Pd%MzlVSl@-CjCav~a;^?uH1@m?I10;)OI(7uKh z4idWPh!91O;Z7BtVGiv^_T5|U{#Sw6?ECo9Kt2eyyEbRFlr^BFv*}|#*x1{3+u;pBuNNPd9~m26 zfi#rqwvHzxDPCo3NRto&SBz{T zaP%CcZfH`mqh-biiL-8c7fZ$DkV|Sa?e;Q4rk$O+lH$EL)dG;-{50we4-EqG6g{oJf1E_lPGK?bLlIAM?+# zVbHi;Pc&Ej0?V20i|j~`dYt;SmF8m9cagnn^^t~?cYOpj+wwD|CcD*H+p|!yU{@qN zGuLPkQLP88`;(8sZ+4Y^1os#b!RY=mFSl3w!jP)q3n%M>7|s|mXq?u(a8r+EX#D_J zLR;XZ4*l)zWLCEp3_2F3*hc}7Uk9q=sj! zUp4?J0=13`eP`ou&|90pLGq6Z*lG#6+1;X8cQKISf77KsB!Z-6VJp>h7)#;x39Ky3 zc^#C*7JX?>&N%xtdd_3}B3Gkx6RdaJX~q?~-`Pg9%j;BJQ8=7E3ASV<*70NKk-pyh zb^2}YO0J_Vn|`4Endql;@@r1p7N15H>sZ@~JV6aStY_!<8p;-3;`1QlV?eWrc~~OW zf1ik%XhAHf(2q1GsmcT?zQ%Oe9L_Y@i(zS=F<*e&kGXlSmwrC>u^X#1NFO$OTCwwc z#e4buGK!+GKr3~-k-7(*WG2rIYLPl*t~ZVPoyovVI0mpx>;QX8&N#Nia$>w&a!^5X ziuN9=-e_oBZ2R*);tba*v^}G{EPTz6{(H%Uy8G;^pXj+Libm6aikfA_n`#I0>iQ~ep=+gN;m67`1`t+NPzA=mL z0B7ZKPlDuT7A=aa8P&M$xiJYob#d?eAt1sWiQ%^P#k7L8PE<1^50RlrI8Xb!OIyEyEKVMAhJWGm|?HNQ76^K172qy7U&;9@P9r0AS_ zD~4w!Q5HDkM93}d^ASTH{C-tNdOcHS1JVGpI0iC@(H#fhyU&QH=<{l2{=o)em!UcO z0zMBzP{E_Fq2~lK(UskCo35aZj(t=O_BZM$cBPi9QKOOQNoMu>*);EyXPExUI2i<# zyw%~kKS zQzpCjlx74!F}Q`cz`pm?lYHV@Q?%Bv)9$vtr67t1LYP%ay=lq`ML!iL1OL2UbzxQk+Ah#4&|J2iTxK&-f39*zU$V>)91f107U{FUIzHR~At(Mc)tdWZYx=I15qP|mwVDjXCxwAn;r=-9 z1zqGH;f2x4pEWbfr&WzWG1xbp;<~f%2y5Zr^wDGu#+apP2FfcPndxfV-U2769F;}; z4f57ZIGA%1$;RywZe7(8^SC5PoXX+|+^&|bG^{9Nh`KAMiTZ*sZ-d_Rk1x4My3SUR z9wtzGSR;(=cWMab*!LMtZ13x(X7;lyS$3Q*BAnEPUB};aSQ}bpfcoD`+UMOTma;z0 z6+}fBIzP-frrwbqUi&m?4?b~y;;PZ*XIZrPg;yT=6MWs8Z}7&p`gbR0k}aHqxcG~L zN`_pS+^#q%MsXQmQTw>*r*uDe=tFI?5Jl(KcF>UO;^5PU>oRG{iohh*#}Q2q*}w^P z&J`+#2};;*W5H_}(SGaeq%fDdX@=OKInesznK?qEmk!^YO#@bejTLrp^O*1!y*}3+ zTPiuz`v-ydsR8o&yyW4=lz-7<)PbsXKPc2(`IW17I@7^+#7!p1gU1-YT!10NoHh>* zk#ocIdSW5cJrAb51IXvZIifx0mLi;(SKYzn`qf+Aoy6``X?H8PcLbhj9x>Z(_4#*v zuL(Vw%M8iey zQkrL8-z;ZfJ`vi2UZP}KwYV-(KIYcyFpet3@xSMl*!HiP=6&C!64NT$EsYMH_)9f2O{|it+`SWKsW7S%?tTZ(HOH9)f zYyXHJQ=;A#c4WGhGwnW+=euEz@+Ao;+M5zAs#Z360hOtRF4%UH4nkz>$vP`DSL0Na zsG*kVR6mrg6npit3lJ*)5yap%gK)_LLHgtG@Up!Tg#(9pBHZa|#A3}*bqYYXc`hu- z(`L~emFzC$lI0lMA!S8Ub$?P}cW18dTOba_$@@wacIpI|^fQ|yU@wtx5e(s290Guu zmv3p=j_lhr6*pxhch5XVxP;@p1k;9a9u9IP<^Qd+(qq)^1i_tU7j&5DnsGjeoyoy4_<+V_hgi1S(xPS@Dt zVJwK>zC4*Y7@v$i1O19~r~zw|3Ei8UJX^di0uN_K_V%gx{O5wLd4d7&wX)~?x`T-G z)pGQv`B;u&yVk9D?G+U|?_^`GD9d|`G}_DA!_4<_D2x&x)~FsQWckpu^&eGru@)R| z`s}@{dUsKUF^?y>?pvoD>Ps@wwN2>r^}t6Pc0c=A$2T<~mUzW~y!DzLNqhqCfjMVOJS|G*X^DSxHbzoEcSGh#T zGM$hV-ENCCa-rrixtq~z8u-pu)YiVf=O@E|dG z)$YOQa;j2oQ2I+S>-90v;X2xGLVW{yZD(Lbz_D<&bhVPh`^YWF{!vC@yzAUH^71MP z*Rlh((?dAlMzUGYBd%uJaDo_QGOT9MuQ=lhdo z9KG>CY{|=?io1jIVaY`V$5k+%Xy8Glj{PtD)qxPhwHG?w-sH|@?7D|jHn*8RUVlj5 zZc%@f^$kPDk(lsOE2I4q&S>x>!y2gT$>wN1`_tsE=|d4`do;W~r-at`<&*+l+d<*f zN9{rxiOm*If|EDDcxQUP5=ips_q`F|9#$jrUV7-jP zNf8ZlIXVBw%3GNlOIlCmQ{_uUY{RV1X+$a`$eE;z$i#SGs%MS_1#g}n-D70_T%kJ| zQJesqT4IGe!CIP4)Ng$ez6q5f^q{I`t~P99&CrH@b;Yys#5wDL>GE7qvaz7pIbaCK z+sz3%@bV*0!?_*BP+DhP-2}x;*ZrbDSY4S^4LUy zu*3Q#B{O2_D_mZLb0mw<5|HJ&G&pu7zLIw7dc_+Dx|wYr^B>3i1|#Iah((gLCS2JZ5lL5#1taBW4MSz7PTgnijX{Y{Nz_)fVemzcsq z?S^Un3VH(K)v{A~QU8fwl$ZDO@@;nc@ed8X_xg)&cCTlRD4z&=Uyqe2&HGAhPA7NE z^N8KV?WyQo`Vxan?y8WRKtBHznQrPCj(QSlz7c`3t;=Jd>z>-EYFHF4 z<5swANYvuIOPMg&ufSW`-pxkWLswo&;VN7Ly^S(p+$0+^Dr7OCRjt{4#0@zzao4%-ckpvk;}My` zFhzjew0&83Nd=d2&C%PZUqNA;g*CioLaDtjdpT&`M`ZN7!Z#{m;u`%o83f#~LHEIY z&>ed{kD%&UgXQphB#2Vl%Cok_jD0(iy@^q|JsSnzTD$p_ED%J#Dr7LtY8HpL<9c7W zL2kcBF&3M(uLN2XK!?H&8wG;F*_%Hc@=jbch)$*{p>s7hdCp2x&hVOw;LBy2%{>!6 zWg;uI7iO zDd%T6G(4Q}6ZYj^`rt#?pKg;Xv41op)D@J)RZVV*2(9^6SMGQo{Jh`s^x4=6-;|d@ zaM{Th9nmd>HfV4>oYp;IYf?HsKsB6w4KeUYaupr1KW|PjeL8FKlXSuUMB&29NuiRt zjFEDfGhIaf`m>x_rLr5>iQM~nCS%PLMT_QjmCO1zSNqU1Fiz$C1ipfRo9T?~wY3FW zWkYXXFts9yGd|4hG=bJ^$;{`v@ol~8Xa-`3)~doQw3$zWXr`$V);|)J z$slpGgrJQ zPz4+{f6bqOjBSo)v{>J#+^^d8q+EIFN~T0y`H!OVqRO8NAy=r5nLRXCEve<1{-zDy2*{DTR$7lu-dN+ z-k4%v;}a*jEmMRerfim4A{4FDvkcu20^^c@jLuq@Y)sU@BIDG$KUrZ;I%WmB=d(6Y zciSlh45IB$o{V;sc1E-czb9iVjv!U!XE2)jO8O1Hwv<($s}9XZEj5Vu37_PB{YKJm zkDOtzQWWkNGr}RZM$uH9oOD`jOchB)b7-;Efaw+`HYrSqFUn~XE;hjv9*TvcCq?a?lDYLj2Hj3#TN5Wv9M9m7M!-X9HQ7QdTz>a=oF z>@;rM!XMGN_7L~NRLp_%$S0Vc zR+!H`3?Sp!ZtH0E;pmNY07ZX-CzGGc*6WJC>;#KOOuivMGhYsMOtG~Lh&l7%Ctt3% zq~EaVmf3e&TaMT|m0jxUMASJ!$0-?N0zq4-E%`jc_lR(``3h>e^U9TLV6q|si>qF- z8A1_>Wi1iiB99N^Ew`9pWJz>MqjtyaXnwf8b&;dPE=&Eh=6mKOv7@`+cbjIMt9@2o zyr9@XZdf>Xp!v?$eDS)5)%xvHBKyf+5$&G=9MOX?!RC-+V;JobboUm7ZZuBgl#d1t zaZ7z*@ii&@D4(>vp~@)BTA};K)RfcRng*-stH>D|^c}=}&5tT$p|x-6`bH7?$dUUO zY*5AbWx) zp;v4fw!wqGoSp2HbEL5N5m~(uO$u?JUMsU65vW=SF8aC$LX}e~1}^h8NhCWm^#q)L zP(r+i+(OiHODCrGg$cZ&7x3SUc${@4my&^=&UU9G3K9b?(Bi<|o&Sckh+viw5kLC` zApJ0r>!`lOp3I)LxnLuvCn(St06t6<0PyZoQ@_mxE7)cJxX_m*BwvT<)JL5NZqZ%4 zRrw2ZTc&yoG$({bEywwh^mli%oC(eE8uulxioCiH#^ObvxrG%f$!?(0MU|Q9Fto&OAauSH(gVL&^o+f zRV+Ujl1%(Xa&2!~#r&fS0rVx%Q{6oGdBkjku|^pzm)tXY;4jX8#Sl`^++?0l-S&H@ z=ajaO0g*w%G1pE;nL8E0&^ z_=N=ZQzP*_ECT5+6P%dc?Jsqw%5dF?VhTwB%{@pEy#l|j8{dFVZh!?z#9Y9cdgX>u zrgh4TObmw)NmMYFVnXQ~vG#Kb{-h+nYo#MhhXg7j}*lA8<(hwV--T`m5o|fy(y}YP;aS`91N@*TiE#;xE}eELJmd zJO8!3!lk>R)A;YFrzGZG>>WNkfFGV`K*b1WW6v z?*78dSaR~)O5qpq0kejRcY)=-+3gcD-1w$!g@RT|5t#(HU3U9N02r(11Qm((g)=r?H?;(!Z2sG^Sd1#nH4n}q7HjSuRTT3k=S z7vtPdREi$^>7ViktvdfFv;yCZyaoRa z9shchMv`Y>!4m1IC4jHfDAKPl(_TZAI8P3W{-$nUCK8ayKU`n< zH|PG!l>E>BWJBBkuWa}`7X9lhlg9JNyV*y_aHyg1$5K4JWaydT1w1i|^DglokA;4- zK7JEGRxy(m%}4*B_+M!QI8q64sxizyRqf9^#*qTiu#pOU`Wvy4et7X3B4jAqxb8o> z@2~9tzmMs!o-a6CujfwsCFzd&^kBh}TvK~^yw0ZJv+66kFD^;5dE_NHuUKLIHdXWZ z|FOizchcaMHRr$3Wcbaq{gw83=NDRHZm6#1k@|nmeQICE`sPP$JkY>3q#e~&zD-9P zGY6$1ztPb*?*U;=%eeEqbN~BEKH&rt{~^kRN&j2N{zJQ+pPIu1dKHt}|LYG;b@o5k zRJ8wKu>UFp)C}js!b2&zE}1sJmZGzHO`SE1`QpuvEu3PDtyEwZD?~P#{?XS%?;(l* zl0W7CgSOwC`|H&nOx{3~_)19_C&}=kFId{;6XJA0&^mi>b5h9-3$13;sg~be7J+qF z$j7xWd6O#o#T6uv6&cik0lF2BKMa{X)peMG`rcx-uUPB|dzJnO7<2wh`0shq8NuF7 zM?>RW_kKh5_nhb2rr_Y~lmG6CegV?D|4N`|xExO6|Lmjg*GAy-H(%vM>ee}RkSaw6 zY4&;JlP-oy%bx6P`hiI((F_t-yP^O9Cfz{=d>r_RY|y>_0Wjq2nOMCA{VbSa+x2Ah zO}cMd6WCjH=r~NXf?6nQD-JMJ;V~E$EPzOyB5AJzz2laP#D08e?{uIZ_}$LkiN8|% zkK=LQ#-}8GN^yJX0I5Zl_Eaw}r}i`DB+Mw;v7(Y zsir7;G;5*aZ#bH-Beay0o4@}Y1)BN8Kn@{_%b-36z#~DA5~0g+%(D+XX0}19mCm4@ z{c)$TaZ|SSlB{(PSG!CIt%xDWVyYCry%;Cz`r(=GdfoLypb+N@8Hvrd@bz9?Lo{>0 zzwu8B+zl1MqqdyKfIZVvSHQIn4%Fpk7y-E~mVi_Ki)1>OtgSYfr2+~mmrl2)BV@Zf ze)ALmm8}1=JL3#eX^i~0AV=j<7BxGn0>=&oM@G$ zH!T%JDF>!S*PRyBQ|fyoTTgAONxJi4Vn|lQu8rI4N&vkyzh+BNff0p(#RPC-3o2%F z)Ci!PW$NHcE~B11Ri_6vb<;4>^#^B@h_9jOm*JZ7cncKw>hW%JqYp&D#?ulACM^H%ti+}GnTmU`Y)y>HrEZ4>HN z{*xpBE2aKMO3mlM0_HI$%(#%=_MdTv;8SodP#qn@Lxf>lK+;3MgSn%btgrJzYoVOm ziWR1){My~FZe?YI@%`lly9~_|vgC^6)&O$JOz`;Ok6jK&FDdP%xJ_`cxl~yz$rb3P zyy#C;?^Y612;@KOV9I66={$j8jdSo_#wa@=~ z+#!1jSCzlPVBNmzF0v}Q;%G$by>3w#!HeO|={AMMO((mh>u?go@}qYLmGEG>hpt9p z>Kxh}4Lz|8&e&sC#WJmkx zWPD^{-R=dIn9!1xtH4eFyOVkWKSL2vd1#!bBhueb()T*WLn6^bzc)^+_Z}Ox@i4J9 zylTQj3_FXg349phYrBluR8|W%R1Y9y&wTx3g3AY0G3yo+NP(&w$~&s&fnXIS>nSKG zb_?83QylWZJUW$j=+?jwZHMnK=LL`$P${ugI%qAK;Uw2a@*h|Lh;u^EEB!&}<$ueJ zUz?7Cm&=mNavPXg&Q4~FO{N6}=37WfvkFftW*48qU0p}}k_Hy1U#ko+Mr@n}0dl7fmUS2U|zBygzTycxd(LYbCObg&0a5zlc#Z1`_{&+xu07HZx zfEG74FI=Mi4eTje$3rn#e|#Sd2$l{DD3gEYZxfDgq$=hn#mZHKE z%fA!8aj(XUpo>dYccm8f9{!n71#}VZI!z0{AUQldC0bO2r_!dKRCTL&xho3Y`@ZKd zi5+reS{*&%KI(Q%q!Tlemkej=iiuI2u6|D4{V{vWAfz)CdrC^yH+l548%cOldqRuf zboBGIJ~Uf4CfsIsF|2CY*Wq79B{xJbx<+&X@EutI@_M8GY3Xlm%iJdt;1YaFy`lEf zh_RnpAzpU6nlQt0$9XBY;NZ*jE0R7mQcPU*_*-t^ot+?K2$(sP37UhJ0Nf#dsaUkm z83q-ins=CJi^TfJ+OIU$Jv7dKmwTpDvl4PS}dos1nMv=Ad zOznBV5X7ORI}g|=7ri5tBF|15>I$9_b`cZYea)uL%o@WUL#ibBq7N3MXT2mUbpJz3 zku4sgU+Fc6-q!GGtU+*?C%F!raZ18uiAY-Er=;^eaWtpS!}XpJl;iYz6#@~3>J!t$ z^6bZn8Rx&T+E;tQb^Si$R;p@MYcEgV3$`0SFJH411Od^ySgMiy7)@?t5Zn>SoV!Qv zyh+YNaCS?Wj9sTzBbr^8QJ}Z-!OQKm#vQ`^JKD!jtL}@fc$H%SpP#;JkFSrFr#m3k zqKaqPed4aRb ztz5;0(q||4f7#m0%-1S-P5#aNo&VGF{W-7yGC?{r_Af1?cTup}6YbeN&B07C0aZ6s zg`{s5iV!(>TIsr)iNx+E2{}Y^_u*l69w+N`EEcuP%AH{^pJHq;r-Av04L2~;T?aA^ zjyo=$DPj%GsHWMTlCTKHiFMn~eIq)Ous`^j-J@qYd5D!3Bh0#wi4FhF{!(V{EeSt8 zXib+YsM=G}EPd-yd$^W6P-?fHu_p8thS=>n>vA+*F3^eyB^LUADY2vZDq(c-vh*x> zpYSW@-o$(48>AWall5N0>if@G>^G(_vnQTmL1?UZt)O!wjypZjvtkaE(zo3jeI_{# zZw|?@=%t+TyeQ;FKtL8r1!{AhT=v4IH1lGwA0C^XlvYeHCm9XYbxRk2>Bj*{ zttaM`cC_Yx_RbxSB}B*KG85ISLIc&%l5b9vJI)jk!FzPjG118AP1Q;t_R219SV#7B z&B?e6pH=t}_a+ut5-eBkc&c5y;;v&A%c;Ru+XvLLFJDf>TAkWY5%mlcWmB`6n^s3^ z&LY^2iKY!euCGG zgK);BIRx6=%O^dAj?yaYVRf^SJ=J=0%8y9!<&da9eZ3q1T}ZYe-{nXKVS`$*@0nKa|{JvQ}-mH;D-(k|O$OCb}J%q&gJZ zghu6t^+No7pI`LhU}ZzZUXlG>sO|zjwc8&XF_nijDsC?3mGpUZnT-hTE{3`Vn+pO1MVQ0sHcy`pik*6=>r+rpYJZ zeRKV62pAZ`{$1y!_rCGN2xf)w^@4_RR(()qW!?Xr+|)|9()EN_V1J0acJt!pWBiuW z^TknX;wnJbT9Nem9WN#;R4PwD&EK(DcczoJmkuy-3O@Udy7A*edzAb z3*V{3U{J}0I;@1LR{15cgXhWoH;T-V9$x41>R%?hcnsSH_hdT`xeOSCaW}>NZv4zU zJ+f*dvW6+}P59z?%HK9$XkmAln)eT#-`5aBmYGm6sE)vknx06xxDIr(NGE=dIdq0p zwUT?K1rz%>y{ctb#qVPEu^IdA6myoazGp0sW%Cc!|NmYK^m3)eQa%x$5qB6 z%6404nD&qYD#12ex_4(Yt{I*rwv%*w2t9di<7JvjMFR-Pl|7@ZRqhsz{8cW5$QYRQ zoDc|W`e}Qd_8jYSnG1CpJ$0MGvON)p890PHB`rm%v%z18xkH{X9)vqgueSfZdb~%8 zT3Z-F){MXc3Jn%H$TfgdKt>;S7(p`z{lh`qQ+%(Tl^IC{Z)3? zusZT|x-!-ZyR$#qoTlGeY}OU|K&5nLUl*gU=c)-NAZ`ehHbS&|120E9?a%D?5z+|C zY&A4UO4iHuLR!EKjlEzna6mV|?qI?XRqlK0PukvTx9lK(?)C+<{o#_{x}Qh{3l;b~ zs{`g2KcGM7b_X5zI+mAgCwqgt^-HUQBsk^Si>7S`A}v*#N16>rNw<1j%hg-nqnfRM z25?4qwlSe~_%==%)LS$B$1J~kqsqVC^89rJ46ko(YZepTzDpD`z^UfcH|aZPw-Bhu zC=6>bow@YuySX+Jv-JFn%qzI&(U;tUa1r;dFn`jvZmKq~vTKh3f?NXd-w?@8D%Mn1 zHs8;p_Ps|~5*^bTnT~3D-~*O?b+*S*$t-njxH!P)h~wM`s_(Mi2=Ult`A z-0qmImm?Qh@;6P0^&#!bRPznq-3U;_pjO9ZqO=9Oq$b}uVWDoXA-s-qIq?;@F(n8( zieG3OD3mTc91MJ}yLx>a{;v7p(T#p{n46xYqg0B(rZJL`#IYGi6rQCR(eyn3R_(hv zS#Gz{Jgs|WvmfqrQXpqnSf0|AZl0LWqJ+K4AHLCOJHyySI;4KJ-3d;`5XhhTzNCcS zpcZ{rZZXN|5YLsqu$uKlOYX`m^u%zk^XbSI^4&4jfapmJ>El9(o>HO6P%X_uZ7rgE z&`|b-;5F?-wJz-`51-3w4~5}sR~GhM$luJFi|JICUSAu9O}eGIKurgVqY~|%Za~gP zb!u=<%M22DXRw5Av25&95GdoYuyTs0x5GR0;wQEU~lw?3%- zSbl26C~Tmpop!x7Vf$303RcTVk&;tT$E=`Lwp+eWx0}De+r9hF_lB+;HyLNJ)?%7f z1YvXVpp@O(Ate1mMH~A}6mZt}dd9%6t<-$xO?=!Hl|$i=cN_pAAXI0C_g8nq?-{lf z))8R=zL2NzVMn@k?M~@g_uguGdEK3J^d{9W}&PDPM=UEnBy&D;d_+F1#{U%xR8=*>IckT`QXEK*UK_vv!e zviOaJ0Fxp5r}cUqEwlFu)doUrE`q7DdmH1^Ev}5Im6Z4kCmwCeM518pla0I}32QH2 zH%A?`gQ?xJKRvI!?@QEUbVEr`Xj@e2fR+PJwH(#xV46uYd)>nXt4p>6FR~pcy{_)Z z;GzXS4#;0|Nmn6w(A6H)ouXVo6Wlu@$sCC-%Xeqh<9UiBU-TE@< z=d=RNsST&cxruc!0wG~$iMP~urLgvgqq{4vts*w&oD(JPN6#%DG3vX}YtVfZmaEaN zQ>TEmILx}Fvy_+mwCJYEV}D9GykolGnw@);isfpLsAQ)5*fOdV#c^+&P1IpYR)m_b zR3xH(_dXHixFW9HTiOCoOx=8T>+Dm5-5{BOd&(y=-88S36gtX+sVBNQ%uMoP^?f@z zbfcA_ETl`(AGo%4lO0l41ZD6D!yFo5qNo|_3nZnd&|yDcJ1$A-lKb#ZS4N0X-+~W8 zrr&DF;h6rSpj7Bgs-0O?i3XR_SP-gkX~Zo50Jy%{3Ndd^$nqoIF;+?@H^N+hT-_&m zvWI9Y>D!&TyWLt?u(;(4PyLrG>I^O5hU3HAb=gYxkqsjKZe!R3&p08`^?nImS2!Wm zEF}g#BO)5swS*R}rR4okdY+Ttt$4ucyTcd~;)J0Sa|(>Cgxe!EY6ZT%+nlJ!Jqi(9 zXAi*^1je*sjh#u`g-4y|-lkO1z@|rR??~tSTC-j1bRU^fr?23JSbfu2y21zTI(gUj zU^)NU(XNxB#F(h*2%Er9znN*6T-3uO$VRNma*N#Ai5_RyHg{(eHi>dL2TH&=)%x5*c^OCiME9!ck?vq^WVL>TO)nWEb!p?weG>OMm*J`)z`aR@eyU-O9ew@Lar zx+@n8_g^r5<#6n8weI?!4L(WC#*|@pKjk=gYAxto_xj`S{&=Th@tx9u05Q#XkselG zCmMz55?M8Dn0A~l7gN`<4Lh^H;8zz?TpK^RGxvlB^01Ixd+gLMXh+{QTyyUta)fh> z^zJD4%xB#KZRxpG5lAp~GQB{Ofu2u~R|Ca^ifPvvV++*hpo~>*!o|-Ckmz}XcBgi- z_#LY<=p>G4uEWMQnU41+Xr)3}SwYtR>fk&M+tGw1JYt;dX<%7LSv5{D!u0 z;rmmmPU!L~==b)(waf7F-9`k>Q9w*B#U6fYpS<@u7^4WBwj8Q^mb1wr0IZI2czx&q z{v=;rNsHHFqimmz8yhlVJN$ z6oo3EeX`%8&9(j66Tht)qbyBxJF329XG9mZ6jNK_(gG6Yfj}h8=P^boBO(Xop>CKM z=d>8+rL6GbR~KHB0*6Nxo)gFii4L8XDZgQ}8}5HNf#%docdHHB?P1Ek+7bG#vK{YH z7_iSd*xegc>h!+|x$5IZlp(fRS_i2sMKxAwL+T_z(IP`uqe!&Qd3cgW&~|sAH%W+f zO(FeZ9_Q-~+N0z3I*4a!cfNXo2am;+Y99}TT&Lcp=uG?W$Li(yb;Ecl(}&hCFqs$* zx{1z8kaz-VJ-;Vt?*dUD)I7yw+~1_aJ7{r=Rr$r*9np1eDZWSZvmsGb2he1t(zP|K z<7lC+(9~Ji7?a{i_;}Mxzg&jHVb}<}woB1=O&h_Hjhf!jE3W3t42j6G>Y$++7O52D zTDp`ZL}Kj0H@sUUBUB#jEkD*osGF?tyy33f%{HN*rpg&~k8KGMkcMT-I9{lAEn|A0 zt05WXl;b)()=FDrjjohe^(CluuUMKrI|{L5#uy1!W_|#;5h-Q1af!}Y-GZt3*^C|1 zj9AV49wF?Bv!=%~$*&ITuc^bCZ(3mBL`MxE0EhR~MGsC`Gj@7!u~V8hEcG(3)3|t# zi2a)CEL^B<&a=!coi_QdiqT8z+Znj{adJ`YtU98nLvlvW81;FVF)W!9KQdVC_Lp2s zU2^(&y4JH7=0;x44!q;UDTIHstpAr^xbZ5^BF1%vfr8huup;#BOpe-2?AUv64Ef@O zka|7(squp|mrQxo3*Kir@S%go6q7i+o@Za;_t&BQ`@IB94^=~fr+n2YHQ?z2G*Rr% zDPh~g3Ju7v$@CtLsR^0*El$nHmQQc(ezl~%^*fX!52lDevcfeiOgB%#Y#@-xOS&{K zjI&H5Sjz$vLFb_-!MHGK+6VW}M=Ggg(ng)d@oCT44iZ|oQ5bh+DNvm!7j}2rr|a(S zua8E3CPX_WSD^e1M-eILsK%9+!0EJ@i6Cg#sPI@~}frM}|lAjS?l=9Kz*FaeSP= zP6}zz;&_M}iqth$PSt}i3R3(H*4?~EFw^YAG#T|uvr(p*gOIlL9udCjmFFHSM?uGJ zHfROD?(YIh5eYpQR-(?O$uWwX?wvePhRo9s}v4U0M6`2IRx6- ziW_kmw^$46C;E6lQ0X7#7F4}{sFu5yl6$aGvg=R6pq`_pMp6!?8}vK$I| z7+a$sz%tp}!`@?Gk<>rf2>}Td~R5PYC z_TN(&pXc-FBHE92XZQJ=H0%vy`4Xe=znf7N#+Y$BVKGM_y@F*WAm<1duvvC$2awb9 zAQM5nuU+pjfXUUV$*)Bz+Ca#Y5ogG=;3&1+*Ul$v zFn;YpBm@~!Scgj(eM0L(|D96Zedqb=!1st-U@OKfpOXB2j_Z5RWya>gt}Dy9BRU|$ zYM&$)X=YK&@Nfhe_{;@T!#eovosg-~t1U&OGZokG7+_o8rNUWr3Ek!ye}V|ax}I$etj7xbOu0j7D4Uy7g=lb)Mt+& zeTe|iC-w5xu80uPh0HRML}^1KnwOBHPQT3`i!;mF)79O>qdKhQI>n|tQr6&;Aoh;q-ivk@i2o%^!>L;F4RTu z5thtm;Lc6-is}pyvczcOv6ce1_`znds|-+v(!uau`-y3iHB-lo&fNI0}g@UvoS> zPj^lDfcsKy$opDi6}WkY%FJ2s(Ya|j5#D*LE;es2d_CzHuJqXUmw0Cc;!V|AsxQ2r z=H7fDMc8l5vkbl5dC^U#<6txtR0^xZoPsJVoyGDV%t;pqosq|ck{5lVIsIL`qS6R3B&Lr>=q~I*wxfNc8Tv5AJ(@HS7lP*A z&7I%X@IR?-E+h4eXZ9z9tB#8esmw?El_KyAF`-KNBx9LhlJe(?PtA0xxE@A zuzPJagYjy)84VN_=YA;!m*gKR&$a9}?hMurgQP7dw{gc0hnaa;>Ulu{h}DnfGD)K8+y(~q9+erw zji4pRxdYk8N}R9&&N@~4{0cGLl7^qbAS;*_#rJM~ zgk9>*SxKIDg)T=WeZws_4RPy5Jo~NG?>CFgPLJ&#LuOn+XK?1uXZP|qE<4YaFS+${ z-k$;mTRZY+>7eap);h^5)Mmm|z%y;<4p8?blJuaC{R#E0zU;Q3@4rdO)1Q!Z+?>aZ zC}=NYTxr9ET&EP09kX0iom{$UehL#w~#h`^cS#)Zg5Zg$?!Fgg#pqnw_K*GR(oCzjQMlkNI)>zF2ilo{6FG*al7z zid=Qf{0f&D;$H&Wp%pZv{WIQ}#FhbcEZ_PnNa9 zNKDQ7I?a3UbXP9ureJAZkwI$fX^8MfFZqtV4fE$Yx11vyg?>zSz?Fno zdv8a$C*Fm(1xDu^B^vCsVlHsKWlO>+fV!h!n`C%X$`4rdhB|q-bX2 zHtVIjigwRSL~Gu{Fo$8E6 zLAE(@Bu$^!fIOIF?D9?Oj%M3|ue5i+cN&YEyrE;DUU*PADN8>4M^ zCZc3RQ$SW$k24~)i+RUs;ZHV`TEBFDDrh*chg4#$<3wz-l6RktYCC}3tDIVi%unpw zp0QH}*f7t+l%Nqq;AxS_%URn=^T!|eOptpL=qWh5*?u|bl9IYppJMcGdEX2Bt1B_6 z*9By3`i?caI%h)*c2w`tTeBFSEBreaCpUKA43pRSP9We_8iBos?Uq$(B;~U$2^sr zWA!5XGoM8GgX>r7pTCQxINOd5UJpO5s+A#uX6(TS=7O|H{$S)s4e#cE-OJ)Qs3oYO zsvnfs-~yiI*ISzf*FGaHyP z=ZrjaKj^z8n3Em=C%^ylYirWnjVoGH(>fBC4i0$XI0}{yo;VI)aL{wQQ}^)mpI^Ij z+)Hy%hukz`XD#$NON`iX=5{dJ!YtQy3M8CO^|Lk-;(faCkI$!W;!l<{hFMK{Ons#O z@tY(1skz67{Z@CtwUQ>6`m~+t6GMIxT}{NjNPOY%AHs{i1c)+ad+9(L=_Kd$a8`Lk-dn58eXs9FJy3mxQ$=lV1!E^-5)87hJ9o3>;t){(xnIO?@v_PqBt?E^({`1>5`>54)mc`BS*_F1d zWKA^zD`Q)PR4>AqrH5Y+)KowonC5Bh@;dh=IyGsves14d-IP$UC5ef0k#-UMqV4EP zF_ufNKqIDGK$%kd$f5IQoO-H>vLIu!tNCMQw}$Oey`c)T(MMq!RkSu{BRNloP3N9c za2t>C-Yd`yOwb~qc3G_48TxKa6jyM2QLmaxzVlTU-mB5*z$?$ZXMeZ~)#b1~5@Svt zPx8o(r9yWW^JYubDN_H8 z(s$RWJ1w6o!G1GKKT}S*!D8AZ%fd^h`(V?wDUhPL-rBR}J{_da!JD9}cjmJ2_w=_E zB2Kn-*udWJ$sSu$ny|nqQ&g$GJv-Hpp!9s2on*JeZ4aX_ghFo3pL6ocusf!g3FCP( zi=$YT1MhH^K406P@5dzTx#$HUtW=wtV-H0HXWzws%UkJq~ zdc_h8dXN#mArI8s_MF%bkK#xR$5yVU8ZQjti@JhtV<`(M1>t4EeUU}yWKjdDC$rlN z%lqp?FB0!suQXmfO5`P_>X)L-d-~bVlIRlsg@1guH}S>!&o&W_lthUvH_Ngd26^+> zVoXDnFI}SF79qBrnYcWUL2tXfz5bgoH{wupB&su#lw8=~D>K_O+ZdZ?oX$6WrA13D z-rOFT$x-L*lgkmXGNvCpc$3IafpjG0tbnX=E7(e-iD55k|HX{x#) z`)Ej9g(Sb|MPyq>P+ck2hc-)T{Oa^)lDCO83RZ_5?4H(_)IA)P82g{)fj1!Ee`R1|?pAcruPS9UZ4qdMvIbkSvkGCjLiB*0wjKCAhO z4mGN=2lm+7LLbLG{;8KP;JHX#BJOjSIGkUZfP%I?{&y`yYNwAE9u|vVz^P|C*GwPJ zQVT78q>^@B1GrI23 z{_Rgbc^h^C659Cy2bGA4;fUyOV}E_DNI28ruGA2qz?b^-XN&TBOupV(Qt++jOaHvr zMdCvez*}@&M$=G6<&!pKj@vlj!=bAei7oB^RG<#~Rle@OVXL?77qjsYKQ^iD|2&zm zG?(IzO@%F};wzq3+oeB8Eh}4-SYZPBix`~tR`SIz;5v3>e@_&$i@(V%E7||NWe+H% z!4|Ic)ut+&;(Z_1*D4BVryDEy3cpeHfXJWB&b9rM<)7i?K-+7rjk{CDc+iD5?%|V~io&-aS(ZAY0w{S*W$$%IsiCYc{^lpadzdA-XpE0&AQ!!Z`-ips zw0`pt+2BsILG6vYarMjc-McuK~ZJK`wS%Tc z=>JugB=2A9yV)nC+jA$iP;hEoCM>OsS0^Yp_jqX=U)&2Um*aUO)QpI+uVzTHnc|=K zAujX+uid^;LhU!_{<^9|+CODr_Ko}WD_97+_CaPF&MZ)7{6GiwCOnDY(5n=$ zXgxk&TauHFXQL?F9EzK;+gy0bvZ-T0y(W(X*=Y79Ds@a2OA>{8CW}I!B{?6YHMIsj z-&X$V`a8z+Kn4M}Cozyp0=@zS13U%S^k?SaN_CdG+A>^#F8zPhNtQX5*uiWi&=bRc zZK|^Wo7lL`a)yL4jFMf#=n!vjb->3+DTy*`AatJT+o(9~(a~oLs5d|McsTd-c&Xjl z#fPeSCb2H+FvmN`pQMf*l{0gOzv-9dkWIT{=1A;J#uKaYE}Hol8?sF_H}ZSfWGi+P zQm3;hGFR|?52O;f?||u0ZZVTj`RPSHq~G$vXg9WqrxHRJp*Twnvu(QTFCXn&(Kz$u z^T}kXg~*l7S!q{cTxEDjs(v5Vd zba%&H_`c`-&N*N1_c!kS`;NgMdpI17&F8b$v*t6OIp?BGFvh|R#7t9c4;@O4%F7mm z)nFksIA0_vPy{h-I}&o?(lZsdw!N~j0bK;Q(*>(?u1aEfmej)hsHJs?Y$Rm@4L0oN z(z?ct!8^Q~tP;kA#FotGAz_M65vigoyrUSt^p0-$tUiFykI8s+zc-u?&tZ2e^aYs= zZCcD6%pDq@@m4Tcv%+TWJ6N6LFFp9_6L`cQ} zRe22pp{y*ffEmU}zG~%iFid#VcN4VC)-(FWRe{#;kDKBzQUw7%QlsTd*p=l9Zl0bOM=xs}HrX>|5(7T2^av6V98-aSSMsS5EN{QpFCiye zY0Zl~hrkCYx6tHM-dY6?uCoUSY8ns=@rMg_+goz?o&Q>apA>x#`MB+0-^Q^l`w`Z; z9QG1l)nFYI{g#fXaaiwXGtbBB!*j)?P#}%NMDsb+xxO3{5fHdo9@AaFX#T3=**Tlx zV6MSxO;C(_zNhQyjxJvS#KE;iDw)r==U|#xY-Y0w+{at*ChV3NjV?wGI1eBbLOx4I zaI>VP_SddiCk2=x$+a#K3R%*d!#7$5`)u9Oirv1*>6&vJugVRBvZ~ zu-~1vEbrNCO3+>HPtM|Ym|ou)G4~zuo6;^dB3tgazDn=JW=M)C+QreJkf|IVC1mZ0 z@I^jE7$RT>tHFcz;mRn%<=Z!$)`S9g*z~`i-G4rh5zQ+txc#0iH~n4Xpahe7g{TIb zT+F_`KGnUrI%g5}f8pbC@$MGMFO*Q=MW7wmOPW=;R^K#uyB2gA{OgM%7UOl0o|M+3 zUZU_&JJL+uWM!<@X?LwHDz9Z=KO%6g*K{x|O|9XI*xmVj3$9f*vQf(NGQc?j#Y=`zRRzqgx#spd8?5;#coM{YNl-Hq}D@pHE_!ywc~N8PvzMc*7jysv@i=0n8csQ~4NaFJ-O zbMocVEGaU6Wv{ufJpD;N>I`&Tt-C`7cpPgd!O+OpAB4JMsqCu8ST#p+G<^=STLlI) zWn8HFL2#a%2tyvZMI(7k=ax&xG=&q z@P$vN2!1JCeYl*)*B%mgjOp_~>+jzk&K})e_e-~~Ip^dol6rj!2~VEGDc}F}32wN` zmgIPLTGYe@KI3he7@IBnLnlq|!y?^VX`tki6Wv9|bCt`Gz%(s7({Y)tJ1@CzS+wsh zHI~H*_H6)49JDC@{C?EFJjE|asAnk%-p10;2q~{`HNO_o=ij^Zwfyfov(KJZ=^24o zgPuZvH5jkTP-)5TtcbaIzm3LWhM8ecB@(4Cjh^eS6Ew{u@IhEg$__P_(iSy-y> z?o=Mc@Ejq<;6#gSq;8qm%eK}?dIWNt(Y-c*<8Iah$@pIDr^IWsCKrH37ayK++KJ{5 zaoUmMdHCq~;M_M2FPY0FoL;x??G(xr`Om~5d?%nsmS!EC3hYbb#?0K^;<6izoUo&m zeehdeB2i@iKx^skW&v9`GWHL z1F>)JJWc~kw)&=H6_#dfSieu!rb4O5^3klA$|wDhu@Ngp771}xMKLT3Uh9~K`0T@# z7^|+vl3ecc*?N(m2WKWuRSfQvh^L#@d%@*swMq<^TPHJS!D-dleBOEbwF8;%m1f1a zlL+J9%=FF(nwgG%UH$eae z3{U)zTb#Bn@6btF+oB9UT%c6jgYrF9yX$(#Tph6Q`Yy7Bc;;+#T%2 z8(Ut9ktxs^)JN;hbssBKs?lA>AU?Py{|b_ zT~0jEy?EK0uWpALIEYRz^#+?kCv^`@4>_#gAz?p}TOZ6YIo;u7X0D@aaw*um-dlL2 zBKrJ&^ZN?>=gTx_#%mq54Wc(nJ3-CN&Q8YF5H%OX1`TfPRW4X;b-&U zp#bXx^RbpaVJ{p0s~)*W|I!El^;j$7Zfi(Hx|sVy=#Czcjfy@cTwfOT5c*Y^E?w)W z$g~efn?-hr{f#(WxeFOCv>S^)^`sX{)oLW*?slCGX<%sgH$&zq)D{me2x8`+@#JSS z8_?36Dctn*e$D$X2*=gZrpyuRfW}{RY1Mi*(;4A6jXJqN0cLr;*=ydW^Hwi75b)aK zagg~G>*8aJCmAvxpKr~{dckyVvW`#!ue?Zb1$kni1Hy*Qu?w zS}`jpR>oo`LzSVLs7+14EnH|vD()z0{Ta9v=o8-PLdhpKbIK`e-5U^Rw1-BmS@kU? zt`oA2n~D>Rm4zC#hnqUx)n9Ol@@?)%N||x{bC}!mO6OOU4FoI+0#974(S_BUi!V=Q ze)Ab`ux`v`l#aqhjQMVo;F-z7%|krIob@{&6%Q-`?j(V1$e>s-OC37eRnRGGK8iC- z8}o|{i=gqgGkD5p10&v}LfZ4qGD zcbI{A72&VhZ^K?h$|QvFxEzS&YLxr-Hl06s^d!7!*JNT8MC^aI#{t22Bb1CbG`6G{ zWRf?xiIr6EyQ*F%aEQX6zuwbF`{5}4OaK!|ocf=!-Gc`k*$_CjIQ!9pPj_ZTX=Svk z?fr3Kwfaui9j{G&OiG>O23?izI524yQzmK0P9_HojHjfgc@pI)hzF)@K7_Ulk z4!e8;%RWvh@Y$XYS085ki_sRY`UP`niy9nL5aX6Yn#Gjz4X7l(aT38?Z^B3O)nsOP z%3=ya@HvvWU;3Bmjc2f>sx)&?LIA=ymQ6}sbUEzE#kX{u1IV60yqM&(}4&yZJillaR;;(-~J7GBxk+^%Inod}7?| zi1tv^;{(Go+ZpZ&g1}XjBrXz?{<~I$yTVM6LAO3qeoXe20KFxrf@_aj@sV#7qdf5e zl(Exf*g*$6TjwgB^P*)auNg`=>9|EYMmGDb(PIxXUTBWZ$FnTvUs5!ExO!#Sol_z1 zZW-NNI9KN(?MB^dzVEWpVynRjsjz_h>`Y>CSZU>|!Qy8-pPhaYrn0V}{pkMN*lVnR z5c7s`Ke2r(_4C&negf28hrVe~6DYEs)tJ%xX&(myO;)y=Jb`fyOzeumwK2 ze<%VGlP1@|=iKdJkE!5=x$P~%%t*-to!Q}h2RsS#Q5SQ?uPeQ#>rFm`)6?;KmXL*F zngp7d>1??a2~3S@Q$^?8CailZBUr!n)DPfS8>p?IJf&i!7g?NCRbs)Dk#w3W+>jHD zC|Y&je9S((H<4H8NCQ+iefGzQ0eBqGqv$jTj25m;(B5b13dlw@ohxK?4eHpM8O+tg zI{SBTvoN3!A1~BVF?T%uOc(*GO{%5A>x9Z+JhOW^=}((zHdQt{JR+djn@)NoC}oHt zYt(qaDms?RiO1P7`e-l@;0IU(=YkPdC&bXmN@5)$a^G~)*L;NDPnK(IP7IR~?a3x} zNt;H;gMrhgN@3(FUd8VB@a3*t!!Dm>)S=L$lkmzu)IOSP9FLN5PAF=e?~|vBc0`5A ztme@v?_we5q6HtQH<%+`=1rzoyz`?1u(R$?LydNNo%9~{QeRUAnn}Z-TN8L3>A~y= zEP*bvIyEoE)_R~+xcUJ=IpCQS?0l(WRO!)8F*+17SRl z3c&}<255mJk50GJIQM^V#K~adQ9e1@Av<(Nmz*Q|a@M9GbZy~2|PphA(e5LHO6I{quVr}a>J0i**cF$Z28KH?B@#kDA5X` z&tG+Tu1J}-K{atJEzmN%kZt21CBrx7MA!1rsTC6k1+zCtvx%uU7&9P#zmUc?hn8-{ zCvP+RAayl7s59F0$Ew?3Z0sCbc+ppchKCR4fv+#MsMtwNI4;$e{es2nifJ|D8StLq zBJAHBogZ3SAdK8oH}9Ia^SeHpUtj#_{X5bwigKf_$SAKb!Y1t@Z-`tOBt-+Ge`s(D zs6IwB8_I3Jo%r9MSw;Cg?TVcWEV)8I-YpPP#@1o(#nw-&w zL^)q=fS4G5m5$i!0sNKRW7-1$ie_+*I67t{+=F8ZMdxcV`|tvL`p~LNv!v;a-{1O2 z+@UK6c7>J@I?>oaC`mFp``i~hQL{YtFHg@&$NATov>Gix#tR`KnRPZh_>AUnW*8u>{%ca+}Wnc2hh+VDr zKZH&=08*i=I8q4+6Yi*{2#zqoF5 zD{%Hh<;fA{UDDr%9bNpeZ_c8$NArTrvNrY`Y=*q*Y#$p_VEY>RRh6>{ezf|qxAs&n zy(296Vn7~#!$pf$tl?%aMIMiA2&}06W~>EX>(m^&8~HZ*&A3zlz?d98f4vRzm^b^f z<)Eictuj72Yza$K607|AVfYMTSRVc7tc55Z(s@fH@}fwP$Kujcoqo=VrpC4%dF1=+ zn+5S-AW>nw-W6Q!IDkeXHuzmoM=3`^bh5-S?WkX4;R^IzRMPQ5N0nzX;Fhv4O5fi+ zm@GGs(Qb0N*{M{{V=sIElXzWH#`c9xvo8V0ix(1#nNkrhWUV>nP>$PoO!{qQ9q3Ii zfqw7*dMJSLK+-&Qn-P?{#8+m2Pjvnp|Mtlo?aa#O(C#5t1mw4vYuP!{gtL5V2d|~b z#}L0HGDE~T0gp+0vytL2I(*G|{PC;-g|C{KHFrawyn3-{(=Ftt`l2rxOMXTYq%zojdo9Xlea-TJK=3(Fdzf zex!X~E?C7h{v^FlTeFFS0hAYgZ}AH#b9R!kGrFSEj;AU$x5Q`?JTG<|fN!L&!2EhI(f#y$f>~Txz?lJl|+*z+93Us*Zs)bBNz{wDjI0i)v|EDfYx_dx`0I?Q|0bV z>%Fnkc2o}TWn2~Wk8jYId_7X2pdQjYh?@>)b9!RgmBN$pJ<6|5z~jQ+vRJt2d{;Er zd0*)#FUM?&I|P@+wPJg>3iuP)H5tl)Ubz}X%d`{M2df~1wx@1Dj!J=L!x!-6$~X3 zX#In{dWmIsA3QRd>55vCXgZg~rkDFU=Mlk4>#tJYbUp&Dl&kKFGP&+j9?q|?&9{EX zX^m`0c2LbWP|{nz`g{G|Z}H%8^1H74f$ighv>`9`(j3i8M->j9#%|~Z1maV?+Hq`W zER(g4PLbQB<>AYHye3#MJFW0QZEw>ZNWLmh2!`{_T2a-W9o zct6&%=a%CMZ--}QzJQQ-;aA*cY3oB-xz`#b2?7*eGfQ1j;-l3LoAIdgSz%)#;CkN| zb9W)@?UlC47S+lCzl!-Rv!r9^6`IaHMa%ZQYbd9*U5`_NYb{=c*>H;!cQ~t( zRyDOs7v>gsVAxB2nAw+zO}~&=93rvYFPjxY7$eFcYVWG4*dOxBbQZ`@F8-l;D%cH6 zW=q>;O>dKC$h(n|0x%c^{lm#sO0BhO$&;V9%F@pxKjOPMj`5n-bx3_*?iE2_Hqetd zoGmChR#Z72p~Bg4+w!g}ilTCW`yZl3A=+YMEqgpYMLLv_H}#pGK0?;_A>*~mIXlz9 zf|?3d&VGv53B0aB?1j4GR#sFwX1yAbJ#^K|?o;L%1Pi>Utx!DY2;qomI@8=8Ryc_J z4^d9tHzH*vofs zkeKl$<}W9^HwUwq4BBE2xXx-bPoCXNk-eEW2jkrvbM(pr0Y%~g$;H}ji;4P#GMBx1 z9YE9PObTvK9CKhO5_-s_Q)%f|# zaH^=e1Z{oj$!W4M5fKp}`|&d3FHF0N&?#i(>fL{eQ`zy~5f#o_=;$$y~aobq-LT-3g)epI@97C9Kp*>Ek?5$|p7E{i^@gB?>^ zPL;(?=P5_Igkbe?6zS@U*{`-qi@r1t+v7y@cWYg2Wp&zrL9LKlmE>75OB>hliMzsr zD32!fqopP-qp>_E(xo=lBJZVIjX-Dg=w3qgOOB%$Th@Xb*ldx75ed(z^4tMAH$B)X0ti) zd9g0wRPR={I?fabGxa&tG7dy-ZwI7C(X8I>`S3ez!8(wO%_>+KCdlMUu|jsI7;~Js zKvt?2bI5pmy_9`K*t+r@4D5z^w|!tu=a>%ya|+1KxCN}o2>Gw5`z4)y4(ae%zq576 zbT1@hLFobC@?A7V=a9$6`Sw9T^6LkN{m0A6W@afredwCbOMDOq0R5L;Iu6gOH_iD4 z>s=un(a)b^V_}SKjs^+*9$pgQ7}jKTi2w4c7n1o{L|f1!uhvL zDZu6iV8q3>J(9X7ik}(;qNr6vjLsP3#PPX48c4*F%EvN@OuRv(D}^61Z+ZK&HLi8o zvrtEvkH;ahNumPE3wiDWKVWvWHgK+z2@+Mm2Kf0Mjuq)dp#^I?Y<+i%T87|g#KZrn&u2higoB#WSpMeHX?Nsez2(YAPyXX) zA)k?}*oKy2bS~YqO$O?O!@$Pq*4>B}w7jTX)re^>;?o=L+8p!R3g$^Rj;3izRm!4@ zN{8R%xeQxe#n~QBM=zDKotdckpTqE^kgiw5i$X)qJeB=YB1msN4e-m~Xa%A7cmEQ6 zWEjn5`=cVH*nD~TApr_fiZfiCEO?Y$@9iFL{C8ve4pQdrKSca#a2wZZ!?`%J`UW@oOLXn&*(^3G(v^mI4R%d>Em z?<2-B&aY&KPyRo|2C9D0Rc)7JwW?DrCO9?sfa+IGrVl9na^G+f!oH?4`olpX#L8&)3l4R^siL z=2J1}W4DsKXlq%2!Yx^0fnk4=@1|P5Xj>3myjeiJ;R-ar!I(KNk6e$w-{H>TimXVpnAoH7gaYmMI#t>IOO(k1=W6k$-KKyWs@_@)`#%uoJ2B1vnYbGv%cC)l7~doXHQiv`Bl)B4-|_cX_SD7-wQmG z9R9BV9RRpfl$nQB6cO4H52zOD3}(qDH`#*#Jxp$T8N&pAcSHd9BlU0-;JYwyFtc3h zJ+8^%{VP86K~CfoxpyMf9w$}1oihLqEJ^1ZVe4q5u6B)m8c0Tx{BV^axq9qHXsa!% zrYrb(exk;qbTFY)7JgMaRS=L4-yTa9@SWU|4j#THe@oH+`vhC-%iWa1gJ0Otz)`X} zPrZnMPOEw_`z2EGrQ>EUMGd`LI(#scJNU`*cA8kY(ZDKb^9+VvLf+1$>uAGHsuDy? z@!efoz7esyjx)jzQpqF-SNI@D2VT~58TB)@gDc2*M_^9pZZ(y|f=MOMD-{QO38={& z)zy#>7t7g;_2}dZSKbKO=~XKodkOIsW}*O~M~!XSeNJe|F=QU{9LqWa5oLSD+`4<0M$?z7P{RtiDn}O;!$QwStbLio#bb)x}kJ|t*73})yPv$9q=bB7c0i~irvxn zK(jwsMGoR_DJqKMVUXx!~HXQ63Fd zWH#!{SGC&3O|#tE>Qo#S73mg$Y(%m{&sf2&7#j+frx&hxc1J<@Hq8_t3?!o@k_ejm z=UXAC!^SNSdNnOlOV#?%NU;Dhl?sTddr)5^2l-zMR~$)y(bS@ZzKxWPGGO9G0=tHr z?(tScwls89#Zv&74HW@nfl^(|@vZ5cMzv3Jp^iya`n+5yVRC(8;H}F334zd;CoZ{J z=>6zF1vDzHD7xE%_A_Q-keB8fA)niua*TRwFi2&HKp|xT0F}z;tXRL@R&Aan?fGk< zEon4Fh}D2>IpH+9T)s?&`&W$vstxkP=h%HbW@`98d?N6;TqTYd&t6hF)^UcIP3AiC zyPdtkPq|qCO#xaCH;E|ja(SX$)^Y2(`11>gbyIKAhu-*) zhYtg@iZ*3ve$*Ti4u6{);T1CJJ+zXJ^Z=)-Y>+w~FoAqM5>>K|GitF543>R{l z_kQQ*wAF&`X!_^4mUWQR5UZIg!G34=K%nfd%6L}#vTed+kEhR^wg`oG5~ZK}L#Xv? zi@@W+oJ{O7p)pTAY zI$7_L55lY)2qiir@B~_qc4nZF%!U%BA>h;90fpL@DH$Wf>QZvknP6ei#qiuhK10F* z@!2%>ltc_Y-Ezl1o;GM$>2>Nz5ASqC;vtXk`~gd_ld+?9mTbkW!b#e+F`#Nm*;?St z88ad208Ol)&7V4i^7!AqOL{<7AoK1Kr=q>zcPu1Mqu#^F;r@Ivyl*(Nugo{yz-ej*@1^Ov|EDn_^aoLI8%9MD&0x zVe*HiZ9Z11iEl(-I@;T5r0TgEbn4xRHlAf6pp*OLjMcd)9vr_Cd>2lNtl0K4ur;R` z1kiD4WE$L1Km3g^;1SD=myn+m`5P?s`Af3*R@=qa!vH$BqmaCK!AF_Fpa!$fcfhW&1bwv<>7 z8GS5LQAYe)G}BNt3Ffs!8`TOl3lASR3C@Ee8cF5yaWR6fCa`0d-@{`edmCp6$Qk6L z6vzzq@l6{HryPf?Rr_s{Ll$tXe<9viFaCm$1qGwdl)X@CkvA;cJ*}**=qn20ujOvl zwVfFVF|G%5S2$Uz=z|7#ofLNXPmQNCjq&B}>`%a7+=g6!ALco}`-@%e9)@m_qPktu zV|JWFG1{#r%b|Ay(_&F9594=XUIYTJBBj`Ha@XX+bfcd?^FI(p?XPwcJ-WG@TfAia zOiTedc zdPvdgrw2=HAJbh!k?yHFV0+oXfo`g{iB|38ClD}qyF*KDlpA`^gP4JI-B0#Tf(_)R zx9g4Y_xxq^NMQ1{PwE!)_&FixTj`C_6oTsu#jGSg+7PBAD@H@<#Bg4idv@{|YTyYY zqs;rhVbkGSN#jY#7Uy*sLs3!~J2gz_^G@dbFygZ9c|m>AjzFhz9)TXto<=Vc#|I9^Ns&|edGhLa&y6E0&mq{WE-ypI4I__{=Dzu1Ibc{TqcOJbdB^`1V$DA#`fI_hCD!E6?{D~jzyH5J9Pk-D zpLyDaD-{!T=c*2mih+$#6VjQ-;v$>BLAId`3z()=G>y`Bgo z7L6itU@I)#7(+QwdmsCHulWVwr@#0e3Tz4cdjhSPNaVFn3Ap zTW(od0)fo3mxGF#wCec;&6CSCChMs_k*w;>)aseEBPZFR4KS6VFGT%gs9(UKv-<9%DdFX%Kc6E9Ed{=kgo&Wh`4j!vD9Tm-%TKZUqG@eGeJs@Nk z&-aHi6+zh^;WcM)w4S1G3Bx?Zis&`4H-!|ADN|*x*ZyLLA zTmc?i>O&tyMQC5*v%mz$yRndi$v^@)aneuZGlk&dVT_+%kiSb`iL2Sm)=7NGVWkwP z>9F?Zcb2T`?!9g#frnr%lC;tC$da1PooSCST#ETjf|XV%^Y?*K$bY(rat{HKPX>=2%F(K^PzUakWVLL1Y<4Fo zh+`II5zb_g{CggdRjiolfw|9{d=bu~&Al!M#Eev`JJV$!j?FrAGzv7zoVPWG`Zgq- zwRP&=^Gg$OMC;sMSN6bYM{5nNI_XAz?rTik7{JyE#bH^$0~x=a{!h=Sk|ui5t#O6- zPV-AtU2&Hv>dkqcp7Cp|;@7Y`lTVeEm<4W3=erHJlvYa}2W@}LhWa;Z7=zRv7iw1v zxs)1&$s9iM&z~q6Dq2)G8?3<;1zdZ^)8_v5)at{vi3kcH>(-9v(DyxZ>YjC1N*A$> zIeFD5O8XF{#S|~jA$8e~!&(6-CP+sAN?5W?>WNziMiC%jpeg)u#xL20aWIOza8)4< z?SyG4n-*R3WR)3j*oijknN5}X)7FzdUhfRHs9;hai@vH}jEvDCu7RnL-X0C0OJoJI z-p=>p1)#BHDodM>t%B*@`@?wU;rEaKrIzi%fNPLnuO}TCIR4e32SC7v9m?%JXIi7i zUG0?Y@_Hq{7koPu!yqlB0}Gd5t?oJyeK1_8u4pz<&nNxMKY)&vEKrx%T_y*;EoQ6_ zW78ejiq)?0$s{l)#(YXw*M44K9*HN_MRKaYh;ssbevR7#afVEUZ^>1(c6pS~lLpWs zidR}qf1l#8Qt$Up}bKRcJV&J|I@_#`AsG&i{gfrXnnd~u*2Io3nD>9hp?W|Gp%^xk~j*`!=HfR5#r>0*e zq4#VI)G$2DK8;nV4ll>R_K~ zvvBBmpqy0?)jtZ|5lBB=?NhjVjIhK*$f82qY<0BriYQ@~UC3*90I1cLA>rK88eF3^ zG4%2Y&#lr!oC9Mk*R~WIWo|3-D%5}52w!$5caoh zk3IG`*vj-e^(L#V6600xgl%Q6Qe7G7%62y* zx&jBdzjT>Wnpy(}K)~WK1dF#VH2IV7(PrLC#=Dr1o9Sv6sdQ7U{R?D5i6L?+Im@WP zXK~C33+aT)1U8ZaVv3%=m-Jf1}*#F|P zm8J_&OBkQ24X@Mz?Sz%DMpRm1H7#oKs8AAnW44yz9MMDY@}#q4XqvoPo<+T(n&TxhnNRbSbH4r)`*rphhs<3a zhaD#i?yAo&-}iw=B#lH+@Z)R*us7{1WMuEo{~QL~XCFa)04}4W`*ybG0{4m(e z=bTp9bwm(~=F*GJvb2C!UJ$-cc+JCv(!pXv$mp;toLsgrwnLUbBjx=7aW18nrt?Bk zgNaQtf0;ROhg8(5tk42F*1Ma^)FOv zWyXdHzr(t7lb()+kF!xO`U{W|T(%ssZSF{Air1AGw)`e@9=Td2-u9O9YJ_;3D?*!j z)gS=*>i~Qa(cseH%zKH#Kzr-LkOD&>tyP$zj#^%UKZ)Wf$=9m$_C+Qj9b1@>VK`=g zroJK0C2jOie1zl*>+1p>D+D_3n^^UCpVm|2_0zumUjJ&f&|8drgK(Mwx zwm64q^+TUTrqA#)4p^6A5YiZqdfaKwpzP!XRQuCb4u}cXN898M?O>BLchGVJ~-?)1p&k#+|5Ua%u{j?ZhhB!m1 z$o_{jEUHC{spY@9@Rm&uHL$*XMa}{#&AHe6cFIzF@@~e-oK&Z|DLY;q z5^vo<-2@22=#UM5mAkD%f&$@*;IP0nG$w`Eq-z2=+|Gvse5iNtoNF zkJIX4on!6d1>t@C&Muft=BUk1Z@4z5?%V4r4vuuk9J8a@2s4N(eH5qAoKYlmH4+GRNH-Lf$(4YiNE00 zB*9e8{GGaK`#`g?RJ=$`Uou6=r@KLL09{Kx4Hkonrdpj5opPSGB#gr`C0MQgvO~U0 z@pS_eOD)JPX*05%{8r=_-aFt5tr=6CGAef7U^UGVEWl+Zb_c;SroiV`a^+!@9aP`f zWRBJQBwwQdJ12h1nn>hC!hYwGkaGgdDPhTu!0nary3a4@W(mBL_B^}m&Em@_w6GaW zA`bC0%Zd6|E+-qJH!Q{yAI^@yq8Pdh3(^MSf;@uEfZw~b_0ls_m8u+1aRNH3(XiC& zK~MlL;=tw2Os!$M4b5*1UC0^)nnE185p<2xvr2lYlU*D^Va;a}vCSxhT&8e(Jlx-q}7!O+$Iqygev z7uPJlAMbViXK&)2C79H%hBnDCS_i z;HEK?gH795s8}6dwv0|{M6dNWuGA>%l$XI1aiF5a=tu13{QA(x>xd_d*y0oY(d1Dd zB_Gw*<=7P^9i9I+)~Njm=YYP;NwK|xUhlbo#N}Wy;-wP+zhFIACokLbo=~qXpq1QA zB9;>ETsjtmeDV4$EUm-%dsZ0_nGC|bG@k@FWzrBkaTg!bZJe~K~CDN&lbchb_ zL0J7GleW?&zKY3_`#fqjlUrfF4JSbHGJ-q`50cQTv59nu6w4FpXgNoIxm5n?mi!Rm z38JF8?8}aCr2ZDdmzAgROZ#x{7N33?#2|z;PQzf zmX|lZD@Y#lwyCo1#`?fp1)S!~SVHPlkr*62f2*SjiMqE-P3wcnJ^~iH)f@# zfV+Uq3@%?=bOCa4;Y$=bvKg^h`QH?B;xw{+au*8x<-3hQ@^J?&CBlaIW&MJU>xvB) z61dC=-NMBV*)5mn@Mqx-sWAH^!~?d^U)DJ95`b>2R{=BfU)iuvjZT7ph^62KOn)m( zL9}REb+MPCpZqHJEfi<4Q5E%AFj|vEUT9oWFFPb{e&X;RSt_eL6B;*OskAqgt=|rx{Qw*PlBb2HW0z3$1a|S z!fn?^nie4*D!&e{PMN|-YXIJ@1C2{bw7%7@L@4{txTx)7`v?}aE{R+uJ&E`udscyv zmIiPek5pKs2q_GkNf;F1n6+oly!jppy;wqie(AKGd2VMk1dxVdx--^K=HD&EC5Lcm zXo*7vOgGc#hwE%<@T+mZqrRbbD)zsb=5;xX9i)(^ExklYfn|k;ruffZkD>N8fU7IS zh{iU@dwLm)JqH5atlEZiQ$gNG?n5mg^j$lhcA?a{I>Vr_((2@M_YK0}(ZPpF!%H-u z)Y06Dhg!IQo|YWrWtVUw{A4Va=0b4YuNs`!8OeC$i`UtWK&8UZ4>y z)KysOC{imG3L&)eybw=#+>{LDZ>dW;(@Pf%K@Ov%r%GHipT^R`TnLY^z)LoU@}Pb9 zMT`pPEue-Jgc7T2p`6-!Yik2--v;rCI(C5*k(PxbL6=^J(PS(?r;MfC7k9hywOt7( zrh&~IKeN-^KKc=4miGx(NcHl*UD>(1VpcYV@m|fih{cOsojk|b)qaJd=BFr`OwC^< zS!Syic|H(FQEEPU=qfoc_xxW79MlECs1h<|GV=b>k|6$cV+V(G82J>gO?QE4CPKm{@!y3orH1CtPmb;n5#6Cs=&;&!moHX8mb0(QaxIj z^L~Pxd;2dC3UR!Fg1hvt**EU?`IH(2446>Q>fvKGAzOG<-iIgq!gtvz|MSP@6@Y;} zHuQ)F{q&-)w7-6@9RPPsn0!){Up?(4J_uLs?D!3!OX^NMW$$#~0kWrpPU70s=fYfR z^%s4zNqk@9%T4LFqt-frNYI+-y%YN4&|YS{(0rcAma<4qoA=?dNXuchWOT<07x%|L z`*mQ`N(fDxlKNGnFOJJ$8UK!>ELDG7>@Q28*RZB7DIM+Gi%;WUeG82y2=ru)1WLnE z>p6PqvVoOQKhK8-nEk#v2H+9{ytp}~sRU~oPTfKUvQqg98Vow>wBy4k-N{*1Z zC>r7t#T$zWgBbiUzTh2g#v{QAsTX(rY0|ut#5;Ax%yi(%R`5RLc~q(JYQ_{bAUH#| z86Bsr$wfblAOEf?vxmyy(gIl>GC|@!&RehiKeL%BrInkj;vrwW?t_QG?`bnYZVCMAD^8U5 zR@?8i-{^EQS%V3=0>svc%Z<^z#^xcX=~ZEH5DI#oOu#!gA6bAz875$~q@i@_!uVkE zD(XY1&CT7ATr21+I+Dw6M;@W*$O_;%HNO)jEpxC9GsrykcGz=-sV ztwfo#AK5gGeV5lh7WC0?CCXe1&IF{gYMy~G20W3atIS1=M=}n)yxCwdd!$*IfB~}m z7F`yM<^69g!AcZ_4^Xw5Og|*u`bwNoDMq?su~5gJ>Z&Fyy*llBe07i^uN~*eWFw&78A{P$?_;d2|`X+a$@!IP^Xztgy*P zSJxRcIeDs7FwVBA==2%y1(vRun$ullNfxZlJuxp!D7#_`u_%?s(V7zDs`bZGq}%J| zRaLHFVaUTT)naq?F2Z+Xm|i^gY@@4F7vKcj;G@5Xnx2P{{sF|wK(rClnvaz=^3EzP^K6hKE`lC;Y!MXHoC>}?=>&aG1 zG!?dWtCKsvCI7qd7Gi35{*O-9Z>K6ODS#V8= zg&4-v@=z{4OBOW$cps-bvD??3nV=K5lO-yuwpZ-V-)M~mxdTNKqqflcN@c4Dv$8Gc66u0F0GA{XV6WhJe`hn|2g}aNyq0ZFEjVH8XGh4VGcRo0<=$My&cjA7(&~ z?dJNlm0()xF1v3R+&U5=k#y436Fd1nfgNe4=;gBJ4ERI|o`Abv|_|pF3*>xtm zo94miv8wOxmpSr*D`rbHNh}`Gpn(zGR4GWGt?A`;e}bGUxCmp6a!$HqO(9O(>34%W zE;N6EQHF+CW(vscP*e5Ccb_XyNRgUD3IjYHSGwbsjey;}pFkj^%)jA^WC*CuH`cit zf8^HbavIY+SWb!QK+*dkC^&D>?eRjM`!&dO+1-99Zt{nXwD(el^|5m33Ud~%??APv(PofE z_XAi`oW4+*Z$uu9%Hde-)}~@IIPFfwTp%$qBr`Ue=J`>BAv{gB%l{(nEuf-aw>My= zR20EL1*A(_K{}NNX@(A!k}hcmL z*LT;q)>*R{7RQ<28+$){KhNIVe}j@G$s)2sKS`hl&GO>ky#Tfo7L-`4!l`0sOxBz4 zZ<)XU@#q2za>I8qIcNIfw1{k|3OQTcs?#5}AV1cZ9<@M^?(wl$PHth9*X)H8`;;Od z2(D6+f4ypPFOi%*nWTQBk*<^Av%+9b@3uJR;Rut($WDsYv0jlYB{uV4Ky5VYK0eba znFOFfE$BAD7(j4ghcWqgC@jxW5Nlk0ZTjE|*}&rQ!keA8NzQzj4S~kUrEX-&){T~8 zZ53U!^k0khnZ*LaENN~(E*`Xv8C<&hC8M(c?h};yzu23Y;ZT{c%XHNL#c|Vd|E8!{ zb-~o6o$gT0bQ#;WI1aXCQLkWyICjZN1p#qAJjO*tcYlUMt!8#niQ9(Q@@N@93Ka@4 zJD>-p@Nhxjpo==iOd|w9>o;2d`PP4P$yDHDk{>b$-?LwM9iUk!Z$dU)Zh7ii;(K66 z{%_T7x8Aj)2h+vFZPWMWE*|{cS%~E2ywuBe`69h+*pok^tDd)ovsuuxd{F->-W&Yt z=YW<}qWRWp{*NHg#x zR$`Amg%Rfr*M&y99c<6l!4bHrcrZ<^LMZt|V=0Yr&a^<_5Z^lm?+Y4UzxFLU(}{lK zld9Zd!?g{;!uv&%;p=@V8^dMmk9E2_1jz_3bf2P6T6uJvoSgv^PtNU)$7|oOwXnry zeht(x?M)2e%ch|XZOB#MM5ix8l;M#gTqT9uO9M z@6ipJmJ2m6d+Td+lSR$I5?zHF2#!RF+R@VK?o`#kqwiG#T5>S<`rZZ+z4Pc!zJove zU9J!GK}XfjMg&{yqUFBEq~gw0%s{}3Ka>Z$^X!4bu}mt_mges8G?`F^yP|c$?wp6o z)vOB!?~?LEB2_H1KwU>IF57Lc&wS4*XB)c-Q^g{5Z!cks!i8K9N!BJRBx{j28qGia zPvSXd6%*FSYbEQxDA5-jfGmCbPwRAVdu(Z$G@iY3nRLl+`K6g9+qnq*nfRjog&WQM z#Ms)C;An>Yw;GgT?~p#Ab|>fUNtk;<1(CAec_I6UWUNgX(95|f1o3Ko<_=Pb&PjyX z*_x+7Ti%6&G^23+c0nTOzTIP_1%!SoKTU zNjdcB3>r|=KcI`CXU0o?@9ub-N8W9T{9L@2eQ*1$YwNf(%-RY-D@%>_G1M1Sk=gs! zAL+GvE|ay;#{UHp()`J6Do4brN4&Pn>u%=6K>38Gz5>5Hy{nDF5?8Zi2 zE(VGy=(xA5-0UIK9JlY`Xx~B?FGASPnVlDZzYq7gOC|(nWU$;>IJzqhdZ7d_rtCan zw4b&PKYxm8o-q6_uZDKKi=oBA!BKGICnTh!`5g1AB%|*-E8*)5x%fA4UVQ?uG?Yxf zUv4g4T_UiwoEPuL}wcJ z3|KYvLq(oQF@QFJ5BGb%WGJOha5NT-oY>D&Z;$;*WayZxwsJe@qU2{opa+z$8-r0m z2}P!khO3T3U-6Mjc%(C72aPxE_}Qv4(?Q%%f88&Bx3_*o_TYBlf#HME?1T3*S=G*< zCd___9r4rCv*&Fj_p>pb(;6QC9qSN^JFH&it|z6|@dnkK@tRH*70>e3;I)rq+w5Z7 z0M*9WUx$)8n5@+0S-FH~H3wpE#G9S()ctyJ|n0W0p0af7uDy$!1+WQ{;AFXC2G^%fg^|pei%IZn8gn zwl1pF9^*!@(gMx0PF~@C5<06we+Bbq%R{q3t4V`vq2t3fCmw4fxHdm$LO~7~4ikcZ zxkGp5v_vgd^x#J#F8M=SRug8G(jgSL+R@M3W;;!xp3#?^Tx7?;Ut5hAs-5W;wGhmn zp3@d#si_o(lh`(Fm zwDdEd|JH{!%~ywR^f5<2f@8JzEJ>YCOb(3XcQv20SmNn-zFIr9?00c7gRjNDk2h1T z6XSNBlPhLhF~nCqCZeJnv_cfcCEOrYi20V2lh`g&lSm6ySuu#`_#i6#k*qn(NS~N0 zhW(l5sN*;%UA+hVw4+rcqj>A#Im8r9h>P{*7~Dc-Y52?g5NaNo4FwM4z4n45nG5jm zSGo}Vui?1jl$_~-M1!}4sR=$@CP!9y|5P}##C4o)8ic+%pl8BEb}~P3hwbKv2)$fk zN)|)wh)32(ClpMQszC3ecT|Q=G~I&pjX-FYJN8_@kBiA0rT&o&|u@06+C;<@MB z6C0?bRSNZ`Fg)mgdlO<8V5K)^Eo!#la{O5}S?up+AItd3ctE8oGk3n|^ zs@RT%;uYNC3TN0{d%TLu=%p?UC_8V$oEN>|#)E;DytF$`F!!}xqz!}!t(*!Mp0JBJe};f_doOMfjX zyqr>>?$tS*i!}dvnPNZxdMA9!JFDkeu1=x%NdJ5tr~KDB`8~d!c#3cp-kf@=fOyYU z7~!6gi0`8m<&TzBnb-m3GuA2Xmc6%BIzvqOryAM ztqOeQs!TNbCZCJgetvb0L>sx#FudcmJa?(Wb3S-$E_^PE)*}e8YLv)nOs#H%zL__|w!_lJR>XDgtz-JnORX<7N6MUc;wth` z>qm`|5Sb5kFW${8u7YX-Su^>YpRzJN5mM zpoNH%TFuek3deXQ)D*UOzTQKri5QQ>{T|5>yjh&#^$r?U$orR51I|y^H5!49B!dHegvaj!fpzw6uAe!c)d?V)XrVSK-K{^&+hSxqzv(WS1S zbMFX>HA35FZ1v#-vfC?PJEQ8JF0!ihPTd!LBX?bu{{ks@%AK^7z_gt4@MMwU%*Yrv zttWhTzi6bv^iYWP&|!(KDIa?0`+1fZn%mk1PbVX;Cp5R#UOVFhHW|ROMF;Q1d0c+C zK3pujz0g^3q1wFDNh8y+OVI;

r(a2ha`zu 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 E12y%KO8@`> literal 0 HcmV?d00001 diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/Cargo.toml b/vm/Cargo.toml index fca39d4aee..bb23ec2014 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -11,6 +11,7 @@ keywords.workspace = true [features] default = ["std", "with_mimalloc"] with_mimalloc = ["dep:mimalloc"] +with_tracer = ["tracer"] std = [ "serde_json/std", "bincode/std", @@ -27,6 +28,7 @@ cairo-1-hints = [ "dep:ark-ff", "dep:ark-std", ] +tracer = [] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = [ diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index be314ac364..dda347101a 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -205,6 +205,17 @@ pub struct DebugInfo { pub(crate) instruction_locations: HashMap, } +impl DebugInfo { + pub fn new(instruction_locations: HashMap) -> Self { + Self { + instruction_locations, + } + } + pub fn get_instruction_locations(&self) -> HashMap { + self.instruction_locations.clone() + } +} + #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct InstructionLocation { @@ -218,6 +229,17 @@ pub struct InputFile { pub filename: String, } +impl InputFile { + #[cfg(feature = "std")] + pub fn get_content(&self) -> Result { + let content = std::fs::read_to_string(self.filename.clone()); + if let Ok(content) = content { + return Ok(content); + } + Err(format!("Failed to read file {}", self.filename.clone())) + } +} + #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct HintLocation { diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index 80747d80cf..f5399d059a 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -308,6 +308,22 @@ impl Program { self.shared_program_data.identifiers.get(id) } + pub fn get_relocated_instruction_locations( + &self, + relocation_table: &[usize], + ) -> Option> { + self.shared_program_data.instruction_locations.as_ref()?; + let relocated_instructions = self + .shared_program_data + .instruction_locations + .as_ref() + .unwrap() + .iter() + .map(|(k, v)| (k + relocation_table[0], v.clone())) + .collect(); + Some(relocated_instructions) + } + pub fn iter_identifiers(&self) -> impl Iterator { self.shared_program_data .identifiers @@ -479,7 +495,7 @@ mod tests { use super::*; use crate::felt_hex; - use crate::serde::deserialize_program::{ApTracking, FlowTrackingData}; + use crate::serde::deserialize_program::{ApTracking, FlowTrackingData, InputFile, Location}; use crate::utils::test_utils::*; use assert_matches::assert_matches; @@ -945,6 +961,61 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_relocated_instruction_locations() { + fn build_instruction_location_for_test(start_line: u32) -> InstructionLocation { + InstructionLocation { + inst: Location { + end_line: 0, + end_col: 0, + input_file: InputFile { + filename: String::from("test"), + }, + parent_location: None, + start_line, + start_col: 0, + }, + hints: vec![], + } + } + + let reference_manager = ReferenceManager { + references: Vec::new(), + }; + let builtins: Vec = Vec::new(); + let data: Vec = vec![]; + let identifiers: HashMap = HashMap::new(); + let mut instruction_locations: HashMap = HashMap::new(); + + let il_1 = build_instruction_location_for_test(0); + let il_2 = build_instruction_location_for_test(2); + let il_3 = build_instruction_location_for_test(3); + instruction_locations.insert(5, il_1.clone()); + instruction_locations.insert(10, il_2.clone()); + instruction_locations.insert(12, il_3.clone()); + + let program = Program::new( + builtins, + data, + None, + HashMap::new(), + reference_manager, + identifiers, + Vec::new(), + Some(instruction_locations), + ) + .unwrap(); + + let relocated_instructions = program.get_relocated_instruction_locations(&[2]); + assert!(relocated_instructions.is_some()); + let relocated_instructions = relocated_instructions.unwrap(); + assert_eq!(relocated_instructions.len(), 3); + assert_eq!(relocated_instructions.get(&7), Some(&il_1)); + assert_eq!(relocated_instructions.get(&12), Some(&il_2)); + assert_eq!(relocated_instructions.get(&14), Some(&il_3)); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn iter_identifiers() { diff --git a/vm/src/vm/context/run_context.rs b/vm/src/vm/context/run_context.rs index 32bad1fec8..d5de5d41f5 100644 --- a/vm/src/vm/context/run_context.rs +++ b/vm/src/vm/context/run_context.rs @@ -26,6 +26,10 @@ impl RunContext { self.pc } + pub fn new(pc: Relocatable, ap: usize, fp: usize) -> Self { + RunContext { pc, ap, fp } + } + pub fn compute_dst_addr( &self, instruction: &Instruction, diff --git a/vm/src/vm/trace/mod.rs b/vm/src/vm/trace/mod.rs index 838bfe9d39..5a9f683570 100644 --- a/vm/src/vm/trace/mod.rs +++ b/vm/src/vm/trace/mod.rs @@ -10,7 +10,7 @@ pub mod trace_entry { ///A trace entry for every instruction that was executed. ///Holds the register values before the instruction was executed. ///Register values for ap & fp are represented as their offsets, as their indexes will always be 1 - #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] pub struct TraceEntry { pub pc: Relocatable, pub ap: usize, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 4dc6be0dcd..4d4238a7ca 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -946,6 +946,11 @@ impl VirtualMachine { self.trace = None } + #[cfg(feature = "with_tracer")] + pub fn relocate_segments(&self) -> Result, MemoryError> { + self.segments.relocate_segments() + } + #[doc(hidden)] pub fn skip_next_instruction_execution(&mut self) { self.skip_instruction_execution = true; From 94a3d3b3387239f7340e48f9065cff483309db78 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:41:21 -0300 Subject: [PATCH 09/36] Fix error handling in `initialize_state` (#1657) * Fix error handling in initialize_state * Add changelog entry * Refactor code according to suggestion --- CHANGELOG.md | 4 +++- vm/src/vm/runners/cairo_runner.rs | 35 +++++++++++-------------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1547001753..fee05bc0f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ #### Upcoming Changes * feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memory registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) -* feat: Make air public inputs deserializable [#1648](https://github.com/lambdaclass/cairo-vm/pull/1648) +* feat: Fix error handling in `initialize_state`[#1657](https://github.com/lambdaclass/cairo-vm/pull/1657) + +* feat: Make air public inputs deserializable [#1657](https://github.com/lambdaclass/cairo-vm/pull/1648) * feat: Show only layout builtins in air private input [#1651](https://github.com/lambdaclass/cairo-vm/pull/1651) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 4dcd533cc9..7becd79eea 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -436,30 +436,19 @@ impl CairoRunner { entrypoint: usize, stack: Vec, ) -> Result<(), RunnerError> { - if let Some(prog_base) = self.program_base { - let initial_pc = Relocatable { - segment_index: prog_base.segment_index, - offset: prog_base.offset + entrypoint, - }; - self.initial_pc = Some(initial_pc); - vm.load_data(prog_base, &self.program.shared_program_data.data) - .map_err(RunnerError::MemoryInitializationError)?; - - // Mark all addresses from the program segment as accessed - let base = self - .program_base - .unwrap_or_else(|| Relocatable::from((0, 0))); - for i in 0..self.program.shared_program_data.data.len() { - vm.segments.memory.mark_as_accessed((base + i)?); - } - } - if let Some(exec_base) = self.execution_base { - vm.segments - .load_data(exec_base, &stack) - .map_err(RunnerError::MemoryInitializationError)?; - } else { - return Err(RunnerError::NoProgBase); + let prog_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + let exec_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; + self.initial_pc = Some((prog_base + entrypoint)?); + vm.load_data(prog_base, &self.program.shared_program_data.data) + .map_err(RunnerError::MemoryInitializationError)?; + + // Mark all addresses from the program segment as accessed + for i in 0..self.program.shared_program_data.data.len() { + vm.segments.memory.mark_as_accessed((prog_base + i)?); } + vm.segments + .load_data(exec_base, &stack) + .map_err(RunnerError::MemoryInitializationError)?; Ok(()) } From c4d21080f82e571b9a513fb220b7f6104b0947f3 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:48:24 -0300 Subject: [PATCH 10/36] Remove ghost file (#1667) --- felt/src/lib_bigint_felt.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 felt/src/lib_bigint_felt.rs diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs deleted file mode 100644 index e69de29bb2..0000000000 From ef9ef5b7704078cf9e5e1b859ba32a84b0f2c341 Mon Sep 17 00:00:00 2001 From: Juan Bono Date: Wed, 20 Mar 2024 10:27:53 -0300 Subject: [PATCH 11/36] Remove Disclaimer from README (#1678) --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 43b4b074ff..bfa07edd4b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ A faster and safer implementation of the Cairo VM in Rust ## Table of Contents - [Table of Contents](#table-of-contents) -- [⚠️ Disclaimer](#️-disclaimer) - [📖 About](#-about) - [The Cairo language](#the-cairo-language) - [🌅 Getting Started](#-getting-started) @@ -53,10 +52,6 @@ A faster and safer implementation of the Cairo VM in Rust - [STARKs](#starks) - [⚖️ License](#️-license) -## ⚠️ Disclaimer - -🚧 `cairo-vm` is still being built therefore breaking changes might happen often so use it at your own risk. 🚧 -Cargo doesn't comply with [semver](https://semver.org/), so we advise to pin the version to 0.1.0. This can be done adding `cairo-vm = "0.1.0"` to your Cargo.toml ## 📖 About From dc603ce70ec5c539ef03f8eca7ec7828cd55d608 Mon Sep 17 00:00:00 2001 From: Pedro Fontana Date: Wed, 20 Mar 2024 16:54:03 -0300 Subject: [PATCH 12/36] Hyper Threading Crate (#1679) * Add Hyper Threading crate * Add naive script * Polish code * Add MAkefile command && create README.md * Update README.md * update Changelog * cargo fmt --------- Co-authored-by: Pedro Fontana --- CHANGELOG.md | 3 + Cargo.lock | 13 +++- Cargo.toml | 3 +- Makefile | 5 ++ README.md | 6 ++ examples/hyper_threading/Cargo.toml | 14 +++++ examples/hyper_threading/README.md | 11 ++++ examples/hyper_threading/hyper-threading.sh | 17 ++++++ examples/hyper_threading/src/main.rs | 67 +++++++++++++++++++++ 9 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 examples/hyper_threading/Cargo.toml create mode 100644 examples/hyper_threading/README.md create mode 100644 examples/hyper_threading/hyper-threading.sh create mode 100644 examples/hyper_threading/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fee05bc0f3..5dd8f750e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## Cairo-VM Changelog #### Upcoming Changes + +* feat: Create hyper_threading crate to benchmark the `cairo-vm` in a hyper-threaded environment [#1679](https://github.com/lambdaclass/cairo-vm/pull/1679) + * feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memory registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) * feat: Fix error handling in `initialize_state`[#1657](https://github.com/lambdaclass/cairo-vm/pull/1657) diff --git a/Cargo.lock b/Cargo.lock index 783fb44464..c3cd2a2000 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1691,6 +1691,15 @@ dependencies = [ "want", ] +[[package]] +name = "hyper_threading" +version = "1.0.0-rc1" +dependencies = [ + "cairo-vm", + "rayon", + "tracing", +] + [[package]] name = "iai-callgrind" version = "0.3.1" @@ -2547,9 +2556,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", diff --git a/Cargo.toml b/Cargo.toml index 707673bde6..f3825f5efa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,8 @@ members = [ "hint_accountant", "examples/wasm-demo", "cairo1-run", - "cairo-vm-tracer" + "cairo-vm-tracer", + "examples/hyper_threading" ] default-members = [ "cairo-vm-cli", diff --git a/Makefile b/Makefile index 5467a61bbf..145b221782 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ endif compare_benchmarks_deps compare_benchmarks docs clean \ compare_trace_memory compare_trace compare_memory compare_pie compare_all_no_proof \ compare_trace_memory_proof compare_all_proof compare_trace_proof compare_memory_proof compare_air_public_input compare_air_private_input\ + hyper-threading-benchmarks \ cairo_bench_programs cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_2_test_contracts \ cairo_trace cairo-vm_trace cairo_proof_trace cairo-vm_proof_trace \ fuzzer-deps fuzzer-run-cairo-compiled fuzzer-run-hint-diff build-cairo-lang hint-accountant \ create-proof-programs-symlinks \ @@ -375,3 +376,7 @@ hint-accountant: build-cairo-lang create-proof-programs-symlinks: cd cairo_programs/proof_programs; ln -s ../*.cairo . + +hyper-threading-benchmarks: cairo_bench_programs + cargo build -p hyper_threading --release && \ + sh examples/hyper_threading/hyper-threading.sh diff --git a/README.md b/README.md index bfa07edd4b..a213ad393e 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,12 @@ Run only the `iai_benchmark` benchmark suite with cargo: cargo bench --bench iai_benchmark ``` +Benchmark the `cairo-vm` in a hyper-threaded environment with the [`examples/hyper_threading/ crate`](examples/hyper_threading/) +```bash +make hyper-threading-benchmarks +``` + + ## 📜 Changelog Keeps track of the latest changes [here](CHANGELOG.md). diff --git a/examples/hyper_threading/Cargo.toml b/examples/hyper_threading/Cargo.toml new file mode 100644 index 0000000000..e518c2bdc0 --- /dev/null +++ b/examples/hyper_threading/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hyper_threading" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +keywords.workspace = true + + +[dependencies] +cairo-vm = { workspace = true } +rayon = "1.9.0" +tracing = "0.1.40" diff --git a/examples/hyper_threading/README.md b/examples/hyper_threading/README.md new file mode 100644 index 0000000000..e69152ef2a --- /dev/null +++ b/examples/hyper_threading/README.md @@ -0,0 +1,11 @@ +# Hyper-Threading Benchmarks for Cairo-VM + +## Overview +This crate is designed to benchmark the performance of Cairo-VM in a hyper-threaded environment. By leveraging the [Rayon library](https://docs.rs/rayon/latest/rayon/), we can transform sequential computations into parallel ones, maximizing the utilization of available CPU cores. + +### Running Benchmarks +To execute the benchmarks, navigate to the project's root directory and run the following command: + +```bash +make hyper-threading-benchmarks +``` diff --git a/examples/hyper_threading/hyper-threading.sh b/examples/hyper_threading/hyper-threading.sh new file mode 100644 index 0000000000..912b6cac75 --- /dev/null +++ b/examples/hyper_threading/hyper-threading.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +thread_counts=(1 2 4 6 8 10 12 16 24 32 ) +binary="target/release/hyper_threading" + + +cmd="hyperfine -r 1" + +# Build the command string with all thread counts +for threads in "${thread_counts[@]}"; do + # For hyperfine, wrap each command in 'sh -c' to correctly handle the environment variable + cmd+=" -n \"threads: ${threads}\" 'sh -c \"RAYON_NUM_THREADS=${threads} ${binary}\"'" +done + +# Execute the hyperfine command +echo "Executing benchmark for all thread counts" +eval $cmd diff --git a/examples/hyper_threading/src/main.rs b/examples/hyper_threading/src/main.rs new file mode 100644 index 0000000000..8c30082d5f --- /dev/null +++ b/examples/hyper_threading/src/main.rs @@ -0,0 +1,67 @@ +use cairo_vm::{ + cairo_run::{cairo_run_program, CairoRunConfig}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, + types::program::Program, +}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +// Define include_bytes_relative macro to prepend a relative path to the file names +macro_rules! include_bytes_relative { + ($fname:expr) => { + include_bytes!(concat!("../../../cairo_programs/benchmarks/", $fname)) + }; +} + +fn main() { + let mut programs = Vec::new(); + + let programs_bytes: [Vec; 18] = [ + include_bytes_relative!("big_factorial.json").to_vec(), + include_bytes_relative!("big_fibonacci.json").to_vec(), + include_bytes_relative!("blake2s_integration_benchmark.json").to_vec(), + include_bytes_relative!("compare_arrays_200000.json").to_vec(), + include_bytes_relative!("dict_integration_benchmark.json").to_vec(), + include_bytes_relative!("field_arithmetic_get_square_benchmark.json").to_vec(), + include_bytes_relative!("integration_builtins.json").to_vec(), + include_bytes_relative!("keccak_integration_benchmark.json").to_vec(), + include_bytes_relative!("linear_search.json").to_vec(), + include_bytes_relative!("math_cmp_and_pow_integration_benchmark.json").to_vec(), + include_bytes_relative!("math_integration_benchmark.json").to_vec(), + include_bytes_relative!("memory_integration_benchmark.json").to_vec(), + include_bytes_relative!("operations_with_data_structures_benchmarks.json").to_vec(), + include_bytes_relative!("pedersen.json").to_vec(), + include_bytes_relative!("poseidon_integration_benchmark.json").to_vec(), + include_bytes_relative!("secp_integration_benchmark.json").to_vec(), + include_bytes_relative!("set_integration_benchmark.json").to_vec(), + include_bytes_relative!("uint256_integration_benchmark.json").to_vec(), + ]; + + for bytes in &programs_bytes { + programs.push(Program::from_bytes(bytes.as_slice(), Some("main")).unwrap()) + } + + let start_time = std::time::Instant::now(); + + // Parallel execution of the program processing + programs.into_par_iter().for_each(|program| { + let cairo_run_config = CairoRunConfig { + entrypoint: "main", + trace_enabled: false, + relocate_mem: false, + layout: "all_cairo", + proof_mode: true, + secure_run: Some(false), + ..Default::default() + }; + let mut hint_executor = BuiltinHintProcessor::new_empty(); + + // Execute each program in parallel + let _result = cairo_run_program(&program, &cairo_run_config, &mut hint_executor) + .expect("Couldn't run program"); + }); + let elapsed = start_time.elapsed(); + + let programs_len: &usize = &programs_bytes.clone().len(); + + tracing::info!(%programs_len, ?elapsed, "Finished"); +} From 16a844e9df476c61f00bdfe5aea810a649f8a684 Mon Sep 17 00:00:00 2001 From: Pedro Fontana Date: Wed, 20 Mar 2024 16:59:38 -0300 Subject: [PATCH 13/36] Update CODEOWNERS (#1680) Co-authored-by: Pedro Fontana --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6d1f46e986..4e1812b071 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @igaray @Oppen @fmoletta @entropidelic @juanbono @pefontana +* @igaray @Oppen @fmoletta @juanbono @pefontana From 0b6d1160f505f7d1b316500a5a3213d207a07d3c Mon Sep 17 00:00:00 2001 From: Alon-Ti <54235977+Alon-Ti@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:16:44 +0200 Subject: [PATCH 14/36] Update segment used offsets with output size. (#1681) Co-authored-by: Pedro Fontana --- vm/src/vm/vm_core.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 4d4238a7ca..947ba263b4 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1107,7 +1107,10 @@ impl VirtualMachine { #[doc(hidden)] pub fn set_output_stop_ptr_offset(&mut self, offset: usize) { if let Some(BuiltinRunner::Output(builtin)) = self.builtin_runners.first_mut() { - builtin.set_stop_ptr_offset(offset) + builtin.set_stop_ptr_offset(offset); + if let Some(segment_used_sizes) = &mut self.segments.segment_used_sizes { + segment_used_sizes[builtin.base()] = offset; + } } } } From bf7d70795b8264dfa8bbf9435f3ab23c1a10e291 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:19:29 -0300 Subject: [PATCH 15/36] Use return type info from sierra when serializing return values in cairo1-run crate (#1665) * Start implementing new serialize_output * Handle enums * Add extra handling for boolean enums * Use new serialization in main loop * Handle panic wrapper * Fix panic handling * Fix variant idx calc * Add test for dict with struct * Add Array & Snapshot * Fix * Handle box + fix spellings + update tests * Update test value * Add signed felt * Add test for squashed dict * Handle Nullable being Null * Complete unhandled barnches * Clippy * Remove old impl of serialize_output * Add Changelog entry * Fix comments * Remove empty case * Fox typo * Handle multiple array formats when serializing * Add test * Fix test value --------- Co-authored-by: Pedro Fontana --- CHANGELOG.md | 6 + cairo1-run/Cargo.toml | 1 + cairo1-run/src/cairo_run.rs | 406 +++++++++++++++++- cairo1-run/src/main.rs | 187 +++----- .../cairo-1-programs/bytes31_ret.cairo | 5 + .../cairo-1-programs/dict_with_struct.cairo | 24 ++ .../cairo-1-programs/felt_dict_squash.cairo | 18 + .../cairo-1-programs/null_ret.cairo | 3 + .../cairo-1-programs/tensor_new.cairo | 65 +++ 9 files changed, 576 insertions(+), 139 deletions(-) create mode 100644 cairo_programs/cairo-1-programs/bytes31_ret.cairo create mode 100644 cairo_programs/cairo-1-programs/dict_with_struct.cairo create mode 100644 cairo_programs/cairo-1-programs/felt_dict_squash.cairo create mode 100644 cairo_programs/cairo-1-programs/null_ret.cairo create mode 100644 cairo_programs/cairo-1-programs/tensor_new.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd8f750e4..8e320723e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ #### Upcoming Changes +* feat(BREAKING): Use return type info from sierra when serializing return values in cairo1-run crate [#1665](https://github.com/lambdaclass/cairo-vm/pull/1665) + * Removed public function `serialize_output`. + * Add field `serialize_output` to `Cairo1RunConfig`. + * Function `cairo_run_program` now returns an extra `Option` value with the serialized output if `serialize_output` is enabled in the config. + * Output serialization improved as it now uses the sierra program data to identify return value's types. + * feat: Create hyper_threading crate to benchmark the `cairo-vm` in a hyper-threaded environment [#1679](https://github.com/lambdaclass/cairo-vm/pull/1679) * feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memory registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml index 391774f86a..a831387a82 100644 --- a/cairo1-run/Cargo.toml +++ b/cairo1-run/Cargo.toml @@ -26,6 +26,7 @@ bincode.workspace = true assert_matches = "1.5.0" rstest = "0.17.0" mimalloc = { version = "0.1.37", default-features = false, optional = true } +num-traits = { version = "0.2", default-features = false } [features] default = ["with_mimalloc"] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 8376727918..fec7569fad 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -13,11 +13,11 @@ use cairo_lang_sierra::{ ConcreteType, NamedType, }, ids::ConcreteTypeId, - program::{Function, Program as SierraProgram}, + program::{Function, GenericArg, Program as SierraProgram}, program_registry::ProgramRegistry, }; use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_gas::{gas_info::GasInfo, objects::CostInfoProvider}; use cairo_lang_sierra_to_casm::{ compiler::CairoProgram, metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, @@ -26,6 +26,7 @@ use cairo_lang_sierra_type_size::get_type_size_map; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + math_utils::signed_felt, serde::deserialize_program::{ ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, }, @@ -43,14 +44,17 @@ use cairo_vm::{ }, Felt252, }; -use itertools::chain; -use std::collections::HashMap; +use itertools::{chain, Itertools}; +use num_traits::{cast::ToPrimitive, Zero}; +use std::{collections::HashMap, iter::Peekable, slice::Iter}; use crate::{Error, FuncArg}; #[derive(Debug)] pub struct Cairo1RunConfig<'a> { pub args: &'a [FuncArg], + // Serializes program output into a user-friendly format + pub serialize_output: bool, pub trace_enabled: bool, pub relocate_mem: bool, pub layout: &'a str, @@ -66,6 +70,7 @@ impl Default for Cairo1RunConfig<'_> { fn default() -> Self { Self { args: Default::default(), + serialize_output: false, trace_enabled: false, relocate_mem: false, layout: "plain", @@ -77,11 +82,19 @@ impl Default for Cairo1RunConfig<'_> { } // Runs a Cairo 1 program -// Returns the runner & VM after execution + the return values +// Returns the runner & VM after execution + the return values + the serialized return values (if serialize_output is enabled) pub fn cairo_run_program( sierra_program: &SierraProgram, cairo_run_config: Cairo1RunConfig, -) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { +) -> Result< + ( + CairoRunner, + VirtualMachine, + Vec, + Option, + ), + Error, +> { let metadata = create_metadata(sierra_program, Some(Default::default()))?; let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; let type_sizes = @@ -211,6 +224,18 @@ pub fn cairo_run_program( // Fetch return values let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + let serialized_output = if cairo_run_config.serialize_output { + Some(serialize_output( + &return_values, + &mut vm, + return_type_id, + &sierra_program_registry, + &type_sizes, + )) + } else { + None + }; + // Set stop pointers for builtins so we can obtain the air public input if cairo_run_config.finalize_builtins { finalize_builtins( @@ -233,7 +258,7 @@ pub fn cairo_run_program( runner.relocate(&mut vm, true)?; - Ok((runner, vm, return_values)) + Ok((runner, vm, return_values, serialized_output)) } fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { @@ -726,6 +751,371 @@ fn finalize_builtins( Ok(()) } +fn serialize_output( + return_values: &[MaybeRelocatable], + vm: &mut VirtualMachine, + return_type_id: &ConcreteTypeId, + sierra_program_registry: &ProgramRegistry, + type_sizes: &UnorderedHashMap, +) -> String { + let mut output_string = String::new(); + let mut return_values_iter: Peekable> = return_values.iter().peekable(); + serialize_output_inner( + &mut return_values_iter, + &mut output_string, + vm, + return_type_id, + sierra_program_registry, + type_sizes, + ); + output_string +} + +fn serialize_output_inner( + return_values_iter: &mut Peekable>, + output_string: &mut String, + vm: &mut VirtualMachine, + return_type_id: &ConcreteTypeId, + sierra_program_registry: &ProgramRegistry, + type_sizes: &UnorderedHashMap, +) { + match sierra_program_registry.get_type(return_type_id).unwrap() { + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Array(info) => { + // Fetch array from memory + let array_start = return_values_iter + .next() + .expect("Missing return value") + .get_relocatable() + .expect("Array start_ptr not Relocatable"); + // Arrays can come in two formats: either [start_ptr, end_ptr] or [end_ptr], with the start_ptr being implicit (base of the end_ptr's segment) + let (array_start, array_size ) = match return_values_iter.peek().and_then(|mr| mr.get_relocatable()) { + Some(array_end) if array_end.segment_index == array_start.segment_index && array_end.offset >= array_start.offset => { + // Pop the value we just peeked + return_values_iter.next(); + (array_start, (array_end - array_start).unwrap()) + } + _ => ((array_start.segment_index, 0).into(), array_start.offset), + }; + let array_data = vm.get_continuous_range(array_start, array_size).unwrap(); + let mut array_data_iter = array_data.iter().peekable(); + let array_elem_id = &info.ty; + // Serialize array data + maybe_add_whitespace(output_string); + output_string.push('['); + while array_data_iter.peek().is_some() { + serialize_output_inner( + &mut array_data_iter, + output_string, + vm, + array_elem_id, + sierra_program_registry, + type_sizes, + ) + } + output_string.push(']'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Box(info) => { + // As this represents a pointer, we need to extract it's values + let ptr = return_values_iter + .next() + .expect("Missing return value") + .get_relocatable() + .expect("Box Pointer is not Relocatable"); + let type_size = type_sizes.type_size(&info.ty); + let data = vm + .get_continuous_range(ptr, type_size) + .expect("Failed to extract value from nullable ptr"); + let mut data_iter = data.iter().peekable(); + serialize_output_inner( + &mut data_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Const(_) => { + unimplemented!("Not supported in the current version") + }, + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Felt252(_) + // Only unsigned integer values implement Into + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Bytes31(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint8(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint16(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint32(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint64(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint128(_) => { + maybe_add_whitespace(output_string); + let val = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Value is not an integer"); + output_string.push_str(&val.to_string()); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint8(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint16(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint32(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint64(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint128(_) => { + maybe_add_whitespace(output_string); + let val = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Value is not an integer"); + output_string.push_str(&signed_felt(val).to_string()); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::NonZero(info) => { + serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Nullable(info) => { + // As this represents a pointer, we need to extract it's values + let ptr = match return_values_iter.next().expect("Missing return value") { + MaybeRelocatable::RelocatableValue(ptr) => *ptr, + MaybeRelocatable::Int(felt) if felt.is_zero() => { + // Nullable is Null + maybe_add_whitespace(output_string); + output_string.push_str("null"); + return; + } + _ => panic!("Invalid Nullable"), + }; + let type_size = type_sizes.type_size(&info.ty); + let data = vm + .get_continuous_range(ptr, type_size) + .expect("Failed to extract value from nullable ptr"); + let mut data_iter = data.iter().peekable(); + serialize_output_inner( + &mut data_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Enum(info) => { + // First we check if it is a Panic enum, as we already handled panics when fetching return values, + // we can ignore them and move on to the non-panic variant + if let GenericArg::UserType(user_type) = &info.info.long_id.generic_args[0] { + if user_type + .debug_name + .as_ref() + .is_some_and(|n| n.starts_with("core::panics::PanicResult")) + { + return serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.variants[0], + sierra_program_registry, + type_sizes, + ); + } + } + let num_variants = &info.variants.len(); + let casm_variant_idx: usize = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Enum tag is not integer") + .to_usize() + .expect("Invalid enum tag"); + // Convert casm variant idx to sierra variant idx + let variant_idx = if *num_variants > 2 { + num_variants - 1 - (casm_variant_idx >> 1) + } else { + casm_variant_idx + }; + let variant_type_id = &info.variants[variant_idx]; + + // Handle core::bool separately + if let GenericArg::UserType(user_type) = &info.info.long_id.generic_args[0] { + if user_type + .debug_name + .as_ref() + .is_some_and(|n| n == "core::bool") + { + // Sanity checks + assert!( + *num_variants == 2 + && variant_idx < 2 + && type_sizes + .get(&info.variants[0]) + .is_some_and(|size| size.is_zero()) + && type_sizes + .get(&info.variants[1]) + .is_some_and(|size| size.is_zero()), + "Malformed bool enum" + ); + + let boolean_string = match variant_idx { + 0 => "false", + _ => "true", + }; + maybe_add_whitespace(output_string); + output_string.push_str(boolean_string); + return; + } + } + // TODO: Something similar to the bool handling could be done for unit enum variants if we could get the type info with the variant names + + // Space is always allocated for the largest enum member, padding with zeros in front for the smaller variants + let mut max_variant_size = 0; + for variant in &info.variants { + let variant_size = type_sizes.get(variant).unwrap(); + max_variant_size = std::cmp::max(max_variant_size, *variant_size) + } + for _ in 0..max_variant_size - type_sizes.get(variant_type_id).unwrap() { + // Remove padding + assert_eq!( + return_values_iter.next(), + Some(&MaybeRelocatable::from(0)), + "Malformed enum" + ); + } + serialize_output_inner( + return_values_iter, + output_string, + vm, + variant_type_id, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Struct(info) => { + for member_type_id in &info.members { + serialize_output_inner( + return_values_iter, + output_string, + vm, + member_type_id, + sierra_program_registry, + type_sizes, + ) + } + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Felt252Dict(info) => { + // Process Dictionary + let dict_ptr = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Dict Ptr not Relocatable"); + if !(dict_ptr.offset + == vm + .get_segment_size(dict_ptr.segment_index as usize) + .unwrap_or_default() + && dict_ptr.offset % 3 == 0) + { + panic!("Return value is not a valid Felt252Dict") + } + // Fetch dictionary values type id + let value_type_id = &info.ty; + // Fetch the dictionary's memory + let dict_mem = vm + .get_continuous_range((dict_ptr.segment_index, 0).into(), dict_ptr.offset) + .expect("Malformed dictionary memory"); + // Serialize the dictionary + output_string.push('{'); + // The dictionary's memory is made up of (key, prev_value, next_value) tuples + // The prev value is not relevant to the user so we can skip over it for calrity + for (key, _, value) in dict_mem.iter().tuples() { + maybe_add_whitespace(output_string); + // Serialize the key wich should always be a Felt value + output_string.push_str(&key.to_string()); + output_string.push(':'); + // Serialize the value + // We create a peekable array here in order to use the serialize_output_inner as the value could be a span + let value_vec = vec![value.clone()]; + let mut value_iter: Peekable> = value_vec.iter().peekable(); + serialize_output_inner( + &mut value_iter, + output_string, + vm, + value_type_id, + sierra_program_registry, + type_sizes, + ); + } + output_string.push('}'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::SquashedFelt252Dict(info) => { + // Process Dictionary + let dict_start = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Squashed dict_start ptr not Relocatable"); + let dict_end = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Squashed dict_end ptr not Relocatable"); + let dict_size = (dict_end - dict_start).unwrap(); + if dict_size % 3 != 0 { + panic!("Return value is not a valid SquashedFelt252Dict") + } + // Fetch dictionary values type id + let value_type_id = &info.ty; + // Fetch the dictionary's memory + let dict_mem = vm + .get_continuous_range(dict_start, dict_size) + .expect("Malformed squashed dictionary memory"); + // Serialize the dictionary + output_string.push('{'); + // The dictionary's memory is made up of (key, prev_value, next_value) tuples + // The prev value is not relevant to the user so we can skip over it for calrity + for (key, _, value) in dict_mem.iter().tuples() { + maybe_add_whitespace(output_string); + // Serialize the key wich should always be a Felt value + output_string.push_str(&key.to_string()); + output_string.push(':'); + // Serialize the value + // We create a peekable array here in order to use the serialize_output_inner as the value could be a span + let value_vec = vec![value.clone()]; + let mut value_iter: Peekable> = value_vec.iter().peekable(); + serialize_output_inner( + &mut value_iter, + output_string, + vm, + value_type_id, + sierra_program_registry, + type_sizes, + ); + } + output_string.push('}'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Span(_) => unimplemented!("Span types get resolved to Array in the current version"), + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Snapshot(info) => { + serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + _ => panic!("Unexpected return type") + } +} + +fn maybe_add_whitespace(string: &mut String) { + if !string.is_empty() && !string.ends_with('[') && !string.ends_with('{') { + string.push(' '); + } +} + #[cfg(test)] mod tests { use std::path::Path; @@ -791,7 +1181,7 @@ mod tests { ..Default::default() }; // Run program - let (runner, vm, return_values) = + let (runner, vm, return_values, _) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values // And handle the panics returning an error, so we need to add it here diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index e9875babb7..e8aeabadb6 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -6,13 +6,10 @@ use cairo_run::Cairo1RunConfig; use cairo_vm::{ air_public_input::PublicInputError, cairo_run::EncodeTraceError, - types::{errors::program_errors::ProgramError, relocatable::MaybeRelocatable}, - vm::{ - errors::{ - memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, - vm_errors::VirtualMachineError, - }, - vm_core::VirtualMachine, + types::errors::program_errors::ProgramError, + vm::errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, + vm_errors::VirtualMachineError, }, Felt252, }; @@ -20,9 +17,7 @@ use clap::{Parser, ValueHint}; use itertools::Itertools; use std::{ io::{self, Write}, - iter::Peekable, path::PathBuf, - slice::Iter, }; use thiserror::Error; @@ -216,6 +211,7 @@ fn run(args: impl Iterator) -> Result, Error> { let cairo_run_config = Cairo1RunConfig { proof_mode: args.proof_mode, + serialize_output: args.print_output, relocate_mem: args.memory_file.is_some() || args.air_public_input.is_some(), layout: &args.layout, trace_enabled: args.trace_file.is_some() || args.air_public_input.is_some(), @@ -232,15 +228,9 @@ fn run(args: impl Iterator) -> Result, Error> { let sierra_program = compile_cairo_project_at_path(&args.filename, compiler_config) .map_err(|err| Error::SierraCompilation(err.to_string()))?; - let (runner, vm, return_values) = + let (runner, vm, _, serialized_output) = cairo_run::cairo_run_program(&sierra_program, cairo_run_config)?; - let output_string = if args.print_output { - Some(serialize_output(&vm, &return_values)) - } else { - None - }; - if let Some(file_path) = args.air_public_input { let json = runner.get_air_public_input(&vm)?.serialize_json()?; std::fs::write(file_path, json)?; @@ -297,7 +287,7 @@ fn run(args: impl Iterator) -> Result, Error> { memory_writer.flush()?; } - Ok(output_string) + Ok(serialized_output) } fn main() -> Result<(), Error> { @@ -331,116 +321,6 @@ fn main() -> Result<(), Error> { } } -/// Serializes the return values in a user-friendly format -/// Displays Arrays using brackets ([]) and Dictionaries using ({}) -/// Recursively dereferences referenced values (such as Span & Box) -pub fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { - let mut output_string = String::new(); - let mut return_values_iter: Peekable> = return_values.iter().peekable(); - serialize_output_inner(&mut return_values_iter, &mut output_string, vm); - fn serialize_output_inner( - iter: &mut Peekable>, - output_string: &mut String, - vm: &VirtualMachine, - ) { - while let Some(val) = iter.next() { - if let MaybeRelocatable::RelocatableValue(x) = val { - // Check if the next value is a relocatable of the same index - if let Some(MaybeRelocatable::RelocatableValue(y)) = iter.peek() { - // Check if the two relocatable values represent a valid array in memory - if x.segment_index == y.segment_index && x.offset <= y.offset { - // Fetch the y value from the iterator so we don't serialize it twice - iter.next(); - // Fetch array - maybe_add_whitespace(output_string); - output_string.push('['); - let array = vm.get_continuous_range(*x, y.offset - x.offset).unwrap(); - let mut array_iter: Peekable> = - array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - output_string.push(']'); - continue; - } - } - - // Check if the single relocatable value represents a span - // In this case, the reloacatable will point us to the (start, end) pair in memory - // For example, the relocatable value may be 14:0, with the segment 14 containing [13:0 13:4] which is a valid array - if let (Ok(x), Ok(y)) = (vm.get_relocatable(*x), vm.get_relocatable(x + 1)) { - if x.segment_index == y.segment_index && y.offset >= x.offset { - // Fetch array - maybe_add_whitespace(output_string); - output_string.push('['); - let array = vm.get_continuous_range(x, y.offset - x.offset).unwrap(); - let mut array_iter: Peekable> = - array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - output_string.push(']'); - continue; - } - } - - // Check if the relocatable value represents a dictionary - // To do so we can check if the relocatable consists of the last dict_ptr, which should be a pointer to the next empty cell in the dictionary's segment - // We can check that the dict_ptr's offset is consistent with the length of the segment and that the segment is made up of tuples of three elements (key, prev_val, val) - if x.offset - == vm - .get_segment_size(x.segment_index as usize) - .unwrap_or_default() - && x.offset % 3 == 0 - { - // Fetch the dictionary's memory - let dict_mem = vm - .get_continuous_range((x.segment_index, 0).into(), x.offset) - .expect("Malformed dictionary memory"); - // Serialize the dictionary - output_string.push('{'); - // The dictionary's memory is made up of (key, prev_value, next_value) tuples - // The prev value is not relevant to the user so we can skip over it for calrity - for (key, _, value) in dict_mem.iter().tuples() { - maybe_add_whitespace(output_string); - // Serialize the key wich should always be a Felt value - output_string.push_str(&key.to_string()); - output_string.push(':'); - // Serialize the value - // We create a peekable array here in order to use the serialize_output_inner as the value could be a span - let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = - value_vec.iter().peekable(); - serialize_output_inner(&mut value_iter, output_string, vm); - } - output_string.push('}'); - continue; - } - - // Finally, if the relocatable is neither the start of an array, a span, or a dictionary, it should be a reference (Such as Box) - // In this case we show the referenced value (if it exists) - // As this reference can also hold a reference we use the serialize_output_inner function to handle it recursively - if let Some(val) = vm.get_maybe(x) { - maybe_add_whitespace(output_string); - let array = vec![val.clone()]; - let mut array_iter: Peekable> = array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - continue; - } - } - maybe_add_whitespace(output_string); - output_string.push_str(&val.to_string()); - } - } - - fn maybe_add_whitespace(string: &mut String) { - if !string.is_empty() - && !string.ends_with('[') - && !string.ends_with(':') - && !string.ends_with('{') - { - string.push(' '); - } - } - output_string -} - #[cfg(test)] mod tests { use super::*; @@ -492,7 +372,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/hello.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_hello_ok(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(Some(res)) if res == "1 1234"); + assert_matches!(run(args), Ok(Some(res)) if res == "1234"); } #[rstest] @@ -556,7 +436,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_simple_ok(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(Some(res)) if res == "1"); + assert_matches!(run(args), Ok(Some(res)) if res == "true"); } #[rstest] @@ -644,7 +524,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_felt_dict(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "{66675:[8 9 10 11] 66676:[1 2 3]}"; + let expected_output = "{66675: [8 9 10 11] 66676: [1 2 3]}"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } @@ -671,7 +551,52 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/nullable_box_vec.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_nullable_box_vec(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "{0:10 1:20 2:30} 3"; + let expected_output = "{0: 10 1: 20 2: 30} 3"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/dict_with_struct.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/dict_with_struct.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_dict_with_struct(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "{0: 1 true 1: 1 false 2: 1 true}"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict_squash.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict_squash.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_felt_dict_squash(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "{66675: [4 5 6] 66676: [1 2 3]}"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/null_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/null_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_null_ret(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "null"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/bytes31_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/bytes31_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_bytes31_ret(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "123"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/tensor_new.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/tensor_new.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_tensor_new(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "[1 2] [1 false 1 true]"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } } diff --git a/cairo_programs/cairo-1-programs/bytes31_ret.cairo b/cairo_programs/cairo-1-programs/bytes31_ret.cairo new file mode 100644 index 0000000000..3a67f9cbf5 --- /dev/null +++ b/cairo_programs/cairo-1-programs/bytes31_ret.cairo @@ -0,0 +1,5 @@ +fn main() -> bytes31 { + let a: u128 = 123; + let b: bytes31 = a.into(); + b +} diff --git a/cairo_programs/cairo-1-programs/dict_with_struct.cairo b/cairo_programs/cairo-1-programs/dict_with_struct.cairo new file mode 100644 index 0000000000..e24df874c2 --- /dev/null +++ b/cairo_programs/cairo-1-programs/dict_with_struct.cairo @@ -0,0 +1,24 @@ +use core::nullable::{nullable_from_box, match_nullable, FromNullableResult}; + + +#[derive(Drop, Copy)] +struct FP16x16 { + mag: u32, + sign: bool +} + +fn main() -> Felt252Dict> { + // Create the dictionary + let mut d: Felt252Dict> = Default::default(); + + let box_a = BoxTrait::new(FP16x16 { mag: 1, sign: false }); + let box_b = BoxTrait::new(FP16x16 { mag: 1, sign: true }); + let box_c = BoxTrait::new(FP16x16 { mag: 1, sign: true }); + + // Insert it as a `Span` + d.insert(0, nullable_from_box(box_c)); + d.insert(1, nullable_from_box(box_a)); + d.insert(2, nullable_from_box(box_b)); + + d +} diff --git a/cairo_programs/cairo-1-programs/felt_dict_squash.cairo b/cairo_programs/cairo-1-programs/felt_dict_squash.cairo new file mode 100644 index 0000000000..7f1e284e32 --- /dev/null +++ b/cairo_programs/cairo-1-programs/felt_dict_squash.cairo @@ -0,0 +1,18 @@ +use core::nullable::{nullable_from_box, match_nullable, FromNullableResult}; +use core::dict::Felt252DictEntry; + +fn main() -> SquashedFelt252Dict>> { + // Create the dictionary + let mut d: Felt252Dict>> = Default::default(); + + // Create the array to insert + let a = array![8, 9, 10, 11]; + let b = array![1, 2, 3]; + let c = array![4, 5, 6]; + + // Insert it as a `Span` + d.insert(66675, nullable_from_box(BoxTrait::new(a.span()))); + d.insert(66676, nullable_from_box(BoxTrait::new(b.span()))); + d.insert(66675, nullable_from_box(BoxTrait::new(c.span()))); + d.squash() +} diff --git a/cairo_programs/cairo-1-programs/null_ret.cairo b/cairo_programs/cairo-1-programs/null_ret.cairo new file mode 100644 index 0000000000..85769a8e15 --- /dev/null +++ b/cairo_programs/cairo-1-programs/null_ret.cairo @@ -0,0 +1,3 @@ +fn main() -> Nullable { + null() +} diff --git a/cairo_programs/cairo-1-programs/tensor_new.cairo b/cairo_programs/cairo-1-programs/tensor_new.cairo new file mode 100644 index 0000000000..2af717beed --- /dev/null +++ b/cairo_programs/cairo-1-programs/tensor_new.cairo @@ -0,0 +1,65 @@ +// FP16x16 +#[derive(Serde, Copy, Drop)] +struct FP16x16 { + mag: u32, + sign: bool +} + +trait FixedTrait { + fn new(mag: MAG, sign: bool) -> T; +} + +impl FP16x16Impl of FixedTrait { + fn new(mag: u32, sign: bool) -> FP16x16 { + FP16x16 { mag: mag, sign: sign } + } +} + +//Tensor +#[derive(Copy, Drop)] +struct Tensor { + shape: Span, + data: Span, +} + +trait TensorTrait { + fn new(shape: Span, data: Span) -> Tensor; +} + +impl FP16x16Tensor of TensorTrait { + fn new(shape: Span, data: Span) -> Tensor { + new_tensor(shape, data) + } +} + +fn new_tensor(shape: Span, data: Span) -> Tensor { + check_shape::(shape, data); + Tensor:: { shape, data } +} + +fn check_shape(shape: Span, data: Span) { + assert(len_from_shape(shape) == data.len(), 'wrong tensor shape'); +} + +fn len_from_shape(mut shape: Span) -> usize { + let mut result: usize = 1; + + loop { + match shape.pop_front() { + Option::Some(item) => { result *= *item; }, + Option::None => { break; } + }; + }; + + result +} + +fn main() -> Tensor { + TensorTrait::new( + array![1, 2].span(), + array![ + FixedTrait::new(1, false), + FixedTrait::new(1, true) + ].span() + ) +} From 72f0baddb007a8440d9f4a49c50dea0a67275a98 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:16:54 -0300 Subject: [PATCH 16/36] Use `Program::from_file` in hyperthreading crate (#1685) * Read programs from file in hyperthreading crate * Fix code * Fix path * fmt --- examples/hyper_threading/Cargo.toml | 2 +- examples/hyper_threading/src/main.rs | 55 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/examples/hyper_threading/Cargo.toml b/examples/hyper_threading/Cargo.toml index e518c2bdc0..854e2c3fde 100644 --- a/examples/hyper_threading/Cargo.toml +++ b/examples/hyper_threading/Cargo.toml @@ -9,6 +9,6 @@ keywords.workspace = true [dependencies] -cairo-vm = { workspace = true } +cairo-vm = { workspace = true, features = ["std"] } rayon = "1.9.0" tracing = "0.1.40" diff --git a/examples/hyper_threading/src/main.rs b/examples/hyper_threading/src/main.rs index 8c30082d5f..eb6fbf47c9 100644 --- a/examples/hyper_threading/src/main.rs +++ b/examples/hyper_threading/src/main.rs @@ -4,40 +4,43 @@ use cairo_vm::{ types::program::Program, }; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::path::Path; -// Define include_bytes_relative macro to prepend a relative path to the file names -macro_rules! include_bytes_relative { +// Define build_filename macro to prepend a relative path to the file names +macro_rules! build_filename { ($fname:expr) => { - include_bytes!(concat!("../../../cairo_programs/benchmarks/", $fname)) + format!("cairo_programs/benchmarks/{}", $fname) }; } fn main() { let mut programs = Vec::new(); - let programs_bytes: [Vec; 18] = [ - include_bytes_relative!("big_factorial.json").to_vec(), - include_bytes_relative!("big_fibonacci.json").to_vec(), - include_bytes_relative!("blake2s_integration_benchmark.json").to_vec(), - include_bytes_relative!("compare_arrays_200000.json").to_vec(), - include_bytes_relative!("dict_integration_benchmark.json").to_vec(), - include_bytes_relative!("field_arithmetic_get_square_benchmark.json").to_vec(), - include_bytes_relative!("integration_builtins.json").to_vec(), - include_bytes_relative!("keccak_integration_benchmark.json").to_vec(), - include_bytes_relative!("linear_search.json").to_vec(), - include_bytes_relative!("math_cmp_and_pow_integration_benchmark.json").to_vec(), - include_bytes_relative!("math_integration_benchmark.json").to_vec(), - include_bytes_relative!("memory_integration_benchmark.json").to_vec(), - include_bytes_relative!("operations_with_data_structures_benchmarks.json").to_vec(), - include_bytes_relative!("pedersen.json").to_vec(), - include_bytes_relative!("poseidon_integration_benchmark.json").to_vec(), - include_bytes_relative!("secp_integration_benchmark.json").to_vec(), - include_bytes_relative!("set_integration_benchmark.json").to_vec(), - include_bytes_relative!("uint256_integration_benchmark.json").to_vec(), + let program_filenames: [String; 18] = [ + build_filename!("big_factorial.json"), + build_filename!("big_fibonacci.json"), + build_filename!("blake2s_integration_benchmark.json"), + build_filename!("compare_arrays_200000.json"), + build_filename!("dict_integration_benchmark.json"), + build_filename!("field_arithmetic_get_square_benchmark.json"), + build_filename!("integration_builtins.json"), + build_filename!("keccak_integration_benchmark.json"), + build_filename!("linear_search.json"), + build_filename!("math_cmp_and_pow_integration_benchmark.json"), + build_filename!("math_integration_benchmark.json"), + build_filename!("memory_integration_benchmark.json"), + build_filename!("operations_with_data_structures_benchmarks.json"), + build_filename!("pedersen.json"), + build_filename!("poseidon_integration_benchmark.json"), + build_filename!("secp_integration_benchmark.json"), + build_filename!("set_integration_benchmark.json"), + build_filename!("uint256_integration_benchmark.json"), ]; - for bytes in &programs_bytes { - programs.push(Program::from_bytes(bytes.as_slice(), Some("main")).unwrap()) + let n_programs = &program_filenames.len(); + + for filename in program_filenames { + programs.push(Program::from_file(Path::new(&filename), Some("main")).unwrap()) } let start_time = std::time::Instant::now(); @@ -61,7 +64,5 @@ fn main() { }); let elapsed = start_time.elapsed(); - let programs_len: &usize = &programs_bytes.clone().len(); - - tracing::info!(%programs_len, ?elapsed, "Finished"); + tracing::info!(%n_programs, ?elapsed, "Finished"); } From 44de6146bb307ee392f83500ddcf791157b943f1 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:14:10 -0300 Subject: [PATCH 17/36] Bump cairo_lang to 0.13.1 in testing env (#1687) * Update requirements.txt * Update program + add changelog entry * Impl new hints * Adjust recursive_large_output params * Update tests --- CHANGELOG.md | 2 ++ cairo_programs/secp.cairo | 12 ++++++------ cairo_programs/secp_integration_tests.cairo | 4 ++-- requirements.txt | 4 ++-- .../builtin_hint_processor_definition.rs | 8 +++++++- .../builtin_hint_processor/hint_code.rs | 5 +++++ .../builtin_hint_processor/poseidon_utils.rs | 15 ++++++++++++++- .../instance_definitions/builtins_instance_def.rs | 6 +++--- vm/src/types/layout.rs | 4 ++-- 9 files changed, 43 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e320723e7..cfa56b1389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Bump cairo_lang to 0.13.1 in testing env [#1687](https://github.com/lambdaclass/cairo-vm/pull/1687) + * feat(BREAKING): Use return type info from sierra when serializing return values in cairo1-run crate [#1665](https://github.com/lambdaclass/cairo-vm/pull/1665) * Removed public function `serialize_output`. * Add field `serialize_output` to `Cairo1RunConfig`. diff --git a/cairo_programs/secp.cairo b/cairo_programs/secp.cairo index 747d8c7421..c7e9018eb0 100644 --- a/cairo_programs/secp.cairo +++ b/cairo_programs/secp.cairo @@ -1,6 +1,6 @@ %builtins range_check -from starkware.cairo.common.cairo_secp.bigint import nondet_bigint3, BigInt3, bigint_to_uint256 - +from starkware.cairo.common.cairo_secp.bigint3 import BigInt3, SumBigInt3 +from starkware.cairo.common.cairo_secp.bigint import nondet_bigint3, bigint_to_uint256 from starkware.cairo.common.cairo_secp.field import verify_zero, UnreducedBigInt3, reduce, is_zero func main{range_check_ptr: felt}() { @@ -43,17 +43,17 @@ func main{range_check_ptr: felt}() { ); // is_zero - let (u) = is_zero(BigInt3(0, 0, 0)); + let (u) = is_zero(SumBigInt3(0, 0, 0)); assert u = 1; let (v) = is_zero( - BigInt3(232113757366008801543585, 232113757366008801543585, 232113757366008801543585) + SumBigInt3(232113757366008801543585, 232113757366008801543585, 232113757366008801543585) ); assert v = 0; - let (w) = is_zero(BigInt3(-10, -10, -10)); + let (w) = is_zero(SumBigInt3(-10, -10, -10)); assert w = 0; - let (z) = is_zero(BigInt3(1833312543, 67523423, 8790312)); + let (z) = is_zero(SumBigInt3(1833312543, 67523423, 8790312)); assert z = 0; return (); diff --git a/cairo_programs/secp_integration_tests.cairo b/cairo_programs/secp_integration_tests.cairo index 7eeaae615b..45ec410248 100644 --- a/cairo_programs/secp_integration_tests.cairo +++ b/cairo_programs/secp_integration_tests.cairo @@ -1,12 +1,12 @@ %builtins range_check from starkware.cairo.common.cairo_secp.bigint import ( - BigInt3, bigint_mul, nondet_bigint3, bigint_to_uint256, uint256_to_bigint, ) +from starkware.cairo.common.cairo_secp.bigint3 import BigInt3, SumBigInt3 from starkware.cairo.common.cairo_secp.signature import ( get_generator_point, validate_signature_entry, @@ -86,7 +86,7 @@ func test_operations{range_check_ptr}(point: EcPoint) { let (zero_uint, _) = uint256_add(slope_uint, neg_slope); let (zero) = uint256_to_bigint(zero_uint); - let (is_z) = is_zero(zero); + let (is_z) = is_zero(SumBigInt3(d0=zero.d0, d1=zero.d1, d2=zero.d2)); assert is_z = 1; let (pow2, scaled) = ec_mul_inner(point, 0, 0); diff --git a/requirements.txt b/requirements.txt index 5eca66949f..41c32affe0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ ecdsa==0.18.0 bitarray==2.7.3 -fastecdsa==2.2.3 +fastecdsa==2.3.0 sympy==1.11.1 typeguard==2.13.3 -cairo-lang==0.12.2 +cairo-lang==0.13.1 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 b23f58635b..93c60f70b5 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 @@ -57,7 +57,7 @@ use crate::{ math_utils::*, memcpy_hint_utils::{add_segment, enter_scope, exit_scope, memcpy_enter_scope}, memset_utils::{memset_enter_scope, memset_step_loop}, - poseidon_utils::{n_greater_than_10, n_greater_than_2}, + poseidon_utils::{elements_over_x, n_greater_than_10, n_greater_than_2}, pow_utils::pow, secp::{ bigint_utils::{bigint_to_uint256, hi_max_bitlen, nondet_bigint3}, @@ -731,6 +731,12 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::NONDET_N_GREATER_THAN_2 => { n_greater_than_2(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::NONDET_ELEMENTS_OVER_TEN => { + elements_over_x(vm, &hint_data.ids_data, &hint_data.ap_tracking, 10) + } + hint_code::NONDET_ELEMENTS_OVER_TWO => { + elements_over_x(vm, &hint_data.ids_data, &hint_data.ap_tracking, 2) + } hint_code::RANDOM_EC_POINT => { random_ec_point_hint(vm, &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 77365943f8..b6b0f1ca24 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1421,3 +1421,8 @@ data = __dict_manager.get_dict(ids.dict_ptr) print( {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} )"#; + +pub const NONDET_ELEMENTS_OVER_TEN: &str = + "memory[ap] = to_felt_or_relocatable(ids.elements_end - ids.elements >= 10)"; +pub const NONDET_ELEMENTS_OVER_TWO: &str = + "memory[ap] = to_felt_or_relocatable(ids.elements_end - ids.elements >= 2)"; diff --git a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs index 7ba388fa7b..e33f35af61 100644 --- a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs @@ -8,7 +8,7 @@ use crate::{ vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; -use super::hint_utils::{get_integer_from_var_name, insert_value_into_ap}; +use super::hint_utils::{get_integer_from_var_name, get_ptr_from_var_name, insert_value_into_ap}; use num_traits::ToPrimitive; // Implements hint: "memory[ap] = to_felt_or_relocatable(ids.n >= 10)" @@ -37,6 +37,19 @@ pub fn n_greater_than_2( insert_value_into_ap(vm, value) } +// Implements hint: "memory[ap] = to_felt_or_relocatable(ids.elements_end - ids.elements >= x)" +pub fn elements_over_x( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + x: usize, +) -> Result<(), HintError> { + let elements_end = get_ptr_from_var_name("elements_end", vm, ids_data, ap_tracking)?; + let elements = get_ptr_from_var_name("elements", vm, ids_data, ap_tracking)?; + let value = Felt252::from(((elements_end - elements)? >= x) as usize); + insert_value_into_ap(vm, value) +} + #[cfg(test)] mod tests { use crate::any_box; diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index 019a926b8f..10675423f7 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -100,13 +100,13 @@ impl BuiltinsInstanceDef { pub(crate) fn recursive_large_output() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(32), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(128), 1)), range_check: Some(RangeCheckInstanceDef::default()), ecdsa: None, bitwise: Some(BitwiseInstanceDef::new(Some(8))), ec_op: None, keccak: None, - poseidon: None, + poseidon: Some(PoseidonInstanceDef::new(Some(8))), } } @@ -249,7 +249,7 @@ mod tests { assert!(builtins.bitwise.is_some()); assert!(builtins.ec_op.is_none()); assert!(builtins.keccak.is_none()); - assert!(builtins.poseidon.is_none()); + assert!(builtins.poseidon.is_some()); } #[test] diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index 82eba1bece..ceea2496f9 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -115,7 +115,7 @@ impl CairoLayout { _public_memory_fraction: 8, _memory_units_per_step: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 13, + _n_trace_colums: 12, _cpu_instance_def: CpuInstanceDef::default(), } } @@ -287,7 +287,7 @@ mod tests { layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 13); + assert_eq!(layout._n_trace_colums, 12); assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } From 42e04161de82d7e5381258def4b65087c8944660 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:33:19 -0300 Subject: [PATCH 18/36] Add zero segment (#1668) * Ignore pesky pie zip files * Add zero segment * Handle zero segment when counting memory holes * Add Changelog entry * Fix macro --- .gitignore | 1 + CHANGELOG.md | 2 + .../builtin_hint_processor/bigint.rs | 1 - .../builtin_hint_processor/poseidon_utils.rs | 1 - vm/src/hint_processor/hint_processor_utils.rs | 1 - vm/src/utils.rs | 11 +--- vm/src/vm/runners/builtin_runner/bitwise.rs | 1 - vm/src/vm/runners/builtin_runner/ec_op.rs | 1 - vm/src/vm/runners/builtin_runner/hash.rs | 1 - vm/src/vm/runners/builtin_runner/keccak.rs | 1 - .../vm/runners/builtin_runner/range_check.rs | 1 - .../runners/builtin_runner/segment_arena.rs | 1 - vm/src/vm/security.rs | 1 - vm/src/vm/vm_core.rs | 10 ++-- vm/src/vm/vm_memory/memory_segments.rs | 59 +++++++++++++++++-- 15 files changed, 64 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index c2c0ebea83..2e1f8ff93e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ **/*.memory **/*.air_public_input **/*.air_private_input +**/*.pie.zip **/*.swp bench/results .python-version diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa56b1389..be9dd49bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Add zero segment [#1668](https://github.com/lambdaclass/cairo-vm/pull/1668) + * feat: Bump cairo_lang to 0.13.1 in testing env [#1687](https://github.com/lambdaclass/cairo-vm/pull/1687) * feat(BREAKING): Use return type info from sierra when serializing return values in cairo1-run crate [#1665](https://github.com/lambdaclass/cairo-vm/pull/1665) diff --git a/vm/src/hint_processor/builtin_hint_processor/bigint.rs b/vm/src/hint_processor/builtin_hint_processor/bigint.rs index 2069446fb7..ff5326bd58 100644 --- a/vm/src/hint_processor/builtin_hint_processor/bigint.rs +++ b/vm/src/hint_processor/builtin_hint_processor/bigint.rs @@ -103,7 +103,6 @@ mod test { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use crate::hint_processor::builtin_hint_processor::hint_code; use crate::hint_processor::hint_processor_definition::{HintProcessorLogic, HintReference}; - use crate::stdlib::collections::HashMap; use crate::types::exec_scope::ExecutionScopes; use crate::utils::test_utils::*; use crate::vm::vm_core::VirtualMachine; diff --git a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs index e33f35af61..02a6dd7f25 100644 --- a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs @@ -57,7 +57,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; use crate::hint_processor::hint_processor_definition::HintReference; - use crate::stdlib::collections::HashMap; use crate::types::exec_scope::ExecutionScopes; use crate::vm::vm_core::VirtualMachine; diff --git a/vm/src/hint_processor/hint_processor_utils.rs b/vm/src/hint_processor/hint_processor_utils.rs index b78c7c9a79..69944d8669 100644 --- a/vm/src/hint_processor/hint_processor_utils.rs +++ b/vm/src/hint_processor/hint_processor_utils.rs @@ -179,7 +179,6 @@ fn get_offset_value_reference( #[cfg(test)] mod tests { use super::*; - use crate::stdlib::collections::HashMap; use crate::{ relocatable, diff --git a/vm/src/utils.rs b/vm/src/utils.rs index eeeec33894..8432a5cd79 100644 --- a/vm/src/utils.rs +++ b/vm/src/utils.rs @@ -121,14 +121,9 @@ pub mod test_utils { macro_rules! segments { ($( (($si:expr, $off:expr), $val:tt) ),* $(,)? ) => { { - let memory = memory!($( (($si, $off), $val) ),*); - $crate::vm::vm_memory::memory_segments::MemorySegmentManager { - memory, - segment_sizes: HashMap::new(), - segment_used_sizes: None, - public_memory_offsets: HashMap::new(), - } - + let mut segments = $crate::vm::vm_memory::memory_segments::MemorySegmentManager::new(); + segments.memory = memory!($( (($si, $off), $val) ),*); + segments } }; diff --git a/vm/src/vm/runners/builtin_runner/bitwise.rs b/vm/src/vm/runners/builtin_runner/bitwise.rs index ca9e1e01cc..1c4bdb889c 100644 --- a/vm/src/vm/runners/builtin_runner/bitwise.rs +++ b/vm/src/vm/runners/builtin_runner/bitwise.rs @@ -224,7 +224,6 @@ mod tests { use super::*; use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; - use crate::stdlib::collections::HashMap; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::runners::builtin_runner::BuiltinRunner; use crate::vm::vm_core::VirtualMachine; diff --git a/vm/src/vm/runners/builtin_runner/ec_op.rs b/vm/src/vm/runners/builtin_runner/ec_op.rs index 37ec430e12..af1951f543 100644 --- a/vm/src/vm/runners/builtin_runner/ec_op.rs +++ b/vm/src/vm/runners/builtin_runner/ec_op.rs @@ -298,7 +298,6 @@ mod tests { use super::*; use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::serde::deserialize_program::BuiltinName; - use crate::stdlib::collections::HashMap; use crate::types::program::Program; use crate::utils::test_utils::*; use crate::vm::errors::cairo_run_errors::CairoRunError; diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index 3ec2aabf27..788c224644 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -219,7 +219,6 @@ mod tests { use super::*; use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::serde::deserialize_program::BuiltinName; - use crate::stdlib::collections::HashMap; use crate::types::program::Program; use crate::utils::test_utils::*; use crate::vm::runners::cairo_runner::CairoRunner; diff --git a/vm/src/vm/runners/builtin_runner/keccak.rs b/vm/src/vm/runners/builtin_runner/keccak.rs index 17e6e3d638..a4f2346b27 100644 --- a/vm/src/vm/runners/builtin_runner/keccak.rs +++ b/vm/src/vm/runners/builtin_runner/keccak.rs @@ -272,7 +272,6 @@ impl KeccakBuiltinRunner { mod tests { use super::*; use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; - use crate::stdlib::collections::HashMap; use crate::types::program::Program; use crate::utils::test_utils::*; use crate::vm::runners::cairo_runner::CairoRunner; diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index 68809b8752..d70e74b9da 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -214,7 +214,6 @@ mod tests { use super::*; use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; - use crate::stdlib::collections::HashMap; use crate::vm::vm_memory::memory::Memory; use crate::{ hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, diff --git a/vm/src/vm/runners/builtin_runner/segment_arena.rs b/vm/src/vm/runners/builtin_runner/segment_arena.rs index 82c9e72aea..7f45f0e8f3 100644 --- a/vm/src/vm/runners/builtin_runner/segment_arena.rs +++ b/vm/src/vm/runners/builtin_runner/segment_arena.rs @@ -142,7 +142,6 @@ fn gen_arg(segments: &mut MemorySegmentManager, data: &[MaybeRelocatable; 3]) -> #[cfg(test)] mod tests { use super::*; - use crate::stdlib::collections::HashMap; use crate::vm::vm_core::VirtualMachine; use crate::{relocatable, utils::test_utils::*, vm::runners::builtin_runner::BuiltinRunner}; #[cfg(target_arch = "wasm32")] diff --git a/vm/src/vm/security.rs b/vm/src/vm/security.rs index 2749fcf154..45440cfd76 100644 --- a/vm/src/vm/security.rs +++ b/vm/src/vm/security.rs @@ -87,7 +87,6 @@ mod test { use super::*; use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::serde::deserialize_program::BuiltinName; - use crate::stdlib::collections::HashMap; use crate::types::relocatable::Relocatable; diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 947ba263b4..8bd47ea1df 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1221,7 +1221,6 @@ mod tests { use crate::vm::runners::builtin_runner::{ BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, }; - use crate::vm::vm_memory::memory::Memory; use crate::{ any_box, hint_processor::builtin_hint_processor::builtin_hint_processor_definition::{ @@ -4321,11 +4320,10 @@ mod tests { ap: 18, fp: 0, }) - .segments(MemorySegmentManager { - segment_sizes: HashMap::new(), - segment_used_sizes: Some(vec![1]), - public_memory_offsets: HashMap::new(), - memory: Memory::new(), + .segments({ + let mut segments = MemorySegmentManager::new(); + segments.segment_used_sizes = Some(vec![1]); + segments }) .skip_instruction_execution(true) .trace(Some(vec![TraceEntry { diff --git a/vm/src/vm/vm_memory/memory_segments.rs b/vm/src/vm/vm_memory/memory_segments.rs index cabba6266b..aa8709419b 100644 --- a/vm/src/vm/vm_memory/memory_segments.rs +++ b/vm/src/vm/vm_memory/memory_segments.rs @@ -1,5 +1,9 @@ +use core::cmp::max; use core::fmt; +use crate::Felt252; +use num_traits::Zero; + use crate::stdlib::prelude::*; use crate::stdlib::{any::Any, collections::HashMap}; use crate::vm::runners::cairo_runner::CairoArg; @@ -12,6 +16,8 @@ use crate::{ }, }; +use super::memory::MemoryCell; + pub struct MemorySegmentManager { pub segment_sizes: HashMap, pub segment_used_sizes: Option>, @@ -19,6 +25,11 @@ pub struct MemorySegmentManager { // A map from segment index to a list of pairs (offset, page_id) that constitute the // public memory. Note that the offset is absolute (not based on the page_id). pub public_memory_offsets: HashMap>, + // Segment index of the zero segment index, a memory segment filled with zeroes, used exclusively by builtin runners + // This segment will never have index 0 so we use 0 to represent uninitialized value + zero_segment_index: usize, + // Segment size of the zero segment index + zero_segment_size: usize, } impl MemorySegmentManager { @@ -71,6 +82,8 @@ impl MemorySegmentManager { segment_used_sizes: None, public_memory_offsets: HashMap::new(), memory: Memory::new(), + zero_segment_index: 0, + zero_segment_size: 0, } } @@ -206,11 +219,16 @@ impl MemorySegmentManager { if i > builtin_segments_start && i <= builtin_segments_end { continue; } - let accessed_amount = match self.memory.get_amount_of_accessed_addresses_for_segment(i) - { - Some(accessed_amount) if accessed_amount > 0 => accessed_amount, - _ => continue, - }; + let accessed_amount = + // Instead of marking the values in the zero segment until zero_segment_size as accessed we use zero_segment_size as accessed_amount + if !self.zero_segment_index.is_zero() && i == self.zero_segment_index { + self.zero_segment_size + } else { + match self.memory.get_amount_of_accessed_addresses_for_segment(i) { + Some(accessed_amount) if accessed_amount > 0 => accessed_amount, + _ => continue, + } + }; let segment_size = self .get_segment_size(i) .ok_or(MemoryError::MissingSegmentUsedSizes)?; @@ -263,6 +281,37 @@ impl MemorySegmentManager { self.public_memory_offsets .insert(segment_index, public_memory.cloned().unwrap_or_default()); } + + // TODO: remove allow + #[allow(unused)] + // Creates the zero segment if it wasn't previously created + // Fills the segment with the value 0 until size is reached + // Returns the index of the zero segment + pub(crate) fn add_zero_segment(&mut self, size: usize) -> usize { + if self.zero_segment_index.is_zero() { + self.zero_segment_index = self.add().segment_index as usize; + } + // Fil zero segment with zero values until size is reached + for _ in 0..self.zero_segment_size.saturating_sub(size) { + // As zero_segment_index is only accessible to the segment manager + // we can asume that it is always valid and index direcly into it + self.memory.data[self.zero_segment_index] + .push(Some(MemoryCell::new(Felt252::ZERO.into()))) + } + self.zero_segment_size = max(self.zero_segment_size, size); + self.zero_segment_index + } + + // TODO: remove allow + #[allow(unused)] + // Finalizes the zero segment and clears it's tracking data from the manager + pub(crate) fn finalize_zero_segment(&mut self) { + if !self.zero_segment_index.is_zero() { + self.finalize(Some(self.zero_segment_size), self.zero_segment_index, None); + self.zero_segment_index = 0; + self.zero_segment_size = 0; + } + } } impl Default for MemorySegmentManager { From 1725f0c1b7b5d550f9633b0ac45c12333f777563 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 3 Apr 2024 21:24:26 +0200 Subject: [PATCH 19/36] Feature: output builtin `add_attribute` method (#1691) Problem: some OS hints need to add attributes to the output builtin. The Python implementation defines an `add_attribute` method that is not present in `cairo-vm`. Solution: port the `add_attribute` method. --- CHANGELOG.md | 2 ++ vm/src/vm/runners/builtin_runner/output.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be9dd49bb4..c4da03eec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: output builtin add_attribute method [#1691](https://github.com/lambdaclass/cairo-vm/pull/1691) + * feat: Add zero segment [#1668](https://github.com/lambdaclass/cairo-vm/pull/1668) * feat: Bump cairo_lang to 0.13.1 in testing env [#1687](https://github.com/lambdaclass/cairo-vm/pull/1687) diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index bf9eb7c7d8..853ba038d2 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -129,6 +129,10 @@ impl OutputBuiltinRunner { } } + pub fn add_attribute(&mut self, name: String, value: Vec) { + self.attributes.insert(name, value); + } + pub fn get_additional_data(&self) -> BuiltinAdditionalData { BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { pages: self.pages.clone(), @@ -611,6 +615,18 @@ mod tests { ) } + #[test] + pub fn add_attribute() { + let mut builtin = OutputBuiltinRunner::new(true); + assert!(builtin.attributes.is_empty()); + + let name = "gps_fact_topology".to_string(); + let values = vec![0, 12, 30]; + builtin.add_attribute(name.clone(), values.clone()); + + assert_eq!(builtin.attributes, HashMap::from([(name, values)])); + } + #[test] fn get_public_memory() { let mut builtin = OutputBuiltinRunner::new(true); From 22a97dcedd58da60ceb2ae64f8c021208edc50b5 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:29:12 -0300 Subject: [PATCH 20/36] Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace` (#1658) * Remove add_additional_hash_builtin * Remove disable_trace * Add changelog entry * Remove tests --- CHANGELOG.md | 2 ++ vm/src/vm/runners/cairo_runner.rs | 42 ------------------------------- vm/src/vm/vm_core.rs | 12 --------- 3 files changed, 2 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4da03eec2..0619cbc798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* BREAKING: Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace`[#1658](https://github.com/lambdaclass/cairo-vm/pull/1658) + * feat: output builtin add_attribute method [#1691](https://github.com/lambdaclass/cairo-vm/pull/1691) * feat: Add zero segment [#1668](https://github.com/lambdaclass/cairo-vm/pull/1668) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 7becd79eea..af4048697a 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1256,22 +1256,6 @@ impl CairoRunner { Ok(()) } - //NOTE: No longer needed in 0.11 - /// Add (or replace if already present) a custom hash builtin. Returns a Relocatable - /// with the new builtin base as the segment index. - pub fn add_additional_hash_builtin(&self, vm: &mut VirtualMachine) -> Relocatable { - // Create, initialize and insert the new custom hash runner. - let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(32), true).into(); - builtin.initialize_segments(&mut vm.segments); - let segment_index = builtin.base() as isize; - vm.builtin_runners.push(builtin); - - Relocatable { - segment_index, - offset: 0, - } - } - // Iterates over the program builtins in reverse, calling BuiltinRunner::final_stack on each of them and returns the final pointer // This method is used by cairo-vm-py to replace starknet functionality pub fn get_builtins_final_stack( @@ -4799,32 +4783,6 @@ mod tests { assert_eq!(bitwise_builtin.stop_ptr, Some(5)); } - /// Test that add_additional_hash_builtin() creates an additional builtin. - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn add_additional_hash_builtin() { - let program = program!(); - let cairo_runner = cairo_runner!(program); - let mut vm = vm!(); - - let num_builtins = vm.builtin_runners.len(); - cairo_runner.add_additional_hash_builtin(&mut vm); - assert_eq!(vm.builtin_runners.len(), num_builtins + 1); - - let builtin = vm - .builtin_runners - .last() - .expect("missing last builtin runner"); - match builtin { - BuiltinRunner::Hash(builtin) => { - assert_eq!(builtin.base(), 0); - assert_eq!(builtin.ratio(), Some(32)); - assert!(builtin.included); - } - _ => unreachable!(), - } - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_from_entrypoint_custom_program_test() { diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 8bd47ea1df..9a20d8887a 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -942,9 +942,6 @@ impl VirtualMachine { Err(VirtualMachineError::NoSignatureBuiltin) } - pub fn disable_trace(&mut self) { - self.trace = None - } #[cfg(feature = "with_tracer")] pub fn relocate_segments(&self) -> Result, MemoryError> { @@ -3842,15 +3839,6 @@ mod tests { assert_eq!(builtins[1].name(), BITWISE_BUILTIN_NAME); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn disable_trace() { - let mut vm = VirtualMachine::new(true); - assert!(vm.trace.is_some()); - vm.disable_trace(); - assert!(vm.trace.is_none()); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_for_continuous_memory() { From ec00e31ec5ed412d520957edef8b3441db82d133 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Thu, 4 Apr 2024 19:34:43 +0200 Subject: [PATCH 21/36] Feature: add a method to retrieve the output builtin from the VM (#1690) * Feature: add a method to retrieve the output builtin from the VM Problem: the output builtin often needs to be manipulated directly in the Starknet bootloader and OS hints. Solution: add a `get_output_builtin() method on the `VirtualMachine` struct to retrieve a reference to the output builtin easily. * revert Cargo.lock * Fix: rename to get_output_builtin_mut() * fmt --- CHANGELOG.md | 2 ++ vm/src/vm/errors/vm_errors.rs | 2 ++ vm/src/vm/vm_core.rs | 38 ++++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0619cbc798..7aeee15383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * BREAKING: Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace`[#1658](https://github.com/lambdaclass/cairo-vm/pull/1658) * feat: output builtin add_attribute method [#1691](https://github.com/lambdaclass/cairo-vm/pull/1691) + +* feat: add a method to retrieve the output builtin from the VM [#1690](https://github.com/lambdaclass/cairo-vm/pull/1690) * feat: Add zero segment [#1668](https://github.com/lambdaclass/cairo-vm/pull/1668) diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index db8f0278e4..cbccde51b5 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -81,6 +81,8 @@ pub enum VirtualMachineError { InconsistentAutoDeduction(Box<(&'static str, MaybeRelocatable, Option)>), #[error("Invalid hint encoding at pc: {0}")] InvalidHintEncoding(Box), + #[error("Expected output builtin to be present")] + NoOutputBuiltin, #[error("Expected range_check builtin to be present")] NoRangeCheckBuiltin, #[error("Expected ecdsa builtin to be present")] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 9a20d8887a..d087952000 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -32,7 +32,7 @@ use core::num::NonZeroUsize; use num_traits::{ToPrimitive, Zero}; use super::errors::runner_errors::RunnerError; -use super::runners::builtin_runner::OUTPUT_BUILTIN_NAME; +use super::runners::builtin_runner::{OutputBuiltinRunner, OUTPUT_BUILTIN_NAME}; const MAX_TRACEBACK_ENTRIES: u32 = 20; @@ -943,6 +943,18 @@ impl VirtualMachine { Err(VirtualMachineError::NoSignatureBuiltin) } + pub fn get_output_builtin_mut( + &mut self, + ) -> Result<&mut OutputBuiltinRunner, VirtualMachineError> { + for builtin in self.get_builtin_runners_as_mut() { + if let BuiltinRunner::Output(output_builtin) = builtin { + return Ok(output_builtin); + }; + } + + Err(VirtualMachineError::NoOutputBuiltin) + } + #[cfg(feature = "with_tracer")] pub fn relocate_segments(&self) -> Result, MemoryError> { self.segments.relocate_segments() @@ -3839,6 +3851,30 @@ mod tests { assert_eq!(builtins[1].name(), BITWISE_BUILTIN_NAME); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_get_output_builtin_mut() { + let mut vm = vm!(); + + assert_matches!( + vm.get_output_builtin_mut(), + Err(VirtualMachineError::NoOutputBuiltin) + ); + + let output_builtin = OutputBuiltinRunner::new(true); + vm.builtin_runners.push(output_builtin.clone().into()); + + let vm_output_builtin = vm + .get_output_builtin_mut() + .expect("Output builtin should be returned"); + + assert_eq!(vm_output_builtin.base(), output_builtin.base()); + assert_eq!(vm_output_builtin.pages, output_builtin.pages); + assert_eq!(vm_output_builtin.attributes, output_builtin.attributes); + assert_eq!(vm_output_builtin.stop_ptr, output_builtin.stop_ptr); + assert_eq!(vm_output_builtin.included, output_builtin.included); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_for_continuous_memory() { From 69ae74534c50725b347837e5282eaaa735c3a096 Mon Sep 17 00:00:00 2001 From: orizi <104711814+orizi@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:06:55 +0300 Subject: [PATCH 22/36] feat: Reorganized builtins to be in the top of stack at the end of run. (#1686) * feat: Reorganized builtins to be in the top of stack at the end of run. * Push fixes * fmt * add changelog change --------- Co-authored-by: Federica Co-authored-by: juanbono --- CHANGELOG.md | 2 + Cargo.lock | 1 + cairo1-run/src/cairo_run.rs | 388 ++++++++++++++++++------------------ 3 files changed, 192 insertions(+), 199 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aeee15383..3b4aa330e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Reorganized builtins to be in the top of stack at the end of a run (Cairo1). + * BREAKING: Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace`[#1658](https://github.com/lambdaclass/cairo-vm/pull/1658) * feat: output builtin add_attribute method [#1691](https://github.com/lambdaclass/cairo-vm/pull/1691) diff --git a/Cargo.lock b/Cargo.lock index c3cd2a2000..d51294e05e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -973,6 +973,7 @@ dependencies = [ "clap", "itertools 0.11.0", "mimalloc", + "num-traits 0.2.18", "rstest", "thiserror", ] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index fec7569fad..f0d2c2df43 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -1,4 +1,6 @@ -use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_casm::{ + casm, casm_extend, hints::Hint, inline::CasmContext, instructions::Instruction, +}; use cairo_lang_sierra::{ extensions::{ bitwise::BitwiseType, @@ -23,7 +25,7 @@ use cairo_lang_sierra_to_casm::{ metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, }; use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_lang_utils::{casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap}; use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, math_utils::signed_felt, @@ -46,7 +48,7 @@ use cairo_vm::{ }; use itertools::{chain, Itertools}; use num_traits::{cast::ToPrimitive, Zero}; -use std::{collections::HashMap, iter::Peekable, slice::Iter}; +use std::{collections::HashMap, iter::Peekable}; use crate::{Error, FuncArg}; @@ -114,8 +116,7 @@ pub fn cairo_run_program( &type_sizes, main_func, initial_gas, - cairo_run_config.proof_mode || cairo_run_config.append_return_values, - cairo_run_config.args, + &cairo_run_config, )?; // Fetch return type data @@ -131,22 +132,12 @@ pub fn cairo_run_program( // This footer is used by lib funcs let libfunc_footer = create_code_footer(); - - // Header used to initiate the infinite loop after executing the program - // Also appends return values to output segment - let proof_mode_header = if cairo_run_config.proof_mode { - create_proof_mode_header(builtins.len() as i16, return_type_size) - } else if cairo_run_config.append_return_values { - create_append_return_values_header(builtins.len() as i16, return_type_size) - } else { - casm! {}.instructions - }; + let builtin_count: i16 = builtins.len().into_or_panic(); // This is the program we are actually running/proving // With (embedded proof mode), cairo1 header and the libfunc footer let instructions = chain!( - proof_mode_header.iter(), - entry_code.iter(), + entry_code.instructions.iter(), casm_program.instructions.iter(), libfunc_footer.iter(), ); @@ -165,12 +156,12 @@ pub fn cairo_run_program( let program = if cairo_run_config.proof_mode { Program::new_for_proof( - builtins, + builtins.clone(), data, 0, // Proof mode is on top - // jmp rel 0 is on PC == 2 - 2, + // `jmp rel 0` is the last line of the entry code. + entry_code.current_code_offset - 2, program_hints, ReferenceManager { references: Vec::new(), @@ -181,7 +172,7 @@ pub fn cairo_run_program( )? } else { Program::new( - builtins, + builtins.clone(), data, Some(0), program_hints, @@ -212,17 +203,17 @@ pub fn cairo_run_program( runner.run_for_steps(1, &mut vm, &mut hint_processor)?; } - if cairo_run_config.proof_mode || cairo_run_config.append_return_values { - // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 - // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size - vm.segments - .segment_sizes - .insert(2, return_type_size as usize); - } runner.end_run(false, false, &mut vm, &mut hint_processor)?; + let skip_output = cairo_run_config.proof_mode || cairo_run_config.append_return_values; // Fetch return values - let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + let return_values = fetch_return_values( + return_type_size, + return_type_id, + &vm, + builtin_count, + skip_output, + )?; let serialized_output = if cairo_run_config.serialize_output { Some(serialize_output( @@ -238,16 +229,28 @@ pub fn cairo_run_program( // Set stop pointers for builtins so we can obtain the air public input if cairo_run_config.finalize_builtins { - finalize_builtins( - cairo_run_config.proof_mode || cairo_run_config.append_return_values, - &main_func.signature.ret_types, - &type_sizes, - &mut vm, - )?; - - if cairo_run_config.proof_mode || cairo_run_config.append_return_values { - // As the output builtin is not used by the program we need to compute it's stop ptr manually - vm.set_output_stop_ptr_offset(return_type_size as usize); + if skip_output { + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict( + &builtins + .iter() + .enumerate() + .map(|(i, builtin)| { + ( + builtin.name(), + (vm.get_ap() - (builtins.len() - 1 - i)).unwrap(), + ) + }) + .collect(), + false, + )?; + } else { + finalize_builtins( + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + builtin_count, + )?; } // Build execution public memory @@ -338,71 +341,6 @@ fn create_code_footer() -> Vec { .instructions } -// Create proof_mode specific instructions -// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) -// wich call the firt program instruction and then initiate an infinite loop. -// And also appending the return values to the output builtin's memory segment -fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 4 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - jmp rel 0; // Infinite loop - }; - ctx.instructions -} - -// Create specific instructions to append the return values to the output segment when not running in proof_mode -// Call the firt program instruction, appends the return values to the output builtin's memory segment and then returns -fn create_append_return_values_header( - builtin_count: i16, - return_type_size: i16, -) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for the call and 1 for the return instruction - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 3 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - ret; - }; - ctx.instructions -} - /// Returns the instructions to add to the beginning of the code to successfully call the main /// function, as well as the builtins required to execute the program. fn create_entry_code( @@ -411,18 +349,19 @@ fn create_entry_code( type_sizes: &UnorderedHashMap, func: &Function, initial_gas: usize, - append_output: bool, - args: &[FuncArg], -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; + config: &Cairo1RunConfig, +) -> Result<(CasmContext, Vec), Error> { + let copy_to_output_builtin = config.proof_mode || config.append_return_values; + let signature = &func.signature; // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, append_output); - + let (builtins, builtin_offset) = + get_function_builtins(&signature.param_types, copy_to_output_builtin); + let mut ctx = casm! {}; // Load all vecs to memory. // Load all array args content to memory. let mut array_args_data = vec![]; let mut ap_offset: i16 = 0; - for arg in args { + for arg in config.args { let FuncArg::Array(values) = arg else { continue; }; @@ -442,10 +381,10 @@ fn create_entry_code( } let mut array_args_data_iter = array_args_data.iter(); let after_arrays_data_offset = ap_offset; - let mut arg_iter = args.iter().enumerate(); + let mut arg_iter = config.args.iter().enumerate(); let mut param_index = 0; let mut expected_arguments_size = 0; - if func.signature.param_types.iter().any(|ty| { + if signature.param_types.iter().any(|ty| { get_info(sierra_program_registry, ty) .map(|x| x.long_id.generic_id == SegmentArenaType::ID) .unwrap_or_default() @@ -464,19 +403,13 @@ fn create_entry_code( } ap_offset += 3; } - for ty in func.signature.param_types.iter() { + + for ty in &signature.param_types { let info = get_info(sierra_program_registry, ty) .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; let generic_ty = &info.long_id.generic_id; if let Some(offset) = builtin_offset.get(generic_ty) { - let mut offset = *offset; - if append_output { - // Everything is off by 2 due to the proof mode header - offset += 2; - } - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } + casm_extend!(ctx, [ap + 0] = [fp - *offset], ap++;); ap_offset += 1; } else if generic_ty == &SystemType::ID { casm_extend! {ctx, @@ -485,15 +418,11 @@ fn create_entry_code( } ap_offset += 1; } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } + casm_extend!(ctx, [ap + 0] = initial_gas, ap++;); ap_offset += 1; } else if generic_ty == &SegmentArenaType::ID { let offset = -ap_offset + after_arrays_data_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } + casm_extend!(ctx, [ap + 0] = [ap + offset] + 3, ap++;); ap_offset += 1; } else { let ty_size = type_sizes[ty]; @@ -529,7 +458,8 @@ fn create_entry_code( param_index += 1; }; } - let actual_args_size = args + let actual_args_size = config + .args .iter() .map(|arg| match arg { FuncArg::Single(_) => 1, @@ -544,17 +474,102 @@ fn create_entry_code( } let before_final_call = ctx.current_code_offset; - let final_call_size = 3; + + let return_type_id = signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + let builtin_count: i16 = builtins.len().into_or_panic(); + let builtin_locations: Vec = builtins + .iter() + .enumerate() + .map(|(i, name)| { + let fp_loc = i.into_or_panic::() - builtin_count - 2; + let generic = match name { + BuiltinName::range_check => RangeCheckType::ID, + BuiltinName::pedersen => PedersenType::ID, + BuiltinName::bitwise => BitwiseType::ID, + BuiltinName::ec_op => EcOpType::ID, + BuiltinName::poseidon => PoseidonType::ID, + BuiltinName::segment_arena => SegmentArenaType::ID, + BuiltinName::keccak | BuiltinName::ecdsa | BuiltinName::output => return fp_loc, + }; + signature + .ret_types + .iter() + .position(|ty| { + sierra_program_registry + .get_type(ty) + .unwrap() + .info() + .long_id + .generic_id + == generic + }) + .map(|i| (signature.ret_types.len() - i).into_or_panic()) + .unwrap_or(fp_loc) + }) + .collect(); + if copy_to_output_builtin { + assert!( + builtins.iter().contains(&BuiltinName::output), + "Output builtin is required for proof mode or append_return_values" + ); + } + + let final_call_size = + // The call. + 2 + // The copying of the return values to the output segment. + + if copy_to_output_builtin { return_type_size.into_or_panic::() + 1 } else { 0 } + // Rewriting the builtins to top of the stack. + + builtins.len() + // The return or infinite loop. + + if config.proof_mode { 2 } else { 1 }; let offset = final_call_size + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - casm_extend! {ctx, - call rel offset; - ret; + casm_extend!(ctx, call rel offset;); + + if copy_to_output_builtin { + let Some(output_builtin_idx) = builtins.iter().position(|b| b == &BuiltinName::output) + else { + panic!("Output builtin is required for proof mode or append_return_values."); + }; + let output_fp_offset: i16 = builtin_locations[output_builtin_idx]; + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + } + let mut ret_builtin_offset = return_type_size - 1; + for (builtin, location) in builtins.iter().zip(builtin_locations) { + if builtin == &BuiltinName::output && copy_to_output_builtin { + casm_extend!(ctx, [ap + 0] = [fp + location] + return_type_size, ap++;); + } else if location < 0 { + casm_extend!(ctx, [ap + 0] = [fp + location], ap++;); + } else { + casm_extend!(ctx, [ap + 0] = [ap - (ret_builtin_offset + location)], ap++;); + } + ret_builtin_offset += 1; + } + + if config.proof_mode { + casm_extend!(ctx, jmp rel 0;); + } else { + casm_extend!(ctx, ret;); } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - Ok((ctx.instructions, builtins)) + Ok((ctx, builtins)) } fn get_info<'a>( @@ -589,74 +604,35 @@ fn create_metadata( } } -/// Type representing the Output builtin. -#[derive(Default)] -pub struct OutputType {} -impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { - const ID: cairo_lang_sierra::ids::GenericTypeId = - cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); - const STORABLE: bool = true; - const DUPLICATABLE: bool = false; - const DROPPABLE: bool = false; - const ZERO_SIZED: bool = false; -} - fn get_function_builtins( - func: &Function, + params: &[cairo_lang_sierra::ids::ConcreteTypeId], append_output: bool, ) -> ( Vec, HashMap, ) { - let entry_params = &func.signature.param_types; let mut builtins = Vec::new(); let mut builtin_offset: HashMap = HashMap::new(); let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - current_offset += 1; + for (debug_name, builtin_name, sierra_id) in [ + ("Poseidon", BuiltinName::poseidon, PoseidonType::ID), + ("EcOp", BuiltinName::ec_op, EcOpType::ID), + ("Bitwise", BuiltinName::bitwise, BitwiseType::ID), + ("RangeCheck", BuiltinName::range_check, RangeCheckType::ID), + ("Pedersen", BuiltinName::pedersen, PedersenType::ID), + ] { + if params + .iter() + .any(|id| id.debug_name.as_deref() == Some(debug_name)) + { + builtins.push(builtin_name); + builtin_offset.insert(sierra_id, current_offset); + current_offset += 1; + } } // Force an output builtin so that we can write the program output into it's segment if append_output { builtins.push(BuiltinName::output); - builtin_offset.insert(OutputType::ID, current_offset); } builtins.reverse(); (builtins, builtin_offset) @@ -666,8 +642,21 @@ fn fetch_return_values( return_type_size: i16, return_type_id: &ConcreteTypeId, vm: &VirtualMachine, + builtin_count: i16, + fetch_from_output: bool, ) -> Result, Error> { - let mut return_values = vm.get_return_values(return_type_size as usize)?; + let mut return_values = if fetch_from_output { + let output_builtin_end = vm + .get_relocatable((vm.get_ap() + (-builtin_count as i32)).unwrap()) + .unwrap(); + let output_builtin_base = (output_builtin_end + (-return_type_size as i32)).unwrap(); + vm.get_continuous_range(output_builtin_base, return_type_size.into_or_panic())? + } else { + vm.get_continuous_range( + (vm.get_ap() - (return_type_size + builtin_count) as usize).unwrap(), + return_type_size as usize, + )? + }; // Check if this result is a Panic result if return_type_id .debug_name @@ -708,10 +697,10 @@ fn fetch_return_values( // Calculates builtins' final_stack setting each stop_ptr // Calling this function is a must if either air_public_input or cairo_pie are needed fn finalize_builtins( - skip_output: bool, main_ret_types: &[ConcreteTypeId], type_sizes: &UnorderedHashMap, vm: &mut VirtualMachine, + builtin_count: i16, ) -> Result<(), Error> { // Set stop pointers for builtins so we can obtain the air public input // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them @@ -721,8 +710,9 @@ fn finalize_builtins( let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); let full_ret_types_size: i16 = ret_types_sizes.sum(); - let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) - .map_err(VirtualMachineError::Math)?; + let mut stack_pointer = (vm.get_ap() + - (full_ret_types_size as usize + builtin_count as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; // Calculate the stack_ptr for each return builtin in the return values let mut builtin_name_to_stack_pointer = HashMap::new(); @@ -747,7 +737,7 @@ fn finalize_builtins( } // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, skip_output)?; + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, false)?; Ok(()) } @@ -759,7 +749,7 @@ fn serialize_output( type_sizes: &UnorderedHashMap, ) -> String { let mut output_string = String::new(); - let mut return_values_iter: Peekable> = return_values.iter().peekable(); + let mut return_values_iter = return_values.iter().peekable(); serialize_output_inner( &mut return_values_iter, &mut output_string, @@ -771,8 +761,8 @@ fn serialize_output( output_string } -fn serialize_output_inner( - return_values_iter: &mut Peekable>, +fn serialize_output_inner<'a>( + return_values_iter: &mut Peekable>, output_string: &mut String, vm: &mut VirtualMachine, return_type_id: &ConcreteTypeId, @@ -1037,7 +1027,7 @@ fn serialize_output_inner( // Serialize the value // We create a peekable array here in order to use the serialize_output_inner as the value could be a span let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = value_vec.iter().peekable(); + let mut value_iter = value_vec.iter().peekable(); serialize_output_inner( &mut value_iter, output_string, @@ -1083,7 +1073,7 @@ fn serialize_output_inner( // Serialize the value // We create a peekable array here in order to use the serialize_output_inner as the value could be a span let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = value_vec.iter().peekable(); + let mut value_iter = value_vec.iter().peekable(); serialize_output_inner( &mut value_iter, output_string, From 4777afdfaf1140368354137417abf806d1de80a7 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:55:59 -0300 Subject: [PATCH 23/36] Remove `#[allow(deprecated)]` & `#[allow(dead_code)]` (#1656) * Remove `#[allow(deprecated)]` & `#[allow(dead_code)]`` " " * Remove allow(dead_code) --- vm/src/hint_processor/builtin_hint_processor/math_utils.rs | 1 - .../hint_processor/builtin_hint_processor/secp/secp_utils.rs | 3 --- vm/src/hint_processor/builtin_hint_processor/secp/signature.rs | 2 -- vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs | 1 - vm/src/types/layout.rs | 2 -- vm/src/vm/vm_core.rs | 1 - 6 files changed, 10 deletions(-) diff --git a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs index 80c326394b..964434b444 100644 --- a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -453,7 +453,6 @@ pub fn sqrt( ))); //This is equal to mod_value > bigint!(2).pow(250) } - #[allow(deprecated)] insert_value_from_var_name( "root", Felt252::from(&isqrt(&mod_value.to_biguint())?), diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs b/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs index 7f34ea4436..4457c975b3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs @@ -106,20 +106,17 @@ mod tests { constants.insert(BASE_86.to_string(), crate::math_utils::pow2_const(86)); let array_1 = bigint3_split(&BigUint::zero()); - #[allow(deprecated)] let array_2 = bigint3_split( &bigint!(999992) .to_biguint() .expect("Couldn't convert to BigUint"), ); - #[allow(deprecated)] let array_3 = bigint3_split( &bigint_str!("7737125245533626718119526477371252455336267181195264773712524553362") .to_biguint() .expect("Couldn't convert to BigUint"), ); //TODO, Check SecpSplitutOfRange limit - #[allow(deprecated)] let array_4 = bigint3_split( &bigint_str!( "773712524553362671811952647737125245533626718119526477371252455336267181195264" diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs b/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs index aee4450cc9..57d72b5bba 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs @@ -109,7 +109,6 @@ pub fn get_point_from_x( constants: &HashMap, ) -> Result<(), HintError> { exec_scopes.insert_value("SECP_P", SECP_P.clone()); - #[allow(deprecated)] let beta = constants .get(BETA) .ok_or_else(|| HintError::MissingConstant(Box::new(BETA)))? @@ -122,7 +121,6 @@ pub fn get_point_from_x( // Divide by 4 let mut y = y_cube_int.modpow(&(&*SECP_P + 1_u32).shr(2_u32), &SECP_P); - #[allow(deprecated)] let v = get_integer_from_var_name("v", vm, ids_data, ap_tracking)?.to_bigint(); if v.is_even() != y.is_even() { y = &*SECP_P - y; diff --git a/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs b/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs index fe04c1f71a..354e332f03 100644 --- a/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs +++ b/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs @@ -13,7 +13,6 @@ pub struct DictTrackerExecScope { /// The data of the dictionary. data: HashMap, /// The index of the dictionary in the dict_infos segment. - #[allow(dead_code)] idx: usize, } diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index ceea2496f9..f9a2f8376f 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -63,7 +63,6 @@ impl CairoLayout { } } - #[allow(dead_code)] pub(crate) fn recursive_instance() -> CairoLayout { CairoLayout { _name: String::from("recursive"), @@ -120,7 +119,6 @@ impl CairoLayout { } } - #[allow(dead_code)] pub(crate) fn all_cairo_instance() -> CairoLayout { CairoLayout { _name: String::from("all_cairo"), diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index d087952000..a4098464e5 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1027,7 +1027,6 @@ impl VirtualMachine { let segment_used_sizes = self.segments.compute_effective_sizes(); let segment_index = builtin.base(); - #[allow(deprecated)] for i in 0..segment_used_sizes[segment_index] { let formatted_value = match self .segments From c692b7594a74ee3245cf7affc7c8cfc296b0398c Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Fri, 5 Apr 2024 23:58:30 +0200 Subject: [PATCH 24/36] Feature: compute program hash chain (#1647) * Feature: compute program hash chain Problem: computing the hash of a program is useful to verify its integrity. This hash is also used for different purposes, like when loading a program with the bootloader. Solution: add a `program_hash` module and the `compute_program_hash_chain` function that computes the hash of a stripped program, making it usable with `Program` and `CairoPie` objects. * compile module in no-std --- CHANGELOG.md | 2 + vm/src/lib.rs | 1 + vm/src/program_hash.rs | 204 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 vm/src/program_hash.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4aa330e8..58e0254b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ * Adds the flag `append_return_values` to both the CLI and `Cairo1RunConfig` struct. * Enabling flag will add the output builtin and the necessary instructions to append the return values to the output builtin's memory segment. +* feat: Compute program hash chain [#1647](https://github.com/lambdaclass/cairo-vm/pull/1647) + * feat: Add cairo1-run output pretty-printing for felts, arrays/spans and dicts [#1630](https://github.com/lambdaclass/cairo-vm/pull/1630) * feat: output builtin features for bootloader support [#1580](https://github.com/lambdaclass/cairo-vm/pull/1580) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 36509273b3..bbd334ae81 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -57,6 +57,7 @@ pub mod air_public_input; pub mod cairo_run; pub mod hint_processor; pub mod math_utils; +pub mod program_hash; pub mod serde; pub mod types; pub mod utils; diff --git a/vm/src/program_hash.rs b/vm/src/program_hash.rs new file mode 100644 index 0000000000..3ff1fa798c --- /dev/null +++ b/vm/src/program_hash.rs @@ -0,0 +1,204 @@ +use starknet_crypto::{pedersen_hash, FieldElement}; + +use crate::Felt252; + +use crate::serde::deserialize_program::BuiltinName; +use crate::stdlib::vec::Vec; +use crate::types::relocatable::MaybeRelocatable; +use crate::vm::runners::cairo_pie::StrippedProgram; + +type HashFunction = fn(&FieldElement, &FieldElement) -> FieldElement; + +#[derive(thiserror_no_std::Error, Debug)] +pub enum HashChainError { + #[error("Data array must contain at least one element.")] + EmptyData, +} + +#[derive(thiserror_no_std::Error, Debug)] +pub enum ProgramHashError { + #[error(transparent)] + HashChain(#[from] HashChainError), + + #[error( + "Invalid program builtin: builtin name too long to be converted to field element: {0}" + )] + InvalidProgramBuiltin(&'static str), + + #[error("Invalid program data: data contains relocatable(s)")] + InvalidProgramData, + + /// Conversion from Felt252 to FieldElement failed. This is unlikely to happen + /// unless the implementation of Felt252 changes and this code is not updated properly. + #[error("Conversion from Felt252 to FieldElement failed")] + Felt252ToFieldElementConversionFailed, +} + +/// Computes a hash chain over the data, in the following order: +/// h(data[0], h(data[1], h(..., h(data[n-2], data[n-1])))). +/// +/// Reimplements this Python function: +/// def compute_hash_chain(data, hash_func=pedersen_hash): +/// assert len(data) >= 1, f"len(data) for hash chain computation must be >= 1; got: {len(data)}." +/// return functools.reduce(lambda x, y: hash_func(y, x), data[::-1]) +fn compute_hash_chain<'a, I>( + data: I, + hash_func: HashFunction, +) -> Result +where + I: Iterator + DoubleEndedIterator, +{ + match data.copied().rev().reduce(|x, y| hash_func(&y, &x)) { + Some(result) => Ok(result), + None => Err(HashChainError::EmptyData), + } +} + +/// Creates an instance of `FieldElement` from a builtin name. +/// +/// Converts the builtin name to bytes then attempts to create a field element from +/// these bytes. This function will fail if the builtin name is over 31 characters. +fn builtin_to_field_element(builtin: &BuiltinName) -> Result { + // The Python implementation uses the builtin name without suffix + let builtin_name = builtin + .name() + .strip_suffix("_builtin") + .unwrap_or(builtin.name()); + + FieldElement::from_byte_slice_be(builtin_name.as_bytes()) + .map_err(|_| ProgramHashError::InvalidProgramBuiltin(builtin.name())) +} + +/// The `value: FieldElement` is `pub(crate)` and there is no accessor. +/// This function converts a `Felt252` to a `FieldElement` using a safe, albeit inefficient, +/// method. +fn felt_to_field_element(felt: &Felt252) -> Result { + let bytes = felt.to_bytes_be(); + FieldElement::from_bytes_be(&bytes) + .map_err(|_e| ProgramHashError::Felt252ToFieldElementConversionFailed) +} + +/// Converts a `MaybeRelocatable` into a `FieldElement` value. +/// +/// Returns `InvalidProgramData` if `maybe_relocatable` is not an integer +fn maybe_relocatable_to_field_element( + maybe_relocatable: &MaybeRelocatable, +) -> Result { + let felt = maybe_relocatable + .get_int_ref() + .ok_or(ProgramHashError::InvalidProgramData)?; + felt_to_field_element(felt) +} + +/// Computes the Pedersen hash of a program. +/// +/// Reimplements this Python function: +/// 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. +/// program_header = [bootloader_version, program.main, len(program.builtins)] + builtin_list +/// 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, +) -> Result { + let program_main = program.main; + let program_main = FieldElement::from(program_main); + + // Convert builtin names to field elements + let builtin_list: Result, _> = program + .builtins + .iter() + .map(builtin_to_field_element) + .collect(); + let builtin_list = builtin_list?; + + let program_header = vec![ + FieldElement::from(bootloader_version), + program_main, + FieldElement::from(program.builtins.len()), + ]; + + let program_data: Result, _> = program + .data + .iter() + .map(maybe_relocatable_to_field_element) + .collect(); + let program_data = program_data?; + + let data_chain_len = program_header.len() + builtin_list.len() + program_data.len(); + let data_chain_len_vec = vec![FieldElement::from(data_chain_len)]; + + // Prepare a chain of iterators to feed to the hash function + let data_chain = [ + &data_chain_len_vec, + &program_header, + &builtin_list, + &program_data, + ]; + + let hash = compute_hash_chain(data_chain.iter().flat_map(|&v| v.iter()), pedersen_hash)?; + Ok(hash) +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "std")] + use {crate::types::program::Program, rstest::rstest, std::path::PathBuf}; + + use starknet_crypto::pedersen_hash; + + use super::*; + + #[test] + fn test_compute_hash_chain() { + let data: Vec = vec![ + FieldElement::from(1u64), + FieldElement::from(2u64), + FieldElement::from(3u64), + ]; + let expected_hash = pedersen_hash( + &FieldElement::from(1u64), + &pedersen_hash(&FieldElement::from(2u64), &FieldElement::from(3u64)), + ); + let computed_hash = compute_hash_chain(data.iter(), pedersen_hash) + .expect("Hash computation failed unexpectedly"); + + assert_eq!(computed_hash, expected_hash); + } + + #[cfg(feature = "std")] + #[rstest] + // Expected hashes generated with `cairo-hash-program` + #[case::fibonacci( + "../cairo_programs/fibonacci.json", + "0x43b17e9592f33142246af4c06cd2b574b460dd1f718d76b51341175a62b220f" + )] + #[case::field_arithmetic( + "../cairo_programs/field_arithmetic.json", + "0x1031772ca86e618b058101af9c9a3277bac90712b750bcea1cc69d6c7cad8a7" + )] + #[case::keccak_copy_inputs( + "../cairo_programs/keccak_copy_inputs.json", + "0x49484fdc8e7a85061f9f21b7e21fe276d8a88c8e96681101a2518809e686c6c" + )] + fn test_compute_program_hash_chain( + #[case] program_path: PathBuf, + #[case] expected_program_hash: String, + ) { + let program = + Program::from_file(program_path.as_path(), Some("main")) + .expect("Could not load program. Did you compile the sample programs? Run `make test` in the root directory."); + let stripped_program = program.get_stripped_program().unwrap(); + let bootloader_version = 0; + + let program_hash = compute_program_hash_chain(&stripped_program, bootloader_version) + .expect("Failed to compute program hash."); + + let program_hash_hex = format!("{:#x}", program_hash); + + assert_eq!(program_hash_hex, expected_program_hash); + } +} From 404407dec64a71e8f373b1ca5d6f66123e514c59 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:10:12 -0300 Subject: [PATCH 25/36] Handle off2 immediate case in `get_integer_from_reference` (#1701) * Handle off2 immediate case in get_integer_from_reference * Add changelog entry --- CHANGELOG.md | 3 + .../builtin_hint_processor/blake2s_utils.rs | 5 +- .../builtin_hint_processor/ec_utils.rs | 6 +- .../find_element_hint.rs | 20 +++--- .../builtin_hint_processor/hint_utils.rs | 14 ++-- .../builtin_hint_processor/keccak_utils.rs | 9 ++- .../builtin_hint_processor/math_utils.rs | 64 ++++++------------- .../memcpy_hint_utils.rs | 3 +- .../builtin_hint_processor/memset_utils.rs | 3 +- .../builtin_hint_processor/set.rs | 2 +- .../builtin_hint_processor/signature.rs | 6 +- .../squash_dict_utils.rs | 7 +- .../builtin_hint_processor/usort.rs | 4 +- vm/src/hint_processor/hint_processor_utils.rs | 59 +++++++++++------ 14 files changed, 99 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e0254b0c..df54baf11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ #### Upcoming Changes +* bugfix(BREAKING): Handle off2 immediate case in `get_integer_from_reference`[#1701](https://github.com/lambdaclass/cairo-vm/pull/1701) + * `get_integer_from_reference` & `get_integer_from_var_name` output changed from `Result, HintError>` to `Result` + * feat: Reorganized builtins to be in the top of stack at the end of a run (Cairo1). * BREAKING: Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace`[#1658](https://github.com/lambdaclass/cairo-vm/pull/1658) 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 5f5fe2ef83..b095bcc69d 100644 --- a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs @@ -271,9 +271,8 @@ pub fn example_blake2s_compress( let blake2s_start = get_ptr_from_var_name("blake2s_start", vm, ids_data, ap_tracking)?; let output = get_ptr_from_var_name("output", vm, ids_data, ap_tracking)?; let n_bytes = get_integer_from_var_name("n_bytes", vm, ids_data, ap_tracking).map(|x| { - x.to_u32().ok_or_else(|| { - HintError::Math(MathError::Felt252ToU32Conversion(Box::new(x.into_owned()))) - }) + x.to_u32() + .ok_or_else(|| HintError::Math(MathError::Felt252ToU32Conversion(Box::new(x)))) })??; let message = get_fixed_size_u32_array::<16>(&vm.get_integer_range(blake2s_start, 16)?)?; diff --git a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs index e3f29091e8..9530141f6d 100644 --- a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs @@ -62,7 +62,7 @@ pub fn random_ec_point_hint( ) -> Result<(), HintError> { let p = EcPoint::from_var_name("p", vm, ids_data, ap_tracking)?; let q = EcPoint::from_var_name("q", vm, ids_data, ap_tracking)?; - let m = get_integer_from_var_name("m", vm, ids_data, ap_tracking)?; + let m = Cow::Owned(get_integer_from_var_name("m", vm, ids_data, ap_tracking)?); let bytes: Vec = [p.x, p.y, m, q.x, q.y] .iter() .flat_map(|x| x.to_bytes_be()) @@ -109,7 +109,7 @@ pub fn chained_ec_op_random_ec_point_hint( ) -> Result<(), HintError> { let n_elms = get_integer_from_var_name("len", vm, ids_data, ap_tracking)?; if n_elms.is_zero() || n_elms.to_usize().is_none() { - return Err(HintError::InvalidLenValue(Box::new(n_elms.into_owned()))); + return Err(HintError::InvalidLenValue(Box::new(n_elms))); } let n_elms = n_elms.to_usize().unwrap(); let p = EcPoint::from_var_name("p", vm, ids_data, ap_tracking)?; @@ -141,7 +141,7 @@ pub fn recover_y_hint( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let p_x = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?.into_owned(); + let p_x = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?; let p_addr = get_relocatable_from_var_name("p", vm, ids_data, ap_tracking)?; vm.insert_value(p_addr, p_x)?; let p_y = Felt252::from( diff --git a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs index ea00247250..b1be97bb82 100644 --- a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs +++ b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs @@ -30,9 +30,7 @@ pub fn find_element( .to_usize() .ok_or_else(|| HintError::ValueOutOfRange(Box::new(*elm_size_bigint.as_ref())))?; if elm_size == 0 { - return Err(HintError::ValueOutOfRange(Box::new( - elm_size_bigint.into_owned(), - ))); + return Err(HintError::ValueOutOfRange(Box::new(elm_size_bigint))); } if let Some(find_element_index_value) = find_element_index { @@ -44,7 +42,7 @@ pub fn find_element( if found_key.as_ref() != key.as_ref() { return Err(HintError::InvalidIndex(Box::new(( find_element_index_value, - key.into_owned(), + key, found_key.into_owned(), )))); } @@ -56,13 +54,13 @@ pub fn find_element( if n_elms.as_ref() > find_element_max_size { return Err(HintError::FindElemMaxSize(Box::new(( *find_element_max_size, - n_elms.into_owned(), + n_elms, )))); } } let n_elms_iter: u32 = n_elms .to_u32() - .ok_or_else(|| MathError::Felt252ToI32Conversion(Box::new(n_elms.into_owned())))?; + .ok_or_else(|| MathError::Felt252ToI32Conversion(Box::new(n_elms)))?; for i in 0..n_elms_iter { let iter_key = vm @@ -80,9 +78,7 @@ pub fn find_element( } } - Err(HintError::NoValueForKeyFindElement(Box::new( - key.into_owned(), - ))) + Err(HintError::NoValueForKeyFindElement(Box::new(key))) } } @@ -93,10 +89,10 @@ pub fn search_sorted_lower( ap_tracking: &ApTracking, ) -> Result<(), HintError> { let find_element_max_size = exec_scopes.get::("find_element_max_size"); - let n_elms = *get_integer_from_var_name("n_elms", vm, ids_data, ap_tracking)?; + let n_elms = get_integer_from_var_name("n_elms", vm, ids_data, ap_tracking)?; let rel_array_ptr = get_relocatable_from_var_name("array_ptr", vm, ids_data, ap_tracking)?; - let elm_size = *get_integer_from_var_name("elm_size", vm, ids_data, ap_tracking)?; - let key = *get_integer_from_var_name("key", vm, ids_data, ap_tracking)?; + let elm_size = get_integer_from_var_name("elm_size", vm, ids_data, ap_tracking)?; + let key = get_integer_from_var_name("key", vm, ids_data, ap_tracking)?; if elm_size == Felt252::ZERO { return Err(HintError::ValueOutOfRange(Box::new(elm_size))); diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_utils.rs b/vm/src/hint_processor/builtin_hint_processor/hint_utils.rs index 3a1808b5a0..07eef72a88 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_utils.rs @@ -1,4 +1,4 @@ -use crate::stdlib::{borrow::Cow, boxed::Box, collections::HashMap, prelude::*}; +use crate::stdlib::{boxed::Box, collections::HashMap, prelude::*}; use crate::Felt252; @@ -83,12 +83,12 @@ pub fn get_relocatable_from_var_name( //Gets the value of a variable name. //If the value is an MaybeRelocatable::Int(Bigint) return &Bigint //else raises Err -pub fn get_integer_from_var_name<'a>( - var_name: &'a str, - vm: &'a VirtualMachine, - ids_data: &'a HashMap, +pub fn get_integer_from_var_name( + var_name: &str, + vm: &VirtualMachine, + ids_data: &HashMap, ap_tracking: &ApTracking, -) -> Result, HintError> { +) -> Result { let reference = get_reference_from_var_name(var_name, ids_data)?; match get_integer_from_reference(vm, reference, ap_tracking) { // Map internal errors into more descriptive variants @@ -260,7 +260,7 @@ mod tests { assert_matches!( get_integer_from_var_name("value", &vm, &ids_data, &ApTracking::new()), - Ok(Cow::Borrowed(x)) if x == &Felt252::from(1) + Ok(x) if x == Felt252::from(1) ); } diff --git a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs index 8a22530f9b..86eec72eee 100644 --- a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs @@ -56,7 +56,7 @@ pub fn unsafe_keccak( if let Ok(keccak_max_size) = exec_scopes.get::("__keccak_max_size") { if length.as_ref() > &keccak_max_size { return Err(HintError::KeccakMaxSize(Box::new(( - length.into_owned(), + length, keccak_max_size, )))); } @@ -71,7 +71,7 @@ pub fn unsafe_keccak( // transform to u64 to make ranges cleaner in the for loop below let u64_length = length .to_u64() - .ok_or_else(|| HintError::InvalidKeccakInputLength(Box::new(length.into_owned())))?; + .ok_or_else(|| HintError::InvalidKeccakInputLength(Box::new(length)))?; const ZEROES: [u8; 32] = [0u8; 32]; let mut keccak_input = Vec::new(); @@ -243,9 +243,8 @@ pub fn split_n_bytes( ) -> Result<(), HintError> { let n_bytes = get_integer_from_var_name("n_bytes", vm, ids_data, ap_tracking).and_then(|x| { - x.to_u64().ok_or_else(|| { - HintError::Math(MathError::Felt252ToU64Conversion(Box::new(x.into_owned()))) - }) + x.to_u64() + .ok_or_else(|| HintError::Math(MathError::Felt252ToU64Conversion(Box::new(x)))) })?; let bytes_in_word = constants .get(BYTES_IN_WORD) diff --git a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs index 964434b444..fdfffb4485 100644 --- a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -161,11 +161,8 @@ pub fn assert_le_felt_v_0_6( let a = &get_integer_from_var_name("a", vm, ids_data, ap_tracking)?; let b = &get_integer_from_var_name("b", vm, ids_data, ap_tracking)?; - if a.as_ref() > b.as_ref() { - return Err(HintError::NonLeFelt252(Box::new(( - a.clone().into_owned(), - b.clone().into_owned(), - )))); + if a > b { + return Err(HintError::NonLeFelt252(Box::new((*a, *b)))); } Ok(()) } @@ -178,15 +175,11 @@ pub fn assert_le_felt_v_0_8( let a = &get_integer_from_var_name("a", vm, ids_data, ap_tracking)?; let b = &get_integer_from_var_name("b", vm, ids_data, ap_tracking)?; - if a.as_ref() > b.as_ref() { - return Err(HintError::NonLeFelt252(Box::new(( - a.clone().into_owned(), - b.clone().into_owned(), - )))); + if a > b { + return Err(HintError::NonLeFelt252(Box::new((*a, *b)))); } let bound = vm.get_range_check_builtin()?._bound.unwrap_or_default(); - let small_inputs = - Felt252::from((a.as_ref() < &bound && b.as_ref() - a.as_ref() < bound) as u8); + let small_inputs = Felt252::from((a < &bound && b - a < bound) as u8); insert_value_from_var_name("small_inputs", small_inputs, vm, ids_data, ap_tracking) } @@ -300,9 +293,7 @@ pub fn assert_nn( // assert 0 <= ids.a % PRIME < range_check_builtin.bound // as prime > 0, a % prime will always be > 0 match &range_check_builtin._bound { - Some(bound) if a.as_ref() >= bound => { - Err(HintError::AssertNNValueOutOfRange(Box::new(a.into_owned()))) - } + Some(bound) if a.as_ref() >= bound => Err(HintError::AssertNNValueOutOfRange(Box::new(a))), _ => Ok(()), } } @@ -321,7 +312,7 @@ pub fn assert_not_zero( let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; if value.is_zero() { return Err(HintError::AssertNotZero(Box::new(( - value.into_owned(), + value, crate::utils::PRIME_STR.to_string(), )))); }; @@ -375,7 +366,7 @@ pub fn is_positive( ap_tracking: &ApTracking, ) -> Result<(), HintError> { let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; - let value_as_int = signed_felt(*value); + let value_as_int = signed_felt(value); let range_check_builtin = vm.get_range_check_builtin()?; // Avoid using abs so we don't allocate a new BigInt @@ -383,9 +374,7 @@ pub fn is_positive( //Main logic (assert a is positive) match &range_check_builtin._bound { Some(bound) if abs_value > bound.to_biguint() => { - return Err(HintError::ValueOutsideValidRange(Box::new( - value.into_owned(), - ))) + return Err(HintError::ValueOutsideValidRange(Box::new(value))) } _ => {} }; @@ -447,10 +436,8 @@ pub fn sqrt( ) -> Result<(), HintError> { let mod_value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; //This is equal to mod_value > Felt252::from(2).pow(250) - if *mod_value > pow2_const(250) { - return Err(HintError::ValueOutside250BitRange(Box::new( - mod_value.into_owned(), - ))); + if mod_value > pow2_const(250) { + return Err(HintError::ValueOutside250BitRange(Box::new(mod_value))); //This is equal to mod_value > bigint!(2).pow(250) } insert_value_from_var_name( @@ -475,15 +462,12 @@ pub fn signed_div_rem( let builtin_bound = &builtin._bound.unwrap_or(Felt252::MAX); if div.is_zero() || div.as_ref() > &div_prime_by_bound(*builtin_bound)? { - return Err(HintError::OutOfValidRange(Box::new(( - div.into_owned(), - *builtin_bound, - )))); + return Err(HintError::OutOfValidRange(Box::new((div, *builtin_bound)))); } let builtin_bound_div_2 = builtin_bound.field_div(&Felt252::TWO.try_into().unwrap()); - if *bound > builtin_bound_div_2 { + if bound > builtin_bound_div_2 { return Err(HintError::OutOfValidRange(Box::new(( - bound.into_owned(), + bound, builtin_bound_div_2, )))); } @@ -496,7 +480,7 @@ pub fn signed_div_rem( if int_bound.abs() < q.abs() { return Err(HintError::OutOfValidRange(Box::new(( Felt252::from(&q), - bound.into_owned(), + bound, )))); } @@ -534,21 +518,18 @@ pub fn unsigned_div_rem( Some(builtin_bound) if div.is_zero() || div.as_ref() > &div_prime_by_bound(*builtin_bound)? => { - return Err(HintError::OutOfValidRange(Box::new(( - div.into_owned(), - *builtin_bound, - )))); + return Err(HintError::OutOfValidRange(Box::new((div, *builtin_bound)))); } None if div.is_zero() => { return Err(HintError::OutOfValidRange(Box::new(( - div.into_owned(), + div, Felt252::ZERO - Felt252::ONE, )))); } _ => {} } - let (q, r) = value.div_rem(&(*div).try_into().map_err(|_| MathError::DividedByZero)?); + let (q, r) = value.div_rem(&(div).try_into().map_err(|_| MathError::DividedByZero)?); insert_value_from_var_name("r", r, vm, ids_data, ap_tracking)?; insert_value_from_var_name("q", q, vm, ids_data, ap_tracking) } @@ -574,7 +555,7 @@ pub fn assert_250_bit( let shift = constants .get(SHIFT) .map_or_else(|| get_constant_from_var_name("SHIFT", constants), Ok)?; - let value = Felt252::from(&signed_felt(*get_integer_from_var_name( + let value = Felt252::from(&signed_felt(get_integer_from_var_name( "value", vm, ids_data, @@ -675,10 +656,7 @@ pub fn assert_lt_felt( // assert (ids.a % PRIME) < (ids.b % PRIME), \ // f'a = {ids.a % PRIME} is not less than b = {ids.b % PRIME}.' if a >= b { - return Err(HintError::AssertLtFelt252(Box::new(( - a.into_owned(), - b.into_owned(), - )))); + return Err(HintError::AssertLtFelt252(Box::new((a, b)))); }; Ok(()) } @@ -688,7 +666,7 @@ pub fn is_quad_residue( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let x = *get_integer_from_var_name("x", vm, ids_data, ap_tracking)?; + let x = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?; if x.is_zero() || x == Felt252::ONE { insert_value_from_var_name("y", *x.as_ref(), vm, ids_data, ap_tracking) diff --git a/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs b/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs index 4709545bbc..2e9b4c55e4 100644 --- a/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs @@ -36,8 +36,7 @@ pub fn memcpy_enter_scope( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let len: Box = - Box::new(get_integer_from_var_name("len", vm, ids_data, ap_tracking)?.into_owned()); + let len: Box = Box::new(get_integer_from_var_name("len", vm, ids_data, ap_tracking)?); exec_scopes.enter_scope(HashMap::from([(String::from("n"), len)])); Ok(()) } diff --git a/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs b/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs index 4ad693968c..066884f920 100644 --- a/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs @@ -21,8 +21,7 @@ pub fn memset_enter_scope( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let n: Box = - Box::new(get_integer_from_var_name("n", vm, ids_data, ap_tracking)?.into_owned()); + let n: Box = Box::new(get_integer_from_var_name("n", vm, ids_data, ap_tracking)?); exec_scopes.enter_scope(HashMap::from([(String::from("n"), n)])); Ok(()) } diff --git a/vm/src/hint_processor/builtin_hint_processor/set.rs b/vm/src/hint_processor/builtin_hint_processor/set.rs index 9d93590c87..28576e4a9f 100644 --- a/vm/src/hint_processor/builtin_hint_processor/set.rs +++ b/vm/src/hint_processor/builtin_hint_processor/set.rs @@ -23,7 +23,7 @@ pub fn set_add( let elm_size = get_integer_from_var_name("elm_size", vm, ids_data, ap_tracking).and_then(|x| { x.to_usize() - .ok_or_else(|| MathError::Felt252ToUsizeConversion(Box::new(x.into_owned())).into()) + .ok_or_else(|| MathError::Felt252ToUsizeConversion(Box::new(x)).into()) })?; let elm_ptr = get_ptr_from_var_name("elm_ptr", vm, ids_data, ap_tracking)?; let set_end_ptr = get_ptr_from_var_name("set_end_ptr", vm, ids_data, ap_tracking)?; diff --git a/vm/src/hint_processor/builtin_hint_processor/signature.rs b/vm/src/hint_processor/builtin_hint_processor/signature.rs index 97b03c764d..23ffa70086 100644 --- a/vm/src/hint_processor/builtin_hint_processor/signature.rs +++ b/vm/src/hint_processor/builtin_hint_processor/signature.rs @@ -20,10 +20,8 @@ pub fn verify_ecdsa_signature( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let signature_r = - get_integer_from_var_name("signature_r", vm, ids_data, ap_tracking)?.into_owned(); - let signature_s = - get_integer_from_var_name("signature_s", vm, ids_data, ap_tracking)?.into_owned(); + let signature_r = get_integer_from_var_name("signature_r", vm, ids_data, ap_tracking)?; + let signature_s = get_integer_from_var_name("signature_s", vm, ids_data, ap_tracking)?; let ecdsa_ptr = get_ptr_from_var_name("ecdsa_ptr", vm, ids_data, ap_tracking)?; let ecdsa_builtin = &mut vm.get_signature_builtin()?; if ecdsa_ptr.segment_index != ecdsa_builtin.base() as isize { diff --git a/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs b/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs index 32ea9cb253..06d385c1a6 100644 --- a/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs @@ -176,7 +176,7 @@ pub fn squash_dict_inner_used_accesses_assert( if n_used_accesses.as_ref() != &Felt252::from(access_indices_at_key.len()) { return Err(HintError::NumUsedAccessesAssertFail(Box::new(( - n_used_accesses.into_owned(), + n_used_accesses, access_indices_at_key.len(), key, )))); @@ -260,14 +260,13 @@ pub fn squash_dict( if let Ok(max_size) = squash_dict_max_size { if n_accesses.as_ref() > &max_size { return Err(HintError::SquashDictMaxSizeExceeded(Box::new(( - max_size, - n_accesses.into_owned(), + max_size, n_accesses, )))); }; }; let n_accesses_usize = n_accesses .to_usize() - .ok_or_else(|| HintError::NAccessesTooBig(Box::new(n_accesses.into_owned())))?; + .ok_or_else(|| HintError::NAccessesTooBig(Box::new(n_accesses)))?; //A map from key to the list of indices accessing it. let mut access_indices = HashMap::>::new(); for i in 0..n_accesses_usize { diff --git a/vm/src/hint_processor/builtin_hint_processor/usort.rs b/vm/src/hint_processor/builtin_hint_processor/usort.rs index f553866fe6..7c22f94fc7 100644 --- a/vm/src/hint_processor/builtin_hint_processor/usort.rs +++ b/vm/src/hint_processor/builtin_hint_processor/usort.rs @@ -43,7 +43,7 @@ pub fn usort_body( if input_len_u64 > usort_max_size { return Err(HintError::UsortOutOfRange(Box::new(( usort_max_size, - input_len.into_owned(), + input_len, )))); } } @@ -98,7 +98,7 @@ pub fn verify_usort( ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?.clone(); + let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; let mut positions = exec_scopes .get_mut_dict_ref::>("positions_dict")? .remove(value.as_ref()) diff --git a/vm/src/hint_processor/hint_processor_utils.rs b/vm/src/hint_processor/hint_processor_utils.rs index 69944d8669..31c60a5ce0 100644 --- a/vm/src/hint_processor/hint_processor_utils.rs +++ b/vm/src/hint_processor/hint_processor_utils.rs @@ -1,4 +1,4 @@ -use crate::stdlib::{borrow::Cow, boxed::Box}; +use crate::stdlib::boxed::Box; use crate::{ serde::deserialize_program::{ApTracking, OffsetValue}, @@ -29,22 +29,26 @@ pub fn insert_value_from_reference( ///Returns the Integer value stored in the given ids variable /// Returns an internal error, users should map it into a more informative type -pub fn get_integer_from_reference<'a>( - vm: &'a VirtualMachine, - hint_reference: &'a HintReference, +pub fn get_integer_from_reference( + vm: &VirtualMachine, + hint_reference: &HintReference, ap_tracking: &ApTracking, -) -> Result, HintError> { - // if the reference register is none, this means it is an immediate value and we - // should return that value. - - if let (OffsetValue::Immediate(int_1), _) = (&hint_reference.offset1, &hint_reference.offset2) { - return Ok(Cow::Borrowed(int_1)); +) -> Result { + // Compute the initial value + let mut val = if let OffsetValue::Immediate(f) = &hint_reference.offset1 { + *f + } else { + let var_addr = compute_addr_from_reference(hint_reference, vm, ap_tracking) + .ok_or(HintError::UnknownIdentifierInternal)?; + vm.get_integer(var_addr) + .map_err(|_| HintError::WrongIdentifierTypeInternal(Box::new(var_addr)))? + .into_owned() + }; + // If offset2 is an immediate, we need to add it's value to the initial value + if let OffsetValue::Immediate(f) = &hint_reference.offset2 { + val += f; } - - let var_addr = compute_addr_from_reference(hint_reference, vm, ap_tracking) - .ok_or(HintError::UnknownIdentifierInternal)?; - vm.get_integer(var_addr) - .map_err(|_| HintError::WrongIdentifierTypeInternal(Box::new(var_addr))) + Ok(val) } ///Returns the Relocatable value stored in the given ids variable @@ -117,7 +121,7 @@ pub fn compute_addr_from_reference( Some((offset1 + value.get_int_ref()?.to_usize()?).ok()?) } OffsetValue::Value(value) => Some((offset1 + *value).ok()?), - _ => None, + _ => Some(offset1), } } @@ -200,12 +204,31 @@ mod tests { assert_eq!( get_integer_from_reference(&vm, &hint_ref, &ApTracking::new()) - .expect("Unexpected get integer fail") - .into_owned(), + .expect("Unexpected get integer fail"), Felt252::from(2) ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_integer_from_reference_with_immediate_off2() { + let mut vm = vm!(); + vm.segments = segments![((1, 0), 1)]; + let hint_ref = HintReference { + offset1: OffsetValue::Reference(Register::FP, 0, false), + offset2: OffsetValue::Immediate(Felt252::TWO), + dereference: false, + ap_tracking_data: Default::default(), + cairo_type: None, + }; + + assert_eq!( + get_integer_from_reference(&vm, &hint_ref, &ApTracking::new()) + .expect("Unexpected get integer fail"), + Felt252::THREE + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_offset_value_reference_valid() { From 918d01ceb11bf4dd502fb65ef14d883b2b13ac6e Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Tue, 9 Apr 2024 12:00:12 -0300 Subject: [PATCH 26/36] ci: optimize build checks (#1384) * ci: optimize build checks Split build checks to parallel builds by: - Sharding the workspace-level feature check; - Launching a job per crate for the crate-level feature check. * ci: test all combinations * Update cairo1-run/Cargo.toml * Update rust version for workflows added by this PR * Download proof_porgrams symlinks * Fix yaml * Fix yaml * Add Checkout step * Update rust toolchain to match other workflows * Remove duplicate feature --------- Co-authored-by: Pedro Fontana Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> Co-authored-by: Federica Co-authored-by: Juan Bono --- .github/workflows/rust.yml | 88 ++++++++++++++++++++++++++++++++---- bench/criterion_benchmark.rs | 2 - bench/iai_benchmark.rs | 2 - cairo-vm-cli/Cargo.toml | 6 ++- cairo-vm-tracer/Cargo.toml | 1 - cairo1-run/Cargo.toml | 2 +- vm/Cargo.toml | 6 +-- vm/src/lib.rs | 1 - vm/src/vm/vm_core.rs | 2 +- 9 files changed, 88 insertions(+), 22 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8af32e5197..07fbdd0542 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -194,6 +194,10 @@ jobs: smoke: needs: merge-caches name: Make sure all builds work + strategy: + fail-fast: false + matrix: + crate: ["vm", "cairo-vm-cli", "cairo1-run"] runs-on: ubuntu-22.04 steps: - name: Install Rust @@ -210,6 +214,7 @@ jobs: uses: taiki-e/install-action@v2 with: tool: cargo-all-features + - name: Checkout uses: actions/checkout@v3 @@ -226,19 +231,86 @@ jobs: fail-on-cache-miss: true # NOTE: we do this separately because --workspace operates in weird ways - - name: Check all features (vm) + - name: Check all features (${{ matrix.crate }}) run: | - cd vm + cd ${{ matrix.crate }} cargo check-all-features + cargo check-all-features --workspace --all-targets + + smoke-workspace: + needs: merge-caches + name: Make sure all builds work (workspace) + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6] + runs-on: ubuntu-22.04 + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@1.74.1 + with: + targets: wasm32-unknown-unknown + + - name: Set up cargo cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Install cargo-all-features + uses: taiki-e/install-action@v2 + with: + tool: cargo-all-features + + - name: Checkout + uses: actions/checkout@v3 + + - name: Download proof programs symlinks + uses: actions/download-artifact@master + with: + name: proof_programs + path: cairo_programs/proof_programs/ + + - name: Fetch programs + uses: actions/cache/restore@v3 + with: + path: ${{ env.CAIRO_PROGRAMS_PATH }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'examples/wasm-demo/src/array_sum.cairo') }} + fail-on-cache-miss: true - - name: Check all features (CLI) - run: | - cd cairo-vm-cli - cargo check-all-features - - name: Check all features (workspace) run: | - cargo check-all-features --workspace --all-targets + cargo check-all-features --n-chunks 6 --chunk ${{ matrix.chunk }} --workspace --all-targets + + smoke-no-std: + needs: merge-caches + name: Make sure all builds work (no_std) + runs-on: ubuntu-22.04 + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@1.74.1 + with: + targets: wasm32-unknown-unknown + + - name: Set up cargo cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Checkout + uses: actions/checkout@v3 + + - name: Download proof programs symlinks + uses: actions/download-artifact@master + with: + name: proof_programs + path: cairo_programs/proof_programs/ + + - name: Fetch programs + uses: actions/cache/restore@v3 + with: + path: ${{ env.CAIRO_PROGRAMS_PATH }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'examples/wasm-demo/src/array_sum.cairo') }} + fail-on-cache-miss: true - name: Check no-std run: | diff --git a/bench/criterion_benchmark.rs b/bench/criterion_benchmark.rs index 5f4c4c1193..48b095cc1f 100644 --- a/bench/criterion_benchmark.rs +++ b/bench/criterion_benchmark.rs @@ -4,10 +4,8 @@ use cairo_vm::{ }; use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -#[cfg(feature = "with_mimalloc")] use mimalloc::MiMalloc; -#[cfg(feature = "with_mimalloc")] #[global_allocator] static ALLOC: MiMalloc = MiMalloc; diff --git a/bench/iai_benchmark.rs b/bench/iai_benchmark.rs index 07fcf214b8..02666c93c4 100644 --- a/bench/iai_benchmark.rs +++ b/bench/iai_benchmark.rs @@ -6,10 +6,8 @@ use cairo_vm::{ vm::{runners::cairo_runner::CairoRunner, vm_core::VirtualMachine}, }; -#[cfg(feature = "with_mimalloc")] use mimalloc::MiMalloc; -#[cfg(feature = "with_mimalloc")] #[global_allocator] static ALLOC: MiMalloc = MiMalloc; diff --git a/cairo-vm-cli/Cargo.toml b/cairo-vm-cli/Cargo.toml index 783e896156..5acd241bf1 100644 --- a/cairo-vm-cli/Cargo.toml +++ b/cairo-vm-cli/Cargo.toml @@ -22,5 +22,7 @@ rstest = "0.17.0" [features] default = ["with_mimalloc"] -with_mimalloc = ["cairo-vm/with_mimalloc", "dep:mimalloc"] -with_tracer = ["cairo-vm/with_tracer", "cairo-vm-tracer"] + +with_mimalloc = ["dep:mimalloc"] +with_tracer = ["cairo-vm/tracer", "cairo-vm-tracer"] + diff --git a/cairo-vm-tracer/Cargo.toml b/cairo-vm-tracer/Cargo.toml index 0ce1ebdea2..e8f933f4b0 100644 --- a/cairo-vm-tracer/Cargo.toml +++ b/cairo-vm-tracer/Cargo.toml @@ -10,7 +10,6 @@ readme.workspace = true default = ["std"] std = [] alloc = [] -tracer = [] [dependencies] cairo-vm = { workspace = true, features = ["arbitrary", "std"] } diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml index a831387a82..a4423fabf8 100644 --- a/cairo1-run/Cargo.toml +++ b/cairo1-run/Cargo.toml @@ -30,4 +30,4 @@ num-traits = { version = "0.2", default-features = false } [features] default = ["with_mimalloc"] -with_mimalloc = ["cairo-vm/with_mimalloc", "dep:mimalloc"] +with_mimalloc = ["dep:mimalloc"] diff --git a/vm/Cargo.toml b/vm/Cargo.toml index bb23ec2014..f31f6df1fb 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -9,9 +9,7 @@ readme.workspace = true keywords.workspace = true [features] -default = ["std", "with_mimalloc"] -with_mimalloc = ["dep:mimalloc"] -with_tracer = ["tracer"] +default = ["std"] std = [ "serde_json/std", "bincode/std", @@ -46,7 +44,6 @@ print = ["std"] [dependencies] zip = {version = "0.6.6", optional = true } -mimalloc = { workspace = true, optional = true } num-bigint = { workspace = true } rand = { workspace = true } num-traits = { workspace = true } @@ -94,6 +91,7 @@ wasm-bindgen-test = "0.3.34" iai-callgrind = "0.3.1" criterion = { version = "0.5.1", features = ["html_reports"] } proptest = "1.0.0" +mimalloc.workspace = true [[bench]] path = "../bench/iai_benchmark.rs" diff --git a/vm/src/lib.rs b/vm/src/lib.rs index bbd334ae81..c4cfbd1334 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -5,7 +5,6 @@ //! - `skip_next_instruction_hint`: Enable the `skip_next_instruction()` hint. Not enabled by default. //! - `hooks`: Enable [`Hooks`](crate::vm::hooks::Hooks) support for the [VirtualMachine](vm::vm_core::VirtualMachine). Not enabled by default. //! - `test_utils`: Enables test utils (`hooks` and `skip_next_instruction` features). Not enabled by default. -//! - `with_mimalloc`: Use [`MiMalloc`](https://crates.io/crates/mimalloc) as the program global allocator. //! - `cairo-1-hints`: Enable hints that were introduced in Cairo 1. Not enabled by default. //! - `arbitrary`: Enables implementations of [`arbitrary::Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/) for some structs. Not enabled by default. diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index a4098464e5..a1e74d87a4 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -955,7 +955,7 @@ impl VirtualMachine { Err(VirtualMachineError::NoOutputBuiltin) } - #[cfg(feature = "with_tracer")] + #[cfg(feature = "tracer")] pub fn relocate_segments(&self) -> Result, MemoryError> { self.segments.relocate_segments() } From 15f9dc0567e8c0987fef390b0e7bb86f0b19daaa Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:57:56 -0300 Subject: [PATCH 27/36] Add mod builtin (#1673) * Ignore pesky pie zip files * Add zero segment * Handle zero segment when counting memory holes * Add Changelog entry * Fix macro * First draft of runner & instance_def * Complete struct * Impl initialize_segments + read_n_words_value * Implement read_inputs * Implement read_memory_vars * Impl fill_inputs * Impl fill_offsets * Impl write_n_words_value * Implement fill_value * Implement fill_memory * Integrate mod builtin under feature flag * Add test file * Impl hint * Push hint impl file * Add quick impl of run_additional_security_checks * Some fixes * Fixes * Fix bugs * Temp testing code * Refactor: Use a struct to represent inputs * Refactor: use a struct to represent memory vars * Refactor: Use a constant N_WORDS * Refactor: use fixed N_WORDS size arrays to ensure safe indexing * Store prime as NonZeroFelt * Refactor: Used a fixed-size array for shift_powers * Clippy * Clippy * Small improvments * Refactor: Use only offsets_ptr instead of all inputs in fill_offsets * Refactor(Simplification): Reduce the return type of read_memory_vars to only a b & c as the other values are not used * Fix(Refactor: Use BigUint to represent values as we have to accomodate values up to U384 * Refactor: Optimize fill_offsets loop so clippy does not complain about it * Add a fill_memory wrapper on the vm to avoid cloning the builtin runners * Fixes * Check batch size * Refactors: Reduce duplication in mod_builtin_fill_memory & combine loops in fill_value * Safety: Use saturating sub * Refactor: Avoid creating empty structs in fill_memory * Add integration test * Add error handling + integration tests * Fix bugged shift_powers creation * Improve error formatting * Simplify error string generation * Fix test * Add test using large batch size + hint impl for circuits using large batch size * Add test with large batch size * Fix hint impl + rename module * Improve error handling of run_additional_security_checks * Remove unused method BuiltinRunner::get_memory_accesses * Impl mark_accessed_memory * Mark cells created by fill_memory as accessed during execution * Remove mark_accessed_memory * Finalize zero segment * WIP: Move final_stack to enum impl * Remove unused methods from builtin runners * Remove commented code * Get rid of empty impls in builtin runners * Move get_memory_segment_addresses impl to enum impl * Impl builtin methods * Add placeholder value * Fix cells_per_instance * Remove temp testing code * Run modulo builtin security checks when running builtin security checks * Use realistic value for mod builtin ratio * Draft(private inputs) * Impl air_private_input * Update private input structure * Improve struct compatibility for serialization * Relocate zero_segment_address + Use BtreeMap keep batches sorted * Fix field names * Clippy * Remove unused attribute from modulo module + limit feature gated code * Fix test files EOF * Relocate ptrs when creating mod private input * Remove feature * Update requirements.txt * Update program + add changelog entry * Impl new hints * Adjust recursive_large_output params * Update tests * Remove allow(unused) * Push bench file * Add non-std imports * Remove unused func * Remove dead code * Box error contents to reduce errir size * Avoid needless to_string when reporting errors * Copy common lib modules so that we can run mod builtin tests in CI * Add mod_builtin to special features so we can run the tests in CI * Fix file * Fix get_used_instances for segment arena * Fix test * Add temp fix * Clippy * fmt * Add no-std import * Add no-std import * Push changelog * Push deleted comment * fmt * Fix typo * Compile with proof mode on CI + add air_private_input tests * Run proof mode integration tests * Push symlinks for convenience as these will be moved soon * Fix test * Fix test value * Fix test value * Fix * Use rea data in layoyts + adjust tests accordingly * Update air priv input tests value (Accounting for increased ratios in layout) * Remove feature mod_builtin * Revert "Remove feature mod_builtin" This reverts commit 5cc921c6e9200dbdc4ad45fa937305f0d9108dd6. * Remove dbg print * Add imports --- .github/workflows/rust.yml | 2 +- CHANGELOG.md | 19 + Makefile | 15 +- cairo-vm-cli/Cargo.toml | 3 +- cairo1-run/src/cairo_run.rs | 6 +- .../mod_builtin_feature/common/modulo.cairo | 124 +++ .../mod_builtin_feature/mod_builtin.cairo | 53 ++ .../mod_builtin_failure.cairo | 53 ++ .../mod_builtin_large_batch_size.cairo | 53 ++ ...d_builtin_large_batch_size_benchmark.cairo | 65 ++ ...mod_builtin_large_batch_size_failure.cairo | 53 ++ .../proof/mod_builtin.cairo | 1 + .../proof/mod_builtin_failure.cairo | 1 + .../proof/mod_builtin_large_batch_size.cairo | 1 + ...d_builtin_large_batch_size_benchmark.cairo | 1 + ...mod_builtin_large_batch_size_failure.cairo | 1 + vm/Cargo.toml | 1 + vm/src/air_private_input.rs | 62 +- .../builtin_hint_processor_definition.rs | 12 + .../builtin_hint_processor/hint_code.rs | 4 + .../builtin_hint_processor/mod.rs | 1 + .../builtin_hint_processor/mod_circuit.rs | 93 ++ vm/src/math_utils/mod.rs | 14 + vm/src/serde/deserialize_program.rs | 5 + vm/src/tests/cairo_run_test.rs | 129 ++- vm/src/tests/mod.rs | 10 +- .../builtins_instance_def.rs | 36 + vm/src/types/instance_definitions/mod.rs | 2 + .../instance_definitions/mod_instance_def.rs | 20 + vm/src/types/relocatable.rs | 26 +- vm/src/vm/errors/runner_errors.rs | 18 + vm/src/vm/errors/vm_errors.rs | 4 + vm/src/vm/runners/builtin_runner/bitwise.rs | 120 +-- vm/src/vm/runners/builtin_runner/ec_op.rs | 113 +-- vm/src/vm/runners/builtin_runner/hash.rs | 106 +-- vm/src/vm/runners/builtin_runner/keccak.rs | 126 +-- vm/src/vm/runners/builtin_runner/mod.rs | 226 ++--- vm/src/vm/runners/builtin_runner/modulo.rs | 899 ++++++++++++++++++ vm/src/vm/runners/builtin_runner/output.rs | 69 +- vm/src/vm/runners/builtin_runner/poseidon.rs | 53 +- .../vm/runners/builtin_runner/range_check.rs | 115 +-- .../runners/builtin_runner/segment_arena.rs | 125 +-- vm/src/vm/runners/builtin_runner/signature.rs | 152 +-- vm/src/vm/runners/cairo_runner.rs | 30 +- vm/src/vm/vm_core.rs | 55 +- vm/src/vm/vm_memory/memory.rs | 24 + vm/src/vm/vm_memory/memory_segments.rs | 4 - 47 files changed, 2061 insertions(+), 1044 deletions(-) create mode 100644 cairo_programs/mod_builtin_feature/common/modulo.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo create mode 100644 vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs create mode 100644 vm/src/types/instance_definitions/mod_instance_def.rs create mode 100644 vm/src/vm/runners/builtin_runner/modulo.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 07fbdd0542..83452c25ff 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -323,7 +323,7 @@ jobs: strategy: fail-fast: false matrix: - special_features: ["", "extensive_hints"] + special_features: ["", "extensive_hints", "mod_builtin"] target: [ test#1, test#2, test#3, test#4, test-no_std#1, test-no_std#2, test-no_std#3, test-no_std#4, test-wasm ] name: Run tests runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index df54baf11c..0471580ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ #### Upcoming Changes +* feat(BREAKING): Add mod builtin [#1673](https://github.com/lambdaclass/cairo-vm/pull/1673) + + Main Changes: + * Add the new `ModBuiltinRunner`, implementing the builtins `add_mod` & `mul_mod` + * Adds `add_mod` & `mul_mod` to the `all_cairo` & `dynamic` layouts under the `mod_builtin` feature flag. This will be added to the main code in a future update. + * Add method `VirtualMachine::fill_memory` in order to perform the new builtin's main logic from within hints + * Add hints to run arithmetic circuits using `add_mod` and/or `mul_mod` builtins + + Other Changes: + * BREAKING: BuiltinRunner method signature change from + `air_private_input(&self, memory: &Memory) -> Vec` to `pub fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec` + * Add `MayleRelocatable::sub_usize` + * Implement `Add for Relocatable` + * Add `Memory::get_usize` + * BREAKING: Clean up unused/duplicated code from builtins module: + * Remove unused method `get_memory_segment_addresses` from all builtin runners & the enum + * Remove empty implementations of `deduce_memory_cell` & `add_validation_rules` from all builtin runners + * Remove duplicated implementation of `final_stack` from all builtin runners except output and move it to the enum implementation + * bugfix(BREAKING): Handle off2 immediate case in `get_integer_from_reference`[#1701](https://github.com/lambdaclass/cairo-vm/pull/1701) * `get_integer_from_reference` & `get_integer_from_var_name` output changed from `Result, HintError>` to `Result` diff --git a/Makefile b/Makefile index 145b221782..86b6d7d7df 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ PROOF_BENCH_DIR=cairo_programs/benchmarks PROOF_BENCH_FILES:=$(wildcard $(PROOF_BENCH_DIR)/*.cairo) PROOF_COMPILED_BENCHES:=$(patsubst $(PROOF_BENCH_DIR)/%.cairo, $(PROOF_BENCH_DIR)/%.json, $(PROOF_BENCH_FILES)) +MOD_BUILTIN_TEST_PROOF_DIR=cairo_programs/mod_builtin_feature/proof +MOD_BUILTIN_TEST_PROOF_FILES:=$(wildcard $(MOD_BUILTIN_TEST_PROOF_DIR)/*.cairo) +COMPILED_MOD_BUILTIN_PROOF_TESTS:=$(patsubst $(MOD_BUILTIN_TEST_PROOF_DIR)/%.cairo, $(MOD_BUILTIN_TEST_PROOF_DIR)/%.json, $(MOD_BUILTIN_TEST_PROOF_FILES)) + $(TEST_PROOF_DIR)/%.json: $(TEST_PROOF_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode @@ -61,6 +65,9 @@ $(TEST_PROOF_DIR)/%.trace $(TEST_PROOF_DIR)/%.memory $(TEST_PROOF_DIR)/%.air_pub $(PROOF_BENCH_DIR)/%.json: $(PROOF_BENCH_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode +$(MOD_BUILTIN_TEST_PROOF_DIR)/%.json: $(MOD_BUILTIN_TEST_PROOF_DIR)/%.cairo + cairo-compile --cairo_path="$(MOD_BUILTIN_TEST_PROOF_DIR):$(MOD_BUILTIN_TEST_PROOF_DIR)" $< --output $@ --proof_mode + # ====================== # Run without proof mode # ====================== @@ -87,6 +94,10 @@ PRINT_TEST_DIR=cairo_programs/print_feature PRINT_TEST_FILES:=$(wildcard $(PRINT_TEST_DIR)/*.cairo) COMPILED_PRINT_TESTS:=$(patsubst $(PRINT_TEST_DIR)/%.cairo, $(PRINT_TEST_DIR)/%.json, $(PRINT_TEST_FILES)) +MOD_BUILTIN_TEST_DIR=cairo_programs/mod_builtin_feature +MOD_BUILTIN_TEST_FILES:=$(wildcard $(MOD_BUILTIN_TEST_DIR)/*.cairo) +COMPILED_MOD_BUILTIN_TESTS:=$(patsubst $(MOD_BUILTIN_TEST_DIR)/%.cairo, $(MOD_BUILTIN_TEST_DIR)/%.json, $(MOD_BUILTIN_TEST_FILES)) + NORETROCOMPAT_DIR:=cairo_programs/noretrocompat NORETROCOMPAT_FILES:=$(wildcard $(NORETROCOMPAT_DIR)/*.cairo) COMPILED_NORETROCOMPAT_TESTS:=$(patsubst $(NORETROCOMPAT_DIR)/%.cairo, $(NORETROCOMPAT_DIR)/%.json, $(NORETROCOMPAT_FILES)) @@ -228,8 +239,8 @@ run: check: cargo check -cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) -cairo_proof_programs: $(COMPILED_PROOF_TESTS) +cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) $(COMPILED_MOD_BUILTIN_TESTS) +cairo_proof_programs: $(COMPILED_PROOF_TESTS) $(COMPILED_MOD_BUILTIN_PROOF_TESTS) cairo_bench_programs: $(COMPILED_BENCHES) cairo_1_test_contracts: $(CAIRO_1_COMPILED_CASM_CONTRACTS) cairo_2_test_contracts: $(CAIRO_2_COMPILED_CASM_CONTRACTS) diff --git a/cairo-vm-cli/Cargo.toml b/cairo-vm-cli/Cargo.toml index 5acd241bf1..0b63d2745e 100644 --- a/cairo-vm-cli/Cargo.toml +++ b/cairo-vm-cli/Cargo.toml @@ -22,7 +22,6 @@ rstest = "0.17.0" [features] default = ["with_mimalloc"] - with_mimalloc = ["dep:mimalloc"] with_tracer = ["cairo-vm/tracer", "cairo-vm-tracer"] - +mod_builtin = ["cairo-vm/mod_builtin"] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index f0d2c2df43..47d1f5bbdb 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -496,7 +496,11 @@ fn create_entry_code( BuiltinName::ec_op => EcOpType::ID, BuiltinName::poseidon => PoseidonType::ID, BuiltinName::segment_arena => SegmentArenaType::ID, - BuiltinName::keccak | BuiltinName::ecdsa | BuiltinName::output => return fp_loc, + BuiltinName::keccak + | BuiltinName::ecdsa + | BuiltinName::output + | BuiltinName::add_mod + | BuiltinName::mul_mod => return fp_loc, }; signature .ret_types diff --git a/cairo_programs/mod_builtin_feature/common/modulo.cairo b/cairo_programs/mod_builtin_feature/common/modulo.cairo new file mode 100644 index 0000000000..b52a85370e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/common/modulo.cairo @@ -0,0 +1,124 @@ +// This file is a copy of common/modulo.cairo + added structs from common/cairo_builtins.cairo so that we can run modulo programs in CI +from starkware.cairo.common.math import safe_div, unsigned_div_rem +from starkware.cairo.common.registers import get_label_location + +// Represents a 384-bit unsigned integer d0 + 2**96 * d1 + 2**192 * d2 + 2**288 * d3 +// where each di is in [0, 2**96). +struct UInt384 { + d0: felt, + d1: felt, + d2: felt, + d3: felt, +} + +// Specifies the Add and Mul Mod builtins memory structure. +struct ModBuiltin { + // The modulus. + p: UInt384, + // A pointer to input values, the intermediate results and the output. + values_ptr: UInt384*, + // A pointer to offsets inside the values array, defining the circuit. + // The offsets array should contain 3 * n elements. + offsets_ptr: felt*, + // The number of operations to perform. + n: felt, +} + +const BATCH_SIZE = 1; + +// Returns the smallest felt 0 <= q < rc_bound such that x <= q * y. +func div_ceil{range_check_ptr}(x: felt, y: felt) -> felt { + let (q, r) = unsigned_div_rem(x, y); + if (r != 0) { + return q + 1; + } else { + return q; + } +} + +// Fills the first instance of the add_mod and mul_mod builtins and calls the fill_memory hint to +// fill the rest of the instances and the missing values in the values table. +// +// This function uses a hardcoded value of batch_size=8, and asserts the instance definitions use +// the same value. +func run_mod_p_circuit_with_large_batch_size{ + range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin* +}( + p: UInt384, + values_ptr: UInt384*, + add_mod_offsets_ptr: felt*, + add_mod_n: felt, + mul_mod_offsets_ptr: felt*, + mul_mod_n: felt, +) { + const BATCH_SIZE = 8; + let add_mod_n_instances = div_ceil(add_mod_n, BATCH_SIZE); + assert add_mod_ptr[0] = ModBuiltin( + p=p, + values_ptr=values_ptr, + offsets_ptr=add_mod_offsets_ptr, + n=add_mod_n_instances * BATCH_SIZE, + ); + + let mul_mod_n_instances = div_ceil(mul_mod_n, BATCH_SIZE); + assert mul_mod_ptr[0] = ModBuiltin( + p=p, + values_ptr=values_ptr, + offsets_ptr=mul_mod_offsets_ptr, + n=mul_mod_n_instances * BATCH_SIZE, + ); + + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} + + let add_mod_ptr = &add_mod_ptr[add_mod_n_instances]; + let mul_mod_ptr = &mul_mod_ptr[mul_mod_n_instances]; + return (); +} + +// Fills the first instance of the add_mod and mul_mod builtins and calls the fill_memory hint to +// fill the rest of the instances and the missing values in the values table. +// +// This function uses a hardcoded value of batch_size=1, and asserts the instance definitions use +// the same value. +func run_mod_p_circuit{add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}( + p: UInt384, + values_ptr: UInt384*, + add_mod_offsets_ptr: felt*, + add_mod_n: felt, + mul_mod_offsets_ptr: felt*, + mul_mod_n: felt, +) { + assert add_mod_ptr[0] = ModBuiltin( + p=p, values_ptr=values_ptr, offsets_ptr=add_mod_offsets_ptr, n=add_mod_n + ); + + assert mul_mod_ptr[0] = ModBuiltin( + p=p, values_ptr=values_ptr, offsets_ptr=mul_mod_offsets_ptr, n=mul_mod_n + ); + + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == 1 + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == 1 + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} + + let add_mod_ptr = &add_mod_ptr[add_mod_n]; + let mul_mod_ptr = &mul_mod_ptr[mul_mod_n]; + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin.cairo b/cairo_programs/mod_builtin_feature/mod_builtin.cairo new file mode 100644 index 0000000000..f7ca04517e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo new file mode 100644 index 0000000000..6e7a9a3175 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=2, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo new file mode 100644 index 0000000000..e25670459e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo new file mode 100644 index 0000000000..21d643af34 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo @@ -0,0 +1,65 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func run_circuit{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} + +func run_loop{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}(n: felt) { + if (n == 0) { + return (); + } + run_circuit(); + return run_loop(n - 1); +} + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + return run_loop(100); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo new file mode 100644 index 0000000000..9605de779a --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=2, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo new file mode 120000 index 0000000000..754dd814da --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo @@ -0,0 +1 @@ +../mod_builtin.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo new file mode 120000 index 0000000000..11df1c30ef --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo @@ -0,0 +1 @@ +../mod_builtin_failure.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo new file mode 120000 index 0000000000..edcda419b0 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo new file mode 120000 index 0000000000..07b4071b7e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size_benchmark.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo new file mode 120000 index 0000000000..1882352a93 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size_failure.cairo \ No newline at end of file diff --git a/vm/Cargo.toml b/vm/Cargo.toml index f31f6df1fb..d1b25dace9 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -27,6 +27,7 @@ cairo-1-hints = [ "dep:ark-std", ] tracer = [] +mod_builtin = [] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = [ diff --git a/vm/src/air_private_input.rs b/vm/src/air_private_input.rs index 52e3ac4cae..d1c5f1a8ca 100644 --- a/vm/src/air_private_input.rs +++ b/vm/src/air_private_input.rs @@ -1,11 +1,12 @@ use crate::{ stdlib::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, prelude::{String, Vec}, }, vm::runners::builtin_runner::{ - BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, KECCAK_BUILTIN_NAME, - POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + ADD_MOD_BUILTIN_NAME, BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, + KECCAK_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME, POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, + SIGNATURE_BUILTIN_NAME, }, }; use serde::{Deserialize, Serialize}; @@ -31,6 +32,10 @@ pub struct AirPrivateInputSerializable { keccak: Option>, #[serde(skip_serializing_if = "Option::is_none")] poseidon: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + add_mod: Option, + #[serde(skip_serializing_if = "Option::is_none")] + mul_mod: Option, } // Contains only builtin public inputs, useful for library users @@ -46,6 +51,7 @@ pub enum PrivateInput { PoseidonState(PrivateInputPoseidonState), KeccakState(PrivateInputKeccakState), Signature(PrivateInputSignature), + Mod(ModInput), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -106,6 +112,44 @@ pub struct SignatureInput { pub w: Felt252, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInput { + pub instances: Vec, + pub zero_value_address: usize, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInputInstance { + pub index: usize, + pub p0: Felt252, + pub p1: Felt252, + pub p2: Felt252, + pub p3: Felt252, + pub values_ptr: usize, + pub offsets_ptr: usize, + pub n: usize, + pub batch: BTreeMap, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInputMemoryVars { + pub a_offset: usize, + pub a0: Felt252, + pub a1: Felt252, + pub a2: Felt252, + pub a3: Felt252, + pub b_offset: usize, + pub b0: Felt252, + pub b1: Felt252, + pub b2: Felt252, + pub b3: Felt252, + pub c_offset: usize, + pub c0: Felt252, + pub c1: Felt252, + pub c2: Felt252, + pub c3: Felt252, +} + impl AirPrivateInput { pub fn to_serializable( &self, @@ -122,6 +166,16 @@ impl AirPrivateInput { ec_op: self.0.get(EC_OP_BUILTIN_NAME).cloned(), keccak: self.0.get(KECCAK_BUILTIN_NAME).cloned(), poseidon: self.0.get(POSEIDON_BUILTIN_NAME).cloned(), + add_mod: self + .0 + .get(ADD_MOD_BUILTIN_NAME) + .and_then(|pi| pi.first()) + .cloned(), + mul_mod: self + .0 + .get(MUL_MOD_BUILTIN_NAME) + .and_then(|pi| pi.first()) + .cloned(), } } } @@ -224,6 +278,8 @@ mod tests { input_s2: Felt252::from(3), }, )]), + add_mod: None, + mul_mod: None, }; let private_input = AirPrivateInput::from(serializable_private_input.clone()); 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 93c60f70b5..e785949884 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 @@ -5,6 +5,7 @@ use super::{ ec_recover_sub_a_b, }, field_arithmetic::{u256_get_square_root, u384_get_square_root, uint384_div}, + mod_circuit::{run_p_mod_circuit, run_p_mod_circuit_with_large_batch_size}, secp::{ ec_utils::{ compute_doubling_slope_external_consts, compute_slope_and_assing_secp_p, @@ -822,6 +823,17 @@ impl HintProcessorLogic for BuiltinHintProcessor { } hint_code::EC_RECOVER_PRODUCT_DIV_M => ec_recover_product_div_m(exec_scopes), hint_code::SPLIT_XX => split_xx(vm, &hint_data.ids_data, &hint_data.ap_tracking), + hint_code::RUN_P_CIRCUIT => { + run_p_mod_circuit(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } + hint_code::RUN_P_CIRCUIT_WITH_LARGE_BATCH_SIZE => { + run_p_mod_circuit_with_large_batch_size( + vm, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + ) + } #[cfg(feature = "skip_next_instruction_hint")] hint_code::SKIP_NEXT_INSTRUCTION => skip_next_instruction(vm), #[cfg(feature = "print")] 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 b6b0f1ca24..563fe0d7e0 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1422,6 +1422,10 @@ print( {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} )"#; +pub const RUN_P_CIRCUIT: &str = "from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner\nassert builtin_runners[\"add_mod_builtin\"].instance_def.batch_size == 1\nassert builtin_runners[\"mul_mod_builtin\"].instance_def.batch_size == 1\n\nModBuiltinRunner.fill_memory(\n memory=memory,\n add_mod=(ids.add_mod_ptr.address_, builtin_runners[\"add_mod_builtin\"], ids.add_mod_n),\n mul_mod=(ids.mul_mod_ptr.address_, builtin_runners[\"mul_mod_builtin\"], ids.mul_mod_n),\n)"; + +pub const RUN_P_CIRCUIT_WITH_LARGE_BATCH_SIZE: &str = "from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner\nassert builtin_runners[\"add_mod_builtin\"].instance_def.batch_size == ids.BATCH_SIZE\nassert builtin_runners[\"mul_mod_builtin\"].instance_def.batch_size == ids.BATCH_SIZE\n\nModBuiltinRunner.fill_memory(\n memory=memory,\n add_mod=(ids.add_mod_ptr.address_, builtin_runners[\"add_mod_builtin\"], ids.add_mod_n),\n mul_mod=(ids.mul_mod_ptr.address_, builtin_runners[\"mul_mod_builtin\"], ids.mul_mod_n),\n)"; + pub const NONDET_ELEMENTS_OVER_TEN: &str = "memory[ap] = to_felt_or_relocatable(ids.elements_end - ids.elements >= 10)"; pub const NONDET_ELEMENTS_OVER_TWO: &str = diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index 8236f8a866..a890ad6ebe 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -16,6 +16,7 @@ pub mod keccak_utils; pub mod math_utils; pub mod memcpy_hint_utils; pub mod memset_utils; +mod mod_circuit; pub mod poseidon_utils; pub mod pow_utils; #[cfg(feature = "print")] diff --git a/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs b/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs new file mode 100644 index 0000000000..22fbc57d19 --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs @@ -0,0 +1,93 @@ +use crate::stdlib::prelude::String; +use crate::{ + hint_processor::hint_processor_definition::HintReference, + serde::deserialize_program::ApTracking, + stdlib::collections::HashMap, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, + Felt252, +}; +#[cfg(not(feature = "mod_builtin"))] +use crate::{stdlib::prelude::Box, types::errors::math_errors::MathError}; +use num_traits::ToPrimitive; + +use super::hint_utils::{get_integer_from_var_name, get_ptr_from_var_name}; +/* Implements Hint: +%{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == 1 + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == 1 + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) +%} +*/ +pub fn run_p_mod_circuit( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + run_p_mod_circuit_inner(vm, ids_data, ap_tracking, 1) +} + +/* Implements Hint: + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} +*/ +#[allow(unused_variables)] +pub fn run_p_mod_circuit_with_large_batch_size( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, +) -> Result<(), HintError> { + #[cfg(not(feature = "mod_builtin"))] + const LARGE_BATCH_SIZE_PATH: &str = + "starkware.cairo.common.modulo.run_mod_p_circuit_with_large_batch_size.BATCH_SIZE"; + #[cfg(not(feature = "mod_builtin"))] + let batch_size = constants + .get(LARGE_BATCH_SIZE_PATH) + .ok_or_else(|| HintError::MissingConstant(Box::new(LARGE_BATCH_SIZE_PATH)))?; + #[cfg(not(feature = "mod_builtin"))] + let batch_size = batch_size + .to_usize() + .ok_or_else(|| MathError::Felt252ToUsizeConversion(Box::new(*batch_size)))?; + #[cfg(feature = "mod_builtin")] + let batch_size = 8; // Hardcoded here as we are not importing from the common lib yet + run_p_mod_circuit_inner(vm, ids_data, ap_tracking, batch_size) +} + +pub fn run_p_mod_circuit_inner( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + batch_size: usize, +) -> Result<(), HintError> { + let add_mod_ptr = get_ptr_from_var_name("add_mod_ptr", vm, ids_data, ap_tracking)?; + let mul_mod_ptr = get_ptr_from_var_name("mul_mod_ptr", vm, ids_data, ap_tracking)?; + let add_mod_n = get_integer_from_var_name("add_mod_n", vm, ids_data, ap_tracking)? + .as_ref() + .to_usize() + .unwrap(); + let mul_mod_n = get_integer_from_var_name("mul_mod_n", vm, ids_data, ap_tracking)? + .as_ref() + .to_usize() + .unwrap(); + vm.mod_builtin_fill_memory( + Some((add_mod_ptr, add_mod_n)), + Some((mul_mod_ptr, mul_mod_n)), + Some(batch_size), + ) + .map_err(HintError::Internal) +} diff --git a/vm/src/math_utils/mod.rs b/vm/src/math_utils/mod.rs index c464cb7228..bd5ff0935d 100644 --- a/vm/src/math_utils/mod.rs +++ b/vm/src/math_utils/mod.rs @@ -196,6 +196,20 @@ pub fn div_mod(n: &BigInt, m: &BigInt, p: &BigInt) -> Result Ok((n * a).mod_floor(p)) } +pub(crate) fn div_mod_unsigned( + n: &BigUint, + m: &BigUint, + p: &BigUint, +) -> Result { + // BigUint to BigInt conversion cannot fail & div_mod will always return a positive value if all values are positive so we can safely unwrap here + div_mod( + &n.to_bigint().unwrap(), + &m.to_bigint().unwrap(), + &p.to_bigint().unwrap(), + ) + .map(|i| i.to_biguint().unwrap()) +} + pub fn ec_add( point_a: (BigInt, BigInt), point_b: (BigInt, BigInt), diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index dda347101a..a849541d23 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -18,6 +18,7 @@ use crate::{ use crate::utils::PRIME_STR; use crate::vm::runners::builtin_runner::SEGMENT_ARENA_BUILTIN_NAME; +use crate::vm::runners::builtin_runner::{ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME}; use crate::Felt252; use crate::{ serde::deserialize_utils, @@ -55,6 +56,8 @@ pub enum BuiltinName { ec_op, poseidon, segment_arena, + add_mod, + mul_mod, } impl BuiltinName { @@ -69,6 +72,8 @@ impl BuiltinName { BuiltinName::ec_op => EC_OP_BUILTIN_NAME, BuiltinName::poseidon => POSEIDON_BUILTIN_NAME, BuiltinName::segment_arena => SEGMENT_ARENA_BUILTIN_NAME, + BuiltinName::add_mod => ADD_MOD_BUILTIN_NAME, + BuiltinName::mul_mod => MUL_MOD_BUILTIN_NAME, } } } diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c06a3d4e3a..60bb867a6e 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1,6 +1,15 @@ -use num_traits::Zero; - use crate::tests::*; +#[cfg(feature = "mod_builtin")] +use crate::{ + utils::test_utils::Program, + vm::{ + runners::{builtin_runner::BuiltinRunner, cairo_runner::CairoRunner}, + security::verify_secure_runner, + vm_core::VirtualMachine, + }, +}; + +use num_traits::Zero; #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -1061,3 +1070,119 @@ fn cairo_run_print_dict_array() { include_bytes!("../../../cairo_programs/print_feature/print_dict_array.json"); run_program_simple(program_data); } + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/mod_builtin.json"); + run_program_with_custom_mod_builtin_params(program_data, false, 1, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_failure() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/mod_builtin_failure.json"); + let error_msg = "mul_mod_builtin: Expected a * b == c (mod p). Got: instance=2, batch=0, p=9, a=2, b=2, c=2."; + run_program_with_custom_mod_builtin_params(program_data, false, 1, 3, Some(error_msg)); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.json" + ); + run_program_with_custom_mod_builtin_params(program_data, false, 8, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size_failure() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.json" + ); + let error_msg = "mul_mod_builtin: Expected a * b == c (mod p). Got: instance=0, batch=2, p=9, a=2, b=2, c=2."; + run_program_with_custom_mod_builtin_params(program_data, false, 8, 3, Some(error_msg)); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_proof() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/proof/mod_builtin.json"); + run_program_with_custom_mod_builtin_params(program_data, true, 1, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size_proof() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.json" + ); + run_program_with_custom_mod_builtin_params(program_data, true, 8, 3, None); +} + +#[cfg(feature = "mod_builtin")] +fn run_program_with_custom_mod_builtin_params( + data: &[u8], + proof_mode: bool, + batch_size: usize, + word_bit_len: u32, + security_error: Option<&str>, +) { + let cairo_run_config = CairoRunConfig { + layout: "all_cairo", + proof_mode, + ..Default::default() + }; + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let program = Program::from_bytes(data, Some(cairo_run_config.entrypoint)).unwrap(); + let mut cairo_runner = CairoRunner::new( + &program, + cairo_run_config.layout, + cairo_run_config.proof_mode, + ) + .unwrap(); + + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = cairo_runner.initialize(&mut vm, false).unwrap(); + // Modify add_mod & mul_mod params + for runner in vm.get_builtin_runners_as_mut() { + if let BuiltinRunner::Mod(runner) = runner { + runner.override_layout_params(batch_size, word_bit_len) + } + } + + cairo_runner + .run_until_pc(end, &mut vm, &mut hint_processor) + .unwrap(); + + if cairo_run_config.proof_mode { + cairo_runner + .run_for_steps(1, &mut vm, &mut hint_processor) + .unwrap(); + } + cairo_runner + .end_run( + cairo_run_config.disable_trace_padding, + false, + &mut vm, + &mut hint_processor, + ) + .unwrap(); + + vm.verify_auto_deductions().unwrap(); + cairo_runner.read_return_values(&mut vm).unwrap(); + if cairo_run_config.proof_mode { + cairo_runner.finalize_segments(&mut vm).unwrap(); + } + let security_res = verify_secure_runner(&cairo_runner, true, None, &mut vm); + if let Some(error) = security_error { + assert!(security_res.is_err()); + assert!(security_res.err().unwrap().to_string().contains(error)); + return; + } + security_res.unwrap(); +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index bb80958789..d09918726f 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -48,24 +48,25 @@ mod skip_instruction_test; //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 fn run_program_simple(data: &[u8]) { - run_program(data, Some("all_cairo"), None, None) + run_program(data, false, Some("all_cairo"), None, None) } //For simple programs that should just succeed but using small layout. fn run_program_small(data: &[u8]) { - run_program(data, Some("small"), None, None) + run_program(data, false, Some("small"), None, None) } fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { - run_program(data, Some("all_cairo"), Some(trace), None) + run_program(data, false, Some("all_cairo"), Some(trace), None) } fn run_program_with_error(data: &[u8], error: &str) { - run_program(data, Some("all_cairo"), None, Some(error)) + run_program(data, false, Some("all_cairo"), None, Some(error)) } fn run_program( data: &[u8], + proof_mode: bool, layout: Option<&str>, trace: Option<&[(usize, usize, usize)]>, error: Option<&str>, @@ -75,6 +76,7 @@ fn run_program( layout: layout.unwrap_or("all_cairo"), relocate_mem: true, trace_enabled: true, + proof_mode, ..Default::default() }; let res = cairo_run(data, &cairo_run_config, &mut hint_executor); diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index 10675423f7..1b21aa6da0 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -1,9 +1,11 @@ +use super::mod_instance_def::ModInstanceDef; use super::{ bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, ecdsa_instance_def::EcdsaInstanceDef, keccak_instance_def::KeccakInstanceDef, pedersen_instance_def::PedersenInstanceDef, poseidon_instance_def::PoseidonInstanceDef, range_check_instance_def::RangeCheckInstanceDef, }; + use serde::Serialize; #[derive(Serialize, Debug, PartialEq)] @@ -16,6 +18,8 @@ pub(crate) struct BuiltinsInstanceDef { pub(crate) ec_op: Option, pub(crate) keccak: Option, pub(crate) poseidon: Option, + pub(crate) add_mod: Option, + pub(crate) mul_mod: Option, } impl BuiltinsInstanceDef { @@ -29,6 +33,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -42,6 +48,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -55,6 +63,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -68,6 +78,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -81,6 +93,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: None, poseidon: Some(PoseidonInstanceDef::default()), + add_mod: None, + mul_mod: None, } } @@ -94,6 +108,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::default()), + add_mod: None, + mul_mod: None, } } @@ -107,6 +123,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: Some(PoseidonInstanceDef::new(Some(8))), + add_mod: None, + mul_mod: None, } } @@ -120,6 +138,14 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::new(Some(256))), + #[cfg(feature = "mod_builtin")] + add_mod: Some(ModInstanceDef::new(Some(128), 1, 96)), + #[cfg(feature = "mod_builtin")] + mul_mod: Some(ModInstanceDef::new(Some(256), 1, 96)), + #[cfg(not(feature = "mod_builtin"))] + add_mod: None, + #[cfg(not(feature = "mod_builtin"))] + mul_mod: None, } } @@ -133,6 +159,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::default()), keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -146,6 +174,14 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(None)), keccak: None, poseidon: None, + #[cfg(feature = "mod_builtin")] + add_mod: Some(ModInstanceDef::new(None, 1, 96)), + #[cfg(feature = "mod_builtin")] + mul_mod: Some(ModInstanceDef::new(None, 1, 96)), + #[cfg(not(feature = "mod_builtin"))] + add_mod: None, + #[cfg(not(feature = "mod_builtin"))] + mul_mod: None, } } } diff --git a/vm/src/types/instance_definitions/mod.rs b/vm/src/types/instance_definitions/mod.rs index 53bcc15d1b..691be29847 100644 --- a/vm/src/types/instance_definitions/mod.rs +++ b/vm/src/types/instance_definitions/mod.rs @@ -5,6 +5,8 @@ pub mod diluted_pool_instance_def; pub mod ec_op_instance_def; pub mod ecdsa_instance_def; pub mod keccak_instance_def; +#[allow(unused)] +pub mod mod_instance_def; pub mod pedersen_instance_def; pub mod poseidon_instance_def; pub mod range_check_instance_def; diff --git a/vm/src/types/instance_definitions/mod_instance_def.rs b/vm/src/types/instance_definitions/mod_instance_def.rs new file mode 100644 index 0000000000..50bd8184c1 --- /dev/null +++ b/vm/src/types/instance_definitions/mod_instance_def.rs @@ -0,0 +1,20 @@ +use serde::Serialize; + +pub(crate) const N_WORDS: usize = 4; + +#[derive(Serialize, Debug, PartialEq, Clone)] +pub(crate) struct ModInstanceDef { + pub(crate) ratio: Option, + pub(crate) word_bit_len: u32, + pub(crate) batch_size: usize, +} + +impl ModInstanceDef { + pub(crate) fn new(ratio: Option, batch_size: usize, word_bit_len: u32) -> Self { + ModInstanceDef { + ratio, + word_bit_len, + batch_size, + } + } +} diff --git a/vm/src/types/relocatable.rs b/vm/src/types/relocatable.rs index 4bac0bba46..e569ce7707 100644 --- a/vm/src/types/relocatable.rs +++ b/vm/src/types/relocatable.rs @@ -127,6 +127,13 @@ impl AddAssign for Relocatable { } } +impl Add for Relocatable { + type Output = Result; + fn add(self, other: u32) -> Result { + self + other as usize + } +} + impl Add for Relocatable { type Output = Result; fn add(self, other: i32) -> Result { @@ -225,15 +232,8 @@ impl MaybeRelocatable { pub fn add_int(&self, other: &Felt252) -> Result { match *self { MaybeRelocatable::Int(ref value) => Ok(MaybeRelocatable::Int(value + other)), - MaybeRelocatable::RelocatableValue(ref rel) => { - let big_offset = other + rel.offset as u64; - let new_offset = big_offset.to_usize().ok_or_else(|| { - MathError::RelocatableAddFelt252OffsetExceeded(Box::new((*rel, *other))) - })?; - Ok(MaybeRelocatable::RelocatableValue(Relocatable { - segment_index: rel.segment_index, - offset: new_offset, - })) + MaybeRelocatable::RelocatableValue(rel) => { + Ok(MaybeRelocatable::RelocatableValue((rel + other)?)) } } } @@ -264,6 +264,14 @@ impl MaybeRelocatable { } } + /// Subs a usize from self + pub fn sub_usize(&self, other: usize) -> Result { + Ok(match *self { + MaybeRelocatable::Int(ref value) => MaybeRelocatable::Int(value - other as u64), + MaybeRelocatable::RelocatableValue(rel) => (rel - other)?.into(), + }) + } + /// Substracts two MaybeRelocatable values and returns the result as a MaybeRelocatable value. /// Only values of the same type may be substracted. /// Relocatable values can only be substracted if they belong to the same segment. diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index 01634ecb2c..ac59da83e9 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -104,6 +104,24 @@ pub enum RunnerError { InvalidPoint, #[error("Page ({0}) is not on the expected segment {1}")] PageNotOnSegment(Relocatable, usize), + #[error("Expected integer at address {} to be smaller than 2^{}. Got: {}.", (*.0).0, (*.0).1, (*.0).2)] + WordExceedsModBuiltinWordBitLen(Box<(Relocatable, u32, Felt252)>), + #[error("{}: Expected n >= 1. Got: {}.", (*.0).0, (*.0).1)] + ModBuiltinNLessThanOne(Box<(&'static str, usize)>), + #[error("{}: Missing value at address {}.", (*.0).0, (*.0).1)] + ModBuiltinMissingValue(Box<(&'static str, Relocatable)>), + #[error("{}: n must be <= {}", (*.0).0, (*.0).1)] + FillMemoryMaxExceeded(Box<(&'static str, usize)>), + #[error("{0}: write_n_words value must be 0 after loop")] + WriteNWordsValueNotZero(&'static str), + #[error("add_mod and mul_mod builtins must have the same n_words and word_bit_len.")] + ModBuiltinsMismatchedInstanceDef, + #[error("At least one of add_mod and mul_mod must be given.")] + FillMemoryNoBuiltinSet, + #[error("Could not fill the values table, add_mod_index={0}, mul_mod_index={1}")] + FillMemoryCoudNotFillTable(usize, usize), + #[error("{}: {}", (*.0).0, (*.0).1)] + ModBuiltinSecurityCheck(Box<(&'static str, String)>), } #[cfg(test)] diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index cbccde51b5..68b8ceed45 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -87,6 +87,8 @@ pub enum VirtualMachineError { NoRangeCheckBuiltin, #[error("Expected ecdsa builtin to be present")] NoSignatureBuiltin, + #[error("Expected {0} to be present")] + NoModBuiltin(&'static str), #[error("Div out of range: 0 < {} <= {}", (*.0).0, (*.0).1)] OutOfValidRange(Box<(Felt252, Felt252)>), #[error("Failed to compare {} and {}, cant compare a relocatable to an integer value", (*.0).0, (*.0).1)] @@ -131,6 +133,8 @@ pub enum VirtualMachineError { FailedToWriteOutput, #[error("Failed to find index {0} in the vm's relocation table")] RelocationNotFound(usize), + #[error("{} batch size is not {}", (*.0).0, (*.0).1)] + ModBuiltinBatchSize(Box<(&'static str, usize)>), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/bitwise.rs b/vm/src/vm/runners/builtin_runner/bitwise.rs index 1c4bdb889c..2a049690a2 100644 --- a/vm/src/vm/runners/builtin_runner/bitwise.rs +++ b/vm/src/vm/runners/builtin_runner/bitwise.rs @@ -15,8 +15,6 @@ use crate::{ }; use num_integer::div_ceil; -use super::BITWISE_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct BitwiseBuiltinRunner { ratio: Option, @@ -63,8 +61,6 @@ impl BitwiseBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -121,10 +117,6 @@ impl BitwiseBuiltinRunner { )))) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -149,43 +141,6 @@ impl BitwiseBuiltinRunner { 4 * partition_lengh + num_trimmed } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(BITWISE_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(BITWISE_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - BITWISE_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - BITWISE_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_used_instances( &self, segments: &MemorySegmentManager, @@ -225,7 +180,7 @@ mod tests { use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; use crate::vm::errors::memory_errors::MemoryError; - use crate::vm::runners::builtin_runner::BuiltinRunner; + use crate::vm::runners::builtin_runner::{BuiltinRunner, BITWISE_BUILTIN_NAME}; use crate::vm::vm_core::VirtualMachine; use crate::Felt252; use crate::{ @@ -258,7 +213,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -282,7 +238,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -310,7 +267,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), false); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), false).into(); let mut vm = vm!(); @@ -334,7 +292,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -492,63 +451,6 @@ mod tests { assert_eq!(result, Ok(None)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -626,7 +528,7 @@ mod tests { let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -644,7 +546,7 @@ mod tests { ((0, 14), 14) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Pair(PrivateInputPair { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/ec_op.rs b/vm/src/vm/runners/builtin_runner/ec_op.rs index af1951f543..2c9be94793 100644 --- a/vm/src/vm/runners/builtin_runner/ec_op.rs +++ b/vm/src/vm/runners/builtin_runner/ec_op.rs @@ -13,8 +13,6 @@ use crate::Felt252; use num_integer::{div_ceil, Integer}; use starknet_types_core::curve::ProjectivePoint; -use super::EC_OP_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct EcOpBuiltinRunner { ratio: Option, @@ -104,8 +102,6 @@ impl EcOpBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -193,10 +189,6 @@ impl EcOpBuiltinRunner { } } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -211,43 +203,6 @@ impl EcOpBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(EC_OP_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(EC_OP_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - EC_OP_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - EC_OP_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn format_ec_op_error( p: ProjectivePoint, m: num_bigint::BigUint, @@ -302,6 +257,7 @@ mod tests { use crate::utils::test_utils::*; use crate::vm::errors::cairo_run_errors::CairoRunError; use crate::vm::errors::vm_errors::VirtualMachineError; + use crate::vm::runners::builtin_runner::EC_OP_BUILTIN_NAME; use crate::vm::runners::cairo_runner::CairoRunner; use crate::{felt_hex, felt_str, relocatable}; @@ -329,7 +285,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -353,7 +310,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -381,7 +339,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), false); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), false).into(); let mut vm = vm!(); @@ -405,7 +364,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -838,57 +798,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -996,7 +905,7 @@ mod tests { let builtin: BuiltinRunner = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -1004,7 +913,7 @@ mod tests { ((0, 4), 4) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![PrivateInput::EcOp(PrivateInputEcOp { index: 0, p_x: 0.into(), diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index 788c224644..c9b437403f 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -14,8 +14,6 @@ use crate::Felt252; use num_integer::{div_ceil, Integer}; use starknet_crypto::{pedersen_hash, FieldElement}; -use super::HASH_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct HashBuiltinRunner { pub base: usize, @@ -66,8 +64,6 @@ impl HashBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -124,10 +120,6 @@ impl HashBuiltinRunner { Ok(None) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -142,43 +134,6 @@ impl HashBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(HASH_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(HASH_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - HASH_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - HASH_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_additional_data(&self) -> BuiltinAdditionalData { let mut verified_addresses = Vec::new(); for (offset, is_verified) in self.verified_addresses.borrow().iter().enumerate() { @@ -221,6 +176,7 @@ mod tests { use crate::serde::deserialize_program::BuiltinName; use crate::types::program::Program; use crate::utils::test_utils::*; + use crate::vm::runners::builtin_runner::HASH_BUILTIN_NAME; use crate::vm::runners::cairo_runner::CairoRunner; use crate::{felt_hex, relocatable}; @@ -246,7 +202,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -270,7 +226,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -298,7 +254,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = HashBuiltinRunner::new(Some(10), false); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -322,7 +278,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -480,54 +436,6 @@ mod tests { assert_eq!(result, Ok(None)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = HashBuiltinRunner::new(Some(256), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -577,7 +485,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = HashBuiltinRunner::new(None, true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -590,7 +498,7 @@ mod tests { ((0, 9), 9) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Pair(PrivateInputPair { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/keccak.rs b/vm/src/vm/runners/builtin_runner/keccak.rs index a4f2346b27..cbd5ad0dc5 100644 --- a/vm/src/vm/runners/builtin_runner/keccak.rs +++ b/vm/src/vm/runners/builtin_runner/keccak.rs @@ -5,7 +5,6 @@ use crate::types::instance_definitions::keccak_instance_def::KeccakInstanceDef; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::errors::runner_errors::RunnerError; -use crate::vm::vm_core::VirtualMachine; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use crate::Felt252; @@ -64,8 +63,6 @@ impl KeccakBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -129,10 +126,6 @@ impl KeccakBuiltinRunner { Ok(self.cache.borrow().get(&address).map(|x| x.into())) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -147,57 +140,6 @@ impl KeccakBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(KECCAK_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(KECCAK_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - KECCAK_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - KECCAK_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - - pub fn get_memory_accesses( - &self, - vm: &VirtualMachine, - ) -> Result, MemoryError> { - let segment_size = vm - .segments - .get_segment_size(self.base) - .ok_or(MemoryError::MissingSegmentUsedSizes)?; - - Ok((0..segment_size) - .map(|i| (self.base as isize, i).into()) - .collect()) - } - pub fn get_used_diluted_check_units(&self, diluted_n_bits: u32) -> usize { // The diluted cells are: // state - 25 rounds times 1600 elements. @@ -301,8 +243,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -326,8 +268,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -354,8 +296,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), false); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), false).into(); let mut vm = vm!(); @@ -379,8 +321,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -444,54 +386,6 @@ mod tests { assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(256)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -732,7 +626,7 @@ mod tests { let builtin: BuiltinRunner = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -743,7 +637,7 @@ mod tests { ((0, 7), 7) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![PrivateInput::KeccakState(PrivateInputKeccakState { index: 0, input_s0: 0.into(), diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index 78eed172a2..9cc2778d11 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -13,21 +13,23 @@ mod bitwise; mod ec_op; mod hash; mod keccak; +mod modulo; mod output; mod poseidon; mod range_check; mod segment_arena; mod signature; -pub use self::keccak::KeccakBuiltinRunner; -pub use self::poseidon::PoseidonBuiltinRunner; -pub use self::segment_arena::SegmentArenaBuiltinRunner; pub use bitwise::BitwiseBuiltinRunner; pub use ec_op::EcOpBuiltinRunner; pub use hash::HashBuiltinRunner; +pub use keccak::KeccakBuiltinRunner; +pub use modulo::ModBuiltinRunner; use num_integer::div_floor; pub use output::OutputBuiltinRunner; +pub use poseidon::PoseidonBuiltinRunner; pub use range_check::RangeCheckBuiltinRunner; +pub use segment_arena::SegmentArenaBuiltinRunner; pub use signature::SignatureBuiltinRunner; use super::cairo_pie::BuiltinAdditionalData; @@ -41,6 +43,8 @@ pub const EC_OP_BUILTIN_NAME: &str = "ec_op_builtin"; pub const KECCAK_BUILTIN_NAME: &str = "keccak_builtin"; pub const POSEIDON_BUILTIN_NAME: &str = "poseidon_builtin"; pub const SEGMENT_ARENA_BUILTIN_NAME: &str = "segment_arena_builtin"; +pub const ADD_MOD_BUILTIN_NAME: &str = "add_mod_builtin"; +pub const MUL_MOD_BUILTIN_NAME: &str = "mul_mod_builtin"; /* NB: this enum is no accident: we may need (and cairo-vm-py *does* need) * structs containing this to be `Send`. The only two ways to achieve that @@ -61,6 +65,7 @@ pub enum BuiltinRunner { Signature(SignatureBuiltinRunner), Poseidon(PoseidonBuiltinRunner), SegmentArena(SegmentArenaBuiltinRunner), + Mod(ModBuiltinRunner), } impl BuiltinRunner { @@ -80,6 +85,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref mut segment_arena) => { segment_arena.initialize_segments(segments) } + BuiltinRunner::Mod(ref mut modulo) => modulo.initialize_segments(segments), } } @@ -94,6 +100,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(ref signature) => signature.initial_stack(), BuiltinRunner::Poseidon(ref poseidon) => poseidon.initial_stack(), BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.initial_stack(), + BuiltinRunner::Mod(ref modulo) => modulo.initial_stack(), } } @@ -101,26 +108,40 @@ impl BuiltinRunner { pub fn final_stack( &mut self, segments: &MemorySegmentManager, - stack_pointer: Relocatable, + pointer: Relocatable, ) -> Result { - match self { - BuiltinRunner::Bitwise(ref mut bitwise) => bitwise.final_stack(segments, stack_pointer), - BuiltinRunner::EcOp(ref mut ec) => ec.final_stack(segments, stack_pointer), - BuiltinRunner::Hash(ref mut hash) => hash.final_stack(segments, stack_pointer), - BuiltinRunner::Output(ref mut output) => output.final_stack(segments, stack_pointer), - BuiltinRunner::RangeCheck(ref mut range_check) => { - range_check.final_stack(segments, stack_pointer) - } - BuiltinRunner::Keccak(ref mut keccak) => keccak.final_stack(segments, stack_pointer), - BuiltinRunner::Signature(ref mut signature) => { - signature.final_stack(segments, stack_pointer) - } - BuiltinRunner::Poseidon(ref mut poseidon) => { - poseidon.final_stack(segments, stack_pointer) + if let BuiltinRunner::Output(output) = self { + return output.final_stack(segments, pointer); + } + if self.included() { + let stop_pointer_addr = + (pointer - 1).map_err(|_| RunnerError::NoStopPointer(Box::new(self.name())))?; + let stop_pointer = segments + .memory + .get_relocatable(stop_pointer_addr) + .map_err(|_| RunnerError::NoStopPointer(Box::new(self.name())))?; + if self.base() as isize != stop_pointer.segment_index { + return Err(RunnerError::InvalidStopPointerIndex(Box::new(( + self.name(), + stop_pointer, + self.base(), + )))); } - BuiltinRunner::SegmentArena(ref mut segment_arena) => { - segment_arena.final_stack(segments, stack_pointer) + let stop_ptr = stop_pointer.offset; + let num_instances = self.get_used_instances(segments)?; + let used = num_instances * self.cells_per_instance() as usize; + if stop_ptr != used { + return Err(RunnerError::InvalidStopPointer(Box::new(( + self.name(), + Relocatable::from((self.base() as isize, used)), + Relocatable::from((self.base() as isize, stop_ptr)), + )))); } + self.set_stop_ptr(stop_ptr); + Ok(stop_pointer_addr) + } else { + self.set_stop_ptr(0); + Ok(pointer) } } @@ -160,6 +181,22 @@ impl BuiltinRunner { } } + /// Returns if the builtin is included in the program builtins + fn included(&self) -> bool { + match *self { + BuiltinRunner::Bitwise(ref bitwise) => bitwise.included, + BuiltinRunner::EcOp(ref ec) => ec.included, + BuiltinRunner::Hash(ref hash) => hash.included, + BuiltinRunner::Output(ref output) => output.included, + BuiltinRunner::RangeCheck(ref range_check) => range_check.included, + BuiltinRunner::Keccak(ref keccak) => keccak.included, + BuiltinRunner::Signature(ref signature) => signature.included, + BuiltinRunner::Poseidon(ref poseidon) => poseidon.included, + BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.included, + BuiltinRunner::Mod(ref modulo) => modulo.included, + } + } + ///Returns the builtin's base pub fn base(&self) -> usize { match *self { @@ -173,6 +210,7 @@ impl BuiltinRunner { BuiltinRunner::Poseidon(ref poseidon) => poseidon.base(), //Warning, returns only the segment index, base offset will be 3 BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.base(), + BuiltinRunner::Mod(ref modulo) => modulo.base(), } } @@ -186,22 +224,16 @@ impl BuiltinRunner { BuiltinRunner::Keccak(keccak) => keccak.ratio(), BuiltinRunner::Signature(ref signature) => signature.ratio(), BuiltinRunner::Poseidon(poseidon) => poseidon.ratio(), + BuiltinRunner::Mod(ref modulo) => modulo.ratio(), } } pub fn add_validation_rule(&self, memory: &mut Memory) { match *self { - BuiltinRunner::Bitwise(ref bitwise) => bitwise.add_validation_rule(memory), - BuiltinRunner::EcOp(ref ec) => ec.add_validation_rule(memory), - BuiltinRunner::Hash(ref hash) => hash.add_validation_rule(memory), - BuiltinRunner::Output(ref output) => output.add_validation_rule(memory), BuiltinRunner::RangeCheck(ref range_check) => range_check.add_validation_rule(memory), - BuiltinRunner::Keccak(ref keccak) => keccak.add_validation_rule(memory), BuiltinRunner::Signature(ref signature) => signature.add_validation_rule(memory), BuiltinRunner::Poseidon(ref poseidon) => poseidon.add_validation_rule(memory), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.add_validation_rule(memory) - } + _ => {} } } @@ -214,55 +246,14 @@ impl BuiltinRunner { BuiltinRunner::Bitwise(ref bitwise) => bitwise.deduce_memory_cell(address, memory), BuiltinRunner::EcOp(ref ec) => ec.deduce_memory_cell(address, memory), BuiltinRunner::Hash(ref hash) => hash.deduce_memory_cell(address, memory), - BuiltinRunner::Output(ref output) => output.deduce_memory_cell(address, memory), - BuiltinRunner::RangeCheck(ref range_check) => { - range_check.deduce_memory_cell(address, memory) - } BuiltinRunner::Keccak(ref keccak) => keccak.deduce_memory_cell(address, memory), - BuiltinRunner::Signature(ref signature) => { - signature.deduce_memory_cell(address, memory) - } BuiltinRunner::Poseidon(ref poseidon) => poseidon.deduce_memory_cell(address, memory), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.deduce_memory_cell(address, memory) - } + _ => Ok(None), } } - pub fn get_memory_accesses( - &self, - vm: &VirtualMachine, - ) -> Result, MemoryError> { - if let BuiltinRunner::SegmentArena(_) = self { - return Ok(vec![]); - } - let base = self.base(); - let segment_size = vm - .segments - .get_segment_size(base) - .ok_or(MemoryError::MissingSegmentUsedSizes)?; - - Ok((0..segment_size) - .map(|i| (base as isize, i).into()) - .collect()) - } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - match self { - BuiltinRunner::Bitwise(ref bitwise) => bitwise.get_memory_segment_addresses(), - BuiltinRunner::EcOp(ref ec) => ec.get_memory_segment_addresses(), - BuiltinRunner::Hash(ref hash) => hash.get_memory_segment_addresses(), - BuiltinRunner::Output(ref output) => output.get_memory_segment_addresses(), - BuiltinRunner::RangeCheck(ref range_check) => { - range_check.get_memory_segment_addresses() - } - BuiltinRunner::Keccak(ref keccak) => keccak.get_memory_segment_addresses(), - BuiltinRunner::Signature(ref signature) => signature.get_memory_segment_addresses(), - BuiltinRunner::Poseidon(ref poseidon) => poseidon.get_memory_segment_addresses(), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.get_memory_segment_addresses() - } - } + (self.base(), self.stop_ptr()) } pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { @@ -278,6 +269,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref segment_arena) => { segment_arena.get_used_cells(segments) } + BuiltinRunner::Mod(ref modulo) => modulo.get_used_cells(segments), } } @@ -297,6 +289,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref segment_arena) => { segment_arena.get_used_instances(segments) } + BuiltinRunner::Mod(modulo) => modulo.get_used_instances(segments), } } @@ -344,6 +337,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(builtin) => builtin.cells_per_instance, BuiltinRunner::Poseidon(builtin) => builtin.cells_per_instance, BuiltinRunner::SegmentArena(builtin) => builtin.cells_per_instance, + BuiltinRunner::Mod(mod_builtin) => mod_builtin.cells_per_instance(), } } @@ -358,6 +352,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(builtin) => builtin.n_input_cells, BuiltinRunner::Poseidon(builtin) => builtin.n_input_cells, BuiltinRunner::SegmentArena(builtin) => builtin.n_input_cells_per_instance, + BuiltinRunner::Mod(builtin) => builtin.n_input_cells(), } } @@ -371,6 +366,8 @@ impl BuiltinRunner { BuiltinRunner::Keccak(builtin) => builtin.instances_per_component, BuiltinRunner::Signature(builtin) => builtin.instances_per_component, BuiltinRunner::Poseidon(builtin) => builtin.instances_per_component, + // TODO: Placeholder till we see layout data + BuiltinRunner::Mod(_) => 1, } } @@ -385,6 +382,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(_) => SIGNATURE_BUILTIN_NAME, BuiltinRunner::Poseidon(_) => POSEIDON_BUILTIN_NAME, BuiltinRunner::SegmentArena(_) => SEGMENT_ARENA_BUILTIN_NAME, + BuiltinRunner::Mod(b) => b.name(), } } @@ -392,6 +390,9 @@ impl BuiltinRunner { if let BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) = self { return Ok(()); } + if let BuiltinRunner::Mod(modulo) = self { + modulo.run_additional_security_checks(vm)?; + } let cells_per_instance = self.cells_per_instance() as usize; let n_input_cells = self.n_input_cells() as usize; let builtin_segment_index = self.base(); @@ -485,20 +486,20 @@ impl BuiltinRunner { } // Returns information about the builtin that should be added to the AIR private input. - pub fn air_private_input(&self, memory: &Memory) -> Vec { + pub fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { match self { - BuiltinRunner::RangeCheck(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Bitwise(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Hash(builtin) => builtin.air_private_input(memory), - BuiltinRunner::EcOp(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Poseidon(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Signature(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Keccak(builtin) => builtin.air_private_input(memory), + BuiltinRunner::RangeCheck(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Bitwise(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Hash(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::EcOp(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Poseidon(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Signature(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Keccak(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Mod(builtin) => builtin.air_private_input(segments), _ => vec![], } } - #[cfg(test)] pub(crate) fn set_stop_ptr(&mut self, stop_ptr: usize) { match self { BuiltinRunner::Bitwise(ref mut bitwise) => bitwise.stop_ptr = Some(stop_ptr), @@ -512,6 +513,22 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref mut segment_arena) => { segment_arena.stop_ptr = Some(stop_ptr) } + BuiltinRunner::Mod(modulo) => modulo.stop_ptr = Some(stop_ptr), + } + } + + pub(crate) fn stop_ptr(&self) -> Option { + match self { + BuiltinRunner::Bitwise(ref bitwise) => bitwise.stop_ptr, + BuiltinRunner::EcOp(ref ec) => ec.stop_ptr, + BuiltinRunner::Hash(ref hash) => hash.stop_ptr, + BuiltinRunner::Output(ref output) => output.stop_ptr, + BuiltinRunner::RangeCheck(ref range_check) => range_check.stop_ptr, + BuiltinRunner::Keccak(ref keccak) => keccak.stop_ptr, + BuiltinRunner::Signature(ref signature) => signature.stop_ptr, + BuiltinRunner::Poseidon(ref poseidon) => poseidon.stop_ptr, + BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.stop_ptr, + BuiltinRunner::Mod(ref modulo) => modulo.stop_ptr, } } } @@ -570,6 +587,12 @@ impl From for BuiltinRunner { } } +impl From for BuiltinRunner { + fn from(runner: ModBuiltinRunner) -> Self { + BuiltinRunner::Mod(runner) + } +} + #[cfg(test)] mod tests { use super::*; @@ -593,49 +616,6 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_bitwise() { diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs new file mode 100644 index 0000000000..8e74113096 --- /dev/null +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -0,0 +1,899 @@ +use crate::{ + air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, + math_utils::{div_mod_unsigned, safe_div_usize}, + stdlib::{ + borrow::Cow, + collections::BTreeMap, + prelude::{Box, Vec}, + }, + types::{ + errors::math_errors::MathError, + instance_definitions::mod_instance_def::{ModInstanceDef, N_WORDS}, + relocatable::{relocate_address, MaybeRelocatable, Relocatable}, + }, + vm::{ + errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, vm_errors::VirtualMachineError, + }, + vm_core::VirtualMachine, + vm_memory::{memory::Memory, memory_segments::MemorySegmentManager}, + }, + Felt252, +}; +use core::{fmt::Display, ops::Shl}; +use num_bigint::BigUint; +use num_integer::div_ceil; +use num_integer::Integer; +use num_traits::One; +use num_traits::Zero; + +//The maximum n value that the function fill_memory accepts. +const FILL_MEMORY_MAX: usize = 100000; + +const INPUT_CELLS: usize = 7; + +const VALUES_PTR_OFFSET: u32 = 4; +const OFFSETS_PTR_OFFSET: u32 = 5; +const N_OFFSET: u32 = 6; + +#[derive(Debug, Clone)] +pub struct ModBuiltinRunner { + builtin_type: ModBuiltinType, + base: usize, + pub(crate) stop_ptr: Option, + instance_def: ModInstanceDef, + pub(crate) included: bool, + zero_segment_index: usize, + zero_segment_size: usize, + // Precomputed powers used for reading and writing values that are represented as n_words words of word_bit_len bits each. + shift: BigUint, + shift_powers: [BigUint; N_WORDS], +} + +#[derive(Debug, Clone)] +pub enum ModBuiltinType { + Mul, + Add, +} + +#[derive(Debug)] +pub enum Operation { + Mul, + Add, + Sub, + DivMod(BigUint), +} + +impl Display for Operation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Operation::Mul => "*".fmt(f), + Operation::Add => "+".fmt(f), + Operation::Sub => "-".fmt(f), + Operation::DivMod(_) => "/".fmt(f), + } + } +} + +#[derive(Debug, Default)] +struct Inputs { + p: BigUint, + p_values: [Felt252; N_WORDS], + values_ptr: Relocatable, + offsets_ptr: Relocatable, + n: usize, +} + +impl ModBuiltinRunner { + pub(crate) fn new_add_mod(instance_def: &ModInstanceDef, included: bool) -> Self { + Self::new(instance_def.clone(), included, ModBuiltinType::Add) + } + + pub(crate) fn new_mul_mod(instance_def: &ModInstanceDef, included: bool) -> Self { + Self::new(instance_def.clone(), included, ModBuiltinType::Mul) + } + + fn new(instance_def: ModInstanceDef, included: bool, builtin_type: ModBuiltinType) -> Self { + let shift = BigUint::one().shl(instance_def.word_bit_len); + let shift_powers = core::array::from_fn(|i| shift.pow(i as u32)); + let zero_segment_size = core::cmp::max(N_WORDS, instance_def.batch_size * 3); + Self { + builtin_type, + base: 0, + stop_ptr: None, + instance_def, + included, + zero_segment_index: 0, + zero_segment_size, + shift, + shift_powers, + } + } + + pub fn name(&self) -> &'static str { + match self.builtin_type { + ModBuiltinType::Mul => super::MUL_MOD_BUILTIN_NAME, + ModBuiltinType::Add => super::ADD_MOD_BUILTIN_NAME, + } + } + + pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) { + self.base = segments.add().segment_index as usize; // segments.add() always returns a positive index + self.zero_segment_index = segments.add_zero_segment(self.zero_segment_size) + } + + pub fn initial_stack(&self) -> Vec { + if self.included { + vec![MaybeRelocatable::from((self.base as isize, 0))] + } else { + vec![] + } + } + + pub fn base(&self) -> usize { + self.base + } + + pub fn ratio(&self) -> Option { + self.instance_def.ratio + } + + pub fn cells_per_instance(&self) -> u32 { + INPUT_CELLS as u32 + } + + pub fn n_input_cells(&self) -> u32 { + INPUT_CELLS as u32 + } + + pub fn batch_size(&self) -> usize { + self.instance_def.batch_size + } + + pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { + segments + .get_segment_used_size(self.base) + .ok_or(MemoryError::MissingSegmentUsedSizes) + } + + pub fn get_used_instances( + &self, + segments: &MemorySegmentManager, + ) -> Result { + let used_cells = self.get_used_cells(segments)?; + Ok(div_ceil(used_cells, self.cells_per_instance() as usize)) + } + + pub(crate) fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { + let segment_index = self.base as isize; + let segment_size = segments + .get_segment_used_size(self.base) + .unwrap_or_default(); + let relocation_table = segments.relocate_segments().unwrap_or_default(); + let mut instances = Vec::::new(); + for instance in 0..segment_size.checked_div(INPUT_CELLS).unwrap_or_default() { + let instance_addr_offset = instance * INPUT_CELLS; + let values_ptr = segments + .memory + .get_relocatable( + ( + segment_index, + instance_addr_offset + VALUES_PTR_OFFSET as usize, + ) + .into(), + ) + .unwrap_or_default(); + let offsets_ptr = segments + .memory + .get_relocatable( + ( + segment_index, + instance_addr_offset + OFFSETS_PTR_OFFSET as usize, + ) + .into(), + ) + .unwrap_or_default(); + let n = segments + .memory + .get_usize((segment_index, instance_addr_offset + N_OFFSET as usize).into()) + .unwrap_or_default(); + let p_values: [Felt252; N_WORDS] = core::array::from_fn(|i| { + segments + .memory + .get_integer((segment_index, instance_addr_offset + i).into()) + .unwrap_or_default() + .into_owned() + }); + let mut batch = BTreeMap::::new(); + let fetch_offset_and_words = |var_index: usize, + index_in_batch: usize| + -> (usize, [Felt252; N_WORDS]) { + let offset = segments + .memory + .get_usize((offsets_ptr + (3 * index_in_batch + var_index)).unwrap_or_default()) + .unwrap_or_default(); + let words: [Felt252; N_WORDS] = core::array::from_fn(|i| { + segments + .memory + .get_integer((values_ptr + (offset + i)).unwrap_or_default()) + .unwrap_or_default() + .into_owned() + }); + (offset, words) + }; + for index_in_batch in 0..self.batch_size() { + let (a_offset, a_values) = fetch_offset_and_words(0, index_in_batch); + let (b_offset, b_values) = fetch_offset_and_words(1, index_in_batch); + let (c_offset, c_values) = fetch_offset_and_words(2, index_in_batch); + batch.insert( + index_in_batch, + ModInputMemoryVars { + a_offset, + b_offset, + c_offset, + a0: a_values[0], + a1: a_values[1], + a2: a_values[2], + a3: a_values[3], + b0: b_values[0], + b1: b_values[1], + b2: b_values[2], + b3: b_values[3], + c0: c_values[0], + c1: c_values[1], + c2: c_values[2], + c3: c_values[3], + }, + ); + } + instances.push(ModInputInstance { + index: instance, + p0: p_values[0], + p1: p_values[1], + p2: p_values[2], + p3: p_values[3], + values_ptr: relocate_address(values_ptr, &relocation_table).unwrap_or_default(), + offsets_ptr: relocate_address(offsets_ptr, &relocation_table).unwrap_or_default(), + n, + batch, + }); + } + + vec![PrivateInput::Mod(ModInput { + instances, + zero_value_address: relocation_table + .get(self.zero_segment_index) + .cloned() + .unwrap_or_default(), + })] + } + + // Reads N_WORDS from memory, starting at address=addr. + // Returns the words and the value if all words are in memory. + // Verifies that all words are integers and are bounded by 2**self.instance_def.word_bit_len. + fn read_n_words_value( + &self, + memory: &Memory, + addr: Relocatable, + ) -> Result<([Felt252; N_WORDS], Option), RunnerError> { + let mut words = Default::default(); + let mut value = BigUint::zero(); + for i in 0..N_WORDS { + let addr_i = (addr + i)?; + match memory.get(&addr_i).map(Cow::into_owned) { + None => return Ok((words, None)), + Some(MaybeRelocatable::RelocatableValue(_)) => { + return Err(MemoryError::ExpectedInteger(Box::new(addr_i)).into()) + } + Some(MaybeRelocatable::Int(word)) => { + let biguint_word = word.to_biguint(); + if biguint_word >= self.shift { + return Err(RunnerError::WordExceedsModBuiltinWordBitLen(Box::new(( + addr_i, + self.instance_def.word_bit_len, + word, + )))); + } + words[i] = word; + value += biguint_word * &self.shift_powers[i]; + } + } + } + Ok((words, Some(value))) + } + + // Reads the inputs to the builtin (see Inputs) from the memory at address=addr. + // Returns a struct with the inputs. Asserts that it exists in memory. + // Returns also the value of p, not just its words. + fn read_inputs(&self, memory: &Memory, addr: Relocatable) -> Result { + let values_ptr = memory.get_relocatable((addr + VALUES_PTR_OFFSET)?)?; + let offsets_ptr = memory.get_relocatable((addr + OFFSETS_PTR_OFFSET)?)?; + let n = memory.get_usize((addr + N_OFFSET)?)?; + if n < 1 { + return Err(RunnerError::ModBuiltinNLessThanOne(Box::new(( + self.name(), + n, + )))); + } + let (p_values, p) = self.read_n_words_value(memory, addr)?; + let p = p.ok_or_else(|| { + RunnerError::ModBuiltinMissingValue(Box::new(( + self.name(), + (addr + N_WORDS).unwrap_or_default(), + ))) + })?; + Ok(Inputs { + p, + p_values, + values_ptr, + offsets_ptr, + n, + }) + } + + // Reads the memory variables to the builtin (see MEMORY_VARS) from the memory given + // the inputs (specifically, values_ptr and offsets_ptr). + // Computes and returns the values of a, b, and c. + fn read_memory_vars( + &self, + memory: &Memory, + values_ptr: Relocatable, + offsets_ptr: Relocatable, + index_in_batch: usize, + ) -> Result<(BigUint, BigUint, BigUint), RunnerError> { + let compute_value = |index: usize| -> Result { + let offset = memory.get_usize((offsets_ptr + (index + 3 * index_in_batch))?)?; + let value_addr = (values_ptr + offset)?; + let (_, value) = self.read_n_words_value(memory, value_addr)?; + let value = value.ok_or_else(|| { + RunnerError::ModBuiltinMissingValue(Box::new(( + self.name(), + (value_addr + N_WORDS).unwrap_or_default(), + ))) + })?; + Ok(value) + }; + + let a = compute_value(0)?; + let b = compute_value(1)?; + let c = compute_value(2)?; + Ok((a, b, c)) + } + + fn fill_inputs( + &self, + memory: &mut Memory, + builtin_ptr: Relocatable, + inputs: &Inputs, + ) -> Result<(), RunnerError> { + if inputs.n > FILL_MEMORY_MAX { + return Err(RunnerError::FillMemoryMaxExceeded(Box::new(( + self.name(), + FILL_MEMORY_MAX, + )))); + } + let n_instances = safe_div_usize(inputs.n, self.instance_def.batch_size)?; + for instance in 1..n_instances { + let instance_ptr = (builtin_ptr + instance * INPUT_CELLS)?; + for i in 0..N_WORDS { + memory.insert_as_accessed((instance_ptr + i)?, &inputs.p_values[i])?; + } + memory.insert_as_accessed((instance_ptr + VALUES_PTR_OFFSET)?, &inputs.values_ptr)?; + memory.insert_as_accessed( + (instance_ptr + OFFSETS_PTR_OFFSET)?, + (inputs.offsets_ptr + (3 * instance * self.instance_def.batch_size))?, + )?; + memory.insert_as_accessed( + (instance_ptr + N_OFFSET)?, + inputs + .n + .saturating_sub(instance * self.instance_def.batch_size), + )?; + } + Ok(()) + } + + // Copies the first offsets in the offsets table to its end, n_copies times. + fn fill_offsets( + &self, + memory: &mut Memory, + offsets_ptr: Relocatable, + index: usize, + n_copies: usize, + ) -> Result<(), RunnerError> { + if n_copies.is_zero() { + return Ok(()); + } + for i in 0..3_usize { + let addr = (offsets_ptr + i)?; + let offset = memory + .get(&((offsets_ptr + i)?)) + .ok_or_else(|| MemoryError::UnknownMemoryCell(Box::new(addr)))? + .into_owned(); + for copy_i in 0..n_copies { + memory.insert_as_accessed((offsets_ptr + (3 * (index + copy_i) + i))?, &offset)?; + } + } + Ok(()) + } + + // Given a value, writes its n_words to memory, starting at address=addr. + fn write_n_words_value( + &self, + memory: &mut Memory, + addr: Relocatable, + value: BigUint, + ) -> Result<(), RunnerError> { + let mut value = value; + for i in 0..N_WORDS { + let word = value.mod_floor(&self.shift); + memory.insert_as_accessed((addr + i)?, Felt252::from(word))?; + value = value.div_floor(&self.shift) + } + if !value.is_zero() { + return Err(RunnerError::WriteNWordsValueNotZero(self.name())); + } + Ok(()) + } + + // Fills a value in the values table, if exactly one value is missing. + // Returns true on success or if all values are already known. + fn fill_value( + &self, + memory: &mut Memory, + inputs: &Inputs, + index: usize, + op: &Operation, + inv_op: &Operation, + ) -> Result { + let mut addresses = Vec::new(); + let mut values = Vec::new(); + for i in 0..3 { + let addr = (inputs.values_ptr + + memory + .get_integer((inputs.offsets_ptr + (3 * index + i))?)? + .as_ref())?; + addresses.push(addr); + let (_, value) = self.read_n_words_value(memory, addr)?; + values.push(value) + } + let (a, b, c) = (&values[0], &values[1], &values[2]); + match (a, b, c) { + // Deduce c from a and b and write it to memory. + (Some(a), Some(b), None) => { + let value = apply_op(a, b, op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[2], value)?; + Ok(true) + } + // Deduce b from a and c and write it to memory. + (Some(a), None, Some(c)) => { + let value = apply_op(c, a, inv_op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[1], value)?; + Ok(true) + } + // Deduce a from b and c and write it to memory. + (None, Some(b), Some(c)) => { + let value = apply_op(c, b, inv_op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[0], value)?; + Ok(true) + } + // All values are already known. + (Some(_), Some(_), Some(_)) => Ok(true), + _ => Ok(false), + } + } + + /// NOTE: It is advisable to use VirtualMachine::mod_builtin_fill_memory instead of this method directly + /// when implementing hints to avoid cloning the runners + + /// Fills the memory with inputs to the builtin instances based on the inputs to the + /// first instance, pads the offsets table to fit the number of operations writen in the + /// input to the first instance, and caculates missing values in the values table. + + /// For each builtin, the given tuple is of the form (builtin_ptr, builtin_runner, n), + /// where n is the number of operations in the offsets table (i.e., the length of the + /// offsets table is 3*n). + + /// The number of operations written to the input of the first instance n' should be at + /// least n and a multiple of batch_size. Previous offsets are copied to the end of the + /// offsets table to make its length 3n'. + pub fn fill_memory( + memory: &mut Memory, + add_mod: Option<(Relocatable, &ModBuiltinRunner, usize)>, + mul_mod: Option<(Relocatable, &ModBuiltinRunner, usize)>, + ) -> Result<(), RunnerError> { + if add_mod.is_none() && mul_mod.is_none() { + return Err(RunnerError::FillMemoryNoBuiltinSet); + } + // Check that the instance definitions of the builtins are the same. + if let (Some((_, add_mod, _)), Some((_, mul_mod, _))) = (add_mod, mul_mod) { + if add_mod.instance_def.word_bit_len != mul_mod.instance_def.word_bit_len { + return Err(RunnerError::ModBuiltinsMismatchedInstanceDef); + } + } + // Fill the inputs to the builtins. + let (add_mod_inputs, add_mod_n) = + if let Some((add_mod_addr, add_mod, add_mod_index)) = add_mod { + let add_mod_inputs = add_mod.read_inputs(memory, add_mod_addr)?; + add_mod.fill_inputs(memory, add_mod_addr, &add_mod_inputs)?; + add_mod.fill_offsets( + memory, + add_mod_inputs.offsets_ptr, + add_mod_index, + add_mod_inputs.n.saturating_sub(add_mod_index), + )?; + (add_mod_inputs, add_mod_index) + } else { + Default::default() + }; + + let (mul_mod_inputs, mul_mod_n) = + if let Some((mul_mod_addr, mul_mod, mul_mod_index)) = mul_mod { + let mul_mod_inputs = mul_mod.read_inputs(memory, mul_mod_addr)?; + mul_mod.fill_inputs(memory, mul_mod_addr, &mul_mod_inputs)?; + mul_mod.fill_offsets( + memory, + mul_mod_inputs.offsets_ptr, + mul_mod_index, + mul_mod_inputs.n.saturating_sub(mul_mod_index), + )?; + (mul_mod_inputs, mul_mod_index) + } else { + Default::default() + }; + + // Get one of the builtin runners - the rest of this function doesn't depend on batch_size. + let mod_runner = if let Some((_, add_mod, _)) = add_mod { + add_mod + } else { + mul_mod.unwrap().1 + }; + // Fill the values table. + let mut add_mod_index = 0; + let mut mul_mod_index = 0; + // Create operation here to avoid cloning p in the loop + let div_operation = Operation::DivMod(mul_mod_inputs.p.clone()); + while add_mod_index < add_mod_n || mul_mod_index < mul_mod_n { + if add_mod_index < add_mod_n + && mod_runner.fill_value( + memory, + &add_mod_inputs, + add_mod_index, + &Operation::Add, + &Operation::Sub, + )? + { + add_mod_index += 1; + } else if mul_mod_index < mul_mod_n + && mod_runner.fill_value( + memory, + &mul_mod_inputs, + mul_mod_index, + &Operation::Mul, + &div_operation, + )? + { + mul_mod_index += 1; + } else { + return Err(RunnerError::FillMemoryCoudNotFillTable( + add_mod_index, + mul_mod_index, + )); + } + } + Ok(()) + } + + // Additional checks added to the standard builtin runner security checks + pub(crate) fn run_additional_security_checks( + &self, + vm: &VirtualMachine, + ) -> Result<(), VirtualMachineError> { + let segment_size = vm + .get_segment_used_size(self.base) + .ok_or(MemoryError::MissingSegmentUsedSizes)?; + let n_instances = div_ceil(segment_size, INPUT_CELLS); + let mut prev_inputs = Inputs::default(); + for instance in 0..n_instances { + let inputs = self.read_inputs( + &vm.segments.memory, + (self.base as isize, instance * INPUT_CELLS).into(), + )?; + if !instance.is_zero() && prev_inputs.n > self.instance_def.batch_size { + for i in 0..N_WORDS { + if inputs.p_values[i] != prev_inputs.p_values[i] { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.p_values[i] != prev_inputs.p_values[i]. Got: i={}, inputs.p_values[i]={}, prev_inputs.p_values[i]={}", + i, inputs.p_values[i], prev_inputs.p_values[i])))).into()); + } + } + if inputs.values_ptr != prev_inputs.values_ptr { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.values_ptr != prev_inputs.values_ptr. Got: inputs.values_ptr={}, prev_inputs.values_ptr={}", + inputs.values_ptr, prev_inputs.values_ptr)))).into()); + } + if inputs.offsets_ptr + != (prev_inputs.offsets_ptr + (3 * self.instance_def.batch_size))? + { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.offsets_ptr != prev_inputs.offsets_ptr + 3 * batch_size. Got: inputs.offsets_ptr={}, prev_inputs.offsets_ptr={}, batch_size={}", + inputs.values_ptr, prev_inputs.values_ptr, self.instance_def.batch_size)))).into()); + } + if inputs.n != prev_inputs.n.saturating_sub(self.instance_def.batch_size) { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.n != prev_inputs.n - batch_size. Got: inputs.n={}, prev_inputs.n={}, batch_size={}", + inputs.n, prev_inputs.n, self.instance_def.batch_size)))).into()); + } + } + for index_in_batch in 0..self.instance_def.batch_size { + let (a, b, c) = self.read_memory_vars( + &vm.segments.memory, + inputs.values_ptr, + inputs.offsets_ptr, + index_in_batch, + )?; + let op = match self.builtin_type { + ModBuiltinType::Add => Operation::Add, + ModBuiltinType::Mul => Operation::Mul, + }; + let a_op_b = apply_op(&a, &b, &op)?.mod_floor(&inputs.p); + if a_op_b != c.mod_floor(&inputs.p) { + // Build error string + let p = inputs.p; + let error_string = format!("Expected a {op} b == c (mod p). Got: instance={instance}, batch={index_in_batch}, p={p}, a={a}, b={b}, c={c}."); + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new(( + self.name(), + error_string, + ))) + .into()); + } + } + prev_inputs = inputs; + } + if !n_instances.is_zero() && prev_inputs.n != self.instance_def.batch_size { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new(( + self.name(), + format!( + "prev_inputs.n != batch_size Got: prev_inputs.n={}, batch_size={}", + prev_inputs.n, self.instance_def.batch_size + ), + ))) + .into()); + } + Ok(()) + } + + #[cfg(test)] + #[cfg(feature = "mod_builtin")] + // Testing method used to test programs that use parameters which are not included in any layout + // For example, programs with large batch size + pub(crate) fn override_layout_params(&mut self, batch_size: usize, word_bit_len: u32) { + self.instance_def.batch_size = batch_size; + self.instance_def.word_bit_len = word_bit_len; + self.shift = BigUint::one().shl(word_bit_len); + self.shift_powers = core::array::from_fn(|i| self.shift.pow(i as u32)); + self.zero_segment_size = core::cmp::max(N_WORDS, batch_size * 3); + } +} + +fn apply_op(lhs: &BigUint, rhs: &BigUint, op: &Operation) -> Result { + Ok(match op { + Operation::Mul => lhs * rhs, + Operation::Add => lhs + rhs, + Operation::Sub => lhs - rhs, + Operation::DivMod(ref p) => div_mod_unsigned(lhs, rhs, p)?, + }) +} + +#[cfg(test)] +mod tests { + + #[test] + #[cfg(feature = "mod_builtin")] + fn test_air_private_input_small_batch_size() { + use super::*; + use crate::{ + air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, + utils::test_utils::Program, + vm::runners::{ + builtin_runner::{BuiltinRunner, ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME}, + cairo_runner::CairoRunner, + }, + Felt252, + }; + + let program_data = include_bytes!( + "../../../../../cairo_programs/mod_builtin_feature/proof/mod_builtin.json" + ); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let program = Program::from_bytes(program_data, Some("main")).unwrap(); + let mut runner = CairoRunner::new(&program, "all_cairo", true).unwrap(); + + let mut vm = VirtualMachine::new(false); + let end = runner.initialize(&mut vm, false).unwrap(); + // Modify add_mod & mul_mod params + for runner in vm.get_builtin_runners_as_mut() { + if let BuiltinRunner::Mod(runner) = runner { + runner.override_layout_params(1, 3) + } + } + + runner + .run_until_pc(end, &mut vm, &mut hint_processor) + .unwrap(); + runner + .run_for_steps(1, &mut vm, &mut hint_processor) + .unwrap(); + runner + .end_run(false, false, &mut vm, &mut hint_processor) + .unwrap(); + runner.read_return_values(&mut vm).unwrap(); + runner.finalize_segments(&mut vm).unwrap(); + + let air_private_input = runner.get_air_private_input(&vm); + assert_eq!( + air_private_input.0.get(ADD_MOD_BUILTIN_NAME).unwrap()[0], + PrivateInput::Mod(ModInput { + instances: vec![ + ModInputInstance { + index: 0, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18959, + n: 2, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 0, + a0: Felt252::ONE, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 12, + b0: Felt252::ZERO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 4, + c0: Felt252::TWO, + c1: Felt252::ONE, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 1, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18962, + n: 1, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 16, + a0: Felt252::ZERO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 20, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 24, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + } + ], + zero_value_address: 18027 + }) + ); + assert_eq!( + air_private_input.0.get(MUL_MOD_BUILTIN_NAME).unwrap()[0], + PrivateInput::Mod(ModInput { + instances: vec![ + ModInputInstance { + index: 0, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18965, + n: 3, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 12, + a0: Felt252::ZERO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 8, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 16, + c0: Felt252::ZERO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 1, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18968, + n: 2, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 0, + a0: Felt252::ONE, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 8, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 20, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 2, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18971, + n: 1, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 8, + a0: Felt252::TWO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 28, + b0: Felt252::ONE, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 24, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + } + ], + zero_value_address: 18027 + }) + ) + } +} diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index 853ba038d2..393b0b7c1d 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -6,7 +6,6 @@ use crate::vm::runners::cairo_pie::{ Attributes, BuiltinAdditionalData, OutputBuiltinAdditionalData, Pages, PublicMemoryPage, }; use crate::vm::vm_core::VirtualMachine; -use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use super::OUTPUT_BUILTIN_NAME; @@ -62,24 +61,10 @@ impl OutputBuiltinRunner { self.base } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - pub fn get_allocated_memory_units(&self, _vm: &VirtualMachine) -> Result { Ok(0) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -376,54 +361,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = OutputBuiltinRunner::new(true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -504,7 +441,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_add_validation_rule() { - let builtin = OutputBuiltinRunner::new(true); + let builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); let mut vm = vm!(); vm.segments = segments![ @@ -535,8 +472,8 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; - assert!(builtin.air_private_input(&memory).is_empty()); + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; + assert!(builtin.air_private_input(&segments).is_empty()); } #[test] diff --git a/vm/src/vm/runners/builtin_runner/poseidon.rs b/vm/src/vm/runners/builtin_runner/poseidon.rs index 3bc28ab228..d2a4ce9c18 100644 --- a/vm/src/vm/runners/builtin_runner/poseidon.rs +++ b/vm/src/vm/runners/builtin_runner/poseidon.rs @@ -110,10 +110,6 @@ impl PoseidonBuiltinRunner { Ok(self.cache.borrow().get(&address).map(|x| x.into())) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -128,43 +124,6 @@ impl PoseidonBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(POSEIDON_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(POSEIDON_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - POSEIDON_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - POSEIDON_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn air_private_input(&self, memory: &Memory) -> Vec { let mut private_inputs = vec![]; if let Some(segment) = memory.data.get(self.base) { @@ -231,7 +190,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -255,7 +214,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -283,7 +242,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), false); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -307,7 +266,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -443,7 +402,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(None, true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -458,7 +417,7 @@ mod tests { ((0, 11), 11) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::PoseidonState(PrivateInputPoseidonState { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index d70e74b9da..64d045f4e1 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -13,7 +13,7 @@ use crate::{ relocatable::{MaybeRelocatable, Relocatable}, }, vm::{ - errors::{memory_errors::MemoryError, runner_errors::RunnerError}, + errors::memory_errors::MemoryError, vm_memory::{ memory::{Memory, ValidationRule}, memory_segments::MemorySegmentManager, @@ -23,8 +23,6 @@ use crate::{ use num_traits::Zero; -use super::RANGE_CHECK_BUILTIN_NAME; - // NOTE: the current implementation is based on the bound 0x10000 const _INNER_RC_BOUND: u64 = 1u64 << INNER_RC_BOUND_SHIFT; const INNER_RC_BOUND_SHIFT: u64 = 16; @@ -107,18 +105,6 @@ impl RangeCheckBuiltinRunner { memory.add_validation_rule(self.base, rule); } - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -159,43 +145,6 @@ impl RangeCheckBuiltinRunner { self.get_used_cells(segments) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(RANGE_CHECK_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(RANGE_CHECK_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - RANGE_CHECK_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - RANGE_CHECK_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn air_private_input(&self, memory: &Memory) -> Vec { let mut private_inputs = vec![]; if let Some(segment) = memory.data.get(self.base) { @@ -214,6 +163,8 @@ mod tests { use super::*; use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; + use crate::vm::errors::runner_errors::RunnerError; + use crate::vm::runners::builtin_runner::RANGE_CHECK_BUILTIN_NAME; use crate::vm::vm_memory::memory::Memory; use crate::{ hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, @@ -242,7 +193,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -266,7 +217,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -294,7 +245,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, false); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, false).into(); let mut vm = vm!(); @@ -318,7 +269,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -453,54 +404,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_base() { @@ -608,9 +511,9 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(None, 4, true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)]; + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Value(PrivateInputValue { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/segment_arena.rs b/vm/src/vm/runners/builtin_runner/segment_arena.rs index 7f45f0e8f3..b505ac3813 100644 --- a/vm/src/vm/runners/builtin_runner/segment_arena.rs +++ b/vm/src/vm/runners/builtin_runner/segment_arena.rs @@ -1,7 +1,4 @@ -use crate::stdlib::boxed::Box; use crate::vm::errors::memory_errors::MemoryError; -use crate::vm::errors::runner_errors::RunnerError; -use crate::vm::vm_memory::memory::Memory; use crate::{ types::relocatable::{MaybeRelocatable, Relocatable}, vm::vm_memory::memory_segments::MemorySegmentManager, @@ -9,8 +6,7 @@ use crate::{ #[cfg(not(feature = "std"))] use alloc::vec::Vec; - -use super::SEGMENT_ARENA_BUILTIN_NAME; +use num_integer::div_ceil; const ARENA_BUILTIN_SIZE: u32 = 3; // The size of the builtin segment at the time of its creation. @@ -19,7 +15,7 @@ const INITIAL_SEGMENT_SIZE: usize = ARENA_BUILTIN_SIZE as usize; #[derive(Debug, Clone)] pub struct SegmentArenaBuiltinRunner { base: Relocatable, - included: bool, + pub(crate) included: bool, pub(crate) cells_per_instance: u32, pub(crate) n_input_cells_per_instance: u32, pub(crate) stop_ptr: Option, @@ -65,60 +61,14 @@ impl SegmentArenaBuiltinRunner { } } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SEGMENT_ARENA_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SEGMENT_ARENA_BUILTIN_NAME)))?; - if self.base.segment_index != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - SEGMENT_ARENA_BUILTIN_NAME, - stop_pointer, - self.base.segment_index as usize, - )))); - } - let used = self.get_used_cells(segments).map_err(RunnerError::Memory)?; - if stop_pointer != (self.base + used)? { - return Err(RunnerError::InvalidStopPointer(Box::new(( - SEGMENT_ARENA_BUILTIN_NAME, - (self.base + used)?, - stop_pointer, - )))); - } - self.stop_ptr = Some(stop_pointer.offset); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(self.base.offset); - Ok(pointer) - } - } - pub fn get_used_instances( &self, segments: &MemorySegmentManager, ) -> Result { - self.get_used_cells(segments) - } - - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base.segment_index as usize, self.stop_ptr) - } - - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) + Ok(div_ceil( + self.get_used_cells(segments)?, + self.cells_per_instance as usize, + )) } pub fn base(&self) -> usize { @@ -142,8 +92,12 @@ fn gen_arg(segments: &mut MemorySegmentManager, data: &[MaybeRelocatable; 3]) -> #[cfg(test)] mod tests { use super::*; + use crate::vm::errors::runner_errors::RunnerError; + use crate::vm::runners::builtin_runner::SEGMENT_ARENA_BUILTIN_NAME; use crate::vm::vm_core::VirtualMachine; use crate::{relocatable, utils::test_utils::*, vm::runners::builtin_runner::BuiltinRunner}; + #[cfg(not(feature = "std"))] + use alloc::boxed::Box; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -186,7 +140,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = SegmentArenaBuiltinRunner::new(true); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); let mut vm = vm!(); @@ -213,7 +167,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_valid() { - let mut builtin = SegmentArenaBuiltinRunner::new(false); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(false).into(); let mut vm = vm!(); @@ -261,7 +215,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = SegmentArenaBuiltinRunner::new(true); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); let mut vm = vm!(); @@ -321,43 +275,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = SegmentArenaBuiltinRunner::new(true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let vm = vm!(); - - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![]),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![]),); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -411,8 +328,8 @@ mod tests { let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); let mut memory_segment_manager = MemorySegmentManager::new(); memory_segment_manager.segment_used_sizes = Some(vec![6]); - - assert_eq!(builtin.get_used_instances(&memory_segment_manager), Ok(3)); + // (SIZE(6) - INITIAL_SIZE(3)) / CELLS_PER_INSTANCE(3) + assert_eq!(builtin.get_used_instances(&memory_segment_manager), Ok(1)); } #[test] @@ -436,14 +353,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_add_validation_rule() { - let builtin = SegmentArenaBuiltinRunner::new(true); - let mut vm = vm!(); - builtin.add_validation_rule(&mut vm.segments.memory); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stackincluded_test() { @@ -501,7 +410,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; - assert!(builtin.air_private_input(&memory).is_empty()); + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; + assert!(builtin.air_private_input(&segments).is_empty()); } } diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index d854afa05a..3d5fa6ef45 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -12,7 +12,7 @@ use crate::{ relocatable::{MaybeRelocatable, Relocatable}, }, vm::{ - errors::{memory_errors::MemoryError, runner_errors::RunnerError}, + errors::memory_errors::MemoryError, vm_memory::{ memory::{Memory, ValidationRule}, memory_segments::MemorySegmentManager, @@ -33,8 +33,6 @@ lazy_static! { .unwrap(); } -use super::SIGNATURE_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct SignatureBuiltinRunner { pub(crate) included: bool, @@ -156,22 +154,10 @@ impl SignatureBuiltinRunner { memory.add_validation_rule(self.base, rule); } - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - pub fn ratio(&self) -> Option { self.ratio } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -186,43 +172,6 @@ impl SignatureBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SIGNATURE_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SIGNATURE_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - SIGNATURE_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - SIGNATURE_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_additional_data(&self) -> BuiltinAdditionalData { // Convert signatures to Felt tuple let signatures: HashMap = self @@ -281,8 +230,11 @@ mod tests { types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, utils::test_utils::*, vm::{ - errors::memory_errors::{InsufficientAllocatedCellsError, MemoryError}, - runners::builtin_runner::BuiltinRunner, + errors::{ + memory_errors::{InsufficientAllocatedCellsError, MemoryError}, + runner_errors::RunnerError, + }, + runners::builtin_runner::{BuiltinRunner, SIGNATURE_BUILTIN_NAME}, vm_core::VirtualMachine, vm_memory::{memory::Memory, memory_segments::MemorySegmentManager}, }, @@ -326,7 +278,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -350,7 +303,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -378,7 +332,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -399,63 +354,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -521,7 +419,8 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_test() { let memory = Memory::new(); - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); assert_eq!(result, Ok(None)); } @@ -540,23 +439,6 @@ mod tests { assert_eq!(builtin.base(), 0); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_get_memory_segment_addresses() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn deduce_memory_cell() { - let memory = Memory::new(); - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); - assert_eq!(result, Ok(None)); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_min_step_not_reached() { @@ -598,7 +480,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_invalid_stop_pointer() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (1, 0))]; assert_eq!( @@ -614,7 +497,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_no_used_instances() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (0, 0))]; assert_eq!( diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index af4048697a..f0482d4724 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -54,10 +54,12 @@ use num_integer::div_rem; use num_traits::{ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; +use super::builtin_runner::ModBuiltinRunner; use super::{ builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}, cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion}, }; +use crate::types::instance_definitions::mod_instance_def::ModInstanceDef; #[derive(Clone, Debug, Eq, PartialEq)] pub enum CairoArg { @@ -259,6 +261,8 @@ impl CairoRunner { BuiltinName::ec_op, BuiltinName::keccak, BuiltinName::poseidon, + BuiltinName::add_mod, + BuiltinName::mul_mod, ]; if !is_subsequence(&self.program.builtins, &builtin_ordered_list) { return Err(RunnerError::DisorderedBuiltins); @@ -329,6 +333,18 @@ impl CairoRunner { .push(PoseidonBuiltinRunner::new(instance_def.ratio, included).into()); } } + if let Some(instance_def) = self.layout.builtins.add_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::add_mod); + if included || self.is_proof_mode() { + builtin_runners.push(ModBuiltinRunner::new_add_mod(instance_def, included).into()); + } + } + if let Some(instance_def) = self.layout.builtins.mul_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::mul_mod); + if included || self.is_proof_mode() { + builtin_runners.push(ModBuiltinRunner::new_mul_mod(instance_def, included).into()); + } + } if !program_builtins.is_empty() && !allow_missing_builtins { return Err(RunnerError::NoBuiltinForInstance(Box::new(( program_builtins.iter().map(|n| n.name()).collect(), @@ -400,6 +416,14 @@ impl CairoRunner { .push(SegmentArenaBuiltinRunner::new(true).into()) } } + BuiltinName::add_mod => vm.builtin_runners.push( + ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(1), 1, 96), true) + .into(), + ), + BuiltinName::mul_mod => vm.builtin_runners.push( + ModBuiltinRunner::new_mul_mod(&ModInstanceDef::new(Some(1), 1, 96), true) + .into(), + ), } } @@ -1089,6 +1113,7 @@ impl CairoRunner { .finalize(Some(size), builtin_runner.base(), None) } } + vm.segments.finalize_zero_segment(); self.segments_finalized = true; Ok(()) } @@ -1413,10 +1438,7 @@ impl CairoRunner { pub fn get_air_private_input(&self, vm: &VirtualMachine) -> AirPrivateInput { let mut private_inputs = HashMap::new(); for builtin in vm.builtin_runners.iter() { - private_inputs.insert( - builtin.name(), - builtin.air_private_input(&vm.segments.memory), - ); + private_inputs.insert(builtin.name(), builtin.air_private_input(&vm.segments)); } AirPrivateInput(private_inputs) } diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index a1e74d87a4..afbf384ddf 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -19,7 +19,9 @@ use crate::{ exec_scope_errors::ExecScopeError, memory_errors::MemoryError, vm_errors::VirtualMachineError, }, - runners::builtin_runner::{BuiltinRunner, RangeCheckBuiltinRunner, SignatureBuiltinRunner}, + runners::builtin_runner::{ + BuiltinRunner, OutputBuiltinRunner, RangeCheckBuiltinRunner, SignatureBuiltinRunner, + }, trace::trace_entry::TraceEntry, vm_memory::memory_segments::MemorySegmentManager, }, @@ -32,7 +34,9 @@ use core::num::NonZeroUsize; use num_traits::{ToPrimitive, Zero}; use super::errors::runner_errors::RunnerError; -use super::runners::builtin_runner::{OutputBuiltinRunner, OUTPUT_BUILTIN_NAME}; +use super::runners::builtin_runner::{ + ModBuiltinRunner, ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, +}; const MAX_TRACEBACK_ENTRIES: u32 = 20; @@ -1121,6 +1125,53 @@ impl VirtualMachine { } } } + + /// Fetches add_mod & mul_mod builtins according to the optional arguments and executes `fill_memory` + /// Returns an error if either of this optional parameters is true but the corresponding builtin is not present + /// Verifies that both builtin's (if present) batch sizes match the batch_size arg if set + // This method is needed as running `fill_memory` direclty from outside the vm struct would require cloning the builtin runners to avoid double borrowing + pub fn mod_builtin_fill_memory( + &mut self, + add_mod_ptr_n: Option<(Relocatable, usize)>, + mul_mod_ptr_n: Option<(Relocatable, usize)>, + batch_size: Option, + ) -> Result<(), VirtualMachineError> { + let fetch_builtin_params = |mod_params: Option<(Relocatable, usize)>, + mod_name: &'static str| + -> Result< + Option<(Relocatable, &ModBuiltinRunner, usize)>, + VirtualMachineError, + > { + if let Some((ptr, n)) = mod_params { + let mod_builtin = self + .builtin_runners + .iter() + .find_map(|b| match b { + BuiltinRunner::Mod(b) if b.name() == mod_name => Some(b), + _ => None, + }) + .ok_or_else(|| VirtualMachineError::NoModBuiltin(mod_name))?; + if let Some(batch_size) = batch_size { + if mod_builtin.batch_size() != batch_size { + return Err(VirtualMachineError::ModBuiltinBatchSize(Box::new(( + mod_builtin.name(), + batch_size, + )))); + } + } + Ok(Some((ptr, mod_builtin, n))) + } else { + Ok(None) + } + }; + + ModBuiltinRunner::fill_memory( + &mut self.segments.memory, + fetch_builtin_params(add_mod_ptr_n, ADD_MOD_BUILTIN_NAME)?, + fetch_builtin_params(mul_mod_ptr_n, MUL_MOD_BUILTIN_NAME)?, + ) + .map_err(VirtualMachineError::RunnerError) + } } pub struct VirtualMachineBuilder { diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index f50d5c11cd..178387f504 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -1,5 +1,6 @@ use crate::stdlib::{borrow::Cow, collections::HashMap, fmt, prelude::*}; +use crate::types::errors::math_errors::MathError; use crate::vm::runners::cairo_pie::CairoPieMemory; use crate::Felt252; use crate::{ @@ -284,6 +285,14 @@ impl Memory { } } + /// Gets the value from memory address as a usize. + /// Returns an Error if the value at the memory address is missing not a Felt252, or can't be converted to usize. + pub fn get_usize(&self, key: Relocatable) -> Result { + let felt = self.get_integer(key)?.into_owned(); + felt.to_usize() + .ok_or_else(|| MemoryError::Math(MathError::Felt252ToUsizeConversion(Box::new(felt)))) + } + /// Gets the value from memory address as a Relocatable value. /// Returns an Error if the value at the memory address is missing or not a Relocatable. pub fn get_relocatable(&self, key: Relocatable) -> Result { @@ -516,6 +525,21 @@ impl Memory { .count(), ) } + + // Inserts a value into memory & inmediately marks it as accessed if insertion was succesful + // Used by ModBuiltinRunner, as it accesses memory outside of it's segment when operating + pub(crate) fn insert_as_accessed( + &mut self, + key: Relocatable, + val: V, + ) -> Result<(), MemoryError> + where + MaybeRelocatable: From, + { + self.insert(key, val)?; + self.mark_as_accessed(key); + Ok(()) + } } impl From<&Memory> for CairoPieMemory { diff --git a/vm/src/vm/vm_memory/memory_segments.rs b/vm/src/vm/vm_memory/memory_segments.rs index aa8709419b..f9886da1a5 100644 --- a/vm/src/vm/vm_memory/memory_segments.rs +++ b/vm/src/vm/vm_memory/memory_segments.rs @@ -282,8 +282,6 @@ impl MemorySegmentManager { .insert(segment_index, public_memory.cloned().unwrap_or_default()); } - // TODO: remove allow - #[allow(unused)] // Creates the zero segment if it wasn't previously created // Fills the segment with the value 0 until size is reached // Returns the index of the zero segment @@ -302,8 +300,6 @@ impl MemorySegmentManager { self.zero_segment_index } - // TODO: remove allow - #[allow(unused)] // Finalizes the zero segment and clears it's tracking data from the manager pub(crate) fn finalize_zero_segment(&mut self) { if !self.zero_segment_index.is_zero() { From ca3b8abf51a4dbc6682d2cc75c2bc4327c324f05 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:12:48 -0300 Subject: [PATCH 28/36] Add range_check96 builtin (#1698) * Admit both types of range_check in range_check_builtin_runner * Add range_check96 to layout + make bound a constant * Clippy * Add specialized constructors & remove n_parts field from instance def * Add changelog entry * fmt * Use const generic to differentiate between RangeCheck and RangeCheck96 * Fix * fmt * Update relocated ptr values to account for added segment * Fix --- CHANGELOG.md | 8 ++ cairo1-run/src/cairo_run.rs | 1 + fuzzer/Cargo.lock | 66 ++++------- .../builtin_hint_processor/math_utils.rs | 63 ++++------ .../squash_dict_utils.rs | 5 +- vm/src/serde/deserialize_program.rs | 3 + .../builtins_instance_def.rs | 17 ++- .../range_check_instance_def.rs | 44 +------ vm/src/utils.rs | 7 +- vm/src/vm/runners/builtin_runner/mod.rs | 106 +++++++++++++---- vm/src/vm/runners/builtin_runner/modulo.rs | 24 ++-- .../vm/runners/builtin_runner/range_check.rs | 109 +++++++++++------- vm/src/vm/runners/cairo_runner.rs | 36 ++++-- vm/src/vm/vm_core.rs | 5 +- vm/src/vm/vm_memory/memory.rs | 12 +- 15 files changed, 279 insertions(+), 227 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0471580ad1..91ef388a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ #### Upcoming Changes +* feat(BREAKING): Add range_check96 builtin[#1698](https://github.com/lambdaclass/cairo-vm/pull/1698) + * Add the new `range_check96` builtin to the `all_cairo` layout. + * `RangeCheckBuiltinRunner` changes: + * Remove field `n_parts`, replacing it with const generic `N_PARTS`. + * Remome `n_parts` argument form method `new`. + * Remove field `_bound`, replacing it with public method `bound`. + * Add public methods `name` & `n_parts`. + * feat(BREAKING): Add mod builtin [#1673](https://github.com/lambdaclass/cairo-vm/pull/1673) Main Changes: diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 47d1f5bbdb..8477eb7d88 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -499,6 +499,7 @@ fn create_entry_code( BuiltinName::keccak | BuiltinName::ecdsa | BuiltinName::output + | BuiltinName::range_check96 | BuiltinName::add_mod | BuiltinName::mul_mod => return fp_loc, }; diff --git a/fuzzer/Cargo.lock b/fuzzer/Cargo.lock index 7d61c3263c..b48dfec845 100644 --- a/fuzzer/Cargo.lock +++ b/fuzzer/Cargo.lock @@ -210,7 +210,7 @@ dependencies = [ [[package]] name = "cairo-vm" -version = "1.0.0-rc0" +version = "1.0.0-rc1" dependencies = [ "anyhow", "arbitrary", @@ -221,7 +221,6 @@ dependencies = [ "hex", "keccak", "lazy_static", - "mimalloc", "nom", "num-bigint", "num-integer", @@ -520,13 +519,26 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lambdaworks-crypto" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4c222d5b2fdc0faf702d3ab361d14589b097f40eac9dc550e27083483edc65" +dependencies = [ + "lambdaworks-math", + "serde", + "sha2", + "sha3", +] + [[package]] name = "lambdaworks-math" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6c4d0ddd1fcd235be5196b1bcc404f89ad3e911f4c190fa01459e05dbf40f8" +checksum = "9ee7dcab3968c71896b8ee4dc829147acc918cffe897af6265b1894527fe3add" dependencies = [ - "thiserror", + "serde", + "serde_json", ] [[package]] @@ -555,16 +567,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "libmimalloc-sys" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -614,15 +616,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mimalloc" -version = "0.1.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" -dependencies = [ - "libmimalloc-sys", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1078,12 +1071,13 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.0.6" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6b868f545d43b474c2c00e9349c489fdeb7ff17eb00cdf339744ac4cae0930" +checksum = "6d53160556d1f23425100f42b3230df747ea05763efee685a2cd939dfb640701" dependencies = [ "arbitrary", "bitvec", + "lambdaworks-crypto", "lambdaworks-math", "lazy_static", "num-bigint", @@ -1132,26 +1126,6 @@ version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" -[[package]] -name = "thiserror" -version = "1.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "thiserror-impl-no-std" version = "2.0.2" diff --git a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs index fdfffb4485..a0fa71dddb 100644 --- a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -45,13 +45,9 @@ pub fn is_nn( ap_tracking: &ApTracking, ) -> Result<(), HintError> { let a = get_integer_from_var_name("a", vm, ids_data, ap_tracking)?; - let range_check_builtin = vm.get_range_check_builtin()?; + let range_check_bound = vm.get_range_check_builtin()?.bound(); //Main logic (assert a is not negative and within the expected range) - let value = match &range_check_builtin._bound { - Some(bound) if a.as_ref() >= bound => Felt252::ONE, - _ => Felt252::ZERO, - }; - insert_value_into_ap(vm, value) + insert_value_into_ap(vm, Felt252::from(a.as_ref() >= range_check_bound)) } //Implements hint: memory[ap] = 0 if 0 <= ((-ids.a - 1) % PRIME) < range_check_builtin.bound else 1 @@ -62,15 +58,9 @@ pub fn is_nn_out_of_range( ) -> Result<(), HintError> { let a = get_integer_from_var_name("a", vm, ids_data, ap_tracking)?; let a = a.as_ref(); - let range_check_builtin = vm.get_range_check_builtin()?; + let range_check_bound = vm.get_range_check_builtin()?.bound(); //Main logic (assert a is not negative and within the expected range) - //let value = if (-a - 1usize).mod_floor(vm.get_prime()) < range_check_builtin._bound { - let value = match &range_check_builtin._bound { - Some(bound) if Felt252::ZERO - (a + 1u64) < *bound => Felt252::ZERO, - None => Felt252::ZERO, - _ => Felt252::ONE, - }; - insert_value_into_ap(vm, value) + insert_value_into_ap(vm, Felt252::from(-(a + 1) >= *range_check_bound)) } /* Implements hint:from starkware.cairo.common.math_utils import assert_integer %{ @@ -178,8 +168,8 @@ pub fn assert_le_felt_v_0_8( if a > b { return Err(HintError::NonLeFelt252(Box::new((*a, *b)))); } - let bound = vm.get_range_check_builtin()?._bound.unwrap_or_default(); - let small_inputs = Felt252::from((a < &bound && b - a < bound) as u8); + let bound = vm.get_range_check_builtin()?.bound(); + let small_inputs = Felt252::from((a < bound && b - a < *bound) as u8); insert_value_from_var_name("small_inputs", small_inputs, vm, ids_data, ap_tracking) } @@ -292,9 +282,10 @@ pub fn assert_nn( let range_check_builtin = vm.get_range_check_builtin()?; // assert 0 <= ids.a % PRIME < range_check_builtin.bound // as prime > 0, a % prime will always be > 0 - match &range_check_builtin._bound { - Some(bound) if a.as_ref() >= bound => Err(HintError::AssertNNValueOutOfRange(Box::new(a))), - _ => Ok(()), + if a.as_ref() >= range_check_builtin.bound() { + Err(HintError::AssertNNValueOutOfRange(Box::new(a))) + } else { + Ok(()) } } @@ -372,12 +363,9 @@ pub fn is_positive( // Avoid using abs so we don't allocate a new BigInt let (sign, abs_value) = value_as_int.into_parts(); //Main logic (assert a is positive) - match &range_check_builtin._bound { - Some(bound) if abs_value > bound.to_biguint() => { - return Err(HintError::ValueOutsideValidRange(Box::new(value))) - } - _ => {} - }; + if abs_value >= range_check_builtin.bound().to_biguint() { + return Err(HintError::ValueOutsideValidRange(Box::new(value))); + } let result = Felt252::from((sign == Sign::Plus) as u8); insert_value_from_var_name("is_positive", result, vm, ids_data, ap_tracking) @@ -460,7 +448,7 @@ pub fn signed_div_rem( let bound = get_integer_from_var_name("bound", vm, ids_data, ap_tracking)?; let builtin = vm.get_range_check_builtin()?; - let builtin_bound = &builtin._bound.unwrap_or(Felt252::MAX); + let builtin_bound = builtin.bound(); if div.is_zero() || div.as_ref() > &div_prime_by_bound(*builtin_bound)? { return Err(HintError::OutOfValidRange(Box::new((div, *builtin_bound)))); } @@ -511,22 +499,11 @@ pub fn unsigned_div_rem( ) -> Result<(), HintError> { let div = get_integer_from_var_name("div", vm, ids_data, ap_tracking)?; let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; - let builtin = vm.get_range_check_builtin()?; + let builtin_bound = vm.get_range_check_builtin()?.bound(); // Main logic - match &builtin._bound { - Some(builtin_bound) - if div.is_zero() || div.as_ref() > &div_prime_by_bound(*builtin_bound)? => - { - return Err(HintError::OutOfValidRange(Box::new((div, *builtin_bound)))); - } - None if div.is_zero() => { - return Err(HintError::OutOfValidRange(Box::new(( - div, - Felt252::ZERO - Felt252::ONE, - )))); - } - _ => {} + if div.is_zero() || div.as_ref() > &div_prime_by_bound(*builtin_bound)? { + return Err(HintError::OutOfValidRange(Box::new((div, *builtin_bound)))); } let (q, r) = value.div_rem(&(div).try_into().map_err(|_| MathError::DividedByZero)?); @@ -1807,9 +1784,9 @@ mod tests { //Initialize fp vm.run_context.fp = 6; //Insert ids into memory - let bound = vm.get_range_check_builtin().unwrap()._bound; + let bound = vm.get_range_check_builtin().unwrap().bound(); vm.segments = segments![((1, 3), (5)), ((1, 4), 10)]; - vm.insert_value((1, 5).into(), bound.unwrap()).unwrap(); + vm.insert_value((1, 5).into(), bound).unwrap(); //Create ids let ids_data = ids_data!["r", "biased_q", "range_check_ptr", "div", "value", "bound"]; //Execute the hint @@ -1817,7 +1794,7 @@ mod tests { assert_matches!( run_hint!(vm, ids_data, hint_code), Err(HintError::OutOfValidRange(bx)) - if *bx == (bound.unwrap(), builtin_bound.field_div(&Felt252::TWO.try_into().unwrap())) + if *bx == (*bound, builtin_bound.field_div(&Felt252::TWO.try_into().unwrap())) ) } diff --git a/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs b/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs index 06d385c1a6..23dc562dae 100644 --- a/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/squash_dict_utils.rs @@ -247,8 +247,7 @@ pub fn squash_dict( let ptr_diff = get_integer_from_var_name("ptr_diff", vm, ids_data, ap_tracking)?; let n_accesses = get_integer_from_var_name("n_accesses", vm, ids_data, ap_tracking)?; //Get range_check_builtin - let range_check_builtin = vm.get_range_check_builtin()?; - let range_check_bound = range_check_builtin._bound; + let range_check_bound = *vm.get_range_check_builtin()?.bound(); //Main Logic let ptr_diff = ptr_diff .to_usize() @@ -284,7 +283,7 @@ pub fn squash_dict( keys.sort(); keys.reverse(); //Are the keys used bigger than the range_check bound. - let big_keys = if keys[0] >= range_check_bound.unwrap() { + let big_keys = if keys[0] >= range_check_bound { Felt252::ONE } else { Felt252::ZERO diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index a849541d23..b897223f87 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -14,6 +14,7 @@ use crate::{ sync::Arc, }, utils::CAIRO_PRIME, + vm::runners::builtin_runner::RANGE_CHECK_96_BUILTIN_NAME, }; use crate::utils::PRIME_STR; @@ -56,6 +57,7 @@ pub enum BuiltinName { ec_op, poseidon, segment_arena, + range_check96, add_mod, mul_mod, } @@ -72,6 +74,7 @@ impl BuiltinName { BuiltinName::ec_op => EC_OP_BUILTIN_NAME, BuiltinName::poseidon => POSEIDON_BUILTIN_NAME, BuiltinName::segment_arena => SEGMENT_ARENA_BUILTIN_NAME, + BuiltinName::range_check96 => RANGE_CHECK_96_BUILTIN_NAME, BuiltinName::add_mod => ADD_MOD_BUILTIN_NAME, BuiltinName::mul_mod => MUL_MOD_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 1b21aa6da0..b54cd3c849 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -18,6 +18,7 @@ pub(crate) struct BuiltinsInstanceDef { pub(crate) ec_op: Option, pub(crate) keccak: Option, pub(crate) poseidon: Option, + pub(crate) range_check96: Option, pub(crate) add_mod: Option, pub(crate) mul_mod: Option, } @@ -33,6 +34,7 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + range_check96: None, add_mod: None, mul_mod: None, } @@ -48,6 +50,7 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + range_check96: None, add_mod: None, mul_mod: None, } @@ -63,6 +66,7 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + range_check96: None, add_mod: None, mul_mod: None, } @@ -78,6 +82,7 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + range_check96: None, add_mod: None, mul_mod: None, } @@ -87,12 +92,13 @@ impl BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, pedersen: Some(PedersenInstanceDef::new(Some(32), 1)), - range_check: Some(RangeCheckInstanceDef::new(Some(16), 8)), + range_check: Some(RangeCheckInstanceDef::new(Some(16))), ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), bitwise: Some(BitwiseInstanceDef::new(Some(64))), ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: None, poseidon: Some(PoseidonInstanceDef::default()), + range_check96: None, add_mod: None, mul_mod: None, } @@ -102,12 +108,13 @@ impl BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, pedersen: Some(PedersenInstanceDef::new(Some(32), 1)), - range_check: Some(RangeCheckInstanceDef::new(Some(16), 8)), + range_check: Some(RangeCheckInstanceDef::new(Some(16))), ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), bitwise: Some(BitwiseInstanceDef::new(Some(64))), ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::default()), + range_check96: None, add_mod: None, mul_mod: None, } @@ -123,6 +130,7 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: Some(PoseidonInstanceDef::new(Some(8))), + range_check96: None, add_mod: None, mul_mod: None, } @@ -138,6 +146,7 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::new(Some(256))), + range_check96: Some(RangeCheckInstanceDef::new(Some(8))), #[cfg(feature = "mod_builtin")] add_mod: Some(ModInstanceDef::new(Some(128), 1, 96)), #[cfg(feature = "mod_builtin")] @@ -159,6 +168,7 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::default()), keccak: None, poseidon: None, + range_check96: None, add_mod: None, mul_mod: None, } @@ -168,12 +178,13 @@ impl BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, pedersen: Some(PedersenInstanceDef::new(None, 4)), - range_check: Some(RangeCheckInstanceDef::new(None, 8)), + range_check: Some(RangeCheckInstanceDef::new(None)), ecdsa: Some(EcdsaInstanceDef::new(None)), bitwise: Some(BitwiseInstanceDef::new(None)), ec_op: Some(EcOpInstanceDef::new(None)), keccak: None, poseidon: None, + range_check96: None, #[cfg(feature = "mod_builtin")] add_mod: Some(ModInstanceDef::new(None, 1, 96)), #[cfg(feature = "mod_builtin")] diff --git a/vm/src/types/instance_definitions/range_check_instance_def.rs b/vm/src/types/instance_definitions/range_check_instance_def.rs index ad46df54c6..de9fa51da3 100644 --- a/vm/src/types/instance_definitions/range_check_instance_def.rs +++ b/vm/src/types/instance_definitions/range_check_instance_def.rs @@ -4,27 +4,15 @@ pub(crate) const CELLS_PER_RANGE_CHECK: u32 = 1; #[derive(Serialize, Debug, PartialEq)] pub(crate) struct RangeCheckInstanceDef { pub(crate) ratio: Option, - pub(crate) n_parts: u32, } impl RangeCheckInstanceDef { pub(crate) fn default() -> Self { - RangeCheckInstanceDef { - ratio: Some(8), - n_parts: 8, - } + RangeCheckInstanceDef { ratio: Some(8) } } - pub(crate) fn new(ratio: Option, n_parts: u32) -> Self { - RangeCheckInstanceDef { ratio, n_parts } - } - - pub(crate) fn _cells_per_builtin(&self) -> u32 { - CELLS_PER_RANGE_CHECK - } - - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - self.n_parts + pub(crate) fn new(ratio: Option) -> Self { + RangeCheckInstanceDef { ratio } } } @@ -35,37 +23,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = RangeCheckInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 8); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = RangeCheckInstanceDef::default(); - assert_eq!(builtin_instance._cells_per_builtin(), 1); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = RangeCheckInstanceDef { - ratio: Some(10), - n_parts: 10, - }; - assert_eq!(RangeCheckInstanceDef::new(Some(10), 10), builtin_instance); + let builtin_instance = RangeCheckInstanceDef { ratio: Some(10) }; + assert_eq!(RangeCheckInstanceDef::new(Some(10)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = RangeCheckInstanceDef { - ratio: Some(8), - n_parts: 8, - }; + let builtin_instance = RangeCheckInstanceDef { ratio: Some(8) }; assert_eq!(RangeCheckInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/utils.rs b/vm/src/utils.rs index 8432a5cd79..4ec902dcfb 100644 --- a/vm/src/utils.rs +++ b/vm/src/utils.rs @@ -236,8 +236,11 @@ pub mod test_utils { () => {{ let mut vm = VirtualMachine::new(false); vm.builtin_runners = vec![ - $crate::vm::runners::builtin_runner::RangeCheckBuiltinRunner::new(Some(8), 8, true) - .into(), + $crate::vm::runners::builtin_runner::RangeCheckBuiltinRunner::<8>::new( + Some(8), + true, + ) + .into(), ]; vm }}; diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index 9cc2778d11..e8f2e9502e 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -20,6 +20,7 @@ mod range_check; mod segment_arena; mod signature; +pub(crate) use self::range_check::{RC_N_PARTS_96, RC_N_PARTS_STANDARD}; pub use bitwise::BitwiseBuiltinRunner; pub use ec_op::EcOpBuiltinRunner; pub use hash::HashBuiltinRunner; @@ -37,6 +38,7 @@ use super::cairo_pie::BuiltinAdditionalData; pub const OUTPUT_BUILTIN_NAME: &str = "output_builtin"; pub const HASH_BUILTIN_NAME: &str = "pedersen_builtin"; pub const RANGE_CHECK_BUILTIN_NAME: &str = "range_check_builtin"; +pub const RANGE_CHECK_96_BUILTIN_NAME: &str = "range_check_96_builtin"; pub const SIGNATURE_BUILTIN_NAME: &str = "ecdsa_builtin"; pub const BITWISE_BUILTIN_NAME: &str = "bitwise_builtin"; pub const EC_OP_BUILTIN_NAME: &str = "ec_op_builtin"; @@ -60,7 +62,8 @@ pub enum BuiltinRunner { EcOp(EcOpBuiltinRunner), Hash(HashBuiltinRunner), Output(OutputBuiltinRunner), - RangeCheck(RangeCheckBuiltinRunner), + RangeCheck(RangeCheckBuiltinRunner), + RangeCheck96(RangeCheckBuiltinRunner), Keccak(KeccakBuiltinRunner), Signature(SignatureBuiltinRunner), Poseidon(PoseidonBuiltinRunner), @@ -79,6 +82,9 @@ impl BuiltinRunner { BuiltinRunner::RangeCheck(ref mut range_check) => { range_check.initialize_segments(segments) } + BuiltinRunner::RangeCheck96(ref mut range_check) => { + range_check.initialize_segments(segments) + } BuiltinRunner::Keccak(ref mut keccak) => keccak.initialize_segments(segments), BuiltinRunner::Signature(ref mut signature) => signature.initialize_segments(segments), BuiltinRunner::Poseidon(ref mut poseidon) => poseidon.initialize_segments(segments), @@ -96,6 +102,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.initial_stack(), BuiltinRunner::Output(ref output) => output.initial_stack(), BuiltinRunner::RangeCheck(ref range_check) => range_check.initial_stack(), + BuiltinRunner::RangeCheck96(ref range_check) => range_check.initial_stack(), BuiltinRunner::Keccak(ref keccak) => keccak.initial_stack(), BuiltinRunner::Signature(ref signature) => signature.initial_stack(), BuiltinRunner::Poseidon(ref poseidon) => poseidon.initial_stack(), @@ -189,6 +196,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.included, BuiltinRunner::Output(ref output) => output.included, BuiltinRunner::RangeCheck(ref range_check) => range_check.included, + BuiltinRunner::RangeCheck96(ref range_check) => range_check.included, BuiltinRunner::Keccak(ref keccak) => keccak.included, BuiltinRunner::Signature(ref signature) => signature.included, BuiltinRunner::Poseidon(ref poseidon) => poseidon.included, @@ -205,6 +213,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.base(), BuiltinRunner::Output(ref output) => output.base(), BuiltinRunner::RangeCheck(ref range_check) => range_check.base(), + BuiltinRunner::RangeCheck96(ref range_check) => range_check.base(), BuiltinRunner::Keccak(ref keccak) => keccak.base(), BuiltinRunner::Signature(ref signature) => signature.base(), BuiltinRunner::Poseidon(ref poseidon) => poseidon.base(), @@ -221,6 +230,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(hash) => hash.ratio(), BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) => None, BuiltinRunner::RangeCheck(range_check) => range_check.ratio(), + BuiltinRunner::RangeCheck96(range_check) => range_check.ratio(), BuiltinRunner::Keccak(keccak) => keccak.ratio(), BuiltinRunner::Signature(ref signature) => signature.ratio(), BuiltinRunner::Poseidon(poseidon) => poseidon.ratio(), @@ -231,6 +241,7 @@ impl BuiltinRunner { pub fn add_validation_rule(&self, memory: &mut Memory) { match *self { BuiltinRunner::RangeCheck(ref range_check) => range_check.add_validation_rule(memory), + BuiltinRunner::RangeCheck96(ref range_check) => range_check.add_validation_rule(memory), BuiltinRunner::Signature(ref signature) => signature.add_validation_rule(memory), BuiltinRunner::Poseidon(ref poseidon) => poseidon.add_validation_rule(memory), _ => {} @@ -263,6 +274,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.get_used_cells(segments), BuiltinRunner::Output(ref output) => output.get_used_cells(segments), BuiltinRunner::RangeCheck(ref range_check) => range_check.get_used_cells(segments), + BuiltinRunner::RangeCheck96(ref range_check) => range_check.get_used_cells(segments), BuiltinRunner::Keccak(ref keccak) => keccak.get_used_cells(segments), BuiltinRunner::Signature(ref signature) => signature.get_used_cells(segments), BuiltinRunner::Poseidon(ref poseidon) => poseidon.get_used_cells(segments), @@ -283,6 +295,9 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.get_used_instances(segments), BuiltinRunner::Output(ref output) => output.get_used_instances(segments), BuiltinRunner::RangeCheck(ref range_check) => range_check.get_used_instances(segments), + BuiltinRunner::RangeCheck96(ref range_check) => { + range_check.get_used_instances(segments) + } BuiltinRunner::Keccak(ref keccak) => keccak.get_used_instances(segments), BuiltinRunner::Signature(ref signature) => signature.get_used_instances(segments), BuiltinRunner::Poseidon(ref poseidon) => poseidon.get_used_instances(segments), @@ -296,6 +311,9 @@ impl BuiltinRunner { pub fn get_range_check_usage(&self, memory: &Memory) -> Option<(usize, usize)> { match self { BuiltinRunner::RangeCheck(ref range_check) => range_check.get_range_check_usage(memory), + BuiltinRunner::RangeCheck96(ref range_check) => { + range_check.get_range_check_usage(memory) + } _ => None, } } @@ -308,7 +326,11 @@ impl BuiltinRunner { match self { BuiltinRunner::RangeCheck(range_check) => { let (used_cells, _) = self.get_used_cells_and_allocated_size(vm)?; - Ok(used_cells * range_check.n_parts as usize) + Ok(used_cells * range_check.n_parts() as usize) + } + BuiltinRunner::RangeCheck96(range_check) => { + let (used_cells, _) = self.get_used_cells_and_allocated_size(vm)?; + Ok(used_cells * range_check.n_parts() as usize) } _ => Ok(0), } @@ -332,6 +354,7 @@ impl BuiltinRunner { BuiltinRunner::EcOp(builtin) => builtin.cells_per_instance, BuiltinRunner::Hash(builtin) => builtin.cells_per_instance, BuiltinRunner::RangeCheck(builtin) => builtin.cells_per_instance, + BuiltinRunner::RangeCheck96(builtin) => builtin.cells_per_instance, BuiltinRunner::Output(_) => 0, BuiltinRunner::Keccak(builtin) => builtin.cells_per_instance, BuiltinRunner::Signature(builtin) => builtin.cells_per_instance, @@ -347,6 +370,7 @@ impl BuiltinRunner { BuiltinRunner::EcOp(builtin) => builtin.n_input_cells, BuiltinRunner::Hash(builtin) => builtin.n_input_cells, BuiltinRunner::RangeCheck(builtin) => builtin.n_input_cells, + BuiltinRunner::RangeCheck96(builtin) => builtin.n_input_cells, BuiltinRunner::Output(_) => 0, BuiltinRunner::Keccak(builtin) => builtin.n_input_cells, BuiltinRunner::Signature(builtin) => builtin.n_input_cells, @@ -362,6 +386,7 @@ impl BuiltinRunner { BuiltinRunner::EcOp(builtin) => builtin.instances_per_component, BuiltinRunner::Hash(builtin) => builtin.instances_per_component, BuiltinRunner::RangeCheck(builtin) => builtin.instances_per_component, + BuiltinRunner::RangeCheck96(builtin) => builtin.instances_per_component, BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) => 1, BuiltinRunner::Keccak(builtin) => builtin.instances_per_component, BuiltinRunner::Signature(builtin) => builtin.instances_per_component, @@ -377,6 +402,7 @@ impl BuiltinRunner { BuiltinRunner::EcOp(_) => EC_OP_BUILTIN_NAME, BuiltinRunner::Hash(_) => HASH_BUILTIN_NAME, BuiltinRunner::RangeCheck(_) => RANGE_CHECK_BUILTIN_NAME, + BuiltinRunner::RangeCheck96(_) => RANGE_CHECK_96_BUILTIN_NAME, BuiltinRunner::Output(_) => OUTPUT_BUILTIN_NAME, BuiltinRunner::Keccak(_) => KECCAK_BUILTIN_NAME, BuiltinRunner::Signature(_) => SIGNATURE_BUILTIN_NAME, @@ -489,6 +515,7 @@ impl BuiltinRunner { pub fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { match self { BuiltinRunner::RangeCheck(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::RangeCheck96(builtin) => builtin.air_private_input(&segments.memory), BuiltinRunner::Bitwise(builtin) => builtin.air_private_input(&segments.memory), BuiltinRunner::Hash(builtin) => builtin.air_private_input(&segments.memory), BuiltinRunner::EcOp(builtin) => builtin.air_private_input(&segments.memory), @@ -507,6 +534,9 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref mut hash) => hash.stop_ptr = Some(stop_ptr), BuiltinRunner::Output(ref mut output) => output.stop_ptr = Some(stop_ptr), BuiltinRunner::RangeCheck(ref mut range_check) => range_check.stop_ptr = Some(stop_ptr), + BuiltinRunner::RangeCheck96(ref mut range_check) => { + range_check.stop_ptr = Some(stop_ptr) + } BuiltinRunner::Keccak(ref mut keccak) => keccak.stop_ptr = Some(stop_ptr), BuiltinRunner::Signature(ref mut signature) => signature.stop_ptr = Some(stop_ptr), BuiltinRunner::Poseidon(ref mut poseidon) => poseidon.stop_ptr = Some(stop_ptr), @@ -524,6 +554,7 @@ impl BuiltinRunner { BuiltinRunner::Hash(ref hash) => hash.stop_ptr, BuiltinRunner::Output(ref output) => output.stop_ptr, BuiltinRunner::RangeCheck(ref range_check) => range_check.stop_ptr, + BuiltinRunner::RangeCheck96(ref range_check) => range_check.stop_ptr, BuiltinRunner::Keccak(ref keccak) => keccak.stop_ptr, BuiltinRunner::Signature(ref signature) => signature.stop_ptr, BuiltinRunner::Poseidon(ref poseidon) => poseidon.stop_ptr, @@ -563,12 +594,18 @@ impl From for BuiltinRunner { } } -impl From for BuiltinRunner { - fn from(runner: RangeCheckBuiltinRunner) -> Self { +impl From> for BuiltinRunner { + fn from(runner: RangeCheckBuiltinRunner) -> Self { BuiltinRunner::RangeCheck(runner) } } +impl From> for BuiltinRunner { + fn from(runner: RangeCheckBuiltinRunner) -> Self { + BuiltinRunner::RangeCheck96(runner) + } +} + impl From for BuiltinRunner { fn from(runner: SignatureBuiltinRunner) -> Self { BuiltinRunner::Signature(runner) @@ -635,7 +672,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_range_check() { - let range_check = RangeCheckBuiltinRunner::new(Some(10), 10, true); + let range_check = RangeCheckBuiltinRunner::::new(Some(10), true); let builtin: BuiltinRunner = range_check.clone().into(); assert_eq!(range_check.n_input_cells, builtin.n_input_cells()) } @@ -683,7 +720,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_cells_per_instance_range_check() { - let range_check = RangeCheckBuiltinRunner::new(Some(10), 10, true); + let range_check = RangeCheckBuiltinRunner::::new(Some(10), true); let builtin: BuiltinRunner = range_check.clone().into(); assert_eq!(range_check.cells_per_instance, builtin.cells_per_instance()) } @@ -739,7 +776,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_name_range_check() { - let range_check = RangeCheckBuiltinRunner::new(Some(10), 10, true); + let range_check = RangeCheckBuiltinRunner::::new(Some(10), true); let builtin: BuiltinRunner = range_check.into(); assert_eq!(RANGE_CHECK_BUILTIN_NAME, builtin.name()) } @@ -909,7 +946,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_range_check_with_items() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(10), 12, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(10), true), + ); let mut vm = vm!(); @@ -996,7 +1035,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_range_check() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); let mut vm = vm!(); vm.current_step = 8; assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(1)); @@ -1048,7 +1089,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_range_check() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); let memory = memory![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)]; assert_eq!(builtin.get_range_check_usage(&memory), Some((0, 4))); } @@ -1139,7 +1182,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_range_check() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); assert_eq!(builtin.get_used_diluted_check_units(270, 7), 0); } @@ -1163,8 +1208,9 @@ mod tests { assert_eq!(hash_builtin.get_memory_segment_addresses(), (0, None),); let output_builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); assert_eq!(output_builtin.get_memory_segment_addresses(), (0, None),); - let range_check_builtin: BuiltinRunner = - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let range_check_builtin: BuiltinRunner = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); assert_eq!( range_check_builtin.get_memory_segment_addresses(), (0, None), @@ -1300,7 +1346,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_range_check_missing_memory_cells_with_offsets() { - let range_check_builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let range_check_builtin = + RangeCheckBuiltinRunner::::new(Some(8), true); let builtin: BuiltinRunner = range_check_builtin.into(); let mut vm = vm!(); @@ -1324,8 +1371,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_range_check_missing_memory_cells() { - let builtin: BuiltinRunner = - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let builtin: BuiltinRunner = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::< + RC_N_PARTS_STANDARD, + >::new(Some(8), true)); let mut vm = vm!(); vm.segments.memory = memory![((0, 1), 1)]; @@ -1341,7 +1389,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_range_check_empty() { - let range_check_builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let range_check_builtin = + RangeCheckBuiltinRunner::::new(Some(8), true); let builtin: BuiltinRunner = range_check_builtin.into(); @@ -1539,7 +1588,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_perm_range_check_units_range_check() { - let builtin_runner: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(8), 8, true).into(); + let builtin_runner: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(8), true).into(); let mut vm = vm!(); vm.current_step = 8; @@ -1560,8 +1610,9 @@ mod tests { assert_eq!(hash_builtin.ratio(), (Some(8)),); let output_builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); assert_eq!(output_builtin.ratio(), None,); - let range_check_builtin: BuiltinRunner = - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let range_check_builtin: BuiltinRunner = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); assert_eq!(range_check_builtin.ratio(), (Some(8)),); let keccak_builtin: BuiltinRunner = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); @@ -1615,8 +1666,9 @@ mod tests { let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); - let range_check_builtin: BuiltinRunner = - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, true)); + let range_check_builtin: BuiltinRunner = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(8), true), + ); assert_eq!(range_check_builtin.get_used_instances(&vm.segments), Ok(4)); } @@ -1631,7 +1683,10 @@ mod tests { BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), false)), BuiltinRunner::Hash(HashBuiltinRunner::new(Some(1), false)), BuiltinRunner::Output(OutputBuiltinRunner::new(false)), - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, false)), + BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::::new( + Some(8), + false, + )), BuiltinRunner::Keccak(KeccakBuiltinRunner::new( &KeccakInstanceDef::default(), false, @@ -1659,7 +1714,10 @@ mod tests { BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), false)), BuiltinRunner::Hash(HashBuiltinRunner::new(Some(1), false)), BuiltinRunner::Output(OutputBuiltinRunner::new(false)), - BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(8), 8, false)), + BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::::new( + Some(8), + false, + )), BuiltinRunner::Keccak(KeccakBuiltinRunner::new( &KeccakInstanceDef::default(), false, diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 8e74113096..dc4e69f8c1 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -739,8 +739,8 @@ mod tests { p1: Felt252::ONE, p2: Felt252::ZERO, p3: Felt252::ZERO, - values_ptr: 18927, - offsets_ptr: 18959, + values_ptr: 23023, + offsets_ptr: 23055, n: 2, batch: BTreeMap::from([( 0, @@ -769,8 +769,8 @@ mod tests { p1: Felt252::ONE, p2: Felt252::ZERO, p3: Felt252::ZERO, - values_ptr: 18927, - offsets_ptr: 18962, + values_ptr: 23023, + offsets_ptr: 23058, n: 1, batch: BTreeMap::from([( 0, @@ -794,7 +794,7 @@ mod tests { ),]) } ], - zero_value_address: 18027 + zero_value_address: 22123 }) ); assert_eq!( @@ -807,8 +807,8 @@ mod tests { p1: Felt252::ONE, p2: Felt252::ZERO, p3: Felt252::ZERO, - values_ptr: 18927, - offsets_ptr: 18965, + values_ptr: 23023, + offsets_ptr: 23061, n: 3, batch: BTreeMap::from([( 0, @@ -837,8 +837,8 @@ mod tests { p1: Felt252::ONE, p2: Felt252::ZERO, p3: Felt252::ZERO, - values_ptr: 18927, - offsets_ptr: 18968, + values_ptr: 23023, + offsets_ptr: 23064, n: 2, batch: BTreeMap::from([( 0, @@ -867,8 +867,8 @@ mod tests { p1: Felt252::ONE, p2: Felt252::ZERO, p3: Felt252::ZERO, - values_ptr: 18927, - offsets_ptr: 18971, + values_ptr: 23023, + offsets_ptr: 23067, n: 1, batch: BTreeMap::from([( 0, @@ -892,7 +892,7 @@ mod tests { ),]) } ], - zero_value_address: 18027 + zero_value_address: 22123 }) ) } diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index 64d045f4e1..cb02e79b46 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -21,47 +21,42 @@ use crate::{ }, }; -use num_traits::Zero; +use lazy_static::lazy_static; + +use super::{RANGE_CHECK_96_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME}; -// NOTE: the current implementation is based on the bound 0x10000 -const _INNER_RC_BOUND: u64 = 1u64 << INNER_RC_BOUND_SHIFT; const INNER_RC_BOUND_SHIFT: u64 = 16; const INNER_RC_BOUND_MASK: u64 = u16::MAX as u64; -// TODO: use constant instead of receiving as false parameter -const N_PARTS: u64 = 8; +pub const RC_N_PARTS_STANDARD: u64 = 8; +pub const RC_N_PARTS_96: u64 = 6; + +lazy_static! { + pub static ref BOUND_STANDARD: Felt252 = + Felt252::TWO.pow(INNER_RC_BOUND_SHIFT * RC_N_PARTS_STANDARD); + pub static ref BOUND_96: Felt252 = Felt252::TWO.pow(INNER_RC_BOUND_SHIFT * RC_N_PARTS_96); +} #[derive(Debug, Clone)] -pub struct RangeCheckBuiltinRunner { +pub struct RangeCheckBuiltinRunner { ratio: Option, base: usize, pub(crate) stop_ptr: Option, pub(crate) cells_per_instance: u32, pub(crate) n_input_cells: u32, - pub _bound: Option, pub(crate) included: bool, - pub(crate) n_parts: u32, pub(crate) instances_per_component: u32, } -impl RangeCheckBuiltinRunner { - pub fn new(ratio: Option, n_parts: u32, included: bool) -> RangeCheckBuiltinRunner { - let bound = Felt252::TWO.pow(16 * n_parts as u128); - let _bound = if n_parts != 0 && bound.is_zero() { - None - } else { - Some(bound) - }; - +impl RangeCheckBuiltinRunner { + pub fn new(ratio: Option, included: bool) -> RangeCheckBuiltinRunner { RangeCheckBuiltinRunner { ratio, base: 0, stop_ptr: None, cells_per_instance: CELLS_PER_RANGE_CHECK, n_input_cells: CELLS_PER_RANGE_CHECK, - _bound, included, - n_parts, instances_per_component: 1, } } @@ -86,8 +81,26 @@ impl RangeCheckBuiltinRunner { self.ratio } + pub fn name(&self) -> &'static str { + match N_PARTS { + RC_N_PARTS_96 => RANGE_CHECK_96_BUILTIN_NAME, + _ => RANGE_CHECK_BUILTIN_NAME, + } + } + + pub fn n_parts(&self) -> u64 { + N_PARTS + } + + pub fn bound(&self) -> &'static Felt252 { + match N_PARTS { + RC_N_PARTS_96 => &BOUND_96, + _ => &BOUND_STANDARD, + } + } + pub fn add_validation_rule(&self, memory: &mut Memory) { - let rule: ValidationRule = ValidationRule(Box::new( + let rule = ValidationRule(Box::new( |memory: &Memory, address: Relocatable| -> Result, MemoryError> { let num = memory .get_integer(address) @@ -130,7 +143,7 @@ impl RangeCheckBuiltinRunner { .rev() .map(move |i| ((digit >> (i * INNER_RC_BOUND_SHIFT)) & INNER_RC_BOUND_MASK)) }) - .take(self.n_parts as usize) + .take(N_PARTS as usize) .fold(rc_bounds, |mm, x| { (min(mm.0, x as usize), max(mm.1, x as usize)) }); @@ -182,7 +195,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_instances() { - let builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(10), true); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![1]); @@ -193,7 +206,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); + let mut builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), true).into(); let mut vm = vm!(); @@ -217,7 +231,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); + let mut builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), true).into(); let mut vm = vm!(); @@ -245,7 +260,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, false).into(); + let mut builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), false).into(); let mut vm = vm!(); @@ -269,7 +285,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); + let mut builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), true).into(); let mut vm = vm!(); @@ -295,7 +312,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_and_allocated_size_test() { - let builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); + let builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), true).into(); let mut vm = vm!(); @@ -341,7 +359,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units() { - let builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); + let builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(10), true).into(); let mut vm = vm!(); @@ -385,7 +404,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initialize_segments_for_range_check() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let mut segments = MemorySegmentManager::new(); builtin.initialize_segments(&mut segments); assert_eq!(builtin.base, 0); @@ -394,7 +413,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_initial_stack_for_range_check_with_base() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); builtin.base = 1; let initial_stack = builtin.initial_stack(); assert_eq!( @@ -407,21 +426,23 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_base() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); assert_eq!(builtin.base(), 0); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_ratio() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); assert_eq!(builtin.ratio(), Some(8)); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(256), true), + ); let vm = vm!(); assert_eq!( @@ -433,7 +454,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_empty() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(256), true), + ); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![0]); @@ -443,7 +466,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); + let builtin = BuiltinRunner::RangeCheck( + RangeCheckBuiltinRunner::::new(Some(256), true), + ); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); @@ -453,7 +478,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_succesful_a() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let memory = memory![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)]; assert_eq!(builtin.get_range_check_usage(&memory), Some((0, 4))); } @@ -461,7 +486,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_succesful_b() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let memory = memory![ ((0, 0), 1465218365), ((0, 1), 2134570341), @@ -474,7 +499,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_succesful_c() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let memory = memory![ ((0, 0), 634834751465218365_i64), ((0, 1), 42876922134570341_i64), @@ -489,7 +514,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_empty_memory() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let memory = Memory::new(); assert_eq!(builtin.get_range_check_usage(&memory), None); } @@ -498,7 +523,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_perm_range_check_units() { - let builtin_runner: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(8), 8, true).into(); + let builtin_runner: BuiltinRunner = + RangeCheckBuiltinRunner::::new(Some(8), true).into(); let mut vm = vm!(); vm.current_step = 8; @@ -509,7 +535,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_air_private_input() { - let builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(None, 4, true).into(); + let builtin: BuiltinRunner = + RangeCheckBuiltinRunner::::new(None, true).into(); let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)]; assert_eq!( diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index f0482d4724..914f0e32c5 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -56,7 +56,9 @@ use serde::{Deserialize, Serialize}; use super::builtin_runner::ModBuiltinRunner; use super::{ - builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}, + builtin_runner::{ + KeccakBuiltinRunner, PoseidonBuiltinRunner, RC_N_PARTS_96, RC_N_PARTS_STANDARD, + }, cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion}, }; use crate::types::instance_definitions::mod_instance_def::ModInstanceDef; @@ -261,6 +263,7 @@ impl CairoRunner { BuiltinName::ec_op, BuiltinName::keccak, BuiltinName::poseidon, + BuiltinName::range_check96, BuiltinName::add_mod, BuiltinName::mul_mod, ]; @@ -288,9 +291,8 @@ impl CairoRunner { let included = program_builtins.remove(&BuiltinName::range_check); if included || self.is_proof_mode() { builtin_runners.push( - RangeCheckBuiltinRunner::new( + RangeCheckBuiltinRunner::::new( instance_def.ratio, - instance_def.n_parts, included, ) .into(), @@ -333,6 +335,16 @@ impl CairoRunner { .push(PoseidonBuiltinRunner::new(instance_def.ratio, included).into()); } } + + if let Some(instance_def) = self.layout.builtins.range_check96.as_ref() { + let included = program_builtins.remove(&BuiltinName::range_check96); + if included || self.is_proof_mode() { + builtin_runners.push( + RangeCheckBuiltinRunner::::new(instance_def.ratio, included) + .into(), + ); + } + } if let Some(instance_def) = self.layout.builtins.add_mod.as_ref() { let included = program_builtins.remove(&BuiltinName::add_mod); if included || self.is_proof_mode() { @@ -388,9 +400,9 @@ impl CairoRunner { BuiltinName::pedersen => vm .builtin_runners .push(HashBuiltinRunner::new(Some(32), true).into()), - BuiltinName::range_check => vm - .builtin_runners - .push(RangeCheckBuiltinRunner::new(Some(1), 8, true).into()), + BuiltinName::range_check => vm.builtin_runners.push( + RangeCheckBuiltinRunner::::new(Some(1), true).into(), + ), BuiltinName::output => vm .builtin_runners .push(OutputBuiltinRunner::new(true).into()), @@ -416,6 +428,9 @@ impl CairoRunner { .push(SegmentArenaBuiltinRunner::new(true).into()) } } + BuiltinName::range_check96 => vm + .builtin_runners + .push(RangeCheckBuiltinRunner::::new(Some(1), true).into()), BuiltinName::add_mod => vm.builtin_runners.push( ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(1), 1, 96), true) .into(), @@ -4097,7 +4112,8 @@ mod tests { vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!( 0x80FF_8000_0530u64 )))]]; - vm.builtin_runners = vec![RangeCheckBuiltinRunner::new(Some(12), 5, true).into()]; + vm.builtin_runners = + vec![RangeCheckBuiltinRunner::::new(Some(12), true).into()]; assert_matches!( cairo_runner.get_perm_range_check_limits(&vm), @@ -4151,7 +4167,8 @@ mod tests { let cairo_runner = cairo_runner!(program); let mut vm = vm!(); - vm.builtin_runners = vec![RangeCheckBuiltinRunner::new(Some(8), 8, true).into()]; + vm.builtin_runners = + vec![RangeCheckBuiltinRunner::::new(Some(8), true).into()]; vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!( 0x80FF_8000_0530u64 )))]]; @@ -4218,7 +4235,8 @@ mod tests { let cairo_runner = cairo_runner!(program); let mut vm = vm!(); - vm.builtin_runners = vec![RangeCheckBuiltinRunner::new(Some(8), 8, true).into()]; + vm.builtin_runners = + vec![RangeCheckBuiltinRunner::::new(Some(8), true).into()]; vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!( 0x80FF_8000_0530u64 )))]]; diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index afbf384ddf..dc8818e81d 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -36,6 +36,7 @@ use num_traits::{ToPrimitive, Zero}; use super::errors::runner_errors::RunnerError; use super::runners::builtin_runner::{ ModBuiltinRunner, ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, + RC_N_PARTS_STANDARD, }; const MAX_TRACEBACK_ENTRIES: u32 = 20; @@ -926,7 +927,9 @@ impl VirtualMachine { self.segments.memory.get_integer_range(addr, size) } - pub fn get_range_check_builtin(&self) -> Result<&RangeCheckBuiltinRunner, VirtualMachineError> { + pub fn get_range_check_builtin( + &self, + ) -> Result<&RangeCheckBuiltinRunner, VirtualMachineError> { for builtin in &self.builtin_runners { if let BuiltinRunner::RangeCheck(range_check_builtin) = builtin { return Ok(range_check_builtin); diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index 178387f504..c776fa14d3 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -632,7 +632,9 @@ mod memory_tests { types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, utils::test_utils::*, vm::{ - runners::builtin_runner::{RangeCheckBuiltinRunner, SignatureBuiltinRunner}, + runners::builtin_runner::{ + RangeCheckBuiltinRunner, SignatureBuiltinRunner, RC_N_PARTS_STANDARD, + }, vm_memory::memory_segments::MemorySegmentManager, }, }; @@ -803,7 +805,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_range_check_within_bounds() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let mut segments = MemorySegmentManager::new(); builtin.initialize_segments(&mut segments); builtin.add_validation_rule(&mut segments.memory); @@ -828,7 +830,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_range_check_outside_bounds() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let mut segments = MemorySegmentManager::new(); segments.add(); builtin.initialize_segments(&mut segments); @@ -919,7 +921,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_range_check_relocatable_value() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let mut segments = MemorySegmentManager::new(); builtin.initialize_segments(&mut segments); segments.memory = memory![((0, 0), (0, 4))]; @@ -936,7 +938,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_range_check_out_of_bounds_diff_segment() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); + let mut builtin = RangeCheckBuiltinRunner::::new(Some(8), true); let mut segments = MemorySegmentManager::new(); segments.memory = Memory::new(); segments.add(); From 6de8f56fcb28f5e5c0669c747cc76ce34cfef6aa Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:21:54 -0300 Subject: [PATCH 29/36] fix: Use program builtins in `initialize_main_entrypoint` & `read_return_values` (#1703) * Ignore pesky pie zip files * Add zero segment * Handle zero segment when counting memory holes * Add Changelog entry * Fix macro * First draft of runner & instance_def * Complete struct * Impl initialize_segments + read_n_words_value * Implement read_inputs * Implement read_memory_vars * Impl fill_inputs * Impl fill_offsets * Impl write_n_words_value * Implement fill_value * Implement fill_memory * Integrate mod builtin under feature flag * Add test file * Impl hint * Push hint impl file * Add quick impl of run_additional_security_checks * Some fixes * Fixes * Fix bugs * Temp testing code * Refactor: Use a struct to represent inputs * Refactor: use a struct to represent memory vars * Refactor: Use a constant N_WORDS * Refactor: use fixed N_WORDS size arrays to ensure safe indexing * Store prime as NonZeroFelt * Refactor: Used a fixed-size array for shift_powers * Clippy * Clippy * Small improvments * Refactor: Use only offsets_ptr instead of all inputs in fill_offsets * Refactor(Simplification): Reduce the return type of read_memory_vars to only a b & c as the other values are not used * Fix(Refactor: Use BigUint to represent values as we have to accomodate values up to U384 * Refactor: Optimize fill_offsets loop so clippy does not complain about it * Add a fill_memory wrapper on the vm to avoid cloning the builtin runners * Fixes * Check batch size * Refactors: Reduce duplication in mod_builtin_fill_memory & combine loops in fill_value * Safety: Use saturating sub * Refactor: Avoid creating empty structs in fill_memory * Add integration test * Add error handling + integration tests * Fix bugged shift_powers creation * Improve error formatting * Simplify error string generation * Fix test * Add test using large batch size + hint impl for circuits using large batch size * Add test with large batch size * Fix hint impl + rename module * Improve error handling of run_additional_security_checks * Remove unused method BuiltinRunner::get_memory_accesses * Impl mark_accessed_memory * Mark cells created by fill_memory as accessed during execution * Remove mark_accessed_memory * Finalize zero segment * WIP: Move final_stack to enum impl * Remove unused methods from builtin runners * Remove commented code * Get rid of empty impls in builtin runners * Move get_memory_segment_addresses impl to enum impl * Impl builtin methods * Add placeholder value * Fix cells_per_instance * Remove temp testing code * Run modulo builtin security checks when running builtin security checks * Use realistic value for mod builtin ratio * Draft(private inputs) * Impl air_private_input * Update private input structure * Improve struct compatibility for serialization * Relocate zero_segment_address + Use BtreeMap keep batches sorted * Fix field names * Clippy * Remove unused attribute from modulo module + limit feature gated code * Fix test files EOF * Relocate ptrs when creating mod private input * Remove feature * Update requirements.txt * Update program + add changelog entry * Impl new hints * Adjust recursive_large_output params * Update tests * Remove allow(unused) * Push bench file * Add non-std imports * Remove unused func * Remove dead code * Box error contents to reduce errir size * Avoid needless to_string when reporting errors * Copy common lib modules so that we can run mod builtin tests in CI * Add mod_builtin to special features so we can run the tests in CI * Fix file * Fix get_used_instances for segment arena * Fix test * Add temp fix * Clippy * fmt * Add no-std import * Add no-std import * Push changelog * Push deleted comment * fmt * Fix typo * Compile with proof mode on CI + add air_private_input tests * Run proof mode integration tests * Push symlinks for convenience as these will be moved soon * Fix test * Fix test value * Fix test value * Fix * Use rea data in layoyts + adjust tests accordingly * Update air priv input tests value (Accounting for increased ratios in layout) * Remove feature mod_builtin * Revert "Remove feature mod_builtin" This reverts commit 5cc921c6e9200dbdc4ad45fa937305f0d9108dd6. * Remove dbg print * Fix read_return_values * Fix initialize_main_entrypoint & read_return_values * Add test * Fix OutputBuiltinRunner::get_public_memory * Fix air public input generation * Fix tests * Fix EOF * Add checks * Fix tests * Fix order * Add CHANGELOG entry * Fix * Re,ove unused import * Fix * Update CHANGELOG.md * Fix test * fmt * Fix --- CHANGELOG.md | 8 ++ cairo-vm-cli/src/main.rs | 4 - cairo_programs/pedersen_extra_builtins.cairo | 11 +++ vm/src/cairo_run.rs | 15 ++- vm/src/tests/cairo_run_test.rs | 50 ++++++++-- vm/src/vm/errors/runner_errors.rs | 4 + vm/src/vm/runners/builtin_runner/mod.rs | 17 ++++ vm/src/vm/runners/builtin_runner/modulo.rs | 10 +- vm/src/vm/runners/builtin_runner/output.rs | 14 +-- vm/src/vm/runners/cairo_runner.rs | 97 ++++++++++++++++---- vm/src/vm/vm_core.rs | 34 ------- 11 files changed, 188 insertions(+), 76 deletions(-) create mode 100644 cairo_programs/pedersen_extra_builtins.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ef388a56..3dac6b636f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ #### Upcoming Changes +* fix(BREAKING): Use program builtins in `initialize_main_entrypoint` & `read_return_values`[#1703](https://github.com/lambdaclass/cairo-vm/pull/1703) + * `initialize_main_entrypoint` now iterates over the program builtins when building the stack & inserts 0 for any missing builtin + * `read_return_values` now only computes the final stack of the builtins in the program + * BREAKING: `read_return_values` now takes a boolean argument `allow_missing_builtins` + * Added method `BuiltinRunner::identifier` to get the `BuiltinName` of each builtin + * BREAKING: `OutputBuiltinRunner::get_public_memory` now takes a reference to `MemorySegmentManager` + * BREAKING: method `VirtualMachine::get_memory_segment_addresses` moved to `CairoRunner::get_memory_segment_addresses` + * feat(BREAKING): Add range_check96 builtin[#1698](https://github.com/lambdaclass/cairo-vm/pull/1698) * Add the new `range_check96` builtin to the `all_cairo` layout. * `RangeCheckBuiltinRunner` changes: diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index f8fc6d0d62..ddb97c068a 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -338,7 +338,6 @@ mod tests { #[values(false, true)] memory_file: bool, #[values(false, true)] mut trace_file: bool, #[values(false, true)] proof_mode: bool, - #[values(false, true)] secure_run: bool, #[values(false, true)] print_output: bool, #[values(false, true)] entrypoint: bool, #[values(false, true)] air_public_input: bool, @@ -371,9 +370,6 @@ mod tests { if trace_file { args.extend_from_slice(&["--trace_file".to_string(), "/dev/null".to_string()]); } - if secure_run { - args.extend_from_slice(&["--secure_run".to_string(), "true".to_string()]); - } if print_output { args.extend_from_slice(&["--print_output".to_string()]); } diff --git a/cairo_programs/pedersen_extra_builtins.cairo b/cairo_programs/pedersen_extra_builtins.cairo new file mode 100644 index 0000000000..513ce93ddb --- /dev/null +++ b/cairo_programs/pedersen_extra_builtins.cairo @@ -0,0 +1,11 @@ +%builtins output pedersen range_check ecdsa bitwise ec_op + +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.cairo.common.hash import hash2 + +func main{output_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr, bitwise_ptr, ec_op_ptr}() { + let (seed) = hash2{hash_ptr=pedersen_ptr}(0, 0); + assert [output_ptr] = seed; + let output_ptr = output_ptr + 1; + return (); +} diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index b0b4a53c60..07c3a29c77 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -100,7 +100,7 @@ pub fn cairo_run_program( )?; vm.verify_auto_deductions()?; - cairo_runner.read_return_values(&mut vm)?; + cairo_runner.read_return_values(&mut vm, allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments(&mut vm)?; } @@ -135,6 +135,10 @@ pub fn cairo_run_fuzzed_program( .secure_run .unwrap_or(!cairo_run_config.proof_mode); + let allow_missing_builtins = cairo_run_config + .allow_missing_builtins + .unwrap_or(cairo_run_config.proof_mode); + let mut cairo_runner = CairoRunner::new( &program, cairo_run_config.layout, @@ -143,12 +147,7 @@ pub fn cairo_run_fuzzed_program( let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); - let _end = cairo_runner.initialize( - &mut vm, - cairo_run_config - .allow_missing_builtins - .unwrap_or(cairo_run_config.proof_mode), - )?; + let _end = cairo_runner.initialize(&mut vm, allow_missing_builtins)?; let res = match cairo_runner.run_until_steps(steps_limit, &mut vm, hint_executor) { Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), // program ran OK but ended before steps limit @@ -160,7 +159,7 @@ pub fn cairo_run_fuzzed_program( cairo_runner.end_run(false, false, &mut vm, hint_executor)?; vm.verify_auto_deductions()?; - cairo_runner.read_return_values(&mut vm)?; + cairo_runner.read_return_values(&mut vm, allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments(&mut vm)?; } diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index 60bb867a6e..bf44200dd3 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1071,6 +1071,40 @@ fn cairo_run_print_dict_array() { run_program_simple(program_data); } +#[test] +fn run_program_allow_missing_builtins() { + let program_data = include_bytes!("../../../cairo_programs/pedersen_extra_builtins.json"); + let config = CairoRunConfig { + allow_missing_builtins: Some(true), + layout: "small", // The program logic only uses builtins in the small layout but contains builtins outside of it + ..Default::default() + }; + assert!(crate::cairo_run::cairo_run( + program_data, + &config, + &mut BuiltinHintProcessor::new_empty() + ) + .is_ok()) +} + +#[test] +fn run_program_allow_missing_builtins_proof() { + let program_data = + include_bytes!("../../../cairo_programs/proof_programs/pedersen_extra_builtins.json"); + let config = CairoRunConfig { + proof_mode: true, + allow_missing_builtins: Some(true), + layout: "small", // The program logic only uses builtins in the small layout but contains builtins outside of it + ..Default::default() + }; + assert!(crate::cairo_run::cairo_run( + program_data, + &config, + &mut BuiltinHintProcessor::new_empty() + ) + .is_ok()) +} + #[test] #[cfg(feature = "mod_builtin")] fn cairo_run_mod_builtin() { @@ -1174,15 +1208,17 @@ fn run_program_with_custom_mod_builtin_params( .unwrap(); vm.verify_auto_deductions().unwrap(); - cairo_runner.read_return_values(&mut vm).unwrap(); + cairo_runner.read_return_values(&mut vm, false).unwrap(); if cairo_run_config.proof_mode { cairo_runner.finalize_segments(&mut vm).unwrap(); } - let security_res = verify_secure_runner(&cairo_runner, true, None, &mut vm); - if let Some(error) = security_error { - assert!(security_res.is_err()); - assert!(security_res.err().unwrap().to_string().contains(error)); - return; + if !cairo_run_config.proof_mode { + let security_res = verify_secure_runner(&cairo_runner, true, None, &mut vm); + if let Some(error) = security_error { + assert!(security_res.is_err()); + assert!(security_res.err().unwrap().to_string().contains(error)); + return; + } + security_res.unwrap(); } - security_res.unwrap(); } diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index ac59da83e9..b0961d69df 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -122,6 +122,10 @@ pub enum RunnerError { FillMemoryCoudNotFillTable(usize, usize), #[error("{}: {}", (*.0).0, (*.0).1)] ModBuiltinSecurityCheck(Box<(&'static str, String)>), + #[error("{0} is missing")] + MissingBuiltin(&'static str), + #[error("The stop pointer of the missing builtin {0} must be 0")] + MissingBuiltinStopPtrNotZero(&'static str), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index e8f2e9502e..75cc0c8983 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -1,5 +1,6 @@ use crate::air_private_input::PrivateInput; use crate::math_utils::safe_div_usize; +use crate::serde::deserialize_program::BuiltinName; use crate::stdlib::prelude::*; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::{self, InsufficientAllocatedCellsError, MemoryError}; @@ -412,6 +413,22 @@ impl BuiltinRunner { } } + pub fn identifier(&self) -> BuiltinName { + match self { + BuiltinRunner::Bitwise(_) => BuiltinName::bitwise, + BuiltinRunner::EcOp(_) => BuiltinName::ec_op, + BuiltinRunner::Hash(_) => BuiltinName::pedersen, + BuiltinRunner::RangeCheck(_) => BuiltinName::range_check, + BuiltinRunner::RangeCheck96(_) => BuiltinName::range_check96, + BuiltinRunner::Output(_) => BuiltinName::output, + BuiltinRunner::Keccak(_) => BuiltinName::keccak, + BuiltinRunner::Signature(_) => BuiltinName::ecdsa, + BuiltinRunner::Poseidon(_) => BuiltinName::poseidon, + BuiltinRunner::SegmentArena(_) => BuiltinName::segment_arena, + BuiltinRunner::Mod(b) => b.identifier(), + } + } + pub fn run_security_checks(&self, vm: &VirtualMachine) -> Result<(), VirtualMachineError> { if let BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) = self { return Ok(()); diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index dc4e69f8c1..924aa65a28 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -1,6 +1,7 @@ use crate::{ air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, math_utils::{div_mod_unsigned, safe_div_usize}, + serde::deserialize_program::BuiltinName, stdlib::{ borrow::Cow, collections::BTreeMap, @@ -117,6 +118,13 @@ impl ModBuiltinRunner { } } + pub fn identifier(&self) -> BuiltinName { + match self.builtin_type { + ModBuiltinType::Mul => BuiltinName::mul_mod, + ModBuiltinType::Add => BuiltinName::add_mod, + } + } + pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) { self.base = segments.add().segment_index as usize; // segments.add() always returns a positive index self.zero_segment_index = segments.add_zero_segment(self.zero_segment_size) @@ -725,7 +733,7 @@ mod tests { runner .end_run(false, false, &mut vm, &mut hint_processor) .unwrap(); - runner.read_return_values(&mut vm).unwrap(); + runner.read_return_values(&mut vm, false).unwrap(); runner.finalize_segments(&mut vm).unwrap(); let air_private_input = runner.get_air_private_input(&vm); diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index 393b0b7c1d..c6283d3756 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -164,10 +164,11 @@ impl OutputBuiltinRunner { Ok(()) } - pub fn get_public_memory(&self) -> Result, RunnerError> { - let size = self - .stop_ptr - .ok_or(RunnerError::NoStopPointer(Box::new(OUTPUT_BUILTIN_NAME)))?; + pub fn get_public_memory( + &self, + segments: &MemorySegmentManager, + ) -> Result, RunnerError> { + let size = self.get_used_cells(segments)?; let mut public_memory: Vec<(usize, usize)> = (0..size).map(|i| (i, 0)).collect(); for (page_id, page) in self.pages.iter() { @@ -590,9 +591,10 @@ mod tests { ) .unwrap(); - builtin.stop_ptr = Some(7); + let mut segments = MemorySegmentManager::new(); + segments.segment_used_sizes = Some(vec![7]); - let public_memory = builtin.get_public_memory().unwrap(); + let public_memory = builtin.get_public_memory(&segments).unwrap(); assert_eq!( public_memory, vec![(0, 0), (1, 0), (2, 1), (3, 1), (4, 2), (5, 2), (6, 2)] diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 914f0e32c5..f405e672af 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -525,9 +525,19 @@ impl CairoRunner { vm: &mut VirtualMachine, ) -> Result { let mut stack = Vec::new(); - - for builtin_runner in vm.builtin_runners.iter() { - stack.append(&mut builtin_runner.initial_stack()); + { + let builtin_runners = vm + .builtin_runners + .iter() + .map(|b| (b.identifier(), b)) + .collect::>(); + for builtin_id in &self.program.builtins { + if let Some(builtin_runner) = builtin_runners.get(builtin_id) { + stack.append(&mut builtin_runner.initial_stack()); + } else { + stack.push(Felt252::ZERO.into()) + } + } } if self.is_proof_mode() { @@ -1120,7 +1130,7 @@ impl CairoRunner { .get_used_cells_and_allocated_size(vm) .map_err(RunnerError::FinalizeSegements)?; if let BuiltinRunner::Output(output_builtin) = builtin_runner { - let public_memory = output_builtin.get_public_memory()?; + let public_memory = output_builtin.get_public_memory(&vm.segments)?; vm.segments .finalize(Some(size), builtin_runner.base(), Some(&public_memory)) } else { @@ -1268,14 +1278,33 @@ impl CairoRunner { Ok(()) } - pub fn read_return_values(&mut self, vm: &mut VirtualMachine) -> Result<(), RunnerError> { + pub fn read_return_values( + &mut self, + vm: &mut VirtualMachine, + allow_missing_builtins: bool, + ) -> Result<(), RunnerError> { if !self.run_ended { return Err(RunnerError::ReadReturnValuesNoEndRun); } let mut pointer = vm.get_ap(); - for builtin_runner in vm.builtin_runners.iter_mut().rev() { - let new_pointer = builtin_runner.final_stack(&vm.segments, pointer)?; - pointer = new_pointer; + for builtin_id in self.program.builtins.iter().rev() { + if let Some(builtin_runner) = vm + .builtin_runners + .iter_mut() + .find(|b| b.identifier() == *builtin_id) + { + let new_pointer = builtin_runner.final_stack(&vm.segments, pointer)?; + pointer = new_pointer; + } else { + if !allow_missing_builtins { + return Err(RunnerError::MissingBuiltin(builtin_id.name())); + } + pointer.offset = pointer.offset.saturating_sub(1); + + if !vm.get_integer(pointer)?.is_zero() { + return Err(RunnerError::MissingBuiltinStopPtrNotZero(builtin_id.name())); + } + } } if self.segments_finalized { return Err(RunnerError::FailedAddingReturnValues); @@ -1441,7 +1470,7 @@ impl CairoRunner { layout_name, dyn_layout, &vm.get_public_memory_addresses()?, - vm.get_memory_segment_addresses()?, + self.get_memory_segment_addresses(vm)?, self.relocated_trace .as_ref() .ok_or(PublicInputError::EmptyTrace)?, @@ -1457,6 +1486,41 @@ impl CairoRunner { } AirPrivateInput(private_inputs) } + + pub fn get_memory_segment_addresses( + &self, + vm: &VirtualMachine, + ) -> Result, VirtualMachineError> { + let relocation_table = vm + .relocation_table + .as_ref() + .ok_or(MemoryError::UnrelocatedMemory)?; + + let relocate = |segment: (usize, usize)| -> Result<(usize, usize), VirtualMachineError> { + let (index, stop_ptr_offset) = segment; + let base = relocation_table + .get(index) + .ok_or(VirtualMachineError::RelocationNotFound(index))?; + Ok((*base, base + stop_ptr_offset)) + }; + + vm.builtin_runners + .iter() + .map(|builtin| -> Result<_, VirtualMachineError> { + let (base, stop_ptr) = builtin.get_memory_segment_addresses(); + let stop_ptr = if self.program.builtins.contains(&builtin.identifier()) { + stop_ptr.ok_or_else(|| RunnerError::NoStopPointer(Box::new(builtin.name())))? + } else { + stop_ptr.unwrap_or_default() + }; + + Ok(( + builtin.name().strip_suffix("_builtin").unwrap_or_default(), + relocate((base, stop_ptr))?, + )) + }) + .collect() + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -3268,6 +3332,7 @@ mod tests { // Swap the first and second builtins (first should be `output`). vm.builtin_runners.swap(0, 1); + cairo_runner.program.builtins.swap(0, 1); cairo_runner.initialize_segments(&mut vm, None); @@ -4671,7 +4736,7 @@ mod tests { let mut vm = vm!(); //Check values written by first call to segments.finalize() - assert_eq!(cairo_runner.read_return_values(&mut vm), Ok(())); + assert_eq!(cairo_runner.read_return_values(&mut vm, false), Ok(())); assert_eq!( cairo_runner .execution_public_memory @@ -4693,7 +4758,7 @@ mod tests { cairo_runner.run_ended = false; let mut vm = vm!(); assert_eq!( - cairo_runner.read_return_values(&mut vm), + cairo_runner.read_return_values(&mut vm, false), Err(RunnerError::ReadReturnValuesNoEndRun) ); } @@ -4712,7 +4777,7 @@ mod tests { cairo_runner.segments_finalized = true; let mut vm = vm!(); assert_eq!( - cairo_runner.read_return_values(&mut vm), + cairo_runner.read_return_values(&mut vm, false), Err(RunnerError::FailedAddingReturnValues) ); } @@ -4740,7 +4805,7 @@ mod tests { vm.set_ap(1); vm.segments.segment_used_sizes = Some(vec![0, 1, 0]); //Check values written by first call to segments.finalize() - assert_eq!(cairo_runner.read_return_values(&mut vm), Ok(())); + assert_eq!(cairo_runner.read_return_values(&mut vm, false), Ok(())); let output_builtin = match &vm.builtin_runners[0] { BuiltinRunner::Output(runner) => runner, _ => unreachable!(), @@ -4771,7 +4836,7 @@ mod tests { vm.set_ap(1); vm.segments.segment_used_sizes = Some(vec![1, 1, 0]); //Check values written by first call to segments.finalize() - assert_eq!(cairo_runner.read_return_values(&mut vm), Ok(())); + assert_eq!(cairo_runner.read_return_values(&mut vm, false), Ok(())); let output_builtin = match &vm.builtin_runners[0] { BuiltinRunner::Output(runner) => runner, _ => unreachable!(), @@ -4809,13 +4874,13 @@ mod tests { // We use 5 as bitwise builtin's segment size as a bitwise instance is 5 cells vm.segments.segment_used_sizes = Some(vec![0, 2, 0, 5]); //Check values written by first call to segments.finalize() - assert_eq!(cairo_runner.read_return_values(&mut vm), Ok(())); + assert_eq!(cairo_runner.read_return_values(&mut vm, false), Ok(())); let output_builtin = match &vm.builtin_runners[0] { BuiltinRunner::Output(runner) => runner, _ => unreachable!(), }; assert_eq!(output_builtin.stop_ptr, Some(0)); - assert_eq!(cairo_runner.read_return_values(&mut vm), Ok(())); + assert_eq!(cairo_runner.read_return_values(&mut vm, false), Ok(())); let bitwise_builtin = match &vm.builtin_runners[1] { BuiltinRunner::Bitwise(runner) => runner, _ => unreachable!(), diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index dc8818e81d..eac5544536 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1064,40 +1064,6 @@ impl VirtualMachine { } } - pub fn get_memory_segment_addresses( - &self, - ) -> Result, VirtualMachineError> { - let relocation_table = self - .relocation_table - .as_ref() - .ok_or(MemoryError::UnrelocatedMemory)?; - - let relocate = |segment: (usize, usize)| -> Result<(usize, usize), VirtualMachineError> { - let (index, stop_ptr_offset) = segment; - let base = relocation_table - .get(index) - .ok_or(VirtualMachineError::RelocationNotFound(index))?; - Ok((*base, base + stop_ptr_offset)) - }; - - self.builtin_runners - .iter() - .map(|builtin| -> Result<_, VirtualMachineError> { - let addresses = - if let (base, Some(stop_ptr)) = builtin.get_memory_segment_addresses() { - (base, stop_ptr) - } else { - return Err(RunnerError::NoStopPointer(Box::new(builtin.name())).into()); - }; - - Ok(( - builtin.name().strip_suffix("_builtin").unwrap_or_default(), - relocate(addresses)?, - )) - }) - .collect() - } - #[doc(hidden)] pub fn builtins_final_stack_from_stack_pointer_dict( &mut self, From 635fef910fcaca41e4c3ea9d3080bc2416f3c2cb Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:24:42 -0300 Subject: [PATCH 30/36] Fix cairo1-run `validate_layouts` (#1710) "recursive" layout was not marked as valid --- cairo1-run/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index e8aeabadb6..8e2a99e021 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -114,6 +114,7 @@ fn validate_layout(value: &str) -> Result { "plain" | "small" | "dex" + | "recursive" | "starknet" | "starknet_with_keccak" | "recursive_large_output" From 1d23afb415ee5c9f6163bb20095f17a475f1a346 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:45:30 -0300 Subject: [PATCH 31/36] refactor: Remove unused code & use constants whenever possible for builtin instance definitions (#1707) * Reduce BitwiseInstanceDef * Cleanup module * Fixes * Yeet CpuInstanceDef * Update tests * Simplify ec op instance def * Clean Ecdsa instance def * Clean Keccak instance def * Update tests * Clean Pedersen instance def * Remove instances_per_component field * Use the constants instead of storing a value for n_input cells and cells_per_instance for all builtins * Extract memory_units_per_step into a constant * Remove underscore from used field * Remove unused field _cpu_component_step * Remove unused field _n_trace_columns * Impl Default for PoseidonInstanceDef * Fix test * Fix test * fmt * Add changelog entry * Fix field name --- CHANGELOG.md | 2 + .../builtin_hint_processor/blake2s_utils.rs | 1 - .../cairo_keccak/keccak_hints.rs | 2 +- .../builtin_hint_processor/ec_utils.rs | 1 - .../field_arithmetic.rs | 1 - .../builtin_hint_processor/garaga.rs | 1 - .../builtin_hint_processor/keccak_utils.rs | 1 - .../builtin_hint_processor/poseidon_utils.rs | 1 - .../builtin_hint_processor/pow_utils.rs | 2 +- .../builtin_hint_processor/segments.rs | 1 - .../builtin_hint_processor/set.rs | 2 +- .../builtin_hint_processor/sha256_utils.rs | 1 - .../builtin_hint_processor/signature.rs | 12 +- .../builtin_hint_processor/uint256_utils.rs | 5 +- .../builtin_hint_processor/uint384.rs | 5 +- .../uint384_extension.rs | 1 - .../builtin_hint_processor/vrf/fq.rs | 1 - .../bitwise_instance_def.rs | 50 +-- .../builtins_instance_def.rs | 18 +- .../instance_definitions/cpu_instance_def.rs | 27 -- .../ec_op_instance_def.rs | 55 +--- .../ecdsa_instance_def.rs | 63 +--- .../keccak_instance_def.rs | 61 +--- vm/src/types/instance_definitions/mod.rs | 1 - .../instance_definitions/mod_instance_def.rs | 2 + .../pedersen_instance_def.rs | 76 +---- .../poseidon_instance_def.rs | 7 +- .../range_check_instance_def.rs | 6 +- vm/src/types/layout.rs | 173 +++------- vm/src/utils.rs | 2 +- vm/src/vm/runners/builtin_runner/bitwise.rs | 87 ++--- vm/src/vm/runners/builtin_runner/ec_op.rs | 73 ++--- vm/src/vm/runners/builtin_runner/hash.rs | 17 +- vm/src/vm/runners/builtin_runner/keccak.rs | 127 +++----- vm/src/vm/runners/builtin_runner/mod.rs | 303 ++++++------------ vm/src/vm/runners/builtin_runner/modulo.rs | 27 +- vm/src/vm/runners/builtin_runner/poseidon.rs | 16 +- .../vm/runners/builtin_runner/range_check.rs | 11 +- .../runners/builtin_runner/segment_arena.rs | 8 +- vm/src/vm/runners/builtin_runner/signature.rs | 79 ++--- vm/src/vm/runners/cairo_runner.rs | 53 ++- vm/src/vm/vm_core.rs | 19 +- vm/src/vm/vm_memory/memory.rs | 5 +- 43 files changed, 402 insertions(+), 1004 deletions(-) delete mode 100644 vm/src/types/instance_definitions/cpu_instance_def.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dac6b636f..f51c9cb46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* refactor: Remove unused code & use constants whenever possible for builtin instance definitions[#1707](https://github.com/lambdaclass/cairo-vm/pull/1707) + * fix(BREAKING): Use program builtins in `initialize_main_entrypoint` & `read_return_values`[#1703](https://github.com/lambdaclass/cairo-vm/pull/1703) * `initialize_main_entrypoint` now iterates over the program builtins when building the stack & inserts 0 for any missing builtin * `read_return_values` now only computes the final stack of the builtins in the program 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 b095bcc69d..e56258018e 100644 --- a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs @@ -301,7 +301,6 @@ mod tests { hint_processor_definition::HintProcessorLogic, }, relocatable, - types::exec_scope::ExecutionScopes, utils::test_utils::*, vm::errors::memory_errors::MemoryError, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs b/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs index 0beea39ee9..202cba60a0 100644 --- a/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs +++ b/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs @@ -375,7 +375,7 @@ mod tests { }, hint_processor_definition::{HintProcessorLogic, HintReference}, }, - types::{exec_scope::ExecutionScopes, relocatable::Relocatable}, + types::relocatable::Relocatable, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs index 9530141f6d..2b8b0e7685 100644 --- a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs @@ -220,7 +220,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; use crate::relocatable; - use crate::types::exec_scope::ExecutionScopes; use crate::types::relocatable::Relocatable; use num_traits::Zero; diff --git a/vm/src/hint_processor/builtin_hint_processor/field_arithmetic.rs b/vm/src/hint_processor/builtin_hint_processor/field_arithmetic.rs index 6f5430a387..9c4ce28fe3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/field_arithmetic.rs +++ b/vm/src/hint_processor/builtin_hint_processor/field_arithmetic.rs @@ -274,7 +274,6 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::exec_scope::ExecutionScopes, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/garaga.rs b/vm/src/hint_processor/builtin_hint_processor/garaga.rs index 37f5a33025..7c1322fff9 100644 --- a/vm/src/hint_processor/builtin_hint_processor/garaga.rs +++ b/vm/src/hint_processor/builtin_hint_processor/garaga.rs @@ -29,7 +29,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; - use crate::types::exec_scope::ExecutionScopes; use crate::Felt252; use crate::{hint_processor::builtin_hint_processor::hint_code, utils::test_utils::*}; diff --git a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs index 86eec72eee..69b593e36f 100644 --- a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs @@ -297,7 +297,6 @@ mod tests { }, hint_processor_definition::{HintProcessorLogic, HintReference}, }, - types::exec_scope::ExecutionScopes, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs index 02a6dd7f25..1cc19daa4d 100644 --- a/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/poseidon_utils.rs @@ -57,7 +57,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; use crate::hint_processor::hint_processor_definition::HintReference; - use crate::types::exec_scope::ExecutionScopes; use crate::vm::vm_core::VirtualMachine; use crate::{hint_processor::builtin_hint_processor::hint_code, utils::test_utils::*}; diff --git a/vm/src/hint_processor/builtin_hint_processor/pow_utils.rs b/vm/src/hint_processor/builtin_hint_processor/pow_utils.rs index a749f4b896..0a31277a18 100644 --- a/vm/src/hint_processor/builtin_hint_processor/pow_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/pow_utils.rs @@ -45,7 +45,7 @@ mod tests { builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData, hint_processor_definition::HintProcessorLogic, }, - types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}, + types::relocatable::MaybeRelocatable, utils::test_utils::*, vm::{errors::memory_errors::MemoryError, vm_core::VirtualMachine}, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/segments.rs b/vm/src/hint_processor/builtin_hint_processor/segments.rs index c23c58d3d4..36576973d9 100644 --- a/vm/src/hint_processor/builtin_hint_processor/segments.rs +++ b/vm/src/hint_processor/builtin_hint_processor/segments.rs @@ -57,7 +57,6 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::exec_scope::ExecutionScopes, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/set.rs b/vm/src/hint_processor/builtin_hint_processor/set.rs index 28576e4a9f..86fc4fdadb 100644 --- a/vm/src/hint_processor/builtin_hint_processor/set.rs +++ b/vm/src/hint_processor/builtin_hint_processor/set.rs @@ -66,7 +66,7 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}, + types::relocatable::MaybeRelocatable, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/sha256_utils.rs b/vm/src/hint_processor/builtin_hint_processor/sha256_utils.rs index 659c752036..a7e77bd42c 100644 --- a/vm/src/hint_processor/builtin_hint_processor/sha256_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/sha256_utils.rs @@ -223,7 +223,6 @@ mod tests { }, hint_processor_definition::{HintProcessorLogic, HintReference}, }, - types::exec_scope::ExecutionScopes, utils::test_utils::*, vm::vm_core::VirtualMachine, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/signature.rs b/vm/src/hint_processor/builtin_hint_processor/signature.rs index 23ffa70086..878a379b7a 100644 --- a/vm/src/hint_processor/builtin_hint_processor/signature.rs +++ b/vm/src/hint_processor/builtin_hint_processor/signature.rs @@ -52,9 +52,6 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::{ - exec_scope::ExecutionScopes, instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, - }, utils::test_utils::*, vm::runners::builtin_runner::SignatureBuiltinRunner, }; @@ -67,8 +64,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn verify_ecdsa_signature_valid() { let mut vm = vm!(); - vm.builtin_runners = - vec![SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into()]; + vm.builtin_runners = vec![SignatureBuiltinRunner::new(Some(512), true).into()]; vm.segments = segments![ ((1, 0), (0, 0)), ( @@ -94,8 +90,7 @@ mod tests { #[test] fn verify_ecdsa_signature_invalid_ecdsa_ptr() { let mut vm = vm!(); - vm.builtin_runners = - vec![SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into()]; + vm.builtin_runners = vec![SignatureBuiltinRunner::new(Some(512), true).into()]; vm.segments = segments![ ((1, 0), (3, 0)), ( @@ -121,8 +116,7 @@ mod tests { #[test] fn verify_ecdsa_signature_invalid_input_cell() { let mut vm = vm!(); - vm.builtin_runners = - vec![SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into()]; + vm.builtin_runners = vec![SignatureBuiltinRunner::new(Some(512), true).into()]; vm.segments = segments![ ((1, 0), (0, 3)), ( diff --git a/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs b/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs index 30fc0d6396..9c91fafa8a 100644 --- a/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs @@ -507,10 +507,7 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::{ - exec_scope::ExecutionScopes, - relocatable::{MaybeRelocatable, Relocatable}, - }, + types::relocatable::{MaybeRelocatable, Relocatable}, utils::test_utils::*, vm::{errors::memory_errors::MemoryError, vm_core::VirtualMachine}, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/uint384.rs b/vm/src/hint_processor/builtin_hint_processor/uint384.rs index c3a9f35683..a973fb9d3d 100644 --- a/vm/src/hint_processor/builtin_hint_processor/uint384.rs +++ b/vm/src/hint_processor/builtin_hint_processor/uint384.rs @@ -252,10 +252,7 @@ mod tests { }, hint_processor_definition::HintProcessorLogic, }, - types::{ - exec_scope::ExecutionScopes, - relocatable::{MaybeRelocatable, Relocatable}, - }, + types::relocatable::{MaybeRelocatable, Relocatable}, utils::test_utils::*, vm::{errors::memory_errors::MemoryError, vm_core::VirtualMachine}, }; diff --git a/vm/src/hint_processor/builtin_hint_processor/uint384_extension.rs b/vm/src/hint_processor/builtin_hint_processor/uint384_extension.rs index 85c3cf6af8..b7ee55bf7d 100644 --- a/vm/src/hint_processor/builtin_hint_processor/uint384_extension.rs +++ b/vm/src/hint_processor/builtin_hint_processor/uint384_extension.rs @@ -73,7 +73,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::hint_code; use crate::hint_processor::builtin_hint_processor::secp::bigint_utils::Uint768; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; - use crate::types::exec_scope::ExecutionScopes; use crate::utils::test_utils::*; use assert_matches::assert_matches; diff --git a/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs b/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs index 91ca9e7f8e..c31047d8e0 100644 --- a/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs +++ b/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs @@ -123,7 +123,6 @@ mod tests { use crate::hint_processor::builtin_hint_processor::hint_code; use crate::hint_processor::hint_processor_definition::HintProcessorLogic; use crate::types::errors::math_errors::MathError; - use crate::types::exec_scope::ExecutionScopes; use crate::utils::test_utils::*; use assert_matches::assert_matches; diff --git a/vm/src/types/instance_definitions/bitwise_instance_def.rs b/vm/src/types/instance_definitions/bitwise_instance_def.rs index e59c219fd3..f5e3ce4354 100644 --- a/vm/src/types/instance_definitions/bitwise_instance_def.rs +++ b/vm/src/types/instance_definitions/bitwise_instance_def.rs @@ -2,34 +2,22 @@ use serde::Serialize; pub(crate) const CELLS_PER_BITWISE: u32 = 5; pub(crate) const INPUT_CELLS_PER_BITWISE: u32 = 2; +pub(crate) const TOTAL_N_BITS: u32 = 251; #[derive(Serialize, Clone, Debug, PartialEq)] pub(crate) struct BitwiseInstanceDef { pub(crate) ratio: Option, - pub(crate) total_n_bits: u32, } -impl BitwiseInstanceDef { - pub(crate) fn default() -> Self { - BitwiseInstanceDef { - ratio: Some(256), - total_n_bits: 251, - } +impl Default for BitwiseInstanceDef { + fn default() -> Self { + BitwiseInstanceDef { ratio: Some(256) } } +} +impl BitwiseInstanceDef { pub(crate) fn new(ratio: Option) -> Self { - BitwiseInstanceDef { - ratio, - total_n_bits: 251, - } - } - - pub(crate) fn _cells_per_builtin(&self) -> u32 { - CELLS_PER_BITWISE - } - - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - 0 + BitwiseInstanceDef { ratio } } } @@ -40,37 +28,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = BitwiseInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 0); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = BitwiseInstanceDef::default(); - assert_eq!(builtin_instance._cells_per_builtin(), 5); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = BitwiseInstanceDef { - ratio: Some(8), - total_n_bits: 251, - }; + let builtin_instance = BitwiseInstanceDef { ratio: Some(8) }; assert_eq!(BitwiseInstanceDef::new(Some(8)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = BitwiseInstanceDef { - ratio: Some(256), - total_n_bits: 251, - }; + let builtin_instance = BitwiseInstanceDef { ratio: Some(256) }; assert_eq!(BitwiseInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index b54cd3c849..a6d0bc44cc 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -6,6 +6,8 @@ use super::{ range_check_instance_def::RangeCheckInstanceDef, }; +pub(crate) const BUILTIN_INSTANCES_PER_COMPONENT: u32 = 1; + use serde::Serialize; #[derive(Serialize, Debug, PartialEq)] @@ -75,7 +77,7 @@ impl BuiltinsInstanceDef { pub(crate) fn recursive() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(128), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(128))), range_check: Some(RangeCheckInstanceDef::default()), ecdsa: None, bitwise: Some(BitwiseInstanceDef::new(Some(8))), @@ -91,7 +93,7 @@ impl BuiltinsInstanceDef { pub(crate) fn starknet() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(32), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(32))), range_check: Some(RangeCheckInstanceDef::new(Some(16))), ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), bitwise: Some(BitwiseInstanceDef::new(Some(64))), @@ -107,12 +109,12 @@ impl BuiltinsInstanceDef { pub(crate) fn starknet_with_keccak() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(32), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(32))), range_check: Some(RangeCheckInstanceDef::new(Some(16))), ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), bitwise: Some(BitwiseInstanceDef::new(Some(64))), ec_op: Some(EcOpInstanceDef::new(Some(1024))), - keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), + keccak: Some(KeccakInstanceDef::new(Some(2048))), poseidon: Some(PoseidonInstanceDef::default()), range_check96: None, add_mod: None, @@ -123,7 +125,7 @@ impl BuiltinsInstanceDef { pub(crate) fn recursive_large_output() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(128), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(128))), range_check: Some(RangeCheckInstanceDef::default()), ecdsa: None, bitwise: Some(BitwiseInstanceDef::new(Some(8))), @@ -139,12 +141,12 @@ impl BuiltinsInstanceDef { pub(crate) fn all_cairo() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(Some(256), 1)), + pedersen: Some(PedersenInstanceDef::new(Some(256))), range_check: Some(RangeCheckInstanceDef::default()), ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), bitwise: Some(BitwiseInstanceDef::new(Some(16))), ec_op: Some(EcOpInstanceDef::new(Some(1024))), - keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), + keccak: Some(KeccakInstanceDef::new(Some(2048))), poseidon: Some(PoseidonInstanceDef::new(Some(256))), range_check96: Some(RangeCheckInstanceDef::new(Some(8))), #[cfg(feature = "mod_builtin")] @@ -177,7 +179,7 @@ impl BuiltinsInstanceDef { pub(crate) fn dynamic() -> BuiltinsInstanceDef { BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(None, 4)), + pedersen: Some(PedersenInstanceDef::new(None)), range_check: Some(RangeCheckInstanceDef::new(None)), ecdsa: Some(EcdsaInstanceDef::new(None)), bitwise: Some(BitwiseInstanceDef::new(None)), diff --git a/vm/src/types/instance_definitions/cpu_instance_def.rs b/vm/src/types/instance_definitions/cpu_instance_def.rs deleted file mode 100644 index 19d22643a1..0000000000 --- a/vm/src/types/instance_definitions/cpu_instance_def.rs +++ /dev/null @@ -1,27 +0,0 @@ -use serde::Serialize; - -#[derive(Serialize, Debug, PartialEq)] -pub(crate) struct CpuInstanceDef { - pub(crate) _safe_call: bool, -} - -impl CpuInstanceDef { - pub(crate) fn default() -> Self { - CpuInstanceDef { _safe_call: true } - } -} - -#[cfg(test)] -mod tests { - use super::CpuInstanceDef; - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::*; - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_default() { - let cpu_instance = CpuInstanceDef::default(); - assert!(cpu_instance._safe_call) - } -} diff --git a/vm/src/types/instance_definitions/ec_op_instance_def.rs b/vm/src/types/instance_definitions/ec_op_instance_def.rs index 75e8039dba..3f4bc6ac2a 100644 --- a/vm/src/types/instance_definitions/ec_op_instance_def.rs +++ b/vm/src/types/instance_definitions/ec_op_instance_def.rs @@ -2,37 +2,22 @@ use serde::Serialize; pub(crate) const CELLS_PER_EC_OP: u32 = 7; pub(crate) const INPUT_CELLS_PER_EC_OP: u32 = 5; +pub(crate) const SCALAR_HEIGHT: u32 = 256; #[derive(Serialize, Clone, Debug, PartialEq)] pub(crate) struct EcOpInstanceDef { pub(crate) ratio: Option, - pub(crate) scalar_height: u32, - pub(crate) _scalar_bits: u32, } -impl EcOpInstanceDef { - pub(crate) fn default() -> Self { - EcOpInstanceDef { - ratio: Some(256), - scalar_height: 256, - _scalar_bits: 252, - } +impl Default for EcOpInstanceDef { + fn default() -> Self { + EcOpInstanceDef { ratio: Some(256) } } +} +impl EcOpInstanceDef { pub(crate) fn new(ratio: Option) -> Self { - EcOpInstanceDef { - ratio, - scalar_height: 256, - _scalar_bits: 252, - } - } - - pub(crate) fn _cells_per_builtin(&self) -> u32 { - CELLS_PER_EC_OP - } - - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - 0 + EcOpInstanceDef { ratio } } } @@ -43,39 +28,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = EcOpInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 0); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = EcOpInstanceDef::default(); - assert_eq!(builtin_instance._cells_per_builtin(), 7); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = EcOpInstanceDef { - ratio: Some(8), - scalar_height: 256, - _scalar_bits: 252, - }; + let builtin_instance = EcOpInstanceDef { ratio: Some(8) }; assert_eq!(EcOpInstanceDef::new(Some(8)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = EcOpInstanceDef { - ratio: Some(256), - scalar_height: 256, - _scalar_bits: 252, - }; + let builtin_instance = EcOpInstanceDef { ratio: Some(256) }; assert_eq!(EcOpInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/types/instance_definitions/ecdsa_instance_def.rs b/vm/src/types/instance_definitions/ecdsa_instance_def.rs index 518e5f5288..42f2ad6beb 100644 --- a/vm/src/types/instance_definitions/ecdsa_instance_def.rs +++ b/vm/src/types/instance_definitions/ecdsa_instance_def.rs @@ -1,44 +1,23 @@ use serde::Serialize; pub(crate) const CELLS_PER_SIGNATURE: u32 = 2; -pub(crate) const _INPUTCELLS_PER_SIGNATURE: u32 = 2; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Serialize, Clone, Debug, PartialEq)] pub(crate) struct EcdsaInstanceDef { pub(crate) ratio: Option, - pub(crate) _repetitions: u32, - pub(crate) _height: u32, - pub(crate) _n_hash_bits: u32, } -impl EcdsaInstanceDef { - pub(crate) fn default() -> Self { - EcdsaInstanceDef { - ratio: Some(512), - _repetitions: 1, - _height: 256, - _n_hash_bits: 251, - } +impl Default for EcdsaInstanceDef { + fn default() -> Self { + EcdsaInstanceDef { ratio: Some(512) } } +} +impl EcdsaInstanceDef { pub(crate) fn new(ratio: Option) -> Self { - EcdsaInstanceDef { - ratio, - _repetitions: 1, - _height: 256, - _n_hash_bits: 251, - } - } - - pub(crate) fn _cells_per_builtin(&self) -> u32 { - CELLS_PER_SIGNATURE - } - - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - 0 + EcdsaInstanceDef { ratio } } } - #[cfg(test)] mod tests { use super::*; @@ -46,41 +25,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = EcdsaInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 0); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = EcdsaInstanceDef::default(); - assert_eq!(builtin_instance._cells_per_builtin(), 2); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = EcdsaInstanceDef { - ratio: Some(8), - _repetitions: 1, - _height: 256, - _n_hash_bits: 251, - }; + let builtin_instance = EcdsaInstanceDef { ratio: Some(8) }; assert_eq!(EcdsaInstanceDef::new(Some(8)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = EcdsaInstanceDef { - ratio: Some(512), - _repetitions: 1, - _height: 256, - _n_hash_bits: 251, - }; + let builtin_instance = EcdsaInstanceDef { ratio: Some(512) }; assert_eq!(EcdsaInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/types/instance_definitions/keccak_instance_def.rs b/vm/src/types/instance_definitions/keccak_instance_def.rs index 655c361fcc..5746050067 100644 --- a/vm/src/types/instance_definitions/keccak_instance_def.rs +++ b/vm/src/types/instance_definitions/keccak_instance_def.rs @@ -1,39 +1,25 @@ use crate::stdlib::prelude::*; use serde::Serialize; +pub(crate) const INPUT_CELLS_PER_KECCAK: u32 = 8; +pub(crate) const CELLS_PER_KECCAK: u32 = 16; +pub(crate) const KECCAK_INSTANCES_PER_COMPONENT: u32 = 16; + #[derive(Serialize, Clone, Debug, PartialEq)] pub(crate) struct KeccakInstanceDef { pub(crate) ratio: Option, - pub(crate) _state_rep: Vec, - pub(crate) _instance_per_component: u32, } impl Default for KeccakInstanceDef { fn default() -> Self { - Self { - // ratio should be equal to 2 ** 11 -> 2048 - ratio: Some(2048), - _state_rep: vec![200; 8], - _instance_per_component: 16, - } + // ratio should be equal to 2 ** 11 -> 2048 + KeccakInstanceDef { ratio: Some(2048) } } } impl KeccakInstanceDef { - pub(crate) fn new(ratio: Option, _state_rep: Vec) -> Self { - Self { - ratio, - _state_rep, - ..Default::default() - } - } - - pub(crate) fn cells_per_builtin(&self) -> u32 { - 2 * self._state_rep.len() as u32 - } - - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - 0 + pub(crate) fn new(ratio: Option) -> Self { + KeccakInstanceDef { ratio } } } @@ -44,42 +30,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = KeccakInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 0); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = KeccakInstanceDef::default(); - assert_eq!(builtin_instance.cells_per_builtin(), 16); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = KeccakInstanceDef { - ratio: Some(2048), - _state_rep: vec![200; 8], - _instance_per_component: 16, - }; - assert_eq!( - KeccakInstanceDef::new(Some(2048), vec![200; 8]), - builtin_instance - ); + let builtin_instance = KeccakInstanceDef { ratio: Some(2048) }; + assert_eq!(KeccakInstanceDef::new(Some(2048)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = KeccakInstanceDef { - ratio: Some(2048), - _state_rep: vec![200; 8], - _instance_per_component: 16, - }; + let builtin_instance = KeccakInstanceDef { ratio: Some(2048) }; assert_eq!(KeccakInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/types/instance_definitions/mod.rs b/vm/src/types/instance_definitions/mod.rs index 691be29847..8f1bba2198 100644 --- a/vm/src/types/instance_definitions/mod.rs +++ b/vm/src/types/instance_definitions/mod.rs @@ -1,6 +1,5 @@ pub mod bitwise_instance_def; pub mod builtins_instance_def; -pub mod cpu_instance_def; pub mod diluted_pool_instance_def; pub mod ec_op_instance_def; pub mod ecdsa_instance_def; diff --git a/vm/src/types/instance_definitions/mod_instance_def.rs b/vm/src/types/instance_definitions/mod_instance_def.rs index 50bd8184c1..0b84077d33 100644 --- a/vm/src/types/instance_definitions/mod_instance_def.rs +++ b/vm/src/types/instance_definitions/mod_instance_def.rs @@ -2,6 +2,8 @@ use serde::Serialize; pub(crate) const N_WORDS: usize = 4; +pub(crate) const CELLS_PER_MOD: u32 = 7; + #[derive(Serialize, Debug, PartialEq, Clone)] pub(crate) struct ModInstanceDef { pub(crate) ratio: Option, diff --git a/vm/src/types/instance_definitions/pedersen_instance_def.rs b/vm/src/types/instance_definitions/pedersen_instance_def.rs index d6273f35f1..5f5bc0e564 100644 --- a/vm/src/types/instance_definitions/pedersen_instance_def.rs +++ b/vm/src/types/instance_definitions/pedersen_instance_def.rs @@ -1,48 +1,22 @@ -use num_bigint::{BigInt, Sign}; use serde::Serialize; pub(crate) const CELLS_PER_HASH: u32 = 3; pub(crate) const INPUT_CELLS_PER_HASH: u32 = 2; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Serialize, Clone, Debug, PartialEq)] pub(crate) struct PedersenInstanceDef { pub(crate) ratio: Option, - pub(crate) _repetitions: u32, - pub(crate) _element_height: u32, - pub(crate) _element_bits: u32, - pub(crate) _n_inputs: u32, - pub(crate) _hash_limit: BigInt, } -impl PedersenInstanceDef { - pub(crate) fn default() -> Self { - PedersenInstanceDef { - ratio: Some(8), - _repetitions: 4, - _element_height: 256, - _element_bits: 252, - _n_inputs: 2, - _hash_limit: BigInt::new(Sign::Plus, vec![1, 0, 0, 0, 0, 0, 17, 134217728]), - } - } - - pub(crate) fn new(ratio: Option, _repetitions: u32) -> Self { - PedersenInstanceDef { - ratio, - _repetitions, - _element_height: 256, - _element_bits: 252, - _n_inputs: 2, - _hash_limit: BigInt::new(Sign::Plus, vec![1, 0, 0, 0, 0, 0, 17, 134217728]), - } - } - - pub(crate) fn _cells_per_builtin(&self) -> u32 { - CELLS_PER_HASH +impl Default for PedersenInstanceDef { + fn default() -> Self { + PedersenInstanceDef { ratio: Some(8) } } +} - pub(crate) fn _range_check_units_per_builtin(&self) -> u32 { - 0 +impl PedersenInstanceDef { + pub(crate) fn new(ratio: Option) -> Self { + PedersenInstanceDef { ratio } } } @@ -53,45 +27,17 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_range_check_units_per_builtin() { - let builtin_instance = PedersenInstanceDef::default(); - assert_eq!(builtin_instance._range_check_units_per_builtin(), 0); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_builtin() { - let builtin_instance = PedersenInstanceDef::default(); - assert_eq!(builtin_instance._cells_per_builtin(), 3); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { - let builtin_instance = PedersenInstanceDef { - ratio: Some(10), - _repetitions: 2, - _element_height: 256, - _element_bits: 252, - _n_inputs: 2, - _hash_limit: BigInt::new(Sign::Plus, vec![1, 0, 0, 0, 0, 0, 17, 134217728]), - }; - assert_eq!(PedersenInstanceDef::new(Some(10), 2), builtin_instance); + let builtin_instance = PedersenInstanceDef { ratio: Some(10) }; + assert_eq!(PedersenInstanceDef::new(Some(10)), builtin_instance); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_default() { - let builtin_instance = PedersenInstanceDef { - ratio: Some(8), - _repetitions: 4, - _element_height: 256, - _element_bits: 252, - _n_inputs: 2, - _hash_limit: BigInt::new(Sign::Plus, vec![1, 0, 0, 0, 0, 0, 17, 134217728]), - }; + let builtin_instance = PedersenInstanceDef { ratio: Some(8) }; assert_eq!(PedersenInstanceDef::default(), builtin_instance); } } diff --git a/vm/src/types/instance_definitions/poseidon_instance_def.rs b/vm/src/types/instance_definitions/poseidon_instance_def.rs index fa478c4c3d..ae52df75fe 100644 --- a/vm/src/types/instance_definitions/poseidon_instance_def.rs +++ b/vm/src/types/instance_definitions/poseidon_instance_def.rs @@ -8,11 +8,12 @@ pub(crate) struct PoseidonInstanceDef { pub(crate) ratio: Option, } -impl PoseidonInstanceDef { - pub(crate) fn default() -> Self { +impl Default for PoseidonInstanceDef { + fn default() -> Self { PoseidonInstanceDef { ratio: Some(32) } } - +} +impl PoseidonInstanceDef { pub(crate) fn new(ratio: Option) -> Self { PoseidonInstanceDef { ratio } } diff --git a/vm/src/types/instance_definitions/range_check_instance_def.rs b/vm/src/types/instance_definitions/range_check_instance_def.rs index de9fa51da3..4524fca9bb 100644 --- a/vm/src/types/instance_definitions/range_check_instance_def.rs +++ b/vm/src/types/instance_definitions/range_check_instance_def.rs @@ -6,11 +6,13 @@ pub(crate) struct RangeCheckInstanceDef { pub(crate) ratio: Option, } -impl RangeCheckInstanceDef { - pub(crate) fn default() -> Self { +impl Default for RangeCheckInstanceDef { + fn default() -> Self { RangeCheckInstanceDef { ratio: Some(8) } } +} +impl RangeCheckInstanceDef { pub(crate) fn new(ratio: Option) -> Self { RangeCheckInstanceDef { ratio } } diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index f9a2f8376f..3533f817fe 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -1,163 +1,120 @@ use crate::stdlib::prelude::*; use super::instance_definitions::{ - builtins_instance_def::BuiltinsInstanceDef, cpu_instance_def::CpuInstanceDef, - diluted_pool_instance_def::DilutedPoolInstanceDef, + builtins_instance_def::BuiltinsInstanceDef, diluted_pool_instance_def::DilutedPoolInstanceDef, }; +pub(crate) const MEMORY_UNITS_PER_STEP: u32 = 8; + use serde::Serialize; #[derive(Serialize, Debug)] pub struct CairoLayout { - pub(crate) _name: String, - pub(crate) _cpu_component_step: u32, + pub(crate) name: String, pub(crate) rc_units: u32, pub(crate) builtins: BuiltinsInstanceDef, - pub(crate) _public_memory_fraction: u32, - pub(crate) _memory_units_per_step: u32, + pub(crate) public_memory_fraction: u32, pub(crate) diluted_pool_instance_def: Option, - pub(crate) _n_trace_colums: u32, - pub(crate) _cpu_instance_def: CpuInstanceDef, } impl CairoLayout { pub(crate) fn plain_instance() -> CairoLayout { CairoLayout { - _name: String::from("plain"), - _cpu_component_step: 1, + name: String::from("plain"), rc_units: 16, builtins: BuiltinsInstanceDef::plain(), - _public_memory_fraction: 4, - _memory_units_per_step: 8, + public_memory_fraction: 4, diluted_pool_instance_def: None, - _n_trace_colums: 8, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn small_instance() -> CairoLayout { CairoLayout { - _name: String::from("small"), - _cpu_component_step: 1, + name: String::from("small"), rc_units: 16, builtins: BuiltinsInstanceDef::small(), - _public_memory_fraction: 4, - _memory_units_per_step: 8, + public_memory_fraction: 4, diluted_pool_instance_def: None, - _n_trace_colums: 25, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn dex_instance() -> CairoLayout { CairoLayout { - _name: String::from("dex"), - _cpu_component_step: 1, + name: String::from("dex"), rc_units: 4, builtins: BuiltinsInstanceDef::dex(), - _public_memory_fraction: 4, - _memory_units_per_step: 8, + public_memory_fraction: 4, diluted_pool_instance_def: None, - _n_trace_colums: 22, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn recursive_instance() -> CairoLayout { CairoLayout { - _name: String::from("recursive"), - _cpu_component_step: 1, + name: String::from("recursive"), rc_units: 4, builtins: BuiltinsInstanceDef::recursive(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 10, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn starknet_instance() -> CairoLayout { CairoLayout { - _name: String::from("starknet"), - _cpu_component_step: 1, + name: String::from("starknet"), rc_units: 4, builtins: BuiltinsInstanceDef::starknet(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::new(2, 4, 16)), - _n_trace_colums: 10, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn starknet_with_keccak_instance() -> CairoLayout { CairoLayout { - _name: String::from("starknet_with_keccak"), - _cpu_component_step: 1, + name: String::from("starknet_with_keccak"), rc_units: 4, builtins: BuiltinsInstanceDef::starknet_with_keccak(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 15, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn recursive_large_output_instance() -> CairoLayout { CairoLayout { - _name: String::from("recursive_large_output"), - _cpu_component_step: 1, + name: String::from("recursive_large_output"), rc_units: 4, builtins: BuiltinsInstanceDef::recursive_large_output(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 12, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn all_cairo_instance() -> CairoLayout { CairoLayout { - _name: String::from("all_cairo"), - _cpu_component_step: 1, + name: String::from("all_cairo"), rc_units: 4, builtins: BuiltinsInstanceDef::all_cairo(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 11, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn all_solidity_instance() -> CairoLayout { CairoLayout { - _name: String::from("all_solidity"), - _cpu_component_step: 1, + name: String::from("all_solidity"), rc_units: 8, builtins: BuiltinsInstanceDef::all_solidity(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 27, - _cpu_instance_def: CpuInstanceDef::default(), } } pub(crate) fn dynamic_instance() -> CairoLayout { CairoLayout { - _name: String::from("dynamic"), - _cpu_component_step: 1, + name: String::from("dynamic"), rc_units: 16, builtins: BuiltinsInstanceDef::dynamic(), - _public_memory_fraction: 8, - _memory_units_per_step: 8, + public_memory_fraction: 8, diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), - _n_trace_colums: 73, - _cpu_instance_def: CpuInstanceDef::default(), } } } @@ -174,15 +131,11 @@ mod tests { fn get_plain_instance() { let layout = CairoLayout::plain_instance(); let builtins = BuiltinsInstanceDef::plain(); - assert_eq!(&layout._name, "plain"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "plain"); assert_eq!(layout.rc_units, 16); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 4); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 4); assert_eq!(layout.diluted_pool_instance_def, None); - assert_eq!(layout._n_trace_colums, 8); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] @@ -190,15 +143,11 @@ mod tests { fn get_small_instance() { let layout = CairoLayout::small_instance(); let builtins = BuiltinsInstanceDef::small(); - assert_eq!(&layout._name, "small"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "small"); assert_eq!(layout.rc_units, 16); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 4); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 4); assert_eq!(layout.diluted_pool_instance_def, None); - assert_eq!(layout._n_trace_colums, 25); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] @@ -206,140 +155,108 @@ mod tests { fn get_dex_instance() { let layout = CairoLayout::dex_instance(); let builtins = BuiltinsInstanceDef::dex(); - assert_eq!(&layout._name, "dex"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "dex"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 4); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 4); assert_eq!(layout.diluted_pool_instance_def, None); - assert_eq!(layout._n_trace_colums, 22); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_recursive_instance() { let layout = CairoLayout::recursive_instance(); let builtins = BuiltinsInstanceDef::recursive(); - assert_eq!(&layout._name, "recursive"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "recursive"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 10); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_starknet_instance() { let layout = CairoLayout::starknet_instance(); let builtins = BuiltinsInstanceDef::starknet(); - assert_eq!(&layout._name, "starknet"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "starknet"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::new(2, 4, 16)) ); - assert_eq!(layout._n_trace_colums, 10); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_starknet_with_keccak_instance() { let layout = CairoLayout::starknet_with_keccak_instance(); let builtins = BuiltinsInstanceDef::starknet_with_keccak(); - assert_eq!(&layout._name, "starknet_with_keccak"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "starknet_with_keccak"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 15); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_recursive_large_output_instance() { let layout = CairoLayout::recursive_large_output_instance(); let builtins = BuiltinsInstanceDef::recursive_large_output(); - assert_eq!(&layout._name, "recursive_large_output"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "recursive_large_output"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 12); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_all_cairo_instance() { let layout = CairoLayout::all_cairo_instance(); let builtins = BuiltinsInstanceDef::all_cairo(); - assert_eq!(&layout._name, "all_cairo"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "all_cairo"); assert_eq!(layout.rc_units, 4); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 11); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_all_solidity_instance() { let layout = CairoLayout::all_solidity_instance(); let builtins = BuiltinsInstanceDef::all_solidity(); - assert_eq!(&layout._name, "all_solidity"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "all_solidity"); assert_eq!(layout.rc_units, 8); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 27); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } #[test] fn get_dynamic_instance() { let layout = CairoLayout::dynamic_instance(); let builtins = BuiltinsInstanceDef::dynamic(); - assert_eq!(&layout._name, "dynamic"); - assert_eq!(layout._cpu_component_step, 1); + assert_eq!(&layout.name, "dynamic"); assert_eq!(layout.rc_units, 16); assert_eq!(layout.builtins, builtins); - assert_eq!(layout._public_memory_fraction, 8); - assert_eq!(layout._memory_units_per_step, 8); + assert_eq!(layout.public_memory_fraction, 8); assert_eq!( layout.diluted_pool_instance_def, Some(DilutedPoolInstanceDef::default()) ); - assert_eq!(layout._n_trace_colums, 73); - assert_eq!(layout._cpu_instance_def, CpuInstanceDef::default()); } } diff --git a/vm/src/utils.rs b/vm/src/utils.rs index 4ec902dcfb..adb68cbc07 100644 --- a/vm/src/utils.rs +++ b/vm/src/utils.rs @@ -440,7 +440,7 @@ pub mod test_utils { macro_rules! exec_scopes_ref { () => { - &mut ExecutionScopes::new() + &mut crate::types::exec_scope::ExecutionScopes::new() }; } pub(crate) use exec_scopes_ref; diff --git a/vm/src/vm/runners/builtin_runner/bitwise.rs b/vm/src/vm/runners/builtin_runner/bitwise.rs index 2a049690a2..e5901194db 100644 --- a/vm/src/vm/runners/builtin_runner/bitwise.rs +++ b/vm/src/vm/runners/builtin_runner/bitwise.rs @@ -3,9 +3,7 @@ use crate::stdlib::{boxed::Box, vec::Vec}; use crate::Felt252; use crate::{ types::{ - instance_definitions::bitwise_instance_def::{ - BitwiseInstanceDef, CELLS_PER_BITWISE, INPUT_CELLS_PER_BITWISE, - }, + instance_definitions::bitwise_instance_def::{CELLS_PER_BITWISE, TOTAL_N_BITS}, relocatable::{MaybeRelocatable, Relocatable}, }, vm::{ @@ -19,25 +17,17 @@ use num_integer::div_ceil; pub struct BitwiseBuiltinRunner { ratio: Option, pub base: usize, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, - bitwise_builtin: BitwiseInstanceDef, pub(crate) stop_ptr: Option, pub(crate) included: bool, - pub(crate) instances_per_component: u32, } impl BitwiseBuiltinRunner { - pub(crate) fn new(instance_def: &BitwiseInstanceDef, included: bool) -> Self { + pub(crate) fn new(ratio: Option, included: bool) -> Self { BitwiseBuiltinRunner { base: 0, - ratio: instance_def.ratio, - cells_per_instance: CELLS_PER_BITWISE, - n_input_cells: INPUT_CELLS_PER_BITWISE, - bitwise_builtin: instance_def.clone(), + ratio, stop_ptr: None, included, - instances_per_component: 1, } } @@ -66,7 +56,7 @@ impl BitwiseBuiltinRunner { address: Relocatable, memory: &Memory, ) -> Result, RunnerError> { - let index = address.offset % self.cells_per_instance as usize; + let index = address.offset % CELLS_PER_BITWISE as usize; if index <= 1 { return Ok(None); } @@ -86,7 +76,7 @@ impl BitwiseBuiltinRunner { if limbs[3] & LEADING_BITS != 0 { return Err(RunnerError::IntegerBiggerThanPowerOfTwo(Box::new(( x_addr, - self.bitwise_builtin.total_n_bits, + TOTAL_N_BITS, *x, )))); } @@ -124,7 +114,7 @@ impl BitwiseBuiltinRunner { } pub fn get_used_diluted_check_units(&self, diluted_spacing: u32, diluted_n_bits: u32) -> usize { - let total_n_bits = self.bitwise_builtin.total_n_bits; + let total_n_bits = TOTAL_N_BITS; let mut partition = Vec::with_capacity(total_n_bits as usize); for i in (0..total_n_bits).step_by((diluted_spacing * diluted_n_bits) as usize) { for j in 0..diluted_spacing { @@ -146,7 +136,7 @@ impl BitwiseBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_BITWISE as usize)) } pub fn air_private_input(&self, memory: &Memory) -> Vec { @@ -194,7 +184,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_instances() { - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let builtin = BitwiseBuiltinRunner::new(Some(10), true); let mut vm = vm!(); @@ -213,8 +203,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -238,8 +227,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -267,8 +255,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), false).into(); + let mut builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -292,8 +279,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -317,8 +303,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_and_allocated_size_test() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); + let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -364,8 +349,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); + let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -410,7 +394,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_for_preset_memory_valid_and() { let memory = memory![((0, 5), 10), ((0, 6), 12), ((0, 7), 0)]; - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 7)), &memory); assert_eq!(result, Ok(Some(MaybeRelocatable::from(Felt252::from(8))))); } @@ -419,7 +403,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_for_preset_memory_valid_xor() { let memory = memory![((0, 5), 10), ((0, 6), 12), ((0, 8), 0)]; - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 8)), &memory); assert_eq!(result, Ok(Some(MaybeRelocatable::from(Felt252::from(6))))); } @@ -428,7 +412,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_for_preset_memory_valid_or() { let memory = memory![((0, 5), 10), ((0, 6), 12), ((0, 9), 0)]; - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 9)), &memory); assert_eq!(result, Ok(Some(MaybeRelocatable::from(Felt252::from(14))))); } @@ -437,7 +421,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_for_preset_memory_incorrect_offset() { let memory = memory![((0, 3), 10), ((0, 4), 12), ((0, 5), 0)]; - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); assert_eq!(result, Ok(None)); } @@ -446,7 +430,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_for_preset_memory_no_values_to_operate() { let memory = memory![((0, 5), 12), ((0, 7), 0)]; - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); assert_eq!(result, Ok(None)); } @@ -454,10 +438,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let vm = vm!(); assert_eq!( @@ -469,10 +450,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_empty() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![0]); @@ -482,10 +460,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); @@ -495,38 +470,28 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_a() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); assert_eq!(builtin.get_used_diluted_check_units(12, 2), 535); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_b() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); assert_eq!(builtin.get_used_diluted_check_units(30, 56), 150); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_c() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); assert_eq!(builtin.get_used_diluted_check_units(50, 25), 250); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_air_private_input() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); let segments = segments![ ((0, 0), 0), diff --git a/vm/src/vm/runners/builtin_runner/ec_op.rs b/vm/src/vm/runners/builtin_runner/ec_op.rs index 2c9be94793..8dd50f0ffa 100644 --- a/vm/src/vm/runners/builtin_runner/ec_op.rs +++ b/vm/src/vm/runners/builtin_runner/ec_op.rs @@ -2,7 +2,7 @@ use crate::air_private_input::{PrivateInput, PrivateInputEcOp}; use crate::stdlib::{borrow::Cow, prelude::*}; use crate::stdlib::{cell::RefCell, collections::HashMap}; use crate::types::instance_definitions::ec_op_instance_def::{ - EcOpInstanceDef, CELLS_PER_EC_OP, INPUT_CELLS_PER_EC_OP, + CELLS_PER_EC_OP, INPUT_CELLS_PER_EC_OP, SCALAR_HEIGHT, }; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; @@ -17,26 +17,18 @@ use starknet_types_core::curve::ProjectivePoint; pub struct EcOpBuiltinRunner { ratio: Option, pub base: usize, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, - ec_op_builtin: EcOpInstanceDef, pub(crate) stop_ptr: Option, pub(crate) included: bool, - pub(crate) instances_per_component: u32, cache: RefCell>, } impl EcOpBuiltinRunner { - pub(crate) fn new(instance_def: &EcOpInstanceDef, included: bool) -> Self { + pub(crate) fn new(ratio: Option, included: bool) -> Self { EcOpBuiltinRunner { base: 0, - ratio: instance_def.ratio, - n_input_cells: INPUT_CELLS_PER_EC_OP, - cells_per_instance: CELLS_PER_EC_OP, - ec_op_builtin: instance_def.clone(), + ratio, stop_ptr: None, included, - instances_per_component: 1, cache: RefCell::new(HashMap::new()), } } @@ -115,9 +107,7 @@ impl EcOpBuiltinRunner { let beta_high: Felt252 = Felt252::from(0x6f21413efbe40de150e596d72f7a8c5_u128); let beta: Felt252 = (beta_high * (Felt252::ONE + Felt252::from(u128::MAX))) + beta_low; - let index = address - .offset - .mod_floor(&(self.cells_per_instance as usize)); + let index = address.offset.mod_floor(&(CELLS_PER_EC_OP as usize)); //Index should be an output cell if index != OUTPUT_INDICES.0 && index != OUTPUT_INDICES.1 { return Ok(None); @@ -132,8 +122,8 @@ impl EcOpBuiltinRunner { //All input cells should be filled, and be integer values //If an input cell is not filled, return None - let mut input_cells = Vec::<&Felt252>::with_capacity(self.n_input_cells as usize); - for i in 0..self.n_input_cells as usize { + let mut input_cells = Vec::<&Felt252>::with_capacity(INPUT_CELLS_PER_EC_OP as usize); + for i in 0..INPUT_CELLS_PER_EC_OP as usize { match memory.get(&(instance + i)?) { None => return Ok(None), Some(addr) => { @@ -174,7 +164,7 @@ impl EcOpBuiltinRunner { (input_cells[0].to_owned(), input_cells[1].to_owned()), (input_cells[2].to_owned(), input_cells[3].to_owned()), input_cells[4], - self.ec_op_builtin.scalar_height, + SCALAR_HEIGHT, )?; self.cache.borrow_mut().insert(x_addr, result.0); self.cache.borrow_mut().insert( @@ -182,7 +172,7 @@ impl EcOpBuiltinRunner { .map_err(|_| RunnerError::Memory(MemoryError::ExpectedInteger(Box::new(x_addr))))?, result.1, ); - match index - self.n_input_cells as usize { + match index - INPUT_CELLS_PER_EC_OP as usize { 0 => Ok(Some(MaybeRelocatable::Int(result.0))), _ => Ok(Some(MaybeRelocatable::Int(result.1))), //Default case corresponds to 1, as there are no other possible cases @@ -200,7 +190,7 @@ impl EcOpBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_EC_OP as usize)) } pub fn format_ec_op_error( @@ -274,7 +264,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_instances() { - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let builtin = EcOpBuiltinRunner::new(Some(10), true); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![1]); @@ -285,8 +275,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -310,8 +299,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -339,8 +327,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), false).into(); + let mut builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -364,8 +351,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); + let mut builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -389,8 +375,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_and_allocated_size_test() { - let builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); + let builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -435,8 +420,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units() { - let builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); + let builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -655,7 +639,7 @@ mod tests { ) ) ]; - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let builtin = EcOpBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((3, 6)), &memory); assert_eq!( @@ -701,7 +685,7 @@ mod tests { ) ]; - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let builtin = EcOpBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((3, 6)), &memory); assert_eq!(result, Ok(None)); } @@ -747,7 +731,7 @@ mod tests { ) ) ]; - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let builtin = EcOpBuiltinRunner::new(Some(256), true); let result = builtin.deduce_memory_cell(Relocatable::from((3, 3)), &memory); assert_eq!(result, Ok(None)); @@ -788,7 +772,7 @@ mod tests { ) ) ]; - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let builtin = EcOpBuiltinRunner::new(Some(256), true); assert_eq!( builtin.deduce_memory_cell(Relocatable::from((3, 6)), &memory), @@ -801,8 +785,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), true)); let vm = vm!(); assert_eq!( @@ -814,8 +797,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_empty() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![0]); @@ -825,8 +807,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); @@ -836,15 +817,14 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stackincluded_test() { - let ec_op_builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let ec_op_builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); assert_eq!(ec_op_builtin.initial_stack(), vec![mayberelocatable!(0, 0)]) } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stack_notincluded_test() { - let ec_op_builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), false); + let ec_op_builtin = EcOpBuiltinRunner::new(Some(256), false); assert_eq!(ec_op_builtin.initial_stack(), Vec::new()) } @@ -902,8 +882,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_air_private_input() { - let builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); let segments = segments![ ((0, 0), 0), diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index c9b437403f..a6d045ade2 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -1,9 +1,7 @@ use crate::air_private_input::{PrivateInput, PrivateInputPair}; use crate::stdlib::{cell::RefCell, prelude::*}; use crate::types::errors::math_errors::MathError; -use crate::types::instance_definitions::pedersen_instance_def::{ - CELLS_PER_HASH, INPUT_CELLS_PER_HASH, -}; +use crate::types::instance_definitions::pedersen_instance_def::CELLS_PER_HASH; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::errors::runner_errors::RunnerError; @@ -18,11 +16,8 @@ use starknet_crypto::{pedersen_hash, FieldElement}; pub struct HashBuiltinRunner { pub base: usize, ratio: Option, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, pub(crate) stop_ptr: Option, pub(crate) included: bool, - pub(crate) instances_per_component: u32, // This act as a cache to optimize calls to deduce_memory_cell // Therefore need interior mutability // 1 at position 'n' means offset 'n' relative to base pointer @@ -35,12 +30,9 @@ impl HashBuiltinRunner { HashBuiltinRunner { base: 0, ratio, - cells_per_instance: CELLS_PER_HASH, - n_input_cells: INPUT_CELLS_PER_HASH, stop_ptr: None, verified_addresses: RefCell::new(Vec::new()), included, - instances_per_component: 1, } } @@ -69,10 +61,7 @@ impl HashBuiltinRunner { address: Relocatable, memory: &Memory, ) -> Result, RunnerError> { - if address - .offset - .mod_floor(&(self.cells_per_instance as usize)) - != 2 + if address.offset.mod_floor(&(CELLS_PER_HASH as usize)) != 2 || *self .verified_addresses .borrow() @@ -131,7 +120,7 @@ impl HashBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_HASH as usize)) } pub fn get_additional_data(&self) -> BuiltinAdditionalData { diff --git a/vm/src/vm/runners/builtin_runner/keccak.rs b/vm/src/vm/runners/builtin_runner/keccak.rs index cbd5ad0dc5..93daf92f82 100644 --- a/vm/src/vm/runners/builtin_runner/keccak.rs +++ b/vm/src/vm/runners/builtin_runner/keccak.rs @@ -1,44 +1,43 @@ use crate::air_private_input::{PrivateInput, PrivateInputKeccakState}; use crate::math_utils::safe_div_usize; use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*}; -use crate::types::instance_definitions::keccak_instance_def::KeccakInstanceDef; +use crate::types::instance_definitions::keccak_instance_def::{ + CELLS_PER_KECCAK, INPUT_CELLS_PER_KECCAK, +}; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::errors::runner_errors::RunnerError; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use crate::Felt252; +use lazy_static::lazy_static; use num_bigint::BigUint; use num_integer::div_ceil; use super::KECCAK_BUILTIN_NAME; const KECCAK_FELT_BYTE_SIZE: usize = 25; // 200 / 8 +const BITS: u32 = 200; +lazy_static! { + static ref KECCAK_INPUT_MAX: Felt252 = Felt252::TWO.pow(BITS); +} #[derive(Debug, Clone)] pub struct KeccakBuiltinRunner { ratio: Option, pub base: usize, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, pub(crate) stop_ptr: Option, pub(crate) included: bool, - state_rep: Vec, - pub(crate) instances_per_component: u32, cache: RefCell>, } impl KeccakBuiltinRunner { - pub(crate) fn new(instance_def: &KeccakInstanceDef, included: bool) -> Self { + pub(crate) fn new(ratio: Option, included: bool) -> Self { KeccakBuiltinRunner { base: 0, - ratio: instance_def.ratio, - n_input_cells: instance_def._state_rep.len() as u32, - cells_per_instance: instance_def.cells_per_builtin(), + ratio, stop_ptr: None, included, - instances_per_component: instance_def._instance_per_component, - state_rep: instance_def._state_rep.clone(), cache: RefCell::new(HashMap::new()), } } @@ -68,19 +67,19 @@ impl KeccakBuiltinRunner { address: Relocatable, memory: &Memory, ) -> Result, RunnerError> { - let index = address.offset % self.cells_per_instance as usize; - if index < self.n_input_cells as usize { + let index = address.offset % CELLS_PER_KECCAK as usize; + if index < INPUT_CELLS_PER_KECCAK as usize { return Ok(None); } if let Some(felt) = self.cache.borrow().get(&address) { return Ok(Some(felt.into())); } let first_input_addr = (address - index)?; - let first_output_addr = (first_input_addr + self.n_input_cells as usize)?; + let first_output_addr = (first_input_addr + INPUT_CELLS_PER_KECCAK as usize)?; let mut input_felts = vec![]; - for i in 0..self.n_input_cells as usize { + for i in 0..INPUT_CELLS_PER_KECCAK as usize { let m_index = (first_input_addr + i)?; let val = match memory.get(&m_index) { Some(value) => { @@ -90,10 +89,10 @@ impl KeccakBuiltinRunner { KECCAK_BUILTIN_NAME, (first_input_addr + i)?, ))))?; - if num >= &(Felt252::TWO.pow(self.state_rep[i])) { + if num >= &KECCAK_INPUT_MAX { return Err(RunnerError::IntegerBiggerThanPowerOfTwo(Box::new(( (first_input_addr + i)?, - self.state_rep[i], + BITS, *num, )))); } @@ -114,8 +113,8 @@ impl KeccakBuiltinRunner { let keccak_result = Self::keccak_f(&input_message)?; let mut start_index = 0_usize; - for (i, bits) in self.state_rep.iter().enumerate() { - let end_index = start_index + *bits as usize / 8; + for i in 0..INPUT_CELLS_PER_KECCAK { + let end_index = start_index + BITS as usize / 8; self.cache.borrow_mut().insert((first_output_addr + i)?, { let mut bytes = keccak_result[start_index..end_index].to_vec(); bytes.resize(32, 0); @@ -137,7 +136,7 @@ impl KeccakBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_KECCAK as usize)) } pub fn get_used_diluted_check_units(&self, diluted_n_bits: u32) -> usize { @@ -169,7 +168,7 @@ impl KeccakBuiltinRunner { if let Some(segment) = memory.data.get(self.base) { let segment_len = segment.len(); for (index, off) in (0..segment_len) - .step_by(self.cells_per_instance as usize) + .step_by(CELLS_PER_KECCAK as usize) .enumerate() { // Add the input cells of each keccak instance to the private inputs @@ -231,8 +230,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_instances() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![1]); @@ -243,8 +241,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let mut builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -268,8 +265,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let mut builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -296,8 +292,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), false).into(); + let mut builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -321,8 +316,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let mut builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -346,8 +340,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_and_allocated_size_test() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -377,8 +370,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); vm.current_step = 160; @@ -389,8 +381,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(2048), true).into(); let vm = vm!(); assert_eq!( @@ -402,8 +393,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_empty() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(2048), true).into(); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![0]); @@ -413,8 +403,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(2048), true).into(); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); @@ -424,7 +413,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stackincluded_test() { - let keccak_builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let keccak_builtin = KeccakBuiltinRunner::new(Some(2048), true); assert_eq!( keccak_builtin.initial_stack(), vec![mayberelocatable!(0, 0)] @@ -434,7 +423,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stack_notincluded_test() { - let keccak_builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), false); + let keccak_builtin = KeccakBuiltinRunner::new(Some(2048), false); assert_eq!(keccak_builtin.initial_stack(), Vec::new()) } @@ -463,7 +452,7 @@ mod tests { ((0, 34), 0), ((0, 35), 0) ]; - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 25)), &memory); assert_eq!( @@ -484,7 +473,7 @@ mod tests { ((0, 7), 120), ((0, 8), 52) ]; - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 1)), &memory); assert_eq!(result, Ok(None)); } @@ -493,7 +482,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_offset_lt_input_cell_length_none() { let memory = memory![((0, 4), 32)]; - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 2)), &memory); assert_eq!(result, Ok(None)); } @@ -503,12 +492,9 @@ mod tests { fn deduce_memory_cell_expected_integer() { let memory = memory![((0, 0), (1, 2))]; - let mut builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - - builtin.n_input_cells = 1; - builtin.cells_per_instance = 100; + let builtin = KeccakBuiltinRunner::new(Some(2048), true); - let result = builtin.deduce_memory_cell(Relocatable::from((0, 1)), &memory); + let result = builtin.deduce_memory_cell(Relocatable::from((0, 9)), &memory); assert_eq!( result, @@ -524,37 +510,19 @@ mod tests { fn deduce_memory_cell_missing_input_cells() { let memory = memory![((0, 1), (1, 2))]; - let mut builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - - builtin.n_input_cells = 1; - builtin.cells_per_instance = 100; + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 1)), &memory); assert_eq!(result, Ok(None)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn deduce_memory_cell_input_cell() { - let memory = memory![((0, 0), (1, 2))]; - - let mut builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - - builtin.n_input_cells = 1; - builtin.cells_per_instance = 100; - - let result = builtin.deduce_memory_cell(Relocatable::from((0, 0)), &memory); - - assert_eq!(result, Ok(None)); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_get_memory_err() { let memory = memory![((0, 35), 0)]; - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 15)), &memory); @@ -564,8 +532,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_memory_int_larger_than_bits() { - let memory = memory![ - ((0, 16), 43), + let mut memory = memory![ ((0, 17), 199), ((0, 18), 0), ((0, 19), 0), @@ -587,8 +554,9 @@ mod tests { ((0, 35), 0) ]; - let keccak_instance = KeccakInstanceDef::new(Some(2048), vec![1; 8]); - let builtin = KeccakBuiltinRunner::new(&keccak_instance, true); + memory.insert((0, 16).into(), Felt252::MAX).unwrap(); + + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result = builtin.deduce_memory_cell(Relocatable::from((0, 25)), &memory); @@ -596,8 +564,8 @@ mod tests { result, Err(RunnerError::IntegerBiggerThanPowerOfTwo(Box::new(( (0, 16).into(), - 1, - 43.into() + BITS, + Felt252::MAX )))) ); } @@ -605,7 +573,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_result() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); + let builtin = KeccakBuiltinRunner::new(Some(2048), true); let result: usize = builtin.get_used_diluted_check_units(16); @@ -623,8 +591,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_air_private_input() { - let builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(2048), true).into(); let segments = segments![ ((0, 0), 0), diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index 75cc0c8983..e034ed46e4 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -2,6 +2,25 @@ use crate::air_private_input::PrivateInput; use crate::math_utils::safe_div_usize; use crate::serde::deserialize_program::BuiltinName; use crate::stdlib::prelude::*; +use crate::types::instance_definitions::bitwise_instance_def::{ + CELLS_PER_BITWISE, INPUT_CELLS_PER_BITWISE, +}; +use crate::types::instance_definitions::builtins_instance_def::BUILTIN_INSTANCES_PER_COMPONENT; +use crate::types::instance_definitions::ec_op_instance_def::{ + CELLS_PER_EC_OP, INPUT_CELLS_PER_EC_OP, +}; +use crate::types::instance_definitions::ecdsa_instance_def::CELLS_PER_SIGNATURE; +use crate::types::instance_definitions::keccak_instance_def::{ + CELLS_PER_KECCAK, INPUT_CELLS_PER_KECCAK, KECCAK_INSTANCES_PER_COMPONENT, +}; +use crate::types::instance_definitions::mod_instance_def::CELLS_PER_MOD; +use crate::types::instance_definitions::pedersen_instance_def::{ + CELLS_PER_HASH, INPUT_CELLS_PER_HASH, +}; +use crate::types::instance_definitions::poseidon_instance_def::{ + CELLS_PER_POSEIDON, INPUT_CELLS_PER_POSEIDON, +}; +use crate::types::instance_definitions::range_check_instance_def::CELLS_PER_RANGE_CHECK; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::{self, InsufficientAllocatedCellsError, MemoryError}; use crate::vm::errors::runner_errors::RunnerError; @@ -22,6 +41,7 @@ mod segment_arena; mod signature; pub(crate) use self::range_check::{RC_N_PARTS_96, RC_N_PARTS_STANDARD}; +use self::segment_arena::ARENA_BUILTIN_SIZE; pub use bitwise::BitwiseBuiltinRunner; pub use ec_op::EcOpBuiltinRunner; pub use hash::HashBuiltinRunner; @@ -351,49 +371,38 @@ impl BuiltinRunner { fn cells_per_instance(&self) -> u32 { match self { - BuiltinRunner::Bitwise(builtin) => builtin.cells_per_instance, - BuiltinRunner::EcOp(builtin) => builtin.cells_per_instance, - BuiltinRunner::Hash(builtin) => builtin.cells_per_instance, - BuiltinRunner::RangeCheck(builtin) => builtin.cells_per_instance, - BuiltinRunner::RangeCheck96(builtin) => builtin.cells_per_instance, + BuiltinRunner::Bitwise(_) => CELLS_PER_BITWISE, + BuiltinRunner::EcOp(_) => CELLS_PER_EC_OP, + BuiltinRunner::Hash(_) => CELLS_PER_HASH, + BuiltinRunner::RangeCheck(_) | BuiltinRunner::RangeCheck96(_) => CELLS_PER_RANGE_CHECK, BuiltinRunner::Output(_) => 0, - BuiltinRunner::Keccak(builtin) => builtin.cells_per_instance, - BuiltinRunner::Signature(builtin) => builtin.cells_per_instance, - BuiltinRunner::Poseidon(builtin) => builtin.cells_per_instance, - BuiltinRunner::SegmentArena(builtin) => builtin.cells_per_instance, - BuiltinRunner::Mod(mod_builtin) => mod_builtin.cells_per_instance(), + BuiltinRunner::Keccak(_) => CELLS_PER_KECCAK, + BuiltinRunner::Signature(_) => CELLS_PER_SIGNATURE, + BuiltinRunner::Poseidon(_) => CELLS_PER_POSEIDON, + BuiltinRunner::SegmentArena(_) => ARENA_BUILTIN_SIZE, + BuiltinRunner::Mod(_) => CELLS_PER_MOD, } } fn n_input_cells(&self) -> u32 { match self { - BuiltinRunner::Bitwise(builtin) => builtin.n_input_cells, - BuiltinRunner::EcOp(builtin) => builtin.n_input_cells, - BuiltinRunner::Hash(builtin) => builtin.n_input_cells, - BuiltinRunner::RangeCheck(builtin) => builtin.n_input_cells, - BuiltinRunner::RangeCheck96(builtin) => builtin.n_input_cells, + BuiltinRunner::Bitwise(_) => INPUT_CELLS_PER_BITWISE, + BuiltinRunner::EcOp(_) => INPUT_CELLS_PER_EC_OP, + BuiltinRunner::Hash(_) => INPUT_CELLS_PER_HASH, + BuiltinRunner::RangeCheck(_) | BuiltinRunner::RangeCheck96(_) => CELLS_PER_RANGE_CHECK, BuiltinRunner::Output(_) => 0, - BuiltinRunner::Keccak(builtin) => builtin.n_input_cells, - BuiltinRunner::Signature(builtin) => builtin.n_input_cells, - BuiltinRunner::Poseidon(builtin) => builtin.n_input_cells, - BuiltinRunner::SegmentArena(builtin) => builtin.n_input_cells_per_instance, - BuiltinRunner::Mod(builtin) => builtin.n_input_cells(), + BuiltinRunner::Keccak(_) => INPUT_CELLS_PER_KECCAK, + BuiltinRunner::Signature(_) => CELLS_PER_SIGNATURE, + BuiltinRunner::Poseidon(_) => INPUT_CELLS_PER_POSEIDON, + BuiltinRunner::SegmentArena(_) => ARENA_BUILTIN_SIZE, + BuiltinRunner::Mod(_) => CELLS_PER_MOD, } } fn instances_per_component(&self) -> u32 { match self { - BuiltinRunner::Bitwise(builtin) => builtin.instances_per_component, - BuiltinRunner::EcOp(builtin) => builtin.instances_per_component, - BuiltinRunner::Hash(builtin) => builtin.instances_per_component, - BuiltinRunner::RangeCheck(builtin) => builtin.instances_per_component, - BuiltinRunner::RangeCheck96(builtin) => builtin.instances_per_component, - BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) => 1, - BuiltinRunner::Keccak(builtin) => builtin.instances_per_component, - BuiltinRunner::Signature(builtin) => builtin.instances_per_component, - BuiltinRunner::Poseidon(builtin) => builtin.instances_per_component, - // TODO: Placeholder till we see layout data - BuiltinRunner::Mod(_) => 1, + BuiltinRunner::Keccak(_) => KECCAK_INSTANCES_PER_COMPONENT, + _ => BUILTIN_INSTANCES_PER_COMPONENT, } } @@ -653,18 +662,10 @@ mod tests { use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; - use crate::types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef; - use crate::types::instance_definitions::keccak_instance_def::KeccakInstanceDef; use crate::types::program::Program; use crate::vm::errors::memory_errors::InsufficientAllocatedCellsError; use crate::vm::runners::cairo_runner::CairoRunner; - use crate::{ - types::instance_definitions::{ - bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, - }, - utils::test_utils::*, - vm::vm_core::VirtualMachine, - }; + use crate::{utils::test_utils::*, vm::vm_core::VirtualMachine}; use assert_matches::assert_matches; #[cfg(target_arch = "wasm32")] @@ -673,9 +674,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_bitwise() { - let bitwise = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let bitwise = BitwiseBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = bitwise.clone().into(); - assert_eq!(bitwise.n_input_cells, builtin.n_input_cells()) + assert_eq!(INPUT_CELLS_PER_BITWISE, builtin.n_input_cells()) } #[test] @@ -683,31 +684,23 @@ mod tests { fn get_n_input_cells_hash() { let hash = HashBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = hash.clone().into(); - assert_eq!(hash.n_input_cells, builtin.n_input_cells()) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_n_input_cells_range_check() { - let range_check = RangeCheckBuiltinRunner::::new(Some(10), true); - let builtin: BuiltinRunner = range_check.clone().into(); - assert_eq!(range_check.n_input_cells, builtin.n_input_cells()) + assert_eq!(INPUT_CELLS_PER_HASH, builtin.n_input_cells()) } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_ec_op() { - let ec_op = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op.clone().into(); - assert_eq!(ec_op.n_input_cells, builtin.n_input_cells()) + assert_eq!(INPUT_CELLS_PER_EC_OP, builtin.n_input_cells()) } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_ecdsa() { - let signature = SignatureBuiltinRunner::new(&EcdsaInstanceDef::new(Some(10)), true); + let signature = SignatureBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = signature.clone().into(); - assert_eq!(signature.n_input_cells, builtin.n_input_cells()) + assert_eq!(CELLS_PER_SIGNATURE, builtin.n_input_cells()) } #[test] @@ -721,9 +714,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_cells_per_instance_bitwise() { - let bitwise = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let bitwise = BitwiseBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = bitwise.clone().into(); - assert_eq!(bitwise.cells_per_instance, builtin.cells_per_instance()) + assert_eq!(CELLS_PER_BITWISE, builtin.cells_per_instance()) } #[test] @@ -731,31 +724,23 @@ mod tests { fn get_cells_per_instance_hash() { let hash = HashBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = hash.clone().into(); - assert_eq!(hash.cells_per_instance, builtin.cells_per_instance()) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_instance_range_check() { - let range_check = RangeCheckBuiltinRunner::::new(Some(10), true); - let builtin: BuiltinRunner = range_check.clone().into(); - assert_eq!(range_check.cells_per_instance, builtin.cells_per_instance()) + assert_eq!(CELLS_PER_HASH, builtin.cells_per_instance()) } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_cells_per_instance_ec_op() { - let ec_op = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op.clone().into(); - assert_eq!(ec_op.cells_per_instance, builtin.cells_per_instance()) + assert_eq!(CELLS_PER_EC_OP, builtin.cells_per_instance()) } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_cells_per_instance_ecdsa() { - let signature = SignatureBuiltinRunner::new(&EcdsaInstanceDef::new(Some(10)), true); + let signature = SignatureBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = signature.clone().into(); - assert_eq!(signature.cells_per_instance, builtin.cells_per_instance()) + assert_eq!(CELLS_PER_SIGNATURE, builtin.cells_per_instance()) } #[test] @@ -766,18 +751,10 @@ mod tests { assert_eq!(0, builtin.cells_per_instance()) } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_cells_per_instance_keccak() { - let keccak = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let builtin: BuiltinRunner = keccak.clone().into(); - assert_eq!(keccak.cells_per_instance, builtin.cells_per_instance()) - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_name_bitwise() { - let bitwise = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let bitwise = BitwiseBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = bitwise.into(); assert_eq!(BITWISE_BUILTIN_NAME, builtin.name()) } @@ -801,7 +778,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_name_ec_op() { - let ec_op = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op.into(); assert_eq!(EC_OP_BUILTIN_NAME, builtin.name()) } @@ -809,7 +786,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_name_ecdsa() { - let signature = SignatureBuiltinRunner::new(&EcdsaInstanceDef::new(Some(10)), true); + let signature = SignatureBuiltinRunner::new(Some(10), true); let builtin: BuiltinRunner = signature.into(); assert_eq!(SIGNATURE_BUILTIN_NAME, builtin.name()) } @@ -825,10 +802,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_bitwise_with_items() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::new(Some(10)), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(10), true)); let mut vm = vm!(); @@ -872,10 +846,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_ec_op_with_items() { - let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new( - &EcOpInstanceDef::new(Some(10)), - true, - )); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(10), true)); let mut vm = vm!(); @@ -1009,10 +980,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_keccak_with_items() { - let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::new(Some(10), vec![200; 8]), - true, - )); + let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(10), true)); let mut vm = vm!(); vm.current_step = 160; @@ -1021,10 +989,7 @@ mod tests { #[test] fn get_allocated_memory_units_keccak_min_steps_not_reached() { - let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::new(Some(10), vec![200; 8]), - true, - )); + let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(10), true)); let mut vm = vm!(); vm.current_step = 10; @@ -1072,10 +1037,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_bitwise() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.current_step = 256; assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(5)); @@ -1084,8 +1046,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_ec_op() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.current_step = 256; assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(7)); @@ -1094,10 +1055,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_units_keccak() { - let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(2048), true)); let mut vm = vm!(); vm.current_step = 32768; assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(256)); @@ -1132,8 +1090,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_ec_op() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), true)); let memory = memory![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)]; assert_eq!(builtin.get_range_check_usage(&memory), None); } @@ -1141,10 +1098,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_range_check_usage_bitwise() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let memory = memory![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)]; assert_eq!(builtin.get_range_check_usage(&memory), None); } @@ -1152,40 +1106,28 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_bitwise() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); assert_eq!(builtin.get_used_diluted_check_units(270, 7), 1255); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_keccak_zero_case() { - let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(2048), true)); assert_eq!(builtin.get_used_diluted_check_units(270, 7), 0); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_keccak_non_zero_case() { - let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(2048), true)); assert_eq!(builtin.get_used_diluted_check_units(0, 8), 32768); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_diluted_check_units_ec_op() { - let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new( - &EcOpInstanceDef::new(Some(10)), - true, - )); + let builtin = BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(10), true)); assert_eq!(builtin.get_used_diluted_check_units(270, 7), 0); } @@ -1215,11 +1157,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_memory_segment_addresses_test() { - let bitwise_builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let bitwise_builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); assert_eq!(bitwise_builtin.get_memory_segment_addresses(), (0, None),); - let ec_op_builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let ec_op_builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); assert_eq!(ec_op_builtin.get_memory_segment_addresses(), (0, None),); let hash_builtin: BuiltinRunner = HashBuiltinRunner::new(Some(8), true).into(); assert_eq!(hash_builtin.get_memory_segment_addresses(), (0, None),); @@ -1246,10 +1186,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_empty_memory() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let vm = vm!(); // Unused builtin shouldn't fail security checks assert_matches!(builtin.run_security_checks(&vm), Ok(())); @@ -1258,10 +1195,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_empty_offsets() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.memory.data = vec![vec![]]; @@ -1272,10 +1206,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_bitwise_missing_memory_cells_with_offsets() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), true)); let mut vm = vm!(); vm.segments.memory = memory![ ((0, 1), (0, 1)), @@ -1295,22 +1226,13 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_bitwise_missing_memory_cells() { - let mut bitwise_builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); - - bitwise_builtin.cells_per_instance = 2; - bitwise_builtin.n_input_cells = 5; + let bitwise_builtin = BitwiseBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = bitwise_builtin.into(); let mut vm = vm!(); - vm.segments.memory = memory![ - ((0, 0), (0, 1)), - ((0, 1), (0, 2)), - ((0, 2), (0, 3)), - ((0, 3), (0, 4)), - ((0, 4), (0, 5)) - ]; + vm.segments.memory = memory![((0, 4), (0, 5))]; assert_matches!( builtin.run_security_checks(&vm), @@ -1421,8 +1343,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_checks_validate_auto_deductions() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); let mut vm = vm!(); vm.segments @@ -1444,7 +1365,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_ec_op_check_memory_empty() { - let ec_op_builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op_builtin = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op_builtin.into(); @@ -1458,7 +1379,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_ec_op_check_memory_1_element() { - let ec_op_builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op_builtin = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op_builtin.into(); @@ -1476,7 +1397,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_ec_op_check_memory_3_elements() { - let ec_op_builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op_builtin = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op_builtin.into(); @@ -1495,8 +1416,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_ec_op_missing_memory_cells_with_offsets() { - let builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); let mut vm = vm!(); vm.segments.memory = memory![ ((0, 1), (0, 1)), @@ -1518,7 +1438,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_security_ec_op_check_memory_gap() { - let ec_op_builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let ec_op_builtin = EcOpBuiltinRunner::new(Some(256), true); let builtin: BuiltinRunner = ec_op_builtin.into(); @@ -1551,8 +1471,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_perm_range_check_units_bitwise() { - let builtin_runner: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let builtin_runner: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); let mut vm = vm!(); vm.current_step = 8; @@ -1565,8 +1484,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_perm_range_check_units_ec_op() { - let builtin_runner: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let builtin_runner: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); let mut vm = vm!(); vm.current_step = 8; @@ -1617,11 +1535,9 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_ratio_tests() { - let bitwise_builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let bitwise_builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); assert_eq!(bitwise_builtin.ratio(), (Some(256)),); - let ec_op_builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let ec_op_builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); assert_eq!(ec_op_builtin.ratio(), (Some(256)),); let hash_builtin: BuiltinRunner = HashBuiltinRunner::new(Some(8), true).into(); assert_eq!(hash_builtin.ratio(), (Some(8)),); @@ -1631,8 +1547,7 @@ mod tests { RangeCheckBuiltinRunner::::new(Some(8), true), ); assert_eq!(range_check_builtin.ratio(), (Some(8)),); - let keccak_builtin: BuiltinRunner = - KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + let keccak_builtin: BuiltinRunner = KeccakBuiltinRunner::new(Some(2048), true).into(); assert_eq!(keccak_builtin.ratio(), (Some(2048)),); } @@ -1642,8 +1557,7 @@ mod tests { let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); - let bitwise_builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + let bitwise_builtin: BuiltinRunner = BitwiseBuiltinRunner::new(Some(256), true).into(); assert_eq!(bitwise_builtin.get_used_instances(&vm.segments), Ok(1)); } @@ -1653,8 +1567,7 @@ mod tests { let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); - let ec_op_builtin: BuiltinRunner = - EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + let ec_op_builtin: BuiltinRunner = EcOpBuiltinRunner::new(Some(256), true).into(); assert_eq!(ec_op_builtin.get_used_instances(&vm.segments), Ok(1)); } @@ -1693,25 +1606,16 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn runners_final_stack() { let mut builtins = vec![ - BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - false, - )), - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), false)), + BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), false)), + BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), false)), BuiltinRunner::Hash(HashBuiltinRunner::new(Some(1), false)), BuiltinRunner::Output(OutputBuiltinRunner::new(false)), BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::::new( Some(8), false, )), - BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::default(), - false, - )), - BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - false, - )), + BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(2048), false)), + BuiltinRunner::Signature(SignatureBuiltinRunner::new(Some(512), false)), ]; let vm = vm!(); @@ -1724,25 +1628,16 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn runners_set_stop_ptr() { let builtins = vec![ - BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - false, - )), - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), false)), + BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new(Some(256), false)), + BuiltinRunner::EcOp(EcOpBuiltinRunner::new(Some(256), false)), BuiltinRunner::Hash(HashBuiltinRunner::new(Some(1), false)), BuiltinRunner::Output(OutputBuiltinRunner::new(false)), BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::::new( Some(8), false, )), - BuiltinRunner::Keccak(KeccakBuiltinRunner::new( - &KeccakInstanceDef::default(), - false, - )), - BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - false, - )), + BuiltinRunner::Keccak(KeccakBuiltinRunner::new(Some(2048), false)), + BuiltinRunner::Signature(SignatureBuiltinRunner::new(Some(512), false)), BuiltinRunner::Poseidon(PoseidonBuiltinRunner::new(Some(32), false)), BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(false)), ]; diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 924aa65a28..91254d4789 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -9,7 +9,7 @@ use crate::{ }, types::{ errors::math_errors::MathError, - instance_definitions::mod_instance_def::{ModInstanceDef, N_WORDS}, + instance_definitions::mod_instance_def::{ModInstanceDef, CELLS_PER_MOD, N_WORDS}, relocatable::{relocate_address, MaybeRelocatable, Relocatable}, }, vm::{ @@ -31,8 +31,6 @@ use num_traits::Zero; //The maximum n value that the function fill_memory accepts. const FILL_MEMORY_MAX: usize = 100000; -const INPUT_CELLS: usize = 7; - const VALUES_PTR_OFFSET: u32 = 4; const OFFSETS_PTR_OFFSET: u32 = 5; const N_OFFSET: u32 = 6; @@ -146,14 +144,6 @@ impl ModBuiltinRunner { self.instance_def.ratio } - pub fn cells_per_instance(&self) -> u32 { - INPUT_CELLS as u32 - } - - pub fn n_input_cells(&self) -> u32 { - INPUT_CELLS as u32 - } - pub fn batch_size(&self) -> usize { self.instance_def.batch_size } @@ -169,7 +159,7 @@ impl ModBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance() as usize)) + Ok(div_ceil(used_cells, CELLS_PER_MOD as usize)) } pub(crate) fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { @@ -179,8 +169,11 @@ impl ModBuiltinRunner { .unwrap_or_default(); let relocation_table = segments.relocate_segments().unwrap_or_default(); let mut instances = Vec::::new(); - for instance in 0..segment_size.checked_div(INPUT_CELLS).unwrap_or_default() { - let instance_addr_offset = instance * INPUT_CELLS; + for instance in 0..segment_size + .checked_div(CELLS_PER_MOD as usize) + .unwrap_or_default() + { + let instance_addr_offset = instance * CELLS_PER_MOD as usize; let values_ptr = segments .memory .get_relocatable( @@ -382,7 +375,7 @@ impl ModBuiltinRunner { } let n_instances = safe_div_usize(inputs.n, self.instance_def.batch_size)?; for instance in 1..n_instances { - let instance_ptr = (builtin_ptr + instance * INPUT_CELLS)?; + let instance_ptr = (builtin_ptr + instance * CELLS_PER_MOD as usize)?; for i in 0..N_WORDS { memory.insert_as_accessed((instance_ptr + i)?, &inputs.p_values[i])?; } @@ -600,12 +593,12 @@ impl ModBuiltinRunner { let segment_size = vm .get_segment_used_size(self.base) .ok_or(MemoryError::MissingSegmentUsedSizes)?; - let n_instances = div_ceil(segment_size, INPUT_CELLS); + let n_instances = div_ceil(segment_size, CELLS_PER_MOD as usize); let mut prev_inputs = Inputs::default(); for instance in 0..n_instances { let inputs = self.read_inputs( &vm.segments.memory, - (self.base as isize, instance * INPUT_CELLS).into(), + (self.base as isize, instance * CELLS_PER_MOD as usize).into(), )?; if !instance.is_zero() && prev_inputs.n > self.instance_def.batch_size { for i in 0..N_WORDS { diff --git a/vm/src/vm/runners/builtin_runner/poseidon.rs b/vm/src/vm/runners/builtin_runner/poseidon.rs index d2a4ce9c18..51b12cc73b 100644 --- a/vm/src/vm/runners/builtin_runner/poseidon.rs +++ b/vm/src/vm/runners/builtin_runner/poseidon.rs @@ -19,12 +19,9 @@ use super::POSEIDON_BUILTIN_NAME; pub struct PoseidonBuiltinRunner { pub base: usize, ratio: Option, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, pub(crate) stop_ptr: Option, pub(crate) included: bool, cache: RefCell>, - pub(crate) instances_per_component: u32, } impl PoseidonBuiltinRunner { @@ -32,12 +29,9 @@ impl PoseidonBuiltinRunner { PoseidonBuiltinRunner { base: 0, ratio, - cells_per_instance: CELLS_PER_POSEIDON, - n_input_cells: INPUT_CELLS_PER_POSEIDON, stop_ptr: None, included, cache: RefCell::new(HashMap::new()), - instances_per_component: 1, } } @@ -68,19 +62,19 @@ impl PoseidonBuiltinRunner { address: Relocatable, memory: &Memory, ) -> Result, RunnerError> { - let index = address.offset % self.cells_per_instance as usize; - if index < self.n_input_cells as usize { + let index = address.offset % CELLS_PER_POSEIDON as usize; + if index < INPUT_CELLS_PER_POSEIDON as usize { return Ok(None); } if let Some(felt) = self.cache.borrow().get(&address) { return Ok(Some(felt.into())); } let first_input_addr = (address - index)?; - let first_output_addr = (first_input_addr + self.n_input_cells as usize)?; + let first_output_addr = (first_input_addr + INPUT_CELLS_PER_POSEIDON as usize)?; let mut input_felts = vec![]; - for i in 0..self.n_input_cells as usize { + for i in 0..INPUT_CELLS_PER_POSEIDON as usize { let m_index = (first_input_addr + i)?; let val = match memory.get(&m_index) { Some(value) => { @@ -121,7 +115,7 @@ impl PoseidonBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_POSEIDON as usize)) } pub fn air_private_input(&self, memory: &Memory) -> Vec { diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index cb02e79b46..b9b49992ce 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -8,10 +8,7 @@ use crate::{ use crate::Felt252; use crate::{ - types::{ - instance_definitions::range_check_instance_def::CELLS_PER_RANGE_CHECK, - relocatable::{MaybeRelocatable, Relocatable}, - }, + types::relocatable::{MaybeRelocatable, Relocatable}, vm::{ errors::memory_errors::MemoryError, vm_memory::{ @@ -42,10 +39,7 @@ pub struct RangeCheckBuiltinRunner { ratio: Option, base: usize, pub(crate) stop_ptr: Option, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, pub(crate) included: bool, - pub(crate) instances_per_component: u32, } impl RangeCheckBuiltinRunner { @@ -54,10 +48,7 @@ impl RangeCheckBuiltinRunner { ratio, base: 0, stop_ptr: None, - cells_per_instance: CELLS_PER_RANGE_CHECK, - n_input_cells: CELLS_PER_RANGE_CHECK, included, - instances_per_component: 1, } } diff --git a/vm/src/vm/runners/builtin_runner/segment_arena.rs b/vm/src/vm/runners/builtin_runner/segment_arena.rs index b505ac3813..5cc61fb706 100644 --- a/vm/src/vm/runners/builtin_runner/segment_arena.rs +++ b/vm/src/vm/runners/builtin_runner/segment_arena.rs @@ -8,7 +8,7 @@ use crate::{ use alloc::vec::Vec; use num_integer::div_ceil; -const ARENA_BUILTIN_SIZE: u32 = 3; +pub(crate) const ARENA_BUILTIN_SIZE: u32 = 3; // The size of the builtin segment at the time of its creation. const INITIAL_SEGMENT_SIZE: usize = ARENA_BUILTIN_SIZE as usize; @@ -16,8 +16,6 @@ const INITIAL_SEGMENT_SIZE: usize = ARENA_BUILTIN_SIZE as usize; pub struct SegmentArenaBuiltinRunner { base: Relocatable, pub(crate) included: bool, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells_per_instance: u32, pub(crate) stop_ptr: Option, } @@ -26,8 +24,6 @@ impl SegmentArenaBuiltinRunner { SegmentArenaBuiltinRunner { base: Relocatable::from((0, 0)), included, - cells_per_instance: ARENA_BUILTIN_SIZE, - n_input_cells_per_instance: ARENA_BUILTIN_SIZE, stop_ptr: None, } } @@ -67,7 +63,7 @@ impl SegmentArenaBuiltinRunner { ) -> Result { Ok(div_ceil( self.get_used_cells(segments)?, - self.cells_per_instance as usize, + ARENA_BUILTIN_SIZE as usize, )) } diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index 3d5fa6ef45..9520ebcc63 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -7,10 +7,7 @@ use crate::types::instance_definitions::ecdsa_instance_def::CELLS_PER_SIGNATURE; use crate::vm::runners::cairo_pie::BuiltinAdditionalData; use crate::Felt252; use crate::{ - types::{ - instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, - relocatable::{MaybeRelocatable, Relocatable}, - }, + types::relocatable::{MaybeRelocatable, Relocatable}, vm::{ errors::memory_errors::MemoryError, vm_memory::{ @@ -38,25 +35,17 @@ pub struct SignatureBuiltinRunner { pub(crate) included: bool, ratio: Option, base: usize, - pub(crate) cells_per_instance: u32, - pub(crate) n_input_cells: u32, - _total_n_bits: u32, pub(crate) stop_ptr: Option, - pub(crate) instances_per_component: u32, signatures: Rc>>, } impl SignatureBuiltinRunner { - pub(crate) fn new(instance_def: &EcdsaInstanceDef, included: bool) -> Self { + pub(crate) fn new(ratio: Option, included: bool) -> Self { SignatureBuiltinRunner { base: 0, included, - ratio: instance_def.ratio, - cells_per_instance: 2, - n_input_cells: 2, - _total_n_bits: 251, + ratio, stop_ptr: None, - instances_per_component: 1, signatures: Rc::new(RefCell::new(HashMap::new())), } } @@ -104,7 +93,7 @@ impl SignatureBuiltinRunner { self.base } pub fn add_validation_rule(&self, memory: &mut Memory) { - let cells_per_instance = self.cells_per_instance; + let cells_per_instance = CELLS_PER_SIGNATURE; let signatures = Rc::clone(&self.signatures); let rule: ValidationRule = ValidationRule(Box::new( move |memory: &Memory, addr: Relocatable| -> Result, MemoryError> { @@ -169,7 +158,7 @@ impl SignatureBuiltinRunner { segments: &MemorySegmentManager, ) -> Result { let used_cells = self.get_used_cells(segments)?; - Ok(div_ceil(used_cells, self.cells_per_instance as usize)) + Ok(div_ceil(used_cells, CELLS_PER_SIGNATURE as usize)) } pub fn get_additional_data(&self) -> BuiltinAdditionalData { @@ -227,7 +216,6 @@ mod tests { use super::*; use crate::{ relocatable, - types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, utils::test_utils::*, vm::{ errors::{ @@ -246,8 +234,7 @@ mod tests { #[test] fn get_used_cells_and_allocated_size_valid() { - let builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::new(Some(10)), true).into(); + let builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); vm.current_step = 110; vm.segments.segment_used_sizes = Some(vec![1]); @@ -257,7 +244,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initialize_segments_for_ecdsa() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin = SignatureBuiltinRunner::new(Some(512), true); let mut segments = MemorySegmentManager::new(); builtin.initialize_segments(&mut segments); assert_eq!(builtin.base, 0); @@ -266,8 +253,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_instances() { - let builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![1]); @@ -278,8 +264,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let mut builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); @@ -303,8 +288,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let mut builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); @@ -332,8 +316,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let mut builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); @@ -357,10 +340,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new(Some(512), true)); let vm = vm!(); assert_eq!( @@ -372,10 +352,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_empty() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new(Some(512), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![0]); @@ -385,10 +362,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); + let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new(Some(512), true)); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![4]); @@ -398,7 +372,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_initial_stack_for_range_check_with_base() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin = SignatureBuiltinRunner::new(Some(512), true); builtin.base = 1; let initial_stack = builtin.initial_stack(); assert_eq!( @@ -411,7 +385,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stack_not_included_test() { - let ecdsa_builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), false); + let ecdsa_builtin = SignatureBuiltinRunner::new(Some(512), false); assert_eq!(ecdsa_builtin.initial_stack(), Vec::new()) } @@ -419,8 +393,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_test() { let memory = Memory::new(); - let builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); assert_eq!(result, Ok(None)); } @@ -428,22 +401,21 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_ratio() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let builtin = SignatureBuiltinRunner::new(Some(512), true); assert_eq!(builtin.ratio(), Some(512)); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_base() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let builtin = SignatureBuiltinRunner::new(Some(512), true); assert_eq!(builtin.base(), 0); } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_min_step_not_reached() { - let builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); vm.current_step = 500; assert_eq!( @@ -460,8 +432,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_and_allocated_size_insufficient_allocated() { - let builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); vm.segments.segment_used_sizes = Some(vec![50]); vm.current_step = 512; @@ -480,8 +451,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_invalid_stop_pointer() { - let mut builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let mut builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (1, 0))]; assert_eq!( @@ -497,8 +467,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_no_used_instances() { - let mut builtin: BuiltinRunner = - SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); + let mut builtin: BuiltinRunner = SignatureBuiltinRunner::new(Some(512), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (0, 0))]; assert_eq!( @@ -509,7 +478,7 @@ mod tests { #[test] fn get_additional_info() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin = SignatureBuiltinRunner::new(Some(512), true); let signatures = HashMap::from([( Relocatable::from((4, 0)), Signature { diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index f405e672af..c61c39b071 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -7,7 +7,7 @@ use crate::{ ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, prelude::*, }, - types::instance_definitions::keccak_instance_def::KeccakInstanceDef, + types::layout::MEMORY_UNITS_PER_STEP, vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry}, @@ -22,10 +22,6 @@ use crate::{ types::{ errors::{math_errors::MathError, program_errors::ProgramError}, exec_scope::ExecutionScopes, - instance_definitions::{ - bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, - ecdsa_instance_def::EcdsaInstanceDef, - }, layout::CairoLayout, program::Program, relocatable::{relocate_address, relocate_value, MaybeRelocatable, Relocatable}, @@ -303,28 +299,30 @@ impl CairoRunner { if let Some(instance_def) = self.layout.builtins.ecdsa.as_ref() { let included = program_builtins.remove(&BuiltinName::ecdsa); if included || self.is_proof_mode() { - builtin_runners.push(SignatureBuiltinRunner::new(instance_def, included).into()); + builtin_runners + .push(SignatureBuiltinRunner::new(instance_def.ratio, included).into()); } } if let Some(instance_def) = self.layout.builtins.bitwise.as_ref() { let included = program_builtins.remove(&BuiltinName::bitwise); if included || self.is_proof_mode() { - builtin_runners.push(BitwiseBuiltinRunner::new(instance_def, included).into()); + builtin_runners + .push(BitwiseBuiltinRunner::new(instance_def.ratio, included).into()); } } if let Some(instance_def) = self.layout.builtins.ec_op.as_ref() { let included = program_builtins.remove(&BuiltinName::ec_op); if included || self.is_proof_mode() { - builtin_runners.push(EcOpBuiltinRunner::new(instance_def, included).into()); + builtin_runners.push(EcOpBuiltinRunner::new(instance_def.ratio, included).into()); } } if let Some(instance_def) = self.layout.builtins.keccak.as_ref() { let included = program_builtins.remove(&BuiltinName::keccak); if included || self.is_proof_mode() { - builtin_runners.push(KeccakBuiltinRunner::new(instance_def, included).into()); + builtin_runners.push(KeccakBuiltinRunner::new(instance_def.ratio, included).into()); } } @@ -360,7 +358,7 @@ impl CairoRunner { if !program_builtins.is_empty() && !allow_missing_builtins { return Err(RunnerError::NoBuiltinForInstance(Box::new(( program_builtins.iter().map(|n| n.name()).collect(), - self.layout._name.clone(), + self.layout.name.clone(), )))); } @@ -406,19 +404,18 @@ impl CairoRunner { BuiltinName::output => vm .builtin_runners .push(OutputBuiltinRunner::new(true).into()), - BuiltinName::ecdsa => vm.builtin_runners.push( - SignatureBuiltinRunner::new(&EcdsaInstanceDef::new(Some(1)), true).into(), - ), - BuiltinName::bitwise => vm.builtin_runners.push( - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(1)), true).into(), - ), + BuiltinName::ecdsa => vm + .builtin_runners + .push(SignatureBuiltinRunner::new(Some(1), true).into()), + BuiltinName::bitwise => vm + .builtin_runners + .push(BitwiseBuiltinRunner::new(Some(1), true).into()), BuiltinName::ec_op => vm .builtin_runners - .push(EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(1)), true).into()), - BuiltinName::keccak => vm.builtin_runners.push( - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(1), vec![200; 8]), true) - .into(), - ), + .push(EcOpBuiltinRunner::new(Some(1), true).into()), + BuiltinName::keccak => vm + .builtin_runners + .push(KeccakBuiltinRunner::new(Some(1), true).into()), BuiltinName::poseidon => vm .builtin_runners .push(PoseidonBuiltinRunner::new(Some(1), true).into()), @@ -1207,13 +1204,13 @@ impl CairoRunner { // Out of the memory units available per step, a fraction is used for public memory, and // four are used for the instruction. - let total_memory_units = instance._memory_units_per_step * vm_current_step_u32; + let total_memory_units = MEMORY_UNITS_PER_STEP * vm_current_step_u32; let (public_memory_units, rem) = - div_rem(total_memory_units, instance._public_memory_fraction); + div_rem(total_memory_units, instance.public_memory_fraction); if rem != 0 { return Err(MathError::SafeDivFailU32( total_memory_units, - instance._public_memory_fraction, + instance.public_memory_fraction, ) .into()); } @@ -1459,7 +1456,7 @@ impl CairoRunner { &self, vm: &VirtualMachine, ) -> Result { - let layout_name = self.get_layout()._name.as_str(); + let layout_name = self.get_layout().name.as_str(); let dyn_layout = match layout_name { "dynamic" => Some(self.get_layout()), _ => None, @@ -1638,7 +1635,6 @@ mod tests { hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, relocatable, serde::deserialize_program::{Identifier, ReferenceManager}, - types::instance_definitions::bitwise_instance_def::BitwiseInstanceDef, utils::test_utils::*, vm::trace::trace_entry::TraceEntry, }; @@ -3789,8 +3785,7 @@ mod tests { let mut vm = vm!(); vm.current_step = 8192; - vm.builtin_runners = - vec![BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into()]; + vm.builtin_runners = vec![BitwiseBuiltinRunner::new(Some(256), true).into()]; assert_matches!(cairo_runner.check_diluted_check_usage(&vm), Ok(())); } @@ -4858,7 +4853,7 @@ mod tests { cairo_runner.segments_finalized = false; let mut vm = vm!(); let output_builtin = OutputBuiltinRunner::new(true); - let bitwise_builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let bitwise_builtin = BitwiseBuiltinRunner::new(Some(256), true); vm.builtin_runners.push(output_builtin.into()); vm.builtin_runners.push(bitwise_builtin.into()); cairo_runner.initialize_segments(&mut vm, None); diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index eac5544536..42a02b80fb 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1256,9 +1256,6 @@ mod tests { }, relocatable, types::{ - instance_definitions::{ - bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, - }, instruction::{Op1Addr, Register}, relocatable::Relocatable, }, @@ -3380,7 +3377,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_bitwise_builtin_valid_and() { let mut vm = vm!(); - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let builtin = BitwiseBuiltinRunner::new(Some(256), true); vm.builtin_runners.push(builtin.into()); vm.segments = segments![((0, 5), 10), ((0, 6), 12), ((0, 7), 0)]; assert_matches!( @@ -3418,7 +3415,7 @@ mod tests { opcode: Opcode::AssertEq, }; - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let mut builtin = BitwiseBuiltinRunner::new(Some(256), true); builtin.base = 2; let mut vm = vm!(); @@ -3459,7 +3456,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_ec_op_builtin_valid() { let mut vm = vm!(); - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let builtin = EcOpBuiltinRunner::new(Some(256), true); vm.builtin_runners.push(builtin.into()); vm.segments = segments![ @@ -3528,7 +3525,7 @@ mod tests { end */ fn verify_auto_deductions_for_ec_op_builtin_valid() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let mut builtin = EcOpBuiltinRunner::new(Some(256), true); builtin.base = 3; let mut vm = vm!(); vm.builtin_runners.push(builtin.into()); @@ -3576,7 +3573,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn verify_auto_deductions_for_ec_op_builtin_valid_points_invalid_result() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); + let mut builtin = EcOpBuiltinRunner::new(Some(256), true); builtin.base = 3; let mut vm = vm!(); vm.builtin_runners.push(builtin.into()); @@ -3647,7 +3644,7 @@ mod tests { end */ fn verify_auto_deductions_bitwise() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let mut builtin = BitwiseBuiltinRunner::new(Some(256), true); builtin.base = 2; let mut vm = vm!(); vm.builtin_runners.push(builtin.into()); @@ -3670,7 +3667,7 @@ mod tests { end */ fn verify_auto_deductions_for_addr_bitwise() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let mut builtin = BitwiseBuiltinRunner::new(Some(256), true); builtin.base = 2; let builtin: BuiltinRunner = builtin.into(); let mut vm = vm!(); @@ -3860,7 +3857,7 @@ mod tests { fn test_get_builtin_runners() { let mut vm = vm!(); let hash_builtin = HashBuiltinRunner::new(Some(8), true); - let bitwise_builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); + let bitwise_builtin = BitwiseBuiltinRunner::new(Some(256), true); vm.builtin_runners.push(hash_builtin.into()); vm.builtin_runners.push(bitwise_builtin.into()); diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index c776fa14d3..2ea342573b 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -629,7 +629,6 @@ mod memory_tests { use super::*; use crate::{ felt_hex, relocatable, - types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, utils::test_utils::*, vm::{ runners::builtin_runner::{ @@ -855,7 +854,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_invalid_signature() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin = SignatureBuiltinRunner::new(Some(512), true); let mut segments = MemorySegmentManager::new(); builtin.initialize_segments(&mut segments); segments.memory = memory![ @@ -885,7 +884,7 @@ mod memory_tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn validate_existing_memory_for_valid_signature() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin = SignatureBuiltinRunner::new(Some(512), true); let signature_r = felt_hex!("0x411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20"); From 95d2c88fcd4ef799ff64fc392efd633c05fa37e4 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Mon, 15 Apr 2024 20:11:04 +0200 Subject: [PATCH 32/36] feat: missing EC hints for Starknet OS 0.13.1 (#1706) * feat: missing EC hints for Starknet OS 0.13.1 * changelog * fix clippy --- CHANGELOG.md | 2 ++ fuzzer/src/fuzz_json.rs | 6 +++-- .../builtin_hint_processor_definition.rs | 24 ++++++++++++++++++- .../builtin_hint_processor/hint_code.rs | 22 ++++++++++++++++- .../builtin_hint_processor/secp/ec_utils.rs | 4 +++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f51c9cb46c..3fbaa1b980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * refactor: Remove unused code & use constants whenever possible for builtin instance definitions[#1707](https://github.com/lambdaclass/cairo-vm/pull/1707) +* feat: missing EC hints for Starknet OS 0.13.1 [#1706](https://github.com/lambdaclass/cairo-vm/pull/1706) + * fix(BREAKING): Use program builtins in `initialize_main_entrypoint` & `read_return_values`[#1703](https://github.com/lambdaclass/cairo-vm/pull/1703) * `initialize_main_entrypoint` now iterates over the program builtins when building the stack & inserts 0 for any missing builtin * `read_return_values` now only computes the final stack of the builtins in the program diff --git a/fuzzer/src/fuzz_json.rs b/fuzzer/src/fuzz_json.rs index 09193c512b..4306a29c67 100644 --- a/fuzzer/src/fuzz_json.rs +++ b/fuzzer/src/fuzz_json.rs @@ -29,7 +29,7 @@ const HEX_SYMBOLS: [&str; 16] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", ]; -const HINTS_CODE: [&str; 184] = [ +const HINTS_CODE: [&str; 186] = [ ADD_SEGMENT, VM_ENTER_SCOPE, VM_EXIT_SCOPE, @@ -139,10 +139,12 @@ const HINTS_CODE: [&str; 184] = [ EC_DOUBLE_SLOPE_V1, EC_DOUBLE_SLOPE_V2, EC_DOUBLE_SLOPE_V3, + EC_DOUBLE_SLOPE_V4, EC_DOUBLE_SLOPE_EXTERNAL_CONSTS, COMPUTE_SLOPE_V1, COMPUTE_SLOPE_V2, - COMPUTE_SLOPE_SECP256R1, + COMPUTE_SLOPE_SECP256R1_V1, + COMPUTE_SLOPE_SECP256R1_V2, IMPORT_SECP256R1_P, COMPUTE_SLOPE_WHITELIST, EC_DOUBLE_ASSIGN_NEW_X_V1, 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 e785949884..3120b91d03 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 @@ -119,6 +119,9 @@ use crate::hint_processor::builtin_hint_processor::skip_next_instruction::skip_n #[cfg(feature = "print")] use crate::hint_processor::builtin_hint_processor::print::{print_array, print_dict, print_felt}; +use crate::hint_processor::builtin_hint_processor::secp::secp_utils::{ + SECP256R1_ALPHA, SECP256R1_P, +}; use super::blake2s_utils::example_blake2s_compress; @@ -532,6 +535,15 @@ impl HintProcessorLogic for BuiltinHintProcessor { &SECP_P, &ALPHA, ), + hint_code::EC_DOUBLE_SLOPE_V4 => compute_doubling_slope( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + "point", + &SECP256R1_P, + &SECP256R1_ALPHA, + ), hint_code::EC_DOUBLE_SLOPE_EXTERNAL_CONSTS => compute_doubling_slope_external_consts( vm, exec_scopes, @@ -559,13 +571,23 @@ impl HintProcessorLogic for BuiltinHintProcessor { "point1", &SECP_P_V2, ), - hint_code::COMPUTE_SLOPE_SECP256R1 => compute_slope( + hint_code::COMPUTE_SLOPE_SECP256R1_V1 => compute_slope( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + "point0", + "point1", + "SECP_P", + ), + hint_code::COMPUTE_SLOPE_SECP256R1_V2 => compute_slope( vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking, "point0", "point1", + "SECP256R1_P", ), hint_code::IMPORT_SECP256R1_P => import_secp256r1_p(exec_scopes), hint_code::COMPUTE_SLOPE_WHITELIST => compute_slope_and_assing_secp_p( 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 563fe0d7e0..03bab59189 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -694,6 +694,15 @@ x = pack(ids.pt.x, PRIME) y = pack(ids.pt.y, PRIME) value = slope = div_mod(3 * x ** 2, 2 * y, SECP_P)"#; +pub const EC_DOUBLE_SLOPE_V4: &str = r#"from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA, SECP256R1_P +from starkware.cairo.common.cairo_secp.secp_utils import pack +from starkware.python.math_utils import ec_double_slope + +# Compute the slope. +x = pack(ids.point.x, SECP256R1_P) +y = pack(ids.point.y, SECP256R1_P) +value = slope = ec_double_slope(point=(x, y), alpha=SECP256R1_ALPHA, p=SECP256R1_P)"#; + pub const EC_DOUBLE_SLOPE_EXTERNAL_CONSTS: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import pack from starkware.python.math_utils import ec_double_slope @@ -722,7 +731,7 @@ x1 = pack(ids.point1.x, PRIME) y1 = pack(ids.point1.y, PRIME) value = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P)"#; -pub const COMPUTE_SLOPE_SECP256R1: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import pack +pub const COMPUTE_SLOPE_SECP256R1_V1: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import pack from starkware.python.math_utils import line_slope # Compute the slope. @@ -732,6 +741,17 @@ x1 = pack(ids.point1.x, PRIME) y1 = pack(ids.point1.y, PRIME) value = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P)"#; +pub const COMPUTE_SLOPE_SECP256R1_V2: &str = r#"from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P +from starkware.cairo.common.cairo_secp.secp_utils import pack +from starkware.python.math_utils import line_slope + +# Compute the slope. +x0 = pack(ids.point0.x, PRIME) +y0 = pack(ids.point0.y, PRIME) +x1 = pack(ids.point1.x, PRIME) +y1 = pack(ids.point1.y, PRIME) +value = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP256R1_P)"#; + pub const IMPORT_SECP256R1_P: &str = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P"; diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs b/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs index d4dbc3b910..fdafd49e07 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs @@ -198,6 +198,7 @@ pub fn compute_slope_and_assing_secp_p( ap_tracking, point0_alias, point1_alias, + "SECP_P", ) } @@ -208,13 +209,14 @@ pub fn compute_slope( ap_tracking: &ApTracking, point0_alias: &str, point1_alias: &str, + secp_p_name: &str, ) -> Result<(), HintError> { //ids.point0 let point0 = EcPoint::from_var_name(point0_alias, vm, ids_data, ap_tracking)?; //ids.point1 let point1 = EcPoint::from_var_name(point1_alias, vm, ids_data, ap_tracking)?; - let secp_p: BigInt = exec_scopes.get("SECP_P")?; + let secp_p: BigInt = exec_scopes.get(secp_p_name)?; let value = line_slope( &(point0.x.pack86(), point0.y.pack86()), From 8c24ca51e0c365bf12015d4c390a0698f17ec888 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:24:09 -0300 Subject: [PATCH 33/36] Remove clippy allow (#1655) Co-authored-by: Pedro Fontana --- vm/src/vm/runners/cairo_pie.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 248a15828f..16bcb80d41 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -188,7 +188,6 @@ mod serde_impl { seq_serializer.end() } - #[allow(clippy::format_collect)] pub fn serialize_memory( values: &[((usize, usize), MaybeRelocatable)], serializer: S, @@ -226,12 +225,11 @@ mod serde_impl { }; } - serializer.serialize_str( - res.iter() - .map(|b| format!("{:02x}", b)) - .collect::() - .as_str(), - ) + let string = res + .iter() + .fold(String::new(), |string, b| string + &format!("{:02x}", b)); + + serializer.serialize_str(&string) } impl CairoPieMemory { From 6ff81262a6d5e201c6468e31d61da0b830151bcc Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:24:40 -0300 Subject: [PATCH 34/36] Add integration test using `range_check96`, `add_mod` & `mul_mod` builtins (#1699) * Add integration test * fmt --------- Co-authored-by: Pedro Fontana --- .../mod_builtin_feature/apply_poly.cairo | 140 ++++++++++++++++++ .../proof/apply_poly.cairo | 1 + vm/src/tests/cairo_run_test.rs | 16 ++ 3 files changed, 157 insertions(+) create mode 100644 cairo_programs/mod_builtin_feature/apply_poly.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/apply_poly.cairo diff --git a/cairo_programs/mod_builtin_feature/apply_poly.cairo b/cairo_programs/mod_builtin_feature/apply_poly.cairo new file mode 100644 index 0000000000..da42bd84c6 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/apply_poly.cairo @@ -0,0 +1,140 @@ +%builtins range_check range_check96 add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit +// from starkware.cairo.common.modulo import run_mod_p_circuit +// from starkware.cairo.common.cairo_builtins import ModBuiltin, UInt384 +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.memcpy import memcpy +from starkware.cairo.common.alloc import alloc + +// Computes the polynomial f(x) = x^8 + 5*x^2 + 1. +func apply_poly{ + range_check_ptr, + range_check96_ptr: felt*, + add_mod_ptr: ModBuiltin*, + mul_mod_ptr: ModBuiltin* +}(x: UInt384*, p: UInt384) -> (res: UInt384*) { + + // Copy inputs and constants into the values_ptr segment. + memcpy(dst=range_check96_ptr, src=x, len=UInt384.SIZE); + let (constants_ptr) = get_label_location(constants); + memcpy(dst=range_check96_ptr + UInt384.SIZE, src=constants_ptr, len=2 * UInt384.SIZE); + let values_ptr = cast(range_check96_ptr, UInt384*); + let range_check96_ptr = range_check96_ptr + 36; + + + let (add_mod_offsets_ptr) = get_label_location(add_offsets); + let (mul_mod_offsets_ptr) = get_label_location(mul_offsets); + run_mod_p_circuit( + p=p, + values_ptr=values_ptr, + add_mod_offsets_ptr=add_mod_offsets_ptr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_ptr, + mul_mod_n=4, + ); + + return (res=values_ptr + 32); + + // values_ptr points to a segment within the range_check96_ptr segment that looks like this: + // + // offset value + // 0 x + // 4 1 + // 8 5 + // 12 x^2 + // 16 x^4 + // 20 x^8 + // 24 5*x^2 + // 28 x^8 + 5*x^2 + // 32 x^8 + 5*x^2 + 1 + + constants: + dw 1; + dw 0; + dw 0; + dw 0; + + dw 5; + dw 0; + dw 0; + dw 0; + + add_offsets: + dw 20; // x^8 + dw 24; // 5*x^2 + dw 28; // x^8 + 5*x^2 + + dw 4; // 1 + dw 28; // x^8 + 5*x^2 + dw 32; // x^8 + 5*x^2 + 1 + + // Placeholders (copies of the first 3 offsets): + dw 20; + dw 24; + dw 28; + dw 20; + dw 24; + dw 28; + dw 20; + dw 24; + dw 28; + dw 20; + dw 24; + dw 28; + dw 20; + dw 24; + dw 28; + dw 20; + dw 24; + dw 28; + + + mul_offsets: + dw 0; // x + dw 0; // x + dw 12; // x^2 + + dw 12; // x^2 + dw 12; // x^2 + dw 16; // x^4 + + dw 16; // x^4 + dw 16; // x^4 + dw 20; // x^8 + + dw 8; // 5 + dw 12; // x^2 + dw 24; // 5*x^2 + + // Placeholders (copies of the first 3 offsets): + dw 0; + dw 0; + dw 12; + dw 0; + dw 0; + dw 12; + dw 0; + dw 0; + dw 12; + dw 0; + dw 0; + dw 12; +} + +func main{range_check_ptr, range_check96_ptr: felt*, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=0xffff, d1=0xffff, d2=0xffff, d3=0xffff); + let (local inputs: UInt384*) = alloc(); + assert inputs[0] = UInt384(d0=0xbbbb, d1=0xaaaa, d2=0x6666, d3=0xffff); + + let res: UInt384* = apply_poly(inputs, p); + + assert res[0].d0 = 0xdb0030d69941baf9893cd667; + assert res[0].d1 = 0xfffffffffffffffee43128e7; + assert res[0].d2 = 0xfd4c69cdf6010eab465c3055; + assert res[0].d3 = 0xea52; + + return(); +} diff --git a/cairo_programs/mod_builtin_feature/proof/apply_poly.cairo b/cairo_programs/mod_builtin_feature/proof/apply_poly.cairo new file mode 120000 index 0000000000..70d9ba18e4 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/apply_poly.cairo @@ -0,0 +1 @@ +../apply_poly.cairo \ No newline at end of file diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index bf44200dd3..fc1023d23e 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1222,3 +1222,19 @@ fn run_program_with_custom_mod_builtin_params( security_res.unwrap(); } } + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_apply_poly() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/apply_poly.json"); + run_program(program_data, false, Some("all_cairo"), None, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_apply_poly_proof() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/proof/apply_poly.json"); + run_program(program_data, true, Some("all_cairo"), None, None); +} From e629428182c6da751d251814f8357ad92c837e1a Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:00:45 -0300 Subject: [PATCH 35/36] Bump `starknet-types-core` version + Use the lib's pedersen hash (#1692) * Bump core types and use its pedersen hash func * Add Changelog entry * Fix param order --------- Co-authored-by: Mario Rugiero --- CHANGELOG.md | 2 ++ Cargo.lock | 13 ++++++------- vm/Cargo.toml | 2 +- vm/src/vm/runners/builtin_runner/hash.rs | 19 ++----------------- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fbaa1b980..a9cc5a97e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* Bump `starknet-types-core` version + Use the lib's pedersen hash [#1692](https://github.com/lambdaclass/cairo-vm/pull/1692) + * refactor: Remove unused code & use constants whenever possible for builtin instance definitions[#1707](https://github.com/lambdaclass/cairo-vm/pull/1707) * feat: missing EC hints for Starknet OS 0.13.1 [#1706](https://github.com/lambdaclass/cairo-vm/pull/1706) diff --git a/Cargo.lock b/Cargo.lock index d51294e05e..499b5fe6c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1892,9 +1892,9 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4c222d5b2fdc0faf702d3ab361d14589b097f40eac9dc550e27083483edc65" +checksum = "458fee521f12d0aa97a2e06eaf134398a5d2ae7b2074af77eb402b0d93138c47" dependencies = [ "lambdaworks-math", "serde", @@ -1904,9 +1904,9 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee7dcab3968c71896b8ee4dc829147acc918cffe897af6265b1894527fe3add" +checksum = "6c74ce6f0d9cb672330b6ca59e85a6c3607a3329e0372ab0d3fe38c2d38e50f9" dependencies = [ "serde", "serde_json", @@ -3045,15 +3045,14 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.0.9" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d53160556d1f23425100f42b3230df747ea05763efee685a2cd939dfb640701" +checksum = "1051b4f4af0bb9b546388a404873ee1e6b9787b9d5b0b3319ecbfadf315ef276" dependencies = [ "arbitrary", "bitvec", "lambdaworks-crypto", "lambdaworks-math", - "lazy_static", "num-bigint", "num-integer", "num-traits 0.2.18", diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d1b25dace9..e04782221e 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -63,7 +63,7 @@ keccak = { workspace = true } hashbrown = { workspace = true } anyhow = { workspace = true } thiserror-no-std = { workspace = true } -starknet-types-core = { version = "0.0.9", default-features = false, features = ["serde", "curve", "num-traits"] } +starknet-types-core = { version = "0.1.0", default-features = false, features = ["serde", "curve", "num-traits", "hash"] } # only for std num-prime = { version = "0.4.3", features = ["big-int"], optional = true } diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index a6d045ade2..bd641f2690 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -1,6 +1,5 @@ use crate::air_private_input::{PrivateInput, PrivateInputPair}; use crate::stdlib::{cell::RefCell, prelude::*}; -use crate::types::errors::math_errors::MathError; use crate::types::instance_definitions::pedersen_instance_def::CELLS_PER_HASH; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; @@ -8,9 +7,8 @@ use crate::vm::errors::runner_errors::RunnerError; use crate::vm::runners::cairo_pie::BuiltinAdditionalData; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; -use crate::Felt252; use num_integer::{div_ceil, Integer}; -use starknet_crypto::{pedersen_hash, FieldElement}; +use starknet_types_core::hash::StarkHash; #[derive(Debug, Clone)] pub struct HashBuiltinRunner { @@ -89,21 +87,8 @@ impl HashBuiltinRunner { .resize(address.offset + 1, false); } self.verified_addresses.borrow_mut()[address.offset] = true; - - //Convert MaybeRelocatable to FieldElement - let a_be_bytes = num_a.to_bytes_be(); - let b_be_bytes = num_b.to_bytes_be(); - let (y, x) = match ( - FieldElement::from_bytes_be(&a_be_bytes), - FieldElement::from_bytes_be(&b_be_bytes), - ) { - (Ok(field_element_a), Ok(field_element_b)) => (field_element_a, field_element_b), - _ => return Err(MathError::ByteConversionError.into()), - }; //Compute pedersen Hash - let fe_result = pedersen_hash(&x, &y); - //Convert result from FieldElement to MaybeRelocatable - let result = Felt252::from_bytes_be(&fe_result.to_bytes_be()); + let result = starknet_types_core::hash::Pedersen::hash(num_b, num_a); return Ok(Some(MaybeRelocatable::from(result))); } Ok(None) From 584eaa23d2d29a854add5e89dac706cb251023c3 Mon Sep 17 00:00:00 2001 From: Pedro Fontana Date: Tue, 16 Apr 2024 19:02:57 -0300 Subject: [PATCH 36/36] Add Hyper Threading benchmarks workflow (#1711) * Add hyper threading workflow * Add thread_counts * Remove downloads and uploads * Revert "Remove downloads and uploads" This reverts commit 3e324ff26b832e252f868d7bea711dacc0b21392. * Remove downloads and uploads of main binary * use echo -e * use printf * eval * use 2 endlines --------- Co-authored-by: Pedro Fontana --- .../workflows/hyper_threading_benchmarks.yml | 110 ++++++++++++++++++ .../hyper-threading-workflow.sh | 34 ++++++ 2 files changed, 144 insertions(+) create mode 100644 .github/workflows/hyper_threading_benchmarks.yml create mode 100644 examples/hyper_threading/hyper-threading-workflow.sh diff --git a/.github/workflows/hyper_threading_benchmarks.yml b/.github/workflows/hyper_threading_benchmarks.yml new file mode 100644 index 0000000000..cebfa0084c --- /dev/null +++ b/.github/workflows/hyper_threading_benchmarks.yml @@ -0,0 +1,110 @@ +name: Benchmark Hyper Threading + +on: + pull_request: + branches: + - main + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - name: Checkout PR + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install Dependencies + run: | + pip install -r requirements.txt + sudo apt update + sudo apt-get install -y hyperfine + + - name: Install Rust + uses: dtolnay/rust-toolchain@1.74.1 + with: + components: rustfmt, clippy + + - name: Compile PR Version + run: | + cargo build --release -p hyper_threading + cp target/release/hyper_threading ${{ github.workspace }}/hyper_threading_pr + cp ./examples/hyper_threading/hyper-threading-workflow.sh ${{ github.workspace }}/hyper-threading-workflow.sh + + - name: Upload PR Binary + uses: actions/upload-artifact@v4 + with: + name: hyper_threading_pr_binary + path: ${{ github.workspace }}/hyper_threading_pr + + - name: Upload Workflow Script + uses: actions/upload-artifact@v4 + with: + name: hyper_threading_workflow_script + path: ${{ github.workspace }}/hyper-threading-workflow.sh + + + - name: Checkout Main Branch + uses: actions/checkout@v2 + with: + ref: 'main' + + - name: Compile Main Version + run: | + cargo build --release -p hyper_threading + cp target/release/hyper_threading ${{ github.workspace }}/hyper_threading_main + + - name: Download hyper_threading_pr_binary + uses: actions/download-artifact@v4 + with: + name: hyper_threading_pr_binary + path: ${{ github.workspace }}/ + + - name: Download hyper_threading_workflow_script + uses: actions/download-artifact@v4 + with: + name: hyper_threading_workflow_script + path: ${{ github.workspace }}/ + + - name: Compile programs + run: make cairo_bench_programs + + - name: Run Benchmarks + run: | + cd ${{ github.workspace }} + chmod +x ./hyper_threading_main + chmod +x ./hyper_threading_pr + chmod +x hyper-threading-workflow.sh + ./hyper-threading-workflow.sh + + - name: Compare Results + run: | + cat result.md + + - name: Find comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: "**Hyper Thereading Benchmark results**" + + - name: Create comment + if: steps.fc.outputs.comment-id == '' + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body-path: result.md + + - name: Update comment + if: steps.fc.outputs.comment-id != '' + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + body-path: result.md + edit-mode: replace diff --git a/examples/hyper_threading/hyper-threading-workflow.sh b/examples/hyper_threading/hyper-threading-workflow.sh new file mode 100644 index 0000000000..5ab1cb1019 --- /dev/null +++ b/examples/hyper_threading/hyper-threading-workflow.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Define a list of RAYON_NUM_THREADS +thread_counts=(1 2 4 6 8 16 ) + +# Define binary names +binaries=("hyper_threading_main" "hyper_threading_pr") + +echo -e "**Hyper Thereading Benchmark results**" >> result.md +printf "\n\n" >> result.md + +# Iter over thread_counts +for threads in "${thread_counts[@]}"; do + # Initialize hyperfine command + cmd="hyperfine -r 2" + + # Add each binary to the command with the current threads value + for binary in "${binaries[@]}"; do + cmd+=" -n \"${binary} threads: ${threads}\" 'RAYON_NUM_THREADS=${threads} ./${binary}'" + done + + # Execute + echo "Running benchmark for ${threads} threads" + printf "\n\n" >> result.md + echo -e $cmd >> result.md + eval $cmd >> result.md + printf "\n\n" >> result.md +done + +{ + echo -e '```' + cat result.md + echo -e '```' +} > temp_result.md && mv temp_result.md result.md