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, які потрібно додати у наступних кроках.

lib.rs
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, щоб перевірити, що початкова програма успішно компілюється.

Terminal
$
build

Визначення типу облікового запису повідомлення

Спочатку визначте структуру для облікового запису повідомлення, який створює програма. Ця структура визначає дані, які будуть зберігатися в обліковому записі, створеному програмою.

У lib.rs, оновіть структуру MessageAccount наступним чином:

lib.rs
#[account]
pub struct MessageAccount {
pub user: Pubkey,
pub message: String,
pub bump: u8,
}

Зберіть програму знову, виконавши build у терміналі.

Terminal
$
build

Цей код визначає, які дані зберігати в обліковому записі повідомлення. Далі ви додасте інструкції програми.

Додати інструкцію створення

Тепер додайте інструкцію create, яка створює та ініціалізує MessageAccount.

Почніть з визначення облікових записів, необхідних для інструкції, оновивши структуру Create наступним чином:

lib.rs
#[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 наступним чином:

lib.rs
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(())
}

Перебудуйте програму.

Terminal
$
build

Додайте інструкцію оновлення

Далі додайте інструкцію update для зміни MessageAccount новим повідомленням.

Як і на попередньому кроці, спочатку вкажіть облікові записи, необхідні для інструкції update.

Оновіть структуру Update наступним чином:

lib.rs
#[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.

lib.rs
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(())
}

Перебудуйте програму

Terminal
$
build

Додайте інструкцію видалення

Далі додайте інструкцію delete для закриття MessageAccount.

Оновіть структуру Delete наступним чином:

lib.rs
#[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.

lib.rs
pub fn delete(_ctx: Context<Delete>) -> Result<()> {
msg!("Delete Message");
Ok(())
}

Перебудуйте програму.

Terminal
$
build

Розгортання програми

Ви завершили базову програму CRUD. Розгорніть програму, виконавши deploy у терміналі Playground.

У цьому прикладі ви розгорнете програму в devnet, кластері Solana для тестування розробки.

Гаманець Playground за замовчуванням підключається до devnet. Переконайтеся, що ваш гаманець Playground має devnet SOL для оплати розгортання програми. Отримайте devnet SOL з Solana Faucet.

Terminal
$
deploy

Налаштування тестового файлу

Стартовий код також включає тестовий файл у anchor.test.ts.

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().

anchor.test.ts
const program = pg.program;
const wallet = pg.wallet;
const [messagePda, messageBump] = PublicKey.findProgramAddressSync(
[Buffer.from("message"), wallet.publicKey.toBuffer()],
program.programId
);

Запустіть тестовий файл, виконавши test у терміналі Playground, щоб перевірити, що він працює як очікувалося. Наступні кроки додають фактичні тести.

Terminal
$
test

Виклик інструкції Create

Оновіть перший тест наступним чином:

anchor.test.ts
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`
);
});

Виклик інструкції оновлення

Оновіть другий тест наступним чином:

anchor.test.ts
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`
);
});

Виклик інструкції видалення

Оновіть третій тест наступним чином:

anchor.test.ts
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 для перегляду деталей транзакції.

Terminal
$
test

Перевірте посилання SolanaFM, щоб переглянути деталі транзакції.

Зауважте, що в цьому прикладі, якщо ви запустите тест знову, інструкція create не виконається, оскільки messageAccount вже існує як обліковий запис. Лише один обліковий запис може існувати для певного PDA.

Is this page helpful?