Function solana_sdk::program::invoke

source ·
pub fn invoke(
    instruction: &Instruction,
    account_infos: &[AccountInfo<'_>]
) -> Result<(), ProgramError>
Expand description

Invoke a cross-program instruction.

Invoking one program from another program requires an Instruction containing the program ID of the other program, instruction data that will be understood by the other program, and a list of AccountInfos corresponding to all of the accounts accessed by the other program. Because the only way for a program to acquire AccountInfo values is by receiving them from the runtime at the [program entrypoint][entrypoint!], any account required by the callee program must transitively be required by the caller program, and provided by its caller. The same is true of the program ID of the called program.

The Instruction is usually built from within the calling program, but may be deserialized from an external source.

This function will not return if the called program returns anything other than success. If the callee returns an error or aborts then the entire transaction will immediately fail. To return data as the result of a cross-program invocation use the set_return_data / get_return_data functions, or have the callee write to a dedicated account for that purpose.

A program may directly call itself recursively, but may not be indirectly called recursively (reentered) by another program. Indirect reentrancy will cause the transaction to immediately fail.

Validation of shared data between programs

The AccountInfo structures passed to this function contain data that is directly accessed by the runtime and is copied to and from the memory space of the called program. Some of that data, the AccountInfo::lamports and AccountInfo::data fields, may be mutated as a side-effect of the called program, if that program has writable access to the given account.

These two fields are stored in RefCells to enforce the aliasing discipline for mutated values required by the Rust language. Prior to invoking the runtime, this routine will test that each RefCell is borrowable as required by the callee and return an error if not.

The CPU cost of these runtime checks can be avoided with the unsafe invoke_unchecked function.

Errors

If the called program completes successfully and violates no runtime invariants, then this function will return successfully. If the callee completes and returns a ProgramError, then the transaction will immediately fail. Control will not return to the caller.

Various runtime invariants are checked before the callee is invoked and before returning control to the caller. If any of these invariants are violated then the transaction will immediately fail. A non-exhaustive list of these invariants includes:

  • The sum of lamports owned by all referenced accounts has not changed.
  • A program has not debited lamports from an account it does not own.
  • A program has not otherwise written to an account that it does not own.
  • A program has not written to an account that is not writable.
  • The size of account data has not exceeded applicable limits.

If the invoked program does not exist or is not executable then the transaction will immediately fail.

If any of the RefCells within the provided AccountInfos cannot be borrowed in accordance with the call’s requirements, an error of ProgramError::AccountBorrowFailed is returned.

Examples

A simple example of transferring lamports via CPI:

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    program::invoke,
    pubkey::Pubkey,
    system_instruction,
    system_program,
};

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let payer = next_account_info(account_info_iter)?;
    let recipient = next_account_info(account_info_iter)?;
    // The system program is a required account to invoke a system
    // instruction, even though we don't use it directly.
    let system_program_account = next_account_info(account_info_iter)?;

    assert!(payer.is_writable);
    assert!(payer.is_signer);
    assert!(recipient.is_writable);
    assert!(system_program::check_id(system_program_account.key));

    let lamports = 1000000;

    invoke(
        &system_instruction::transfer(payer.key, recipient.key, lamports),
        &[payer.clone(), recipient.clone(), system_program_account.clone()],
    )
}