diff --git a/Cargo.lock b/Cargo.lock index fd08cfd9..679b2bb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1496,6 +1496,7 @@ version = "1.0.1" source = "git+https://github.com/lambdaclass/cairo-vm.git?tag=v1.0.1#93f1f54f38059af89850b1c06d4c901300f9b94d" dependencies = [ "anyhow", + "arbitrary", "bincode 2.0.0-rc.3", "bitvec", "generic-array", @@ -8725,6 +8726,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" dependencies = [ + "arbitrary", "lambdaworks-crypto", "lambdaworks-math", "num-bigint", diff --git a/crates/exex/Cargo.toml b/crates/exex/Cargo.toml index 22282f5d..e2337311 100644 --- a/crates/exex/Cargo.toml +++ b/crates/exex/Cargo.toml @@ -10,7 +10,9 @@ workspace = true [dependencies] # Cairo-VM deps -cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm.git", tag = "v1.0.1" } +cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm.git", tag = "v1.0.1", features = [ + "test_utils", +] } kakarot-pool = { workspace = true } diff --git a/crates/exex/src/lib.rs b/crates/exex/src/lib.rs index 97cd4888..b77379e2 100644 --- a/crates/exex/src/lib.rs +++ b/crates/exex/src/lib.rs @@ -3,3 +3,4 @@ pub mod execution; pub mod exex; pub mod hints; pub mod model; +pub mod serde; diff --git a/crates/exex/src/serde.rs b/crates/exex/src/serde.rs new file mode 100644 index 00000000..3bd7a5e7 --- /dev/null +++ b/crates/exex/src/serde.rs @@ -0,0 +1,226 @@ +use cairo_vm::{ + serde::deserialize_program::Identifier, + vm::{runners::cairo_runner::CairoRunner, vm_memory::memory::Memory}, +}; +use thiserror::Error; + +/// Represents errors that can occur during the serialization and deserialization processes between +/// Cairo VM programs and Rust representations. +#[derive(Debug, Error)] +pub enum KakarotSerdeError { + /// Error variant indicating that no identifier matching the specified name was found. + #[error("Expected one struct named '{struct_name}', found 0 matches. Expected type: {expected_type:?}")] + IdentifierNotFound { + /// The name of the struct that was not found. + struct_name: String, + /// The expected type of the struct (if applicable). + expected_type: Option, + }, + + /// Error variant indicating that multiple identifiers matching the specified name were found. + #[error("Expected one struct named '{struct_name}', found {count} matches. Expected type: {expected_type:?}")] + MultipleIdentifiersFound { + /// The name of the struct for which multiple identifiers were found. + struct_name: String, + /// The expected type of the struct (if applicable). + expected_type: Option, + /// The number of matching identifiers found. + count: usize, + }, +} + +/// A structure representing the Kakarot serialization and deserialization context for Cairo +/// programs. +/// +/// This struct encapsulates the components required to serialize and deserialize +/// Kakarot programs, including: +/// - The Cairo runner responsible for executing the program +/// - The memory +#[allow(missing_debug_implementations)] +pub struct KakarotSerde { + /// The Cairo runner used to execute Kakarot programs. + /// + /// This runner interacts with the Cairo virtual machine, providing the necessary + /// infrastructure for running and managing the execution of Cairo programs. + /// It is responsible for handling program execution flow, managing state, and + /// providing access to program identifiers. + runner: CairoRunner, + + /// The memory used to store memory cells inside segments and relocation rules. + /// + /// This is used to have direct access to the memory cells and their values. + #[allow(dead_code)] + memory: Memory, +} + +impl KakarotSerde { + /// Retrieves a unique identifier from the Cairo program based on the specified struct name and + /// expected type. + /// + /// This function searches for identifiers that match the provided struct name and type within + /// the Cairo program's identifier mappings. It returns an error if no identifiers or + /// multiple identifiers are found. + pub fn get_identifier( + &self, + struct_name: &str, + expected_type: Option, + ) -> Result { + // Retrieve identifiers from the program and filter them based on the struct name and + // expected type + let identifiers = self + .runner + .get_program() + .iter_identifiers() + .filter(|(key, value)| { + key.contains(struct_name) && + key.split('.').last() == struct_name.split('.').last() && + value.type_ == expected_type + }) + .map(|(_, value)| value) + .collect::>(); + + // Match on the number of found identifiers + match identifiers.len() { + // No identifiers found + 0 => Err(KakarotSerdeError::IdentifierNotFound { + struct_name: struct_name.to_string(), + expected_type, + }), + // Exactly one identifier found, return it + 1 => Ok(identifiers[0].clone()), + // More than one identifier found + count => Err(KakarotSerdeError::MultipleIdentifiersFound { + struct_name: struct_name.to_string(), + expected_type, + count, + }), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cairo_vm::types::{layout_name::LayoutName, program::Program}; + + fn setup_kakarot_serde() -> KakarotSerde { + // Load the valid program content from a JSON file + let program_content = include_bytes!("../../../cairo/programs/os.json"); + + // Create a Program instance from the loaded bytes, specifying "main" as the entry point + let program = Program::from_bytes(program_content, Some("main")).unwrap(); + + // Initialize a CairoRunner with the created program and default parameters + let runner = CairoRunner::new(&program, LayoutName::plain, false, false).unwrap(); + + // Return an instance of KakarotSerde + KakarotSerde { runner, memory: Memory::new() } + } + + #[test] + fn test_program_identifier_valid() { + // Setup the KakarotSerde instance + let kakarot_serde = setup_kakarot_serde(); + + // Check if the identifier "main" with expected type "function" is correctly retrieved + assert_eq!( + kakarot_serde.get_identifier("main", Some("function".to_string())).unwrap(), + Identifier { + pc: Some(3478), + type_: Some("function".to_string()), + value: None, + full_name: None, + members: None, + cairo_type: None + } + ); + + // Check if the identifier "__temp0" with expected type "reference" is correctly retrieved + assert_eq!( + kakarot_serde.get_identifier("__temp0", Some("reference".to_string())).unwrap(), + Identifier { + pc: None, + type_: Some("reference".to_string()), + value: None, + full_name: Some("starkware.cairo.common.memcpy.memcpy.__temp0".to_string()), + members: None, + cairo_type: Some("felt".to_string()) + } + ); + } + + #[test] + fn test_non_existent_identifier() { + // Setup the KakarotSerde instance + let kakarot_serde = setup_kakarot_serde(); + + // Test for a non-existent identifier + let result = + kakarot_serde.get_identifier("non_existent_struct", Some("function".to_string())); + + // Check if the error is valid and validate its parameters + if let Err(KakarotSerdeError::IdentifierNotFound { struct_name, expected_type }) = result { + assert_eq!(struct_name, "non_existent_struct"); + assert_eq!(expected_type, Some("function".to_string())); + } else { + panic!("Expected KakarotSerdeError::IdentifierNotFound"); + } + } + + #[test] + fn test_incorrect_identifier_usage() { + // Setup the KakarotSerde instance + let kakarot_serde = setup_kakarot_serde(); + + // Test for an identifier used incorrectly (not the last segment of the full name) + let result = kakarot_serde.get_identifier("check_range", Some("struct".to_string())); + + // Check if the error is valid and validate its parameters + if let Err(KakarotSerdeError::IdentifierNotFound { struct_name, expected_type }) = result { + assert_eq!(struct_name, "check_range"); + assert_eq!(expected_type, Some("struct".to_string())); + } else { + panic!("Expected KakarotSerdeError::IdentifierNotFound"); + } + } + + #[test] + fn test_valid_identifier_incorrect_type() { + // Setup the KakarotSerde instance + let kakarot_serde = setup_kakarot_serde(); + + // Test for a valid identifier but with an incorrect type + let result = kakarot_serde.get_identifier("main", Some("struct".to_string())); + + // Check if the error is valid and validate its parameters + if let Err(KakarotSerdeError::IdentifierNotFound { struct_name, expected_type }) = result { + assert_eq!(struct_name, "main"); + assert_eq!(expected_type, Some("struct".to_string())); + } else { + panic!("Expected KakarotSerdeError::IdentifierNotFound"); + } + } + + #[test] + fn test_identifier_with_multiple_matches() { + // Setup the KakarotSerde instance + let kakarot_serde = setup_kakarot_serde(); + + // Test for an identifier with multiple matches + let result = kakarot_serde.get_identifier("ImplicitArgs", Some("struct".to_string())); + + // Check if the error is valid and validate its parameters + if let Err(KakarotSerdeError::MultipleIdentifiersFound { + struct_name, + expected_type, + count, + }) = result + { + assert_eq!(struct_name, "ImplicitArgs"); + assert_eq!(expected_type, Some("struct".to_string())); + assert_eq!(count, 63); + } else { + panic!("Expected KakarotSerdeError::MultipleIdentifiersFound"); + } + } +}