dojo_types/
packing.rs

1use std::any::type_name;
2use std::str::FromStr;
3
4use crypto_bigint::U256;
5use num_traits::ToPrimitive;
6use starknet::core::types::{Felt, FromStrError};
7use starknet::core::utils::{
8    cairo_short_string_to_felt, parse_cairo_short_string, CairoShortStringToFeltError,
9    ParseCairoShortStringError,
10};
11
12use crate::primitive::{Primitive, PrimitiveError};
13use crate::schema::{self, EnumOption, Ty};
14
15#[derive(Debug, thiserror::Error)]
16pub enum ParseError {
17    #[error("Invalid schema: {0}")]
18    InvalidSchema(String),
19    #[error("Value out of range")]
20    Primitive(#[from] PrimitiveError),
21    #[error("Error when parsing felt: {0}")]
22    FromStr(#[from] FromStrError),
23    #[error(transparent)]
24    ParseCairoShortStringError(#[from] ParseCairoShortStringError),
25    #[error(transparent)]
26    CairoShortStringToFeltError(#[from] CairoShortStringToFeltError),
27}
28
29impl ParseError {
30    pub fn invalid_schema_with_msg(msg: &str) -> Self {
31        Self::InvalidSchema(msg.to_string())
32    }
33
34    pub fn invalid_schema() -> Self {
35        Self::InvalidSchema(String::from(""))
36    }
37}
38
39#[derive(Debug, thiserror::Error)]
40pub enum PackingError {
41    #[error(transparent)]
42    Parse(#[from] ParseError),
43    #[error("Error when unpacking entity")]
44    UnpackingEntityError,
45    #[error(transparent)]
46    Primitive(#[from] PrimitiveError),
47}
48
49/// Unpacks a vector of packed values according to a given layout.
50///
51/// # Arguments
52///
53/// * `packed_values` - A vector of FieldElement values that are packed.
54/// * `layout` - A vector of FieldElement values that describe the layout of the packed values.
55///
56/// # Returns
57///
58/// * `Result<Vec<Felt>, PackingError>` - A Result containing a vector of unpacked Felt values if
59///   successful, or an error if unsuccessful.
60pub fn unpack(mut packed: Vec<Felt>, layout: Vec<Felt>) -> Result<Vec<Felt>, PackingError> {
61    packed.reverse();
62    let mut unpacked = vec![];
63
64    let felt = packed.pop().ok_or(PackingError::UnpackingEntityError)?;
65    let mut unpacking = U256::from_be_slice(&felt.to_bytes_be());
66    let mut offset = 0;
67    // Iterate over the layout.
68    for size in layout {
69        let size: u8 = size.to_u8().ok_or_else(|| PrimitiveError::ValueOutOfRange {
70            r#type: type_name::<u8>(),
71            value: size,
72        })?;
73
74        let size: usize = size.into();
75        let remaining_bits = 251 - offset;
76
77        // If there are less remaining bits than the size, move to the next felt for unpacking.
78        if remaining_bits < size {
79            let felt = packed.pop().ok_or(PackingError::UnpackingEntityError)?;
80            unpacking = U256::from_be_slice(&felt.to_bytes_be());
81            offset = 0;
82        }
83
84        let mut mask = U256::from(0_u8);
85        for _ in 0..size {
86            mask = (mask << 1) | U256::from(1_u8);
87        }
88
89        let result = mask & (unpacking >> offset);
90        let result_fe = Felt::from_hex(&result.to_string()).map_err(ParseError::FromStr)?;
91        unpacked.push(result_fe);
92
93        // Update unpacking to be the shifted value after extracting the result.
94        offset += size;
95    }
96
97    Ok(unpacked)
98}
99
100/// Parse a raw schema of a model into a Cairo type, [Ty]
101pub fn parse_ty(data: &[Felt]) -> Result<Ty, ParseError> {
102    if data.is_empty() {
103        return Err(ParseError::invalid_schema_with_msg(
104            "The function parse_ty expects at least one felt to know the member type variant, \
105             empty input found.",
106        ));
107    }
108
109    let member_type: u8 = data[0].to_u8().ok_or_else(|| PrimitiveError::ValueOutOfRange {
110        r#type: type_name::<u8>(),
111        value: data[0],
112    })?;
113
114    match member_type {
115        0 => parse_simple(&data[1..]),
116        1 => parse_struct(&data[1..]),
117        2 => parse_enum(&data[1..]),
118        3 => parse_tuple(&data[1..]),
119        4 => parse_array(&data[1..]),
120        5 => parse_byte_array(),
121        _ => Err(ParseError::invalid_schema_with_msg(&format!(
122            "Unsupported member type variant `{}`.",
123            member_type
124        ))),
125    }
126}
127
128fn parse_simple(data: &[Felt]) -> Result<Ty, ParseError> {
129    let ty = parse_cairo_short_string(&data[0])?;
130    let primitive = match Primitive::from_str(&ty) {
131        Ok(primitive) => primitive,
132        Err(_) => {
133            return Err(ParseError::invalid_schema_with_msg(&format!(
134                "Unsupported simple type primitive `{}`.",
135                ty
136            )));
137        }
138    };
139
140    Ok(Ty::Primitive(primitive))
141}
142
143fn parse_struct(data: &[Felt]) -> Result<Ty, ParseError> {
144    // A struct has at least 3 elements: name, attrs len, and children len.
145    if data.len() < 3 {
146        return Err(ParseError::invalid_schema_with_msg(&format!(
147            "The function parse_struct expects at least three felts: name, attrs len, and \
148             children len. Input of size {} found.",
149            data.len()
150        )));
151    }
152
153    let name = parse_cairo_short_string(&data[0])?;
154
155    let attrs_len: u32 = data[1].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
156        r#type: type_name::<u32>(),
157        value: data[1],
158    })?;
159    let attrs_slice_start = 2;
160    let attrs_slice_end = attrs_slice_start + attrs_len as usize;
161    let _attrs = &data[attrs_slice_start..attrs_slice_end];
162
163    let children_len: u32 = data[attrs_slice_end].to_u32().ok_or_else(|| {
164        PrimitiveError::ValueOutOfRange { r#type: type_name::<u32>(), value: data[attrs_slice_end] }
165    })?;
166
167    let children_len = children_len as usize;
168
169    let mut children = vec![];
170    let mut offset = attrs_slice_end + 1;
171
172    for i in 0..children_len {
173        let start = i + offset;
174        let len: u32 = data[start].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
175            r#type: type_name::<u32>(),
176            value: data[start],
177        })?;
178        let slice_start = start + 1;
179        let slice_end = slice_start + len as usize;
180        children.push(parse_member(&data[slice_start..slice_end])?);
181        offset += len as usize;
182    }
183
184    Ok(Ty::Struct(schema::Struct { name, children }))
185}
186
187fn parse_member(data: &[Felt]) -> Result<schema::Member, ParseError> {
188    if data.len() < 3 {
189        return Err(ParseError::invalid_schema_with_msg(&format!(
190            "The function parse_member expects at least three felts: name, attributes len, and \
191             ty. Input of size {} found.",
192            data.len()
193        )));
194    }
195
196    let name = parse_cairo_short_string(&data[0])?;
197
198    let attributes_len: u32 = data[1].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
199        r#type: type_name::<u32>(),
200        value: data[1],
201    })?;
202    let slice_start = 2;
203    let slice_end = slice_start + attributes_len as usize;
204    let attributes = &data[slice_start..slice_end];
205
206    let key = attributes.contains(&cairo_short_string_to_felt("key")?);
207    let ty = parse_ty(&data[slice_end..])?;
208
209    Ok(schema::Member { name, ty, key })
210}
211
212fn parse_enum(data: &[Felt]) -> Result<Ty, ParseError> {
213    if data.len() < 3 {
214        return Err(ParseError::invalid_schema_with_msg(&format!(
215            "The function parse_enum expects at least three felts: name, attributes len, and \
216             values len. Input of size {} found.",
217            data.len()
218        )));
219    }
220
221    let name = parse_cairo_short_string(&data[0])?;
222
223    let attrs_len: u32 = data[1].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
224        r#type: type_name::<u32>(),
225        value: data[1],
226    })?;
227    let attrs_slice_start = 2;
228    let attrs_slice_end = attrs_slice_start + attrs_len as usize;
229    let _attrs = &data[attrs_slice_start..attrs_slice_end];
230
231    let values_len: u32 = data[attrs_slice_end].to_u32().ok_or_else(|| {
232        PrimitiveError::ValueOutOfRange { r#type: type_name::<u32>(), value: data[attrs_slice_end] }
233    })?;
234    let values_len = values_len as usize;
235
236    let mut values = vec![];
237    let mut offset = attrs_slice_end + 1;
238
239    for i in 0..values_len {
240        let start = i + offset;
241        let name = parse_cairo_short_string(&data[start])?;
242        let slice_start = start + 2;
243        let len: u32 = data[start + 3].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
244            r#type: type_name::<u32>(),
245            value: data[start + 3],
246        })?;
247        let len = len + 1; // Account for Ty enum index
248
249        let slice_end = slice_start + len as usize;
250        values.push(EnumOption { name, ty: parse_ty(&data[slice_start..slice_end])? });
251        offset += len as usize + 2;
252    }
253
254    Ok(Ty::Enum(schema::Enum { name, option: None, options: values }))
255}
256
257fn parse_tuple(data: &[Felt]) -> Result<Ty, ParseError> {
258    if data.is_empty() {
259        // The unit type is defined as an empty tuple.
260        return Ok(Ty::Tuple(vec![]));
261    }
262
263    let children_len: u32 = data[0].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
264        r#type: type_name::<u32>(),
265        value: data[0],
266    })?;
267    let children_len = children_len as usize;
268
269    let mut children = vec![];
270    let mut offset = 1;
271
272    for i in 0..children_len {
273        let start = i + offset;
274        let len: u32 = data[start].to_u32().ok_or_else(|| PrimitiveError::ValueOutOfRange {
275            r#type: type_name::<u32>(),
276            value: data[start],
277        })?;
278        let slice_start = start + 1;
279        let slice_end = slice_start + len as usize;
280        children.push(parse_ty(&data[slice_start..slice_end])?);
281        offset += len as usize;
282    }
283
284    Ok(Ty::Tuple(children))
285}
286
287fn parse_array(data: &[Felt]) -> Result<Ty, ParseError> {
288    if data.is_empty() || data.len() != 2 {
289        return Err(ParseError::invalid_schema_with_msg(
290            "The function parse_array expects exactly one felt to know the item type, empty input \
291             found.",
292        ));
293    }
294
295    // Arrays always have the same type for all elements.
296    // In the introspect, the array type is given by the first (and unique) element in `Ty`.
297    let mut v = data.to_vec();
298    let _ = v.remove(0);
299
300    let item_ty = parse_ty(v.as_slice())?;
301    Ok(Ty::Array(vec![item_ty]))
302}
303
304fn parse_byte_array() -> Result<Ty, ParseError> {
305    Ok(Ty::ByteArray("".to_string()))
306}
307
308#[cfg(test)]
309mod tests {
310    use starknet::core::types::Felt;
311    use starknet::core::utils::cairo_short_string_to_felt;
312
313    use super::*;
314
315    #[test]
316    fn parse_simple_with_invalid_value() {
317        let data = [Felt::default()];
318        assert!(parse_simple(&data).is_err());
319    }
320
321    #[test]
322    fn parse_simple_with_valid_value() {
323        let data = [cairo_short_string_to_felt("u8").unwrap()];
324        assert_eq!(parse_simple(&data).unwrap(), Ty::Primitive(Primitive::U8(None)));
325    }
326
327    #[test]
328    fn parse_struct_with_invalid_value() {
329        // No attr len and no children.
330        let data = [cairo_short_string_to_felt("bad_struct").unwrap()];
331        assert!(parse_struct(&data).is_err());
332
333        // Only with attr len.
334        let data = [cairo_short_string_to_felt("bad_struct").unwrap(), Felt::default()];
335        assert!(parse_struct(&data).is_err());
336    }
337
338    #[test]
339    fn parse_struct_empty() {
340        let data =
341            [cairo_short_string_to_felt("empty_struct").unwrap(), Felt::default(), Felt::default()];
342
343        assert_eq!(
344            parse_struct(&data).unwrap(),
345            Ty::Struct(schema::Struct { name: "empty_struct".to_string(), children: vec![] })
346        );
347    }
348
349    #[test]
350    fn parse_array_with_invalid_value() {
351        let data = [Felt::default()];
352        assert!(parse_array(&data).is_err());
353    }
354}