Cross Program Invocation
Una invocación entre programas (CPI) ocurre cuando un programa de Solana invoca directamente las instrucciones de otro programa. Esto permite la composición de programas. Si piensas en una instrucción de Solana como un punto final de API que un programa expone a la red, un CPI es como un punto final que internamente invoca a otro.
Al realizar un CPI, un programa puede firmar en nombre de un PDA derivado de su ID de programa. Estos privilegios de firmante se extienden desde el programa que llama hasta el programa llamado.
Ejemplo de invocación entre programas
Al realizar un CPI, los privilegios de cuenta se extienden de un programa a otro. Supongamos que el Programa A recibe una instrucción con una cuenta firmante y una cuenta escribible. El Programa A luego hace un CPI al Programa B. Ahora, el Programa B puede usar las mismas cuentas que el Programa A, con sus permisos originales. (Esto significa que el Programa B puede firmar con la cuenta firmante y puede escribir en la cuenta escribible). Si el Programa B hace sus propios CPIs, puede pasar estos mismos permisos hacia adelante, hasta una profundidad de 4.
La altura máxima de la invocación de instrucciones del programa se llama
max_instruction_stack_depth
y está establecida en la constante MAX_INSTRUCTION_STACK_DEPTH
de 5.
La altura de la pila comienza en 1 para la transacción inicial y aumenta en 1 cada vez que un programa invoca otra instrucción, limitando la profundidad de invocación para CPIs a 4.
CPIs con firmantes PDA
Cuando un CPI requiere un firmante PDA, se utiliza la función
invoke_signed.
Esta toma las semillas de firmante utilizadas para
derivar PDAs firmantes. El runtime de Solana internamente
llama a
create_program_address
utilizando el signers_seeds y el program_id del programa que realiza la
llamada. Cuando se verifica un PDA, este es
añadido como firmante válido.
pub fn invoke_signed(instruction: &Instruction,account_infos: &[AccountInfo],signers_seeds: &[&[&[u8]]],) -> ProgramResult {// --snip--invoke_signed_unchecked(instruction, account_infos, signers_seeds)}
Los ejemplos a continuación realizan un CPI con firmantes PDA utilizando Anchor y Rust nativo. Cada ejemplo incluye una única instrucción para transferir SOL desde un PDA a una cuenta destinataria, utilizando un CPI firmado por el PDA.
Anchor
Los siguientes ejemplos muestran tres enfoques para implementar CPIs en un programa Anchor. Los ejemplos son funcionalmente equivalentes, pero cada uno demuestra un nivel diferente de abstracción.
- Ejemplo 1: Utiliza 
CpiContextde Anchor y función auxiliar. - Ejemplo 2: Utiliza la función 
system_instruction::transferdel cratesolana_program. (El ejemplo 1 es una abstracción de esta implementación.) - Ejemplo 3: Construye la instrucción CPI manualmente. Este enfoque es útil cuando no hay un crate disponible para ayudar a construir la instrucción que deseas invocar.
 
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
El ejemplo a continuación realiza un CPI con firmantes PDA desde un programa escrito en Rust nativo. Incluye una única instrucción que transfiere SOL desde una cuenta PDA a otra. El CPI está firmado por la cuenta PDA. (El archivo de prueba utiliza LiteSVM para probar el programa.)
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 sin firmantes PDA
Cuando un CPI no requiere firmantes PDA, se utiliza la función
invoke.
La función invoke llama a la función invoke_signed con un array
signers_seeds vacío. El array de firmantes vacío indica que no se requieren
PDAs para la firma.
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {invoke_signed(instruction, account_infos, &[])}
Los ejemplos a continuación realizan un CPI utilizando Anchor y Rust nativo. Incluyen una única instrucción que transfiere SOL de una cuenta a otra.
Anchor
Los siguientes ejemplos muestran tres enfoques para implementar CPIs en un programa Anchor. Los ejemplos son funcionalmente equivalentes, pero cada uno demuestra un nivel diferente de abstracción.
- Ejemplo 1: Utiliza 
CpiContextde Anchor y una función auxiliar. - Ejemplo 2: Utiliza la función 
system_instruction::transferdel cratesolana_program. - Ejemplo 3: Construye la instrucción CPI manualmente. Este enfoque es útil cuando no existe un crate para ayudar a construir la instrucción que deseas invocar.
 
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
El siguiente ejemplo muestra cómo realizar un CPI desde un programa escrito en Rust nativo. Incluye una única instrucción que transfiere SOL de una cuenta a otra. (El archivo de prueba utiliza LiteSVM para probar el programa.)
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?