Кратко
Создавайте PDA аккаунты через invoke_signed с использованием сидов PDA.
Только владеющая программа может подписывать за PDA. Ограничение Anchor
init автоматизирует создание PDA аккаунта.
Подпись PDA через invoke_signed
Когда программе нужно подписать от имени PDA во время CPI, используется
invoke_signed с сидом PDA. В рантайме проверяется, что сиды действительно
порождают ожидаемый PDA с использованием ID вызывающей программы, что
гарантирует, что только владеющая программа может подписывать. Полный процесс
проверки смотрите в разделе
PDA-подпись.
Создание PDA аккаунта
Получение PDA и создание аккаунта по адресу PDA — это разные операции. После получения адреса необходимо явно создать аккаунт.
Чтобы создать аккаунт по адресу PDA, программа-инициатор вызывает инструкцию
System Program create_account через invoke_signed,
передавая сиды PDA, чтобы рантайм мог проверить полномочия программы на этот
адрес.
В примере ниже используется Anchor framework
для создания нового аккаунта с адресом, производным от программы. Программа
содержит единственную инструкцию initialize для создания
нового аккаунта, который будет хранить
адрес пользователя и bump seed,
использованные для получения PDA.
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, следующие:
- Фиксированная строка: "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.
В примере также показано, как получить новый аккаунт, который будет создан.
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 с тем же адресом seed user,
транзакция завершится неудачей. Это произойдет потому, что аккаунт уже
существует по этому производному адресу.
Is this page helpful?