Cross Program Invocation
Een cross-program invocation (CPI) vindt plaats wanneer een Solana-programma direct de instructies van een ander programma aanroept. Dit maakt programma-compositie mogelijk. Als je een Solana instructie ziet als een API-eindpunt dat een programma blootstelt aan het netwerk, dan is een CPI als een eindpunt dat intern een ander eindpunt aanroept.
Bij het maken van een CPI kan een programma ondertekenen namens een PDA die is afgeleid van zijn programma-ID. Deze ondertekeningsrechten worden uitgebreid van het aanroepende programma naar het aangeroepen programma.
Cross-program invocation voorbeeld
Bij het maken van een CPI worden accountrechten uitgebreid van het ene programma naar het andere. Stel dat Programma A een instructie ontvangt met een ondertekenend account en een beschrijfbaar account. Programma A maakt vervolgens een CPI naar Programma B. Nu kan Programma B dezelfde accounts gebruiken als Programma A, met hun oorspronkelijke rechten. (Dit betekent dat Programma B kan ondertekenen met het ondertekenende account en kan schrijven naar het beschrijfbare account.) Als Programma B zijn eigen CPI's maakt, kan het deze zelfde rechten doorgeven, tot een diepte van 4.
De maximale hoogte van de programma-instructie-aanroep wordt de
max_instruction_stack_depth
genoemd en is ingesteld op de MAX_INSTRUCTION_STACK_DEPTH
constante van 5.
De stapelhoogte begint bij 1 voor de initiële transactie en neemt met 1 toe elke keer dat een programma een andere instructie aanroept, waardoor de aanroepdiepte voor CPI's beperkt wordt tot 4.
CPI's met PDA-ondertekenaars
Wanneer een CPI een PDA-ondertekenaar vereist, wordt de
invoke_signed
functie gebruikt. Deze neemt de ondertekenaar seeds die
worden gebruikt voor het afleiden van ondertekenaar PDAs.
De Solana-runtime roept intern
create_program_address
aan met de signers_seeds en de program_id van het aanroepende programma.
Wanneer een PDA geverifieerd is, wordt deze
toegevoegd als een geldige ondertekenaar.
pub fn invoke_signed(instruction: &Instruction,account_infos: &[AccountInfo],signers_seeds: &[&[&[u8]]],) -> ProgramResult {// --snip--invoke_signed_unchecked(instruction, account_infos, signers_seeds)}
De onderstaande voorbeelden maken een CPI met PDA-ondertekenaars met behulp van Anchor en Native Rust. Elk voorbeeld bevat een enkele instructie om SOL over te maken van een PDA naar een ontvangend account, met behulp van een CPI ondertekend door de PDA.
Anchor
De volgende voorbeelden tonen drie benaderingen voor het implementeren van CPIs in een Anchor programma. De voorbeelden zijn functioneel gelijkwaardig, maar elk demonstreert een ander niveau van abstractie.
- Voorbeeld 1: Gebruikt Anchor's
CpiContexten hulpfunctie. - Voorbeeld 2: Gebruikt de
system_instruction::transferfunctie van desolana_programcrate. (Voorbeeld 1 is een abstractie van deze implementatie.) - Voorbeeld 3: Bouwt de CPI-instructie handmatig. Deze aanpak is nuttig wanneer er geen crate beschikbaar is om de instructie te helpen bouwen die je wilt aanroepen.
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
Het onderstaande voorbeeld maakt een CPI met PDA-ondertekenaars vanuit een programma geschreven in Native Rust. Het bevat een enkele instructie die SOL overmaakt van een PDA-account naar een ander account. De CPI wordt ondertekend door het PDA-account. (Het testbestand gebruikt LiteSVM om het programma te testen.)
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(())}}}
CPIs zonder PDA-ondertekenaars
Wanneer een CPI geen PDA-ondertekenaars vereist, wordt de
invoke
functie gebruikt. De invoke functie roept de invoke_signed functie aan met
een lege signers_seeds array. De lege ondertekenaars-array geeft aan dat er
geen PDAs nodig zijn voor ondertekening.
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {invoke_signed(instruction, account_infos, &[])}
De onderstaande voorbeelden maken een CPI met behulp van Anchor en Native Rust. Het bevat een enkele instructie die SOL van het ene account naar het andere overmaakt.
Anchor
De volgende voorbeelden tonen drie benaderingen voor het implementeren van CPI's in een Anchor programma. De voorbeelden zijn functioneel gelijkwaardig, maar elk demonstreert een ander abstractieniveau.
- Voorbeeld 1: Gebruikt Anchor's
CpiContexten hulpfunctie. - Voorbeeld 2: Gebruikt de
system_instruction::transferfunctie van desolana_programcrate. - Voorbeeld 3: Bouwt de CPI-instructie handmatig. Deze aanpak is nuttig wanneer er geen crate bestaat om de instructie te bouwen die je wilt aanroepen.
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
Het volgende voorbeeld laat zien hoe je een CPI maakt vanuit een programma geschreven in Native Rust. Het bevat een enkele instructie die SOL van het ene account naar het andere overmaakt. (Het testbestand gebruikt LiteSVM om het programma te testen.)
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?