hopper_core/account/verified.rs
1//! Verified account wrappers -- type-safe proof of validation.
2//!
3//! `VerifiedAccount<T>` and `VerifiedAccountMut<T>` can only be constructed
4//! through validated loading paths (tiered loading). Holding one is proof
5//! that the account passed the required checks.
6//!
7//! These wrappers intentionally expose whole-layout `&T` / `&mut T` overlays,
8//! but the references are tied to `&self` / `&mut self`: they cannot outlive
9//! the wrapper, and the wrapper owns either a Hopper borrow guard or a
10//! pre-validated raw slice. This is the proof-wrapper exception to Hopper's
11//! usual "no naked raw reference from account access" rule. Field-level hot
12//! paths should still prefer generated accessors or segment leases.
13
14use super::header::HEADER_LEN;
15use super::pod::{FixedLayout, Pod};
16use hopper_runtime::error::ProgramError;
17use hopper_runtime::{Ref, RefMut};
18
19enum VerifiedBytes<'a> {
20 Borrowed(Ref<'a, [u8]>),
21 Raw(&'a [u8]),
22}
23
24impl<'a> VerifiedBytes<'a> {
25 #[inline(always)]
26 fn as_slice(&self) -> &[u8] {
27 match self {
28 Self::Borrowed(bytes) => bytes,
29 Self::Raw(bytes) => bytes,
30 }
31 }
32}
33
34/// Immutable verified account -- proof that validation passed.
35pub struct VerifiedAccount<'a, T: Pod + FixedLayout> {
36 data: VerifiedBytes<'a>,
37 _phantom: core::marker::PhantomData<T>,
38}
39
40impl<'a, T: Pod + FixedLayout> VerifiedAccount<'a, T> {
41 /// Construct from pre-validated data.
42 ///
43 /// Only tiered loading functions should create these.
44 #[inline(always)]
45 pub fn new(data: &'a [u8]) -> Result<Self, ProgramError> {
46 if data.len() < T::SIZE {
47 return Err(ProgramError::AccountDataTooSmall);
48 }
49 Ok(Self {
50 data: VerifiedBytes::Raw(data),
51 _phantom: core::marker::PhantomData,
52 })
53 }
54
55 /// Construct from a Hopper borrow guard.
56 #[inline(always)]
57 pub fn from_ref(data: Ref<'a, [u8]>) -> Result<Self, ProgramError> {
58 if data.len() < T::SIZE {
59 return Err(ProgramError::AccountDataTooSmall);
60 }
61 Ok(Self {
62 data: VerifiedBytes::Borrowed(data),
63 _phantom: core::marker::PhantomData,
64 })
65 }
66
67 /// Get an immutable reference to the overlay. Infallible after construction.
68 ///
69 /// The returned reference is bounded by `&self`; it cannot outlive this
70 /// `VerifiedAccount`, which owns the borrow guard or validated raw slice
71 /// proving the overlay is safe to read.
72 #[inline(always)]
73 pub fn get(&self) -> &T {
74 // SAFETY: Size validated at construction. T: Pod, alignment-1 guaranteed.
75 unsafe { &*(self.data().as_ptr() as *const T) }
76 }
77
78 /// Run a closure with the verified whole-layout overlay.
79 ///
80 /// This is equivalent to `f(self.get())`, but makes the guard-scoped
81 /// lifetime obvious at call sites.
82 #[inline(always)]
83 pub fn with<U, F>(&self, f: F) -> U
84 where
85 F: FnOnce(&T) -> U,
86 {
87 f(self.get())
88 }
89
90 /// Raw data.
91 #[inline(always)]
92 pub fn data(&self) -> &[u8] {
93 self.data.as_slice()
94 }
95
96 /// Body data after header.
97 #[inline(always)]
98 pub fn body(&self) -> &[u8] {
99 let data = self.data();
100 if data.len() > HEADER_LEN {
101 &data[HEADER_LEN..]
102 } else {
103 &[]
104 }
105 }
106
107 /// Project a field from the verified overlay.
108 ///
109 /// The closure receives the typed overlay and returns a reference into it.
110 /// The returned reference carries the lifetime of the verified data,
111 /// preserving proof-of-validation provenance.
112 ///
113 /// ```ignore
114 /// let vault = Vault::load(account, program_id)?;
115 /// let authority: &[u8; 32] = vault.map(|v| &v.authority);
116 /// let balance: &WireU64 = vault.map(|v| &v.balance);
117 /// ```
118 #[inline(always)]
119 pub fn map<U, F>(&self, f: F) -> U
120 where
121 F: FnOnce(&T) -> U,
122 {
123 f(self.get())
124 }
125
126 /// Project a byte sub-slice from the verified data.
127 ///
128 /// Returns a sub-slice of the already-validated data at the given
129 /// offset and length. Useful for accessing raw segments or embedded
130 /// sub-structures without re-validation.
131 #[inline]
132 pub fn slice(&self, offset: usize, len: usize) -> Result<&[u8], ProgramError> {
133 let end = offset
134 .checked_add(len)
135 .ok_or(ProgramError::ArithmeticOverflow)?;
136 if end > self.data().len() {
137 return Err(ProgramError::AccountDataTooSmall);
138 }
139 Ok(&self.data()[offset..end])
140 }
141
142 /// Overlay a second Pod type at a given offset within verified data.
143 ///
144 /// Useful for accessing embedded sub-layouts in segmented accounts
145 /// where the outer account has already been validated.
146 #[inline]
147 pub fn overlay_at<U: Pod + FixedLayout>(&self, offset: usize) -> Result<&U, ProgramError> {
148 let end = offset
149 .checked_add(U::SIZE)
150 .ok_or(ProgramError::ArithmeticOverflow)?;
151 if end > self.data().len() {
152 return Err(ProgramError::AccountDataTooSmall);
153 }
154 // SAFETY: Bounds checked. U: Pod + FixedLayout guarantees align 1.
155 Ok(unsafe { &*(self.data().as_ptr().add(offset) as *const U) })
156 }
157}
158
159enum VerifiedBytesMut<'a> {
160 Borrowed(RefMut<'a, [u8]>),
161 Raw(&'a mut [u8]),
162}
163
164impl VerifiedBytesMut<'_> {
165 #[inline(always)]
166 fn as_slice(&self) -> &[u8] {
167 match self {
168 Self::Borrowed(bytes) => bytes,
169 Self::Raw(bytes) => bytes,
170 }
171 }
172
173 #[inline(always)]
174 fn as_mut_slice(&mut self) -> &mut [u8] {
175 match self {
176 Self::Borrowed(bytes) => bytes,
177 Self::Raw(bytes) => bytes,
178 }
179 }
180}
181
182/// Mutable verified account -- proof that validation passed, with write access.
183pub struct VerifiedAccountMut<'a, T: Pod + FixedLayout> {
184 data: VerifiedBytesMut<'a>,
185 _phantom: core::marker::PhantomData<T>,
186}
187
188impl<'a, T: Pod + FixedLayout> VerifiedAccountMut<'a, T> {
189 /// Construct from pre-validated mutable data.
190 #[inline(always)]
191 pub fn new(data: &'a mut [u8]) -> Result<Self, ProgramError> {
192 if data.len() < T::SIZE {
193 return Err(ProgramError::AccountDataTooSmall);
194 }
195 Ok(Self {
196 data: VerifiedBytesMut::Raw(data),
197 _phantom: core::marker::PhantomData,
198 })
199 }
200
201 /// Construct from a Hopper mutable borrow guard.
202 #[inline(always)]
203 pub fn from_ref_mut(data: RefMut<'a, [u8]>) -> Result<Self, ProgramError> {
204 if data.len() < T::SIZE {
205 return Err(ProgramError::AccountDataTooSmall);
206 }
207 Ok(Self {
208 data: VerifiedBytesMut::Borrowed(data),
209 _phantom: core::marker::PhantomData,
210 })
211 }
212
213 /// Get an immutable reference to the overlay.
214 ///
215 /// The returned reference is bounded by `&self`; it cannot outlive this
216 /// `VerifiedAccountMut`, which owns the mutable borrow guard or validated
217 /// raw mutable slice.
218 #[inline(always)]
219 pub fn get(&self) -> &T {
220 // SAFETY: Size validated at construction.
221 unsafe { &*(self.data().as_ptr() as *const T) }
222 }
223
224 /// Get a mutable reference to the overlay.
225 ///
226 /// The returned reference is bounded by `&mut self`, so there can be only
227 /// one mutable overlay at a time and it cannot outlive the wrapper that
228 /// owns the underlying account borrow.
229 #[inline(always)]
230 pub fn get_mut(&mut self) -> &mut T {
231 // SAFETY: Size validated at construction. We have exclusive access.
232 unsafe { &mut *(self.data_mut().as_mut_ptr() as *mut T) }
233 }
234
235 /// Run a closure with the verified whole-layout overlay.
236 #[inline(always)]
237 pub fn with<U, F>(&self, f: F) -> U
238 where
239 F: FnOnce(&T) -> U,
240 {
241 f(self.get())
242 }
243
244 /// Run a closure with the verified mutable whole-layout overlay.
245 #[inline(always)]
246 pub fn with_mut<U, F>(&mut self, f: F) -> U
247 where
248 F: FnOnce(&mut T) -> U,
249 {
250 f(self.get_mut())
251 }
252
253 /// Raw data (immutable).
254 #[inline(always)]
255 pub fn data(&self) -> &[u8] {
256 self.data.as_slice()
257 }
258
259 /// Raw data (mutable).
260 #[inline(always)]
261 pub fn data_mut(&mut self) -> &mut [u8] {
262 self.data.as_mut_slice()
263 }
264
265 /// Project a field from the verified overlay (immutable).
266 #[inline(always)]
267 pub fn map<U, F>(&self, f: F) -> U
268 where
269 F: FnOnce(&T) -> U,
270 {
271 f(self.get())
272 }
273
274 /// Project a field for mutation.
275 ///
276 /// ```ignore
277 /// let mut vault = Vault::load_mut(account, program_id)?;
278 /// vault.map_mut(|v| {
279 /// v.balance = WireU64::new(100);
280 /// });
281 /// ```
282 #[inline(always)]
283 pub fn map_mut<U, F>(&mut self, f: F) -> U
284 where
285 F: FnOnce(&mut T) -> U,
286 {
287 f(self.get_mut())
288 }
289
290 /// Overlay a second Pod type at a given offset (immutable).
291 #[inline]
292 pub fn overlay_at<U: Pod + FixedLayout>(&self, offset: usize) -> Result<&U, ProgramError> {
293 let end = offset
294 .checked_add(U::SIZE)
295 .ok_or(ProgramError::ArithmeticOverflow)?;
296 if end > self.data().len() {
297 return Err(ProgramError::AccountDataTooSmall);
298 }
299 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
300 Ok(unsafe { &*(self.data().as_ptr().add(offset) as *const U) })
301 }
302
303 /// Overlay a second Pod type at a given offset (mutable).
304 #[inline]
305 pub fn overlay_at_mut<U: Pod + FixedLayout>(
306 &mut self,
307 offset: usize,
308 ) -> Result<&mut U, ProgramError> {
309 let end = offset
310 .checked_add(U::SIZE)
311 .ok_or(ProgramError::ArithmeticOverflow)?;
312 if end > self.data().len() {
313 return Err(ProgramError::AccountDataTooSmall);
314 }
315 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
316 Ok(unsafe { &mut *(self.data_mut().as_mut_ptr().add(offset) as *mut U) })
317 }
318}