use hopper_runtime::error::ProgramError;
use hopper_runtime::ProgramResult;
pub struct BorshTape<'a> {
buf: &'a mut [u8],
pos: usize,
}
impl<'a> BorshTape<'a> {
#[inline]
pub fn new(buf: &'a mut [u8]) -> Self {
Self { buf, pos: 0 }
}
#[inline]
pub fn len(&self) -> usize {
self.pos
}
#[inline]
pub fn is_empty(&self) -> bool {
self.pos == 0
}
#[inline]
pub fn remaining(&self) -> usize {
self.buf.len() - self.pos
}
fn reserve(&mut self, n: usize) -> ProgramResult {
if self.pos.saturating_add(n) > self.buf.len() {
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
}
#[inline]
pub fn write_disc(&mut self, disc: u8) -> ProgramResult {
self.write_u8(disc)
}
#[inline]
pub fn write_u8(&mut self, value: u8) -> ProgramResult {
self.reserve(1)?;
self.buf[self.pos] = value;
self.pos += 1;
Ok(())
}
#[inline]
pub fn write_u16_le(&mut self, value: u16) -> ProgramResult {
self.reserve(2)?;
self.buf[self.pos..self.pos + 2].copy_from_slice(&value.to_le_bytes());
self.pos += 2;
Ok(())
}
#[inline]
pub fn write_u32_le(&mut self, value: u32) -> ProgramResult {
self.reserve(4)?;
self.buf[self.pos..self.pos + 4].copy_from_slice(&value.to_le_bytes());
self.pos += 4;
Ok(())
}
#[inline]
pub fn write_u64_le(&mut self, value: u64) -> ProgramResult {
self.reserve(8)?;
self.buf[self.pos..self.pos + 8].copy_from_slice(&value.to_le_bytes());
self.pos += 8;
Ok(())
}
#[inline]
pub fn write_bool(&mut self, value: bool) -> ProgramResult {
self.write_u8(if value { 1 } else { 0 })
}
#[inline]
pub fn write_str(&mut self, value: &str) -> ProgramResult {
let bytes = value.as_bytes();
self.write_u32_le(bytes.len() as u32)?;
self.reserve(bytes.len())?;
self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
self.pos += bytes.len();
Ok(())
}
#[inline]
pub fn write_option_none(&mut self) -> ProgramResult {
self.write_u8(0)
}
#[inline]
pub fn write_option_some_tag(&mut self) -> ProgramResult {
self.write_u8(1)
}
#[inline]
pub fn write_option_u64_le(&mut self, value: Option<u64>) -> ProgramResult {
match value {
None => self.write_option_none(),
Some(v) => {
self.write_option_some_tag()?;
self.write_u64_le(v)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_borsh_string_with_length_prefix() {
let mut buf = [0u8; 64];
let len = {
let mut tape = BorshTape::new(&mut buf);
tape.write_str("hi").unwrap();
tape.len()
};
assert_eq!(&buf[..6], &[2, 0, 0, 0, b'h', b'i']);
assert_eq!(len, 6);
}
#[test]
fn rejects_overflow() {
let mut buf = [0u8; 4];
let mut tape = BorshTape::new(&mut buf);
assert!(tape.write_str("hi").is_err());
}
#[test]
fn writes_option_some_u64_with_tag() {
let mut buf = [0u8; 16];
let mut tape = BorshTape::new(&mut buf);
tape.write_option_u64_le(Some(42)).unwrap();
assert_eq!(&buf[..9], &[1, 42, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn writes_option_none_u64_as_zero_byte() {
let mut buf = [0u8; 16];
let len = {
let mut tape = BorshTape::new(&mut buf);
tape.write_option_u64_le(None).unwrap();
tape.len()
};
assert_eq!(&buf[..1], &[0]);
assert_eq!(len, 1);
}
}