use core::marker::PhantomData;
use crate::borrow::{Ref, RefMut};
use crate::error::ProgramError;
use crate::segment_lease::SegmentLease;
pub trait TailCodec: Sized {
const MAX_ENCODED_LEN: usize;
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError>;
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError>;
}
pub trait TailElement: TailCodec + Copy + Default + PartialEq {}
impl<T> TailElement for T where T: TailCodec + Copy + Default + PartialEq {}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct TailBytes<'a> {
bytes: &'a [u8],
}
impl<'a> TailBytes<'a> {
#[inline(always)]
pub const fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
}
#[inline(always)]
pub const fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.bytes.len()
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct TailStr<'a> {
bytes: &'a [u8],
}
impl<'a> TailStr<'a> {
#[inline(always)]
pub const fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
}
#[inline(always)]
pub const fn from_str(value: &'a str) -> Self {
Self {
bytes: value.as_bytes(),
}
}
#[inline(always)]
pub const fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline]
pub fn as_str(&self) -> Result<&'a str, ProgramError> {
core::str::from_utf8(self.bytes).map_err(|_| ProgramError::InvalidAccountData)
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.bytes.len()
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
impl TailCodec for u8 {
const MAX_ENCODED_LEN: usize = 1;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.is_empty() {
return Err(ProgramError::AccountDataTooSmall);
}
out[0] = *self;
Ok(1)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
input
.first()
.copied()
.map(|b| (b, 1))
.ok_or(ProgramError::InvalidAccountData)
}
}
macro_rules! tail_codec_int {
( $( $ty:ty : $n:expr ),+ $(,)? ) => {
$(
impl TailCodec for $ty {
const MAX_ENCODED_LEN: usize = $n;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.len() < $n {
return Err(ProgramError::AccountDataTooSmall);
}
out[..$n].copy_from_slice(&self.to_le_bytes());
Ok($n)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
if input.len() < $n {
return Err(ProgramError::InvalidAccountData);
}
let mut bytes = [0u8; $n];
bytes.copy_from_slice(&input[..$n]);
Ok((Self::from_le_bytes(bytes), $n))
}
}
)+
};
}
tail_codec_int! {
u16: 2, u32: 4, u64: 8, u128: 16,
i16: 2, i32: 4, i64: 8, i128: 16,
}
impl TailCodec for bool {
const MAX_ENCODED_LEN: usize = 1;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.is_empty() {
return Err(ProgramError::AccountDataTooSmall);
}
out[0] = if *self { 1 } else { 0 };
Ok(1)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
match input.first().copied() {
Some(0) => Ok((false, 1)),
Some(1) => Ok((true, 1)),
_ => Err(ProgramError::InvalidAccountData),
}
}
}
impl<const N: usize> TailCodec for [u8; N] {
const MAX_ENCODED_LEN: usize = N;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.len() < N {
return Err(ProgramError::AccountDataTooSmall);
}
out[..N].copy_from_slice(self);
Ok(N)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
if input.len() < N {
return Err(ProgramError::InvalidAccountData);
}
let mut out = [0u8; N];
out.copy_from_slice(&input[..N]);
Ok((out, N))
}
}
impl<T: TailCodec> TailCodec for Option<T> {
const MAX_ENCODED_LEN: usize = 1 + T::MAX_ENCODED_LEN;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.is_empty() {
return Err(ProgramError::AccountDataTooSmall);
}
match self {
None => {
out[0] = 0;
Ok(1)
}
Some(inner) => {
out[0] = 1;
let written = inner.encode(&mut out[1..])?;
Ok(1 + written)
}
}
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
match input.first().copied() {
Some(0) => Ok((None, 1)),
Some(1) => {
let (inner, n) = T::decode(&input[1..])?;
Ok((Some(inner), 1 + n))
}
_ => Err(ProgramError::InvalidAccountData),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct BoundedString<const N: usize> {
len: u16,
bytes: [u8; N],
}
impl<const N: usize> BoundedString<N> {
#[inline]
pub const fn empty() -> Self {
Self {
len: 0,
bytes: [0u8; N],
}
}
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn from_str(value: &str) -> Result<Self, ProgramError> {
Self::from_bytes(value.as_bytes())
}
#[inline]
pub fn from_bytes(value: &[u8]) -> Result<Self, ProgramError> {
if value.len() > N || value.len() > u16::MAX as usize {
return Err(ProgramError::InvalidInstructionData);
}
let mut out = Self::empty();
out.bytes[..value.len()].copy_from_slice(value);
out.len = value.len() as u16;
Ok(out)
}
#[inline]
pub fn set_str(&mut self, value: &str) -> Result<(), ProgramError> {
self.set_bytes(value.as_bytes())
}
#[inline]
pub fn set_bytes(&mut self, value: &[u8]) -> Result<(), ProgramError> {
if value.len() > N || value.len() > u16::MAX as usize {
return Err(ProgramError::InvalidInstructionData);
}
self.bytes = [0u8; N];
self.bytes[..value.len()].copy_from_slice(value);
self.len = value.len() as u16;
Ok(())
}
#[inline]
pub fn clear(&mut self) {
self.bytes = [0u8; N];
self.len = 0;
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub fn as_str(&self) -> Result<&str, ProgramError> {
core::str::from_utf8(self.as_bytes()).map_err(|_| ProgramError::InvalidAccountData)
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline(always)]
pub const fn capacity(&self) -> usize {
N
}
#[inline(always)]
pub const fn remaining_capacity(&self) -> usize {
N - self.len as usize
}
#[inline(always)]
pub const fn is_full(&self) -> bool {
self.len as usize == N
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<const N: usize> Default for BoundedString<N> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl<const N: usize> TailCodec for BoundedString<N> {
const MAX_ENCODED_LEN: usize = 2 + N;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
let len = self.len as usize;
if len > N || out.len() < 2 + len {
return Err(ProgramError::AccountDataTooSmall);
}
out[..2].copy_from_slice(&self.len.to_le_bytes());
out[2..2 + len].copy_from_slice(&self.bytes[..len]);
Ok(2 + len)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
if input.len() < 2 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([input[0], input[1]]) as usize;
if len > N || input.len() < 2 + len {
return Err(ProgramError::InvalidAccountData);
}
let mut out = Self::empty();
out.len = len as u16;
out.bytes[..len].copy_from_slice(&input[2..2 + len]);
Ok((out, 2 + len))
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct BoundedVec<T, const N: usize>
where
T: TailCodec + Copy + Default,
{
len: u16,
items: [T; N],
}
impl<T, const N: usize> BoundedVec<T, N>
where
T: TailCodec + Copy + Default,
{
#[inline]
pub fn empty() -> Self {
Self {
len: 0,
items: [T::default(); N],
}
}
#[inline]
pub fn from_slice(values: &[T]) -> Result<Self, ProgramError> {
if values.len() > N || values.len() > u16::MAX as usize {
return Err(ProgramError::InvalidInstructionData);
}
let mut out = Self::empty();
out.items[..values.len()].copy_from_slice(values);
out.len = values.len() as u16;
Ok(out)
}
#[inline]
pub fn push(&mut self, item: T) -> Result<(), ProgramError> {
let len = self.len as usize;
if len >= N || len >= u16::MAX as usize {
return Err(ProgramError::AccountDataTooSmall);
}
self.items[len] = item;
self.len += 1;
Ok(())
}
#[inline]
pub fn pop(&mut self) -> Option<T> {
let len = self.len as usize;
if len == 0 {
return None;
}
let new_len = len - 1;
let item = self.items[new_len];
self.items[new_len] = T::default();
self.len = new_len as u16;
Some(item)
}
#[inline]
pub fn clear(&mut self) {
let len = self.len as usize;
let mut i = 0;
while i < len {
self.items[i] = T::default();
i += 1;
}
self.len = 0;
}
#[inline(always)]
pub fn as_slice(&self) -> &[T] {
&self.items[..self.len as usize]
}
#[inline(always)]
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.items[..self.len as usize]
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline(always)]
pub const fn capacity(&self) -> usize {
N
}
#[inline(always)]
pub const fn remaining_capacity(&self) -> usize {
N - self.len as usize
}
#[inline(always)]
pub const fn is_full(&self) -> bool {
self.len as usize == N
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T, const N: usize> BoundedVec<T, N>
where
T: TailCodec + Copy + Default + PartialEq,
{
#[inline]
pub fn contains(&self, item: &T) -> bool {
self.as_slice().iter().any(|candidate| candidate == item)
}
#[inline]
pub fn push_unique(&mut self, item: T) -> Result<bool, ProgramError> {
if self.contains(&item) {
return Ok(false);
}
self.push(item)?;
Ok(true)
}
#[inline]
pub fn remove_first(&mut self, item: &T) -> bool {
let len = self.len as usize;
let mut found = None;
let mut i = 0;
while i < len {
if &self.items[i] == item {
found = Some(i);
break;
}
i += 1;
}
let Some(index) = found else {
return false;
};
let mut j = index;
while j + 1 < len {
self.items[j] = self.items[j + 1];
j += 1;
}
self.items[len - 1] = T::default();
self.len = (len - 1) as u16;
true
}
}
pub type HopperString<const N: usize> = BoundedString<N>;
pub type HopperVec<T, const N: usize> = BoundedVec<T, N>;
impl<T, const N: usize> Default for BoundedVec<T, N>
where
T: TailCodec + Copy + Default,
{
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl<T, const N: usize> TailCodec for BoundedVec<T, N>
where
T: TailCodec + Copy + Default,
{
const MAX_ENCODED_LEN: usize = 2 + (N * T::MAX_ENCODED_LEN);
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
let len = self.len as usize;
if len > N || out.len() < 2 {
return Err(ProgramError::AccountDataTooSmall);
}
out[..2].copy_from_slice(&self.len.to_le_bytes());
let mut cursor = 2;
for item in self.as_slice() {
let written = item.encode(&mut out[cursor..])?;
cursor = cursor
.checked_add(written)
.ok_or(ProgramError::AccountDataTooSmall)?;
}
Ok(cursor)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
if input.len() < 2 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([input[0], input[1]]) as usize;
if len > N {
return Err(ProgramError::InvalidAccountData);
}
let mut out = Self::empty();
let mut cursor = 2;
let mut i = 0;
while i < len {
let (item, consumed) = T::decode(&input[cursor..])?;
out.items[i] = item;
cursor = cursor
.checked_add(consumed)
.ok_or(ProgramError::InvalidAccountData)?;
i += 1;
}
out.len = len as u16;
Ok((out, cursor))
}
}
impl TailCodec for crate::address::Address {
const MAX_ENCODED_LEN: usize = 32;
#[inline]
fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
if out.len() < 32 {
return Err(ProgramError::AccountDataTooSmall);
}
out[..32].copy_from_slice(self.as_array());
Ok(32)
}
#[inline]
fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
if input.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&input[..32]);
Ok((crate::address::Address::new(bytes), 32))
}
}
pub const SEQ_LEN_PREFIX: usize = 4;
pub trait SeqElement: TailElement {
const STRIDE: usize;
}
macro_rules! seq_element_fixed {
( $( $ty:ty ),+ $(,)? ) => {
$(
impl SeqElement for $ty {
const STRIDE: usize = <$ty as TailCodec>::MAX_ENCODED_LEN;
}
)+
};
}
seq_element_fixed!(u8, u16, u32, u64, u128, i16, i32, i64, i128, bool);
impl SeqElement for crate::address::Address {
const STRIDE: usize = 32;
}
#[inline(always)]
pub const fn seq_capacity_for<T: SeqElement>(region_len: usize) -> usize {
if region_len < SEQ_LEN_PREFIX {
return 0;
}
(region_len - SEQ_LEN_PREFIX) / T::STRIDE
}
#[inline(always)]
pub const fn seq_region_bytes_for<T: SeqElement>(n: usize) -> usize {
SEQ_LEN_PREFIX + n * T::STRIDE
}
#[derive(Clone, Copy)]
pub struct TailSeq<'a, T: SeqElement> {
region: &'a [u8],
_marker: PhantomData<T>,
}
impl<'a, T: SeqElement> TailSeq<'a, T> {
#[inline]
pub fn from_region(region: &'a [u8]) -> Result<Self, ProgramError> {
if region.len() < SEQ_LEN_PREFIX {
return Err(ProgramError::AccountDataTooSmall);
}
let this = Self {
region,
_marker: PhantomData,
};
if this.len() as usize > this.capacity() {
return Err(ProgramError::InvalidAccountData);
}
Ok(this)
}
#[inline]
pub fn len(&self) -> u32 {
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&self.region[..SEQ_LEN_PREFIX]);
u32::from_le_bytes(bytes)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn capacity(&self) -> usize {
seq_capacity_for::<T>(self.region.len())
}
#[inline]
pub fn get(&self, index: usize) -> Result<T, ProgramError> {
if index >= self.len() as usize {
return Err(ProgramError::InvalidArgument);
}
let start = SEQ_LEN_PREFIX + index * T::STRIDE;
let end = start + T::STRIDE;
let bytes = self
.region
.get(start..end)
.ok_or(ProgramError::InvalidAccountData)?;
let (value, _consumed) = T::decode(bytes)?;
Ok(value)
}
#[inline]
pub fn iter(&self) -> TailSeqIter<'a, T> {
TailSeqIter {
region: self.region,
len: self.len() as usize,
pos: 0,
_marker: PhantomData,
}
}
}
pub struct TailSeqIter<'a, T: SeqElement> {
region: &'a [u8],
len: usize,
pos: usize,
_marker: PhantomData<T>,
}
impl<T: SeqElement> Iterator for TailSeqIter<'_, T> {
type Item = Result<T, ProgramError>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.len {
return None;
}
let start = SEQ_LEN_PREFIX + self.pos * T::STRIDE;
let end = start + T::STRIDE;
self.pos += 1;
let item = match self.region.get(start..end) {
Some(bytes) => T::decode(bytes).map(|(value, _)| value),
None => Err(ProgramError::InvalidAccountData),
};
Some(item)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len - self.pos;
(remaining, Some(remaining))
}
}
pub struct TailSeqMut<'a, T: SeqElement> {
region: &'a mut [u8],
_marker: PhantomData<T>,
}
impl<'a, T: SeqElement> TailSeqMut<'a, T> {
#[inline]
pub fn from_region(region: &'a mut [u8]) -> Result<Self, ProgramError> {
if region.len() < SEQ_LEN_PREFIX {
return Err(ProgramError::AccountDataTooSmall);
}
let this = Self {
region,
_marker: PhantomData,
};
if this.len() as usize > this.capacity() {
return Err(ProgramError::InvalidAccountData);
}
Ok(this)
}
#[inline]
pub fn len(&self) -> u32 {
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&self.region[..SEQ_LEN_PREFIX]);
u32::from_le_bytes(bytes)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn capacity(&self) -> usize {
seq_capacity_for::<T>(self.region.len())
}
#[inline]
pub fn is_full(&self) -> bool {
self.len() as usize >= self.capacity()
}
#[inline]
pub fn remaining_capacity(&self) -> usize {
self.capacity().saturating_sub(self.len() as usize)
}
#[inline]
pub fn get(&self, index: usize) -> Result<T, ProgramError> {
if index >= self.len() as usize {
return Err(ProgramError::InvalidArgument);
}
let start = SEQ_LEN_PREFIX + index * T::STRIDE;
let end = start + T::STRIDE;
let bytes = self
.region
.get(start..end)
.ok_or(ProgramError::InvalidAccountData)?;
let (value, _consumed) = T::decode(bytes)?;
Ok(value)
}
#[inline]
pub fn set(&mut self, index: usize, value: T) -> Result<(), ProgramError> {
if index >= self.len() as usize {
return Err(ProgramError::InvalidArgument);
}
let start = SEQ_LEN_PREFIX + index * T::STRIDE;
let end = start + T::STRIDE;
let slot = self
.region
.get_mut(start..end)
.ok_or(ProgramError::AccountDataTooSmall)?;
let _ = value.encode(slot)?;
Ok(())
}
#[inline]
pub fn push(&mut self, value: T) -> Result<(), ProgramError> {
let len = self.len() as usize;
if len >= self.capacity() {
return Err(ProgramError::AccountDataTooSmall);
}
let start = SEQ_LEN_PREFIX + len * T::STRIDE;
let end = start + T::STRIDE;
let slot = self
.region
.get_mut(start..end)
.ok_or(ProgramError::AccountDataTooSmall)?;
let _ = value.encode(slot)?;
let new_len = (len as u32) + 1;
self.region[..SEQ_LEN_PREFIX].copy_from_slice(&new_len.to_le_bytes());
Ok(())
}
#[inline]
pub fn swap_remove(&mut self, index: usize) -> Result<T, ProgramError> {
let len = self.len() as usize;
if index >= len {
return Err(ProgramError::InvalidArgument);
}
let removed = self.get(index)?;
let last = len - 1;
let last_start = SEQ_LEN_PREFIX + last * T::STRIDE;
if index != last {
let idx_start = SEQ_LEN_PREFIX + index * T::STRIDE;
self.region
.copy_within(last_start..last_start + T::STRIDE, idx_start);
}
if let Some(tail) = self.region.get_mut(last_start..last_start + T::STRIDE) {
for byte in tail.iter_mut() {
*byte = 0;
}
}
self.region[..SEQ_LEN_PREFIX].copy_from_slice(&(last as u32).to_le_bytes());
Ok(removed)
}
#[inline]
pub fn as_seq(&self) -> TailSeq<'_, T> {
TailSeq {
region: self.region,
_marker: PhantomData,
}
}
}
pub struct SeqTailRead<'a, T: SeqElement> {
region: Ref<'a, [u8]>,
_lease: SegmentLease<'a>,
_marker: PhantomData<T>,
}
impl<'a, T: SeqElement> SeqTailRead<'a, T> {
#[doc(hidden)]
#[inline]
pub fn new(region: Ref<'a, [u8]>, lease: SegmentLease<'a>) -> Self {
Self {
region,
_lease: lease,
_marker: PhantomData,
}
}
#[inline]
pub fn seq(&self) -> Result<TailSeq<'_, T>, ProgramError> {
TailSeq::from_region(&self.region)
}
}
pub struct SeqTailWrite<'a, T: SeqElement> {
region: RefMut<'a, [u8]>,
_lease: SegmentLease<'a>,
_marker: PhantomData<T>,
}
impl<'a, T: SeqElement> SeqTailWrite<'a, T> {
#[doc(hidden)]
#[inline]
pub fn new(region: RefMut<'a, [u8]>, lease: SegmentLease<'a>) -> Self {
Self {
region,
_lease: lease,
_marker: PhantomData,
}
}
#[inline]
pub fn seq_mut(&mut self) -> Result<TailSeqMut<'_, T>, ProgramError> {
TailSeqMut::from_region(&mut self.region)
}
#[inline]
pub fn seq(&self) -> Result<TailSeq<'_, T>, ProgramError> {
TailSeq::from_region(&self.region)
}
}
#[inline]
pub fn read_tail_len(data: &[u8], body_end: usize) -> Result<u32, ProgramError> {
let end = body_end
.checked_add(4)
.ok_or(ProgramError::AccountDataTooSmall)?;
if data.len() < end {
return Err(ProgramError::AccountDataTooSmall);
}
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&data[body_end..end]);
Ok(u32::from_le_bytes(bytes))
}
#[inline]
pub fn tail_payload(data: &[u8], body_end: usize) -> Result<&[u8], ProgramError> {
let len = read_tail_len(data, body_end)? as usize;
let start = body_end + 4;
let end = start
.checked_add(len)
.ok_or(ProgramError::InvalidAccountData)?;
if data.len() < end {
return Err(ProgramError::InvalidAccountData);
}
Ok(&data[start..end])
}
#[inline]
pub fn tail_capacity(data: &[u8], body_end: usize) -> Result<usize, ProgramError> {
let start = body_end
.checked_add(4)
.ok_or(ProgramError::AccountDataTooSmall)?;
if data.len() < start {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(data.len() - start)
}
#[inline]
pub fn borrow_bounded_str<const N: usize>(input: &[u8]) -> Result<(&str, usize), ProgramError> {
if input.len() < 2 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([input[0], input[1]]) as usize;
if len > N || input.len() < 2 + len {
return Err(ProgramError::InvalidAccountData);
}
let bytes = &input[2..2 + len];
let value = core::str::from_utf8(bytes).map_err(|_| ProgramError::InvalidAccountData)?;
Ok((value, 2 + len))
}
#[inline]
pub fn borrow_address_slice<const N: usize>(
input: &[u8],
) -> Result<(&[crate::address::Address], usize), ProgramError> {
if input.len() < 2 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([input[0], input[1]]) as usize;
if len > N {
return Err(ProgramError::InvalidAccountData);
}
let byte_len = len
.checked_mul(32)
.ok_or(ProgramError::InvalidAccountData)?;
let end = 2usize
.checked_add(byte_len)
.ok_or(ProgramError::InvalidAccountData)?;
if input.len() < end {
return Err(ProgramError::InvalidAccountData);
}
let bytes = &input[2..end];
let ptr = bytes.as_ptr() as *const crate::address::Address;
let values = unsafe { core::slice::from_raw_parts(ptr, len) };
Ok((values, end))
}
#[inline]
pub fn read_tail<T: TailCodec>(data: &[u8], body_end: usize) -> Result<T, ProgramError> {
let payload = tail_payload(data, body_end)?;
let (value, consumed) = T::decode(payload)?;
if consumed != payload.len() {
return Err(ProgramError::InvalidAccountData);
}
Ok(value)
}
#[inline]
pub fn write_tail<T: TailCodec>(
data: &mut [u8],
body_end: usize,
tail: &T,
) -> Result<usize, ProgramError> {
let prefix_end = body_end
.checked_add(4)
.ok_or(ProgramError::AccountDataTooSmall)?;
if data.len() < prefix_end {
return Err(ProgramError::AccountDataTooSmall);
}
let written = tail.encode(&mut data[prefix_end..])?;
if written > u32::MAX as usize {
return Err(ProgramError::InvalidAccountData);
}
data[body_end..prefix_end].copy_from_slice(&(written as u32).to_le_bytes());
Ok(written)
}
#[inline]
pub fn write_tail_payload(
data: &mut [u8],
body_end: usize,
payload: &[u8],
) -> Result<usize, ProgramError> {
let prefix_end = body_end
.checked_add(4)
.ok_or(ProgramError::AccountDataTooSmall)?;
let payload_end = prefix_end
.checked_add(payload.len())
.ok_or(ProgramError::AccountDataTooSmall)?;
if data.len() < payload_end || payload.len() > u32::MAX as usize {
return Err(ProgramError::AccountDataTooSmall);
}
data[prefix_end..payload_end].copy_from_slice(payload);
data[body_end..prefix_end].copy_from_slice(&(payload.len() as u32).to_le_bytes());
Ok(payload.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn u32_roundtrip() {
let mut buf = [0u8; 8];
let n = 0xDEAD_BEEFu32.encode(&mut buf).unwrap();
assert_eq!(n, 4);
let (back, consumed) = u32::decode(&buf).unwrap();
assert_eq!(consumed, 4);
assert_eq!(back, 0xDEAD_BEEF);
}
#[test]
fn u64_roundtrip() {
let mut buf = [0u8; 8];
0x0123_4567_89AB_CDEFu64.encode(&mut buf).unwrap();
let (back, _) = u64::decode(&buf).unwrap();
assert_eq!(back, 0x0123_4567_89AB_CDEF);
}
#[test]
fn bool_encode_decode() {
let mut buf = [0u8; 1];
true.encode(&mut buf).unwrap();
assert_eq!(buf[0], 1);
assert_eq!(bool::decode(&buf).unwrap(), (true, 1));
false.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0);
assert_eq!(bool::decode(&buf).unwrap(), (false, 1));
}
#[test]
fn bool_rejects_garbage() {
let buf = [2u8];
assert!(bool::decode(&buf).is_err());
}
#[test]
fn byte_array_roundtrip() {
let src: [u8; 8] = *b"HOPPER!!";
let mut buf = [0u8; 16];
let n = src.encode(&mut buf).unwrap();
assert_eq!(n, 8);
let (back, consumed) = <[u8; 8]>::decode(&buf).unwrap();
assert_eq!(consumed, 8);
assert_eq!(back, src);
}
#[test]
fn option_none_encodes_to_one_byte() {
let mut buf = [0u8; 16];
let n = Option::<u64>::None.encode(&mut buf).unwrap();
assert_eq!(n, 1);
assert_eq!(buf[0], 0);
let (back, c) = <Option<u64>>::decode(&buf).unwrap();
assert_eq!(back, None);
assert_eq!(c, 1);
}
#[test]
fn option_some_includes_inner_payload() {
let mut buf = [0u8; 16];
let n = Option::<u64>::Some(0xAAAA_BBBB_CCCC_DDDD)
.encode(&mut buf)
.unwrap();
assert_eq!(n, 9);
assert_eq!(buf[0], 1);
let (back, c) = <Option<u64>>::decode(&buf).unwrap();
assert_eq!(back, Some(0xAAAA_BBBB_CCCC_DDDD));
assert_eq!(c, 9);
}
#[test]
fn option_rejects_invalid_tag() {
let buf = [7u8, 0, 0, 0, 0, 0, 0, 0, 0];
assert!(<Option<u64>>::decode(&buf).is_err());
}
#[test]
fn tail_length_prefix_roundtrip() {
let mut data = [0u8; 64];
let body_end = 24usize;
let tail_value: u64 = 0x1234_5678_9ABC_DEF0;
let written = write_tail(&mut data, body_end, &tail_value).unwrap();
assert_eq!(written, 8);
let read_len = read_tail_len(&data, body_end).unwrap();
assert_eq!(read_len, 8);
let back: u64 = read_tail::<u64>(&data, body_end).unwrap();
assert_eq!(back, tail_value);
}
#[test]
fn tail_decode_rejects_excess_payload() {
let mut data = [0u8; 32];
let body_end = 16usize;
data[body_end..body_end + 4].copy_from_slice(&8u32.to_le_bytes());
data[body_end + 4..body_end + 8].copy_from_slice(&0x1122_3344u32.to_le_bytes());
data[body_end + 8..body_end + 12].copy_from_slice(&0xFFu32.to_le_bytes());
let result = read_tail::<u32>(&data, body_end);
assert!(result.is_err());
}
#[test]
fn tail_bounds_check_on_short_buffer() {
let data = [0u8; 10];
assert!(read_tail_len(&data, 16).is_err());
assert!(tail_payload(&data, 16).is_err());
}
#[test]
fn max_encoded_len_matches_actual_encode_size() {
let mut buf = [0u8; 32];
assert_eq!(0u32.encode(&mut buf).unwrap(), u32::MAX_ENCODED_LEN);
assert_eq!(0u64.encode(&mut buf).unwrap(), u64::MAX_ENCODED_LEN);
assert_eq!(true.encode(&mut buf).unwrap(), bool::MAX_ENCODED_LEN);
assert_eq!(
[0u8; 7].encode(&mut buf).unwrap(),
<[u8; 7]>::MAX_ENCODED_LEN
);
assert_eq!(Option::<u32>::None.encode(&mut buf).unwrap(), 1);
assert_eq!(
Option::<u32>::Some(0).encode(&mut buf).unwrap(),
<Option<u32>>::MAX_ENCODED_LEN
);
}
#[test]
fn bounded_string_roundtrip() {
let label = BoundedString::<32>::from_str("multisig").unwrap();
let mut buf = [0u8; BoundedString::<32>::MAX_ENCODED_LEN];
let written = label.encode(&mut buf).unwrap();
assert_eq!(written, 10);
let (back, consumed) = BoundedString::<32>::decode(&buf).unwrap();
assert_eq!(consumed, written);
assert_eq!(back.as_str().unwrap(), "multisig");
}
#[test]
fn bounded_string_capacity_helpers() {
let mut label = HopperString::<8>::from_str("ops").unwrap();
assert_eq!(label.remaining_capacity(), 5);
assert!(!label.is_full());
label.set_str("12345678").unwrap();
assert!(label.is_full());
label.clear();
assert!(label.is_empty());
assert_eq!(label.as_bytes(), b"");
}
#[test]
fn bounded_vec_roundtrip() {
let mut vec = BoundedVec::<u64, 4>::empty();
vec.push(7).unwrap();
vec.push(9).unwrap();
let mut buf = [0u8; BoundedVec::<u64, 4>::MAX_ENCODED_LEN];
let written = vec.encode(&mut buf).unwrap();
assert_eq!(written, 18);
let (back, consumed) = BoundedVec::<u64, 4>::decode(&buf).unwrap();
assert_eq!(consumed, written);
assert_eq!(back.as_slice(), &[7, 9]);
}
#[test]
fn bounded_vec_set_helpers_preserve_order() {
let mut vec = HopperVec::<u64, 4>::empty();
assert_eq!(vec.remaining_capacity(), 4);
assert!(vec.push_unique(7).unwrap());
assert!(!vec.push_unique(7).unwrap());
vec.push(9).unwrap();
vec.push(11).unwrap();
assert!(vec.contains(&9));
assert!(vec.remove_first(&9));
assert_eq!(vec.as_slice(), &[7, 11]);
assert_eq!(vec.pop(), Some(11));
assert_eq!(vec.as_slice(), &[7]);
vec.clear();
assert!(vec.is_empty());
}
use crate::address::Address;
#[test]
fn seq_element_stride_matches_encoded_len() {
assert_eq!(<u8 as SeqElement>::STRIDE, 1);
assert_eq!(<u16 as SeqElement>::STRIDE, 2);
assert_eq!(<u32 as SeqElement>::STRIDE, 4);
assert_eq!(<u64 as SeqElement>::STRIDE, 8);
assert_eq!(<bool as SeqElement>::STRIDE, 1);
assert_eq!(<Address as SeqElement>::STRIDE, 32);
}
#[test]
fn seq_capacity_is_region_derived() {
assert_eq!(seq_capacity_for::<u64>(4 + 8 * 3), 3);
assert_eq!(seq_capacity_for::<u64>(4 + 8 * 3 + 5), 3); assert_eq!(seq_capacity_for::<u64>(4), 0);
assert_eq!(seq_capacity_for::<u64>(0), 0); assert_eq!(seq_region_bytes_for::<Address>(10), 4 + 32 * 10);
}
#[test]
fn seq_push_get_iter_roundtrip() {
let mut region = [0u8; 4 + 8 * 4];
let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
assert_eq!(seq.capacity(), 4);
assert_eq!(seq.len(), 0);
assert!(seq.is_empty());
assert_eq!(seq.remaining_capacity(), 4);
seq.push(10).unwrap();
seq.push(20).unwrap();
seq.push(30).unwrap();
assert_eq!(seq.len(), 3);
assert_eq!(seq.remaining_capacity(), 1);
assert_eq!(seq.get(0).unwrap(), 10);
assert_eq!(seq.get(2).unwrap(), 30);
assert!(seq.get(3).is_err());
seq.set(1, 99).unwrap();
assert_eq!(seq.get(1).unwrap(), 99);
assert!(seq.set(3, 1).is_err());
let read = TailSeq::<u64>::from_region(®ion).unwrap();
assert_eq!(read.len(), 3);
let collected: Result<std::vec::Vec<u64>, _> = read.iter().collect();
assert_eq!(collected.unwrap(), std::vec![10, 99, 30]);
}
#[test]
fn seq_push_at_capacity_is_account_data_too_small() {
let mut region = [0u8; 4 + 8 * 2]; let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
seq.push(1).unwrap();
seq.push(2).unwrap();
assert!(seq.is_full());
assert_eq!(seq.remaining_capacity(), 0);
assert_eq!(seq.push(3), Err(ProgramError::AccountDataTooSmall));
assert_eq!(seq.len(), 2);
assert_eq!(seq.get(0).unwrap(), 1);
assert_eq!(seq.get(1).unwrap(), 2);
}
#[test]
fn seq_swap_remove_moves_last_into_hole() {
let mut region = [0u8; 4 + 8 * 4];
let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
for v in [10u64, 20, 30, 40] {
seq.push(v).unwrap();
}
assert_eq!(seq.swap_remove(1).unwrap(), 20);
assert_eq!(seq.len(), 3);
assert_eq!(seq.get(0).unwrap(), 10);
assert_eq!(seq.get(1).unwrap(), 40);
assert_eq!(seq.get(2).unwrap(), 30);
assert_eq!(seq.swap_remove(2).unwrap(), 30);
assert_eq!(seq.len(), 2);
seq.push(50).unwrap();
assert_eq!(seq.get(2).unwrap(), 50);
assert!(seq.swap_remove(9).is_err());
}
#[test]
fn seq_of_addresses_roundtrips() {
let mut region = [0u8; 4 + 32 * 3];
let mut seq = TailSeqMut::<Address>::from_region(&mut region).unwrap();
let a = Address::new([7u8; 32]);
let b = Address::new([9u8; 32]);
seq.push(a).unwrap();
seq.push(b).unwrap();
assert_eq!(seq.get(0).unwrap(), a);
assert_eq!(seq.get(1).unwrap(), b);
assert_eq!(seq.capacity(), 3);
}
#[test]
fn seq_from_region_rejects_corrupt_count_and_tiny_region() {
let mut tiny = [0u8; 2];
assert!(TailSeqMut::<u64>::from_region(&mut tiny).is_err());
let mut region = [0u8; 4 + 8 * 2];
region[..4].copy_from_slice(&9999u32.to_le_bytes());
assert!(TailSeqMut::<u64>::from_region(&mut region).is_err());
assert!(TailSeq::<u64>::from_region(®ion).is_err());
}
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
fn bounded_string_decode_never_exceeds_capacity() {
let len: u16 = kani::any();
let mut input = [0u8; BoundedString::<4>::MAX_ENCODED_LEN];
input[..2].copy_from_slice(&len.to_le_bytes());
let result = BoundedString::<4>::decode(&input);
if len as usize > 4 {
assert!(result.is_err());
} else {
let (decoded, consumed) = result.unwrap();
assert!(decoded.len() <= decoded.capacity());
assert_eq!(consumed, 2 + decoded.len());
}
}
#[kani::proof]
fn bounded_vec_mutators_preserve_capacity() {
let values: [u8; 5] = kani::any();
let mut vec = BoundedVec::<u8, 4>::empty();
let _ = vec.push(values[0]);
let _ = vec.push(values[1]);
let _ = vec.push(values[2]);
let _ = vec.push(values[3]);
let fifth = vec.push(values[4]);
assert!(vec.len() <= vec.capacity());
assert!(fifth.is_err());
let _ = vec.pop();
assert!(vec.len() <= vec.capacity());
vec.clear();
assert_eq!(vec.len(), 0);
}
#[kani::proof]
fn tail_payload_bounds_checks_arbitrary_prefixes() {
let data: [u8; 16] = kani::any();
let body_end: usize = kani::any();
kani::assume(body_end < data.len());
let result = tail_payload(&data, body_end);
if let Ok(payload) = result {
assert!(payload.len() <= data.len());
}
}
}