Resumen
Usa invoke_signed cuando el programa que llama necesita firmar en nombre
de un PDA que posee. El runtime deriva las claves públicas del PDA a partir de
las semillas de firmante proporcionadas y las añade al conjunto de firmantes
válidos antes de verificar los privilegios.
CPIs con firmantes PDA
Cuando un CPI requiere un firmante PDA, usa
invoke_signed
con las semillas de firmante usadas para derivar el
PDA. Para más detalles sobre cómo el runtime verifica las
firmas PDA, consulta firma PDA.
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 usando Anchor y Rust nativo. Cada ejemplo incluye una única instrucción para transferir SOL desde un PDA a una cuenta destinataria, usando un CPI firmado por el PDA.
Anchor
Los siguientes ejemplos muestran dos enfoques para implementar CPIs en un programa Anchor. Los ejemplos son funcionalmente equivalentes, pero demuestran diferentes niveles de abstracción.
- Ejemplo 1: Usa
CpiContextde Anchor y función auxiliar. - Ejemplo 2: Usa 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 siguiente ejemplo 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(())}}}
Is this page helpful?