Skip to content

Commit

Permalink
serde: implement serialize_option (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
tcoratger authored Nov 5, 2024
1 parent 04be8f4 commit b976144
Show file tree
Hide file tree
Showing 3 changed files with 1,403 additions and 22 deletions.
206 changes: 184 additions & 22 deletions crates/exex/src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ pub enum KakarotSerdeError {
/// The name of the missing field.
field: String,
},

/// Error variant indicating that an invalid value was encountered during serialization.
#[error("Invalid value for field '{field}' in serialization process.")]
InvalidFieldValue {
/// The name of the invalid field.
field: String,
},
}

/// Represents the types used in Cairo, including felt types, pointers, tuples, and structs.
Expand Down Expand Up @@ -344,6 +351,37 @@ impl KakarotSerde {
}
}

/// Serializes an optional value at the specified pointer in Cairo memory.
///
/// In Cairo, a `model.Option` contains two fields:
/// - `is_some`: A boolean field indicating whether the option contains a value.
/// - `value`: The value contained in the option, if `is_some` is `true`.
///
/// # Errors
///
/// This function returns an error if:
/// - The `is_some` field is not `0` or `1`.
/// - The `is_some` field is `1`, but the `value` field is missing.
pub fn serialize_option(
&self,
ptr: Relocatable,
) -> Result<Option<MaybeRelocatable>, KakarotSerdeError> {
// Retrieve the serialized "Option" struct as a map of field names to values.
let raw = self.serialize_pointers("model.Option", ptr)?;

// Validate the "is_some" field.
match raw.get("is_some") {
Some(Some(MaybeRelocatable::Int(v))) if *v == Felt252::ZERO => Ok(None),
Some(Some(MaybeRelocatable::Int(v))) if *v == Felt252::ONE => {
// `is_some` is `1`, so check for "value" field.
raw.get("value")
.cloned()
.ok_or_else(|| KakarotSerdeError::MissingField { field: "value".to_string() })
}
_ => Err(KakarotSerdeError::InvalidFieldValue { field: "is_some".to_string() }),
}
}

