diff --git a/.gitignore b/.gitignore index ec23090624..99296e57b4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ cairo-vm-pypy-env/* *.tar ensure-no_std/Cargo.lock + +!vm/src/tests/cairo_pie_test_output.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 94710b2a69..661e7627e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* fix: Remove Deserialize derive from CairoPie and fix Serialize implementation to match Python's [#1444](https://github.com/lambdaclass/cairo-vm/pull/1444) + * fix: ec_recover hints no longer panic when divisor is 0 [#1433](https://github.com/lambdaclass/cairo-vm/pull/1433) * feat: Implement the Serialize and Deserialize traits for the CairoPie struct [#1438](https://github.com/lambdaclass/cairo-vm/pull/1438) @@ -107,7 +109,7 @@ `get_hint_data(self, &[HintReference], &mut dyn HintProcessor) -> Result, VirtualMachineError>` * Hook methods receive `&[Box]` rather than `&HashMap>>` -#### [0.8.4] +#### [0.8.4] **YANKED** #### [0.8.3] @@ -194,12 +196,12 @@ * BREAKING: Change `RunResources` usage: * Modify field type `RunResources.n_steps: Option,` - + * Public Api Changes: * CairoRunner::run_until_pc: Now receive a `&mut RunResources` instead of an `&mut Option` * CairoRunner::run_from_entrypoint: Now receive a `&mut RunResources` instead of an `&mut Option` * VirtualMachine::Step: Add `&mut RunResources` as input - * Trait HintProcessor::execute_hint: Add `&mut RunResources` as an input + * Trait HintProcessor::execute_hint: Add `&mut RunResources` as an input * perf: accumulate `min` and `max` instruction offsets during run to speed up range check [#1080](https://github.com/lambdaclass/cairo-vm/pull/) BREAKING: `Cairo_runner::get_perm_range_check_limits` no longer returns an error when called without trace enabled, as it no longer depends on it @@ -213,7 +215,7 @@ * BREAKING: Add no_std compatibility to cairo-vm (cairo-1-hints feature still not supported) * Move the vm to its own directory and crate, different from the workspace [#1215](https://github.com/lambdaclass/cairo-vm/pull/1215) - * Add an `ensure_no_std` crate that the CI will use to check that new changes don't revert `no_std` support [#1215](https://github.com/lambdaclass/cairo-vm/pull/1215) [#1232](https://github.com/lambdaclass/cairo-vm/pull/1232) + * Add an `ensure_no_std` crate that the CI will use to check that new changes don't revert `no_std` support [#1215](https://github.com/lambdaclass/cairo-vm/pull/1215) [#1232](https://github.com/lambdaclass/cairo-vm/pull/1232) * replace the use of `num-prime::is_prime` by a custom implementation, therefore restoring `no_std` compatibility [#1238](https://github.com/lambdaclass/cairo-vm/pull/1238) @@ -514,7 +516,7 @@ value = x_inv = div_mod(1, x, SECP_P) ``` - + * Implement hint for `starkware.cairo.common.cairo_keccak.keccak._copy_inputs` as described by whitelist `starknet/security/whitelists/cairo_keccak.json` [#1058](https://github.com/lambdaclass/cairo-vm/pull/1058) `BuiltinHintProcessor` now supports the following hint: @@ -1163,7 +1165,7 @@ * Implement hint on vrf.json lib [#1049](https://github.com/lambdaclass/cairo-vm/pull/1049) `BuiltinHintProcessor` now supports the following hint: - + ```python def split(num: int, num_bits_shift: int, length: int): a = [] @@ -1306,19 +1308,19 @@ * Implement hint on uint384_extension lib [#983](https://github.com/lambdaclass/cairo-vm/pull/983) `BuiltinHintProcessor` now supports the following hint: - + ```python def split(num: int, num_bits_shift: int, length: int): a = [] for _ in range(length): a.append( num & ((1 << num_bits_shift) - 1) ) - num = num >> num_bits_shift + num = num >> num_bits_shift return tuple(a) def pack(z, num_bits_shift: int) -> int: limbs = (z.d0, z.d1, z.d2) return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) - + def pack_extended(z, num_bits_shift: int) -> int: limbs = (z.d0, z.d1, z.d2, z.d3, z.d4, z.d5) return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) @@ -1354,13 +1356,13 @@ * Add missing hints [#1014](https://github.com/lambdaclass/cairo-vm/pull/1014): `BuiltinHintProcessor` now supports the following hints: ```python - from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P + from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P ``` - and: + and: ```python 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) @@ -1379,7 +1381,7 @@ s = pack(ids.s, PRIME) % N value = res = div_mod(x, s, N) ``` - and: + and: ```python value = k = safe_div(res * s - x, N) ``` diff --git a/vm/src/tests/get_cairo_pie_tests.rs b/vm/src/tests/cairo_pie_test.rs similarity index 95% rename from vm/src/tests/get_cairo_pie_tests.rs rename to vm/src/tests/cairo_pie_test.rs index 257dbb8e08..93b5947510 100644 --- a/vm/src/tests/get_cairo_pie_tests.rs +++ b/vm/src/tests/cairo_pie_test.rs @@ -1,22 +1,7 @@ -use crate::{ - stdlib::{collections::HashMap, prelude::*}, - vm::runners::cairo_pie::CairoPie, -}; - -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, + stdlib::{collections::HashMap, prelude::*}, types::relocatable::Relocatable, vm::runners::{ builtin_runner::{ @@ -29,6 +14,16 @@ use crate::{ cairo_runner::ExecutionResources, }, }; +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, +}; #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -251,14 +246,14 @@ fn relocate_segments() { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] -fn serialize_and_deserialize_cairo_pie() { +fn serialize_cairo_pie() { // Run the program - let program_content = include_bytes!("../../../cairo_programs/relocate_segments.json"); + let program_content = include_bytes!("../../../cairo_programs/print.json"); let mut hint_processor = BuiltinHintProcessor::new_empty(); let result = cairo_run( program_content, &CairoRunConfig { - layout: "all_cairo", + layout: "small", ..Default::default() }, &mut hint_processor, @@ -269,7 +264,10 @@ fn serialize_and_deserialize_cairo_pie() { let result = runner.get_cairo_pie(&vm); assert!(result.is_ok()); let cairo_pie = result.unwrap(); - let cairo_pie_serialized = serde_json::to_string(&cairo_pie).unwrap(); - let cairo_pie_deserialized: CairoPie = serde_json::from_str(&cairo_pie_serialized).unwrap(); - assert_eq!(cairo_pie_deserialized, cairo_pie); + + assert_eq!( + serde_json::to_value(cairo_pie).unwrap(), + serde_json::from_str::(include_str!("cairo_pie_test_output.json")) + .unwrap(), + ); } diff --git a/vm/src/tests/cairo_pie_test_output.json b/vm/src/tests/cairo_pie_test_output.json new file mode 100644 index 0000000000..3d83f71382 --- /dev/null +++ b/vm/src/tests/cairo_pie_test_output.json @@ -0,0 +1,110 @@ +{ + "additional_data": { + "output_builtin": { + "pages": {}, + "attributes": {} + } + }, + "execution_resources": { + "n_memory_holes": 0, + "builtin_instance_counter": { + "output_builtin": 1 + }, + "n_steps": 7 + }, + "memory": [ + [ + 4612671182993129469, + 5198983563776393216, + 1, + 2345108766317314046, + 5191102247248822272, + 5189976364521848832, + 1234, + 1226245742482522112, + 3618502788666131213697322783095070105623107215331596699973092056135872020474, + 2345108766317314046 + ], + [ + [ + 2, + 0 + ], + [ + 3, + 0 + ], + [ + 4, + 0 + ], + [ + 2, + 0 + ], + 1234, + [ + 1, + 3 + ], + [ + 0, + 9 + ], + [ + 2, + 1 + ] + ], + [ + 1234 + ] + ], + "metadata": { + "extra_segments": [], + "ret_pc_segment": { + "size": 0, + "index": 4 + }, + "execution_segment": { + "size": 8, + "index": 1 + }, + "program": { + "builtins": [ + "output" + ], + "prime": 3618502788666131213697322783095070105623107215331596699973092056135872020481, + "main": 4, + "data": [ + 4612671182993129469, + 5198983563776393216, + 1, + 2345108766317314046, + 5191102247248822272, + 5189976364521848832, + 1234, + 1226245742482522112, + 3618502788666131213697322783095070105623107215331596699973092056135872020474, + 2345108766317314046 + ] + }, + "ret_fp_segment": { + "size": 0, + "index": 3 + }, + "builtin_segments": { + "output": { + "size": 1, + "index": 2 + } + }, + "program_segment": { + "size": 10, + "index": 0 + } + }, + "version": { + "cairo_pie": "1.1" + } +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index ad09cc03a0..cecacbad8d 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -39,7 +39,7 @@ mod cairo_run_test; mod pedersen_test; mod struct_test; -mod get_cairo_pie_tests; +mod cairo_pie_test; #[cfg(feature = "skip_next_instruction_hint")] mod skip_instruction_test; diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index a88e04aae4..f42d2ddbd0 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -1,7 +1,10 @@ -use crate::stdlib::{ - collections::{BTreeMap, HashMap}, - prelude::*, - sync::Arc, +use crate::{ + stdlib::{ + collections::{BTreeMap, HashMap}, + prelude::*, + sync::Arc, + }, + vm::runners::cairo_pie::StrippedProgram, }; #[cfg(feature = "cairo-1-hints")] @@ -20,7 +23,6 @@ use crate::{ use cairo_lang_starknet::casm_contract_class::CasmContractClass; use core::num::NonZeroUsize; use felt::{Felt252, PRIME_STR}; -use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::path::Path; @@ -164,13 +166,6 @@ pub struct Program { pub(crate) builtins: Vec, } -#[derive(Serialize, Deserialize, 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( @@ -332,6 +327,7 @@ impl Program { .shared_program_data .main .ok_or(ProgramError::StrippedProgramNoMain)?, + prime: (), }) } } diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 9dacb46b50..54890a28a3 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -1,10 +1,13 @@ +use super::cairo_runner::ExecutionResources; +use crate::{ + felt::Felt252, + serde::deserialize_program::BuiltinName, + stdlib::{collections::HashMap, prelude::*}, + types::relocatable::{MaybeRelocatable, Relocatable}, +}; use serde::{Deserialize, Serialize}; -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}; +const CAIRO_PIE_VERSION: &str = "1.1"; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct SegmentInfo { @@ -43,6 +46,7 @@ pub struct OutputBuiltinAdditionalData { } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] pub enum BuiltinAdditionalData { // Contains verified addresses as contiguous index, value pairs Hash(Vec), @@ -52,15 +56,17 @@ pub enum BuiltinAdditionalData { None, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] pub struct CairoPie { pub metadata: CairoPieMetadata, + #[serde(serialize_with = "serde_impl::serialize_memory")] pub memory: CairoPieMemory, pub execution_resources: ExecutionResources, pub additional_data: HashMap, + pub version: CairoPieVersion, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] pub struct CairoPieMetadata { pub program: StrippedProgram, pub program_segment: SegmentInfo, @@ -70,3 +76,243 @@ pub struct CairoPieMetadata { pub builtin_segments: HashMap, pub extra_segments: Vec, } + +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct StrippedProgram { + #[serde(serialize_with = "serde_impl::serialize_program_data")] + pub data: Vec, + pub builtins: Vec, + pub main: usize, + + // Dummy field for serialization only. + #[serde(serialize_with = "serde_impl::serialize_prime")] + pub prime: (), +} + +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct CairoPieVersion { + // Dummy field for serialization only. + #[serde(serialize_with = "serde_impl::serialize_version")] + pub cairo_pie: (), +} + +mod serde_impl { + use super::CAIRO_PIE_VERSION; + use crate::{ + types::relocatable::{MaybeRelocatable, Relocatable}, + utils::CAIRO_PRIME, + }; + #[cfg(any(target_arch = "wasm32", no_std, not(feature = "std")))] + use alloc::collections::{btree_map::Entry, BTreeMap}; + use felt::Felt252; + use serde::{ + ser::{SerializeSeq, SerializeTuple}, + Serialize, Serializer, + }; + #[cfg(not(any(target_arch = "wasm32", no_std, not(feature = "std"))))] + use std::collections::{btree_map::Entry, BTreeMap}; + + struct Felt252Wrapper<'a>(&'a Felt252); + struct RelocatableWrapper<'a>(&'a Relocatable); + + struct MissingSegment; + + struct MemoryData<'a>(&'a BTreeMap); + struct MemoryHole; + + impl<'a> Serialize for Felt252Wrapper<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[cfg(any(target_arch = "wasm32", no_std, not(feature = "std")))] + use crate::alloc::string::ToString; + + // Note: This uses an API intended only for testing. + serde_json::Number::from_string_unchecked(self.0.to_string()).serialize(serializer) + } + } + + impl<'a> Serialize for RelocatableWrapper<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut tuple_serializer = serializer.serialize_tuple(2)?; + + tuple_serializer.serialize_element(&self.0.segment_index)?; + tuple_serializer.serialize_element(&self.0.offset)?; + + tuple_serializer.end() + } + } + + impl Serialize for MissingSegment { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_none() + } + } + + impl<'a> Serialize for MemoryData<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq_serializer = serializer.serialize_seq(Some( + self.0 + .last_key_value() + .map(|x| *x.0 + 1) + .unwrap_or_default(), + ))?; + + let mut last_offset = None; + for (offset, value) in self.0.iter() { + // Serialize memory holes as `None`. + for _ in last_offset.map(|x| x + 1).unwrap_or_default()..*offset { + seq_serializer.serialize_element(&MemoryHole)?; + } + + // Update the last offset to check for memory holes after itself. + last_offset = Some(*offset); + + // Serialize the data. + match value { + MaybeRelocatable::RelocatableValue(x) => { + seq_serializer.serialize_element(&RelocatableWrapper(x))? + } + MaybeRelocatable::Int(x) => { + seq_serializer.serialize_element(&Felt252Wrapper(x))? + } + } + } + + seq_serializer.end() + } + } + + impl Serialize for MemoryHole { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_none() + } + } + + pub fn serialize_program_data( + values: &[MaybeRelocatable], + serializer: S, + ) -> Result + where + S: Serializer, + { + let mut seq_serializer = serializer.serialize_seq(Some(values.len()))?; + + for value in values { + match value { + MaybeRelocatable::RelocatableValue(_) => todo!(), + MaybeRelocatable::Int(x) => { + seq_serializer.serialize_element(&Felt252Wrapper(x))?; + } + }; + } + + seq_serializer.end() + } + + pub fn serialize_memory( + values: &[((usize, usize), MaybeRelocatable)], + serializer: S, + ) -> Result + where + S: Serializer, + { + let mut memory = BTreeMap::new(); + for value in values { + let segment_entry = match memory.entry(value.0 .0) { + Entry::Vacant(x) => x.insert(BTreeMap::new()), + Entry::Occupied(x) => x.into_mut(), + }; + + segment_entry.insert(value.0 .1, &value.1); + } + + let mut seq_serializer = serializer.serialize_seq(Some( + memory + .last_entry() + .map(|x| *x.key() + 1) + .unwrap_or_default(), + ))?; + + let mut last_segment = None; + for (segment_idx, segment_data) in memory { + // Serialize missing segments as `None`. + for _ in last_segment.map(|x| x + 1).unwrap_or_default()..segment_idx { + seq_serializer.serialize_element(&MissingSegment)?; + } + + // Update the last segment to check for missing segments after itself. + last_segment = Some(segment_idx); + + // Serialize the data. + seq_serializer.serialize_element(&MemoryData(&segment_data))?; + } + + seq_serializer.end() + } + + pub fn serialize_prime(_value: &(), serializer: S) -> Result + where + S: Serializer, + { + #[cfg(any(target_arch = "wasm32", no_std, not(feature = "std")))] + use crate::alloc::string::ToString; + + // Note: This uses an API intended only for testing. + serde_json::Number::from_string_unchecked(CAIRO_PRIME.to_string()).serialize(serializer) + } + + pub fn serialize_version(_value: &(), serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(CAIRO_PIE_VERSION) + } +} + +#[cfg(test)] +mod test { + use super::*; + use serde_json::json; + + #[test] + fn serialize_cairo_pie_memory() { + #[derive(Serialize)] + struct MemoryWrapper( + #[serde(serialize_with = "serde_impl::serialize_memory")] CairoPieMemory, + ); + + let memory = MemoryWrapper(vec![ + ((1, 0), MaybeRelocatable::Int(10.into())), + ((1, 1), MaybeRelocatable::Int(11.into())), + ((1, 4), MaybeRelocatable::Int(12.into())), + ((1, 8), MaybeRelocatable::RelocatableValue((1, 2).into())), + ((2, 0), MaybeRelocatable::RelocatableValue((3, 4).into())), + ((4, 8), MaybeRelocatable::RelocatableValue((5, 6).into())), + ]); + + assert_eq!( + serde_json::to_value(memory).unwrap(), + json!([ + (), + [10, 11, (), (), 12, (), (), (), [1, 2]], + [[3, 4,]], + (), + [(), (), (), (), (), (), (), (), [5, 6]] + ]), + ); + } +} diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 6fac3dd69a..b9507b4e75 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -52,7 +52,7 @@ use serde::{Deserialize, Serialize}; use super::{ builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner, OUTPUT_BUILTIN_NAME}, - cairo_pie::{self, CairoPie, CairoPieMetadata}, + cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion}, }; #[derive(Clone, Debug, Eq, PartialEq)] @@ -1251,6 +1251,7 @@ impl CairoRunner { .iter() .map(|b| (b.name().to_string(), b.get_additional_data())) .collect(), + version: CairoPieVersion { cairo_pie: () }, }) }