Program Derived Address
У цьому розділі ви дізнаєтеся, як створити базову програму для створення, читання, оновлення та видалення (CRUD).
Цей посібник демонструє просту програму, де користувачі можуть створювати, оновлювати та видаляти повідомлення. Кожне повідомлення існує в обліковому записі з детермінованою адресою, отриманою від самої програми (Program Derived Address або PDA).
Цей посібник проведе вас через процес створення та тестування програми Solana за допомогою фреймворку Anchor, демонструючи Program Derived Addresses (PDAs). Для отримання додаткової інформації зверніться до сторінки Program Derived Addresses.
Для довідки ви можете переглянути фінальний код після завершення розділів про PDA та Cross-Program Invocation (CPI).
Початковий код
Почніть з відкриття цього посилання на Solana Playground з початковим кодом. Потім натисніть кнопку "Import", щоб додати програму до ваших проєктів Solana Playground.
Імпорт
У файлі lib.rs
ви знайдете програму з інструкціями create
,
update
та delete
, які потрібно додати у
наступних кроках.
use anchor_lang::prelude::*;declare_id!("8KPzbM2Cwn4Yjak7QYAEH9wyoQh86NcBicaLuzPaejdw");#[program]pub mod pda {use super::*;pub fn create(_ctx: Context<Create>) -> Result<()> {Ok(())}pub fn update(_ctx: Context<Update>) -> Result<()> {Ok(())}pub fn delete(_ctx: Context<Delete>) -> Result<()> {Ok(())}}#[derive(Accounts)]pub struct Create {}#[derive(Accounts)]pub struct Update {}#[derive(Accounts)]pub struct Delete {}#[account]pub struct MessageAccount {}
Перед початком запустіть build
у терміналі Playground, щоб перевірити,
що початкова програма успішно компілюється.
$build
Визначення типу облікового запису повідомлення
Спочатку визначте структуру для облікового запису повідомлення, який створює програма. Ця структура визначає дані, які будуть зберігатися в обліковому записі, створеному програмою.
У lib.rs
, оновіть структуру MessageAccount
наступним чином:
#[account]pub struct MessageAccount {pub user: Pubkey,pub message: String,pub bump: u8,}
Зберіть програму знову, виконавши build
у терміналі.
$build
Цей код визначає, які дані зберігати в обліковому записі повідомлення. Далі ви додасте інструкції програми.
Додати інструкцію створення
Тепер додайте інструкцію create
, яка створює та ініціалізує
MessageAccount
.
Почніть з визначення облікових записів, необхідних для інструкції, оновивши
структуру Create
наступним чином:
#[derive(Accounts)]#[instruction(message: String)]pub struct Create<'info> {#[account(mut)]pub user: Signer<'info>,#[account(init,seeds = [b"message", user.key().as_ref()],bump,payer = user,space = 8 + 32 + 4 + message.len() + 1)]pub message_account: Account<'info, MessageAccount>,pub system_program: Program<'info, System>,}
Далі, додайте бізнес-логіку для інструкції create
, оновивши функцію
create
наступним чином:
pub fn create(ctx: Context<Create>, message: String) -> Result<()> {msg!("Create Message: {}", message);let account_data = &mut ctx.accounts.message_account;account_data.user = ctx.accounts.user.key();account_data.message = message;account_data.bump = ctx.bumps.message_account;Ok(())}
Перебудуйте програму.
$build
Додайте інструкцію оновлення
Далі додайте інструкцію update
для зміни MessageAccount
новим повідомленням.
Як і на попередньому кроці, спочатку вкажіть облікові записи, необхідні для
інструкції update
.
Оновіть структуру Update
наступним чином:
#[derive(Accounts)]#[instruction(message: String)]pub struct Update<'info> {#[account(mut)]pub user: Signer<'info>,#[account(mut,seeds = [b"message", user.key().as_ref()],bump = message_account.bump,realloc = 8 + 32 + 4 + message.len() + 1,realloc::payer = user,realloc::zero = true,)]pub message_account: Account<'info, MessageAccount>,pub system_program: Program<'info, System>,}
Далі додайте логіку для інструкції update
.
pub fn update(ctx: Context<Update>, message: String) -> Result<()> {msg!("Update Message: {}", message);let account_data = &mut ctx.accounts.message_account;account_data.message = message;Ok(())}
Перебудуйте програму
$build
Додайте інструкцію видалення
Далі додайте інструкцію delete
для закриття MessageAccount
.
Оновіть структуру Delete
наступним чином:
#[derive(Accounts)]pub struct Delete<'info> {#[account(mut)]pub user: Signer<'info>,#[account(mut,seeds = [b"message", user.key().as_ref()],bump = message_account.bump,close = user,)]pub message_account: Account<'info, MessageAccount>,}
Далі додайте логіку для інструкції delete
.
pub fn delete(_ctx: Context<Delete>) -> Result<()> {msg!("Delete Message");Ok(())}
Перебудуйте програму.
$build
Розгортання програми
Ви завершили базову програму CRUD. Розгорніть програму, виконавши deploy
у
терміналі Playground.
У цьому прикладі ви розгорнете програму в devnet, кластері Solana для тестування розробки.
Гаманець Playground за замовчуванням підключається до devnet. Переконайтеся, що ваш гаманець Playground має devnet SOL для оплати розгортання програми. Отримайте devnet SOL з Solana Faucet.
$deploy
Налаштування тестового файлу
Стартовий код також включає тестовий файл у anchor.test.ts
.
import { PublicKey } from "@solana/web3.js";describe("pda", () => {it("Create Message Account", async () => {});it("Update Message Account", async () => {});it("Delete Message Account", async () => {});});
Додайте код нижче всередині describe()
, але перед розділами it()
.
const program = pg.program;const wallet = pg.wallet;const [messagePda, messageBump] = PublicKey.findProgramAddressSync([Buffer.from("message"), wallet.publicKey.toBuffer()],program.programId);
Запустіть тестовий файл, виконавши test
у терміналі Playground, щоб
перевірити, що він працює як очікувалося. Наступні кроки додають фактичні тести.
$test
Виклик інструкції Create
Оновіть перший тест наступним чином:
it("Create Message Account", async () => {const message = "Hello, World!";const transactionSignature = await program.methods.create(message).accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetch(messagePda,"confirmed");console.log(JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Виклик інструкції оновлення
Оновіть другий тест наступним чином:
it("Update Message Account", async () => {const message = "Hello, Solana!";const transactionSignature = await program.methods.update(message).accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetch(messagePda,"confirmed");console.log(JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Виклик інструкції видалення
Оновіть третій тест наступним чином:
it("Delete Message Account", async () => {const transactionSignature = await program.methods.delete().accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetchNullable(messagePda,"confirmed");console.log("Expect Null:", JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Запуск тесту
Після підготовки тестів запустіть тестовий файл за допомогою test
у
терміналі Playground. Ця команда запускає тести для програми, розгорнутої в
devnet, і записує посилання на SolanaFM для перегляду деталей транзакції.
$test
Перевірте посилання SolanaFM, щоб переглянути деталі транзакції.
Зауважте, що в цьому прикладі, якщо ви запустите тест знову, інструкція
create
не виконається, оскільки messageAccount
вже існує як обліковий
запис. Лише один обліковий запис може існувати для певного PDA.
Is this page helpful?