Cross Program Invocation

Een cross-program invocation (CPI) vindt plaats wanneer een Solana-programma direct de instructies van een ander programma aanroept. Dit maakt programma-compositie mogelijk. Als je een Solana instructie ziet als een API-eindpunt dat een programma blootstelt aan het netwerk, dan is een CPI als een eindpunt dat intern een ander eindpunt aanroept.

Bij het maken van een CPI kan een programma ondertekenen namens een PDA die is afgeleid van zijn programma-ID. Deze ondertekeningsrechten worden uitgebreid van het aanroepende programma naar het aangeroepen programma.

Cross-program invocation voorbeeldCross-program invocation voorbeeld

Bij het maken van een CPI worden accountrechten uitgebreid van het ene programma naar het andere. Stel dat Programma A een instructie ontvangt met een ondertekenend account en een beschrijfbaar account. Programma A maakt vervolgens een CPI naar Programma B. Nu kan Programma B dezelfde accounts gebruiken als Programma A, met hun oorspronkelijke rechten. (Dit betekent dat Programma B kan ondertekenen met het ondertekenende account en kan schrijven naar het beschrijfbare account.) Als Programma B zijn eigen CPI's maakt, kan het deze zelfde rechten doorgeven, tot een diepte van 4.

De maximale hoogte van de programma-instructie-aanroep wordt de max_instruction_stack_depth genoemd en is ingesteld op de MAX_INSTRUCTION_STACK_DEPTH constante van 5.

De stapelhoogte begint bij 1 voor de initiële transactie en neemt met 1 toe elke keer dat een programma een andere instructie aanroept, waardoor de aanroepdiepte voor CPI's beperkt wordt tot 4.

CPI's met PDA-ondertekenaars

Wanneer een CPI een PDA-ondertekenaar vereist, wordt de invoke_signed functie gebruikt. Deze neemt de ondertekenaar seeds die worden gebruikt voor het afleiden van ondertekenaar PDAs. De Solana-runtime roept intern create_program_address aan met de signers_seeds en de program_id van het aanroepende programma. Wanneer een PDA geverifieerd is, wordt deze toegevoegd als een geldige ondertekenaar.

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

De onderstaande voorbeelden maken een CPI met PDA-ondertekenaars met behulp van Anchor en Native Rust. Elk voorbeeld bevat een enkele instructie om SOL over te maken van een PDA naar een ontvangend account, met behulp van een CPI ondertekend door de PDA.

Anchor

De volgende voorbeelden tonen drie benaderingen voor het implementeren van CPIs in een Anchor programma. De voorbeelden zijn functioneel gelijkwaardig, maar elk demonstreert een ander niveau van abstractie.

  • Voorbeeld 1: Gebruikt Anchor's CpiContext en hulpfunctie.
  • Voorbeeld 2: Gebruikt de system_instruction::transfer functie van de solana_program crate. (Voorbeeld 1 is een abstractie van deze implementatie.)
  • Voorbeeld 3: Bouwt de CPI-instructie handmatig. Deze aanpak is nuttig wanneer er geen crate beschikbaar is om de instructie te helpen bouwen die je wilt aanroepen.
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

Het onderstaande voorbeeld maakt een CPI met PDA-ondertekenaars vanuit een programma geschreven in Native Rust. Het bevat een enkele instructie die SOL overmaakt van een PDA-account naar een ander account. De CPI wordt ondertekend door het PDA-account. (Het testbestand gebruikt LiteSVM om het programma te testen.)

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 zonder PDA-ondertekenaars

Wanneer een CPI geen PDA-ondertekenaars vereist, wordt de invoke functie gebruikt. De invoke functie roept de invoke_signed functie aan met een lege signers_seeds array. De lege ondertekenaars-array geeft aan dat er geen PDAs nodig zijn voor ondertekening.

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

De onderstaande voorbeelden maken een CPI met behulp van Anchor en Native Rust. Het bevat een enkele instructie die SOL van het ene account naar het andere overmaakt.

Anchor

De volgende voorbeelden tonen drie benaderingen voor het implementeren van CPI's in een Anchor programma. De voorbeelden zijn functioneel gelijkwaardig, maar elk demonstreert een ander abstractieniveau.

  • Voorbeeld 1: Gebruikt Anchor's CpiContext en hulpfunctie.
  • Voorbeeld 2: Gebruikt de system_instruction::transfer functie van de solana_program crate.
  • Voorbeeld 3: Bouwt de CPI-instructie handmatig. Deze aanpak is nuttig wanneer er geen crate bestaat om de instructie te bouwen die je wilt aanroepen.
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

Het volgende voorbeeld laat zien hoe je een CPI maakt vanuit een programma geschreven in Native Rust. Het bevat een enkele instructie die SOL van het ene account naar het andere overmaakt. (Het testbestand gebruikt LiteSVM om het programma te testen.)

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?

Inhoudsopgave

Pagina Bewerken

Beheerd door

© 2025 Solana Foundation.
Alle rechten voorbehouden.
Blijf Verbonden