use hopper_runtime::error::ProgramError;
#[inline(always)]
pub fn read_dynamic_u8(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
if offset >= data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len = data[offset] as usize;
let data_start = offset + 1;
let data_end = data_start + len;
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok((&data[data_start..data_end], data_end))
}
#[inline(always)]
pub fn read_dynamic_u16(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
if offset + 2 > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
let data_start = offset + 2;
let data_end = data_start + len;
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok((&data[data_start..data_end], data_end))
}
#[inline(always)]
pub fn read_dynamic_u32(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
if offset + 4 > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]) as usize;
let data_start = offset + 4;
let data_end = data_start + len;
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok((&data[data_start..data_end], data_end))
}
#[inline(always)]
pub fn write_dynamic_u8(
data: &mut [u8],
offset: usize,
value: &[u8],
max_len: usize,
) -> Result<usize, ProgramError> {
if value.len() > max_len || value.len() > 255 {
return Err(ProgramError::InvalidInstructionData);
}
let data_start = offset + 1;
let data_end = data_start + value.len();
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
data[offset] = value.len() as u8;
data[data_start..data_end].copy_from_slice(value);
Ok(data_end)
}
#[inline(always)]
pub fn write_dynamic_u16(
data: &mut [u8],
offset: usize,
value: &[u8],
max_len: usize,
) -> Result<usize, ProgramError> {
if value.len() > max_len || value.len() > 65535 {
return Err(ProgramError::InvalidInstructionData);
}
let data_start = offset + 2;
let data_end = data_start + value.len();
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len_bytes = (value.len() as u16).to_le_bytes();
data[offset] = len_bytes[0];
data[offset + 1] = len_bytes[1];
data[data_start..data_end].copy_from_slice(value);
Ok(data_end)
}
#[inline(always)]
pub fn write_dynamic_u32(
data: &mut [u8],
offset: usize,
value: &[u8],
max_len: usize,
) -> Result<usize, ProgramError> {
if value.len() > max_len {
return Err(ProgramError::InvalidInstructionData);
}
let data_start = offset + 4;
let data_end = data_start + value.len();
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len_bytes = (value.len() as u32).to_le_bytes();
data[offset] = len_bytes[0];
data[offset + 1] = len_bytes[1];
data[offset + 2] = len_bytes[2];
data[offset + 3] = len_bytes[3];
data[data_start..data_end].copy_from_slice(value);
Ok(data_end)
}
pub struct DynamicView<'a, const N: usize> {
data: &'a [u8],
offsets: [u32; N],
lengths: [u32; N],
}
impl<'a, const N: usize> DynamicView<'a, N> {
#[inline]
pub fn parse(
data: &'a [u8],
base_offset: usize,
prefix_sizes: &[u8; N],
) -> Result<Self, ProgramError> {
let mut offsets = [0u32; N];
let mut lengths = [0u32; N];
let mut cursor = base_offset;
let mut i = 0;
while i < N {
let prefix_size = prefix_sizes[i] as usize;
if cursor + prefix_size > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len = match prefix_size {
1 => data[cursor] as u32,
2 => u16::from_le_bytes([data[cursor], data[cursor + 1]]) as u32,
4 => u32::from_le_bytes([
data[cursor],
data[cursor + 1],
data[cursor + 2],
data[cursor + 3],
]),
_ => return Err(ProgramError::InvalidInstructionData),
};
let data_start = cursor + prefix_size;
let data_end = data_start + len as usize;
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
offsets[i] = data_start as u32;
lengths[i] = len;
cursor = data_end;
i += 1;
}
Ok(Self {
data,
offsets,
lengths,
})
}
#[inline(always)]
pub fn field(&self, index: usize) -> &[u8] {
let offset = self.offsets[index] as usize;
let len = self.lengths[index] as usize;
&self.data[offset..offset + len]
}
#[inline(always)]
pub fn field_len(&self, index: usize) -> usize {
self.lengths[index] as usize
}
#[inline]
pub fn field_as_str(&self, index: usize) -> Result<&str, ProgramError> {
core::str::from_utf8(self.field(index)).map_err(|_| ProgramError::InvalidAccountData)
}
#[inline]
pub fn total_dynamic_bytes(&self) -> usize {
if N == 0 {
return 0;
}
let last_offset = self.offsets[N - 1] as usize;
let last_len = self.lengths[N - 1] as usize;
last_offset + last_len
}
}
pub struct DynamicViewMut<'a, const N: usize> {
data: &'a mut [u8],
offsets: [u32; N],
lengths: [u32; N],
}
impl<'a, const N: usize> DynamicViewMut<'a, N> {
#[inline]
pub fn parse(
data: &'a mut [u8],
base_offset: usize,
prefix_sizes: &[u8; N],
) -> Result<Self, ProgramError> {
let mut offsets = [0u32; N];
let mut lengths = [0u32; N];
let mut cursor = base_offset;
let mut i = 0;
while i < N {
let prefix_size = prefix_sizes[i] as usize;
if cursor + prefix_size > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let len = match prefix_size {
1 => data[cursor] as u32,
2 => u16::from_le_bytes([data[cursor], data[cursor + 1]]) as u32,
4 => u32::from_le_bytes([
data[cursor],
data[cursor + 1],
data[cursor + 2],
data[cursor + 3],
]),
_ => return Err(ProgramError::InvalidInstructionData),
};
let data_start = cursor + prefix_size;
let data_end = data_start + len as usize;
if data_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
offsets[i] = data_start as u32;
lengths[i] = len;
cursor = data_end;
i += 1;
}
Ok(Self {
data,
offsets,
lengths,
})
}
#[inline(always)]
pub fn field(&self, index: usize) -> &[u8] {
let offset = self.offsets[index] as usize;
let len = self.lengths[index] as usize;
&self.data[offset..offset + len]
}
#[inline(always)]
pub fn field_len(&self, index: usize) -> usize {
self.lengths[index] as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dynamic_u8_roundtrip() {
let mut buf = [0u8; 64];
let next = write_dynamic_u8(&mut buf, 0, b"hello", 32).unwrap();
assert_eq!(next, 6); let (data, next2) = read_dynamic_u8(&buf, 0).unwrap();
assert_eq!(data, b"hello");
assert_eq!(next2, 6);
}
#[test]
fn dynamic_u16_roundtrip() {
let mut buf = [0u8; 64];
let next = write_dynamic_u16(&mut buf, 0, b"world!", 32).unwrap();
assert_eq!(next, 8); let (data, next2) = read_dynamic_u16(&buf, 0).unwrap();
assert_eq!(data, b"world!");
assert_eq!(next2, 8);
}
#[test]
fn dynamic_view_parse_and_access() {
let mut buf = [0u8; 128];
let off = write_dynamic_u8(&mut buf, 0, b"alice", 32).unwrap();
let _off2 = write_dynamic_u8(&mut buf, off, b"this is a bio", 128).unwrap();
let view = DynamicView::<2>::parse(&buf, 0, &[1, 1]).unwrap();
assert_eq!(view.field(0), b"alice");
assert_eq!(view.field(1), b"this is a bio");
assert_eq!(view.field_as_str(0).unwrap(), "alice");
}
#[test]
fn dynamic_view_mixed_prefixes() {
let mut buf = [0u8; 128];
let off1 = write_dynamic_u8(&mut buf, 0, b"hi", 32).unwrap();
let _off2 = write_dynamic_u16(&mut buf, off1, b"longer data here", 256).unwrap();
let view = DynamicView::<2>::parse(&buf, 0, &[1, 2]).unwrap();
assert_eq!(view.field(0), b"hi");
assert_eq!(view.field(1), b"longer data here");
}
}