rialo_s_cpi/lib.rs
1//! Cross-program invocation.
2//!
3//! Solana programs may call other programs, termed [_cross-program
4//! invocations_][cpi] (CPI), with the [`invoke`] and [`invoke_signed`]
5//! functions.
6//!
7//! This crate does not support overwriting syscall stubs for offchain code.
8//! If you want to overwrite syscall stubs, use the wrapper functions in
9//! [`rialo_s_program::program`].
10//!
11//! [`invoke`]: invoke
12//! [`invoke_signed`]: invoke_signed
13//! [cpi]: https://solana.com/docs/core/cpi
14//! [`rialo_s_program::program`]: https://docs.rs/solana-program/latest/rialo_s_program/program/
15
16#![allow(unsafe_code)]
17
18use rialo_s_account_info::AccountInfo;
19use rialo_s_instruction::{AccountMeta, Instruction};
20use rialo_s_program_error::ProgramResult;
21use rialo_s_pubkey::Pubkey;
22#[cfg(target_os = "solana")]
23pub mod syscalls;
24
25/// Invoke a cross-program instruction.
26///
27/// Invoking one program from another program requires an [`Instruction`]
28/// containing the program ID of the other program, instruction data that
29/// will be understood by the other program, and a list of [`AccountInfo`]s
30/// corresponding to all of the accounts accessed by the other program. Because
31/// the only way for a program to acquire `AccountInfo` values is by receiving
32/// them from the runtime at the [program entrypoint][entrypoint!], any account
33/// required by the callee program must transitively be required by the caller
34/// program, and provided by _its_ caller. The same is true of the program ID of
35/// the called program.
36///
37/// [entrypoint!]: https://docs.rs/solana-entrypoint/latest/solana_entrypoint/macro.entrypoint.html
38///
39/// The `Instruction` is usually built from within the calling program, but may
40/// be deserialized from an external source.
41///
42/// This function will not return if the called program returns anything other
43/// than success. If the callee returns an error or aborts then the entire
44/// transaction will immediately fail. To return data as the result of a
45/// cross-program invocation use the [`set_return_data`] / [`get_return_data`]
46/// functions, or have the callee write to a dedicated account for that purpose.
47///
48/// A program may directly call itself recursively, but may not be indirectly
49/// called recursively (reentered) by another program. Indirect reentrancy will
50/// cause the transaction to immediately fail.
51///
52/// # Validation of shared data between programs
53///
54/// The `AccountInfo` structures passed to this function contain data that is
55/// directly accessed by the runtime and is copied to and from the memory space
56/// of the called program. Some of that data, the [`AccountInfo::kelvins`] and
57/// [`AccountInfo::data`] fields, may be mutated as a side-effect of the called
58/// program, if that program has writable access to the given account.
59///
60/// These two fields are stored in [`RefCell`]s to enforce the aliasing
61/// discipline for mutated values required by the Rust language. Prior to
62/// invoking the runtime, this routine will test that each `RefCell` is
63/// borrowable as required by the callee and return an error if not.
64///
65/// The CPU cost of these runtime checks can be avoided with the unsafe
66/// [`invoke_unchecked`] function.
67///
68/// [`RefCell`]: std::cell::RefCell
69///
70/// # Errors
71///
72/// If the called program completes successfully and violates no runtime
73/// invariants, then this function will return successfully. If the callee
74/// completes and returns a [`ProgramError`], then the transaction will
75/// immediately fail. Control will not return to the caller.
76///
77/// Various runtime invariants are checked before the callee is invoked and
78/// before returning control to the caller. If any of these invariants are
79/// violated then the transaction will immediately fail. A non-exhaustive list
80/// of these invariants includes:
81///
82/// - The sum of kelvins owned by all referenced accounts has not changed.
83/// - A program has not debited kelvins from an account it does not own.
84/// - A program has not otherwise written to an account that it does not own.
85/// - A program has not written to an account that is not writable.
86/// - The size of account data has not exceeded applicable limits.
87///
88/// If the invoked program does not exist or is not executable then
89/// the transaction will immediately fail.
90///
91/// If any of the `RefCell`s within the provided `AccountInfo`s cannot be
92/// borrowed in accordance with the call's requirements, an error of
93/// [`ProgramError::AccountBorrowFailed`] is returned.
94///
95/// [`ProgramError`]: https://docs.rs/solana-program-error/latest/rialo_s_program_error/enum.ProgramError.html
96/// [`ProgramError::AccountBorrowFailed`]: https://docs.rs/solana-program-error/latest/rialo_s_program_error/enum.ProgramError.html#variant.AccountBorrowFailed
97///
98/// # Examples
99///
100/// A simple example of transferring kelvins via CPI:
101///
102/// ```
103/// use rialo_s_cpi::invoke;
104/// use rialo_s_account_info::{next_account_info, AccountInfo};
105/// use rialo_s_program_entrypoint::entrypoint;
106/// use rialo_s_program_error::ProgramResult;
107/// use rialo_s_pubkey::Pubkey;
108/// use rialo_s_sdk_ids::system_program;
109/// use rialo_s_system_interface::instruction as system_instruction;
110///
111/// entrypoint!(process_instruction);
112///
113/// fn process_instruction(
114/// program_id: &Pubkey,
115/// accounts: &[AccountInfo],
116/// instruction_data: &[u8],
117/// ) -> ProgramResult {
118/// let account_info_iter = &mut accounts.iter();
119///
120/// let payer = next_account_info(account_info_iter)?;
121/// let recipient = next_account_info(account_info_iter)?;
122/// // The system program is a required account to invoke a system
123/// // instruction, even though we don't use it directly.
124/// let system_program_account = next_account_info(account_info_iter)?;
125///
126/// assert!(payer.is_writable);
127/// assert!(payer.is_signer);
128/// assert!(recipient.is_writable);
129/// assert!(system_program::check_id(system_program_account.key));
130///
131/// let kelvins = 1000000;
132///
133/// invoke(
134/// &system_instruction::transfer(payer.key, recipient.key, kelvins),
135/// &[payer.clone(), recipient.clone(), system_program_account.clone()],
136/// )
137/// }
138/// ```
139pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo<'_>]) -> ProgramResult {
140 invoke_signed(instruction, account_infos, &[])
141}
142
143/// Invoke a cross-program instruction but don't enforce Rust's aliasing rules.
144///
145/// This function is like [`invoke`] except that it does not check that
146/// [`RefCell`]s within [`AccountInfo`]s are properly borrowable as described in
147/// the documentation for that function. Those checks consume CPU cycles that
148/// this function avoids.
149///
150/// [`RefCell`]: std::cell::RefCell
151///
152/// # Safety
153///
154/// __This function is incorrectly missing an `unsafe` declaration.__
155///
156/// If any of the writable accounts passed to the callee contain data that is
157/// borrowed within the calling program, and that data is written to by the
158/// callee, then Rust's aliasing rules will be violated and cause undefined
159/// behavior.
160pub fn invoke_unchecked(
161 instruction: &Instruction,
162 account_infos: &[AccountInfo<'_>],
163) -> ProgramResult {
164 invoke_signed_unchecked(instruction, account_infos, &[])
165}
166
167/// Invoke a cross-program instruction with program signatures.
168///
169/// This function is like [`invoke`] with the additional ability to virtually
170/// sign an invocation on behalf of one or more [program derived addresses][pda] (PDAs)
171/// controlled by the calling program, allowing the callee to mutate them, or
172/// otherwise confirm that a PDA program key has authorized the actions of the
173/// callee.
174///
175/// There is no cryptographic signing involved — PDA signing is a runtime
176/// construct that allows the calling program to control accounts as if it could
177/// cryptographically sign for them; and the callee to treat the account as if it
178/// was cryptographically signed.
179///
180/// The `signer_seeds` parameter is a slice of `u8` slices where the inner
181/// slices represent the seeds plus the _bump seed_ used to derive (with
182/// [`Pubkey::find_program_address`]) one of the PDAs within the `account_infos`
183/// slice of `AccountInfo`s. During invocation, the runtime will re-derive the
184/// PDA from the seeds and the calling program's ID, and if it matches one of
185/// the accounts in `account_info`, will consider that account "signed".
186///
187/// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
188/// [`Pubkey::find_program_address`]: https://docs.rs/solana-pubkey/latest/rialo_s_pubkey/struct.Pubkey.html#method.find_program_address
189///
190/// See the documentation for [`Pubkey::find_program_address`] for more
191/// about program derived addresses.
192///
193/// # Examples
194///
195/// A simple example of creating an account for a PDA:
196///
197/// ```
198/// use rialo_s_cpi::invoke_signed;
199/// use rialo_s_account_info::{next_account_info, AccountInfo};
200/// use rialo_s_program_entrypoint::entrypoint;
201/// use rialo_s_program_error::ProgramResult;
202/// use rialo_s_pubkey::Pubkey;
203/// use rialo_s_sdk_ids::system_program;
204/// use rialo_s_system_interface::instruction as system_instruction;
205///
206/// entrypoint!(process_instruction);
207///
208/// fn process_instruction(
209/// program_id: &Pubkey,
210/// accounts: &[AccountInfo],
211/// instruction_data: &[u8],
212/// ) -> ProgramResult {
213/// let account_info_iter = &mut accounts.iter();
214/// let payer = next_account_info(account_info_iter)?;
215/// let vault_pda = next_account_info(account_info_iter)?;
216/// let system_program = next_account_info(account_info_iter)?;
217///
218/// assert!(payer.is_writable);
219/// assert!(payer.is_signer);
220/// assert!(vault_pda.is_writable);
221/// assert_eq!(vault_pda.owner, &system_program::ID);
222/// assert!(system_program::check_id(system_program.key));
223///
224/// let vault_bump_seed = instruction_data[0];
225/// let vault_seeds = &[b"vault", payer.key.as_ref(), &[vault_bump_seed]];
226/// let expected_vault_pda = Pubkey::create_program_address(vault_seeds, program_id)?;
227///
228/// assert_eq!(vault_pda.key, &expected_vault_pda);
229///
230/// let kelvins = 10000000;
231/// let vault_size = 16;
232///
233/// invoke_signed(
234/// &system_instruction::create_account(
235/// &payer.key,
236/// &vault_pda.key,
237/// kelvins,
238/// vault_size,
239/// &program_id,
240/// ),
241/// &[
242/// payer.clone(),
243/// vault_pda.clone(),
244/// ],
245/// &[
246/// &[
247/// b"vault",
248/// payer.key.as_ref(),
249/// &[vault_bump_seed],
250/// ],
251/// ]
252/// )?;
253/// Ok(())
254/// }
255/// ```
256pub fn invoke_signed(
257 instruction: &Instruction,
258 account_infos: &[AccountInfo<'_>],
259 signers_seeds: &[&[&[u8]]],
260) -> ProgramResult {
261 // Check that the account RefCells are consistent with the request
262 for account_meta in instruction.accounts.iter() {
263 for account_info in account_infos.iter() {
264 if account_meta.pubkey == *account_info.key {
265 if account_meta.is_writable {
266 let _ = account_info.try_borrow_mut_kelvins()?;
267 let _ = account_info.try_borrow_mut_data()?;
268 } else {
269 let _ = account_info.try_borrow_kelvins()?;
270 let _ = account_info.try_borrow_data()?;
271 }
272 break;
273 }
274 }
275 }
276
277 invoke_signed_unchecked(instruction, account_infos, signers_seeds)
278}
279
280/// Copied from `rialo_s_program_entrypoint::SUCCESS`
281/// to avoid a `rialo_s_program_entrypoint` dependency
282const _SUCCESS: u64 = 0;
283#[cfg(test)]
284static_assertions::const_assert_eq!(_SUCCESS, rialo_s_program_entrypoint::SUCCESS);
285
286/// Marshalled `AccountInfo` containing raw pointers to VM memory.
287///
288/// Allows the RISC-V executor to extract account info for CPI calls.
289#[repr(C)]
290pub struct HostAccountInfo {
291 pub key: Pubkey,
292 pub kelvins: *mut u64,
293 pub data_ptr: *mut u8,
294 pub data_len: usize,
295 pub data_len_ptr: *mut u8,
296 pub owner: *mut Pubkey,
297 pub rent_epoch: u64,
298 pub is_signer: bool,
299 pub is_writable: bool,
300 pub executable: bool,
301}
302
303impl From<&AccountInfo<'_>> for HostAccountInfo {
304 fn from(account: &AccountInfo<'_>) -> Self {
305 let kelvins_ptr: &mut u64 = &mut account.kelvins.borrow_mut();
306 let data_ref: &mut [u8] = &mut account.data.borrow_mut();
307 let data_ptr = data_ref.as_mut_ptr();
308 let data_len = data_ref.len();
309 let data_len_ptr = account.data.as_ptr() as *const u64 as u64 + 8;
310
311 HostAccountInfo {
312 key: *account.key,
313 kelvins: kelvins_ptr,
314 data_ptr,
315 data_len,
316 data_len_ptr: data_len_ptr as *mut u8,
317 owner: account.owner as *const _ as *mut Pubkey,
318 rent_epoch: account.rent_epoch,
319 is_signer: account.is_signer,
320 is_writable: account.is_writable,
321 executable: account.executable,
322 }
323 }
324}
325
326/// Marshal `&[AccountInfo]` into `Vec<HostAccountInfo>` suitable for RISC-V unmarshalling.
327pub fn marshal_accounts(accounts: &[AccountInfo<'_>]) -> Vec<HostAccountInfo> {
328 accounts.iter().map(HostAccountInfo::from).collect()
329}
330
331/// Marshalled `Instruction` containing raw pointers to VM memory.
332///
333/// Allows the RISC-V executor to extract account info for CPI calls.
334#[repr(C)]
335pub struct HostInstruction {
336 pub program_id: Pubkey,
337 pub accounts_ptr: *const AccountMeta,
338 pub accounts_len: usize,
339 pub data_ptr: *const u8,
340 pub data_len: usize,
341}
342
343/// Marshal an `Instruction` into a `HostInstruction` suitable for RISC-V unmarshalling.
344pub fn marshal_instruction(instruction: &Instruction) -> HostInstruction {
345 HostInstruction {
346 program_id: instruction.program_id,
347 accounts_ptr: instruction.accounts.as_ptr(),
348 accounts_len: instruction.accounts.len(),
349 data_ptr: instruction.data.as_ptr(),
350 data_len: instruction.data.len(),
351 }
352}
353
354/// Invoke a cross-program instruction with signatures but don't enforce Rust's
355/// aliasing rules.
356///
357/// This function is like [`invoke_signed`] except that it does not check that
358/// [`RefCell`]s within [`AccountInfo`]s are properly borrowable as described in
359/// the documentation for that function. Those checks consume CPU cycles that
360/// this function avoids.
361///
362/// [`RefCell`]: std::cell::RefCell
363///
364/// # Safety
365///
366/// __This function is incorrectly missing an `unsafe` declaration.__
367///
368/// If any of the writable accounts passed to the callee contain data that is
369/// borrowed within the calling program, and that data is written to by the
370/// callee, then Rust's aliasing rules will be violated and cause undefined
371/// behavior.
372#[allow(unused_variables)]
373pub fn invoke_signed_unchecked(
374 instruction: &Instruction,
375 account_infos: &[AccountInfo<'_>],
376 signers_seeds: &[&[&[u8]]],
377) -> ProgramResult {
378 #[cfg(all(target_os = "solana", not(target_arch = "riscv64")))]
379 {
380 let instruction =
381 rialo_s_stable_layout::stable_instruction::StableInstruction::from(instruction.clone());
382 let result = unsafe {
383 crate::syscalls::rlo_invoke_signed_rust(
384 &instruction as *const _ as *const u8,
385 account_infos as *const _ as *const u8,
386 account_infos.len() as u64,
387 signers_seeds as *const _ as *const u8,
388 signers_seeds.len() as u64,
389 )
390 };
391 match result {
392 _SUCCESS => Ok(()),
393 _ => Err(result.into()),
394 }
395 }
396
397 #[cfg(all(target_os = "solana", target_arch = "riscv64"))]
398 {
399 let instruction = marshal_instruction(instruction);
400 let instruction_ptr: *const HostInstruction = &instruction;
401
402 let accounts = marshal_accounts(account_infos);
403 let account_infos = accounts.as_ptr();
404 let account_sizes = accounts.len();
405
406 let result = unsafe {
407 crate::syscalls::rlo_invoke_signed_rust(
408 instruction_ptr as *const _ as *const u8,
409 account_infos as *const _ as *const u8,
410 account_sizes as u64,
411 signers_seeds as *const _ as *const u8,
412 signers_seeds.len() as u64,
413 )
414 };
415 match result {
416 _SUCCESS => Ok(()),
417 _ => Err(result.into()),
418 }
419 }
420
421 #[cfg(not(target_os = "solana"))]
422 Ok(())
423}
424
425/// Maximum size that can be set using [`set_return_data`].
426pub const MAX_RETURN_DATA: usize = 1024;
427
428/// Set the running program's return data.
429///
430/// Return data is a dedicated per-transaction buffer for data passed
431/// from cross-program invoked programs back to their caller.
432///
433/// The maximum size of return data is [`MAX_RETURN_DATA`]. Return data is
434/// retrieved by the caller with [`get_return_data`].
435#[allow(unused_variables)]
436pub fn set_return_data(data: &[u8]) {
437 #[cfg(target_os = "solana")]
438 unsafe {
439 crate::syscalls::rlo_set_return_data(data.as_ptr(), data.len() as u64)
440 };
441}
442
443/// Get the return data from an invoked program.
444///
445/// For every transaction there is a single buffer with maximum length
446/// [`MAX_RETURN_DATA`], paired with a [`Pubkey`] representing the program ID of
447/// the program that most recently set the return data. Thus the return data is
448/// a global resource and care must be taken to ensure that it represents what
449/// is expected: called programs are free to set or not set the return data; and
450/// the return data may represent values set by programs multiple calls down the
451/// call stack, depending on the circumstances of transaction execution.
452///
453/// Return data is set by the callee with [`set_return_data`].
454///
455/// Return data is cleared before every CPI invocation — a program that
456/// has invoked no other programs can expect the return data to be `None`; if no
457/// return data was set by the previous CPI invocation, then this function
458/// returns `None`.
459///
460/// Return data is not cleared after returning from CPI invocations — a
461/// program that has called another program may retrieve return data that was
462/// not set by the called program, but instead set by a program further down the
463/// call stack; or, if a program calls itself recursively, it is possible that
464/// the return data was not set by the immediate call to that program, but by a
465/// subsequent recursive call to that program. Likewise, an external RPC caller
466/// may see return data that was not set by the program it is directly calling,
467/// but by a program that program called.
468///
469/// For more about return data see the [documentation for the return data proposal][rdp].
470///
471/// [rdp]: https://docs.solanalabs.com/proposals/return-data
472pub fn get_return_data() -> Option<(Pubkey, Vec<u8>)> {
473 #[cfg(target_os = "solana")]
474 {
475 use std::cmp::min;
476
477 let mut buf = [0u8; MAX_RETURN_DATA];
478 let mut program_id = Pubkey::default();
479
480 let size = unsafe {
481 crate::syscalls::rlo_get_return_data(
482 buf.as_mut_ptr(),
483 buf.len() as u64,
484 &mut program_id,
485 )
486 };
487
488 if size == 0 {
489 None
490 } else {
491 let size = min(size as usize, MAX_RETURN_DATA);
492 Some((program_id, buf[..size as usize].to_vec()))
493 }
494 }
495
496 #[cfg(not(target_os = "solana"))]
497 None
498}