Cross Program Invocation

Μια διεπαφή μεταξύ προγραμμάτων (CPI) συμβαίνει όταν ένα πρόγραμμα Solana καλεί απευθείας τις εντολές ενός άλλου προγράμματος. Αυτό επιτρέπει τη σύνθεση προγραμμάτων. Αν σκεφτείτε μια εντολή Solana ως ένα τελικό σημείο API που ένα πρόγραμμα εκθέτει στο δίκτυο, ένα CPI είναι σαν ένα τελικό σημείο που καλεί εσωτερικά ένα άλλο.

Όταν πραγματοποιείται ένα CPI, ένα πρόγραμμα μπορεί να υπογράψει εκ μέρους ενός PDA που προέρχεται από το αναγνωριστικό του προγράμματος. Αυτά τα προνόμια υπογραφής επεκτείνονται από το πρόγραμμα που καλεί στο πρόγραμμα που καλείται.

Παράδειγμα διεπαφής μεταξύ προγραμμάτωνΠαράδειγμα διεπαφής μεταξύ προγραμμάτων

Όταν πραγματοποιείται ένα CPI, τα προνόμια λογαριασμού επεκτείνονται από το ένα πρόγραμμα στο άλλο. Ας πούμε ότι το Πρόγραμμα Α λαμβάνει μια εντολή με έναν λογαριασμό υπογράφοντα και έναν εγγράψιμο λογαριασμό. Το Πρόγραμμα Α στη συνέχεια κάνει ένα CPI στο Πρόγραμμα Β. Τώρα, το Πρόγραμμα Β μπορεί να χρησιμοποιήσει τους ίδιους λογαριασμούς με το Πρόγραμμα Α, με τα αρχικά τους δικαιώματα. (Αυτό σημαίνει ότι το Πρόγραμμα Β μπορεί να υπογράψει με τον λογαριασμό υπογράφοντα και μπορεί να γράψει στον εγγράψιμο λογαριασμό.) Αν το Πρόγραμμα Β κάνει τα δικά του CPIs, μπορεί να μεταβιβάσει αυτά τα ίδια δικαιώματα προς τα εμπρός, μέχρι βάθος 4.

Το μέγιστο ύψος της κλήσης εντολών προγράμματος ονομάζεται max_instruction_stack_depth και ορίζεται στη σταθερά MAX_INSTRUCTION_STACK_DEPTH του 5.

Το ύψος της στοίβας ξεκινά από το 1 για την αρχική συναλλαγή και αυξάνεται κατά 1 κάθε φορά που ένα πρόγραμμα καλεί μια άλλη εντολή, περιορίζοντας το βάθος κλήσης για CPIs στο 4.

CPIs με υπογράφοντες PDA

Όταν ένα CPI απαιτεί έναν PDA υπογράφοντα, χρησιμοποιείται η συνάρτηση invoke_signed. Λαμβάνει τα seeds υπογραφής που χρησιμοποιούνται για την παραγωγή των PDAs υπογραφόντων. Το Solana runtime εσωτερικά καλεί τη συνάρτηση 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 και Native Rust. Κάθε παράδειγμα περιλαμβάνει μια μόνο εντολή για τη μεταφορά SOL από ένα PDA σε έναν λογαριασμό παραλήπτη, χρησιμοποιώντας ένα CPI υπογεγραμμένο από το PDA.

Anchor

Τα παρακάτω παραδείγματα δείχνουν τρεις προσεγγίσεις για την υλοποίηση CPIs σε ένα πρόγραμμα Anchor. Τα παραδείγματα είναι λειτουργικά ισοδύναμα, αλλά το καθένα επιδεικνύει διαφορετικό επίπεδο αφαίρεσης.

  • Παράδειγμα 1: Χρησιμοποιεί το CpiContext του Anchor και βοηθητική συνάρτηση.
  • Παράδειγμα 2: Χρησιμοποιεί τη συνάρτηση system_instruction::transfer από το solana_program crate. (Το Παράδειγμα 1 είναι μια αφαίρεση αυτής της υλοποίησης.)
  • Παράδειγμα 3: Κατασκευάζει την εντολή CPI χειροκίνητα. Αυτή η προσέγγιση είναι χρήσιμη όταν δεν υπάρχει διαθέσιμο crate για να βοηθήσει στην κατασκευή της εντολής που θέλετε να καλέσετε.
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 υπογράφοντες από ένα πρόγραμμα γραμμένο σε Native 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(())
}
}
}

CPIs χωρίς PDA υπογράφοντες

Όταν ένα CPI δεν απαιτεί PDA υπογράφοντες, χρησιμοποιείται η συνάρτηση invoke. Η συνάρτηση invoke καλεί τη συνάρτηση invoke_signed με ένα κενό πίνακα signers_seeds. Ο κενός πίνακας υπογραφόντων υποδεικνύει ότι δεν απαιτούνται PDAs για υπογραφή.

Invoke function
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 crate.
  • Παράδειγμα 3: Κατασκευάζει την εντολή CPI χειροκίνητα. Αυτή η προσέγγιση είναι χρήσιμη όταν δεν υπάρχει crate για να βοηθήσει στην κατασκευή της εντολής που θέλετε να καλέσετε.
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 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.
Με επιφύλαξη παντός δικαιώματος.