1use hopper_runtime::error::ProgramError;
35
36#[inline(always)]
38pub fn read_dynamic_u8(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
39 if offset >= data.len() {
40 return Err(ProgramError::AccountDataTooSmall);
41 }
42 let len = data[offset] as usize;
43 let data_start = offset + 1;
44 let data_end = data_start + len;
45 if data_end > data.len() {
46 return Err(ProgramError::AccountDataTooSmall);
47 }
48 Ok((&data[data_start..data_end], data_end))
49}
50
51#[inline(always)]
53pub fn read_dynamic_u16(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
54 if offset + 2 > data.len() {
55 return Err(ProgramError::AccountDataTooSmall);
56 }
57 let len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
58 let data_start = offset + 2;
59 let data_end = data_start + len;
60 if data_end > data.len() {
61 return Err(ProgramError::AccountDataTooSmall);
62 }
63 Ok((&data[data_start..data_end], data_end))
64}
65
66#[inline(always)]
68pub fn read_dynamic_u32(data: &[u8], offset: usize) -> Result<(&[u8], usize), ProgramError> {
69 if offset + 4 > data.len() {
70 return Err(ProgramError::AccountDataTooSmall);
71 }
72 let len = u32::from_le_bytes([
73 data[offset],
74 data[offset + 1],
75 data[offset + 2],
76 data[offset + 3],
77 ]) as usize;
78 let data_start = offset + 4;
79 let data_end = data_start + len;
80 if data_end > data.len() {
81 return Err(ProgramError::AccountDataTooSmall);
82 }
83 Ok((&data[data_start..data_end], data_end))
84}
85
86#[inline(always)]
88pub fn write_dynamic_u8(
89 data: &mut [u8],
90 offset: usize,
91 value: &[u8],
92 max_len: usize,
93) -> Result<usize, ProgramError> {
94 if value.len() > max_len || value.len() > 255 {
95 return Err(ProgramError::InvalidInstructionData);
96 }
97 let data_start = offset + 1;
98 let data_end = data_start + value.len();
99 if data_end > data.len() {
100 return Err(ProgramError::AccountDataTooSmall);
101 }
102 data[offset] = value.len() as u8;
103 data[data_start..data_end].copy_from_slice(value);
104 Ok(data_end)
105}
106
107#[inline(always)]
109pub fn write_dynamic_u16(
110 data: &mut [u8],
111 offset: usize,
112 value: &[u8],
113 max_len: usize,
114) -> Result<usize, ProgramError> {
115 if value.len() > max_len || value.len() > 65535 {
116 return Err(ProgramError::InvalidInstructionData);
117 }
118 let data_start = offset + 2;
119 let data_end = data_start + value.len();
120 if data_end > data.len() {
121 return Err(ProgramError::AccountDataTooSmall);
122 }
123 let len_bytes = (value.len() as u16).to_le_bytes();
124 data[offset] = len_bytes[0];
125 data[offset + 1] = len_bytes[1];
126 data[data_start..data_end].copy_from_slice(value);
127 Ok(data_end)
128}
129
130#[inline(always)]
132pub fn write_dynamic_u32(
133 data: &mut [u8],
134 offset: usize,
135 value: &[u8],
136 max_len: usize,
137) -> Result<usize, ProgramError> {
138 if value.len() > max_len {
139 return Err(ProgramError::InvalidInstructionData);
140 }
141 let data_start = offset + 4;
142 let data_end = data_start + value.len();
143 if data_end > data.len() {
144 return Err(ProgramError::AccountDataTooSmall);
145 }
146 let len_bytes = (value.len() as u32).to_le_bytes();
147 data[offset] = len_bytes[0];
148 data[offset + 1] = len_bytes[1];
149 data[offset + 2] = len_bytes[2];
150 data[offset + 3] = len_bytes[3];
151 data[data_start..data_end].copy_from_slice(value);
152 Ok(data_end)
153}
154
155pub struct DynamicView<'a, const N: usize> {
162 data: &'a [u8],
164 offsets: [u32; N],
167 lengths: [u32; N],
169}
170
171impl<'a, const N: usize> DynamicView<'a, N> {
172 #[inline]
176 pub fn parse(
177 data: &'a [u8],
178 base_offset: usize,
179 prefix_sizes: &[u8; N],
180 ) -> Result<Self, ProgramError> {
181 let mut offsets = [0u32; N];
182 let mut lengths = [0u32; N];
183 let mut cursor = base_offset;
184
185 let mut i = 0;
186 while i < N {
187 let prefix_size = prefix_sizes[i] as usize;
188 if cursor + prefix_size > data.len() {
189 return Err(ProgramError::AccountDataTooSmall);
190 }
191 let len = match prefix_size {
192 1 => data[cursor] as u32,
193 2 => u16::from_le_bytes([data[cursor], data[cursor + 1]]) as u32,
194 4 => u32::from_le_bytes([
195 data[cursor],
196 data[cursor + 1],
197 data[cursor + 2],
198 data[cursor + 3],
199 ]),
200 _ => return Err(ProgramError::InvalidInstructionData),
201 };
202 let data_start = cursor + prefix_size;
203 let data_end = data_start + len as usize;
204 if data_end > data.len() {
205 return Err(ProgramError::AccountDataTooSmall);
206 }
207 offsets[i] = data_start as u32;
208 lengths[i] = len;
209 cursor = data_end;
210 i += 1;
211 }
212
213 Ok(Self {
214 data,
215 offsets,
216 lengths,
217 })
218 }
219
220 #[inline(always)]
222 pub fn field(&self, index: usize) -> &[u8] {
223 let offset = self.offsets[index] as usize;
224 let len = self.lengths[index] as usize;
225 &self.data[offset..offset + len]
226 }
227
228 #[inline(always)]
230 pub fn field_len(&self, index: usize) -> usize {
231 self.lengths[index] as usize
232 }
233
234 #[inline]
236 pub fn field_as_str(&self, index: usize) -> Result<&str, ProgramError> {
237 core::str::from_utf8(self.field(index)).map_err(|_| ProgramError::InvalidAccountData)
238 }
239
240 #[inline]
242 pub fn total_dynamic_bytes(&self) -> usize {
243 if N == 0 {
244 return 0;
245 }
246 let last_offset = self.offsets[N - 1] as usize;
247 let last_len = self.lengths[N - 1] as usize;
248 last_offset + last_len
249 }
250}
251
252pub struct DynamicViewMut<'a, const N: usize> {
254 data: &'a mut [u8],
255 offsets: [u32; N],
256 lengths: [u32; N],
257}
258
259impl<'a, const N: usize> DynamicViewMut<'a, N> {
260 #[inline]
262 pub fn parse(
263 data: &'a mut [u8],
264 base_offset: usize,
265 prefix_sizes: &[u8; N],
266 ) -> Result<Self, ProgramError> {
267 let mut offsets = [0u32; N];
268 let mut lengths = [0u32; N];
269 let mut cursor = base_offset;
270
271 let mut i = 0;
272 while i < N {
273 let prefix_size = prefix_sizes[i] as usize;
274 if cursor + prefix_size > data.len() {
275 return Err(ProgramError::AccountDataTooSmall);
276 }
277 let len = match prefix_size {
278 1 => data[cursor] as u32,
279 2 => u16::from_le_bytes([data[cursor], data[cursor + 1]]) as u32,
280 4 => u32::from_le_bytes([
281 data[cursor],
282 data[cursor + 1],
283 data[cursor + 2],
284 data[cursor + 3],
285 ]),
286 _ => return Err(ProgramError::InvalidInstructionData),
287 };
288 let data_start = cursor + prefix_size;
289 let data_end = data_start + len as usize;
290 if data_end > data.len() {
291 return Err(ProgramError::AccountDataTooSmall);
292 }
293 offsets[i] = data_start as u32;
294 lengths[i] = len;
295 cursor = data_end;
296 i += 1;
297 }
298
299 Ok(Self {
300 data,
301 offsets,
302 lengths,
303 })
304 }
305
306 #[inline(always)]
308 pub fn field(&self, index: usize) -> &[u8] {
309 let offset = self.offsets[index] as usize;
310 let len = self.lengths[index] as usize;
311 &self.data[offset..offset + len]
312 }
313
314 #[inline(always)]
316 pub fn field_len(&self, index: usize) -> usize {
317 self.lengths[index] as usize
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn dynamic_u8_roundtrip() {
327 let mut buf = [0u8; 64];
328 let next = write_dynamic_u8(&mut buf, 0, b"hello", 32).unwrap();
329 assert_eq!(next, 6); let (data, next2) = read_dynamic_u8(&buf, 0).unwrap();
331 assert_eq!(data, b"hello");
332 assert_eq!(next2, 6);
333 }
334
335 #[test]
336 fn dynamic_u16_roundtrip() {
337 let mut buf = [0u8; 64];
338 let next = write_dynamic_u16(&mut buf, 0, b"world!", 32).unwrap();
339 assert_eq!(next, 8); let (data, next2) = read_dynamic_u16(&buf, 0).unwrap();
341 assert_eq!(data, b"world!");
342 assert_eq!(next2, 8);
343 }
344
345 #[test]
346 fn dynamic_view_parse_and_access() {
347 let mut buf = [0u8; 128];
348 let off = write_dynamic_u8(&mut buf, 0, b"alice", 32).unwrap();
350 let _off2 = write_dynamic_u8(&mut buf, off, b"this is a bio", 128).unwrap();
351
352 let view = DynamicView::<2>::parse(&buf, 0, &[1, 1]).unwrap();
353 assert_eq!(view.field(0), b"alice");
354 assert_eq!(view.field(1), b"this is a bio");
355 assert_eq!(view.field_as_str(0).unwrap(), "alice");
356 }
357
358 #[test]
359 fn dynamic_view_mixed_prefixes() {
360 let mut buf = [0u8; 128];
361 let off1 = write_dynamic_u8(&mut buf, 0, b"hi", 32).unwrap();
363 let _off2 = write_dynamic_u16(&mut buf, off1, b"longer data here", 256).unwrap();
364
365 let view = DynamicView::<2>::parse(&buf, 0, &[1, 2]).unwrap();
366 assert_eq!(view.field(0), b"hi");
367 assert_eq!(view.field(1), b"longer data here");
368 }
369}