Cross Program Invocation

Una Cross Program Invocation (CPI) si verifica quando un programma Solana invoca direttamente le istruzioni di un altro programma. Questo permette la componibilità dei programmi. Se pensi a un'istruzione Solana come a un endpoint API che un programma espone alla rete, una CPI è come un endpoint che internamente invoca un altro endpoint.

Quando effettua una CPI, un programma può firmare per conto di un PDA derivato dal suo ID programma. Questi privilegi di firma si estendono dal programma chiamante al programma chiamato.

Esempio di Cross-program invocationEsempio di Cross-program invocation

Quando si effettua una CPI, i privilegi dell'account si estendono da un programma all'altro. Supponiamo che il Programma A riceva un'istruzione con un account firmatario e un account scrivibile. Il Programma A effettua quindi una CPI al Programma B. Ora, il Programma B può utilizzare gli stessi account del Programma A, con i loro permessi originali. (Ciò significa che il Programma B può firmare con l'account firmatario e può scrivere sull'account scrivibile.) Se il Programma B effettua le proprie CPI, può trasmettere questi stessi permessi, fino a una profondità di 4.

L'altezza massima dell'invocazione di istruzioni del programma è chiamata max_instruction_stack_depth ed è impostata sulla costante MAX_INSTRUCTION_STACK_DEPTH di 5.

L'altezza dello stack inizia a 1 per la transazione iniziale e aumenta di 1 ogni volta che un programma invoca un'altra istruzione, limitando la profondità di invocazione per le CPI a 4.

CPI con firmatari PDA

Quando una CPI richiede un firmatario PDA, viene utilizzata la funzione invoke_signed. Essa accetta i seed del firmatario utilizzati per derivare i PDA firmatari. Il runtime di Solana internamente chiama create_program_address utilizzando il signers_seeds e il program_id del programma chiamante. Quando un PDA viene verificato, viene aggiunto come firmatario valido.

Invoke signed
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
// --snip--
invoke_signed_unchecked(instruction, account_infos, signers_seeds)
}

Gli esempi seguenti effettuano una CPI con firmatari PDA utilizzando Anchor e Rust nativo. Ogni esempio include una singola istruzione per trasferire SOL da un PDA a un account destinatario, utilizzando una CPI firmata dal PDA.

Anchor

I seguenti esempi mostrano tre approcci per implementare le CPI in un programma Anchor. Gli esempi sono funzionalmente equivalenti, ma ciascuno dimostra un diverso livello di astrazione.

  • Esempio 1: Utilizza CpiContext di Anchor e la funzione helper.
  • Esempio 2: Utilizza la funzione system_instruction::transfer dal crate solana_program. (L'esempio 1 è un'astrazione di questa implementazione.)
  • Esempio 3: Costruisce l'istruzione CPI manualmente. Questo approccio è utile quando non è disponibile alcun crate per aiutare a costruire l'istruzione che si desidera invocare.
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

L'esempio seguente effettua una CPI con firmatari PDA da un programma scritto in Rust nativo. Include una singola istruzione che trasferisce SOL da un account PDA a un altro. La CPI è firmata dall'account PDA. (Il file di test utilizza LiteSVM per testare il programma.)

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(())
}
}
}

CPI senza firmatari PDA

Quando una CPI non richiede firmatari PDA, viene utilizzata la funzione invoke. La funzione invoke chiama la funzione invoke_signed con un array signers_seeds vuoto. L'array di firmatari vuoto indica che non sono richiesti PDA per la firma.

Invoke function
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
invoke_signed(instruction, account_infos, &[])
}

Gli esempi seguenti mostrano come effettuare un CPI utilizzando Anchor e Rust nativo. Includono una singola istruzione che trasferisce SOL da un account a un altro.

Anchor

I seguenti esempi mostrano tre approcci per implementare i CPI in un programma Anchor. Gli esempi sono funzionalmente equivalenti, ma ciascuno dimostra un diverso livello di astrazione.

  • Esempio 1: Utilizza CpiContext di Anchor e la funzione helper.
  • Esempio 2: Utilizza la funzione system_instruction::transfer dal crate solana_program.
  • Esempio 3: Costruisce l'istruzione CPI manualmente. Questo approccio è utile quando non esiste un crate per aiutare a costruire l'istruzione che si desidera invocare.
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

Il seguente esempio mostra come effettuare un CPI da un programma scritto in Rust nativo. Include una singola istruzione che trasferisce SOL da un account a un altro. (Il file di test utilizza LiteSVM per testare il programma.)

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?

Indice

Modifica Pagina

Gestito da

© 2025 Solana Foundation.
Tutti i diritti riservati.
Rimani Connesso