use std::hash::{Hash, Hasher};
use std::io;
use std::num::NonZeroUsize;
use std::sync::LazyLock;
use ahash::AHasher;
use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
use cairo_lang_starknet_classes::contract_class::ContractClass;
use lru::LruCache;
use parking_lot::Mutex;
use serde_json::ser::Formatter;
use serde_json::{Map, Value};
use crate::error::{DevnetResult, Error, JsonError};
#[allow(clippy::expect_used)]
static CASM_CACHE: LazyLock<Mutex<LruCache<u64, (serde_json::Value, CasmContractClass)>>> =
LazyLock::new(|| {
Mutex::new(LruCache::new(NonZeroUsize::new(10).expect("cache size must be > 0")))
});
pub fn traverse_and_exclude_recursively<F>(
value: &Value,
exclude_condition: &F,
) -> serde_json::Value
where
F: Fn(&String, &Value) -> bool,
{
match value {
Value::Object(object) => {
let mut new_object = Map::new();
for (key, value) in object {
if exclude_condition(key, value) {
continue;
}
let inner_val = traverse_and_exclude_recursively(value, exclude_condition);
new_object.insert(key.to_string(), inner_val);
}
Value::Object(new_object.clone())
}
Value::Array(array) => {
let mut inner_arr = Vec::<Value>::new();
for value in array {
let inner_val = traverse_and_exclude_recursively(value, exclude_condition);
match inner_val.as_object() {
Some(inner_obj) if inner_obj.is_empty() => {}
_ => inner_arr.push(inner_val),
}
}
Value::Array(inner_arr)
}
_ => value.clone(),
}
}
pub struct StarknetFormatter;
impl Formatter for StarknetFormatter {
fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b": ")
}
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
if first { Ok(()) } else { writer.write_all(b", ") }
}
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
if first { Ok(()) } else { writer.write_all(b", ") }
}
}
pub fn compile_sierra_contract(sierra_contract: &ContractClass) -> DevnetResult<CasmContractClass> {
let sierra_contract_json = serde_json::to_value(sierra_contract)
.map_err(|err| Error::JsonError(JsonError::SerdeJsonError(err)))?;
compile_sierra_contract_json(sierra_contract_json)
}
pub fn compile_sierra_contract_json(
sierra_contract_json: serde_json::Value,
) -> DevnetResult<CasmContractClass> {
let cache_key = {
let mut hasher = AHasher::default();
sierra_contract_json.hash(&mut hasher);
hasher.finish()
};
{
let mut cache = CASM_CACHE.lock();
if let Some((cached_input, cached_output)) = cache.get(&cache_key)
&& cached_input == &sierra_contract_json
{
return Ok(cached_output.clone());
}
}
let casm_json = usc::compile_contract(sierra_contract_json.clone())
.map_err(|err| Error::SierraCompilationError { reason: err.to_string() })?;
let casm = serde_json::from_value::<CasmContractClass>(casm_json)
.map_err(|err| Error::JsonError(JsonError::SerdeJsonError(err)))?;
{
let mut cache = CASM_CACHE.lock();
cache.put(cache_key, (sierra_contract_json, casm.clone()));
}
Ok(casm)
}
#[cfg(test)]
pub(crate) mod test_utils {
pub(crate) const CAIRO_0_ACCOUNT_CONTRACT_PATH: &str =
"../../contracts/test_artifacts/cairo0/account.json";
pub(crate) const CAIRO_0_RPC_CONTRACT_PATH: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/cairo_0_rpc.json");
pub(crate) const CAIRO_0_ACCOUNT_CONTRACT_HASH: &str =
"0x4d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f";
pub(crate) const CAIRO_1_EVENTS_CONTRACT_PATH: &str =
"../../contracts/test_artifacts/cairo1/events/events_2.0.1_compiler.sierra";
pub(crate) const CAIRO_1_CONTRACT_SIERRA_HASH: &str =
"0x113bf26d112a164297e04381212c9bd7409f07591f0a04f539bdf56693eaaf3";
}
#[cfg(test)]
mod tests {
use serde_json::Value;
#[test]
fn serde_remove_elements_from_json() {
let input = r#"
{
"name": "John Doe",
"isStudent": true,
"age":30,
"address": {
"street": "Vlvo",
"city": "Anytown",
"state": "Any"
},
"should_be_removed": [],
"scores":
[
{
"street": "AAA",
"age": 5,
"should_be_removed": []
},
{
"age": 5
}
],
"arr": [90, 85, 95]
}
"#;
let expected_output = r#"
{
"name": "John Doe",
"isStudent": true,
"age":30,
"address": {
"street": "Vlvo",
"city": "Anytown",
"state": "Any"
},
"scores":
[
{
"street": "AAA",
"age": 5
},
{
"age": 5
}
],
"arr": [90, 85, 95]
}
"#;
let value: Value = serde_json::from_str(input).unwrap();
let res = crate::utils::traverse_and_exclude_recursively(&value, &|key, val| {
key == "should_be_removed" && val.is_array() && val.as_array().unwrap().is_empty()
});
assert_eq!(res, serde_json::from_str::<serde_json::Value>(expected_output).unwrap());
}
}