Cross Program Invocation
Uma invocação entre programas (CPI) ocorre quando um programa Solana invoca diretamente as instruções de outro programa. Isso permite a composição de programas. Se você pensar em uma instrução da Solana como um endpoint de API que um programa expõe para a rede, um CPI é como um endpoint invocando internamente outro.
Ao fazer um CPI, um programa pode assinar em nome de um PDA derivado do seu ID de programa. Esses privilégios de assinatura se estendem do programa chamador para o programa chamado.
Exemplo de invocação entre programas
Ao fazer um CPI, os privilégios da conta se estendem de um programa para outro. Vamos dizer que o Programa A recebe uma instrução com uma conta assinante e uma conta gravável. O Programa A então faz um CPI para o Programa B. Agora, o Programa B pode usar as mesmas contas que o Programa A, com suas permissões originais. (Isso significa que o Programa B pode assinar com a conta assinante e pode escrever na conta gravável.) Se o Programa B fizer seus próprios CPIs, ele pode passar essas mesmas permissões adiante, até uma profundidade de 4.
A altura máxima da invocação de instrução do programa é chamada de
max_instruction_stack_depth
e é definida como a constante MAX_INSTRUCTION_STACK_DEPTH
de 5.
A altura da pilha começa em 1 para a transação inicial e aumenta em 1 cada vez que um programa invoca outra instrução, limitando a profundidade de invocação para CPIs a 4.
CPIs com assinantes PDA
Quando um CPI requer um assinante PDA, a função
invoke_signed
é usada. Ela recebe as seeds do assinante usadas para
derivar PDAs assinantes. O runtime da Solana internamente
chama
create_program_address
usando o signers_seeds e o program_id do programa chamador. Quando um PDA é
verificado, ele é
adicionado como um assinante válido.
pub fn invoke_signed(instruction: &Instruction,account_infos: &[AccountInfo],signers_seeds: &[&[&[u8]]],) -> ProgramResult {// --snip--invoke_signed_unchecked(instruction, account_infos, signers_seeds)}
Os exemplos abaixo fazem um CPI com assinantes PDA usando Anchor e Rust Nativo. Cada exemplo inclui uma única instrução para transferir SOL de um PDA para uma conta destinatária, usando um CPI assinado pelo PDA.
Anchor
Os exemplos a seguir mostram três abordagens para implementar CPIs em um programa Anchor. Os exemplos são funcionalmente equivalentes, mas cada um demonstra um nível diferente de abstração.
- Exemplo 1: Usa o
CpiContextdo Anchor e função auxiliar. - Exemplo 2: Usa a função
system_instruction::transferdo cratesolana_program. (O exemplo 1 é uma abstração desta implementação.) - Exemplo 3: Constrói a instrução CPI manualmente. Esta abordagem é útil quando não existe um crate disponível para ajudar a construir a instrução que você deseja invocar.
use anchor_lang::prelude::*;use anchor_lang::system_program::{transfer, Transfer};declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");#[program]pub mod cpi {use super::*;pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {let from_pubkey = ctx.accounts.pda_account.to_account_info();let to_pubkey = ctx.accounts.recipient.to_account_info();let program_id = ctx.accounts.system_program.to_account_info();let seed = to_pubkey.key();let bump_seed = ctx.bumps.pda_account;let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];let cpi_context = CpiContext::new(program_id,Transfer {from: from_pubkey,to: to_pubkey,},).with_signer(signer_seeds);transfer(cpi_context, amount)?;Ok(())}}#[derive(Accounts)]pub struct SolTransfer<'info> {#[account(mut,seeds = [b"pda", recipient.key().as_ref()],bump,)]pda_account: SystemAccount<'info>,#[account(mut)]recipient: SystemAccount<'info>,system_program: Program<'info, System>,}
Rust
O exemplo abaixo faz um CPI com assinantes PDA a partir de um programa escrito em Rust Nativo. Ele inclui uma única instrução que transfere SOL de uma conta PDA para outra. O CPI é assinado pela conta PDA. (O arquivo de teste usa LiteSVM para testar o programa.)
use borsh::BorshDeserialize;use solana_program::{account_info::AccountInfo,entrypoint,entrypoint::ProgramResult,program::invoke_signed,program_error::ProgramError,pubkey::Pubkey,system_instruction,};// Declare program entrypointentrypoint!(process_instruction);// Define program instructions#[derive(BorshDeserialize)]enum ProgramInstruction {SolTransfer { amount: u64 },}impl ProgramInstruction {fn unpack(input: &[u8]) -> Result<Self, ProgramError> {Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)}}pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {// Deserialize instruction datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [pda_account_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Derive PDA and verify it matches the account provided by clientlet recipient_pubkey = recipient_info.key;let seeds = &[b"pda", recipient_pubkey.as_ref()];let (expected_pda, bump_seed) = Pubkey::find_program_address(seeds, program_id);if expected_pda != *pda_account_info.key {return Err(ProgramError::InvalidArgument);}// Create the transfer instructionlet transfer_ix = system_instruction::transfer(pda_account_info.key,recipient_info.key,amount,);// Create signer seeds for PDAlet signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];// Invoke the transfer instruction with PDA as signerinvoke_signed(&transfer_ix,&[pda_account_info.clone(),recipient_info.clone(),system_program_info.clone(),],signer_seeds,)?;Ok(())}}}
CPIs sem assinantes PDA
Quando um CPI não requer assinantes PDA, a função
invoke
é usada. A função invoke chama a função invoke_signed com um array
signers_seeds vazio. O array de assinantes vazio indica que nenhum PDA é
necessário para assinatura.
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {invoke_signed(instruction, account_infos, &[])}
Os exemplos abaixo fazem um CPI usando Anchor e Rust Nativo. Incluem uma única instrução que transfere SOL de uma conta para outra.
Anchor
Os exemplos a seguir mostram três abordagens para implementar CPIs em um programa Anchor. Os exemplos são funcionalmente equivalentes, mas cada um demonstra um nível diferente de abstração.
- Exemplo 1: Usa o
CpiContextdo Anchor e função auxiliar. - Exemplo 2: Usa a função
system_instruction::transferdo cratesolana_program. - Exemplo 3: Constrói a instrução CPI manualmente. Esta abordagem é útil quando não existe um crate para ajudar a construir a instrução que você deseja invocar.
use anchor_lang::prelude::*;use anchor_lang::system_program::{transfer, Transfer};declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi");#[program]pub mod cpi {use super::*;pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {let from_pubkey = ctx.accounts.sender.to_account_info();let to_pubkey = ctx.accounts.recipient.to_account_info();let program_id = ctx.accounts.system_program.to_account_info();let cpi_context = CpiContext::new(program_id,Transfer {from: from_pubkey,to: to_pubkey,},);transfer(cpi_context, amount)?;Ok(())}}#[derive(Accounts)]pub struct SolTransfer<'info> {#[account(mut)]sender: Signer<'info>,#[account(mut)]recipient: SystemAccount<'info>,system_program: Program<'info, System>,}
Rust
O exemplo a seguir mostra como fazer um CPI a partir de um programa escrito em Rust Nativo. Inclui uma única instrução que transfere SOL de uma conta para outra. (O arquivo de teste usa LiteSVM para testar o programa.)
use borsh::BorshDeserialize;use solana_program::{account_info::AccountInfo,entrypoint,entrypoint::ProgramResult,program::invoke,program_error::ProgramError,pubkey::Pubkey,system_instruction,};// Declare program entrypointentrypoint!(process_instruction);// Define program instructions#[derive(BorshDeserialize)]enum ProgramInstruction {SolTransfer { amount: u64 },}impl ProgramInstruction {fn unpack(input: &[u8]) -> Result<Self, ProgramError> {Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)}}pub fn process_instruction(_program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {// Deserialize instruction datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [sender_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Verify the sender is a signerif !sender_info.is_signer {return Err(ProgramError::MissingRequiredSignature);}// Create and invoke the transfer instructionlet transfer_ix = system_instruction::transfer(sender_info.key,recipient_info.key,amount,);invoke(&transfer_ix,&[sender_info.clone(),recipient_info.clone(),system_program_info.clone(),],)?;Ok(())}}}
Is this page helpful?