sealevel_tools/cpi/
mod.rs

1//! Utility methods for cross-program invocations.
2
3#[cfg(feature = "alloc")]
4mod alloc;
5#[cfg(feature = "token")]
6pub mod ata_program;
7pub mod system_program;
8#[cfg(feature = "token")]
9pub mod token_program;
10
11#[cfg(feature = "alloc")]
12pub use alloc::*;
13
14pub use solana_cpi::{set_return_data, MAX_RETURN_DATA};
15
16#[cfg(feature = "alloc")]
17pub use solana_cpi::get_return_data as checked_dynamic_return_data;
18
19use core::ops::Deref;
20
21use crate::{
22    entrypoint::{AccountInfoC, AccountMetaC, InstructionC, NoStdAccountInfo, ProgramResult},
23    pubkey::Pubkey,
24};
25
26/// Associate signer seeds with an [NoStdAccountInfo]. Signer seeds may be [None] if
27/// [NoStdAccountInfo::is_signer] is true.
28#[derive(Clone, PartialEq, Eq)]
29pub struct CpiAuthority<'a, 'b: 'a> {
30    pub account: &'b NoStdAccountInfo,
31    pub signer_seeds: Option<&'a [&'b [u8]]>,
32}
33
34impl<'a, 'b: 'a> Deref for CpiAuthority<'a, 'b> {
35    type Target = NoStdAccountInfo;
36
37    fn deref(&self) -> &Self::Target {
38        self.account
39    }
40}
41
42/// Because [CpiAuthority] can have a [None] value for [CpiAuthority::signer_seeds], this method
43/// finds seeds that can be unwrapped returns them in a fixed size array. The number of seeds is
44/// returned as well so the remaining array elements can be disregarded when its slice is passed
45/// into an invoke method.
46#[inline(always)]
47pub fn unwrap_signers_seeds<'a, 'b: 'a, const NUM_POSSIBLE: usize>(
48    possible_signers_seeds: &[Option<&'a [&'b [u8]]>; NUM_POSSIBLE],
49) -> ([&'a [&'b [u8]]; NUM_POSSIBLE], usize) {
50    let mut signers_seeds = [Default::default(); NUM_POSSIBLE];
51    let mut end = 0;
52
53    possible_signers_seeds.iter().for_each(|seeds| {
54        if let Some(seeds) = seeds {
55            signers_seeds[end] = *seeds;
56            end += 1;
57        }
58    });
59
60    (signers_seeds, end)
61}
62
63/// Setup to invoke a cross-program instruction. To avoid using heap memory, it is recommended to
64/// pass in references to fixed arrays of accounts and infos.
65#[derive(Debug, Clone)]
66pub struct CpiInstruction<'a> {
67    pub program_id: &'a Pubkey,
68
69    /// These accounts are be generated with [NoStdAccountInfo::to_meta_c].
70    pub accounts: &'a [AccountMetaC],
71    pub data: &'a [u8],
72}
73
74impl<'a> CpiInstruction<'a> {
75    /// Invoke the cross-program instruction with the specified account infos and signer seeds.
76    #[inline(always)]
77    pub fn invoke_signed(&self, infos: &[AccountInfoC], signers_seeds: &[&[&[u8]]]) {
78        let Self {
79            program_id,
80            accounts,
81            data,
82        } = *self;
83
84        let instruction = InstructionC {
85            program_id,
86            accounts: accounts.as_ptr(),
87            accounts_len: accounts.len() as u64,
88            data: data.as_ptr(),
89            data_len: data.len() as u64,
90        };
91
92        invoke_signed_c(&instruction, infos, signers_seeds);
93    }
94
95    /// Invoke the cross-program instruction with the specified account infos and optional signer
96    /// seeds (which may come from [CpiAuthority] structs).
97    #[inline(always)]
98    pub fn invoke_possibly_signed<const NUM_POSSIBLE: usize>(
99        &self,
100        infos: &[AccountInfoC],
101        possible_signers_seeds: &[Option<&[&[u8]]>; NUM_POSSIBLE],
102    ) {
103        let (signers_seeds, end) = unwrap_signers_seeds(possible_signers_seeds);
104        self.invoke_signed(infos, &signers_seeds[..end]);
105    }
106
107    /// Invoke the cross-program instruction with the specified account infos and signer seeds. This
108    /// method returns data from the CPI call as a fixed size array of bytes.
109    #[inline(always)]
110    pub fn checked_return_data<const DATA_LEN: usize>(
111        &self,
112        infos: &[AccountInfoC],
113        signers_seeds: &[&[&[u8]]],
114    ) -> Option<[u8; DATA_LEN]> {
115        self.invoke_signed(infos, signers_seeds);
116        checked_return_data::<DATA_LEN>().map(|(_, data)| data)
117    }
118
119    /// Invoke the cross-program instruction with the specified account infos and signer seeds. This
120    /// method returns data from the CPI call as a vector of bytes.
121    #[cfg(feature = "alloc")]
122    #[inline(always)]
123    pub fn checked_dynamic_return_data(
124        &self,
125        infos: &[AccountInfoC],
126        signers_seeds: &[&[&[u8]]],
127    ) -> Option<::alloc::vec::Vec<u8>> {
128        self.invoke_signed(infos, signers_seeds);
129        checked_dynamic_return_data().map(|(_, data)| data)
130    }
131}
132
133/// Similar to [invoke_signed](solana_cpi::invoke_signed), but performs a lower level call to the
134/// runtime and does not try to perform any account borrows.
135#[allow(unexpected_cfgs)]
136#[inline(always)]
137pub fn invoke_signed_c(
138    instruction: &InstructionC,
139    infos: &[AccountInfoC],
140    signers_seeds: &[&[&[u8]]],
141) {
142    #[cfg(target_os = "solana")]
143    unsafe {
144        solana_cpi::syscalls::sol_invoke_signed_c(
145            instruction as *const InstructionC as *const u8,
146            infos.as_ptr() as *const u8,
147            infos.len() as u64,
148            signers_seeds.as_ptr() as *const u8,
149            signers_seeds.len() as u64,
150        );
151    }
152
153    #[cfg(not(target_os = "solana"))]
154    let _ = (instruction, infos, signers_seeds);
155}
156
157/// Check lamports and data borrows on [NoStdAccountInfo]. If writable, this method checks mutable
158/// borrows. Otherwise it checks immutable borrows. These borrows are checked in
159/// [solana_cpi::invoke_signed] before CPI is called (and will be executed in [try_invoke_signed]).
160#[inline(always)]
161pub fn try_check_borrow_account_info(account_info: &NoStdAccountInfo) -> ProgramResult {
162    if account_info.is_writable() {
163        let _ = account_info.try_borrow_mut_lamports()?;
164        let _ = account_info.try_borrow_mut_data()?;
165    } else {
166        let _ = account_info.try_borrow_lamports()?;
167        let _ = account_info.try_borrow_data()?;
168    }
169
170    Ok(())
171}
172
173/// Get the return data from an invoked program as a fixed array of bytes (maximum size defined by
174/// [solana_cpi::MAX_RETURN_DATA]). If the return data's size differs from the specified array size,
175/// this method will return [None].
176#[allow(unexpected_cfgs)]
177pub fn checked_return_data<const DATA_LEN: usize>() -> Option<(Pubkey, [u8; DATA_LEN])> {
178    assert!(
179        DATA_LEN <= MAX_RETURN_DATA,
180        "Return data size exceeds 1,024 bytes"
181    );
182
183    #[cfg(target_os = "solana")]
184    {
185        let mut buf = [0; DATA_LEN];
186        let mut program_id = Pubkey::default();
187
188        let size = unsafe {
189            solana_cpi::syscalls::sol_get_return_data(
190                buf.as_mut_ptr(),
191                buf.len() as u64,
192                &mut program_id,
193            )
194        };
195
196        if size == 0 || size != (DATA_LEN as u64) {
197            None
198        } else {
199            Some((program_id, buf))
200        }
201    }
202
203    #[cfg(not(target_os = "solana"))]
204    None
205}