Cross Program Invocation

Cross Program Invocation (CPI) terjadi ketika satu program Solana secara langsung memanggil instruksi program lain. Ini memungkinkan komposabilitas program. Jika Anda menganggap instruksi Solana sebagai endpoint API yang diekspos program ke jaringan, CPI seperti satu endpoint yang secara internal memanggil endpoint lainnya.

Saat membuat CPI, sebuah program dapat menandatangani atas nama PDA yang diturunkan dari ID programnya. Hak istimewa penandatangan ini diperluas dari program pemanggil ke program yang dipanggil.

Contoh cross-program invocationContoh cross-program invocation

Saat membuat CPI, hak istimewa akun diperluas dari satu program ke program lainnya. Misalkan Program A menerima instruksi dengan akun penandatangan dan akun yang dapat ditulis. Program A kemudian membuat CPI ke Program B. Sekarang, Program B dapat menggunakan akun yang sama seperti Program A, dengan izin aslinya. (Artinya Program B dapat menandatangani dengan akun penandatangan dan dapat menulis ke akun yang dapat ditulis.) Jika Program B membuat CPI sendiri, ia dapat meneruskan izin yang sama, hingga kedalaman 4.

Ketinggian maksimum pemanggilan instruksi program disebut max_instruction_stack_depth dan ditetapkan ke konstanta MAX_INSTRUCTION_STACK_DEPTH sebesar 5.

Ketinggian stack dimulai pada 1 untuk transaksi awal dan bertambah 1 setiap kali program memanggil instruksi lain, membatasi kedalaman pemanggilan untuk CPI hingga 4.

CPI dengan penandatangan PDA

Ketika CPI memerlukan PDA signer, fungsi invoke_signed digunakan. Fungsi ini mengambil signer seeds yang digunakan untuk menurunkan PDA signer. Runtime Solana secara internal memanggil create_program_address menggunakan signers_seeds dan program_id dari program pemanggil. Ketika PDA terverifikasi, alamat tersebut ditambahkan sebagai signer yang valid.

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

Contoh-contoh berikut membuat CPI dengan PDA signer menggunakan Anchor dan Native Rust. Setiap contoh mencakup satu instruksi untuk mentransfer SOL dari PDA ke akun penerima, menggunakan CPI yang ditandatangani oleh PDA.

Anchor

Contoh-contoh berikut menunjukkan tiga pendekatan untuk mengimplementasikan CPI dalam program Anchor. Contoh-contoh tersebut secara fungsional setara, tetapi masing-masing mendemonstrasikan tingkat abstraksi yang berbeda.

  • Contoh 1: Menggunakan CpiContext Anchor dan fungsi pembantu.
  • Contoh 2: Menggunakan fungsi system_instruction::transfer dari crate solana_program. (Contoh 1 adalah abstraksi dari implementasi ini.)
  • Contoh 3: Membangun instruksi CPI secara manual. Pendekatan ini berguna ketika tidak ada crate yang tersedia untuk membantu membangun instruksi yang ingin Anda panggil.
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

Contoh di bawah ini membuat CPI dengan PDA signer dari program yang ditulis dalam Native Rust. Contoh ini mencakup satu instruksi yang mentransfer SOL dari akun PDA ke akun lain. CPI ditandatangani oleh akun PDA. (File pengujian menggunakan LiteSVM untuk menguji program.)

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 tanpa PDA signer

Ketika CPI tidak memerlukan PDA signer, fungsi invoke digunakan. Fungsi invoke memanggil fungsi invoke_signed dengan array signers_seeds kosong. Array signer kosong menunjukkan bahwa tidak ada PDA yang diperlukan untuk penandatanganan.

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

Contoh-contoh di bawah ini membuat CPI menggunakan Anchor dan Native Rust. Ini mencakup satu instruksi yang mentransfer SOL dari satu akun ke akun lainnya.

Anchor

Contoh-contoh berikut menunjukkan tiga pendekatan untuk mengimplementasikan CPI dalam program Anchor. Contoh-contoh tersebut secara fungsional setara, tetapi masing-masing menunjukkan tingkat abstraksi yang berbeda.

  • Contoh 1: Menggunakan CpiContext Anchor dan fungsi pembantu.
  • Contoh 2: Menggunakan fungsi system_instruction::transfer dari crate solana_program.
  • Contoh 3: Membangun instruksi CPI secara manual. Pendekatan ini berguna ketika tidak ada crate yang tersedia untuk membantu membangun instruksi yang ingin Anda panggil.
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

Contoh berikut menunjukkan cara membuat CPI dari program yang ditulis dalam Native Rust. Ini mencakup satu instruksi yang mentransfer SOL dari satu akun ke akun lainnya. (File pengujian menggunakan LiteSVM untuk menguji program.)

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?

Daftar Isi

Edit Halaman

Dikelola oleh

© 2025 Yayasan Solana.
Semua hak dilindungi.