Program Derived Address

В этом разделе вы узнаете, как создать базовую программу для создания, чтения, обновления и удаления (CRUD).

Это руководство демонстрирует простую программу, где пользователи могут создавать, обновлять и удалять сообщения. Каждое сообщение существует в аккаунте с детерминированным адресом, выведенным из самой программы (Program Derived Address или PDA).

Это руководство проведет вас через процесс создания и тестирования программы Solana с использованием фреймворка Anchor, демонстрируя использование Program Derived Addresses (PDA). Для получения дополнительной информации обратитесь к странице 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?

Program Derived Address | Solana