ssz/
legacy.rs

1//! Provides a "legacy" version of SSZ encoding for `Option<T> where T: Encode + Decode`.
2//!
3//! The SSZ specification changed in 2021 to use a 1-byte union selector, instead of a 4-byte one
4//! which was used in the Lighthouse database.
5//!
6//! Users can use the `four_byte_option_impl` macro to define a module that can be used with the
7//! `#[ssz(with = "module")]`.
8//!
9//! ## Example
10//!
11//! ```rust
12//! use ssz_derive::{Encode, Decode};
13//! use ssz::four_byte_option_impl;
14//!
15//! four_byte_option_impl!(impl_for_u64, u64);
16//!
17//! #[derive(Encode, Decode)]
18//! struct Foo {
19//!     #[ssz(with = "impl_for_u64")]
20//!     a: Option<u64>,
21//! }
22//! ```
23
24use crate::*;
25
26#[macro_export]
27macro_rules! four_byte_option_impl {
28    ($mod_name: ident, $type: ty) => {
29        #[allow(dead_code)]
30        mod $mod_name {
31            use super::*;
32
33            pub mod encode {
34                use super::*;
35                #[allow(unused_imports)]
36                use ssz::*;
37
38                pub fn is_ssz_fixed_len() -> bool {
39                    false
40                }
41
42                pub fn ssz_fixed_len() -> usize {
43                    BYTES_PER_LENGTH_OFFSET
44                }
45
46                pub fn ssz_bytes_len(opt: &Option<$type>) -> usize {
47                    if let Some(some) = opt {
48                        let len = if <$type as Encode>::is_ssz_fixed_len() {
49                            <$type as Encode>::ssz_fixed_len()
50                        } else {
51                            <$type as Encode>::ssz_bytes_len(some)
52                        };
53                        len + BYTES_PER_LENGTH_OFFSET
54                    } else {
55                        BYTES_PER_LENGTH_OFFSET
56                    }
57                }
58
59                pub fn ssz_append(opt: &Option<$type>, buf: &mut Vec<u8>) {
60                    match opt {
61                        None => buf.extend_from_slice(&legacy::encode_four_byte_union_selector(0)),
62                        Some(t) => {
63                            buf.extend_from_slice(&legacy::encode_four_byte_union_selector(1));
64                            t.ssz_append(buf);
65                        }
66                    }
67                }
68
69                pub fn as_ssz_bytes(opt: &Option<$type>) -> Vec<u8> {
70                    let mut buf = vec![];
71
72                    ssz_append(opt, &mut buf);
73
74                    buf
75                }
76            }
77
78            pub mod decode {
79                use super::*;
80                #[allow(unused_imports)]
81                use ssz::*;
82
83                pub fn is_ssz_fixed_len() -> bool {
84                    false
85                }
86
87                pub fn ssz_fixed_len() -> usize {
88                    BYTES_PER_LENGTH_OFFSET
89                }
90
91                pub fn from_ssz_bytes(bytes: &[u8]) -> Result<Option<$type>, DecodeError> {
92                    if bytes.len() < BYTES_PER_LENGTH_OFFSET {
93                        return Err(DecodeError::InvalidByteLength {
94                            len: bytes.len(),
95                            expected: BYTES_PER_LENGTH_OFFSET,
96                        });
97                    }
98
99                    let (index_bytes, value_bytes) = bytes.split_at(BYTES_PER_LENGTH_OFFSET);
100
101                    let index = legacy::read_four_byte_union_selector(index_bytes)?;
102                    if index == 0 {
103                        Ok(None)
104                    } else if index == 1 {
105                        Ok(Some(<$type as ssz::Decode>::from_ssz_bytes(value_bytes)?))
106                    } else {
107                        Err(DecodeError::BytesInvalid(format!(
108                            "{} is not a valid union index for Option<T>",
109                            index
110                        )))
111                    }
112                }
113            }
114        }
115    };
116}
117
118pub fn encode_four_byte_union_selector(selector: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
119    encode_length(selector)
120}
121
122pub fn read_four_byte_union_selector(bytes: &[u8]) -> Result<usize, DecodeError> {
123    read_offset(bytes)
124}
125
126#[cfg(test)]
127mod test {
128    use super::*;
129    use crate as ssz;
130    use ssz_derive::{Decode, Encode};
131
132    type VecU16 = Vec<u16>;
133
134    four_byte_option_impl!(impl_u16, u16);
135    four_byte_option_impl!(impl_vec_u16, VecU16);
136
137    #[test]
138    fn ssz_encode_option_u16() {
139        let item = Some(65535_u16);
140        let bytes = vec![1, 0, 0, 0, 255, 255];
141        assert_eq!(impl_u16::encode::as_ssz_bytes(&item), bytes);
142        assert_eq!(impl_u16::decode::from_ssz_bytes(&bytes).unwrap(), item);
143
144        let item = None;
145        let bytes = vec![0, 0, 0, 0];
146        assert_eq!(impl_u16::encode::as_ssz_bytes(&item), bytes);
147        assert_eq!(impl_u16::decode::from_ssz_bytes(&bytes).unwrap(), None);
148    }
149
150    #[test]
151    fn ssz_encode_option_vec_u16() {
152        let item = Some(vec![0_u16, 1]);
153        let bytes = vec![1, 0, 0, 0, 0, 0, 1, 0];
154        assert_eq!(impl_vec_u16::encode::as_ssz_bytes(&item), bytes);
155        assert_eq!(impl_vec_u16::decode::from_ssz_bytes(&bytes).unwrap(), item);
156
157        let item = None;
158        let bytes = vec![0, 0, 0, 0];
159        assert_eq!(impl_vec_u16::encode::as_ssz_bytes(&item), bytes);
160        assert_eq!(impl_vec_u16::decode::from_ssz_bytes(&bytes).unwrap(), item);
161    }
162
163    fn round_trip<T: Encode + Decode + std::fmt::Debug + PartialEq>(items: Vec<T>) {
164        for item in items {
165            let encoded = &item.as_ssz_bytes();
166            assert_eq!(item.ssz_bytes_len(), encoded.len());
167            assert_eq!(T::from_ssz_bytes(encoded), Ok(item));
168        }
169    }
170
171    #[derive(Debug, PartialEq, Encode, Decode)]
172    struct TwoVariableLenOptions {
173        a: u16,
174        #[ssz(with = "impl_u16")]
175        b: Option<u16>,
176        #[ssz(with = "impl_vec_u16")]
177        c: Option<Vec<u16>>,
178        #[ssz(with = "impl_vec_u16")]
179        d: Option<Vec<u16>>,
180    }
181
182    #[test]
183    #[allow(clippy::zero_prefixed_literal)]
184    fn two_variable_len_options_encoding() {
185        let s = TwoVariableLenOptions {
186            a: 42,
187            b: None,
188            c: Some(vec![0]),
189            d: None,
190        };
191
192        let bytes = vec![
193            //  1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21
194            //      | option<u16>   | offset        | offset        | option<u16    | 1st list
195            42, 00, 14, 00, 00, 00, 18, 00, 00, 00, 24, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00,
196            //  23  24  25  26  27
197            //      | 2nd list
198            00, 00, 00, 00, 00, 00,
199        ];
200
201        assert_eq!(s.as_ssz_bytes(), bytes);
202    }
203
204    #[test]
205    fn two_variable_len_options_round_trip() {
206        let vec: Vec<TwoVariableLenOptions> = vec![
207            TwoVariableLenOptions {
208                a: 42,
209                b: Some(12),
210                c: Some(vec![0]),
211                d: Some(vec![1]),
212            },
213            TwoVariableLenOptions {
214                a: 42,
215                b: Some(12),
216                c: Some(vec![0]),
217                d: None,
218            },
219            TwoVariableLenOptions {
220                a: 42,
221                b: None,
222                c: Some(vec![0]),
223                d: None,
224            },
225            TwoVariableLenOptions {
226                a: 42,
227                b: None,
228                c: None,
229                d: None,
230            },
231        ];
232
233        round_trip(vec);
234    }
235
236    #[test]
237    fn tuple_u8_u16() {
238        let vec: Vec<(u8, u16)> = vec![
239            (0, 0),
240            (0, 1),
241            (1, 0),
242            (u8::max_value(), u16::max_value()),
243            (0, u16::max_value()),
244            (u8::max_value(), 0),
245            (42, 12301),
246        ];
247
248        round_trip(vec);
249    }
250
251    #[test]
252    fn tuple_vec_vec() {
253        let vec: Vec<(u64, Vec<u8>, Vec<Vec<u16>>)> = vec![
254            (0, vec![], vec![vec![]]),
255            (99, vec![101], vec![vec![], vec![]]),
256            (
257                42,
258                vec![12, 13, 14],
259                vec![vec![99, 98, 97, 96], vec![42, 44, 46, 48, 50]],
260            ),
261        ];
262
263        round_trip(vec);
264    }
265}