摘要
通过 invoke_signed 及 PDA 的 seed,可以创建 PDA 账户。只有 拥有该 PDA
的程序才能为其签名。Anchor 的 init 约束可自动化 PDA 账户的创建。
通过 invoke_signed 进行 PDA 签名
当程序需要在 CPI 期间代表 PDA 进行签名时,会使用 invoke_signed
及 PDA 的 seed。运行时会验证这些 seed 是否能根据调用程序的 ID推导出预期的 PDA,从而确保只有拥有该 PDA 的程序才能签名。完整的验证流程请参见
PDA 签名。
创建 PDA 账户
推导 PDA 和在 PDA 上创建账户是两个独立的操作。在推导出地址后,必须显式创建该账户。
要在 PDA 上创建账户,推导程序需要调用 System Program 的 create_account
指令,并通过 invoke_signed
传递 PDA 的 seed,以便运行时验证程序对该地址的权限。
下面的示例使用 Anchor framework
通过程序派生地址创建新账户。该程序包含一个 initialize
指令用于创建新账户,该账户将存储用于推导 PDA 的
user address 和 bump seed。
use anchor_lang::prelude::*;declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");#[program]pub mod pda_account {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {let account_data = &mut ctx.accounts.pda_account;// store the address of the `user`account_data.user = *ctx.accounts.user.key;// store the canonical bumpaccount_data.bump = ctx.bumps.pda_account;Ok(())}}#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub user: Signer<'info>,#[account(init,// define the seeds to derive the PDAseeds = [b"data", user.key().as_ref()],// use the canonical bumpbump,payer = user,space = 8 + DataAccount::INIT_SPACE // 8 bytes for Anchor account discriminator)]pub pda_account: Account<'info, DataAccount>,pub system_program: Program<'info, System>,}#[account]#[derive(InitSpace)]pub struct DataAccount {pub user: Pubkey,pub bump: u8,}
init 约束会告知 Anchor
调用 System Program
使用 PDA 作为地址创建新账户。用于创建 PDA 的 seed 包括:
- 固定字符串:"data"
- 指令中提供的用户账户地址
- 标准的 bump seed
在此示例中,bump 约束未被赋值,因此 Anchor 会使用 find_program_address
来推导 PDA 并查找 bump。
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE // 8 bytes for Anchor account discriminator)]pub pda_account: Account<'info, DataAccount>,
下面的测试文件包含一个交易,该交易会调用 initialize
指令,以使用程序派生地址创建新账户。该文件还包含 推导 PDA
的代码。
该示例还展示了如何 fetch 即将创建的新账户。
import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { PdaAccount } from "../target/types/pda_account";import { PublicKey } from "@solana/web3.js";describe("pda-account", () => {const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.PdaAccount as Program<PdaAccount>;const user = provider.wallet as anchor.Wallet;// Derive the PDA address using the seeds specified on the programconst [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});});
如果你再次使用相同的 initialize 指令和相同的 user 地址 seed 调用,
该交易将会失败。原因是派生地址上已经存在一个账户。
Is this page helpful?