Skip to main content

hopper_core/accounts/
hopper_account.rs

1//! Layout-bound typed account with read/write/init.
2//!
3//! `HopperAccount<T>` bridges the account DSL to Hopper's existing zero-copy
4//! overlay infrastructure. It wraps an AccountView and provides ergonomic
5//! `read()`, `write()`, and `init()` methods that delegate to the verified
6//! overlay path.
7
8use hopper_runtime::error::ProgramError;
9use hopper_runtime::{AccountView, Address};
10
11use crate::account::{FixedLayout, Pod, VerifiedAccount, VerifiedAccountMut};
12use crate::check;
13use crate::check::modifier::HopperLayout;
14
15/// A layout-bound, owner-validated account.
16///
17/// Wraps a hopper-native `AccountView` with layout type information.
18/// Access the typed data via `read()` for immutable or `write()` for mutable.
19///
20/// Validation is performed at construction time:
21/// - Owner matches program_id
22/// - Discriminator, version, layout_id match T's constants
23/// - Account data size matches T's expected layout size
24pub struct HopperAccount<'a, T: Pod + FixedLayout + HopperLayout> {
25    view: &'a AccountView,
26    #[allow(dead_code)] // stored for future PDA derivation and CPI use
27    program_id: &'a Address,
28    _marker: core::marker::PhantomData<T>,
29}
30
31impl<'a, T: Pod + FixedLayout + HopperLayout> HopperAccount<'a, T> {
32    /// Construct from an AccountView with full header validation.
33    ///
34    /// Validates: owner == program_id, discriminator, version, layout_id, size.
35    #[inline]
36    pub fn from_account(
37        account: &'a AccountView,
38        program_id: &'a Address,
39    ) -> Result<Self, ProgramError> {
40        check::check_owner(account, program_id)?;
41        let data = account.try_borrow()?;
42        crate::account::check_header(&data, T::DISC, T::VERSION, &T::LAYOUT_ID)?;
43        check::check_size(&data, T::LEN_WITH_HEADER)?;
44        Ok(Self {
45            view: account,
46            program_id,
47            _marker: core::marker::PhantomData,
48        })
49    }
50
51    /// Construct from an AccountView that must also be writable.
52    #[inline]
53    pub fn from_account_mut(
54        account: &'a AccountView,
55        program_id: &'a Address,
56    ) -> Result<Self, ProgramError> {
57        check::check_writable(account)?;
58        Self::from_account(account, program_id)
59    }
60
61    /// Read the typed layout overlay (immutable).
62    ///
63    /// Returns a reference to the typed layout struct overlaid on account data.
64    ///
65    /// # Safety
66    ///
67    /// The caller must ensure no conflicting mutable borrows exist on this
68    /// account's data. Frame-level borrow tracking handles this in normal usage.
69    #[inline]
70    pub fn read(&self) -> Result<VerifiedAccount<'a, T>, ProgramError> {
71        let data = self.view.try_borrow()?;
72        VerifiedAccount::from_ref(data)
73    }
74
75    /// Write to the typed layout overlay (mutable).
76    ///
77    /// Returns a mutable reference to the typed layout struct.
78    /// The account must have been constructed via `from_account_mut` or
79    /// the caller must ensure writability.
80    ///
81    /// # Safety
82    ///
83    /// The caller must ensure exclusive access to this account's data.
84    #[inline]
85    pub fn write(&self) -> Result<VerifiedAccountMut<'a, T>, ProgramError> {
86        let data = self.view.try_borrow_mut()?;
87        VerifiedAccountMut::from_ref_mut(data)
88    }
89
90    /// The account's address.
91    #[inline(always)]
92    pub fn address(&self) -> &Address {
93        self.view.address()
94    }
95
96    /// The account's owner.
97    ///
98    /// # Safety
99    ///
100    /// Caller must ensure no conflicting mutable borrows on the account.
101    #[inline(always)]
102    pub unsafe fn owner(&self) -> &Address {
103        // SAFETY: Caller guarantees no conflicting borrows.
104        unsafe { self.view.owner() }
105    }
106
107    /// The underlying AccountView.
108    #[inline(always)]
109    pub fn to_account_view(&self) -> &'a AccountView {
110        self.view
111    }
112
113    /// Current lamports on the account.
114    #[inline(always)]
115    pub fn lamports(&self) -> u64 {
116        self.view.lamports()
117    }
118}