cast/
rlp_converter.rs

1use alloy_primitives::{hex, U256};
2use alloy_rlp::{Buf, Decodable, Encodable, Header};
3use eyre::Context;
4use serde_json::Value;
5use std::fmt;
6
7/// Arbitrary nested data.
8///
9/// - `Item::Array(vec![])` is equivalent to `[]`.
10/// - `Item::Array(vec![Item::Data(vec![])])` is equivalent to `[""]` or `[null]`.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum Item {
13    Data(Vec<u8>),
14    Array(Vec<Item>),
15}
16
17impl Encodable for Item {
18    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
19        match self {
20            Self::Array(arr) => arr.encode(out),
21            Self::Data(data) => <[u8]>::encode(data, out),
22        }
23    }
24}
25
26impl Decodable for Item {
27    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
28        let h = Header::decode(buf)?;
29        if buf.len() < h.payload_length {
30            return Err(alloy_rlp::Error::InputTooShort);
31        }
32        let mut d = &buf[..h.payload_length];
33        let r = if h.list {
34            let view = &mut d;
35            let mut v = Vec::new();
36            while !view.is_empty() {
37                v.push(Self::decode(view)?);
38            }
39            Ok(Self::Array(v))
40        } else {
41            Ok(Self::Data(d.to_vec()))
42        };
43        buf.advance(h.payload_length);
44        r
45    }
46}
47
48impl Item {
49    pub(crate) fn value_to_item(value: &Value) -> eyre::Result<Self> {
50        match value {
51            Value::Null => Ok(Self::Data(vec![])),
52            Value::Bool(_) => {
53                eyre::bail!("RLP input can not contain booleans")
54            }
55            Value::Number(n) => {
56                Ok(Self::Data(n.to_string().parse::<U256>()?.to_be_bytes_trimmed_vec()))
57            }
58            Value::String(s) => Ok(Self::Data(hex::decode(s).wrap_err("Could not decode hex")?)),
59            Value::Array(values) => values.iter().map(Self::value_to_item).collect(),
60            Value::Object(_) => {
61                eyre::bail!("RLP input can not contain objects")
62            }
63        }
64    }
65}
66
67impl FromIterator<Self> for Item {
68    fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
69        Self::Array(Vec::from_iter(iter))
70    }
71}
72
73// Display as hex values
74impl fmt::Display for Item {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            Self::Data(dat) => {
78                write!(f, "\"0x{}\"", hex::encode(dat))?;
79            }
80            Self::Array(items) => {
81                f.write_str("[")?;
82                for (i, item) in items.iter().enumerate() {
83                    if i > 0 {
84                        f.write_str(",")?;
85                    }
86                    fmt::Display::fmt(item, f)?;
87                }
88                f.write_str("]")?;
89            }
90        };
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use crate::rlp_converter::Item;
98    use alloy_primitives::hex;
99    use alloy_rlp::{Bytes, Decodable};
100    use serde_json::Result as JsonResult;
101
102    // https://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers
103    fn array_von_neuman() -> Item {
104        Item::Array(vec![
105            Item::Array(vec![]),
106            Item::Array(vec![Item::Array(vec![])]),
107            Item::Array(vec![Item::Array(vec![]), Item::Array(vec![Item::Array(vec![])])]),
108        ])
109    }
110
111    #[test]
112    #[expect(clippy::disallowed_macros)]
113    fn encode_decode_test() -> alloy_rlp::Result<()> {
114        let parameters = vec![
115            (1, b"\xc0".to_vec(), Item::Array(vec![])),
116            (2, b"\xc1\x80".to_vec(), Item::Array(vec![Item::Data(vec![])])),
117            (3, b"\xc4\x83dog".to_vec(), Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])),
118            (
119                4,
120                b"\xc5\xc4\x83dog".to_vec(),
121                Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]),
122            ),
123            (
124                5,
125                b"\xc8\x83dog\x83cat".to_vec(),
126                Item::Array(vec![
127                    Item::Data(vec![0x64, 0x6f, 0x67]),
128                    Item::Data(vec![0x63, 0x61, 0x74]),
129                ]),
130            ),
131            (6, b"\xc7\xc0\xc1\xc0\xc3\xc0\xc1\xc0".to_vec(), array_von_neuman()),
132            (
133                7,
134                b"\xcd\x83\x6c\x6f\x6c\xc3\xc2\xc1\xc0\xc4\x83\x6f\x6c\x6f".to_vec(),
135                Item::Array(vec![
136                    Item::Data(vec![b'\x6c', b'\x6f', b'\x6c']),
137                    Item::Array(vec![Item::Array(vec![Item::Array(vec![Item::Array(vec![])])])]),
138                    Item::Array(vec![Item::Data(vec![b'\x6f', b'\x6c', b'\x6f'])]),
139                ]),
140            ),
141        ];
142        for params in parameters {
143            let encoded = alloy_rlp::encode(&params.2);
144            assert_eq!(Item::decode(&mut &encoded[..])?, params.2);
145            let decoded = Item::decode(&mut &params.1[..])?;
146            assert_eq!(alloy_rlp::encode(&decoded), params.1);
147            println!("case {} validated", params.0)
148        }
149
150        Ok(())
151    }
152
153    #[test]
154    #[expect(clippy::disallowed_macros)]
155    fn deserialize_from_str_test_hex() -> JsonResult<()> {
156        let parameters = vec![
157            (1, "[\"\"]", Item::Array(vec![Item::Data(vec![])])),
158            (2, "[\"0x646f67\"]", Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])),
159            (
160                3,
161                "[[\"646f67\"]]",
162                Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]),
163            ),
164            (
165                4,
166                "[\"646f67\",\"0x636174\"]",
167                Item::Array(vec![
168                    Item::Data(vec![0x64, 0x6f, 0x67]),
169                    Item::Data(vec![0x63, 0x61, 0x74]),
170                ]),
171            ),
172            (6, "[[],[[]],[[],[[]]]]", array_von_neuman()),
173        ];
174        for params in parameters {
175            let val = serde_json::from_str(params.1)?;
176            let item = Item::value_to_item(&val).unwrap();
177            assert_eq!(item, params.2);
178            println!("case {} validated", params.0);
179        }
180
181        Ok(())
182    }
183
184    #[test]
185    fn rlp_data() {
186        // <https://github.com/foundry-rs/foundry/issues/9197>
187        let hex_val_rlp = hex!("820002");
188        let item = Item::decode(&mut &hex_val_rlp[..]).unwrap();
189
190        let data = hex!("0002");
191        let encoded = alloy_rlp::encode(&data[..]);
192        let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap();
193        assert_eq!(Item::Data(decoded.to_vec()), item);
194
195        let hex_val_rlp = hex!("00");
196        let item = Item::decode(&mut &hex_val_rlp[..]).unwrap();
197
198        let data = hex!("00");
199        let encoded = alloy_rlp::encode(&data[..]);
200        let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap();
201        assert_eq!(Item::Data(decoded.to_vec()), item);
202    }
203}