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}