starknet_devnet_types/
serde_helpers.rs

1pub mod rpc_sierra_contract_class_to_sierra_contract_class {
2    use serde::{Deserialize, Deserializer};
3
4    pub fn deserialize_to_sierra_contract_class<'de, D>(
5        deserializer: D,
6    ) -> Result<cairo_lang_starknet_classes::contract_class::ContractClass, D::Error>
7    where
8        D: Deserializer<'de>,
9    {
10        let mut json_obj = serde_json::Value::deserialize(deserializer)?;
11        // Take the inner part of the string value which is expected to be a JSON array and replace
12        // it with the deserialized value.
13        // If for some reason the abi field is empty string, remove it from collection
14        if let Some(serde_json::Value::String(abi_string)) = json_obj.get("abi") {
15            if !abi_string.is_empty() {
16                let arr: serde_json::Value =
17                    serde_json::from_str(abi_string).map_err(serde::de::Error::custom)?;
18
19                json_obj
20                    .as_object_mut()
21                    .ok_or(serde::de::Error::custom("Expected to be an object"))?
22                    .insert("abi".to_string(), arr);
23            } else {
24                json_obj
25                    .as_object_mut()
26                    .ok_or(serde::de::Error::custom("Expected to be an object"))?
27                    .remove("abi");
28            }
29        };
30
31        serde_json::from_value(json_obj).map_err(serde::de::Error::custom)
32    }
33
34    #[cfg(test)]
35    mod tests {
36        use serde::Deserialize;
37
38        use crate::serde_helpers::rpc_sierra_contract_class_to_sierra_contract_class::deserialize_to_sierra_contract_class;
39
40        #[test]
41        fn correct_deserialization_from_sierra_contract_class_with_abi_field_as_string() {
42            #[derive(Deserialize)]
43            struct TestDeserialization(
44                #[allow(unused)]
45                #[serde(deserialize_with = "deserialize_to_sierra_contract_class")]
46                cairo_lang_starknet_classes::contract_class::ContractClass,
47            );
48
49            let path = concat!(
50                env!("CARGO_MANIFEST_DIR"),
51                "/test_data/sierra_contract_class_with_abi_as_string.json"
52            );
53
54            let json_str = std::fs::read_to_string(path).unwrap();
55
56            serde_json::from_str::<TestDeserialization>(&json_str).unwrap();
57        }
58    }
59}
60pub mod hex_string {
61    use serde::{Deserialize, Deserializer, Serializer};
62    use starknet_rs_core::types::Felt;
63
64    use crate::contract_address::ContractAddress;
65    use crate::felt::felt_from_prefixed_hex;
66    use crate::patricia_key::PatriciaKey;
67
68    pub fn deserialize_to_prefixed_patricia_key<'de, D>(
69        deserializer: D,
70    ) -> Result<PatriciaKey, D::Error>
71    where
72        D: Deserializer<'de>,
73    {
74        let felt = deserialize_prefixed_hex_string_to_felt(deserializer)?;
75        PatriciaKey::new(felt).map_err(serde::de::Error::custom)
76    }
77
78    pub fn deserialize_to_prefixed_contract_address<'de, D>(
79        deserializer: D,
80    ) -> Result<ContractAddress, D::Error>
81    where
82        D: Deserializer<'de>,
83    {
84        let felt = deserialize_prefixed_hex_string_to_felt(deserializer)?;
85        ContractAddress::new(felt).map_err(serde::de::Error::custom)
86    }
87
88    pub fn serialize_patricia_key_to_prefixed_hex<S>(
89        patricia_key: &PatriciaKey,
90        s: S,
91    ) -> Result<S::Ok, S::Error>
92    where
93        S: Serializer,
94    {
95        s.serialize_str(&patricia_key.to_felt().to_hex_string())
96    }
97
98    pub fn serialize_contract_address_to_prefixed_hex<S>(
99        contract_address: &ContractAddress,
100        s: S,
101    ) -> Result<S::Ok, S::Error>
102    where
103        S: Serializer,
104    {
105        serialize_patricia_key_to_prefixed_hex(&contract_address.0, s)
106    }
107
108    pub fn deserialize_prefixed_hex_string_to_felt<'de, D>(
109        deserializer: D,
110    ) -> Result<Felt, D::Error>
111    where
112        D: Deserializer<'de>,
113    {
114        let buf = String::deserialize(deserializer)?;
115
116        felt_from_prefixed_hex(&buf).map_err(serde::de::Error::custom)
117    }
118
119    #[cfg(test)]
120    mod tests {
121        use serde::{Deserialize, Serialize};
122        use starknet_rs_core::types::Felt;
123
124        use crate::contract_address::ContractAddress;
125        use crate::felt::felt_from_prefixed_hex;
126        use crate::patricia_key::PatriciaKey;
127        use crate::serde_helpers::hex_string::{
128            deserialize_to_prefixed_contract_address, deserialize_to_prefixed_patricia_key,
129            serialize_contract_address_to_prefixed_hex,
130        };
131
132        #[test]
133        fn deserialization_of_prefixed_hex_patricia_key_should_be_successful() {
134            #[derive(Deserialize)]
135            struct TestDeserialization {
136                #[serde(deserialize_with = "deserialize_to_prefixed_patricia_key")]
137                data: PatriciaKey,
138            }
139
140            let json_str =
141                r#"{"data": "0x800000000000000000000000000000000000000000000000000000000000000"}"#;
142            let data = serde_json::from_str::<TestDeserialization>(json_str).unwrap();
143            assert!(
144                data.data.to_felt()
145                    == felt_from_prefixed_hex(
146                        "0x800000000000000000000000000000000000000000000000000000000000000"
147                    )
148                    .unwrap()
149            )
150        }
151
152        #[test]
153        fn deserialization_of_prefixed_hex_patricia_key_should_return_error() {
154            #[derive(Deserialize)]
155            struct TestDeserialization {
156                #[allow(unused)]
157                #[serde(deserialize_with = "deserialize_to_prefixed_patricia_key")]
158                data: PatriciaKey,
159            }
160
161            let json_str =
162                r#"{"data": "0x800000000000000000000000000000000000000000000000000000000000001"}"#;
163            assert!(serde_json::from_str::<TestDeserialization>(json_str).is_err())
164        }
165
166        #[test]
167        fn deserialization_of_prefixed_hex_contract_address_should_return_error() {
168            #[derive(Deserialize)]
169            struct TestDeserialization {
170                #[allow(unused)]
171                #[serde(deserialize_with = "deserialize_to_prefixed_contract_address")]
172                data: ContractAddress,
173            }
174
175            let json_str =
176                r#"{"data": "0x800000000000000000000000000000000000000000000000000000000000001"}"#;
177            assert!(serde_json::from_str::<TestDeserialization>(json_str).is_err())
178        }
179
180        #[test]
181        fn serialization_of_prefixed_hex_contract_address_should_be_correct() {
182            #[derive(Serialize)]
183            struct TestSerialization(
184                #[serde(serialize_with = "serialize_contract_address_to_prefixed_hex")]
185                ContractAddress,
186            );
187
188            let data = TestSerialization(ContractAddress::new(Felt::ONE).unwrap());
189
190            assert_eq!(serde_json::to_string(&data).unwrap(), r#""0x1""#);
191        }
192    }
193}
194
195pub mod dec_string {
196    use std::str::FromStr;
197
198    use bigdecimal::BigDecimal;
199    use num_bigint::BigUint;
200    use serde::Deserialize;
201
202    pub fn deserialize_biguint<'de, D>(deserializer: D) -> Result<BigUint, D::Error>
203    where
204        D: serde::Deserializer<'de>,
205    {
206        let number = serde_json::Number::deserialize(deserializer)?;
207        // biguint can't handle stringified scientific notation (that's what number can be)
208        let big_decimal =
209            BigDecimal::from_str(number.as_str()).map_err(serde::de::Error::custom)?;
210
211        // scale to 0 to force stringifying without scientific notation for large values (e.g. 1e30)
212        BigUint::from_str(&big_decimal.with_scale(0).to_string()).map_err(serde::de::Error::custom)
213    }
214
215    #[cfg(test)]
216    mod tests {
217        use num_bigint::BigUint;
218        use serde::Deserialize;
219
220        use crate::serde_helpers::dec_string::deserialize_biguint;
221
222        #[test]
223        fn deserialization_biguint() {
224            #[derive(Deserialize)]
225            struct TestDeserializationStruct {
226                #[serde(deserialize_with = "deserialize_biguint")]
227                value: BigUint,
228            }
229
230            for (json_str, expected) in [
231                (
232                    r#"{"value": 3618502788666131106986593281521497120414687020801267626233049500247285301248}"#,
233                    BigUint::from(1_u8) << 251,
234                ),
235                (r#"{"value": 1000000000000000000000000000000}"#, BigUint::from(10_u8).pow(30)),
236                (
237                    r#"{"value": 1000000000000000000000000000001}"#,
238                    BigUint::from(10_u8).pow(30) + BigUint::from(1_u8),
239                ),
240                (r#"{"value": 1e30}"#, BigUint::from(10_u8).pow(30)),
241                (r#"{"value": 1.23e1}"#, BigUint::from(12_u8)),
242                (r#"{"value": 1.29e1}"#, BigUint::from(12_u8)),
243                (r#"{"value": 100.0}"#, BigUint::from(100_u8)),
244            ] {
245                match serde_json::from_str::<TestDeserializationStruct>(json_str) {
246                    Ok(data) => assert_eq!(data.value, expected),
247                    Err(e) => panic!("Unexpected response {e} for parsing: {json_str}"),
248                }
249            }
250        }
251    }
252}
253
254pub mod base_64_gzipped_json_string {
255    use base64::Engine;
256    use flate2::Compression;
257    use flate2::write::GzEncoder;
258    use serde::{Deserialize, Deserializer, Serializer};
259    use serde_json::Value;
260    use starknet_rs_core::serde::byte_array::base64 as base64Sir;
261    use starknet_rs_core::types::contract::legacy::LegacyProgram;
262
263    pub fn deserialize_to_serde_json_value_with_keys_ordered_in_alphabetical_order<'de, D>(
264        deserializer: D,
265    ) -> Result<serde_json::Value, D::Error>
266    where
267        D: Deserializer<'de>,
268    {
269        let buf = String::deserialize(deserializer)?;
270        if buf.is_empty() {
271            return Ok(serde_json::Value::Null);
272        }
273
274        // TODO: change on starknet_rs_core::serde::byte_array::base64
275        let bytes = base64::engine::general_purpose::STANDARD
276            .decode(buf)
277            .map_err(|_| serde::de::Error::custom("program: Unable to decode base64 string"))?;
278
279        let decoder = flate2::read::GzDecoder::new(bytes.as_slice());
280        let starknet_program: LegacyProgram = serde_json::from_reader(decoder)
281            .map_err(|_| serde::de::Error::custom("program: Unable to decode gzipped bytes"))?;
282
283        serde_json::to_value(starknet_program)
284            .map_err(|_| serde::de::Error::custom("program: Unable to parse to JSON"))
285    }
286
287    pub fn serialize_program_to_base64<S>(program: &Value, serializer: S) -> Result<S::Ok, S::Error>
288    where
289        S: Serializer,
290    {
291        let mut buffer = Vec::new();
292        let encoder = GzEncoder::new(&mut buffer, Compression::best());
293        serde_json::to_writer(encoder, program)
294            .map_err(|_| serde::ser::Error::custom("program: Unable to encode program"))?;
295
296        base64Sir::serialize(&buffer, serializer)
297    }
298
299    #[cfg(test)]
300    mod tests {
301        use std::fs::File;
302
303        use serde::Deserialize;
304        use serde_json::json;
305
306        use crate::serde_helpers::base_64_gzipped_json_string::deserialize_to_serde_json_value_with_keys_ordered_in_alphabetical_order;
307        use crate::utils::test_utils::CAIRO_0_RPC_CONTRACT_PATH;
308
309        #[test]
310        fn deserialize_successfully_starknet_api_program() {
311            let json_value: serde_json::Value =
312                serde_json::from_reader(File::open(CAIRO_0_RPC_CONTRACT_PATH).unwrap()).unwrap();
313
314            #[derive(Deserialize)]
315            struct TestDeserialization {
316                #[allow(unused)]
317                #[serde(
318                    deserialize_with = "deserialize_to_serde_json_value_with_keys_ordered_in_alphabetical_order"
319                )]
320                program: serde_json::Value,
321            }
322
323            serde_json::from_str::<TestDeserialization>(
324                &serde_json::to_string(&json!({ "program": json_value["program"]})).unwrap(),
325            )
326            .unwrap();
327        }
328    }
329}