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}