Cross Program Invocation

Cross Program Invocation (CPI) відбувається, коли одна програма Solana безпосередньо викликає інструкції іншої програми. Це дозволяє створювати композиції програм. Якщо розглядати інструкцію Solana як кінцеву точку API, яку програма надає мережі, то CPI — це ніби один кінцевий пункт внутрішньо викликає інший.

Під час виконання CPI програма може підписувати від імені PDA, отриманого з її ідентифікатора програми. Ці привілеї підпису поширюються від програми-викликача до програми-отримувача.

Приклад міжпрограмного викликуПриклад міжпрограмного виклику

При виконанні CPI привілеї облікових записів поширюються з однієї програми на іншу. Припустимо, Програма A отримує інструкцію з обліковим записом підписувача та записуваним обліковим записом. Потім Програма A робить CPI до Програми B. Тепер Програма B може використовувати ті самі облікові записи, що й Програма A, з їхніми початковими дозволами. (Це означає, що Програма B може підписувати обліковим записом підписувача та записувати в записуваний обліковий запис). Якщо Програма B робить власні CPI, вона може передавати ці самі дозволи далі, до глибини 4.

Максимальна висота виклику програмної інструкції називається max_instruction_stack_depth і встановлюється константою MAX_INSTRUCTION_STACK_DEPTH зі значенням 5.

Висота стеку починається з 1 для початкової транзакції і збільшується на 1 кожного разу, коли програма викликає іншу інструкцію, обмежуючи глибину виклику для CPI до 4.

CPI з підписувачами PDA

Коли CPI потребує підписанта PDA, використовується функція invoke_signed. Вона приймає насіння підписанта, що використовується для отримання підписантів PDA. Середовище виконання Solana внутрішньо викликає create_program_address, використовуючи signers_seeds та program_id програми, що викликає. Коли PDA перевірено, він додається як дійсний підписант.

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

Наведені нижче приклади демонструють CPI з підписантами PDA, використовуючи Anchor та нативний Rust. Кожен приклад містить одну інструкцію для переказу SOL з PDA на рахунок отримувача, використовуючи CPI, підписаний PDA.

Anchor

Наступні приклади показують три підходи до реалізації CPI в Anchor програмі. Приклади є функціонально еквівалентними, але кожен демонструє різний рівень абстракції.

  • Приклад 1: Використовує CpiContext Anchor та допоміжну функцію.
  • Приклад 2: Використовує функцію system_instruction::transfer з крейту solana_program. (Приклад 1 є абстракцією цієї реалізації.)
  • Приклад 3: Будує інструкцію CPI вручну. Цей підхід є корисним, коли немає доступного крейту для допомоги у побудові інструкції, яку ви хочете викликати.
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

Наведений нижче приклад демонструє CPI з підписантами PDA з програми, написаної на нативному Rust. Він містить одну інструкцію, яка переказує SOL з рахунку PDA на інший. CPI підписується рахунком PDA. (Тестовий файл використовує LiteSVM для тестування програми.)

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 без підписантів PDA

Коли CPI не потребує підписантів PDA, використовується функція invoke. Функція invoke викликає функцію invoke_signed з порожнім масивом signers_seeds. Порожній масив підписантів вказує, що жодні PDA не потрібні для підписання.

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

Наведені нижче приклади демонструють створення CPI за допомогою Anchor та нативного Rust. Вони включають одну інструкцію, яка переказує SOL з одного облікового запису на інший.

Anchor

Наступні приклади показують три підходи до реалізації CPI в програмі Anchor. Приклади функціонально еквівалентні, але кожен демонструє різний рівень абстракції.

  • Приклад 1: Використовує CpiContext Anchor та допоміжну функцію.
  • Приклад 2: Використовує функцію system_instruction::transfer з крейту solana_program.
  • Приклад 3: Створює інструкцію CPI вручну. Цей підхід корисний, коли не існує крейту для допомоги у створенні інструкції, яку ви хочете викликати.
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

Наступний приклад показує, як створити CPI з програми, написаної на нативному Rust. Він включає одну інструкцію, яка переказує SOL з одного облікового запису на інший. (Тестовий файл використовує LiteSVM для тестування програми.)

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?

Зміст

Редагувати сторінку

Керується

© 2025 Фонд Solana.
Всі права захищені.
Залишайтеся на зв'язку