Skip to main content

hopper_core/accounts/
context.rs

1//! Typed instruction context and account struct trait.
2
3use hopper_runtime::error::ProgramError;
4use hopper_runtime::{AccountView, Address};
5
6#[cfg(feature = "explain")]
7use super::explain::ContextExplain;
8
9/// Trait implemented by account structs (manually or via derive).
10///
11/// Provides typed construction from raw accounts and optional schema metadata.
12pub trait HopperAccounts<'a>: Sized {
13    /// PDA bump storage type for this context.
14    type Bumps: Default;
15
16    /// Number of accounts consumed by this context struct.
17    ///
18    /// Used by `hopper_entry()` to split the accounts slice into consumed
19    /// accounts and remaining accounts for CPI forwarding.
20    const ACCOUNT_COUNT: usize;
21
22    /// Construct the account struct from raw instruction inputs.
23    ///
24    /// Performs all validation: signer checks, writable checks, owner checks,
25    /// PDA verification, layout validation.
26    fn try_from_accounts(
27        program_id: &'a Address,
28        accounts: &'a [AccountView],
29        instruction_data: &'a [u8],
30    ) -> Result<(Self, Self::Bumps), ProgramError>;
31
32    /// Optional static schema for introspection and explain.
33    #[cfg(feature = "explain")]
34    fn context_schema() -> Option<&'static crate::accounts::explain::ContextSchema> {
35        None
36    }
37}
38
39/// Typed instruction context carrying validated accounts, bumps, and metadata.
40///
41/// Replaces Anchor's `Context<T>` with Hopper-native semantics: receipts,
42/// explain, schema access, and remaining accounts.
43pub struct HopperCtx<'a, T>
44where
45    T: HopperAccounts<'a>,
46{
47    /// Validated accounts struct.
48    pub accounts: T,
49    /// Resolved PDA bumps.
50    pub bumps: T::Bumps,
51    /// The executing program's address.
52    pub program_id: &'a Address,
53    /// Remaining unparsed instruction data (after dispatch tag).
54    pub instruction_data: &'a [u8],
55    /// Accounts not consumed by the struct (for CPI or dynamic use).
56    pub remaining_accounts: &'a [AccountView],
57}
58
59impl<'a, T> HopperCtx<'a, T>
60where
61    T: HopperAccounts<'a>,
62{
63    /// Emit a default receipt for the current mutation.
64    ///
65    /// Routes to the existing Hopper receipt infrastructure when a receipt
66    /// profile is bound to this context.
67    #[inline]
68    pub fn emit_receipt(&self) -> Result<(), ProgramError> {
69        // v1: receipt emission requires an active StateReceipt.
70        // This is a convenience hook; callers should use StateReceipt::begin()
71        // and commit() for full receipt control.
72        Ok(())
73    }
74
75    /// Generate a human-readable explanation of this context and its accounts.
76    #[cfg(feature = "explain")]
77    #[inline]
78    pub fn explain(&self) -> ContextExplain {
79        ContextExplain::from_schema(T::context_schema())
80    }
81
82    /// Access the static context schema, if available.
83    #[cfg(feature = "explain")]
84    #[inline]
85    pub fn schema(&self) -> Option<&'static crate::accounts::explain::ContextSchema> {
86        T::context_schema()
87    }
88
89    /// Construct a context from pre-validated parts.
90    ///
91    /// Callers must ensure all accounts have already been validated.
92    /// Typically used by derive-generated `try_from_accounts` or `entry()`.
93    #[inline]
94    pub fn new(
95        accounts: T,
96        bumps: T::Bumps,
97        program_id: &'a Address,
98        instruction_data: &'a [u8],
99        remaining_accounts: &'a [AccountView],
100    ) -> Self {
101        Self {
102            accounts,
103            bumps,
104            program_id,
105            instruction_data,
106            remaining_accounts,
107        }
108    }
109}