Skip to main content

ssz/
vector.rs

1//! `FixedVector<T, N>` — homogeneous list with compile-time-fixed length.
2//!
3//! Wire format: fixed-len T → simple concatenation of N elements;
4//! variable-len T → N×4-byte offset table followed by variable payloads.
5//! No length prefix.
6
7use alloc::vec::Vec;
8use core::fmt;
9use digest::Digest;
10use digest::typenum::U32;
11
12use crate::list::encode_var_list_payloads;
13use crate::merkle::{merkleize, pack_bytes};
14use crate::{BYTES_PER_LENGTH_OFFSET, Decode, DecodeError, Encode, HashTreeRoot, read_offset};
15
16/// SSZ vector with a compile-time length of `N`.
17///
18/// Invariant: `inner.len() == N`.
19pub struct FixedVector<T, const N: usize> {
20    inner: Vec<T>,
21}
22
23impl<T, const N: usize> FixedVector<T, N> {
24    /// Build a `FixedVector` from an existing `Vec`, verifying the length.
25    pub fn from_vec(v: Vec<T>) -> Result<Self, DecodeError> {
26        if v.len() != N {
27            return Err(DecodeError::LengthMismatch {
28                expected: N,
29                actual: v.len(),
30            });
31        }
32        Ok(Self { inner: v })
33    }
34
35    /// Borrow the underlying storage.
36    pub fn as_slice(&self) -> &[T] {
37        &self.inner
38    }
39
40    /// Length of the vector (always `N`).
41    pub const fn len(&self) -> usize {
42        N
43    }
44
45    /// `true` iff `N == 0`.
46    pub const fn is_empty(&self) -> bool {
47        N == 0
48    }
49
50    /// Returns the inner storage.
51    pub fn into_inner(self) -> Vec<T> {
52        self.inner
53    }
54}
55
56impl<T: Clone, const N: usize> FixedVector<T, N> {
57    /// Convenience: build from a slice.
58    pub fn from_slice(items: &[T]) -> Result<Self, DecodeError> {
59        if items.len() != N {
60            return Err(DecodeError::LengthMismatch {
61                expected: N,
62                actual: items.len(),
63            });
64        }
65        let mut v: Vec<T> = Vec::with_capacity(N);
66        for t in items {
67            v.push(t.clone());
68        }
69        Ok(Self { inner: v })
70    }
71}
72
73impl<T, const N: usize> core::ops::Deref for FixedVector<T, N> {
74    type Target = [T];
75    fn deref(&self) -> &[T] {
76        &self.inner
77    }
78}
79
80impl<T: fmt::Debug, const N: usize> fmt::Debug for FixedVector<T, N> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.debug_struct("FixedVector")
83            .field("len", &N)
84            .field("inner", &&self.inner[..])
85            .finish()
86    }
87}
88
89impl<T: Clone, const N: usize> Clone for FixedVector<T, N> {
90    fn clone(&self) -> Self {
91        Self {
92            inner: self.inner.clone(),
93        }
94    }
95}
96
97impl<T: PartialEq, const N: usize> PartialEq for FixedVector<T, N> {
98    fn eq(&self, other: &Self) -> bool {
99        self.inner == other.inner
100    }
101}
102
103impl<T: Eq, const N: usize> Eq for FixedVector<T, N> {}
104
105// --------------------------------------------------------------------------
106// Encode / Decode / HashTreeRoot
107// --------------------------------------------------------------------------
108
109impl<T: Encode, const N: usize> Encode for FixedVector<T, N> {
110    fn is_ssz_fixed_len() -> bool {
111        T::is_ssz_fixed_len()
112    }
113    fn ssz_fixed_len() -> usize {
114        if T::is_ssz_fixed_len() {
115            T::ssz_fixed_len() * N
116        } else {
117            BYTES_PER_LENGTH_OFFSET
118        }
119    }
120    fn ssz_bytes_len(&self) -> usize {
121        if T::is_ssz_fixed_len() {
122            T::ssz_fixed_len() * N
123        } else {
124            let mut total = N * BYTES_PER_LENGTH_OFFSET;
125            for item in &self.inner {
126                total += item.ssz_bytes_len();
127            }
128            total
129        }
130    }
131    fn ssz_append(&self, buf: &mut Vec<u8>) {
132        encode_fixed_vector(self.inner.iter(), buf);
133    }
134}
135
136impl<T: Decode, const N: usize> Decode for FixedVector<T, N> {
137    fn is_ssz_fixed_len() -> bool {
138        T::is_ssz_fixed_len()
139    }
140    fn ssz_fixed_len() -> usize {
141        if T::is_ssz_fixed_len() {
142            T::ssz_fixed_len() * N
143        } else {
144            BYTES_PER_LENGTH_OFFSET
145        }
146    }
147    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
148        let decoded = decode_fixed_vector::<T>(bytes, N)?;
149        let mut inner: Vec<T> = Vec::with_capacity(N);
150        for t in decoded {
151            inner.push(t);
152        }
153        Ok(Self { inner })
154    }
155}
156
157impl<T: HashTreeRoot + Encode, const N: usize> HashTreeRoot for FixedVector<T, N> {
158    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
159        if T::is_basic_type() {
160            // Basic-type vector: pack encoded bytes into chunks and merkleize.
161            let mut buf: Vec<u8> = Vec::new();
162            for t in &self.inner {
163                t.ssz_append(&mut buf);
164            }
165            let chunks = pack_bytes(&buf);
166            let total_bytes = N.saturating_mul(T::ssz_fixed_len());
167            let chunk_limit = total_bytes.div_ceil(32).max(1);
168            merkleize::<D>(&chunks, chunk_limit)
169        } else {
170            // Composite vector: merkleize per-element roots.
171            let roots: Vec<[u8; 32]> = self.inner.iter().map(|t| t.hash_tree_root::<D>()).collect();
172            merkleize::<D>(&roots, N.max(1))
173        }
174    }
175}
176
177// --------------------------------------------------------------------------
178// Free helpers (shared with [T; N] impls)
179// --------------------------------------------------------------------------
180
181pub(crate) fn encode_fixed_vector<'a, T, I>(items: I, buf: &mut Vec<u8>)
182where
183    T: Encode + 'a,
184    I: Iterator<Item = &'a T>,
185{
186    let items_vec: Vec<&T> = items.collect();
187    if T::is_ssz_fixed_len() {
188        for item in &items_vec {
189            item.ssz_append(buf);
190        }
191    } else {
192        encode_var_list_payloads(&items_vec, buf);
193    }
194}
195
196pub(crate) fn decode_fixed_vector<T: Decode>(
197    bytes: &[u8],
198    n: usize,
199) -> Result<Vec<T>, DecodeError> {
200    if T::is_ssz_fixed_len() {
201        let elem_size = T::ssz_fixed_len();
202        let expected = elem_size.saturating_mul(n);
203        if bytes.len() != expected {
204            return Err(DecodeError::LengthMismatch {
205                expected,
206                actual: bytes.len(),
207            });
208        }
209        let mut out = Vec::with_capacity(n);
210        for i in 0..n {
211            let start = i * elem_size;
212            out.push(T::from_ssz_bytes(&bytes[start..start + elem_size])?);
213        }
214        Ok(out)
215    } else {
216        decode_var_collection::<T>(bytes, Some(n))
217    }
218}
219
220/// Shared variable-length collection decoder.
221///
222/// If `expected_len` is `Some(n)`, the decoder enforces exactly `n`
223/// elements (vector mode). Otherwise the length is inferred from the first
224/// offset (list mode).
225pub(crate) fn decode_var_collection<T: Decode>(
226    bytes: &[u8],
227    expected_len: Option<usize>,
228) -> Result<Vec<T>, DecodeError> {
229    if bytes.is_empty() {
230        return match expected_len {
231            None | Some(0) => Ok(Vec::new()),
232            Some(n) => Err(DecodeError::LengthMismatch {
233                expected: n,
234                actual: 0,
235            }),
236        };
237    }
238    let first = read_offset(bytes, 0)?;
239    if first % BYTES_PER_LENGTH_OFFSET != 0 {
240        return Err(DecodeError::InvalidOffset {
241            offset: first,
242            len: bytes.len(),
243            fixed: 0,
244        });
245    }
246    if first > bytes.len() {
247        return Err(DecodeError::InvalidOffset {
248            offset: first,
249            len: bytes.len(),
250            fixed: 0,
251        });
252    }
253    let n = first / BYTES_PER_LENGTH_OFFSET;
254    if let Some(expected) = expected_len
255        && n != expected
256    {
257        return Err(DecodeError::LengthMismatch {
258            expected,
259            actual: n,
260        });
261    }
262    let mut offsets = Vec::with_capacity(n + 1);
263    offsets.push(first);
264    for i in 1..n {
265        let off = read_offset(bytes, i * BYTES_PER_LENGTH_OFFSET)?;
266        if off < offsets[i - 1] {
267            return Err(DecodeError::OffsetsNotMonotonic {
268                prev: offsets[i - 1],
269                curr: off,
270            });
271        }
272        if off > bytes.len() {
273            return Err(DecodeError::InvalidOffset {
274                offset: off,
275                len: bytes.len(),
276                fixed: first,
277            });
278        }
279        offsets.push(off);
280    }
281    offsets.push(bytes.len());
282
283    let mut out = Vec::with_capacity(n);
284    for i in 0..n {
285        let slice = &bytes[offsets[i]..offsets[i + 1]];
286        out.push(T::from_ssz_bytes(slice)?);
287    }
288    Ok(out)
289}