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 crate::error::ProgramError;
9use crate::raw_account::RuntimeAccount;
10use crate::{ProgramResult, NOT_BORROWED};
11use core::marker::PhantomData;
12
13// ── InstructionAccount ───────────────────────────────────────────────
14
15/// Metadata for an account referenced in a CPI instruction.
16#[repr(C)]
17#[derive(Debug, Clone)]
18pub struct InstructionAccount<'a> {
19    /// Public key of the account.
20    pub address: &'a Address,
21    /// Whether the account should be writable.
22    pub is_writable: bool,
23    /// Whether the account should sign.
24    pub is_signer: bool,
25}
26
27impl<'a> InstructionAccount<'a> {
28    /// Construct with explicit flags.
29    #[inline(always)]
30    pub const fn new(address: &'a Address, is_writable: bool, is_signer: bool) -> Self {
31        Self {
32            address,
33            is_writable,
34            is_signer,
35        }
36    }
37
38    /// Read-only, non-signer.
39    #[inline(always)]
40    pub const fn readonly(address: &'a Address) -> Self {
41        Self {
42            address,
43            is_writable: false,
44            is_signer: false,
45        }
46    }
47
48    /// Writable, non-signer.
49    #[inline(always)]
50    pub const fn writable(address: &'a Address) -> Self {
51        Self {
52            address,
53            is_writable: true,
54            is_signer: false,
55        }
56    }
57
58    /// Read-only signer.
59    #[inline(always)]
60    pub const fn readonly_signer(address: &'a Address) -> Self {
61        Self {
62            address,
63            is_writable: false,
64            is_signer: true,
65        }
66    }
67
68    /// Writable signer.
69    #[inline(always)]
70    pub const fn writable_signer(address: &'a Address) -> Self {
71        Self {
72            address,
73            is_writable: true,
74            is_signer: true,
75        }
76    }
77}
78
79impl<'a> From<&'a AccountView<'a>> for InstructionAccount<'a> {
80    #[inline(always)]
81    fn from(view: &'a AccountView<'a>) -> Self {
82        Self {
83            address: view.address(),
84            is_writable: view.is_writable(),
85            is_signer: view.is_signer(),
86        }
87    }
88}
89
90// ── InstructionView ──────────────────────────────────────────────────
91
92/// A cross-program instruction to invoke.
93#[derive(Debug, Clone)]
94pub struct InstructionView<'a, 'b, 'c, 'd>
95where
96    'a: 'b,
97{
98    /// Program to call.
99    pub program_id: &'c Address,
100    /// Instruction data.
101    pub data: &'d [u8],
102    /// Account metadata.
103    pub accounts: &'b [InstructionAccount<'a>],
104}
105
106// ── CpiAccount ───────────────────────────────────────────────────────
107
108/// C-ABI account info passed to `sol_invoke_signed_c`.
109///
110/// This matches the Solana runtime's expected layout for CPI account infos.
111#[repr(C)]
112#[derive(Clone, Copy, Debug)]
113pub struct CpiAccount<'a> {
114    address: *const Address,
115    lamports: *const u64,
116    data_len: u64,
117    data: *const u8,
118    owner: *const Address,
119    rent_epoch: u64,
120    is_signer: bool,
121    is_writable: bool,
122    executable: bool,
123    _account_view: PhantomData<&'a AccountView<'a>>,
124}
125
126impl<'a> From<&'a AccountView<'a>> for CpiAccount<'a> {
127    #[inline(always)]
128    fn from(view: &'a AccountView<'a>) -> Self {
129        let raw = view.account_ptr();
130        // Single u32 read extracts [borrow_state, is_signer, is_writable, executable].
131        // On little-endian BPF: byte 1 = is_signer, byte 2 = is_writable, byte 3 = executable.
132        // SAFETY: `raw` points at the RuntimeAccount header in the Solana input
133        // buffer; its first 4 bytes pack [borrow_state, is_signer, is_writable,
134        // executable]. `read_unaligned` reads them as a u32 without assuming
135        // 4-byte pointer alignment.
136        let header = unsafe { core::ptr::read_unaligned(raw as *const u32) };
137        Self {
138            address: unsafe { &(*raw).address as *const Address },
139            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
140            lamports: unsafe { &(*raw).lamports as *const u64 },
141            data_len: view.data_len() as u64,
142            data: view.data_ptr_unchecked(),
143            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
144            owner: unsafe { &(*raw).owner as *const Address },
145            rent_epoch: 0,
146            is_signer: header & 0x0000_FF00 != 0,
147            is_writable: header & 0x00FF_0000 != 0,
148            executable: header & 0xFF00_0000 != 0,
149            _account_view: PhantomData,
150        }
151    }
152}
153
154impl<'a> CpiAccount<'a> {
155    /// Rebuild one instruction meta from protocol-declared flags.
156    ///
157    /// The flags deliberately do not come from the outer account view: a PDA
158    /// may be a signer only for this CPI, and an outer-writable account may be
159    /// intentionally read-only to the callee.
160    #[inline(always)]
161    pub(crate) fn instruction_account(
162        &self,
163        is_writable: bool,
164        is_signer: bool,
165    ) -> InstructionAccount<'a> {
166        // SAFETY: `CpiAccount::from` captured this pointer from an
167        // `AccountView<'a>` and the private fields prevent safe fabrication.
168        let address = unsafe { &*self.address };
169        InstructionAccount::new(address, is_writable, is_signer)
170    }
171}
172
173/// Validate the borrow state and writable privilege encoded by specialized
174/// CPI builders before entering a syscall.
175///
176/// `writable_mask` describes the callee instruction metas, not the outer
177/// transaction privileges: bit `i` is set when account `i` will be writable
178/// in the CPI. Read-only metas need shared-borrow compatibility; writable
179/// metas need both outer writable privilege and exclusive-borrow compatibility.
180#[inline(always)]
181pub(crate) fn preflight_cpi_accounts(
182    accounts: &[CpiAccount<'_>],
183    writable_mask: usize,
184    signer_mask: usize,
185    has_pda_signers: bool,
186) -> ProgramResult {
187    let mut index = 0usize;
188    while index < accounts.len() {
189        let account = &accounts[index];
190        // With no PDA signer seeds, a missing outer signature cannot be
191        // satisfied by the runtime. Match the generic checked invoke path.
192        // Nonempty seeds are NOT proof of authority: the SVM derives and
193        // authenticates the instruction's PDA signers at the syscall boundary.
194        if signer_mask & (1usize << index) != 0 && !account.is_signer && !has_pda_signers {
195            return Err(ProgramError::MissingRequiredSignature);
196        }
197        let is_writable_meta = writable_mask & (1usize << index) != 0;
198        if is_writable_meta && !account.is_writable {
199            return Err(ProgramError::Immutable);
200        }
201
202        // `CpiAccount::from` always derives `data` from the byte immediately
203        // after its RuntimeAccount header. The fields are private, so safe
204        // callers cannot synthesize a CpiAccount with a different relation.
205        let raw = unsafe { account.data.sub(RuntimeAccount::SIZE) as *const RuntimeAccount };
206        // SAFETY: `raw` was recovered from the invariant above and remains
207        // valid for the `CpiAccount` lifetime.
208        let borrow_state = unsafe { (*raw).borrow_state };
209        let compatible = if is_writable_meta {
210            borrow_state == NOT_BORROWED
211        } else {
212            borrow_state != 0
213        };
214        if !compatible {
215            return Err(ProgramError::AccountBorrowFailed);
216        }
217
218        index += 1;
219    }
220    Ok(())
221}
222
223// Pin the two C structures handed to `sol_invoke_signed_c`. Rust `bool` is one
224// byte, matching the syscall ABI's byte flags; the tail padding rounds each
225// record to pointer alignment.
226const _: () = {
227    assert!(core::mem::size_of::<InstructionAccount<'static>>() == 16);
228    assert!(core::mem::align_of::<InstructionAccount<'static>>() == 8);
229    assert!(core::mem::offset_of!(InstructionAccount<'static>, address) == 0);
230    assert!(core::mem::offset_of!(InstructionAccount<'static>, is_writable) == 8);
231    assert!(core::mem::offset_of!(InstructionAccount<'static>, is_signer) == 9);
232
233    assert!(core::mem::size_of::<CpiAccount<'static>>() == 56);
234    assert!(core::mem::align_of::<CpiAccount<'static>>() == 8);
235    assert!(core::mem::offset_of!(CpiAccount<'static>, address) == 0);
236    assert!(core::mem::offset_of!(CpiAccount<'static>, lamports) == 8);
237    assert!(core::mem::offset_of!(CpiAccount<'static>, data_len) == 16);
238    assert!(core::mem::offset_of!(CpiAccount<'static>, data) == 24);
239    assert!(core::mem::offset_of!(CpiAccount<'static>, owner) == 32);
240    assert!(core::mem::offset_of!(CpiAccount<'static>, rent_epoch) == 40);
241    assert!(core::mem::offset_of!(CpiAccount<'static>, is_signer) == 48);
242    assert!(core::mem::offset_of!(CpiAccount<'static>, is_writable) == 49);
243    assert!(core::mem::offset_of!(CpiAccount<'static>, executable) == 50);
244};
245
246// ── Seed ─────────────────────────────────────────────────────────────
247
248/// A single PDA seed for CPI signing.
249#[repr(C)]
250#[derive(Debug, Clone)]
251pub struct Seed<'a> {
252    pub(crate) seed: *const u8,
253    pub(crate) len: u64,
254    _bytes: PhantomData<&'a [u8]>,
255}
256
257impl<'a> From<&'a [u8]> for Seed<'a> {
258    #[inline(always)]
259    fn from(bytes: &'a [u8]) -> Self {
260        Self {
261            seed: bytes.as_ptr(),
262            len: bytes.len() as u64,
263            _bytes: PhantomData,
264        }
265    }
266}
267
268impl<'a, const N: usize> From<&'a [u8; N]> for Seed<'a> {
269    #[inline(always)]
270    fn from(bytes: &'a [u8; N]) -> Self {
271        Self {
272            seed: bytes.as_ptr(),
273            len: N as u64,
274            _bytes: PhantomData,
275        }
276    }
277}
278
279impl core::ops::Deref for Seed<'_> {
280    type Target = [u8];
281
282    #[inline(always)]
283    fn deref(&self) -> &[u8] {
284        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
285        unsafe { core::slice::from_raw_parts(self.seed, self.len as usize) }
286    }
287}
288
289// ── Signer ───────────────────────────────────────────────────────────
290
291/// A PDA signer: a set of seeds that derive the signing PDA.
292#[repr(C)]
293#[derive(Debug, Clone)]
294pub struct Signer<'a, 'b> {
295    pub(crate) seeds: *const Seed<'a>,
296    pub(crate) len: u64,
297    _seeds: PhantomData<&'b [Seed<'a>]>,
298}
299
300impl<'a, 'b> From<&'b [Seed<'a>]> for Signer<'a, 'b> {
301    #[inline(always)]
302    fn from(seeds: &'b [Seed<'a>]) -> Self {
303        Self {
304            seeds: seeds.as_ptr(),
305            len: seeds.len() as u64,
306            _seeds: PhantomData,
307        }
308    }
309}
310
311impl<'a, 'b, const N: usize> From<&'b [Seed<'a>; N]> for Signer<'a, 'b> {
312    #[inline(always)]
313    fn from(seeds: &'b [Seed<'a>; N]) -> Self {
314        Self {
315            seeds: seeds.as_ptr(),
316            len: N as u64,
317            _seeds: PhantomData,
318        }
319    }
320}
321
322/// Convenience macro for building an array of `Seed` from expressions.
323///
324/// Usage: `let seeds = seeds!(b"vault", mint_key.as_ref(), &[bump]);`
325#[macro_export]
326macro_rules! seeds {
327    ( $($seed:expr),* $(,)? ) => {
328        [$(
329            $crate::instruction::Seed::from($seed),
330        )*]
331    };
332}