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?