|
| 1 | +//! Cross-program invocation helpers. |
| 2 | +
|
| 3 | +use core::{mem::MaybeUninit, ops::Deref}; |
| 4 | + |
| 5 | +use crate::{ |
| 6 | + account_info::AccountInfo, |
| 7 | + instruction::{Account, AccountMeta, Instruction, Signer}, |
| 8 | + program_error::ProgramError, |
| 9 | + pubkey::Pubkey, |
| 10 | + ProgramResult, |
| 11 | +}; |
| 12 | + |
| 13 | +/// Maximum number of accounts that can be passed to a cross-program invocation. |
| 14 | +const MAX_CPI_ACCOUNTS: usize = 64; |
| 15 | + |
| 16 | +/// An `Instruction` as expected by `sol_invoke_signed_c`. |
| 17 | +/// |
| 18 | +/// DO NOT EXPOSE THIS STRUCT: |
| 19 | +/// |
| 20 | +/// To ensure pointers are valid upon use, the scope of this struct should |
| 21 | +/// only be limited to the stack where sol_invoke_signed_c happens and then |
| 22 | +/// discarded immediately after. |
| 23 | +#[repr(C)] |
| 24 | +#[derive(Debug, PartialEq, Clone)] |
| 25 | +struct CInstruction<'a> { |
| 26 | + /// Public key of the program. |
| 27 | + program_id: *const Pubkey, |
| 28 | + |
| 29 | + /// Accounts expected by the program instruction. |
| 30 | + accounts: *const AccountMeta<'a>, |
| 31 | + |
| 32 | + /// Number of accounts expected by the program instruction. |
| 33 | + accounts_len: u64, |
| 34 | + |
| 35 | + /// Data expected by the program instruction. |
| 36 | + data: *const u8, |
| 37 | + |
| 38 | + /// Length of the data expected by the program instruction. |
| 39 | + data_len: u64, |
| 40 | +} |
| 41 | + |
| 42 | +impl<'a> From<&Instruction<'a, '_, '_, '_>> for CInstruction<'a> { |
| 43 | + fn from(instruction: &Instruction<'a, '_, '_, '_>) -> Self { |
| 44 | + CInstruction { |
| 45 | + program_id: instruction.program_id, |
| 46 | + accounts: instruction.accounts.as_ptr(), |
| 47 | + accounts_len: instruction.accounts.len() as u64, |
| 48 | + data: instruction.data.as_ptr(), |
| 49 | + data_len: instruction.data.len() as u64, |
| 50 | + } |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +/// Invoke a cross-program instruction. |
| 55 | +/// |
| 56 | +/// # Important |
| 57 | +/// |
| 58 | +/// The accounts on the `account_infos` slice must be in the same order as the |
| 59 | +/// `accounts` field of the `instruction`. |
| 60 | +#[inline(always)] |
| 61 | +pub fn invoke<const ACCOUNTS: usize>( |
| 62 | + instruction: &Instruction, |
| 63 | + account_infos: &[&AccountInfo; ACCOUNTS], |
| 64 | +) -> ProgramResult { |
| 65 | + invoke_signed(instruction, account_infos, &[]) |
| 66 | +} |
| 67 | + |
| 68 | +/// Invoke a cross-program instruction from a slice of `AccountInfo`s. |
| 69 | +/// |
| 70 | +/// # Important |
| 71 | +/// |
| 72 | +/// The accounts on the `account_infos` slice must be in the same order as the |
| 73 | +/// `accounts` field of the `instruction`. |
| 74 | +#[inline(always)] |
| 75 | +pub fn slice_invoke(instruction: &Instruction, account_infos: &[&AccountInfo]) -> ProgramResult { |
| 76 | + slice_invoke_signed(instruction, account_infos, &[]) |
| 77 | +} |
| 78 | + |
| 79 | +/// Invoke a cross-program instruction with signatures. |
| 80 | +/// |
| 81 | +/// # Important |
| 82 | +/// |
| 83 | +/// The accounts on the `account_infos` slice must be in the same order as the |
| 84 | +/// `accounts` field of the `instruction`. |
| 85 | +pub fn invoke_signed<const ACCOUNTS: usize>( |
| 86 | + instruction: &Instruction, |
| 87 | + account_infos: &[&AccountInfo; ACCOUNTS], |
| 88 | + signers_seeds: &[Signer], |
| 89 | +) -> ProgramResult { |
| 90 | + if instruction.accounts.len() < ACCOUNTS { |
| 91 | + return Err(ProgramError::NotEnoughAccountKeys); |
| 92 | + } |
| 93 | + |
| 94 | + const UNINIT: MaybeUninit<Account> = MaybeUninit::<Account>::uninit(); |
| 95 | + let mut accounts = [UNINIT; ACCOUNTS]; |
| 96 | + |
| 97 | + for index in 0..ACCOUNTS { |
| 98 | + let account_info = account_infos[index]; |
| 99 | + let account_meta = &instruction.accounts[index]; |
| 100 | + |
| 101 | + if account_info.key() != account_meta.pubkey { |
| 102 | + return Err(ProgramError::InvalidArgument); |
| 103 | + } |
| 104 | + |
| 105 | + if account_meta.is_writable { |
| 106 | + account_info.check_borrow_mut_data()?; |
| 107 | + account_info.check_borrow_mut_lamports()?; |
| 108 | + } else { |
| 109 | + account_info.check_borrow_data()?; |
| 110 | + account_info.check_borrow_lamports()?; |
| 111 | + } |
| 112 | + |
| 113 | + accounts[index].write(Account::from(account_infos[index])); |
| 114 | + } |
| 115 | + |
| 116 | + unsafe { |
| 117 | + invoke_signed_unchecked( |
| 118 | + instruction, |
| 119 | + core::slice::from_raw_parts(accounts.as_ptr() as _, ACCOUNTS), |
| 120 | + signers_seeds, |
| 121 | + ); |
| 122 | + } |
| 123 | + |
| 124 | + Ok(()) |
| 125 | +} |
| 126 | + |
| 127 | +/// Invoke a cross-program instruction with signatures from a slice of |
| 128 | +/// `AccountInfo`s. |
| 129 | +/// |
| 130 | +/// # Important |
| 131 | +/// |
| 132 | +/// The accounts on the `account_infos` slice must be in the same order as the |
| 133 | +/// `accounts` field of the `instruction`. |
| 134 | +pub fn slice_invoke_signed( |
| 135 | + instruction: &Instruction, |
| 136 | + account_infos: &[&AccountInfo], |
| 137 | + signers_seeds: &[Signer], |
| 138 | +) -> ProgramResult { |
| 139 | + if instruction.accounts.len() < account_infos.len() { |
| 140 | + return Err(ProgramError::NotEnoughAccountKeys); |
| 141 | + } |
| 142 | + |
| 143 | + if account_infos.len() > MAX_CPI_ACCOUNTS { |
| 144 | + return Err(ProgramError::InvalidArgument); |
| 145 | + } |
| 146 | + |
| 147 | + const UNINIT: MaybeUninit<Account> = MaybeUninit::<Account>::uninit(); |
| 148 | + let mut accounts = [UNINIT; MAX_CPI_ACCOUNTS]; |
| 149 | + let mut len = 0; |
| 150 | + |
| 151 | + for (account_info, account_meta) in account_infos.iter().zip(instruction.accounts.iter()) { |
| 152 | + if account_info.key() != account_meta.pubkey { |
| 153 | + return Err(ProgramError::InvalidArgument); |
| 154 | + } |
| 155 | + |
| 156 | + if account_meta.is_writable { |
| 157 | + account_info.check_borrow_mut_data()?; |
| 158 | + account_info.check_borrow_mut_lamports()?; |
| 159 | + } else { |
| 160 | + account_info.check_borrow_data()?; |
| 161 | + account_info.check_borrow_lamports()?; |
| 162 | + } |
| 163 | + // SAFETY: The number of accounts has been validated to be less than |
| 164 | + // `MAX_CPI_ACCOUNTS`. |
| 165 | + unsafe { |
| 166 | + accounts |
| 167 | + .get_unchecked_mut(len) |
| 168 | + .write(Account::from(*account_info)); |
| 169 | + } |
| 170 | + |
| 171 | + len += 1; |
| 172 | + } |
| 173 | + // SAFETY: The accounts have been validated. |
| 174 | + unsafe { |
| 175 | + invoke_signed_unchecked( |
| 176 | + instruction, |
| 177 | + core::slice::from_raw_parts(accounts.as_ptr() as _, len), |
| 178 | + signers_seeds, |
| 179 | + ); |
| 180 | + } |
| 181 | + |
| 182 | + Ok(()) |
| 183 | +} |
| 184 | + |
| 185 | +/// Invoke a cross-program instruction but don't enforce Rust's aliasing rules. |
| 186 | +/// |
| 187 | +/// This function does not check that [`Account`]s are properly borrowable. |
| 188 | +/// Those checks consume CPU cycles that this function avoids. |
| 189 | +/// |
| 190 | +/// # Safety |
| 191 | +/// |
| 192 | +/// If any of the writable accounts passed to the callee contain data that is |
| 193 | +/// borrowed within the calling program, and that data is written to by the |
| 194 | +/// callee, then Rust's aliasing rules will be violated and cause undefined |
| 195 | +/// behavior. |
| 196 | +#[inline(always)] |
| 197 | +pub unsafe fn invoke_unchecked(instruction: &Instruction, accounts: &[Account]) { |
| 198 | + invoke_signed_unchecked(instruction, accounts, &[]) |
| 199 | +} |
| 200 | + |
| 201 | +/// Invoke a cross-program instruction with signatures but don't enforce Rust's |
| 202 | +/// aliasing rules. |
| 203 | +/// |
| 204 | +/// This function does not check that [`Account`]s are properly borrowable. |
| 205 | +/// Those checks consume CPU cycles that this function avoids. |
| 206 | +/// |
| 207 | +/// # Safety |
| 208 | +/// |
| 209 | +/// If any of the writable accounts passed to the callee contain data that is |
| 210 | +/// borrowed within the calling program, and that data is written to by the |
| 211 | +/// callee, then Rust's aliasing rules will be violated and cause undefined |
| 212 | +/// behavior. |
| 213 | +pub unsafe fn invoke_signed_unchecked( |
| 214 | + instruction: &Instruction, |
| 215 | + accounts: &[Account], |
| 216 | + signers_seeds: &[Signer], |
| 217 | +) { |
| 218 | + #[cfg(target_os = "solana")] |
| 219 | + { |
| 220 | + let instruction = CInstruction::from(instruction); |
| 221 | + unsafe { |
| 222 | + crate::syscalls::sol_invoke_signed_c( |
| 223 | + &instruction as *const _ as *const u8, |
| 224 | + accounts as *const _ as *const u8, |
| 225 | + accounts.len() as u64, |
| 226 | + signers_seeds as *const _ as *const u8, |
| 227 | + signers_seeds.len() as u64, |
| 228 | + ) |
| 229 | + }; |
| 230 | + } |
| 231 | + |
| 232 | + #[cfg(not(target_os = "solana"))] |
| 233 | + core::hint::black_box((instruction, accounts, signers_seeds)); |
| 234 | +} |
| 235 | + |
| 236 | +/// Maximum size that can be set using [`set_return_data`]. |
| 237 | +pub const MAX_RETURN_DATA: usize = 1024; |
| 238 | + |
| 239 | +/// Set the running program's return data. |
| 240 | +/// |
| 241 | +/// Return data is a dedicated per-transaction buffer for data passed |
| 242 | +/// from cross-program invoked programs back to their caller. |
| 243 | +/// |
| 244 | +/// The maximum size of return data is [`MAX_RETURN_DATA`]. Return data is |
| 245 | +/// retrieved by the caller with [`get_return_data`]. |
| 246 | +pub fn set_return_data(data: &[u8]) { |
| 247 | + #[cfg(target_os = "solana")] |
| 248 | + unsafe { |
| 249 | + crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64) |
| 250 | + }; |
| 251 | + |
| 252 | + #[cfg(not(target_os = "solana"))] |
| 253 | + core::hint::black_box(data); |
| 254 | +} |
| 255 | + |
| 256 | +/// Get the return data from an invoked program. |
| 257 | +/// |
| 258 | +/// For every transaction there is a single buffer with maximum length |
| 259 | +/// [`MAX_RETURN_DATA`], paired with a [`Pubkey`] representing the program ID of |
| 260 | +/// the program that most recently set the return data. Thus the return data is |
| 261 | +/// a global resource and care must be taken to ensure that it represents what |
| 262 | +/// is expected: called programs are free to set or not set the return data; and |
| 263 | +/// the return data may represent values set by programs multiple calls down the |
| 264 | +/// call stack, depending on the circumstances of transaction execution. |
| 265 | +/// |
| 266 | +/// Return data is set by the callee with [`set_return_data`]. |
| 267 | +/// |
| 268 | +/// Return data is cleared before every CPI invocation — a program that |
| 269 | +/// has invoked no other programs can expect the return data to be `None`; if no |
| 270 | +/// return data was set by the previous CPI invocation, then this function |
| 271 | +/// returns `None`. |
| 272 | +/// |
| 273 | +/// Return data is not cleared after returning from CPI invocations — a |
| 274 | +/// program that has called another program may retrieve return data that was |
| 275 | +/// not set by the called program, but instead set by a program further down the |
| 276 | +/// call stack; or, if a program calls itself recursively, it is possible that |
| 277 | +/// the return data was not set by the immediate call to that program, but by a |
| 278 | +/// subsequent recursive call to that program. Likewise, an external RPC caller |
| 279 | +/// may see return data that was not set by the program it is directly calling, |
| 280 | +/// but by a program that program called. |
| 281 | +/// |
| 282 | +/// For more about return data see the [documentation for the return data proposal][rdp]. |
| 283 | +/// |
| 284 | +/// [rdp]: https://docs.solanalabs.com/proposals/return-data |
| 285 | +pub fn get_return_data() -> Option<ReturnData> { |
| 286 | + #[cfg(target_os = "solana")] |
| 287 | + { |
| 288 | + const UNINIT_BYTE: core::mem::MaybeUninit<u8> = core::mem::MaybeUninit::<u8>::uninit(); |
| 289 | + let mut data = [UNINIT_BYTE; MAX_RETURN_DATA]; |
| 290 | + let mut program_id = Pubkey::default(); |
| 291 | + |
| 292 | + let size = unsafe { |
| 293 | + crate::syscalls::sol_get_return_data( |
| 294 | + data.as_mut_ptr() as *mut u8, |
| 295 | + data.len() as u64, |
| 296 | + &mut program_id, |
| 297 | + ) |
| 298 | + }; |
| 299 | + |
| 300 | + if size == 0 { |
| 301 | + None |
| 302 | + } else { |
| 303 | + Some(ReturnData { |
| 304 | + program_id, |
| 305 | + data, |
| 306 | + size: core::cmp::min(size as usize, MAX_RETURN_DATA), |
| 307 | + }) |
| 308 | + } |
| 309 | + } |
| 310 | + |
| 311 | + #[cfg(not(target_os = "solana"))] |
| 312 | + core::hint::black_box(None) |
| 313 | +} |
| 314 | + |
| 315 | +/// Struct to hold the return data from an invoked program. |
| 316 | +pub struct ReturnData { |
| 317 | + /// Program that most recently set the return data. |
| 318 | + program_id: Pubkey, |
| 319 | + |
| 320 | + /// Return data set by the program. |
| 321 | + data: [core::mem::MaybeUninit<u8>; MAX_RETURN_DATA], |
| 322 | + |
| 323 | + /// Length of the return data. |
| 324 | + size: usize, |
| 325 | +} |
| 326 | + |
| 327 | +impl ReturnData { |
| 328 | + /// Returns the program that most recently set the return data. |
| 329 | + pub fn program_id(&self) -> &Pubkey { |
| 330 | + &self.program_id |
| 331 | + } |
| 332 | + |
| 333 | + /// Return the data set by the program. |
| 334 | + pub fn as_slice(&self) -> &[u8] { |
| 335 | + unsafe { core::slice::from_raw_parts(self.data.as_ptr() as _, self.size) } |
| 336 | + } |
| 337 | +} |
| 338 | + |
| 339 | +impl Deref for ReturnData { |
| 340 | + type Target = [u8]; |
| 341 | + |
| 342 | + fn deref(&self) -> &Self::Target { |
| 343 | + self.as_slice() |
| 344 | + } |
| 345 | +} |
0 commit comments