Cross Program Invocation

Cross Program Invocation (CPI) xảy ra khi một chương trình Solana trực tiếp gọi các lệnh của một chương trình khác. Điều này cho phép khả năng kết hợp giữa các chương trình. Nếu bạn coi một lệnh Solana như một điểm cuối API mà một chương trình cung cấp cho mạng lưới, thì CPI giống như một điểm cuối nội bộ gọi một điểm cuối khác.

Khi thực hiện CPI, một chương trình có thể thay mặt cho một PDA được tạo ra từ ID chương trình của nó. Các đặc quyền ký này được mở rộng từ chương trình gọi đến chương trình được gọi.

Ví dụ về Cross-program invocationVí dụ về Cross-program invocation

Khi thực hiện CPI, các đặc quyền tài khoản được mở rộng từ chương trình này sang chương trình khác. Giả sử Chương trình A nhận được một lệnh với một tài khoản người ký và một tài khoản có thể ghi. Sau đó Chương trình A thực hiện CPI đến Chương trình B. Bây giờ, Chương trình B có thể sử dụng các tài khoản giống như Chương trình A, với các quyền ban đầu của chúng. (Nghĩa là Chương trình B có thể ký với tài khoản người ký và có thể ghi vào tài khoản có thể ghi.) Nếu Chương trình B thực hiện CPI riêng của nó, nó có thể chuyển tiếp các quyền này, lên đến độ sâu là 4.

Chiều cao tối đa của việc gọi lệnh chương trình được gọi là max_instruction_stack_depth và được đặt thành hằng số MAX_INSTRUCTION_STACK_DEPTH là 5.

Chiều cao ngăn xếp bắt đầu từ 1 cho giao dịch ban đầu và tăng thêm 1 mỗi khi một chương trình gọi một lệnh khác, giới hạn độ sâu gọi cho CPI là 4.

CPI với người ký PDA

Khi một CPI yêu cầu một PDA signer, hàm invoke_signed được sử dụng. Nó nhận signer seeds được sử dụng để tạo ra PDAs signer. Runtime của Solana nội bộ gọi create_program_address sử dụng signers_seedsprogram_id của chương trình gọi. Khi một PDA được xác minh, nó sẽ được thêm vào như một signer hợp lệ.

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

Các ví dụ dưới đây thực hiện CPI với PDA signers sử dụng Anchor và Native Rust. Mỗi ví dụ bao gồm một lệnh duy nhất để chuyển SOL từ một PDA đến một tài khoản người nhận, sử dụng CPI được ký bởi PDA.

Anchor

Các ví dụ sau đây cho thấy ba cách tiếp cận để triển khai CPI trong một chương trình Anchor. Các ví dụ này tương đương về chức năng, nhưng mỗi ví dụ minh họa một mức độ trừu tượng hóa khác nhau.

  • Ví dụ 1: Sử dụng CpiContext của Anchor và hàm trợ giúp.
  • Ví dụ 2: Sử dụng hàm system_instruction::transfer từ crate solana_program. (Ví dụ 1 là một trừu tượng hóa của cách triển khai này.)
  • Ví dụ 3: Xây dựng lệnh CPI thủ công. Cách tiếp cận này hữu ích khi không có crate nào có sẵn để giúp xây dựng lệnh mà bạn muốn gọi.
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

Ví dụ dưới đây thực hiện CPI với PDA signers từ một chương trình được viết bằng Native Rust. Nó bao gồm một lệnh duy nhất chuyển SOL từ tài khoản PDA đến tài khoản khác. CPI được ký bởi tài khoản PDA. (Tệp kiểm thử sử dụng LiteSVM để kiểm tra chương trình.)

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 không có PDA signers

Khi một CPI không yêu cầu PDA signers, hàm invoke được sử dụng. Hàm invoke gọi hàm invoke_signed với mảng signers_seeds rỗng. Mảng signers rỗng chỉ ra rằng không có PDA nào được yêu cầu để ký.

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

Các ví dụ dưới đây thực hiện CPI sử dụng Anchor và Native Rust. Mỗi ví dụ bao gồm một lệnh duy nhất chuyển SOL từ tài khoản này sang tài khoản khác.

Anchor

Các ví dụ sau đây trình bày ba cách tiếp cận để triển khai CPI trong một chương trình Anchor. Các ví dụ có chức năng tương đương nhau, nhưng mỗi ví dụ thể hiện một mức độ trừu tượng khác nhau.

  • Ví dụ 1: Sử dụng CpiContext của Anchor và hàm trợ giúp.
  • Ví dụ 2: Sử dụng hàm system_instruction::transfer từ crate solana_program.
  • Ví dụ 3: Xây dựng lệnh CPI thủ công. Cách tiếp cận này hữu ích khi không có crate nào tồn tại để giúp xây dựng lệnh bạn muốn gọi.
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

Ví dụ sau đây cho thấy cách thực hiện CPI từ một chương trình được viết bằng Native Rust. Nó bao gồm một lệnh duy nhất chuyển SOL từ tài khoản này sang tài khoản khác. (File kiểm thử sử dụng LiteSVM để kiểm tra chương trình.)

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?

Mục lục

Chỉnh sửa trang

Quản lý bởi

© 2025 Solana Foundation.
Đã đăng ký bản quyền.
Kết nối