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 programasEjemplo 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.

Invoke signed
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 CpiContext de Anchor y función auxiliar.
  • Ejemplo 2: Utiliza la función system_instruction::transfer del crate solana_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 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 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.

Invoke function
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 CpiContext de Anchor y una función auxiliar.
  • Ejemplo 2: Utiliza la función system_instruction::transfer del crate solana_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 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?

Tabla de Contenidos

Editar Página

Gestionado por

© 2025 Fundación Solana.
Todos los derechos reservados.
Conéctate