Skip to main content

hopper_core/check/
modifier.rs

1//! Modifier-style composable account wrappers.
2//!
3//! Account constraints as nested type wrappers that compose at compile time.
4//! Each wrapper adds one constraint and delegates the rest to the inner type.
5//!
6//! ```text
7//! Signer<Mut<Account<'a, Vault>>>  -- verified signer + writable + typed overlay
8//! Mut<Account<'a, Vault>>          -- verified writable + typed overlay
9//! Account<'a, Vault>               -- verified owner + disc + layout_id
10//! ReadOnly<'a, Vault>              -- foreign account, read-only
11//! ```
12//!
13//! ## Design Principles
14//!
15//! - Zero runtime cost: wrapper structs are transparent or ZST stacks
16//! - Each modifier validates exactly one property
17//! - Inner value is accessible via `.inner()` or `Deref`
18//! - All validation happens in `from_account()` -- if you have the type, it's valid
19
20use crate::account::{FixedLayout, Pod, VerifiedAccount, VerifiedAccountMut};
21use crate::check;
22use hopper_runtime::error::ProgramError;
23use hopper_runtime::{AccountView, Address};
24
25/// A typed, owner-validated account (immutable).
26///
27/// Proves: owner == program_id, disc match, version match, layout_id match, size match.
28pub struct Account<'a, T: Pod + FixedLayout> {
29    view: &'a AccountView,
30    verified: VerifiedAccount<'a, T>,
31}
32
33impl<'a, T: Pod + FixedLayout> Account<'a, T> {
34    /// The underlying AccountView.
35    #[inline(always)]
36    pub fn view(&self) -> &'a AccountView {
37        self.view
38    }
39
40    /// The verified typed overlay.
41    #[inline(always)]
42    pub fn get(&self) -> &T {
43        self.verified.get()
44    }
45
46    /// The raw verified data.
47    #[inline(always)]
48    pub fn data(&self) -> &[u8] {
49        self.verified.data()
50    }
51
52    /// Project a field from the typed overlay.
53    #[inline(always)]
54    pub fn map<U, F>(&self, f: F) -> U
55    where
56        F: FnOnce(&T) -> U,
57    {
58        self.verified.map(f)
59    }
60}
61
62/// A typed, owner-validated account (mutable).
63///
64/// Proves: owner == program_id, writable, disc match, layout_id match, size match.
65pub struct AccountMut<'a, T: Pod + FixedLayout> {
66    view: &'a AccountView,
67    verified: VerifiedAccountMut<'a, T>,
68}
69
70impl<'a, T: Pod + FixedLayout> AccountMut<'a, T> {
71    /// The underlying AccountView.
72    #[inline(always)]
73    pub fn view(&self) -> &'a AccountView {
74        self.view
75    }
76
77    /// The verified typed overlay (immutable).
78    #[inline(always)]
79    pub fn get(&self) -> &T {
80        self.verified.get()
81    }
82
83    /// The verified typed overlay (mutable).
84    #[inline(always)]
85    pub fn get_mut(&mut self) -> &mut T {
86        self.verified.get_mut()
87    }
88
89    /// Map a field mutably.
90    #[inline(always)]
91    pub fn map_mut<U, F>(&mut self, f: F) -> U
92    where
93        F: FnOnce(&mut T) -> U,
94    {
95        self.verified.map_mut(f)
96    }
97}
98
99/// Signer wrapper -- validates that the account is a signer.
100///
101/// Wraps any inner account type that provides `view()`.
102pub struct Signer<I> {
103    inner: I,
104}
105
106impl<I> Signer<I> {
107    /// Access the inner account.
108    #[inline(always)]
109    pub fn inner(&self) -> &I {
110        &self.inner
111    }
112
113    /// Consume the wrapper and return the inner account.
114    #[inline(always)]
115    pub fn into_inner(self) -> I {
116        self.inner
117    }
118}
119
120/// Writable wrapper -- validates that the account is writable.
121pub struct Mut<I> {
122    inner: I,
123}
124
125impl<I> Mut<I> {
126    /// Access the inner account.
127    #[inline(always)]
128    pub fn inner(&self) -> &I {
129        &self.inner
130    }
131
132    /// Consume the wrapper and return the inner account.
133    #[inline(always)]
134    pub fn into_inner(self) -> I {
135        self.inner
136    }
137}
138
139// -- Construction traits --
140
141/// Trait for types that can be constructed from an AccountView with validation.
142pub trait FromAccount<'a>: Sized {
143    /// Construct this type from an account, performing all required validation.
144    fn from_account(account: &'a AccountView, program_id: &Address) -> Result<Self, ProgramError>;
145}
146
147// Account<T>: owner + disc + version + layout_id + size
148impl<'a, T: Pod + FixedLayout + HopperLayout> FromAccount<'a> for Account<'a, T> {
149    #[inline]
150    fn from_account(account: &'a AccountView, program_id: &Address) -> Result<Self, ProgramError> {
151        check::check_owner(account, program_id)?;
152        let data = account.try_borrow()?;
153        crate::account::check_header(&data, T::DISC, T::VERSION, &T::LAYOUT_ID)?;
154        check::check_size(&data, T::LEN_WITH_HEADER)?;
155        let verified = VerifiedAccount::from_ref(data)?;
156        Ok(Self {
157            view: account,
158            verified,
159        })
160    }
161}
162
163// AccountMut<T>: owner + writable + disc + version + layout_id + size
164impl<'a, T: Pod + FixedLayout + HopperLayout> FromAccount<'a> for AccountMut<'a, T> {
165    #[inline]
166    fn from_account(account: &'a AccountView, program_id: &Address) -> Result<Self, ProgramError> {
167        check::check_owner(account, program_id)?;
168        check::check_writable(account)?;
169        let data = account.try_borrow_mut()?;
170        crate::account::check_header(&data, T::DISC, T::VERSION, &T::LAYOUT_ID)?;
171        check::check_size(&data, T::LEN_WITH_HEADER)?;
172        let verified = VerifiedAccountMut::from_ref_mut(data)?;
173        Ok(Self {
174            view: account,
175            verified,
176        })
177    }
178}
179
180// Signer<I>: validates signer, then delegates to inner
181impl<'a, I: FromAccount<'a> + HasView<'a>> FromAccount<'a> for Signer<I> {
182    #[inline]
183    fn from_account(account: &'a AccountView, program_id: &Address) -> Result<Self, ProgramError> {
184        check::check_signer(account)?;
185        let inner = I::from_account(account, program_id)?;
186        Ok(Self { inner })
187    }
188}
189
190// Mut<I>: validates writable, then delegates to inner
191impl<'a, I: FromAccount<'a> + HasView<'a>> FromAccount<'a> for Mut<I> {
192    #[inline]
193    fn from_account(account: &'a AccountView, program_id: &Address) -> Result<Self, ProgramError> {
194        check::check_writable(account)?;
195        let inner = I::from_account(account, program_id)?;
196        Ok(Self { inner })
197    }
198}
199
200/// Helper trait for types that hold an `&AccountView`.
201pub trait HasView<'a> {
202    fn view(&self) -> &'a AccountView;
203}
204
205impl<'a, T: Pod + FixedLayout> HasView<'a> for Account<'a, T> {
206    #[inline(always)]
207    fn view(&self) -> &'a AccountView {
208        self.view
209    }
210}
211
212impl<'a, T: Pod + FixedLayout> HasView<'a> for AccountMut<'a, T> {
213    #[inline(always)]
214    fn view(&self) -> &'a AccountView {
215        self.view
216    }
217}
218
219impl<'a, I: HasView<'a>> HasView<'a> for Signer<I> {
220    #[inline(always)]
221    fn view(&self) -> &'a AccountView {
222        self.inner.view()
223    }
224}
225
226impl<'a, I: HasView<'a>> HasView<'a> for Mut<I> {
227    #[inline(always)]
228    fn view(&self) -> &'a AccountView {
229        self.inner.view()
230    }
231}
232
233/// Trait implemented by `hopper_layout!` types providing layout metadata.
234///
235/// This bridges the macro-generated constants (DISC, VERSION, LAYOUT_ID, LEN)
236/// into a trait for generic consumption by modifier wrappers.
237pub trait HopperLayout: Pod + FixedLayout {
238    const DISC: u8;
239    const VERSION: u8;
240    const LAYOUT_ID: [u8; 8];
241    const LEN_WITH_HEADER: usize;
242}