Skip to main content

jiminy_core/account/
cursor.rs

1//! Zero-copy read/write cursors over byte slices.
2//!
3//! [`SliceCursor`] for sequential reads, [`DataWriter`] for sequential writes.
4//! Both are position-tracked and bounds-checked - you get
5//! `AccountDataTooSmall` instead of a panic if you read/write past the end.
6//!
7//! Typed methods are generated by internal `impl_cursor_read!` /
8//! `impl_cursor_write!` macros. Same API, way less copy-paste.
9
10use pinocchio::{error::ProgramError, Address};
11
12// ── Internal code-gen macros ─────────────────────────────────────────────────
13
14/// Generate `read_$name` methods on SliceCursor for LE integer types.
15macro_rules! impl_cursor_read {
16    ($( $name:ident -> $ty:ty, $size:literal; )*) => {
17        $(
18            #[inline(always)]
19            pub fn $name(&mut self) -> Result<$ty, ProgramError> {
20                let end = self.pos + $size;
21                if end > self.data.len() {
22                    return Err(ProgramError::AccountDataTooSmall);
23                }
24                let val = <$ty>::from_le_bytes(self.data[self.pos..end].try_into().unwrap());
25                self.pos = end;
26                Ok(val)
27            }
28        )*
29    };
30}
31
32/// Generate `write_$name` methods on DataWriter for LE integer types.
33macro_rules! impl_cursor_write {
34    ($( $name:ident ($ty:ty), $size:literal; )*) => {
35        $(
36            #[inline(always)]
37            pub fn $name(&mut self, val: $ty) -> Result<(), ProgramError> {
38                let end = self.pos + $size;
39                if end > self.data.len() {
40                    return Err(ProgramError::AccountDataTooSmall);
41                }
42                self.data[self.pos..end].copy_from_slice(&val.to_le_bytes());
43                self.pos = end;
44                Ok(())
45            }
46        )*
47    };
48}
49
50// ── SliceCursor ──────────────────────────────────────────────────────────────
51
52/// Zero-copy read cursor over a byte slice.
53///
54/// Tracks the current position and reads typed fields sequentially.
55/// Every read is bounds-checked.
56///
57/// ```rust,ignore
58/// let data = account.try_borrow()?;
59/// let mut cur = SliceCursor::new(&data[1..]); // skip discriminator
60/// let balance   = cur.read_u64()?;
61/// let recipient = cur.read_address()?;
62/// let flags     = cur.read_u8()?;
63/// ```
64pub struct SliceCursor<'a> {
65    data: &'a [u8],
66    pos: usize,
67}
68
69impl<'a> SliceCursor<'a> {
70    #[inline(always)]
71    pub fn new(data: &'a [u8]) -> Self {
72        Self { data, pos: 0 }
73    }
74
75    /// Bytes remaining from the current position.
76    #[inline(always)]
77    pub fn remaining(&self) -> usize {
78        self.data.len().saturating_sub(self.pos)
79    }
80
81    /// Current byte offset into the slice.
82    #[inline(always)]
83    pub fn position(&self) -> usize {
84        self.pos
85    }
86
87    #[inline(always)]
88    pub fn read_u8(&mut self) -> Result<u8, ProgramError> {
89        if self.pos >= self.data.len() {
90            return Err(ProgramError::AccountDataTooSmall);
91        }
92        let val = self.data[self.pos];
93        self.pos += 1;
94        Ok(val)
95    }
96
97    // LE integer reads: generated by macro.
98    impl_cursor_read! {
99        read_u16  -> u16,  2;
100        read_u32  -> u32,  4;
101        read_u64  -> u64,  8;
102        read_u128 -> u128, 16;
103        read_i16  -> i16,  2;
104        read_i32  -> i32,  4;
105        read_i64  -> i64,  8;
106        read_i128 -> i128, 16;
107    }
108
109    /// `0` → `false`, anything else → `true`.
110    #[inline(always)]
111    pub fn read_bool(&mut self) -> Result<bool, ProgramError> {
112        Ok(self.read_u8()? != 0)
113    }
114
115    #[inline(always)]
116    pub fn read_i8(&mut self) -> Result<i8, ProgramError> {
117        Ok(self.read_u8()? as i8)
118    }
119
120    #[inline(always)]
121    pub fn read_address(&mut self) -> Result<Address, ProgramError> {
122        let end = self.pos + 32;
123        if end > self.data.len() {
124            return Err(ProgramError::AccountDataTooSmall);
125        }
126        let arr: [u8; 32] = self.data[self.pos..end].try_into().unwrap();
127        self.pos = end;
128        Ok(arr.into())
129    }
130
131    /// Skip `n` bytes without reading them.
132    #[inline(always)]
133    pub fn skip(&mut self, n: usize) -> Result<(), ProgramError> {
134        let end = self.pos.checked_add(n).ok_or(ProgramError::AccountDataTooSmall)?;
135        if end > self.data.len() {
136            return Err(ProgramError::AccountDataTooSmall);
137        }
138        self.pos = end;
139        Ok(())
140    }
141
142    /// Return the remaining unread portion of the slice from the current position.
143    #[inline(always)]
144    pub fn data_from_position(&self) -> &'a [u8] {
145        if self.pos >= self.data.len() {
146            &[]
147        } else {
148            &self.data[self.pos..]
149        }
150    }
151
152    /// Create a cursor for instruction data with minimum length validation.
153    #[inline(always)]
154    pub fn from_instruction(data: &'a [u8], min_len: usize) -> Result<Self, ProgramError> {
155        if data.len() < min_len {
156            return Err(ProgramError::InvalidInstructionData);
157        }
158        Ok(Self { data, pos: 0 })
159    }
160}
161
162// ── DataWriter ───────────────────────────────────────────────────────────────
163
164/// Zero-copy write cursor over a mutable byte slice.
165///
166/// Position-tracked and bounds-checked. Use this when initializing a new
167/// account's data layout.
168///
169/// ```rust,ignore
170/// let mut raw = account.try_borrow_mut()?;
171/// let mut w = DataWriter::new(&mut *raw);
172/// w.write_u8(MY_DISC)?;
173/// w.write_u64(0)?;
174/// w.write_address(&authority)?;
175/// ```
176pub struct DataWriter<'a> {
177    data: &'a mut [u8],
178    pos: usize,
179}
180
181impl<'a> DataWriter<'a> {
182    #[inline(always)]
183    pub fn new(data: &'a mut [u8]) -> Self {
184        Self { data, pos: 0 }
185    }
186
187    /// Number of bytes written so far.
188    #[inline(always)]
189    pub fn written(&self) -> usize {
190        self.pos
191    }
192
193    #[inline(always)]
194    pub fn write_u8(&mut self, val: u8) -> Result<(), ProgramError> {
195        if self.pos >= self.data.len() {
196            return Err(ProgramError::AccountDataTooSmall);
197        }
198        self.data[self.pos] = val;
199        self.pos += 1;
200        Ok(())
201    }
202
203    // LE integer writes: generated by macro.
204    impl_cursor_write! {
205        write_u16(u16),   2;
206        write_u32(u32),   4;
207        write_u64(u64),   8;
208        write_u128(u128), 16;
209        write_i16(i16),   2;
210        write_i32(i32),   4;
211        write_i64(i64),   8;
212        write_i128(i128), 16;
213    }
214
215    /// Writes `1u8` for `true`, `0u8` for `false`.
216    #[inline(always)]
217    pub fn write_bool(&mut self, val: bool) -> Result<(), ProgramError> {
218        self.write_u8(val as u8)
219    }
220
221    #[inline(always)]
222    pub fn write_i8(&mut self, val: i8) -> Result<(), ProgramError> {
223        self.write_u8(val as u8)
224    }
225
226    #[inline(always)]
227    pub fn write_address(&mut self, addr: &Address) -> Result<(), ProgramError> {
228        let end = self.pos + 32;
229        if end > self.data.len() {
230            return Err(ProgramError::AccountDataTooSmall);
231        }
232        self.data[self.pos..end].copy_from_slice(addr.as_array());
233        self.pos = end;
234        Ok(())
235    }
236}
237
238// ── Init helpers ─────────────────────────────────────────────────────────────
239
240/// Zero-fill `data` before writing any fields.
241///
242/// Call this immediately after allocating account space (via system program
243/// CPI) and before writing the discriminator or any other fields.
244#[inline(always)]
245pub fn zero_init(data: &mut [u8]) {
246    data.fill(0);
247}
248
249/// Write a discriminator byte to `data[0]`.
250#[inline(always)]
251pub fn write_discriminator(data: &mut [u8], discriminator: u8) -> Result<(), ProgramError> {
252    if data.is_empty() {
253        return Err(ProgramError::AccountDataTooSmall);
254    }
255    data[0] = discriminator;
256    Ok(())
257}