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}