Cross Program Invocation

Cross Program Invocation (CPI) występuje, gdy jeden program Solana bezpośrednio wywołuje instrukcje innego programu. Pozwala to na komponowanie programów. Jeśli potraktujesz instrukcję Solany jak endpoint API, który program udostępnia w sieci, to CPI jest jak wewnętrzne wywołanie jednego endpointu przez inny.

Podczas wykonywania CPI program może podpisywać w imieniu PDA wyprowadzonego ze swojego ID programu. Te uprawnienia podpisującego są przekazywane z programu wywołującego do programu wywoływanego.

Przykład cross-program invocationPrzykład cross-program invocation

Podczas wykonywania CPI uprawnienia do kont są przekazywane z jednego programu do drugiego. Załóżmy, że Program A otrzymuje instrukcję z kontem podpisującym i kontem z prawem zapisu. Następnie Program A wykonuje CPI do Programu B. Teraz Program B może korzystać z tych samych kont co Program A, z zachowaniem ich oryginalnych uprawnień. (Oznacza to, że Program B może podpisywać przy użyciu konta podpisującego i zapisywać na koncie z prawem zapisu.) Jeśli Program B wykona własne CPI, może przekazać te same uprawnienia dalej, maksymalnie do głębokości 4.

Maksymalna wysokość stosu wywołań instrukcji programu to max_instruction_stack_depth i jest ustawiona na wartość stałej MAX_INSTRUCTION_STACK_DEPTH, która wynosi 5.

Wysokość stosu zaczyna się od 1 dla początkowej transakcji i zwiększa się o 1 za każdym razem, gdy program wywołuje kolejną instrukcję, ograniczając głębokość wywołań CPI do 4.

CPI z podpisującymi PDA

Gdy CPI wymaga podpisującego PDA, używana jest funkcja invoke_signed. Przyjmuje ona ziarna podpisującego używane do derivacji podpisujących PDA. Środowisko uruchomieniowe Solana wewnętrznie wywołuje create_program_address używając signers_seeds oraz program_id programu wywołującego. Gdy PDA zostanie zweryfikowany, jest dodawany jako ważny podpisujący.

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

Poniższe przykłady pokazują, jak wykonać CPI z podpisującymi PDA przy użyciu Anchor i Native Rust. Każdy przykład zawiera pojedynczą instrukcję przesyłającą SOL z PDA na konto odbiorcy, przy użyciu CPI podpisanego przez PDA.

Anchor

Poniższe przykłady pokazują trzy podejścia do implementacji CPI w programie Anchor. Przykłady są funkcjonalnie równoważne, ale każdy z nich demonstruje inny poziom abstrakcji.

  • Przykład 1: Używa CpiContext i funkcji pomocniczej Anchor.
  • Przykład 2: Używa funkcji system_instruction::transfer z crate'a solana_program. (Przykład 1 jest abstrakcją tej implementacji.)
  • Przykład 3: Ręcznie konstruuje instrukcję CPI. To podejście jest przydatne, gdy nie istnieje crate, który pomaga zbudować instrukcję, którą chcesz wywołać.
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

Poniższy przykład pokazuje, jak wykonać CPI z podpisującymi PDA z programu napisanego w Native Rust. Zawiera pojedynczą instrukcję przesyłającą SOL z konta PDA na inne konto. CPI jest podpisany przez konto PDA. (Plik testowy wykorzystuje LiteSVM do testowania programu.)

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 bez podpisujących PDA

Gdy CPI nie wymaga podpisujących PDA, używana jest funkcja invoke. Funkcja invoke wywołuje funkcję invoke_signed z pustą tablicą signers_seeds. Pusta tablica podpisujących oznacza, że nie są wymagane żadne PDA do podpisu.

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

Poniższe przykłady pokazują, jak wykonać CPI przy użyciu Anchor i Native Rust. Każdy przykład zawiera pojedynczą instrukcję przesyłającą SOL z jednego konta na drugie.

Anchor

Poniższe przykłady przedstawiają trzy podejścia do implementacji CPI w programie Anchor. Przykłady są funkcjonalnie równoważne, ale każdy z nich pokazuje inny poziom abstrakcji.

  • Przykład 1: Używa CpiContext oraz funkcji pomocniczej Anchor.
  • Przykład 2: Używa funkcji system_instruction::transfer z crate solana_program.
  • Przykład 3: Ręcznie konstruuje instrukcję CPI. To podejście jest przydatne, gdy nie istnieje crate, który pomaga zbudować instrukcję, którą chcesz wywołać.
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

Poniższy przykład pokazuje, jak wykonać CPI z programu napisanego w Native Rust. Zawiera pojedynczą instrukcję, która przesyła SOL z jednego konta na drugie. (Plik testowy wykorzystuje LiteSVM do testowania programu.)

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?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco