use std::collections::HashMap;
use std::num::ParseIntError;
use cairo_lang_starknet_classes::casm_contract_class::CasmContractEntryPoint;
use itertools::Itertools;
use serde::de::Error as DeserializationError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use crate::contract_class::EntryPointType;
use crate::core::EntryPointSelector;
use crate::hash::StarkHash;
use crate::serde_utils::deserialize_optional_contract_class_abi_entry_vector;
use crate::StarknetApiError;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ContractClass {
#[serde(default, deserialize_with = "deserialize_optional_contract_class_abi_entry_vector")]
pub abi: Option<Vec<ContractClassAbiEntry>>,
pub program: Program,
pub entry_points_by_type: HashMap<EntryPointType, Vec<EntryPointV0>>,
}
impl ContractClass {
pub fn bytecode_length(&self) -> usize {
self.program.data.as_array().expect("The program data must be an array.").len()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields, untagged)]
pub enum ContractClassAbiEntry {
Constructor(FunctionAbiEntry<ConstructorType>),
Event(EventAbiEntry),
Function(FunctionAbiEntry<FunctionType>),
L1Handler(FunctionAbiEntry<L1HandlerType>),
Struct(StructAbiEntry),
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct EventAbiEntry {
pub data: Vec<TypedParameter>,
pub keys: Vec<TypedParameter>,
pub name: String,
pub r#type: EventType,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
#[default]
Event,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct FunctionAbiEntry<TYPE> {
pub inputs: Vec<TypedParameter>,
pub name: String,
pub outputs: Vec<TypedParameter>,
#[serde(rename = "stateMutability", default, skip_serializing_if = "Option::is_none")]
pub state_mutability: Option<FunctionStateMutability>,
pub r#type: TYPE,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FunctionType {
#[default]
Function,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConstructorType {
#[default]
Constructor,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum L1HandlerType {
#[default]
L1Handler,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub enum FunctionStateMutability {
#[serde(rename = "view")]
#[default]
View,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct StructAbiEntry {
pub members: Vec<StructMember>,
pub name: String,
pub size: usize,
pub r#type: StructType,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StructType {
#[default]
Struct,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct StructMember {
pub name: String,
pub offset: usize,
pub r#type: String,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct Program {
#[serde(default)]
pub attributes: serde_json::Value,
pub builtins: serde_json::Value,
#[serde(default)]
pub compiler_version: serde_json::Value,
pub data: serde_json::Value,
#[serde(default)]
pub debug_info: serde_json::Value,
#[serde(serialize_with = "serialize_hints_sorted")]
pub hints: serde_json::Value,
pub identifiers: serde_json::Value,
pub main_scope: serde_json::Value,
pub prime: serde_json::Value,
pub reference_manager: serde_json::Value,
}
fn serialize_hints_sorted<S>(hints: &serde_json::Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if hints.is_null() {
return serializer.serialize_none();
}
let hints_map =
hints.as_object().ok_or(serde::ser::Error::custom("Hints are not a mapping."))?;
serializer.collect_map(
hints_map
.iter()
.map(|(k, v)| Ok((k.parse::<u32>()?, v)))
.collect::<Result<Vec<_>, ParseIntError>>()
.map_err(serde::ser::Error::custom)?
.iter()
.sorted_by_key(|(k, _v)| *k)
.map(|(k, v)| (k.to_string(), v)),
)
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
pub struct EntryPointV0 {
pub selector: EntryPointSelector,
pub offset: EntryPointOffset,
}
impl TryFrom<CasmContractEntryPoint> for EntryPointV0 {
type Error = StarknetApiError;
fn try_from(value: CasmContractEntryPoint) -> Result<Self, Self::Error> {
Ok(EntryPointV0 {
selector: EntryPointSelector(StarkHash::from(value.selector)),
offset: EntryPointOffset(value.offset),
})
}
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct TypedParameter {
pub name: String,
pub r#type: String,
}
#[derive(
Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord,
)]
pub struct EntryPointOffset(
#[serde(deserialize_with = "number_or_string", serialize_with = "usize_to_hex")] pub usize,
);
impl TryFrom<String> for EntryPointOffset {
type Error = StarknetApiError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(Self(hex_string_try_into_usize(&value)?))
}
}
pub fn number_or_string<'de, D: Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
let usize_value = match Value::deserialize(deserializer)? {
Value::Number(number) => number
.as_u64()
.and_then(|num_u64| usize::try_from(num_u64).ok())
.ok_or(DeserializationError::custom("Cannot cast number to usize."))?,
Value::String(s) => hex_string_try_into_usize(&s).map_err(DeserializationError::custom)?,
_ => return Err(DeserializationError::custom("Cannot cast value into usize.")),
};
Ok(usize_value)
}
fn hex_string_try_into_usize(hex_string: &str) -> Result<usize, std::num::ParseIntError> {
usize::from_str_radix(hex_string.trim_start_matches("0x"), 16)
}
fn usize_to_hex<S>(value: &usize, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(format!("{value:#x}").as_str())
}