Skip to main content

hopper_native/
instruction.rs

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