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?