Skip to main content

hopper_core/account/
cursor.rs

1//! Sequential read/write cursors for zero-copy instruction and account data.
2
3use hopper_runtime::error::ProgramError;
4
5/// Sequential read cursor over a byte slice.
6///
7/// Tracks a position and provides typed reads that advance it.
8/// Used for parsing instruction data and reading account fields sequentially.
9pub struct SliceCursor<'a> {
10    data: &'a [u8],
11    pos: usize,
12}
13
14impl<'a> SliceCursor<'a> {
15    /// Create a new cursor at position 0.
16    #[inline(always)]
17    pub const fn new(data: &'a [u8]) -> Self {
18        Self { data, pos: 0 }
19    }
20
21    /// Create a cursor from instruction data, validating minimum length.
22    #[inline(always)]
23    pub fn from_instruction(data: &'a [u8], min_len: usize) -> Result<Self, ProgramError> {
24        if data.len() < min_len {
25            return Err(ProgramError::InvalidInstructionData);
26        }
27        Ok(Self::new(data))
28    }
29
30    /// Current byte position.
31    #[inline(always)]
32    pub const fn position(&self) -> usize {
33        self.pos
34    }
35
36    /// Remaining bytes after current position.
37    #[inline(always)]
38    pub const fn remaining(&self) -> usize {
39        self.data.len() - self.pos
40    }
41
42    /// Reference to remaining data from current position.
43    #[inline(always)]
44    pub fn data_from_position(&self) -> &'a [u8] {
45        &self.data[self.pos..]
46    }
47
48    /// Skip `n` bytes forward.
49    #[inline(always)]
50    pub fn skip(&mut self, n: usize) -> Result<(), ProgramError> {
51        if self.remaining() < n {
52            return Err(ProgramError::InvalidAccountData);
53        }
54        self.pos += n;
55        Ok(())
56    }
57
58    /// Read a `u8` and advance.
59    #[inline(always)]
60    pub fn read_u8(&mut self) -> Result<u8, ProgramError> {
61        if self.remaining() < 1 {
62            return Err(ProgramError::InvalidAccountData);
63        }
64        let v = self.data[self.pos];
65        self.pos += 1;
66        Ok(v)
67    }
68
69    /// Read a little-endian `u16` and advance.
70    #[inline(always)]
71    pub fn read_u16(&mut self) -> Result<u16, ProgramError> {
72        if self.remaining() < 2 {
73            return Err(ProgramError::InvalidAccountData);
74        }
75        let v = u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]);
76        self.pos += 2;
77        Ok(v)
78    }
79
80    /// Read a little-endian `u32` and advance.
81    #[inline(always)]
82    pub fn read_u32(&mut self) -> Result<u32, ProgramError> {
83        if self.remaining() < 4 {
84            return Err(ProgramError::InvalidAccountData);
85        }
86        let v = u32::from_le_bytes([
87            self.data[self.pos],
88            self.data[self.pos + 1],
89            self.data[self.pos + 2],
90            self.data[self.pos + 3],
91        ]);
92        self.pos += 4;
93        Ok(v)
94    }
95
96    /// Read a little-endian `u64` and advance.
97    #[inline(always)]
98    pub fn read_u64(&mut self) -> Result<u64, ProgramError> {
99        if self.remaining() < 8 {
100            return Err(ProgramError::InvalidAccountData);
101        }
102        let v = u64::from_le_bytes([
103            self.data[self.pos],
104            self.data[self.pos + 1],
105            self.data[self.pos + 2],
106            self.data[self.pos + 3],
107            self.data[self.pos + 4],
108            self.data[self.pos + 5],
109            self.data[self.pos + 6],
110            self.data[self.pos + 7],
111        ]);
112        self.pos += 8;
113        Ok(v)
114    }
115
116    /// Read a little-endian `i64` and advance.
117    #[inline(always)]
118    pub fn read_i64(&mut self) -> Result<i64, ProgramError> {
119        self.read_u64().map(|v| v as i64)
120    }
121
122    /// Read a boolean (0 = false, non-zero = true) and advance.
123    #[inline(always)]
124    pub fn read_bool(&mut self) -> Result<bool, ProgramError> {
125        self.read_u8().map(|v| v != 0)
126    }
127
128    /// Read a 32-byte address and advance.
129    #[inline(always)]
130    pub fn read_address(&mut self) -> Result<&'a [u8; 32], ProgramError> {
131        if self.remaining() < 32 {
132            return Err(ProgramError::InvalidAccountData);
133        }
134        // SAFETY: We checked length. [u8; 32] has alignment 1.
135        let addr = unsafe { &*(self.data.as_ptr().add(self.pos) as *const [u8; 32]) };
136        self.pos += 32;
137        Ok(addr)
138    }
139
140    /// Read a fixed-size byte array and advance.
141    #[inline(always)]
142    pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], ProgramError> {
143        if self.remaining() < len {
144            return Err(ProgramError::InvalidAccountData);
145        }
146        let slice = &self.data[self.pos..self.pos + len];
147        self.pos += len;
148        Ok(slice)
149    }
150}
151
152/// Sequential write cursor over a mutable byte slice.
153pub struct DataWriter<'a> {
154    data: &'a mut [u8],
155    pos: usize,
156}
157
158impl<'a> DataWriter<'a> {
159    /// Create a new writer at position 0.
160    #[inline(always)]
161    pub fn new(data: &'a mut [u8]) -> Self {
162        Self { data, pos: 0 }
163    }
164
165    /// How many bytes have been written.
166    #[inline(always)]
167    pub const fn written(&self) -> usize {
168        self.pos
169    }
170
171    /// Remaining writable bytes.
172    #[inline(always)]
173    pub const fn remaining(&self) -> usize {
174        self.data.len() - self.pos
175    }
176
177    /// Write a `u8` and advance.
178    #[inline(always)]
179    pub fn write_u8(&mut self, v: u8) -> Result<(), ProgramError> {
180        if self.remaining() < 1 {
181            return Err(ProgramError::AccountDataTooSmall);
182        }
183        self.data[self.pos] = v;
184        self.pos += 1;
185        Ok(())
186    }
187
188    /// Write a little-endian `u16` and advance.
189    #[inline(always)]
190    pub fn write_u16(&mut self, v: u16) -> Result<(), ProgramError> {
191        if self.remaining() < 2 {
192            return Err(ProgramError::AccountDataTooSmall);
193        }
194        let bytes = v.to_le_bytes();
195        self.data[self.pos] = bytes[0];
196        self.data[self.pos + 1] = bytes[1];
197        self.pos += 2;
198        Ok(())
199    }
200
201    /// Write a little-endian `u32` and advance.
202    #[inline(always)]
203    pub fn write_u32(&mut self, v: u32) -> Result<(), ProgramError> {
204        if self.remaining() < 4 {
205            return Err(ProgramError::AccountDataTooSmall);
206        }
207        let bytes = v.to_le_bytes();
208        self.data[self.pos..self.pos + 4].copy_from_slice(&bytes);
209        self.pos += 4;
210        Ok(())
211    }
212
213    /// Write a little-endian `u64` and advance.
214    #[inline(always)]
215    pub fn write_u64(&mut self, v: u64) -> Result<(), ProgramError> {
216        if self.remaining() < 8 {
217            return Err(ProgramError::AccountDataTooSmall);
218        }
219        let bytes = v.to_le_bytes();
220        self.data[self.pos..self.pos + 8].copy_from_slice(&bytes);
221        self.pos += 8;
222        Ok(())
223    }
224
225    /// Write a boolean (normalized to 0x00 or 0x01) and advance.
226    #[inline(always)]
227    pub fn write_bool(&mut self, v: bool) -> Result<(), ProgramError> {
228        self.write_u8(v as u8)
229    }
230
231    /// Write a 32-byte address and advance.
232    #[inline(always)]
233    pub fn write_address(&mut self, addr: &[u8; 32]) -> Result<(), ProgramError> {
234        if self.remaining() < 32 {
235            return Err(ProgramError::AccountDataTooSmall);
236        }
237        self.data[self.pos..self.pos + 32].copy_from_slice(addr);
238        self.pos += 32;
239        Ok(())
240    }
241
242    /// Write raw bytes and advance.
243    #[inline(always)]
244    pub fn write_bytes(&mut self, src: &[u8]) -> Result<(), ProgramError> {
245        if self.remaining() < src.len() {
246            return Err(ProgramError::AccountDataTooSmall);
247        }
248        self.data[self.pos..self.pos + src.len()].copy_from_slice(src);
249        self.pos += src.len();
250        Ok(())
251    }
252}