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}