Skip to main content

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}