Cross Program Invocation (CPI)

Cross Program Invocation (CPI), bir programın başka bir programın talimatlarını çağırması anlamına gelir. Bu, Solana programlarının birleştirilebilirliğine olanak tanır.

Talimatları, bir programın ağa sunduğu API uç noktaları olarak, CPI'yi ise bir API'nin dahili olarak başka bir API'yi çağırması olarak düşünebilirsiniz.

Cross Program InvocationCross Program Invocation

Önemli Noktalar

  • Cross Program Invocation, Solana program talimatlarının doğrudan başka bir programdaki talimatları çağırmasını sağlar.
  • Çağıran programdan gelen imzalayan ayrıcalıkları çağrılan programa aktarılır.
  • Cross Program Invocation yaparken, programlar kendi program kimliklerinden türetilen PDA'lar adına imza atabilirler.
  • Çağrılan program, diğer programlara en fazla 4 derinliğe kadar daha fazla CPI yapabilir.

CPI nedir?

Cross Program Invocation (CPI), bir programın başka bir programın talimatlarını çağırmasıdır.

CPI ile bir program talimatı yazmak, bir talimata işlem eklemek için oluşturma modeliyle aynıdır. Arka planda, her CPI talimatı şunları belirtmelidir:

  • Program adresi: Çağrılacak programı belirtir
  • Hesaplar: Talimatın okuduğu veya yazdığı, diğer programlar dahil her hesabı listeler
  • Talimat Verisi: Programda hangi talimatın çağrılacağını ve talimatın ihtiyaç duyduğu verileri (fonksiyon argümanları) belirtir

Bir program başka bir programa Cross Program Invocation (CPI) yaptığında:

  • İlk işlemdeki imzalayan ayrıcalıkları çağrılan programa aktarılır (örn. A->B)
  • Çağrılan program, diğer programlara en fazla 4 derinliğe kadar daha fazla CPI yapabilir (örn. B->C, C->D)
  • Programlar, program kimliğinden türetilen PDA'lar adına "imzalayabilir"

Solana program çalışma zamanı, max_instruction_stack_depth sabitini MAX_INSTRUCTION_STACK_DEPTH olarak 5 değerine ayarlar. Bu, program talimat çağrı yığınının maksimum yüksekliğini temsil eder. Yığın yüksekliği, ilk işlem için 1'den başlar ve bir program başka bir talimatı çağırdığında 1 artar. Bu ayar, CPI'lar için çağrı derinliğini 4 ile sınırlar.

Bir işlem işlendiğinde, hesap ayrıcalıkları bir programdan diğerine aktarılır. İşte bunun anlamı:

Diyelim ki Program A şunları içeren bir talimat alıyor:

  • İşlemi imzalayan bir hesap
  • Yazılabilir bir hesap (değiştirilebilir)

Program A, Program B'ye bir CPI yaptığında:

  • Program B, bu hesapları orijinal izinleriyle kullanabilir
  • Program B, imzalayan hesapla imza atabilir
  • Program B, yazılabilir hesaba yazabilir
  • Program B, kendi CPI'larını yaparsa bu aynı izinleri ileride de kullanabilir

Cross Program Invocation

invoke fonksiyonu, PDA imzalayanları gerektirmeyen CPI'ları işler. Bu fonksiyon, invoke_signed fonksiyonunu boş bir signers_seeds dizisiyle çağırır, bu da imzalama için gerekli PDA olmadığını gösterir.

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

Aşağıdaki örnekler, Anchor Framework ve Native Rust kullanarak nasıl CPI yapılacağını göstermektedir. Örnek programlar, CPI kullanarak bir hesaptan diğerine SOL aktaran tek bir talimat içerir.

Anchor Framework

Aşağıdaki örnekler, bir Anchor programında Çapraz Program Çağrıları (CPIs) oluşturmanın üç farklı yolunu, her biri farklı soyutlama düzeyinde olmak üzere sunmaktadır. Tüm örnekler aynı şekilde çalışır. Ana amaç, bir CPI'nin uygulama ayrıntılarını göstermektir.

  • Örnek 1: CPI talimatını oluşturmak için Anchor'ın CpiContext ve yardımcı fonksiyonunu kullanır.
  • Örnek 2: CPI talimatını oluşturmak için solana_program crate'inden system_instruction::transfer fonksiyonunu kullanır. Örnek 1 bu uygulamayı soyutlar.
  • Örnek 3: CPI talimatını manuel olarak oluşturur. Bu yaklaşım, talimatı oluşturmaya yardımcı olacak bir crate olmadığında kullanışlıdır.
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>,
}

Native Rust

Aşağıdaki örnek, Native Rust ile yazılmış bir programdan nasıl CPI yapılacağını göstermektedir. Program, bir CPI kullanarak bir hesaptan diğerine SOL aktaran tek bir talimat içerir. Test dosyası, programı test etmek için LiteSVM kullanır.

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(())
}
}
}

PDA İmzalayanları ile Çapraz Program Çağrıları

invoke_signed fonksiyonu, PDA imzalayanları gerektiren CPI'ları işler. Fonksiyon, imzalayan PDA'ları türetmek için gereken seed'leri signer_seeds olarak alır.

PDA'ların nasıl türetileceği hakkında ayrıntılar için Program Derived Address sayfasına başvurabilirsiniz.

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

Bir CPI içeren bir talimatı işlerken, Solana çalışma zamanı dahili olarak çağıran programın signers_seeds ve program_id değerlerini kullanarak create_program_address fonksiyonunu çağırır. Geçerli bir PDA doğrulandığında, adres geçerli bir imzalayan olarak eklenir.

Aşağıdaki örnekler, Anchor Framework ve Native Rust kullanarak PDA imzalayanları ile nasıl CPI yapılacağını göstermektedir. Örnek programlar, PDA tarafından imzalanmış bir CPI kullanarak bir PDA'dan alıcı hesaba SOL aktaran tek bir talimat içerir.

Anchor Framework

Aşağıdaki örnekler, bir Anchor programında Çapraz Program Çağrılarını (Cross Program Invocations - CPIs) uygulamanın üç farklı yaklaşımını içerir, her biri farklı bir soyutlama düzeyindedir. Tüm örnekler işlevsel olarak eşdeğerdir. Ana amaç, bir CPI'nin uygulama detaylarını göstermektir.

  • Örnek 1: CPI talimatını oluşturmak için Anchor'ın CpiContext ve yardımcı fonksiyonunu kullanır.
  • Örnek 2: CPI talimatını oluşturmak için solana_program crate'inden system_instruction::transfer fonksiyonunu kullanır. Örnek 1, bu uygulamanın bir soyutlamasıdır.
  • Örnek 3: CPI talimatını manuel olarak oluşturur. Bu yaklaşım, çağırmak istediğiniz talimatı oluşturmaya yardımcı olacak bir crate mevcut olmadığında kullanışlıdır.
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>,
}

Native Rust

Aşağıdaki örnek, Native Rust ile yazılmış bir programdan PDA imzalayanları ile nasıl CPI yapılacağını göstermektedir. Program, PDA tarafından imzalanmış bir CPI kullanarak bir PDA'dan alıcı hesaba SOL aktaran tek bir talimat içerir. Test dosyası, programı test etmek için LiteSVM kullanır.

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(())
}
}
}

Is this page helpful?

İçindekiler

Sayfayı Düzenle