Skip to main content

cast/
rlp_converter.rs

1use alloy_primitives::{U256, hex};
2use alloy_rlp::{Decodable, Encodable, Header, PayloadView};
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<Self>),
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        struct ListFrame<'a> {
29            remaining: std::vec::IntoIter<&'a [u8]>,
30            items: Vec<Item>,
31        }
32
33        let items = match Header::decode_raw(buf)? {
34            PayloadView::String(data) => return Ok(Self::Data(data.to_vec())),
35            PayloadView::List(items) => items,
36        };
37
38        let mut frames = vec![ListFrame { remaining: items.into_iter(), items: Vec::new() }];
39        loop {
40            let Some(encoded) = frames.last_mut().unwrap().remaining.next() else {
41                let frame = frames.pop().unwrap();
42                let item = Self::Array(frame.items);
43                if let Some(parent) = frames.last_mut() {
44                    parent.items.push(item);
45                    continue;
46                }
47                return Ok(item);
48            };
49
50            match Header::decode_raw(&mut &encoded[..])? {
51                PayloadView::String(data) => {
52                    frames.last_mut().unwrap().items.push(Self::Data(data.to_vec()));
53                }
54                PayloadView::List(items) => {
55                    frames.push(ListFrame { remaining: items.into_iter(), items: Vec::new() });
56                }
57            }
58        }
59    }
60}
61
62impl Drop for Item {
63    fn drop(&mut self) {
64        // The default recursive drop can overflow after successfully decoding deeply nested RLP.
65        let Self::Array(items) = self else { return };
66        let mut pending = std::mem::take(items);
67        while let Some(mut item) = pending.pop() {
68            if let Self::Array(children) = &mut item {
69                pending.append(children);
70            }
71        }
72    }
73}
74
75impl Item {
76    pub(crate) fn value_to_item(value: &Value) -> eyre::Result<Self> {
77        match value {
78            Value::Null => Ok(Self::Data(vec![])),
79            Value::Bool(_) => {
80                eyre::bail!("RLP input can not contain booleans");
81            }
82            Value::Number(n) => {
83                Ok(Self::Data(n.to_string().parse::<U256>()?.to_be_bytes_trimmed_vec()))
84            }
85            Value::String(s) => Ok(Self::Data(hex::decode(s).wrap_err("Could not decode hex")?)),
86            Value::Array(values) => values.iter().map(Self::value_to_item).collect(),
87            Value::Object(_) => {
88                eyre::bail!("RLP input can not contain objects");
89            }
90        }
91    }
92}
93
94impl FromIterator<Self> for Item {
95    fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
96        Self::Array(Vec::from_iter(iter))
97    }
98}
99
100// Display as hex values
101impl fmt::Display for Item {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
103        enum Task<'a> {
104            Item(&'a Item),
105            Comma,
106            Close,
107        }
108
109        let mut tasks = vec![Task::Item(self)];
110        while let Some(task) = tasks.pop() {
111            match task {
112                Task::Item(Self::Data(data)) => write!(f, "\"0x{}\"", hex::encode(data))?,
113                Task::Item(Self::Array(items)) => {
114                    f.write_str("[")?;
115                    tasks.push(Task::Close);
116                    for (i, item) in items.iter().enumerate().rev() {
117                        tasks.push(Task::Item(item));
118                        if i > 0 {
119                            tasks.push(Task::Comma);
120                        }
121                    }
122                }
123                Task::Comma => f.write_str(",")?,
124                Task::Close => f.write_str("]")?,
125            }
126        }
127        Ok(())
128    }
129}
130
131#[cfg(test)]
132mod test {
133    use crate::rlp_converter::Item;
134    use alloy_primitives::hex;
135    use alloy_rlp::{Bytes, Decodable};
136    use serde_json::Result as JsonResult;
137
138    // https://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers
139    fn array_von_neuman() -> Item {
140        Item::Array(vec![
141            Item::Array(vec![]),
142            Item::Array(vec![Item::Array(vec![])]),
143            Item::Array(vec![Item::Array(vec![]), Item::Array(vec![Item::Array(vec![])])]),
144        ])
145    }
146
147    #[test]
148    #[expect(clippy::disallowed_macros)]
149    fn encode_decode_test() -> alloy_rlp::Result<()> {
150        let parameters = vec![
151            (1, b"\xc0".to_vec(), Item::Array(vec![])),
152            (2, b"\xc1\x80".to_vec(), Item::Array(vec![Item::Data(vec![])])),
153            (3, b"\xc4\x83dog".to_vec(), Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])),
154            (
155                4,
156                b"\xc5\xc4\x83dog".to_vec(),
157                Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]),
158            ),
159            (
160                5,
161                b"\xc8\x83dog\x83cat".to_vec(),
162                Item::Array(vec![
163                    Item::Data(vec![0x64, 0x6f, 0x67]),
164                    Item::Data(vec![0x63, 0x61, 0x74]),
165                ]),
166            ),
167            (6, b"\xc7\xc0\xc1\xc0\xc3\xc0\xc1\xc0".to_vec(), array_von_neuman()),
168            (
169                7,
170                b"\xcd\x83\x6c\x6f\x6c\xc3\xc2\xc1\xc0\xc4\x83\x6f\x6c\x6f".to_vec(),
171                Item::Array(vec![
172                    Item::Data(vec![b'\x6c', b'\x6f', b'\x6c']),
173                    Item::Array(vec![Item::Array(vec![Item::Array(vec![Item::Array(vec![])])])]),
174                    Item::Array(vec![Item::Data(vec![b'\x6f', b'\x6c', b'\x6f'])]),
175                ]),
176            ),
177        ];
178        for params in parameters {
179            let encoded = alloy_rlp::encode(&params.2);
180            assert_eq!(Item::decode(&mut &encoded[..])?, params.2);
181            let decoded = Item::decode(&mut &params.1[..])?;
182            assert_eq!(alloy_rlp::encode(&decoded), params.1);
183            println!("case {} validated", params.0)
184        }
185
186        Ok(())
187    }
188
189    #[test]
190    #[expect(clippy::disallowed_macros)]
191    fn deserialize_from_str_test_hex() -> JsonResult<()> {
192        let parameters = vec![
193            (1, "[\"\"]", Item::Array(vec![Item::Data(vec![])])),
194            (2, "[\"0x646f67\"]", Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])),
195            (
196                3,
197                "[[\"646f67\"]]",
198                Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]),
199            ),
200            (
201                4,
202                "[\"646f67\",\"0x636174\"]",
203                Item::Array(vec![
204                    Item::Data(vec![0x64, 0x6f, 0x67]),
205                    Item::Data(vec![0x63, 0x61, 0x74]),
206                ]),
207            ),
208            (6, "[[],[[]],[[],[[]]]]", array_von_neuman()),
209        ];
210        for params in parameters {
211            let val = serde_json::from_str(params.1)?;
212            let item = Item::value_to_item(&val).unwrap();
213            assert_eq!(item, params.2);
214            println!("case {} validated", params.0);
215        }
216
217        Ok(())
218    }
219
220    #[test]
221    fn rlp_data() {
222        // <https://github.com/foundry-rs/foundry/issues/9197>
223        let hex_val_rlp = hex!("820002");
224        let item = Item::decode(&mut &hex_val_rlp[..]).unwrap();
225
226        let data = hex!("0002");
227        let encoded = alloy_rlp::encode(&data[..]);
228        let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap();
229        assert_eq!(Item::Data(decoded.to_vec()), item);
230
231        let hex_val_rlp = hex!("00");
232        let item = Item::decode(&mut &hex_val_rlp[..]).unwrap();
233
234        let data = hex!("00");
235        let encoded = alloy_rlp::encode(&data[..]);
236        let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap();
237        assert_eq!(Item::Data(decoded.to_vec()), item);
238    }
239}