use crate::ssz::hash::{
BYTES_PER_CHUNK, chunkify_fixed_non_empty, merkleize_with_limit, mix_in_length,
};
use crate::ssz::{HashTreeRoot, SszDecode, SszElement, SszEncode, SszFixedLen};
use crate::types::bytes::Bytes32;
use crate::unsafe_vec::{write_at, write_bytes_at};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SszVector<T, const LENGTH: usize> {
data: Vec<T>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SszList<T, const LIMIT: usize> {
data: Vec<T>,
}
#[inline]
fn pack_basic_fixed_chunks<T>(items: &[T], elem_len: usize) -> Vec<Bytes32>
where
T: SszEncode,
{
let total = items.len() * elem_len;
if total == 0 {
return Vec::new();
}
if elem_len > BYTES_PER_CHUNK {
let mut bytes = Vec::with_capacity(total);
for item in items {
item.encode_ssz_into(&mut bytes);
}
return chunkify_fixed_non_empty(&bytes);
}
let chunk_count = total.div_ceil(BYTES_PER_CHUNK);
let mut chunks = Vec::with_capacity(chunk_count);
let mut chunk = [0u8; 32];
let mut filled = 0usize;
for item in items {
let mut elem_buf = [0u8; 32];
unsafe { item.write_fixed_ssz(elem_buf.as_mut_ptr()) };
let mut src_start = 0usize;
while src_start < elem_len {
let space = BYTES_PER_CHUNK - filled;
let to_copy = (elem_len - src_start).min(space);
chunk[filled..filled + to_copy]
.copy_from_slice(&elem_buf[src_start..src_start + to_copy]);
filled += to_copy;
src_start += to_copy;
if filled == BYTES_PER_CHUNK {
chunks.push(Bytes32::from(chunk));
chunk = [0u8; 32];
filled = 0;
}
}
}
if filled != 0 {
chunks.push(Bytes32::from(chunk));
}
chunks
}
impl<T, const LENGTH: usize> SszVector<T, LENGTH> {
pub fn new(data: Vec<T>) -> Result<Self, String> {
if data.len() != LENGTH {
return Err(format!(
"SszVector expects {} elements, got {}",
LENGTH,
data.len()
));
}
Ok(Self { data })
}
#[inline]
pub fn as_slice(&self) -> &[T] {
&self.data
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
#[inline]
pub fn iter(&self) -> core::slice::Iter<'_, T> {
self.data.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> {
self.data.iter_mut()
}
#[inline]
pub fn len(&self) -> usize {
LENGTH
}
#[inline]
pub fn is_empty(&self) -> bool {
LENGTH == 0
}
#[inline]
pub fn into_inner(self) -> Vec<T> {
self.data
}
#[inline]
pub fn get(&self, index: usize) -> Option<&T> {
self.data.get(index)
}
#[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.data.get_mut(index)
}
#[inline]
pub fn first(&self) -> Option<&T> {
self.data.first()
}
#[inline]
pub fn first_mut(&mut self) -> Option<&mut T> {
self.data.first_mut()
}
#[inline]
pub fn last(&self) -> Option<&T> {
self.data.last()
}
#[inline]
pub fn last_mut(&mut self) -> Option<&mut T> {
self.data.last_mut()
}
pub fn encode_ssz_fixed_into(&self, out: &mut [u8])
where
T: SszFixedLen + SszEncode + SszElement,
{
let elem_len = T::fixed_len();
let expected = elem_len
.checked_mul(LENGTH)
.expect("SszVector fixed length overflows usize");
debug_assert!(
out.len() == expected,
"fixed-size SszVector encode expects {} bytes, got {}",
expected,
out.len()
);
for (idx, item) in self.data.iter().enumerate() {
let offset = idx * elem_len;
unsafe { item.write_fixed_ssz(out.as_mut_ptr().add(offset)) };
}
}
pub fn decode_ssz_checked(bytes: &[u8]) -> Result<Self, String>
where
T: SszDecode + SszElement,
{
if let Some(elem_len) = T::fixed_len_opt() {
let expected = elem_len * LENGTH;
if bytes.len() != expected {
return Err(format!(
"SszVector expects {} bytes, got {}",
expected,
bytes.len()
));
}
return Self::decode_ssz(bytes);
}
let table_len = 4 * LENGTH;
if bytes.len() < table_len {
return Err("SszVector offset table exceeds input length".to_string());
}
let off = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
if off != table_len {
return Err("SszVector first offset must equal table length".to_string());
}
let mut prev = off;
for i in 1..LENGTH {
let off_start = i * 4;
let off_end = off_start + 4;
let off = u32::from_le_bytes(bytes[off_start..off_end].try_into().unwrap()) as usize;
if off < prev || off > bytes.len() {
return Err("SszVector offsets are invalid".to_string());
}
prev = off;
}
Self::decode_ssz(bytes)
}
}
impl<T, const LIMIT: usize> Default for SszList<T, LIMIT> {
fn default() -> Self {
Self { data: Vec::new() }
}
}
impl<T, const LIMIT: usize> SszList<T, LIMIT> {
pub fn new(data: Vec<T>) -> Result<Self, String> {
if data.len() > LIMIT {
return Err(format!(
"SszList length {} exceeds limit {}",
data.len(),
LIMIT
));
}
Ok(Self { data })
}
#[inline]
pub fn as_slice(&self) -> &[T] {
&self.data
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
#[inline]
pub fn iter(&self) -> core::slice::Iter<'_, T> {
self.data.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> {
self.data.iter_mut()
}
#[inline]
pub fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[inline]
pub fn into_inner(self) -> Vec<T> {
self.data
}
#[inline]
pub fn get(&self, index: usize) -> Option<&T> {
self.data.get(index)
}
#[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.data.get_mut(index)
}
#[inline]
pub fn first(&self) -> Option<&T> {
self.data.first()
}
#[inline]
pub fn first_mut(&mut self) -> Option<&mut T> {
self.data.first_mut()
}
#[inline]
pub fn last(&self) -> Option<&T> {
self.data.last()
}
#[inline]
pub fn last_mut(&mut self) -> Option<&mut T> {
self.data.last_mut()
}
#[inline]
pub fn push(&mut self, value: T) -> Result<(), String> {
if self.data.len() == LIMIT {
return Err(format!("SszList length {} exceeds limit {}", LIMIT + 1, LIMIT));
}
self.data.push(value);
Ok(())
}
#[inline]
pub fn extend_copy(&mut self, additional: usize, value: T) -> Result<(), String>
where
T: Copy,
{
if additional == 0 {
return Ok(());
}
let new_len = self
.data
.len()
.checked_add(additional)
.ok_or_else(|| "SszList length overflow".to_string())?;
if new_len > LIMIT {
return Err(format!("SszList length {} exceeds limit {}", new_len, LIMIT));
}
let start = self.data.len();
self.data.reserve(additional);
unsafe { self.data.set_len(new_len) };
for idx in 0..additional {
unsafe { write_at(&mut self.data, start + idx, value) };
}
Ok(())
}
#[inline]
pub fn pop(&mut self) -> Option<T> {
self.data.pop()
}
#[inline]
pub fn truncate(&mut self, len: usize) {
self.data.truncate(len);
}
#[inline]
pub fn clear(&mut self) {
self.data.clear();
}
pub fn decode_ssz_checked(bytes: &[u8]) -> Result<Self, String>
where
T: SszDecode + SszElement,
{
if let Some(elem_len) = T::fixed_len_opt() {
if !bytes.len().is_multiple_of(elem_len) {
return Err("SszList length is not a multiple of element size".to_string());
}
let len = bytes.len() / elem_len;
if len > LIMIT {
return Err(format!("SszList length {} exceeds limit {}", len, LIMIT));
}
return Self::decode_ssz(bytes);
}
if bytes.is_empty() {
return Ok(Self { data: Vec::new() });
}
if bytes.len() < 4 {
return Err("SszList missing offset table".to_string());
}
let first = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
if !first.is_multiple_of(4) {
return Err("SszList first offset must be multiple of 4".to_string());
}
let len = first / 4;
if len > LIMIT {
return Err(format!("SszList length {} exceeds limit {}", len, LIMIT));
}
let table_len = 4 * len;
if first != table_len {
return Err("SszList first offset must equal table length".to_string());
}
if bytes.len() < table_len {
return Err("SszList offset table exceeds input length".to_string());
}
let mut prev = table_len;
for i in 1..len {
let off_start = i * 4;
let off_end = off_start + 4;
let off = u32::from_le_bytes(bytes[off_start..off_end].try_into().unwrap()) as usize;
if off < prev || off > bytes.len() {
return Err("SszList offsets are invalid".to_string());
}
prev = off;
}
Self::decode_ssz(bytes)
}
}
impl<T, const LENGTH: usize> SszEncode for SszVector<T, LENGTH>
where
T: SszEncode + SszElement,
{
fn encode_ssz(&self) -> Vec<u8> {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * LENGTH;
let mut out: Vec<u8> = Vec::with_capacity(total);
unsafe { out.set_len(total) };
for (idx, item) in self.data.iter().enumerate() {
let offset = idx * elem_len;
unsafe { item.write_fixed_ssz(out.as_mut_ptr().add(offset)) };
}
return out;
}
let count = self.data.len();
let mut offsets = Vec::with_capacity(count);
let mut elems = Vec::with_capacity(count);
unsafe {
offsets.set_len(count);
elems.set_len(count);
}
let mut cursor = 4 * count;
for (idx, item) in self.data.iter().enumerate() {
let bytes = item.encode_ssz();
unsafe { write_at(&mut offsets, idx, cursor as u32) };
cursor += bytes.len();
unsafe { write_at(&mut elems, idx, bytes) };
}
let mut out = Vec::with_capacity(cursor);
unsafe { out.set_len(cursor) };
let mut cursor = 0usize;
for off in offsets {
unsafe { write_bytes_at(&mut out, cursor, &off.to_le_bytes()) };
cursor += 4;
}
for bytes in elems {
unsafe { write_bytes_at(&mut out, cursor, &bytes) };
cursor += bytes.len();
}
out
}
fn encode_ssz_checked(&self) -> Result<Vec<u8>, String> {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * LENGTH;
let mut out = Vec::with_capacity(total);
for item in &self.data {
let bytes = item.encode_ssz_checked()?;
if bytes.len() != elem_len {
return Err(format!(
"fixed-size SszVector element encoded to {} bytes, expected {}",
bytes.len(),
elem_len
));
}
out.extend_from_slice(&bytes);
}
return Ok(out);
}
let count = self.data.len();
let mut offsets = Vec::with_capacity(count);
let mut elems = Vec::with_capacity(count);
let mut cursor = 4 * count;
for item in &self.data {
let bytes = item.encode_ssz_checked()?;
offsets.push(cursor as u32);
cursor += bytes.len();
elems.push(bytes);
}
let mut out = Vec::with_capacity(cursor);
unsafe { out.set_len(cursor) };
let mut cursor = 0usize;
for off in offsets {
unsafe { write_bytes_at(&mut out, cursor, &off.to_le_bytes()) };
cursor += 4;
}
for bytes in elems {
unsafe { write_bytes_at(&mut out, cursor, &bytes) };
cursor += bytes.len();
}
Ok(out)
}
fn encode_ssz_into(&self, out: &mut Vec<u8>) {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * LENGTH;
let start = out.len();
out.reserve(total);
unsafe { out.set_len(start + total) };
for (idx, item) in self.data.iter().enumerate() {
let offset = start + idx * elem_len;
unsafe { item.write_fixed_ssz(out.as_mut_ptr().add(offset)) };
}
return;
}
let count = self.data.len();
let table_start = out.len();
let table_len = 4 * count;
out.reserve(table_len);
unsafe { out.set_len(table_start + table_len) };
for (idx, item) in self.data.iter().enumerate() {
let offset = (out.len() - table_start) as u32;
unsafe { write_bytes_at(out, table_start + idx * 4, &offset.to_le_bytes()) };
item.encode_ssz_into(out);
}
}
unsafe fn write_fixed_ssz(&self, dst: *mut u8) {
let Some(elem_len) = T::fixed_len_opt() else {
panic!("variable-size SszVector cannot be written via write_fixed_ssz");
};
for (idx, item) in self.data.iter().enumerate() {
let offset = idx * elem_len;
unsafe { item.write_fixed_ssz(dst.add(offset)) };
}
}
}
impl<T, const LIMIT: usize> SszEncode for SszList<T, LIMIT>
where
T: SszEncode + SszElement,
{
fn encode_ssz(&self) -> Vec<u8> {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * self.data.len();
let mut out: Vec<u8> = Vec::with_capacity(total);
unsafe { out.set_len(total) };
for (idx, item) in self.data.iter().enumerate() {
let offset = idx * elem_len;
unsafe { item.write_fixed_ssz(out.as_mut_ptr().add(offset)) };
}
return out;
}
let count = self.data.len();
let mut offsets = Vec::with_capacity(count);
let mut elems = Vec::with_capacity(count);
unsafe {
offsets.set_len(count);
elems.set_len(count);
}
let mut cursor = 4 * count;
for (idx, item) in self.data.iter().enumerate() {
let bytes = item.encode_ssz();
unsafe { write_at(&mut offsets, idx, cursor as u32) };
cursor += bytes.len();
unsafe { write_at(&mut elems, idx, bytes) };
}
let mut out = Vec::with_capacity(cursor);
let table_len = 4 * count;
unsafe { out.set_len(cursor) };
for (idx, off) in offsets.iter().enumerate() {
unsafe { write_bytes_at(&mut out, idx * 4, &off.to_le_bytes()) };
}
let mut payload_cursor = table_len;
for bytes in elems {
unsafe { write_bytes_at(&mut out, payload_cursor, &bytes) };
payload_cursor += bytes.len();
}
out
}
fn encode_ssz_checked(&self) -> Result<Vec<u8>, String> {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * self.data.len();
let mut out = Vec::with_capacity(total);
for item in &self.data {
let bytes = item.encode_ssz_checked()?;
if bytes.len() != elem_len {
return Err(format!(
"fixed-size SszList element encoded to {} bytes, expected {}",
bytes.len(),
elem_len
));
}
out.extend_from_slice(&bytes);
}
return Ok(out);
}
let count = self.data.len();
let mut offsets = Vec::with_capacity(count);
let mut elems = Vec::with_capacity(count);
let mut cursor = 4 * count;
for item in &self.data {
let bytes = item.encode_ssz_checked()?;
offsets.push(cursor as u32);
cursor += bytes.len();
elems.push(bytes);
}
let mut out = Vec::with_capacity(cursor);
let table_len = 4 * count;
unsafe { out.set_len(cursor) };
for (idx, off) in offsets.iter().enumerate() {
unsafe { write_bytes_at(&mut out, idx * 4, &off.to_le_bytes()) };
}
let mut payload_cursor = table_len;
for bytes in elems {
unsafe { write_bytes_at(&mut out, payload_cursor, &bytes) };
payload_cursor += bytes.len();
}
Ok(out)
}
fn encode_ssz_into(&self, out: &mut Vec<u8>) {
if let Some(elem_len) = T::fixed_len_opt() {
let total = elem_len * self.data.len();
let start = out.len();
out.reserve(total);
unsafe { out.set_len(start + total) };
for (idx, item) in self.data.iter().enumerate() {
let offset = start + idx * elem_len;
unsafe { item.write_fixed_ssz(out.as_mut_ptr().add(offset)) };
}
return;
}
let count = self.data.len();
let table_start = out.len();
let table_len = 4 * count;
out.reserve(table_len);
unsafe { out.set_len(table_start + table_len) };
for (idx, item) in self.data.iter().enumerate() {
let offset = (out.len() - table_start) as u32;
unsafe { write_bytes_at(out, table_start + idx * 4, &offset.to_le_bytes()) };
item.encode_ssz_into(out);
}
}
unsafe fn write_fixed_ssz(&self, _dst: *mut u8) {
panic!("SszList is variable-size and cannot be written via write_fixed_ssz");
}
}
impl<T, const LENGTH: usize> SszDecode for SszVector<T, LENGTH>
where
T: SszDecode + SszElement,
{
fn decode_ssz(bytes: &[u8]) -> Result<Self, String> {
if let Some(elem_len) = T::fixed_len_opt() {
let mut data = Vec::with_capacity(LENGTH);
unsafe { data.set_len(LENGTH) };
for i in 0..LENGTH {
let start = i * elem_len;
let end = start + elem_len;
let x = match T::decode_ssz(&bytes[start..end]) {
Ok(val) => val,
Err(err) => {
unsafe {
let ptr: *mut T = data.as_mut_ptr();
for j in 0..i {
core::ptr::drop_in_place(ptr.add(j));
}
data.set_len(0);
}
return Err(err);
}
};
unsafe { write_at(&mut data, i, x) };
}
return Ok(Self { data });
}
let mut data = Vec::with_capacity(LENGTH);
unsafe { data.set_len(LENGTH) };
for i in 0..LENGTH {
let off_start = i * 4;
let off_end = off_start + 4;
let start = u32::from_le_bytes(bytes[off_start..off_end].try_into().unwrap()) as usize;
let end = if i + 1 < LENGTH {
let next_start = (i + 1) * 4;
let next_end = next_start + 4;
u32::from_le_bytes(bytes[next_start..next_end].try_into().unwrap()) as usize
} else {
bytes.len()
};
let x = match T::decode_ssz(&bytes[start..end]) {
Ok(val) => val,
Err(err) => {
unsafe {
let ptr: *mut T = data.as_mut_ptr();
for j in 0..i {
core::ptr::drop_in_place(ptr.add(j));
}
data.set_len(0);
}
return Err(err);
}
};
unsafe { write_at(&mut data, i, x) };
}
Ok(Self { data })
}
}
impl<T, const LIMIT: usize> SszDecode for SszList<T, LIMIT>
where
T: SszDecode + SszElement,
{
fn decode_ssz(bytes: &[u8]) -> Result<Self, String> {
if let Some(elem_len) = T::fixed_len_opt() {
let len = bytes.len() / elem_len;
let mut data = Vec::with_capacity(len);
unsafe { data.set_len(len) };
for i in 0..len {
let start = i * elem_len;
let end = start + elem_len;
let x = match T::decode_ssz(&bytes[start..end]) {
Ok(val) => val,
Err(err) => {
unsafe {
let ptr: *mut T = data.as_mut_ptr();
for j in 0..i {
core::ptr::drop_in_place(ptr.add(j));
}
data.set_len(0);
}
return Err(err);
}
};
unsafe { write_at(&mut data, i, x) };
}
return Ok(Self { data });
}
if bytes.is_empty() {
return Ok(Self { data: Vec::new() });
}
let first = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
let len = first / 4;
let mut data = Vec::with_capacity(len);
unsafe { data.set_len(len) };
for i in 0..len {
let off_start = i * 4;
let off_end = off_start + 4;
let start = u32::from_le_bytes(bytes[off_start..off_end].try_into().unwrap()) as usize;
let end = if i + 1 < len {
let next_start = (i + 1) * 4;
let next_end = next_start + 4;
u32::from_le_bytes(bytes[next_start..next_end].try_into().unwrap()) as usize
} else {
bytes.len()
};
let x = match T::decode_ssz(&bytes[start..end]) {
Ok(val) => val,
Err(err) => {
unsafe {
let ptr: *mut T = data.as_mut_ptr();
for j in 0..i {
core::ptr::drop_in_place(ptr.add(j));
}
data.set_len(0);
}
return Err(err);
}
};
unsafe { write_at(&mut data, i, x) };
}
Ok(Self { data })
}
}
impl<T, const LENGTH: usize> HashTreeRoot for SszVector<T, LENGTH>
where
T: SszEncode + SszElement + HashTreeRoot,
{
fn hash_tree_root(&self) -> [u8; 32] {
if let Some(elem_len) = T::fixed_len_opt().filter(|_| T::tree_pack_basic()) {
let chunks = pack_basic_fixed_chunks(&self.data, elem_len);
let limit_chunks = (LENGTH * elem_len).div_ceil(BYTES_PER_CHUNK);
let root =
merkleize_with_limit(&chunks, limit_chunks).unwrap_or_else(|_| Bytes32::zero());
return *root.as_ref();
}
let count = self.data.len();
let mut chunks = Vec::with_capacity(count);
unsafe { chunks.set_len(count) };
for (i, item) in self.data.iter().enumerate() {
let root = Bytes32::from(item.hash_tree_root());
unsafe { write_at(&mut chunks, i, root) };
}
let root = merkleize_with_limit(&chunks, LENGTH).unwrap_or_else(|_| Bytes32::zero());
*root.as_ref()
}
}
impl<T, const LIMIT: usize> HashTreeRoot for SszList<T, LIMIT>
where
T: SszEncode + SszElement + HashTreeRoot,
{
fn hash_tree_root(&self) -> [u8; 32] {
if let Some(elem_len) = T::fixed_len_opt().filter(|_| T::tree_pack_basic()) {
let chunks = pack_basic_fixed_chunks(&self.data, elem_len);
let limit_chunks = (LIMIT * elem_len).div_ceil(BYTES_PER_CHUNK);
let root =
merkleize_with_limit(&chunks, limit_chunks).unwrap_or_else(|_| Bytes32::zero());
let mixed = mix_in_length(&root, self.data.len());
return *mixed.as_ref();
}
let count = self.data.len();
let mut chunks = Vec::with_capacity(count);
unsafe { chunks.set_len(count) };
for (i, item) in self.data.iter().enumerate() {
let root = Bytes32::from(item.hash_tree_root());
unsafe { write_at(&mut chunks, i, root) };
}
let root = merkleize_with_limit(&chunks, LIMIT).unwrap_or_else(|_| Bytes32::zero());
let mixed = mix_in_length(&root, count);
*mixed.as_ref()
}
}
impl<T, const LENGTH: usize> SszElement for SszVector<T, LENGTH>
where
T: SszElement,
{
fn fixed_len_opt() -> Option<usize> {
if LENGTH == 0 {
return None;
}
T::fixed_len_opt().and_then(|elem_len| elem_len.checked_mul(LENGTH))
}
}
impl<T, const LIMIT: usize> SszElement for SszList<T, LIMIT> {}
#[cfg(test)]
mod tests {
use super::{SszList, SszVector};
use crate::ssz::SszEncode;
use crate::types::bitlist::BitVector;
#[test]
fn list_encode_into_matches_encode_ssz_for_nested_lists() {
let inner = SszList::<u64, 8>::new(vec![1, 2, 3, 4]).unwrap();
let outer = SszList::<SszList<u64, 8>, 4>::new(vec![inner.clone(), inner]).unwrap();
let mut out = Vec::new();
outer.encode_ssz_into(&mut out);
assert_eq!(out, outer.encode_ssz());
}
#[test]
fn vector_encode_into_matches_encode_ssz_for_nested_lists() {
let inner = SszList::<u64, 8>::new(vec![1, 2, 3, 4]).unwrap();
let vector = SszVector::<SszList<u64, 8>, 2>::new(vec![inner.clone(), inner]).unwrap();
let mut out = Vec::new();
vector.encode_ssz_into(&mut out);
assert_eq!(out, vector.encode_ssz());
}
#[test]
fn list_of_fixed_vectors_encodes_without_offsets() {
let first = SszVector::<u64, 2>::new(vec![1, 2]).unwrap();
let second = SszVector::<u64, 2>::new(vec![3, 4]).unwrap();
let outer = SszList::<SszVector<u64, 2>, 4>::new(vec![first, second]).unwrap();
let mut expected = Vec::new();
for value in [1u64, 2, 3, 4] {
expected.extend_from_slice(&value.to_le_bytes());
}
assert_eq!(outer.encode_ssz(), expected);
}
#[test]
fn list_of_zero_length_vectors_uses_offsets_and_round_trips() {
let first = SszVector::<u64, 0>::new(vec![]).unwrap();
let second = SszVector::<u64, 0>::new(vec![]).unwrap();
let outer = SszList::<SszVector<u64, 0>, 4>::new(vec![first, second]).unwrap();
assert_eq!(outer.encode_ssz(), vec![8, 0, 0, 0, 8, 0, 0, 0]);
let decoded = SszList::<SszVector<u64, 0>, 4>::decode_ssz_checked(&outer.encode_ssz())
.expect("zero-length vector list should decode");
assert_eq!(decoded, outer);
}
#[test]
fn list_encode_ssz_checked_validates_elements() {
let invalid = BitVector::<3> { data: vec![0xff] };
let list = SszList::<BitVector<3>, 4>::new(vec![invalid]).unwrap();
assert_eq!(
list.encode_ssz_checked().unwrap_err(),
"BitVector has non-zero unused bits"
);
}
#[test]
fn vector_encode_ssz_checked_validates_elements() {
let invalid = BitVector::<3> { data: vec![0xff] };
let vector = SszVector::<BitVector<3>, 1>::new(vec![invalid]).unwrap();
assert_eq!(
vector.encode_ssz_checked().unwrap_err(),
"BitVector has non-zero unused bits"
);
}
#[test]
fn fixed_vector_fixed_encode_matches_vec_encode() {
let vector = SszVector::<u64, 2>::new(vec![1, 2]).unwrap();
let mut out = [0u8; 16];
vector.encode_ssz_fixed_into(&mut out);
assert_eq!(out.as_slice(), vector.encode_ssz().as_slice());
}
#[test]
fn vector_mut_access_preserves_length_invariant() {
let mut vector = SszVector::<u64, 2>::new(vec![1, 2]).unwrap();
vector.as_mut_slice()[0] = 9;
*vector.get_mut(1).unwrap() = 7;
assert_eq!(vector.as_slice(), &[9, 7]);
assert_eq!(vector.len(), 2);
}
#[test]
fn list_push_respects_limit() {
let mut list = SszList::<u64, 2>::new(vec![1]).unwrap();
list.push(2).unwrap();
assert_eq!(list.as_slice(), &[1, 2]);
assert_eq!(
list.push(3).unwrap_err(),
"SszList length 3 exceeds limit 2"
);
}
#[test]
fn list_mutators_update_contents_without_exposing_backing_vec() {
let mut list = SszList::<u64, 4>::new(vec![1, 2, 3]).unwrap();
*list.first_mut().unwrap() = 10;
*list.last_mut().unwrap() = 30;
list.truncate(2);
list.push(40).unwrap();
assert_eq!(list.pop(), Some(40));
list.clear();
assert!(list.is_empty());
}
#[test]
fn list_extend_copy_respects_limit_and_repeats_value() {
let mut list = SszList::<u64, 4>::new(vec![1]).unwrap();
list.extend_copy(2, 9).unwrap();
assert_eq!(list.as_slice(), &[1, 9, 9]);
assert_eq!(
list.extend_copy(2, 7).unwrap_err(),
"SszList length 5 exceeds limit 4"
);
}
}