Cross Program Invocation
يحدث Cross Program Invocation (CPI) عندما يقوم أحد برامج سولانا باستدعاء تعليمات برنامج آخر بشكل مباشر. هذا يسمح بإمكانية تركيب البرامج. إذا فكرت في تعليمات سولانا كنقطة نهاية API يعرضها البرنامج للشبكة، فإن CPI يشبه نقطة نهاية واحدة تستدعي أخرى داخليًا.
عند إجراء CPI، يمكن للبرنامج التوقيع نيابة عن PDA مشتق من معرف البرنامج الخاص به. تمتد امتيازات التوقيع هذه من البرنامج المستدعي إلى البرنامج المستدعى.
مثال على استدعاء البرامج المتقاطعة
عند إجراء CPI، تمتد امتيازات الحساب من برنامج إلى آخر. لنفترض أن البرنامج A يتلقى تعليمات مع حساب موقّع وحساب قابل للكتابة. ثم يقوم البرنامج A بإجراء CPI إلى البرنامج B. الآن، يمكن للبرنامج B استخدام نفس الحسابات مثل البرنامج A، مع أذوناتها الأصلية. (مما يعني أن البرنامج B يمكنه التوقيع باستخدام حساب الموقّع ويمكنه الكتابة في الحساب القابل للكتابة.) إذا قام البرنامج B بإجراء CPIs الخاصة به، فيمكنه تمرير نفس الأذونات للأمام، حتى عمق 4.
الحد الأقصى لارتفاع استدعاء تعليمات البرنامج يسمى
max_instruction_stack_depth
وهو محدد بثابت MAX_INSTRUCTION_STACK_DEPTH
بقيمة 5.
يبدأ ارتفاع المكدس عند 1 للمعاملة الأولية ويزداد بمقدار 1 في كل مرة يستدعي فيها البرنامج تعليمات أخرى، مما يحد من عمق الاستدعاء لـ CPIs إلى 4.
CPIs مع موقعي PDA
عند إجراء استدعاء CPI يتطلب موقع PDA، يتم استخدام دالة
invoke_signed.
تأخذ هذه الدالة بذور التوقيع المستخدمة لاشتقاق
PDAs الموقعة. يقوم وقت تشغيل سولانا داخلياً باستدعاء
create_program_address
باستخدام signers_seeds و program_id للبرنامج المستدعي. عندما يتم التحقق من
صحة PDA، يتم
إضافته كموقع صالح.
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 entrypointentrypoint!(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 datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [pda_account_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Derive PDA and verify it matches the account provided by clientlet 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 instructionlet transfer_ix = system_instruction::transfer(pda_account_info.key,recipient_info.key,amount,);// Create signer seeds for PDAlet signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];// Invoke the transfer instruction with PDA as signerinvoke_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
فارغة. تشير المصفوفة الفارغة للموقعين إلى أنه لا توجد حاجة لـ PDAs للتوقيع.
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {invoke_signed(instruction, account_infos, &[])}
توضح الأمثلة أدناه كيفية إجراء استدعاء عبر البرامج (CPI) باستخدام Anchor و Native Rust. تتضمن تعليمة واحدة تنقل SOL من حساب إلى آخر.
Anchor
تعرض الأمثلة التالية ثلاث طرق لتنفيذ استدعاءات عبر البرامج (CPIs) في برنامج 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) من برنامج مكتوب بلغة Native 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 entrypointentrypoint!(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 datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [sender_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Verify the sender is a signerif !sender_info.is_signer {return Err(ProgramError::MissingRequiredSignature);}// Create and invoke the transfer instructionlet 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?