diff --git a/CHANGELOG.md b/CHANGELOG.md index 52686b8f43..8f1324894d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ #### Upcoming Changes -#### [0.8.6] - 2023-8-11 +* feat: Implement `CairoRunner.get_cairo_pie`[#1375](https://github.com/lambdaclass/cairo-vm/pull/1375/files) * fix: Fix `SPLIT_FELT` hint [#1387](https://github.com/lambdaclass/cairo-vm/pull/1387) @@ -12,6 +12,8 @@ * Various functions in the `math_utils` crate can now return a `MathError` : `div_mod`, `ec_add`, `line_slope`, `ec_double`, `ec_double_slope`. * Fixes `UINT256_MUL_INV_MOD_P` hint so that it behaves like the python code. +#### [0.8.6] - 2023-8-11 + * fix: Handle error in hint `UINT256_MUL_DIV_MOD` when divides by zero [#1367](https://github.com/lambdaclass/cairo-vm/pull/1367) * Add HintError::SyscallError and VmErrors::HINT_ERROR_STR constant [#1357](https://github.com/lambdaclass/cairo-rs/pull/1357) diff --git a/vm/src/tests/get_cairo_pie_tests.rs b/vm/src/tests/get_cairo_pie_tests.rs new file mode 100644 index 0000000000..9661ca4d4f --- /dev/null +++ b/vm/src/tests/get_cairo_pie_tests.rs @@ -0,0 +1,247 @@ +use crate::stdlib::{collections::HashMap, prelude::*}; + +use felt::felt_str; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ + cairo_run::{cairo_run, CairoRunConfig}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, + types::relocatable::Relocatable, + vm::runners::{ + builtin_runner::{ + HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, + SIGNATURE_BUILTIN_NAME, + }, + cairo_pie::{ + BuiltinAdditionalData, CairoPieMemory, OutputBuiltinAdditionalData, SegmentInfo, + }, + cairo_runner::ExecutionResources, + }, +}; + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn pedersen_test() { + // Run the program + let program_content = include_bytes!("../../../cairo_programs/pedersen_test.json"); + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let result = cairo_run( + program_content, + &CairoRunConfig { + layout: "all_cairo", + ..Default::default() + }, + &mut hint_processor, + ); + assert!(result.is_ok()); + let (runner, vm) = result.unwrap(); + // Obtain the pie + let result = runner.get_cairo_pie(&vm); + assert!(result.is_ok()); + let cairo_pie = result.unwrap(); + // Check pie values + // CairoPieMedatada + let pie_metadata = cairo_pie.metadata; + // ret_pc_segment + assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((6, 0))); + // builtin_segments + let expected_builtin_segments = HashMap::from([ + (String::from("output"), SegmentInfo::from((2, 1))), + (String::from("pedersen"), SegmentInfo::from((3, 3))), + (String::from("range_check"), SegmentInfo::from((4, 0))), + ]); + assert_eq!(pie_metadata.builtin_segments, expected_builtin_segments); + // program_segment + assert_eq!(pie_metadata.program_segment, SegmentInfo::from((0, 19))); + // ret_fp_segment + assert_eq!(pie_metadata.ret_fp_segment, SegmentInfo::from((5, 0))); + //program + assert_eq!(pie_metadata.program.main, 6); + assert_eq!(pie_metadata.program.builtins, runner.program.builtins); + assert_eq!( + pie_metadata.program.data, + runner.program.shared_program_data.data + ); + // execution_segment + assert_eq!(pie_metadata.execution_segment, SegmentInfo::from((1, 15))); + // extra_segments + assert!(pie_metadata.extra_segments.is_empty()); + + // execution_resources + let expected_execution_resources = ExecutionResources { + n_steps: 14, + n_memory_holes: 0, + builtin_instance_counter: HashMap::from([ + (RANGE_CHECK_BUILTIN_NAME.to_string(), 0), + (OUTPUT_BUILTIN_NAME.to_string(), 1), + (HASH_BUILTIN_NAME.to_string(), 1), + ]), + }; + assert_eq!(cairo_pie.execution_resources, expected_execution_resources); + // additional_data + let expected_additional_data = HashMap::from([ + ( + OUTPUT_BUILTIN_NAME.to_string(), + BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { + pages: HashMap::new(), + attributes: HashMap::new(), + }), + ), + ( + HASH_BUILTIN_NAME.to_string(), + BuiltinAdditionalData::Hash(vec![Relocatable::from((3, 2))]), + ), + ( + RANGE_CHECK_BUILTIN_NAME.to_string(), + BuiltinAdditionalData::None, + ), + ]); + assert_eq!(cairo_pie.additional_data, expected_additional_data); + // memory + assert_eq!( + cairo_pie.memory, + Into::::into(&vm.segments.memory) + ); +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn common_signature() { + // Run the program + let program_content = include_bytes!("../../../cairo_programs/common_signature.json"); + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let result = cairo_run( + program_content, + &CairoRunConfig { + layout: "all_cairo", + ..Default::default() + }, + &mut hint_processor, + ); + assert!(result.is_ok()); + let (runner, vm) = result.unwrap(); + // Obtain the pie + let result = runner.get_cairo_pie(&vm); + assert!(result.is_ok()); + let cairo_pie = result.unwrap(); + // Check pie values + // CairoPieMedatada + let pie_metadata = cairo_pie.metadata; + // ret_pc_segment + assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((4, 0))); + // builtin_segments + let expected_builtin_segments = + HashMap::from([(String::from("ecdsa"), SegmentInfo::from((2, 2)))]); + assert_eq!(pie_metadata.builtin_segments, expected_builtin_segments); + // program_segment + assert_eq!(pie_metadata.program_segment, SegmentInfo::from((0, 21))); + // ret_fp_segment + assert_eq!(pie_metadata.ret_fp_segment, SegmentInfo::from((3, 0))); + //program + assert_eq!(pie_metadata.program.main, 9); + assert_eq!(pie_metadata.program.builtins, runner.program.builtins); + assert_eq!( + pie_metadata.program.data, + runner.program.shared_program_data.data + ); + // execution_segment + assert_eq!(pie_metadata.execution_segment, SegmentInfo::from((1, 11))); + // extra_segments + assert!(pie_metadata.extra_segments.is_empty()); + + // execution_resources + let expected_execution_resources = ExecutionResources { + n_steps: 11, + n_memory_holes: 0, + builtin_instance_counter: HashMap::from([(SIGNATURE_BUILTIN_NAME.to_string(), 1)]), + }; + assert_eq!(cairo_pie.execution_resources, expected_execution_resources); + // additional_data + let expected_additional_data = HashMap::from([( + SIGNATURE_BUILTIN_NAME.to_string(), + BuiltinAdditionalData::Signature(HashMap::from([( + Relocatable::from((2, 0)), + ( + felt_str!( + "3086480810278599376317923499561306189851900463386393948998357832163236918254" + ), + felt_str!( + "598673427589502599949712887611119751108407514580626464031881322743364689811" + ), + ), + )])), + )]); + assert_eq!(cairo_pie.additional_data, expected_additional_data); + // memory + assert_eq!( + cairo_pie.memory, + Into::::into(&vm.segments.memory) + ); +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn relocate_segments() { + // Run the program + let program_content = include_bytes!("../../../cairo_programs/relocate_segments.json"); + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let result = cairo_run( + program_content, + &CairoRunConfig { + layout: "all_cairo", + ..Default::default() + }, + &mut hint_processor, + ); + assert!(result.is_ok()); + let (runner, vm) = result.unwrap(); + // Obtain the pie + let result = runner.get_cairo_pie(&vm); + assert!(result.is_ok()); + let cairo_pie = result.unwrap(); + // Check pie values + // CairoPieMedatada + let pie_metadata = cairo_pie.metadata; + // ret_pc_segment + assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((3, 0))); + // builtin_segments + assert!(pie_metadata.builtin_segments.is_empty()); + // program_segment + assert_eq!(pie_metadata.program_segment, SegmentInfo::from((0, 32))); + // ret_fp_segment + assert_eq!(pie_metadata.ret_fp_segment, SegmentInfo::from((2, 0))); + //program + assert_eq!(pie_metadata.program.main, 5); + assert!(pie_metadata.program.builtins.is_empty()); + assert_eq!( + pie_metadata.program.data, + runner.program.shared_program_data.data + ); + // execution_segment + assert_eq!(pie_metadata.execution_segment, SegmentInfo::from((1, 16))); + // extra_segments + assert_eq!(pie_metadata.extra_segments, vec![SegmentInfo::from((4, 3))]); + + // execution_resources + let expected_execution_resources = ExecutionResources { + n_steps: 22, + n_memory_holes: 0, + builtin_instance_counter: HashMap::default(), + }; + assert_eq!(cairo_pie.execution_resources, expected_execution_resources); + // additional_data + assert!(cairo_pie.additional_data.is_empty()); + // memory + assert_eq!( + cairo_pie.memory, + Into::::into(&vm.segments.memory) + ); +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 7d75c8345a..ad09cc03a0 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -39,6 +39,7 @@ mod cairo_run_test; mod pedersen_test; mod struct_test; +mod get_cairo_pie_tests; #[cfg(feature = "skip_next_instruction_hint")] mod skip_instruction_test; diff --git a/vm/src/types/errors/program_errors.rs b/vm/src/types/errors/program_errors.rs index 4a3d16c712..c10177aa5f 100644 --- a/vm/src/types/errors/program_errors.rs +++ b/vm/src/types/errors/program_errors.rs @@ -17,6 +17,8 @@ pub enum ProgramError { ConstWithoutValue(String), #[error("Expected prime {PRIME_STR}, got {0}")] PrimeDiffers(String), + #[error("Can't build a StrippedProgram from a Program without main")] + StrippedProgramNoMain, #[error("Hint PC ({0}) is greater or equal to program length ({1})")] InvalidHintPc(usize, usize), } diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index aefd91c7b0..9b2f765827 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -111,6 +111,13 @@ pub struct Program { pub(crate) builtins: Vec, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StrippedProgram { + pub data: Vec, + pub builtins: Vec, + pub main: usize, +} + impl Program { #[allow(clippy::too_many_arguments)] pub fn new( @@ -252,6 +259,20 @@ impl Program { }) .collect() } + + // Obtains a reduced version of the program + // Doesn't contain hints + // Can be used for verifying execution. + pub fn get_stripped_program(&self) -> Result { + Ok(StrippedProgram { + data: self.shared_program_data.data.clone(), + builtins: self.builtins.clone(), + main: self + .shared_program_data + .main + .ok_or(ProgramError::StrippedProgramNoMain)?, + }) + } } impl Default for Program { @@ -318,6 +339,8 @@ mod tests { use felt::felt_str; use num_traits::Zero; + use assert_matches::assert_matches; + #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -1042,4 +1065,28 @@ mod tests { assert_eq!(program, Program::default()); } + + #[test] + fn get_stripped_program() { + let program_content = include_bytes!("../../../cairo_programs/pedersen_test.json"); + let program = Program::from_bytes(program_content, Some("main")).unwrap(); + let stripped_program = program.get_stripped_program().unwrap(); + assert_eq!(stripped_program.builtins, program.builtins); + assert_eq!(stripped_program.data, program.shared_program_data.data); + assert_eq!( + stripped_program.main, + program.shared_program_data.main.unwrap() + ); + } + + #[test] + fn get_stripped_no_main() { + let program_content = + include_bytes!("../../../cairo_programs/proof_programs/fibonacci.json"); + let program = Program::from_bytes(program_content, None).unwrap(); + assert_matches!( + program.get_stripped_program(), + Err(ProgramError::StrippedProgramNoMain) + ); + } } diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index ddd39e7269..a9b26a8d5a 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -5,7 +5,7 @@ use crate::stdlib::{collections::HashSet, prelude::*}; use thiserror_no_std::Error; -use super::memory_errors::MemoryError; +use super::{memory_errors::MemoryError, trace_errors::TraceError}; use crate::types::{errors::math_errors::MathError, relocatable::Relocatable}; use felt::Felt252; @@ -85,6 +85,22 @@ pub enum RunnerError { BuiltinExpectedInteger(Box<(&'static str, Relocatable)>), #[error("keccak_builtin: Failed to convert input cells to u64 values")] KeccakInputCellsNotU64, + #[error("Unexpected ret_fp_segment size")] + UnexpectedRetFpSegmentSize, + #[error("Unexpected ret_pc_segment size")] + UnexpectedRetPcSegmentSize, + #[error("Expected program base offset to be zero")] + ProgramBaseOffsetNotZero, + #[error("Expected execution base offset to be zero")] + ExecBaseOffsetNotZero, + #[error("Expected ret_fp offset to be zero")] + RetFpOffsetNotZero, + #[error("Expected ret_pc offset to be zero")] + RetPcOffsetNotZero, + #[error("Can't build a StrippedProgram from a Program without main")] + StrippedProgramNoMain, + #[error(transparent)] + Trace(#[from] TraceError), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index 3f8e667044..bb3cd7d6c2 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -5,6 +5,7 @@ use crate::types::instance_definitions::pedersen_instance_def::{ 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; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use felt::Felt252; @@ -176,6 +177,16 @@ impl HashBuiltinRunner { 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() { + if *is_verified { + verified_addresses.push(Relocatable::from((self.base as isize, offset))); + } + } + BuiltinAdditionalData::Hash(verified_addresses) + } } #[cfg(test)] @@ -525,4 +536,16 @@ mod tests { vm.segments.segment_used_sizes = Some(vec![4]); assert_eq!(builtin.get_used_cells(&vm.segments), Ok(4)); } + + #[test] + fn get_additional_info() { + let mut builtin = HashBuiltinRunner::new(Some(1), true); + let verified_addresses = vec![Relocatable::from((0, 3)), Relocatable::from((0, 6))]; + builtin.verified_addresses = + RefCell::new(vec![false, false, false, true, false, false, true]); + assert_eq!( + builtin.get_additional_data(), + BuiltinAdditionalData::Hash(verified_addresses) + ) + } } diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index d12433bbe0..4b27c80eb4 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -29,6 +29,8 @@ pub use output::OutputBuiltinRunner; pub use range_check::RangeCheckBuiltinRunner; pub use signature::SignatureBuiltinRunner; +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"; @@ -472,6 +474,15 @@ impl BuiltinRunner { } } + pub fn get_additional_data(&self) -> BuiltinAdditionalData { + match self { + BuiltinRunner::Hash(builtin) => builtin.get_additional_data(), + BuiltinRunner::Output(builtin) => builtin.get_additional_data(), + BuiltinRunner::Signature(builtin) => builtin.get_additional_data(), + _ => BuiltinAdditionalData::None, + } + } + #[cfg(test)] pub(crate) fn set_stop_ptr(&mut self, stop_ptr: usize) { match self { @@ -1674,4 +1685,10 @@ mod tests { assert_eq!(stop_ptr, Some(ptr)); } } + + #[test] + fn get_additonal_data_none() { + let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(None, true).into(); + assert_eq!(builtin.get_additional_data(), BuiltinAdditionalData::None) + } } diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index e558de95eb..75fce41506 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -1,7 +1,8 @@ -use crate::stdlib::prelude::*; +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::vm_core::VirtualMachine; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; @@ -106,6 +107,13 @@ impl OutputBuiltinRunner { Ok(pointer) } } + + pub fn get_additional_data(&self) -> BuiltinAdditionalData { + BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { + pages: HashMap::default(), + attributes: HashMap::default(), + }) + } } impl Default for OutputBuiltinRunner { @@ -430,4 +438,16 @@ mod tests { vm.segments.segment_used_sizes = Some(vec![0]); builtin.add_validation_rule(&mut vm.segments.memory); } + + #[test] + fn get_additional_info_no_pages_no_attributes() { + let builtin = OutputBuiltinRunner::new(true); + assert_eq!( + builtin.get_additional_data(), + BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { + pages: HashMap::default(), + attributes: HashMap::default() + }) + ) + } } diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index cfc0db9d0c..7a253d9add 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -1,5 +1,6 @@ use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*, rc::Rc}; +use crate::vm::runners::cairo_pie::BuiltinAdditionalData; use crate::{ types::{ instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, @@ -213,6 +214,25 @@ impl SignatureBuiltinRunner { Ok(pointer) } } + + pub fn get_additional_data(&self) -> BuiltinAdditionalData { + // Convert signatures to Felt tuple + let signatures: HashMap = self + .signatures + .borrow() + .iter() + .map(|(k, v)| { + ( + *k, + ( + Felt252::from_bytes_be(&v.r.to_bytes_be()), + Felt252::from_bytes_be(&v.s.to_bytes_be()), + ), + ) + }) + .collect(); + BuiltinAdditionalData::Signature(signatures) + } } #[cfg(test)] @@ -230,6 +250,7 @@ mod tests { }, }; + use felt::felt_str; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -563,4 +584,25 @@ mod tests { Err(RunnerError::Memory(MemoryError::MissingSegmentUsedSizes)) ) } + + #[test] + fn get_additional_info() { + let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let signatures = HashMap::from([( + Relocatable::from((4, 0)), + Signature { + r: FieldElement::from_dec_str("45678").unwrap(), + s: FieldElement::from_dec_str("1239").unwrap(), + }, + )]); + builtin.signatures = Rc::new(RefCell::new(signatures)); + let signatures = HashMap::from([( + Relocatable::from((4, 0)), + (felt_str!("45678"), felt_str!("1239")), + )]); + assert_eq!( + builtin.get_additional_data(), + BuiltinAdditionalData::Signature(signatures) + ) + } } diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs new file mode 100644 index 0000000000..5359785ab5 --- /dev/null +++ b/vm/src/vm/runners/cairo_pie.rs @@ -0,0 +1,70 @@ +use super::cairo_runner::ExecutionResources; +use crate::felt::Felt252; +use crate::stdlib::{collections::HashMap, prelude::*}; +use crate::types::program::StrippedProgram; +use crate::types::relocatable::{MaybeRelocatable, Relocatable}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SegmentInfo { + pub index: isize, + pub size: usize, +} + +impl From<(isize, usize)> for SegmentInfo { + fn from(value: (isize, usize)) -> Self { + SegmentInfo { + index: value.0, + size: value.1, + } + } +} + +// A simplified version of Memory, without any additional data besides its elements +// Contains all addr-value pairs, ordered by index and offset +// Allows practical serialization + conversion between CairoPieMemory & Memory +pub type CairoPieMemory = Vec<((usize, usize), MaybeRelocatable)>; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicMemoryPage { + pub start: usize, + pub size: usize, +} + +// HashMap value based on starknet/core/os/output.cairo usage +pub type Attributes = HashMap>; +pub type Pages = HashMap; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OutputBuiltinAdditionalData { + pub pages: Pages, + pub attributes: Attributes, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BuiltinAdditionalData { + // Contains verified addresses as contiguous index, value pairs + Hash(Vec), + Output(OutputBuiltinAdditionalData), + // Signatures are composed of (r, s) tuples + Signature(HashMap), + None, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CairoPie { + pub metadata: CairoPieMetadata, + pub memory: CairoPieMemory, + pub execution_resources: ExecutionResources, + pub additional_data: HashMap, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CairoPieMetadata { + pub program: StrippedProgram, + pub program_segment: SegmentInfo, + pub execution_segment: SegmentInfo, + pub ret_fp_segment: SegmentInfo, + pub ret_pc_segment: SegmentInfo, + pub builtin_segments: HashMap, + pub extra_segments: Vec, +} diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index a4811a5a78..e5e39d92a3 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -47,10 +47,13 @@ use crate::{ }; use felt::Felt252; use num_integer::div_rem; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use serde::Deserialize; -use super::builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}; +use super::{ + builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}, + cairo_pie::{self, CairoPie, CairoPieMetadata}, +}; #[derive(Clone, Debug, Eq, PartialEq)] pub enum CairoArg { @@ -830,6 +833,34 @@ impl CairoRunner { Ok(builtin_segment_info) } + // Returns a map from builtin's name wihout the "_builtin" suffix to its base's segment index and stop_ptr offset + // Aka the builtin's segment number and its maximum offset + pub fn get_builtin_segment_info_for_pie( + &self, + vm: &VirtualMachine, + ) -> Result, RunnerError> { + let mut builtin_segment_info = HashMap::new(); + + for builtin in &vm.builtin_runners { + let (index, stop_ptr) = builtin.get_memory_segment_addresses(); + + builtin_segment_info.insert( + builtin + .name() + .strip_suffix("_builtin") + .unwrap_or_default() + .to_string(), + ( + index as isize, + stop_ptr.ok_or_else(|| RunnerError::NoStopPointer(Box::new(builtin.name())))?, + ) + .into(), + ); + } + + Ok(builtin_segment_info) + } + pub fn get_execution_resources( &self, vm: &VirtualMachine, @@ -1117,6 +1148,102 @@ impl CairoRunner { &self.program } + // Constructs and returns a CairoPie representing the current VM run. + pub fn get_cairo_pie(&self, vm: &VirtualMachine) -> Result { + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + let execution_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; + + let builtin_segments = self.get_builtin_segment_info_for_pie(vm)?; + let mut known_segment_indices = HashSet::new(); + for info in builtin_segments.values() { + known_segment_indices.insert(info.index); + } + let n_used_builtins = self.program.builtins_len(); + let return_fp_addr = (execution_base + n_used_builtins)?; + let return_fp = vm.segments.memory.get_relocatable(return_fp_addr)?; + let return_pc = vm.segments.memory.get_relocatable((return_fp_addr + 1)?)?; + + if let None | Some(false) = return_fp + .segment_index + .to_usize() + .and_then(|u| vm.segments.get_segment_size(u)) + .map(|u| u.is_zero()) + { + // return_fp negative index / no size / size is zero + return Err(RunnerError::UnexpectedRetFpSegmentSize); + } + + if let None | Some(false) = return_pc + .segment_index + .to_usize() + .and_then(|u| vm.segments.get_segment_size(u)) + .map(|u| u.is_zero()) + { + // return_pc negative index / no size / size is zero + return Err(RunnerError::UnexpectedRetPcSegmentSize); + } + + if program_base.offset != 0 { + return Err(RunnerError::ProgramBaseOffsetNotZero); + } + known_segment_indices.insert(program_base.segment_index); + + if execution_base.offset != 0 { + return Err(RunnerError::ExecBaseOffsetNotZero); + } + known_segment_indices.insert(execution_base.segment_index); + + if return_fp.offset != 0 { + return Err(RunnerError::RetFpOffsetNotZero); + } + known_segment_indices.insert(return_fp.segment_index); + + if return_pc.offset != 0 { + return Err(RunnerError::RetPcOffsetNotZero); + } + known_segment_indices.insert(return_pc.segment_index); + + // Put all the remaining segments in extra_segments. + let mut extra_segments = Vec::default(); + for index in 0..vm.segments.num_segments() { + if !known_segment_indices.contains(&(index as isize)) { + extra_segments.push( + ( + index as isize, + vm.get_segment_size(index) + .ok_or(MemoryError::MissingSegmentUsedSizes)?, + ) + .into(), + ); + } + } + + let execution_size = (vm.get_ap() - execution_base)?; + let metadata = CairoPieMetadata { + program: self + .get_program() + .get_stripped_program() + .map_err(|_| RunnerError::StrippedProgramNoMain)?, + program_segment: (program_base.segment_index, self.program.data_len()).into(), + execution_segment: (execution_base.segment_index, execution_size).into(), + ret_fp_segment: (return_fp.segment_index, 0).into(), + ret_pc_segment: (return_pc.segment_index, 0).into(), + builtin_segments, + extra_segments, + }; + + Ok(CairoPie { + metadata, + memory: (&vm.segments.memory).into(), + execution_resources: self.get_execution_resources(vm)?, + additional_data: vm + .builtin_runners + .iter() + .map(|b| (b.name().to_string(), b.get_additional_data())) + .collect(), + }) + } + /// Return CairoRunner.layout fn get_layout(&self) -> &CairoLayout { &self.layout @@ -5000,4 +5127,167 @@ mod tests { ); assert_eq!(hint_processor.run_resources(), &RunResources::new(0)); } + + #[test] + fn get_cairo_pie_no_program_base() { + let runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let vm = vm!(); + assert_eq!(runner.get_cairo_pie(&vm), Err(RunnerError::NoProgBase)) + } + + #[test] + fn get_cairo_pie_no_execution_base() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + assert_eq!(runner.get_cairo_pie(&vm), Err(RunnerError::NoExecBase)) + } + + #[test] + fn get_cairo_pie_no_segment_sizes() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 0).into()) + .unwrap(); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::UnexpectedRetFpSegmentSize) + ); + } + + #[test] + fn get_cairo_pie_ret_pc_segment_size_not_zero() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 0).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 1)]); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::UnexpectedRetPcSegmentSize) + ); + } + + #[test] + fn get_cairo_pie_program_base_offset_not_zero() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 1))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 0).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::ProgramBaseOffsetNotZero) + ); + } + + #[test] + fn get_cairo_pie_execution_base_offset_not_zero() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 1))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 1).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 2).into(), (3, 0).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::ExecBaseOffsetNotZero) + ); + } + + #[test] + fn get_cairo_pie_ret_fp_offset_not_zero() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 1).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 0).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::RetFpOffsetNotZero) + ); + } + + #[test] + fn get_cairo_pie_ret_pc_offset_not_zero() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 1).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); + assert_eq!( + runner.get_cairo_pie(&vm), + Err(RunnerError::RetPcOffsetNotZero) + ); + } + + #[test] + fn get_cairo_pie_ok() { + let mut runner = CairoRunner::new(&Program::default(), "all_cairo", false).unwrap(); + let mut vm = vm!(); + runner.program_base = Some(Relocatable::from((0, 0))); + runner.execution_base = Some(Relocatable::from((1, 0))); + vm.add_memory_segment(); + vm.add_memory_segment(); + // return_fp + vm.insert_value::((1, 0).into(), (2, 0).into()) + .unwrap(); + // return_pc + vm.insert_value::((1, 1).into(), (3, 0).into()) + .unwrap(); + // segment sizes + vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); + } } diff --git a/vm/src/vm/runners/mod.rs b/vm/src/vm/runners/mod.rs index a44972051a..ed5ecaa7dc 100644 --- a/vm/src/vm/runners/mod.rs +++ b/vm/src/vm/runners/mod.rs @@ -1,2 +1,3 @@ pub mod builtin_runner; +pub mod cairo_pie; pub mod cairo_runner; diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index 098e2480fd..c178d93cde 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::vm::runners::cairo_pie::CairoPieMemory; use crate::{ types::relocatable::{MaybeRelocatable, Relocatable}, utils::from_relocatable_to_indexes, @@ -513,6 +514,20 @@ impl Memory { } } +impl From<&Memory> for CairoPieMemory { + fn from(mem: &Memory) -> CairoPieMemory { + let mut pie_memory = Vec::default(); + for (i, segment) in mem.data.iter().enumerate() { + for (j, elem) in segment.iter().enumerate() { + if let Some(cell) = elem { + pie_memory.push(((i, j), cell.get_value().clone())) + } + } + } + pie_memory + } +} + impl fmt::Display for Memory { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (i, segment) in self.temp_data.iter().enumerate() { @@ -1611,4 +1626,18 @@ mod memory_tests { check_memcmp((-2, 0), (-3, 5), 8, Greater, 0); check_memcmp((-3, 5), (-2, 0), 8, Less, 0); } + + #[test] + fn cairo_pie_memory_from_memory() { + let memory = memory![((8, 9), 3), ((1, 2), 5), ((7, 6), (1, 2))]; + + assert_eq!( + CairoPieMemory::from(&memory), + vec![ + ((1, 2), MaybeRelocatable::from(5)), + ((7, 6), MaybeRelocatable::from((1, 2))), + ((8, 9), MaybeRelocatable::from(3)) + ] + ) + } }