Skip to main content

hopper_core/abi/
field_ref.rs

1//! Typed, borrow-split field references for non-overlapping access to account data.
2//!
3//! `FieldRef` and `FieldMut` hold independent subslices of account data,
4//! allowing simultaneous immutable (or individually mutable) access to
5//! different fields without violating Rust's aliasing rules.
6
7use hopper_runtime::error::ProgramError;
8
9/// Immutable typed view over a field's bytes.
10#[derive(Clone, Copy)]
11pub struct FieldRef<'a> {
12    data: &'a [u8],
13}
14
15impl<'a> FieldRef<'a> {
16    /// Create a field reference over the given byte slice.
17    #[inline(always)]
18    pub const fn new(data: &'a [u8]) -> Self {
19        Self { data }
20    }
21
22    /// Raw bytes of this field.
23    #[inline(always)]
24    pub const fn as_bytes(&self) -> &[u8] {
25        self.data
26    }
27
28    /// Read a `u8` from offset 0.
29    #[inline(always)]
30    pub fn read_u8(&self) -> Result<u8, ProgramError> {
31        self.data
32            .first()
33            .copied()
34            .ok_or(ProgramError::InvalidAccountData)
35    }
36
37    /// Read a little-endian `u16` from offset 0.
38    #[inline(always)]
39    pub fn read_u16(&self) -> Result<u16, ProgramError> {
40        if self.data.len() < 2 {
41            return Err(ProgramError::InvalidAccountData);
42        }
43        Ok(u16::from_le_bytes([self.data[0], self.data[1]]))
44    }
45
46    /// Read a little-endian `u32` from offset 0.
47    #[inline(always)]
48    pub fn read_u32(&self) -> Result<u32, ProgramError> {
49        if self.data.len() < 4 {
50            return Err(ProgramError::InvalidAccountData);
51        }
52        Ok(u32::from_le_bytes([
53            self.data[0],
54            self.data[1],
55            self.data[2],
56            self.data[3],
57        ]))
58    }
59
60    /// Read a little-endian `u64` from offset 0.
61    #[inline(always)]
62    pub fn read_u64(&self) -> Result<u64, ProgramError> {
63        if self.data.len() < 8 {
64            return Err(ProgramError::InvalidAccountData);
65        }
66        Ok(u64::from_le_bytes([
67            self.data[0],
68            self.data[1],
69            self.data[2],
70            self.data[3],
71            self.data[4],
72            self.data[5],
73            self.data[6],
74            self.data[7],
75        ]))
76    }
77
78    /// Read a boolean from offset 0 (0 = false, non-zero = true).
79    #[inline(always)]
80    pub fn read_bool(&self) -> Result<bool, ProgramError> {
81        self.read_u8().map(|v| v != 0)
82    }
83
84    /// Borrow as a 32-byte address reference.
85    #[inline(always)]
86    pub fn as_address(&self) -> Result<&[u8; 32], ProgramError> {
87        if self.data.len() < 32 {
88            return Err(ProgramError::InvalidAccountData);
89        }
90        // SAFETY: We checked length >= 32. Alignment is 1 for [u8; 32].
91        Ok(unsafe { &*(self.data.as_ptr() as *const [u8; 32]) })
92    }
93}
94
95/// Mutable typed view over a field's bytes.
96pub struct FieldMut<'a> {
97    data: &'a mut [u8],
98}
99
100impl<'a> FieldMut<'a> {
101    /// Create a mutable field reference over the given byte slice.
102    #[inline(always)]
103    pub fn new(data: &'a mut [u8]) -> Self {
104        Self { data }
105    }
106
107    /// Read a `u8` from offset 0.
108    #[inline(always)]
109    pub fn read_u8(&self) -> Result<u8, ProgramError> {
110        self.data
111            .first()
112            .copied()
113            .ok_or(ProgramError::InvalidAccountData)
114    }
115
116    /// Read a little-endian `u64` from offset 0.
117    #[inline(always)]
118    pub fn read_u64(&self) -> Result<u64, ProgramError> {
119        if self.data.len() < 8 {
120            return Err(ProgramError::InvalidAccountData);
121        }
122        Ok(u64::from_le_bytes([
123            self.data[0],
124            self.data[1],
125            self.data[2],
126            self.data[3],
127            self.data[4],
128            self.data[5],
129            self.data[6],
130            self.data[7],
131        ]))
132    }
133
134    /// Write a `u8` at offset 0.
135    #[inline(always)]
136    pub fn write_u8(&mut self, v: u8) -> Result<(), ProgramError> {
137        if self.data.is_empty() {
138            return Err(ProgramError::InvalidAccountData);
139        }
140        self.data[0] = v;
141        Ok(())
142    }
143
144    /// Write a little-endian `u16` at offset 0.
145    #[inline(always)]
146    pub fn write_u16(&mut self, v: u16) -> Result<(), ProgramError> {
147        if self.data.len() < 2 {
148            return Err(ProgramError::InvalidAccountData);
149        }
150        let bytes = v.to_le_bytes();
151        self.data[0] = bytes[0];
152        self.data[1] = bytes[1];
153        Ok(())
154    }
155
156    /// Write a little-endian `u32` at offset 0.
157    #[inline(always)]
158    pub fn write_u32(&mut self, v: u32) -> Result<(), ProgramError> {
159        if self.data.len() < 4 {
160            return Err(ProgramError::InvalidAccountData);
161        }
162        let bytes = v.to_le_bytes();
163        self.data[..4].copy_from_slice(&bytes);
164        Ok(())
165    }
166
167    /// Write a little-endian `u64` at offset 0.
168    #[inline(always)]
169    pub fn write_u64(&mut self, v: u64) -> Result<(), ProgramError> {
170        if self.data.len() < 8 {
171            return Err(ProgramError::InvalidAccountData);
172        }
173        let bytes = v.to_le_bytes();
174        self.data[..8].copy_from_slice(&bytes);
175        Ok(())
176    }
177
178    /// Write a boolean (normalized to 0x00 or 0x01).
179    #[inline(always)]
180    pub fn write_bool(&mut self, v: bool) -> Result<(), ProgramError> {
181        self.write_u8(v as u8)
182    }
183
184    /// Write a 32-byte address.
185    #[inline(always)]
186    pub fn write_address(&mut self, addr: &[u8; 32]) -> Result<(), ProgramError> {
187        if self.data.len() < 32 {
188            return Err(ProgramError::InvalidAccountData);
189        }
190        self.data[..32].copy_from_slice(addr);
191        Ok(())
192    }
193
194    /// Copy raw bytes into this field.
195    #[inline(always)]
196    pub fn copy_from(&mut self, src: &[u8]) -> Result<(), ProgramError> {
197        if self.data.len() < src.len() {
198            return Err(ProgramError::InvalidAccountData);
199        }
200        self.data[..src.len()].copy_from_slice(src);
201        Ok(())
202    }
203
204    /// Borrow the underlying bytes immutably.
205    #[inline(always)]
206    pub fn as_bytes(&self) -> &[u8] {
207        self.data
208    }
209
210    /// Borrow the underlying bytes mutably.
211    #[inline(always)]
212    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
213        self.data
214    }
215}