PDA 서명자를 사용한 CPI

요약

호출 프로그램이 소유한 PDA를 대신하여 서명해야 할 때 _rsinvoke_signed_를 사용하세요. 런타임은 제공된 서명자 시드에서 PDA 공개키를 파생하고 권한 확인 전에 유효한 서명자 집합에 추가합니다.

PDA 서명자를 사용한 CPI

CPI에 PDA 서명자가 필요한 경우, PDA를 파생하는 데 사용된 signer seeds와 함께 invoke_signed를 사용하세요. 런타임이 PDA 서명을 검증하는 방법에 대한 자세한 내용은 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)
}

아래 예제는 Anchor 및 네이티브 Rust를 사용하여 PDA 서명자로 CPI를 수행합니다. 각 예제에는 PDA가 서명한 CPI를 사용하여 PDA에서 수신자 계정으로 SOL을 전송하는 단일 명령이 포함되어 있습니다.

Anchor

다음 예제는 Anchor 프로그램에서 CPI를 구현하는 두 가지 접근 방식을 보여줍니다. 예제는 기능적으로 동일하지만 서로 다른 수준의 추상화를 보여줍니다.

  • 예제 1: Anchor의 CpiContext 및 헬퍼 함수를 사용합니다.
  • 예제 2: solana_program 크레이트의 system_instruction::transfer 함수를 사용합니다. (예제 1은 이 구현의 추상화입니다.)
  • 예제 3: CPI 명령을 수동으로 구성합니다. 이 접근 방식은 호출하려는 명령을 빌드하는 데 도움이 되는 크레이트가 없을 때 유용합니다.
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

아래 예제는 네이티브 Rust로 작성된 프로그램에서 PDA 서명자를 사용한 CPI를 수행합니다. 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(())
}
}
}

Is this page helpful?

목차

페이지 편집

관리자

© 2026 솔라나 재단.
모든 권리 보유.
연결하기