/// Serializes the specified scope within the Cairo VM by attempting to extract
/// a specific data structure based on the scope's path.
pub fn serialize_scope(
Expand Down Expand Up @@ -389,9 +427,28 @@ mod tests {
};
use std::str::FromStr;

fn setup_kakarot_serde() -> KakarotSerde {
/// Represents different test programs used for testing serialization and deserialization.
enum TestProgram {
KeccakAddUint256,
ModelOption,
}

impl TestProgram {
/// Retrieves the byte representation of the selected test program.
///
/// This method returns the contents of the JSON file associated with each test program,
/// allowing the test runner to load the serialized test data directly into memory.
fn path(&self) -> &[u8] {
match self {
Self::KeccakAddUint256 => include_bytes!("../testdata/keccak_add_uint256.json"),
Self::ModelOption => include_bytes!("../testdata/model_option.json"),
}
}
}

fn setup_kakarot_serde(test_program: TestProgram) -> KakarotSerde {
// Load the valid program content from a JSON file
let program_content = include_bytes!("../testdata/keccak_add_uint256.json");
let program_content = test_program.path();

// Create a Program instance from the loaded bytes, specifying "main" as the entry point
let program = Program::from_bytes(program_content, Some("main")).unwrap();
Expand All @@ -406,7 +463,7 @@ mod tests {
#[test]
fn test_program_identifier_valid() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Check if the identifier "main" with expected type "function" is correctly retrieved
assert_eq!(
Expand Down Expand Up @@ -440,7 +497,7 @@ mod tests {
#[test]
fn test_non_existent_identifier() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Test for a non-existent identifier
let result =
Expand All @@ -459,7 +516,7 @@ mod tests {
#[test]
fn test_incorrect_identifier_usage() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// 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()));
Expand All @@ -477,7 +534,7 @@ mod tests {
#[test]
fn test_valid_identifier_incorrect_type() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Test for a valid identifier but with an incorrect type
let result = kakarot_serde.get_identifier("main", Some("struct".to_string()));
Expand All @@ -495,7 +552,7 @@ mod tests {
#[test]
fn test_identifier_with_multiple_matches() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Test for an identifier with multiple matches
let result = kakarot_serde.get_identifier("ImplicitArgs", Some("struct".to_string()));
Expand All @@ -514,7 +571,7 @@ mod tests {
#[test]
fn test_serialize_pointer_not_struct() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Add a new memory segment to the virtual machine (VM).
let base = kakarot_serde.runner.vm.add_memory_segment();
Expand All @@ -535,7 +592,7 @@ mod tests {
#[test]
fn test_serialize_pointer_empty() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Serialize the pointers of the "ImplicitArgs" struct but without any memory segment.
let result = kakarot_serde
Expand All @@ -549,7 +606,7 @@ mod tests {
#[test]
fn test_serialize_pointer_valid() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Setup
let output_ptr = Felt252::ZERO;
Expand Down Expand Up @@ -591,7 +648,7 @@ mod tests {
#[test]
fn test_serialize_null_no_pointer() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Setup
let output_ptr = Relocatable { segment_index: 10, offset: 11 };
Expand Down Expand Up @@ -631,7 +688,7 @@ mod tests {
#[test]
fn test_serialize_uint256_0() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// U256 to be serialized
let x = U256::ZERO;
Expand Down Expand Up @@ -661,7 +718,7 @@ mod tests {
#[test]
fn test_serialize_uint256_valid() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// U256 to be serialized
let x =
Expand Down Expand Up @@ -693,7 +750,7 @@ mod tests {
#[test]
fn test_serialize_uint256_not_int_high() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// U256 to be serialized
let x = U256::MAX;
Expand Down Expand Up @@ -728,7 +785,7 @@ mod tests {
#[test]
fn test_serialize_uint256_not_int_low() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// U256 to be serialized
let x = U256::MAX;
Expand Down Expand Up @@ -757,7 +814,7 @@ mod tests {
#[test]
fn test_get_offset_tuple() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Create Cairo types for Tuple members.
let member1 = TupleItem::new(Some("a".to_string()), CairoType::felt_type(None), None);
Expand All @@ -778,7 +835,7 @@ mod tests {
#[test]
fn test_get_offset_struct_invalid_identifier() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Create a Cairo type for a Struct with an invalid identifier.
let cairo_type = CairoType::Struct {
Expand All @@ -801,7 +858,7 @@ mod tests {
#[test]
fn test_get_offset_struct_valid_identifier() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Create a Cairo type for a Struct with a valid identifier (3 members).
let cairo_type = CairoType::Struct {
Expand All @@ -816,7 +873,7 @@ mod tests {
#[test]
fn test_get_offset_struct_valid_identifier_without_members() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Create a Cairo type for a Struct with a valid identifier (no members).
let cairo_type = CairoType::Struct {
Expand All @@ -831,7 +888,7 @@ mod tests {
#[test]
fn test_get_offset_felt_pointer() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();
let kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Create a Cairo type for a Felt.
let cairo_type = CairoType::felt_type(None);
Expand Down Expand Up @@ -1000,7 +1057,7 @@ mod tests {
#[test]
fn test_serialize_inner_felt() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Setup
let output_ptr = Relocatable { segment_index: 10, offset: 11 };
Expand Down Expand Up @@ -1056,7 +1113,7 @@ mod tests {
#[test]
fn test_serialize_scope_uint256() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();
let mut kakarot_serde = setup_kakarot_serde(TestProgram::KeccakAddUint256);

// Define a ScopedName ending in "Uint256"
let scope = ScopedName {
Expand Down Expand Up @@ -1095,4 +1152,109 @@ mod tests {
// Assert that the result matches the expected serialized U256 value
assert_eq!(result, Ok(SerializedScope::U256(x)));
}

#[test]
fn test_serialize_option_some_value() {
// Setup KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde(TestProgram::ModelOption);

// Setup
let is_some = Felt252::ONE;
let value_ptr = kakarot_serde.runner.vm.add_memory_segment();

// Insert values in memory
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![
MaybeRelocatable::Int(is_some),
MaybeRelocatable::RelocatableValue(value_ptr),
])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the Option struct using the new memory segment.
let result =
kakarot_serde.serialize_option(base).expect("failed to serialize model.Option");

// Assert that the result matches the expected serialized struct members.
assert_eq!(result, Some(MaybeRelocatable::RelocatableValue(value_ptr)));
}

#[test]
fn test_serialize_option_none_value() {
// Setup KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde(TestProgram::ModelOption);

// Setup `is_some` as 0 to indicate None
let is_some = Felt252::ZERO;

// Insert values in memory
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![MaybeRelocatable::Int(is_some)])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the Option struct with `is_some` as `false`.
let result =
kakarot_serde.serialize_option(base).expect("failed to serialize model.Option");

// Assert that the result is None since `is_some` is `false`.
assert!(result.is_none());
}

#[test]
fn test_serialize_option_missing_value_error() {
// Setup KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde(TestProgram::ModelOption);

// Set `is_some` to 1 but don't provide a `value` field to trigger an error.
let is_some = Felt252::ONE;

// Insert `is_some` in memory without a corresponding `value`.
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![MaybeRelocatable::Int(is_some)])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the Option struct expecting an error due to missing `value`.
let result = kakarot_serde.serialize_option(base);

// Assert that an error is returned for the missing `value` field.
assert_eq!(result, Err(KakarotSerdeError::MissingField { field: "value".to_string() }));
}

#[test]
fn test_serialize_option_invalid_is_some_error() {
// Setup KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde(TestProgram::ModelOption);

// Set `is_some` to an invalid value (e.g., 2) to trigger an error.
let invalid_is_some = Felt252::from(2);

// Insert invalid `is_some` in memory.
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![MaybeRelocatable::Int(invalid_is_some)])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the Option struct expecting an error due to invalid `is_some`.
let result = kakarot_serde.serialize_option(base);

// Assert that an error is returned for the invalid `is_some` value.
assert_eq!(
result,
Err(KakarotSerdeError::InvalidFieldValue { field: "is_some".to_string() })
);
}
}
10 changes: 10 additions & 0 deletions crates/exex/testdata/model_option.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
%builtins output range_check

from src.model import model

func main{output_ptr: felt*, range_check_ptr}() {
let address = 0xdead;
let res = model.Option(is_some=1, value=address);

return ();
}
Loading

0 comments on commit b976144

Please sign in to comment.