Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kkrt native 2.8 #1

Open
wants to merge 13 commits into
base: native2.8.x
Choose a base branch
from
Prev Previous commit
Next Next commit
serialize
  • Loading branch information
enitrat committed Sep 24, 2024
commit 101dccf5c7eb32d8906897053cf4b4e102a597c9
181 changes: 173 additions & 8 deletions crates/blockifier/src/execution/contract_class.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use std::sync::Arc;
use cairo_lang_casm;
use serde::de::Error;
use cairo_lang_casm::hints::Hint;
use num_traits::Num;
use cairo_lang_sierra::ids::FunctionId;
use cairo_lang_starknet_classes::casm_contract_class::{CasmContractClass, CasmContractEntryPoint};
use cairo_lang_starknet_classes::contract_class::{
@@ -29,7 +30,8 @@ use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use itertools::Itertools;

use serde::de::{Error as DeserializationError, Visitor};
use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use starknet_api::core::EntryPointSelector;
use starknet_api::deprecated_contract_class::{
ContractClass as DeprecatedContractClass,
@@ -43,7 +45,7 @@ use starknet_types_core::hash::{Poseidon, StarkHash};

use super::entry_point::EntryPointExecutionResult;
use super::errors::EntryPointExecutionError;
use super::execution_utils::poseidon_hash_many_cost;
use super::execution_utils::{cairo_vm_to_sn_api_program, poseidon_hash_many_cost};
use super::native::utils::contract_entrypoint_to_entrypoint_selector;
use crate::abi::abi_utils::selector_from_name;
use crate::abi::constants::{self, CONSTRUCTOR_ENTRY_POINT_NAME};
@@ -71,6 +73,7 @@ pub enum ContractClass {
V1Native(NativeContractClassV1),
}


impl<'de> Deserialize<'de> for ContractClass {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -162,7 +165,7 @@ impl ContractClass {
/// class.
// Note: when deserializing from a SN API class JSON string, the ABI field is ignored
// by serde, since it is not required for execution.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct ContractClassV0(pub Arc<ContractClassV0Inner>);
impl Deref for ContractClassV0 {
type Target = ContractClassV0Inner;
@@ -210,9 +213,9 @@ impl ContractClassV0 {
}
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct ContractClassV0Inner {
#[serde(deserialize_with = "deserialize_program")]
#[serde(deserialize_with = "deserialize_program", serialize_with = "serialize_program")]
pub program: Program,
pub entry_points_by_type: HashMap<EntryPointType, Vec<EntryPoint>>,
}
@@ -233,8 +236,7 @@ impl TryFrom<DeprecatedContractClass> for ContractClassV0 {
/// Represents a runnable Cario (Cairo 1) Starknet contract class (meaning, the program is runnable
/// by the VM). We wrap the actual class in an Arc to avoid cloning the program when cloning the
/// class.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[serde(try_from = "CasmContractClass")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractClassV1(pub Arc<ContractClassV1Inner>);
impl Deref for ContractClassV1 {
type Target = ContractClassV1Inner;
@@ -244,6 +246,154 @@ impl Deref for ContractClassV1 {
}
}

impl Serialize for ContractClassV1 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Convert the ContractClassV1 instance to CasmContractClass
let casm_contract_class: CasmContractClass = self
.try_into()
.map_err(|err: ProgramError| serde::ser::Error::custom(err.to_string()))?;
// Serialize the JSON string to bytes
casm_contract_class.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for ContractClassV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize into a JSON value
let json_value: serde_json::Value = Deserialize::deserialize(deserializer)?;
// Convert into a JSON string
let json_string = serde_json::to_string(&json_value)
.map_err(|err| DeserializationError::custom(err.to_string()))?;
// Use try_from_json_string to deserialize into ContractClassV1
ContractClassV1::try_from_json_string(&json_string)
.map_err(|err| DeserializationError::custom(err.to_string()))
}
}

// Implementation of the TryInto trait to convert a reference of ContractClassV1 into
// CasmContractClass.
impl TryInto<CasmContractClass> for &ContractClassV1 {
// Definition of the error type that can be returned during the conversion.
type Error = ProgramError;
// Implementation of the try_into function which performs the conversion.
fn try_into(self) -> Result<CasmContractClass, Self::Error> {
// Converting the program data into a vector of BigUintAsHex.
let bytecode: Vec<cairo_lang_utils::bigint::BigUintAsHex> = self
.program
.iter_data()
.map(|x| cairo_lang_utils::bigint::BigUintAsHex {
value: x.get_int_ref().unwrap().to_biguint(),
})
.collect();
// Serialize the Program object to JSON bytes.
let serialized_program = self.program.serialize()?;
// Deserialize the JSON bytes into a serde_json::Value.
let json_value: serde_json::Value = serde_json::from_slice(&serialized_program)?;
// Extract the hints from the JSON value.
let hints = json_value.get("hints").ok_or_else(|| {
ProgramError::Parse(serde::ser::Error::custom("failed to parse hints"))
})?;
// Transform the hints into a vector of tuples (usize, Vec<Hint>).
let hints: Vec<(usize, Vec<Hint>)> = hints
.as_object() // Convert to JSON object.
.unwrap()
.iter()
.map(|(key, value)| {
// Transform each hint value into a Vec<Hint>.
let hints: Vec<Hint> = value
.as_array() // Convert to JSON array.
.unwrap()
.iter()
.map(|hint_params| {
// Extract the "code" parameter and convert to a string.
let hint_param_code = hint_params.get("code").unwrap().clone();
let hint_string = hint_param_code.as_str().expect("failed to parse hint as string");
// Retrieve the hint from the self.hints map.
self.hints.get(hint_string).expect("failed to get hint").clone()
})
.collect();
// Convert the key to usize and create a tuple (usize, Vec<Hint>).
(key.parse().unwrap(), hints)
})
.collect();
// Define the bytecode segment lengths
let bytecode_segment_lengths = Some(self.bytecode_segment_lengths.clone());
// Transform the entry points of type Constructor into CasmContractEntryPoint.
let constructor = self
.entry_points_by_type
.get(&EntryPointType::Constructor)
.unwrap_or(&vec![])
.iter()
.map(|constructor| CasmContractEntryPoint {
selector: num_bigint::BigUint::from_bytes_be(&constructor.selector.0.to_bytes_be()),
offset: constructor.offset.0,
builtins: constructor
.builtins
.clone()
.into_iter()
.map(|x| x.to_string().into())
.collect(),
})
.collect();
// Transform the entry points of type External into CasmContractEntryPoint.
let external = self
.entry_points_by_type
.get(&EntryPointType::External)
.unwrap_or(&vec![])
.iter()
.map(|external| CasmContractEntryPoint {
selector: num_bigint::BigUint::from_bytes_be(&external.selector.0.to_bytes_be()),
offset: external.offset.0,
builtins: external
.builtins
.clone()
.into_iter()
.map(|x| x.to_string().into())
.collect(),
})
.collect();
// Transform the entry points of type L1Handler into CasmContractEntryPoint.
let l1_handler = self
.entry_points_by_type
.get(&EntryPointType::L1Handler)
.unwrap_or(&vec![])
.iter()
.map(|l1_handler| CasmContractEntryPoint {
selector: num_bigint::BigUint::from_bytes_be(&l1_handler.selector.0.to_bytes_be()),
offset: l1_handler.offset.0,
builtins: l1_handler
.builtins
.clone()
.into_iter()
.map(|x| x.to_string().into())
.collect(),
})
.collect();
// Construct the CasmContractClass from the extracted and transformed data.
Ok(CasmContractClass {
prime: num_bigint::BigUint::from_str_radix(&self.program.prime()[2..], 16)
.expect("failed to parse prime"),
compiler_version: "".to_string(),
bytecode,
bytecode_segment_lengths,
hints,
pythonic_hints: None,
entry_points_by_type:
cairo_lang_starknet_classes::casm_contract_class::CasmContractEntryPoints {
constructor,
external,
l1_handler,
},
})
}
}

impl ContractClassV1 {
fn constructor_selector(&self) -> Option<EntryPointSelector> {
Some(self.0.entry_points_by_type[&EntryPointType::Constructor].first()?.selector)
@@ -513,6 +663,16 @@ pub fn deserialize_program<'de, D: Deserializer<'de>>(
.map_err(|err| DeserializationError::custom(err.to_string()))
}

/// Converts the program type from Cairo VM into a SN API-compatible type.
pub fn serialize_program<S: Serializer>(
program: &Program,
serializer: S,
) -> Result<S::Ok, S::Error> {
let deprecated_program = cairo_vm_to_sn_api_program(program.clone())
.map_err(|err| serde::ser::Error::custom(err.to_string()))?;
deprecated_program.serialize(serializer)
}

// V1 utilities.

// TODO(spapini): Share with cairo-lang-runner.
@@ -536,7 +696,12 @@ fn convert_entry_points_v1(external: Vec<CasmContractEntryPoint>) -> Vec<EntryPo
builtins: ep
.builtins
.into_iter()
.map(|builtin| BuiltinName::from_str(&builtin).expect("Unrecognized builtin."))
.map(|builtin| match BuiltinName::from_str(&builtin) {
Some(builtin) => builtin,
None => {
BuiltinName::from_str_with_suffix(&builtin).expect("Unrecognized builtin.")
}
})
.collect(),
})
.collect()
120 changes: 119 additions & 1 deletion crates/blockifier/src/execution/execution_utils.rs
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ use starknet_api::core::ClassHash;
use starknet_api::deprecated_contract_class::Program as DeprecatedProgram;
use starknet_api::transaction::Calldata;
use starknet_types_core::felt::Felt;
use std::str::FromStr;

use super::entry_point::ConstructorEntryPointExecutionResult;
use super::errors::ConstructorEntryPointExecutionError;
@@ -138,6 +139,13 @@ pub fn felt_range_from_ptr(
Ok(values)
}

#[derive(serde::Serialize, serde::Deserialize)]
pub struct ReferenceTmp {
pub ap_tracking_data: cairo_vm::serde::deserialize_program::ApTracking,
pub pc: Option<usize>,
pub value_address: cairo_vm::serde::deserialize_program::ValueAddress,
}

// TODO(Elin,01/05/2023): aim to use LC's implementation once it's in a separate crate.
pub fn sn_api_to_cairo_vm_program(program: DeprecatedProgram) -> Result<Program, ProgramError> {
let identifiers = serde_json::from_value::<HashMap<String, Identifier>>(program.identifiers)?;
@@ -154,7 +162,32 @@ pub fn sn_api_to_cairo_vm_program(program: DeprecatedProgram) -> Result<Program,
};

let instruction_locations = None;
let reference_manager = serde_json::from_value::<ReferenceManager>(program.reference_manager)?;

// Deserialize the references in ReferenceManager
let mut reference_manager = ReferenceManager::default();
if let Some(references_value) = program.reference_manager.get("references") {
for reference_value in references_value
.as_array()
.unwrap_or_else(|| panic!("Expected 'references' to be an array"))
{
if reference_value.get("value_address").is_some() {
// Directly deserialize references_value without using deserialize_value_address
let tmp = serde_json::from_value::<ReferenceTmp>(reference_value.clone())?;
reference_manager.references.push(
cairo_vm::serde::deserialize_program::Reference {
ap_tracking_data: tmp.ap_tracking_data,
pc: tmp.pc,
value_address: tmp.value_address,
},
);
} else {
let tmp = serde_json::from_value::<cairo_vm::serde::deserialize_program::Reference>(
reference_value.clone(),
)?;
reference_manager.references.push(tmp);
}
}
}

let program = Program::new(
builtins,
@@ -170,6 +203,91 @@ pub fn sn_api_to_cairo_vm_program(program: DeprecatedProgram) -> Result<Program,
Ok(program)
}

// Function to convert MaybeRelocatable to hex string
fn maybe_relocatable_to_hex_string(mr: &MaybeRelocatable) -> String {
match mr {
MaybeRelocatable::Int(value) => value.to_hex_string(),
_ => unimplemented!(),
}
}
// Helper function to process identifiers
fn process_identifiers(
json_value: &serde_json::Value,
) -> serde_json::Map<String, serde_json::Value> {
json_value.get("identifiers").and_then(serde_json::Value::as_object).map_or_else(
serde_json::Map::new,
|identifiers_obj| {
identifiers_obj
.iter()
.filter_map(|(key, inner_value)| {
inner_value.as_object().map(|inner_obj| {
let filtered_inner_obj = inner_obj
.iter()
.filter_map(|(inner_key, inner_val)| {
if inner_val.is_null() {
return None;
}
// Rename the key if it's "type_" to "type"
let renamed_key = if inner_key == "type_" {
"type".to_string()
} else {
inner_key.to_string()
};
// Check if the key is "value" and extract the "val" field to
// convert it to a JSON number
let value = match renamed_key.as_str() {
"value" => serde_json::Value::Number(
serde_json::Number::from_str(
&Felt::from_str(inner_val.as_str().unwrap())
.unwrap()
.to_string(),
)
.unwrap(),
),
_ => inner_val.clone(),
};
Some((renamed_key, value))
})
.collect::<serde_json::Map<_, _>>();
(key.to_string(), serde_json::Value::Object(filtered_inner_obj))
})
})
.collect()
},
)
}
// Main function to convert Program to DeprecatedProgram
pub fn cairo_vm_to_sn_api_program(program: Program) -> Result<DeprecatedProgram, ProgramError> {
// Serialize the Program object to JSON bytes
let serialized_program = program.serialize()?;
// Deserialize the JSON bytes into a Value
let json_value: serde_json::Value = serde_json::from_slice(&serialized_program)?;
// Convert the data segment to the expected hex string format
let data = serde_json::to_value(
program
.iter_data()
.cloned()
.map(|mr: MaybeRelocatable| maybe_relocatable_to_hex_string(&mr))
.collect::<Vec<String>>(),
)?;
// Process identifiers
let identifiers = process_identifiers(&json_value);
// println!("identifiers: {:?}", identifiers);
Ok(DeprecatedProgram {
attributes: json_value.get("attributes").cloned().unwrap_or_default(),
builtins: json_value.get("builtins").cloned().unwrap(),
compiler_version: json_value.get("compiler_version").cloned().unwrap_or_default(),
data,
debug_info: json_value.get("debug_info").cloned().unwrap_or_default(),
hints: json_value.get("hints").cloned().unwrap(),
identifiers: serde_json::Value::Object(identifiers),
main_scope: json_value.get("main_scope").cloned().unwrap_or_default(),
prime: json_value.get("prime").cloned().unwrap(),
reference_manager: json_value.get("reference_manager").cloned().unwrap(),
})
}


#[derive(Debug)]
// Invariant: read-only.
pub struct ReadOnlySegment {