Skip to main content

ssz/
smallvec_impl.rs

1//! SSZ blanket impls for [`smallvec::SmallVec<A>`].
2//!
3//! The wire format and hash-tree-root are **byte/root-identical to
4//! `Vec<A::Item>`** (see the `Vec<T>` impl in [`crate::collections`]): a
5//! variable-length list capped at [`MAX_VEC_LEN`]. `SmallVec` only changes
6//! the in-memory storage (inline-then-spill); its serialized and merkleized
7//! forms must not diverge from `Vec`, so this mirrors the `Vec<T>` impl
8//! field-for-field. Keep the two in sync — a divergence would silently fork
9//! the hash of any cap embedding a `SmallVec`-backed field.
10//!
11//! `javm-cap`'s `Key` (`SmallVec<[u8; N]>`) and `SlotPath`
12//! (`SmallVec<[Key; M]>`) are `#[ssz(transparent)]` newtypes that forward
13//! to these impls — one generic impl covers both the basic-element (byte key)
14//! and composite-element (key path) cases.
15
16use alloc::vec::Vec;
17use digest::Digest;
18use digest::typenum::U32;
19use smallvec::{Array, SmallVec};
20
21use crate::collections::MAX_VEC_LEN;
22use crate::merkle::{merkleize, mix_in_length, pack_bytes};
23use crate::vector::decode_var_collection;
24use crate::{BYTES_PER_LENGTH_OFFSET, Decode, DecodeError, Encode, HashTreeRoot};
25
26impl<A: Array> Encode for SmallVec<A>
27where
28    A::Item: Encode,
29{
30    fn is_ssz_fixed_len() -> bool {
31        false
32    }
33    fn ssz_fixed_len() -> usize {
34        BYTES_PER_LENGTH_OFFSET
35    }
36    fn ssz_bytes_len(&self) -> usize {
37        if A::Item::is_ssz_fixed_len() {
38            A::Item::ssz_fixed_len() * self.len()
39        } else {
40            let mut total = self.len() * BYTES_PER_LENGTH_OFFSET;
41            for item in self {
42                total += item.ssz_bytes_len();
43            }
44            total
45        }
46    }
47    fn ssz_append(&self, buf: &mut Vec<u8>) {
48        if A::Item::is_ssz_fixed_len() {
49            for item in self {
50                item.ssz_append(buf);
51            }
52            return;
53        }
54        // Variable-length element: offset table + payloads (mirrors `Vec<T>`).
55        let n = self.len();
56        let header = n * BYTES_PER_LENGTH_OFFSET;
57        let start = buf.len();
58        buf.resize(start + header, 0u8);
59        let mut running = header as u32;
60        for (i, item) in self.iter().enumerate() {
61            let off_pos = start + i * BYTES_PER_LENGTH_OFFSET;
62            buf[off_pos..off_pos + 4].copy_from_slice(&running.to_le_bytes());
63            let before = buf.len();
64            item.ssz_append(buf);
65            let after = buf.len();
66            running = running
67                .checked_add((after - before) as u32)
68                .expect("ssz offset overflow");
69        }
70    }
71}
72
73impl<A: Array> Decode for SmallVec<A>
74where
75    A::Item: Decode,
76{
77    fn is_ssz_fixed_len() -> bool {
78        false
79    }
80    fn ssz_fixed_len() -> usize {
81        BYTES_PER_LENGTH_OFFSET
82    }
83    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
84        let items: Vec<A::Item> = if A::Item::is_ssz_fixed_len() {
85            let elem = A::Item::ssz_fixed_len();
86            if elem == 0 {
87                return Err(DecodeError::Custom(
88                    "zero-sized fixed-length SmallVec element",
89                ));
90            }
91            if !bytes.len().is_multiple_of(elem) {
92                return Err(DecodeError::LengthMismatch {
93                    expected: bytes.len().div_ceil(elem) * elem,
94                    actual: bytes.len(),
95                });
96            }
97            let n = bytes.len() / elem;
98            if (n as u64) > MAX_VEC_LEN {
99                return Err(DecodeError::BoundExceeded {
100                    len: n as u64,
101                    bound: MAX_VEC_LEN,
102                });
103            }
104            let mut out = Vec::with_capacity(n);
105            for i in 0..n {
106                let s = i * elem;
107                out.push(A::Item::from_ssz_bytes(&bytes[s..s + elem])?);
108            }
109            out
110        } else {
111            let out: Vec<A::Item> = decode_var_collection::<A::Item>(bytes, None)?;
112            if (out.len() as u64) > MAX_VEC_LEN {
113                return Err(DecodeError::BoundExceeded {
114                    len: out.len() as u64,
115                    bound: MAX_VEC_LEN,
116                });
117            }
118            out
119        };
120        // `from_vec` reuses the heap allocation when the list spilled and
121        // inlines otherwise — never reallocates.
122        Ok(SmallVec::from_vec(items))
123    }
124}
125
126impl<A: Array> HashTreeRoot for SmallVec<A>
127where
128    A::Item: HashTreeRoot + Encode,
129{
130    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
131        let len = self.len() as u64;
132        let inner_root = if A::Item::is_basic_type() {
133            let mut buf: Vec<u8> = Vec::new();
134            for t in self {
135                t.ssz_append(&mut buf);
136            }
137            let chunks = pack_bytes(&buf);
138            let cap_bytes = (MAX_VEC_LEN as usize).saturating_mul(A::Item::ssz_fixed_len());
139            let chunk_limit = cap_bytes.div_ceil(32).max(1);
140            merkleize::<D>(&chunks, chunk_limit)
141        } else {
142            let roots: Vec<[u8; 32]> = self.iter().map(|t| t.hash_tree_root::<D>()).collect();
143            merkleize::<D>(&roots, (MAX_VEC_LEN as usize).max(1))
144        };
145        mix_in_length::<D>(inner_root, len)
146    }
147}