Cross Program Invocation

Eine Cross Program Invocation (CPI) tritt auf, wenn ein Solana-Programm direkt die Anweisungen eines anderen Programms aufruft. Dies ermöglicht die Komposition von Programmen. Wenn Sie sich eine Solana Anweisung als API-Endpunkt vorstellen, den ein Programm dem Netzwerk zur Verfügung stellt, ist eine CPI wie ein Endpunkt, der intern einen anderen aufruft.

Bei der Durchführung einer CPI kann ein Programm im Namen eines PDA signieren, der von seiner Programm-ID abgeleitet ist. Diese Signer-Privilegien erstrecken sich vom aufrufenden Programm zum aufgerufenen Programm.

Cross-program invocation BeispielCross-program invocation Beispiel

Bei einer CPI werden Kontenberechtigungen von einem Programm auf ein anderes übertragen. Angenommen, Programm A erhält eine Anweisung mit einem Signer-Konto und einem beschreibbaren Konto. Programm A führt dann eine CPI zu Programm B durch. Jetzt kann Programm B dieselben Konten wie Programm A mit ihren ursprünglichen Berechtigungen verwenden. (Das bedeutet, Programm B kann mit dem Signer-Konto signieren und in das beschreibbare Konto schreiben.) Wenn Programm B eigene CPIs durchführt, kann es diese gleichen Berechtigungen weitergeben, bis zu einer Tiefe von 4.

Die maximale Höhe der Programmbefehlsaufrufkette wird als max_instruction_stack_depth bezeichnet und ist auf die Konstante MAX_INSTRUCTION_STACK_DEPTH von 5 festgelegt.

Die Stapelhöhe beginnt bei 1 für die erste Transaktion und erhöht sich um 1 jedes Mal, wenn ein Programm eine andere Anweisung aufruft, wodurch die Aufruftiefe für CPIs auf 4 begrenzt wird.

CPIs mit PDA-Signern

Wenn ein CPI einen PDA Signer erfordert, wird die invoke_signed Funktion verwendet. Sie nimmt die Signer Seeds auf, die für die Ableitung von Signer PDAs verwendet werden. Die Solana-Laufzeit ruft intern create_program_address auf und verwendet dabei die signers_seeds und die program_id des aufrufenden Programms. Wenn eine PDA verifiziert wird, wird sie als gültiger Signer hinzugefügt.

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

Die folgenden Beispiele führen einen CPI mit PDA Signern unter Verwendung von Anchor und Native Rust durch. Jedes Beispiel enthält eine einzelne Anweisung, um SOL von einer PDA zu einem Empfängerkonto zu übertragen, wobei ein von der PDA signierter CPI verwendet wird.

Anchor

Die folgenden Beispiele zeigen drei Ansätze zur Implementierung von CPIs in einem Anchor Programm. Die Beispiele sind funktional gleichwertig, aber jedes demonstriert eine andere Abstraktionsebene.

  • Beispiel 1: Verwendet Anchor's CpiContext und Hilfsfunktion.
  • Beispiel 2: Verwendet die system_instruction::transfer Funktion aus dem solana_program Crate. (Beispiel 1 ist eine Abstraktion dieser Implementierung.)
  • Beispiel 3: Erstellt die CPI-Anweisung manuell. Dieser Ansatz ist nützlich, wenn kein Crate verfügbar ist, um die Anweisung zu erstellen, die du aufrufen möchtest.
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

Das folgende Beispiel führt einen CPI mit PDA Signern aus einem in Native Rust geschriebenen Programm durch. Es enthält eine einzelne Anweisung, die SOL von einem PDA-Konto zu einem anderen überträgt. Der CPI wird vom PDA-Konto signiert. (Die Testdatei verwendet LiteSVM, um das Programm zu 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 ohne PDA Signer

Wenn ein CPI keine PDA Signer erfordert, wird die invoke Funktion verwendet. Die invoke Funktion ruft die invoke_signed Funktion mit einem leeren signers_seeds Array auf. Das leere Signers-Array zeigt an, dass keine PDAs für die Signierung erforderlich sind.

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

Die folgenden Beispiele führen einen CPI mit Anchor und nativem Rust durch. Sie enthalten eine einzelne Anweisung, die SOL von einem Konto zu einem anderen überträgt.

Anchor

Die folgenden Beispiele zeigen drei Ansätze zur Implementierung von CPIs in einem Anchor-Programm. Die Beispiele sind funktional gleichwertig, demonstrieren jedoch jeweils einen unterschiedlichen Abstraktionsgrad.

  • Beispiel 1: Verwendet Anchor's CpiContext und Hilfsfunktion.
  • Beispiel 2: Verwendet die system_instruction::transfer Funktion aus dem solana_program Crate.
  • Beispiel 3: Erstellt die CPI-Anweisung manuell. Dieser Ansatz ist nützlich, wenn kein Crate existiert, um die Anweisung zu erstellen, die Sie aufrufen möchten.
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

Das folgende Beispiel zeigt, wie man einen CPI aus einem in nativem Rust geschriebenen Programm durchführt. Es enthält eine einzelne Anweisung, die SOL von einem Konto zu einem anderen überträgt. (Die Testdatei verwendet LiteSVM, um das Programm zu 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?

Inhaltsverzeichnis

Seite bearbeiten

Verwaltet von

© 2025 Solana Foundation.
Alle Rechte vorbehalten.
Verbinden Sie sich