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 programasExemplo 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.

Invoke signed
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 CpiContext do Anchor e função auxiliar.
  • Exemplo 2: Usa a função system_instruction::transfer do crate solana_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 entrypoint
entrypoint!(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 data
let instruction = ProgramInstruction::unpack(instruction_data)?;
// Process instruction
match instruction {
ProgramInstruction::SolTransfer { amount } => {
// Parse accounts
let [pda_account_info, recipient_info, system_program_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Derive PDA and verify it matches the account provided by client
let 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 instruction
let transfer_ix = system_instruction::transfer(
pda_account_info.key,
recipient_info.key,
amount,
);
// Create signer seeds for PDA
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];
// Invoke the transfer instruction with PDA as signer
invoke_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.

Invoke function
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 CpiContext do Anchor e função auxiliar.
  • Exemplo 2: Usa a função system_instruction::transfer do crate solana_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 entrypoint
entrypoint!(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 data
let instruction = ProgramInstruction::unpack(instruction_data)?;
// Process instruction
match instruction {
ProgramInstruction::SolTransfer { amount } => {
// Parse accounts
let [sender_info, recipient_info, system_program_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Verify the sender is a signer
if !sender_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Create and invoke the transfer instruction
let 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?

Índice

Editar Página

Gerenciado por

© 2025 Fundação Solana.
Todos os direitos reservados.