Skip to main content

hopper_core/frame/
args.rs

1//! Phase-specific instruction argument decomposition.
2//!
3//! Allows instruction data to be split into typed phases that align with
4//! the `PhasedFrame` execution model: resolve, validate, execute.
5//!
6//! Each phase can access only the subset of instruction args it needs,
7//! enforcing separation of concerns at the type level.
8//!
9//! ## Usage
10//!
11//! ```ignore
12//! struct WithdrawArgs<'a> {
13//!     amount: u64,
14//!     memo: &'a [u8],
15//! }
16//!
17//! impl<'a> InstructionArgs<'a> for WithdrawArgs<'a> {
18//!     fn parse(data: &'a [u8]) -> Result<Self, ProgramError> {
19//!         if data.len() < 8 {
20//!             return Err(ProgramError::InvalidInstructionData);
21//!         }
22//!         Ok(Self {
23//!             amount: u64::from_le_bytes(data[..8].try_into().unwrap()),
24//!             memo: &data[8..],
25//!         })
26//!     }
27//! }
28//!
29//! // In phased execution:
30//! let frame = PhasedFrame::new(program_id, accounts, ix_data)?;
31//! let args = WithdrawArgs::parse(ix_data)?;
32//!
33//! frame
34//!     .resolve(2, |accounts, _pid| Ok(MyAccounts { ... }))?
35//!     .validate_with_args(&args, |ctx, _pid, args| {
36//!         hopper_require!(args.amount > 0, ZeroAmount);
37//!         Ok(())
38//!     })?
39//!     .execute_with_args(&args, |ctx, args| {
40//!         // use args.amount for mutation
41//!         Ok(())
42//!     })?;
43//! ```
44
45use hopper_runtime::error::ProgramError;
46
47/// Trait for parsing instruction data into a typed args struct.
48///
49/// Implement this for each instruction's argument format.
50/// The lifetime `'a` allows zero-copy references into the instruction data.
51pub trait InstructionArgs<'a>: Sized {
52    /// Parse instruction data into typed args.
53    fn parse(data: &'a [u8]) -> Result<Self, ProgramError>;
54}
55
56/// Trait for args that can be validated independently of accounts.
57///
58/// Implement this to perform pure argument validation (range checks,
59/// non-zero assertions, format validation) before touching any account state.
60pub trait ValidateArgs {
61    /// Validate the arguments in isolation. Called before account validation.
62    fn validate(&self) -> Result<(), ProgramError>;
63}
64
65// -- Extensions to ResolvedFrame for arg-aware validation/execution --
66
67use super::phase::{ExecutionContext, ResolvedFrame, ValidatedFrame};
68use hopper_runtime::{Address, ProgramResult};
69
70impl<'a, T> ResolvedFrame<'a, T> {
71    /// Validate with access to both resolved accounts AND typed instruction args.
72    ///
73    /// This is the arg-aware counterpart of `validate()`.
74    /// The closure receives (accounts, program_id, args).
75    #[inline]
76    pub fn validate_with_args<A, F>(
77        self,
78        args: &A,
79        f: F,
80    ) -> Result<ValidatedFrame<'a, T>, ProgramError>
81    where
82        F: FnOnce(&T, &Address, &A) -> ProgramResult,
83    {
84        f(&self.resolved, self.program_id, args)?;
85        Ok(ValidatedFrame {
86            program_id: self.program_id,
87            accounts: self.accounts,
88            ix_data: self.ix_data,
89            mutable_borrows: self.mutable_borrows,
90            resolved: self.resolved,
91        })
92    }
93}
94
95impl<'a, T> ValidatedFrame<'a, T> {
96    /// Execute with access to typed instruction args.
97    ///
98    /// This is the arg-aware counterpart of `execute()`.
99    #[inline]
100    pub fn execute_with_args<A, R, F>(mut self, args: &A, f: F) -> Result<R, ProgramError>
101    where
102        F: FnOnce(&mut ExecutionContext<'a, '_, T>, &A) -> Result<R, ProgramError>,
103    {
104        let mut ctx = ExecutionContext {
105            program_id: self.program_id,
106            accounts: self.accounts,
107            ix_data: self.ix_data,
108            mutable_borrows: &mut self.mutable_borrows,
109            resolved: &self.resolved,
110        };
111        f(&mut ctx, args)
112    }
113}