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