starknet_core/serde/
unsigned_field_element.rs

1use alloc::{fmt::Formatter, format};
2
3use crypto_bigint::U256;
4use serde::{
5    de::{Error as DeError, Visitor},
6    Deserializer, Serializer,
7};
8use serde_with::{DeserializeAs, SerializeAs};
9
10use starknet_types_core::felt::Felt;
11
12const PRIME: U256 =
13    U256::from_be_hex("0800000000000011000000000000000000000000000000000000000000000001");
14
15/// Serialize/deserialize [`Felt`] as hex strings. For use with `serde_with`.
16#[derive(Debug)]
17pub struct UfeHex;
18
19/// Serialize/deserialize [`Option<Felt>`] as hex strings. For use with `serde_with`.
20#[derive(Debug)]
21pub struct UfeHexOption;
22
23/// Serialize/deserialize [`Option<Felt>`] as hex strings in a pending block hash context. For use
24/// with `serde_with`.
25#[derive(Debug)]
26pub struct UfePendingBlockHash;
27
28struct UfeHexVisitor;
29struct UfeHexOptionVisitor;
30struct UfePendingBlockHashVisitor;
31
32impl SerializeAs<Felt> for UfeHex {
33    fn serialize_as<S>(value: &Felt, serializer: S) -> Result<S::Ok, S::Error>
34    where
35        S: Serializer,
36    {
37        if serializer.is_human_readable() {
38            serializer.serialize_str(&format!("{value:#x}"))
39        } else {
40            serializer.serialize_bytes(&value.to_bytes_be())
41        }
42    }
43}
44
45impl<'de> DeserializeAs<'de, Felt> for UfeHex {
46    fn deserialize_as<D>(deserializer: D) -> Result<Felt, D::Error>
47    where
48        D: Deserializer<'de>,
49    {
50        if deserializer.is_human_readable() {
51            deserializer.deserialize_any(UfeHexVisitor)
52        } else {
53            deserializer.deserialize_bytes(UfeHexVisitor)
54        }
55    }
56}
57
58impl Visitor<'_> for UfeHexVisitor {
59    type Value = Felt;
60
61    fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result {
62        write!(formatter, "a hex string, or an array of u8")
63    }
64
65    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
66    where
67        E: DeError,
68    {
69        Felt::from_hex(v).map_err(|err| DeError::custom(format!("invalid hex string: {err}")))
70    }
71
72    fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
73        let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?;
74
75        if U256::from_be_slice(&buf) < PRIME {
76            Ok(Felt::from_bytes_be(&buf))
77        } else {
78            Err(serde::de::Error::custom("field element value out of range"))
79        }
80    }
81}
82
83impl SerializeAs<Option<Felt>> for UfeHexOption {
84    fn serialize_as<S>(value: &Option<Felt>, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        if serializer.is_human_readable() {
89            match value {
90                Some(value) => serializer.serialize_str(&format!("{value:#064x}")),
91                None => serializer.serialize_none(),
92            }
93        } else {
94            match value {
95                Some(value) => serializer.serialize_bytes(&value.to_bytes_be()),
96                None => serializer.serialize_bytes(&[]),
97            }
98        }
99    }
100}
101
102impl<'de> DeserializeAs<'de, Option<Felt>> for UfeHexOption {
103    fn deserialize_as<D>(deserializer: D) -> Result<Option<Felt>, D::Error>
104    where
105        D: Deserializer<'de>,
106    {
107        if deserializer.is_human_readable() {
108            deserializer.deserialize_any(UfeHexOptionVisitor)
109        } else {
110            deserializer.deserialize_bytes(UfeHexOptionVisitor)
111        }
112    }
113}
114
115impl Visitor<'_> for UfeHexOptionVisitor {
116    type Value = Option<Felt>;
117
118    fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result {
119        write!(formatter, "string")
120    }
121
122    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
123    where
124        E: DeError,
125    {
126        match v {
127            "" => Ok(None),
128            _ => match Felt::from_hex(v) {
129                Ok(value) => Ok(Some(value)),
130                Err(err) => Err(DeError::custom(format!("invalid hex string: {err}"))),
131            },
132        }
133    }
134
135    fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
136        if v.is_empty() {
137            return Ok(None);
138        }
139
140        let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?;
141
142        if U256::from_be_slice(&buf) < PRIME {
143            Ok(Some(Felt::from_bytes_be(&buf)))
144        } else {
145            Err(serde::de::Error::custom("field element value out of range"))
146        }
147    }
148}
149
150impl SerializeAs<Option<Felt>> for UfePendingBlockHash {
151    fn serialize_as<S>(value: &Option<Felt>, serializer: S) -> Result<S::Ok, S::Error>
152    where
153        S: Serializer,
154    {
155        if serializer.is_human_readable() {
156            match value {
157                Some(value) => serializer.serialize_str(&format!("{value:#064x}")),
158                // We don't know if it's `null` or `"pending"`
159                None => serializer.serialize_none(),
160            }
161        } else {
162            match value {
163                Some(value) => serializer.serialize_bytes(&value.to_bytes_be()),
164                None => serializer.serialize_bytes(&[]),
165            }
166        }
167    }
168}
169
170impl<'de> DeserializeAs<'de, Option<Felt>> for UfePendingBlockHash {
171    fn deserialize_as<D>(deserializer: D) -> Result<Option<Felt>, D::Error>
172    where
173        D: Deserializer<'de>,
174    {
175        if deserializer.is_human_readable() {
176            deserializer.deserialize_any(UfePendingBlockHashVisitor)
177        } else {
178            deserializer.deserialize_bytes(UfePendingBlockHashVisitor)
179        }
180    }
181}
182
183impl Visitor<'_> for UfePendingBlockHashVisitor {
184    type Value = Option<Felt>;
185
186    fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result {
187        write!(formatter, "string")
188    }
189
190    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
191    where
192        E: DeError,
193    {
194        if v.is_empty() || v == "pending" || v == "None" {
195            Ok(None)
196        } else {
197            match Felt::from_hex(v) {
198                Ok(value) => Ok(Some(value)),
199                Err(err) => Err(DeError::custom(format!("invalid hex string: {err}"))),
200            }
201        }
202    }
203
204    fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
205        if v.is_empty() {
206            return Ok(None);
207        }
208
209        let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?;
210
211        if U256::from_be_slice(&buf) < PRIME {
212            Ok(Some(Felt::from_bytes_be(&buf)))
213        } else {
214            Err(serde::de::Error::custom("field element value out of range"))
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    use hex_literal::hex;
224    use serde::{Deserialize, Serialize};
225    use serde_with::serde_as;
226
227    #[serde_as]
228    #[derive(Serialize, Deserialize)]
229    struct TestStruct(#[serde_as(as = "UfeHex")] pub Felt);
230
231    #[serde_as]
232    #[derive(Serialize, Deserialize)]
233    struct TestOptionStruct(#[serde_as(as = "UfeHexOption")] pub Option<Felt>);
234
235    #[serde_as]
236    #[derive(Serialize, Deserialize)]
237    struct TestBlockHashStruct(#[serde_as(as = "UfePendingBlockHash")] pub Option<Felt>);
238
239    #[test]
240    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
241    fn bin_ser() {
242        let r = bincode::serialize(&TestStruct(Felt::ONE)).unwrap();
243        assert_eq!(
244            r,
245            hex!(
246                "2000000000000000 0000000000000000000000000000000000000000000000000000000000000001"
247            )
248        );
249    }
250
251    #[test]
252    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
253    fn bin_deser() {
254        let r = bincode::deserialize::<TestStruct>(&hex!(
255            "2000000000000000 0000000000000000000000000000000000000000000000000000000000000001"
256        ))
257        .unwrap();
258        assert_eq!(r.0, Felt::ONE);
259    }
260
261    #[test]
262    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
263    fn bin_deser_out_of_range() {
264        if bincode::deserialize::<TestStruct>(&hex!(
265            "2000000000000000 0800000000000011000000000000000000000000000000000000000000000001"
266        ))
267        .is_ok()
268        {
269            panic!("deserialization should fail")
270        }
271    }
272
273    #[test]
274    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
275    fn option_deser_empty_string() {
276        let r = serde_json::from_str::<TestOptionStruct>("\"\"").unwrap();
277        assert_eq!(r.0, None);
278    }
279
280    #[test]
281    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
282    fn option_bin_ser_none() {
283        let r = bincode::serialize(&TestOptionStruct(None)).unwrap();
284        assert_eq!(r, hex!("0000000000000000"));
285    }
286
287    #[test]
288    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
289    fn option_bin_deser_none() {
290        let r = bincode::deserialize::<TestOptionStruct>(&hex!("0000000000000000")).unwrap();
291        assert_eq!(r.0, None);
292    }
293
294    #[test]
295    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
296    fn pending_block_hash_deser_pending() {
297        let r = serde_json::from_str::<TestBlockHashStruct>("\"pending\"").unwrap();
298        assert_eq!(r.0, None);
299    }
300
301    #[test]
302    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
303    fn pending_block_hash_bin_ser_none() {
304        let r = bincode::serialize(&TestBlockHashStruct(None)).unwrap();
305        assert_eq!(r, hex!("0000000000000000"));
306    }
307
308    #[test]
309    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
310    fn pending_block_hash_bin_deser_none() {
311        let r = bincode::deserialize::<TestBlockHashStruct>(&hex!("0000000000000000")).unwrap();
312        assert_eq!(r.0, None);
313    }
314}