Skip to main content

hopper_runtime/
instruction.rs

1//! Hopper-owned CPI instruction types.
2//!
3//! These types form the instruction ABI for Hopper cross-program invocation.
4//! They are Hopper-owned and reference Hopper's `Address` and `AccountView`
5//! types, giving the framework full control over its public API surface.
6
7use core::marker::PhantomData;
8use crate::address::Address;
9use crate::account::AccountView;
10
11// ── InstructionAccount ───────────────────────────────────────────────
12
13/// Metadata for an account referenced in a CPI instruction.
14#[repr(C)]
15#[derive(Debug, Clone)]
16pub struct InstructionAccount<'a> {
17    /// Public key of the account.
18    pub address: &'a Address,
19    /// Whether the account should be writable.
20    pub is_writable: bool,
21    /// Whether the account should sign.
22    pub is_signer: bool,
23}
24
25impl<'a> InstructionAccount<'a> {
26    /// Construct with explicit flags.
27    #[inline(always)]
28    pub const fn new(address: &'a Address, is_writable: bool, is_signer: bool) -> Self {
29        Self { address, is_writable, is_signer }
30    }
31
32    /// Read-only, non-signer.
33    #[inline(always)]
34    pub const fn readonly(address: &'a Address) -> Self {
35        Self { address, is_writable: false, is_signer: false }
36    }
37
38    /// Writable, non-signer.
39    #[inline(always)]
40    pub const fn writable(address: &'a Address) -> Self {
41        Self { address, is_writable: true, is_signer: false }
42    }
43
44    /// Read-only signer.
45    #[inline(always)]
46    pub const fn readonly_signer(address: &'a Address) -> Self {
47        Self { address, is_writable: false, is_signer: true }
48    }
49
50    /// Writable signer.
51    #[inline(always)]
52    pub const fn writable_signer(address: &'a Address) -> Self {
53        Self { address, is_writable: true, is_signer: true }
54    }
55}
56
57impl<'a> From<&'a AccountView> for InstructionAccount<'a> {
58    #[inline(always)]
59    fn from(view: &'a AccountView) -> Self {
60        Self {
61            address: view.address(),
62            is_writable: view.is_writable(),
63            is_signer: view.is_signer(),
64        }
65    }
66}
67
68// ── InstructionView ──────────────────────────────────────────────────
69
70/// A cross-program instruction to invoke.
71#[derive(Debug, Clone)]
72pub struct InstructionView<'a, 'b, 'c, 'd>
73where
74    'a: 'b,
75{
76    /// Program to call.
77    pub program_id: &'c Address,
78    /// Instruction data.
79    pub data: &'d [u8],
80    /// Account metadata.
81    pub accounts: &'b [InstructionAccount<'a>],
82}
83
84// ── CpiAccount (hopper-native backend) ───────────────────────────────
85
86/// C-ABI account info passed to `sol_invoke_signed_c`.
87///
88/// This matches the Solana runtime's expected layout for CPI account infos.
89/// Only available with hopper-native-backend because it requires direct
90/// pointer access into the runtime's account memory.
91#[cfg(feature = "hopper-native-backend")]
92#[repr(C)]
93#[derive(Clone, Copy, Debug)]
94pub struct CpiAccount<'a> {
95    address: *const Address,
96    lamports: *const u64,
97    data_len: u64,
98    data: *const u8,
99    owner: *const Address,
100    rent_epoch: u64,
101    is_signer: bool,
102    is_writable: bool,
103    executable: bool,
104    _account_view: PhantomData<&'a AccountView>,
105}
106
107#[cfg(feature = "hopper-native-backend")]
108impl<'a> From<&'a AccountView> for CpiAccount<'a> {
109    #[inline]
110    fn from(view: &'a AccountView) -> Self {
111        let raw = view.account_ptr();
112        // SAFETY: account_ptr() returns a valid pointer to the runtime
113        // account struct. The address and owner fields have the same binary
114        // layout as hopper_runtime::Address (#[repr(transparent)] over [u8; 32]).
115        Self {
116            address: unsafe { core::ptr::addr_of!((*raw).address) as *const Address },
117            lamports: unsafe { core::ptr::addr_of!((*raw).lamports) },
118            data_len: view.data_len() as u64,
119            data: view.data_ptr_unchecked(),
120            owner: unsafe { core::ptr::addr_of!((*raw).owner) as *const Address },
121            rent_epoch: 0,
122            is_signer: view.is_signer(),
123            is_writable: view.is_writable(),
124            executable: view.executable(),
125            _account_view: PhantomData,
126        }
127    }
128}
129
130// ── Seed ─────────────────────────────────────────────────────────────
131
132/// A single PDA seed for CPI signing.
133#[repr(C)]
134#[derive(Debug, Clone)]
135pub struct Seed<'a> {
136    pub(crate) seed: *const u8,
137    pub(crate) len: u64,
138    _bytes: PhantomData<&'a [u8]>,
139}
140
141impl<'a> From<&'a [u8]> for Seed<'a> {
142    #[inline(always)]
143    fn from(bytes: &'a [u8]) -> Self {
144        Self {
145            seed: bytes.as_ptr(),
146            len: bytes.len() as u64,
147            _bytes: PhantomData,
148        }
149    }
150}
151
152impl<'a, const N: usize> From<&'a [u8; N]> for Seed<'a> {
153    #[inline(always)]
154    fn from(bytes: &'a [u8; N]) -> Self {
155        Self {
156            seed: bytes.as_ptr(),
157            len: N as u64,
158            _bytes: PhantomData,
159        }
160    }
161}
162
163impl core::ops::Deref for Seed<'_> {
164    type Target = [u8];
165
166    #[inline(always)]
167    fn deref(&self) -> &[u8] {
168        unsafe { core::slice::from_raw_parts(self.seed, self.len as usize) }
169    }
170}
171
172// ── Signer ───────────────────────────────────────────────────────────
173
174/// A PDA signer: a set of seeds that derive the signing PDA.
175#[repr(C)]
176#[derive(Debug, Clone)]
177pub struct Signer<'a, 'b> {
178    pub(crate) seeds: *const Seed<'a>,
179    pub(crate) len: u64,
180    _seeds: PhantomData<&'b [Seed<'a>]>,
181}
182
183impl<'a, 'b> From<&'b [Seed<'a>]> for Signer<'a, 'b> {
184    #[inline(always)]
185    fn from(seeds: &'b [Seed<'a>]) -> Self {
186        Self {
187            seeds: seeds.as_ptr(),
188            len: seeds.len() as u64,
189            _seeds: PhantomData,
190        }
191    }
192}
193
194impl<'a, 'b, const N: usize> From<&'b [Seed<'a>; N]> for Signer<'a, 'b> {
195    #[inline(always)]
196    fn from(seeds: &'b [Seed<'a>; N]) -> Self {
197        Self {
198            seeds: seeds.as_ptr(),
199            len: N as u64,
200            _seeds: PhantomData,
201        }
202    }
203}
204
205/// Convenience macro for building an array of `Seed` from expressions.
206///
207/// Usage: `let seeds = seeds!(b"vault", mint_key.as_ref(), &[bump]);`
208#[macro_export]
209macro_rules! seeds {
210    ( $($seed:expr),* $(,)? ) => {
211        [$(
212            $crate::instruction::Seed::from($seed),
213        )*]
214    };
215}