From 546c58be90e1134384ac5b2e4232568151215a49 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 9 Jan 2024 13:52:39 +0900 Subject: [PATCH] fix(katana-primitives): fix legacy program conversion between RPC and inner types (#1403) --- Cargo.lock | 117 +++---- Cargo.toml | 2 +- crates/katana/primitives/Cargo.toml | 1 + .../katana/primitives/src/conversion/rpc.rs | 323 ++++++++++++++++-- 4 files changed, 359 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 468168b43e..f61e5cb64e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,7 +465,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.25.0", - "syn 2.0.41", + "syn 2.0.47", "thiserror", ] @@ -572,7 +572,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -620,7 +620,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -637,7 +637,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -852,7 +852,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -1135,7 +1135,7 @@ dependencies = [ "quote", "serde_json", "starknet", - "syn 2.0.41", + "syn 2.0.47", "thiserror", ] @@ -1151,7 +1151,7 @@ dependencies = [ "quote", "serde_json", "starknet", - "syn 2.0.41", + "syn 2.0.47", "thiserror", ] @@ -1392,7 +1392,7 @@ checksum = "c8cc59c40344194d2cc825071080d887826dcf0df37de71e58fc8aa4c344bb84" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -1917,7 +1917,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2317,7 +2317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2384,7 +2384,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2406,7 +2406,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3136,7 +3136,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.41", + "syn 2.0.47", "toml 0.8.8", "walkdir", ] @@ -3154,7 +3154,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3180,7 +3180,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.41", + "syn 2.0.47", "tempfile", "thiserror", "tiny-keccak", @@ -3642,7 +3642,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3713,7 +3713,7 @@ checksum = "d4cf186fea4af17825116f72932fe52cce9a13bae39ff63b4dc0cfdb3fb4bde1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -4133,7 +4133,7 @@ checksum = "02a5bcaf6704d9354a3071cede7e77d366a5980c7352e102e2c2f9b645b1d3ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -5477,7 +5477,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -5564,6 +5564,7 @@ dependencies = [ "flate2", "serde", "serde_json", + "serde_with", "starknet", "starknet_api", "thiserror", @@ -5996,7 +5997,7 @@ checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -6427,7 +6428,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -6520,7 +6521,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -6801,7 +6802,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -6865,7 +6866,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -6909,7 +6910,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -7110,7 +7111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -7172,9 +7173,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] @@ -7291,7 +7292,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "regex", - "syn 2.0.41", + "syn 2.0.47", "tempfile", "which 4.4.2", ] @@ -7319,7 +7320,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -7364,9 +7365,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -7751,7 +7752,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.41", + "syn 2.0.47", "unicode-ident", ] @@ -7764,7 +7765,7 @@ dependencies = [ "quote", "rand", "rustc_version", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -8218,9 +8219,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] @@ -8247,13 +8248,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -8297,7 +8298,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -8346,7 +8347,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -8381,7 +8382,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9023,7 +9024,7 @@ checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ "starknet-curve 0.4.0", "starknet-ff", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9066,7 +9067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "840be1a7eb5735863eee47d3a3f26df45b9be2c519e8da294e74b4d0524d77d1" dependencies = [ "starknet-core", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9199,7 +9200,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9241,9 +9242,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" dependencies = [ "proc-macro2", "quote", @@ -9363,7 +9364,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9383,7 +9384,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9518,7 +9519,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -9745,7 +9746,7 @@ dependencies = [ "proc-macro2", "prost-build 0.12.3", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -10075,7 +10076,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -10104,7 +10105,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -10223,7 +10224,7 @@ checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -10521,7 +10522,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", "wasm-bindgen-shared", ] @@ -10555,7 +10556,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10995,7 +10996,7 @@ checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -11015,7 +11016,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e298885dc1..39d7f367cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,7 @@ scarb = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } scarb-ui = { git = "https://github.com/software-mansion/scarb", tag = "v2.4.0" } semver = "1.0.5" serde = { version = "1.0.192", features = [ "derive" ] } -serde_json = "1.0" +serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } sqlx = { version = "0.7.2", features = [ "chrono", "macros", "regexp", "runtime-async-std", "runtime-tokio", "sqlite", "uuid" ] } diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 1398a4ec7a..3f2b36eddc 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -12,6 +12,7 @@ cairo-vm.workspace = true derive_more.workspace = true serde.workspace = true serde_json.workspace = true +serde_with.workspace = true starknet.workspace = true thiserror.workspace = true diff --git a/crates/katana/primitives/src/conversion/rpc.rs b/crates/katana/primitives/src/conversion/rpc.rs index 653a30412c..09c7146e56 100644 --- a/crates/katana/primitives/src/conversion/rpc.rs +++ b/crates/katana/primitives/src/conversion/rpc.rs @@ -1,12 +1,24 @@ -use std::collections::HashMap; -use std::io::{Read, Write}; +use std::collections::{BTreeMap, HashMap}; +use std::io::{self, Read, Write}; +use std::mem; +use std::str::FromStr; use anyhow::{anyhow, Result}; use blockifier::execution::contract_class::ContractClassV0; use cairo_lang_starknet::casm_contract_class::CasmContractClass; -use cairo_vm::serde::deserialize_program::ProgramJson; -use serde_json::json; +use cairo_vm::felt::Felt252; +use cairo_vm::serde::deserialize_program::{ApTracking, OffsetValue, ProgramJson, ValueAddress}; +use cairo_vm::types::instruction::Register; +use cairo_vm::types::program::Program; +use serde::{Deserialize, Serialize, Serializer}; +use serde_json::{json, Number}; +use serde_with::serde_as; +use starknet::core::serde::unsigned_field_element::UfeHex; pub use starknet::core::types::contract::legacy::{LegacyContractClass, LegacyProgram}; +use starknet::core::types::contract::legacy::{ + LegacyDebugInfo, LegacyFlowTrackingData, LegacyHint, LegacyIdentifier, LegacyReferenceManager, + RawLegacyAbiEntry, RawLegacyEntryPoints, +}; pub use starknet::core::types::contract::CompiledClass; use starknet::core::types::{ CompressedLegacyContractClass, ContractClass, LegacyContractEntryPoint, LegacyEntryPointsByType, @@ -25,6 +37,11 @@ mod primitives { pub use crate::FieldElement; } +use cairo_vm::serde::deserialize_program::{ + serialize_program_data, Attribute, BuiltinName, DebugInfo, HintParams, Member, +}; +use cairo_vm::types::relocatable::MaybeRelocatable; + /// Converts the legacy inner compiled class type [CompiledContractClassV0] into its RPC equivalent /// [`ContractClass`]. pub fn legacy_inner_to_rpc_class( @@ -59,13 +76,10 @@ pub fn legacy_inner_to_rpc_class( let entry_points_by_type = to_rpc_legacy_entry_points_by_type(&legacy_contract_class.entry_points_by_type)?; - let program = { - let program: ProgramJson = legacy_contract_class.program.clone().into(); - compress(&serde_json::to_vec(&program)?)? - }; + let compressed_program = compress_legacy_program_data(legacy_contract_class.program.clone())?; Ok(ContractClass::Legacy(CompressedLegacyContractClass { - program, + program: compressed_program, abi: None, entry_points_by_type, })) @@ -107,20 +121,61 @@ pub fn compiled_class_hash_from_flattened_sierra_class( pub fn legacy_rpc_to_inner_compiled_class( compressed_legacy_contract: &CompressedLegacyContractClass, ) -> Result<(ClassHash, CompiledContractClass)> { - let legacy_program_json = decompress(&compressed_legacy_contract.program)?; - let legacy_program: LegacyProgram = serde_json::from_str(&legacy_program_json)?; - - let flattened = json!({ - "program": legacy_program, - "abi": compressed_legacy_contract.abi, + let class_json = json!({ + "abi": compressed_legacy_contract.abi.clone().unwrap_or_default(), "entry_points_by_type": compressed_legacy_contract.entry_points_by_type, + "program": decompress_legacy_program_data(&compressed_legacy_contract.program)?, }); - let legacy_contract_class: LegacyContractClass = serde_json::from_value(flattened.clone())?; - let class_hash = legacy_contract_class.class_hash()?; - let contract_class: ContractClassV0 = serde_json::from_value(flattened)?; + #[allow(unused)] + #[derive(Deserialize)] + struct LegacyAttribute { + #[serde(default)] + accessible_scopes: Vec, + end_pc: u64, + flow_tracking_data: Option, + name: String, + start_pc: u64, + value: String, + } + + #[allow(unused)] + #[serde_as] + #[derive(Deserialize)] + pub struct LegacyProgram { + attributes: Option>, + builtins: Vec, + compiler_version: Option, + #[serde_as(as = "Vec")] + data: Vec, + debug_info: Option, + hints: BTreeMap>, + identifiers: BTreeMap, + main_scope: String, + prime: String, + reference_manager: LegacyReferenceManager, + } + + #[allow(unused)] + #[derive(Deserialize)] + struct LegacyContractClassJson { + abi: Vec, + entry_points_by_type: RawLegacyEntryPoints, + program: LegacyProgram, + } - Ok((class_hash, CompiledContractClass::V0(contract_class))) + // SAFETY: `LegacyContractClassJson` MUST maintain same memory layout as `LegacyContractClass`. + // This would only work if the fields are in the same order and have the same size. Though, + // both types are using default Rust repr, which means there is no guarantee by the compiler + // that the memory layout of both types will be the same despite comprised of the same + // fields and types. + let class: LegacyContractClassJson = serde_json::from_value(class_json.clone())?; + let class: LegacyContractClass = unsafe { mem::transmute(class) }; + + let inner_class: ContractClassV0 = serde_json::from_value(class_json)?; + let class_hash = class.class_hash()?; + + Ok((class_hash, CompiledContractClass::V0(inner_class))) } /// Converts `starknet-rs` RPC [FlattenedSierraClass] type to Cairo's @@ -142,15 +197,233 @@ fn rpc_to_cairo_contract_class( }) } -fn compress(data: &[u8]) -> Result, std::io::Error> { +fn compress_legacy_program_data(legacy_program: Program) -> Result, io::Error> { + fn felt_as_dec_str( + value: &Option, + serializer: S, + ) -> Result { + let dec_str = format!("{}", value.clone().unwrap_or_default().to_signed_felt()); + let number = Number::from_str(&dec_str).expect("valid number"); + number.serialize(serializer) + } + + fn value_address_in_str_format( + value_address: &ValueAddress, + serializer: S, + ) -> Result { + serializer.serialize_str(&parse_value_address_to_str(value_address.clone())) + } + + fn zero_if_none(pc: &Option, serializer: S) -> Result { + serializer.serialize_u64(pc.as_ref().map_or(0, |x| *x as u64)) + } + + #[derive(Serialize)] + struct Identifier { + #[serde(skip_serializing_if = "Option::is_none")] + pc: Option, + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + type_: Option, + #[serde(serialize_with = "felt_as_dec_str")] + #[serde(skip_serializing_if = "Option::is_none")] + value: Option, + #[serde(skip_serializing_if = "Option::is_none")] + full_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + members: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + cairo_type: Option, + } + + #[derive(Serialize)] + struct Reference { + ap_tracking_data: ApTracking, + #[serde(serialize_with = "zero_if_none")] + pc: Option, + #[serde(rename(serialize = "value"))] + #[serde(serialize_with = "value_address_in_str_format")] + value_address: ValueAddress, + } + + #[derive(Serialize)] + struct ReferenceManager { + references: Vec, + } + + #[derive(Serialize)] + struct SerializableProgramJson { + prime: String, + builtins: Vec, + #[serde(serialize_with = "serialize_program_data")] + #[serde(deserialize_with = "deserialize_array_of_bigint_hex")] + data: Vec, + identifiers: HashMap, + hints: HashMap>, + reference_manager: ReferenceManager, + attributes: Vec, + debug_info: Option, + } + + // SAFETY: `SerializableProgramJson` MUST maintain same memory layout as `ProgramJson`. This + // would only work if the fields are in the same order and have the same size. Though, both + // types are using default Rust repr, which means there is no guarantee by the compiler that the + // memory layout of both types will be the same despite comprised of the same fields and + // types. + let program: ProgramJson = ProgramJson::from(legacy_program); + let program: SerializableProgramJson = unsafe { mem::transmute(program) }; + + let buffer = serde_json::to_vec(&program)?; let mut gzip_encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); - Write::write_all(&mut gzip_encoder, data)?; + Write::write_all(&mut gzip_encoder, &buffer)?; gzip_encoder.finish() } -fn decompress(data: &[u8]) -> Result { +fn decompress_legacy_program_data(data: &[u8]) -> Result { + #[derive(Deserialize)] + #[allow(unused)] + struct LegacyAttribute { + #[serde(default)] + accessible_scopes: Vec, + end_pc: u64, + flow_tracking_data: Option, + name: String, + start_pc: u64, + value: String, + } + + #[repr(transparent)] + #[derive(Deserialize)] + struct MainScope(String); + + impl Default for MainScope { + fn default() -> Self { + Self(String::from("__main__")) + } + } + + #[serde_as] + #[allow(unused)] + #[derive(Deserialize)] + struct LegacyProgramJson { + attributes: Option>, + builtins: Vec, + compiler_version: Option, + #[serde_as(as = "Vec")] + data: Vec, + debug_info: Option, + hints: BTreeMap>, + identifiers: BTreeMap, + #[serde(default)] + main_scope: MainScope, + prime: String, + reference_manager: LegacyReferenceManager, + } + let mut decoder = flate2::read::GzDecoder::new(data); - let mut decoded = String::new(); - Read::read_to_string(&mut decoder, &mut decoded)?; - Ok(decoded) + let mut decoded = Vec::new(); + Read::read_to_end(&mut decoder, &mut decoded)?; + + // SAFETY: `LegacyProgramJson` MUST maintain same memory layout as `LegacyProgram`. This + // would only work if the fields are in the same order and have the same size. Though, both + // types are using default Rust repr, which means there is no guarantee by the compiler that the + // memory layout of both types will be the same despite comprised of the same fields and + // types. + let program: LegacyProgramJson = serde_json::from_slice(&decoded)?; + let program: LegacyProgram = unsafe { mem::transmute(program) }; + + Ok(program) +} + +fn parse_value_address_to_str(value_address: ValueAddress) -> String { + fn handle_offset_ref(offset: i32, str: &mut String) { + if offset == 0 { + return; + } + + str.push_str(" + "); + str.push_str(&if offset.is_negative() { format!("({offset})") } else { offset.to_string() }) + } + + fn handle_offset_val(value: OffsetValue, str: &mut String) { + match value { + OffsetValue::Reference(rx, offset, deref) => { + let mut tmp = String::from(match rx { + Register::FP => "fp", + Register::AP => "ap", + }); + + handle_offset_ref(offset, &mut tmp); + + if deref { + str.push_str(&format!("[{tmp}]")); + } else { + str.push_str(&tmp); + } + } + + OffsetValue::Value(value) => handle_offset_ref(value, str), + + OffsetValue::Immediate(value) => { + if value == Felt252::from(0u32) { + return; + } + + str.push_str(" + "); + str.push_str(&value.to_string()); + } + } + } + + let mut str = String::new(); + let is_value: bool; + + if let OffsetValue::Immediate(_) = value_address.offset2 { + is_value = false; + } else { + is_value = true; + } + + handle_offset_val(value_address.offset1, &mut str); + handle_offset_val(value_address.offset2, &mut str); + + str.push_str(", "); + str.push_str(&value_address.value_type); + + if is_value { + str.push('*'); + } + + str = format!("cast({str})"); + + if value_address.dereference { + str = format!("[{str}]"); + } + + str +} + +#[cfg(test)] +mod tests { + use starknet::core::types::ContractClass; + + use super::{legacy_inner_to_rpc_class, legacy_rpc_to_inner_compiled_class}; + use crate::utils::class::parse_compiled_class_v0; + + // There are some discrepancies between the legacy RPC and the inner compiled class types which + // results in some data lost during the conversion. Therefore, we are unable to assert for + // equality between the original and the converted class. Instead, we assert that the conversion + // is successful and that the converted class can be converted back + #[test] + fn legacy_rpc_to_inner_and_back() { + let class_json = include_str!("../../../core/contracts/compiled/account.json"); + let class = parse_compiled_class_v0(class_json).unwrap(); + + let Ok(ContractClass::Legacy(compressed_legacy_class)) = legacy_inner_to_rpc_class(class) + else { + panic!("Expected legacy class"); + }; + + legacy_rpc_to_inner_compiled_class(&compressed_legacy_class).unwrap(); + } }