Program Derived Address
このセクションでは、基本的なCreate, Read, Update, Delete(CRUD)プログラムの構築方法を学びます。
このガイドでは、ユーザーがメッセージを作成、更新、削除できるシンプルなプログラムを紹介します。各メッセージは、プログラム自体から派生した決定論的アドレス(Program Derived AddressまたはPDA)を持つアカウントに存在します。
このガイドでは、Anchorフレームワークを使用してSolanaプログラムを構築しテストする方法を説明しながら、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 {}
始める前に、Playgroundターミナルで*shellbuild
*を実行して、スタータープログラムが正常にビルドされることを確認してください。
$build
メッセージアカウントタイプの定義
まず、プログラムが作成するメッセージアカウントの構造を定義します。この構造は、プログラムによって作成されるアカウントに保存するデータを定義します。
lib.rs
では、*rsMessageAccount
*構造体を以下のように更新してください:
#[account]pub struct MessageAccount {pub user: Pubkey,pub message: String,pub bump: u8,}
ターミナルで*shellbuild
*を実行して、プログラムを再度ビルドしてください。
$build
このコードは、メッセージアカウントに保存するデータを定義しています。次に、プログラムの命令を追加します。
Create命令を追加する
次に、*rscreate
*命令を追加して *rsMessageAccount
*を作成し初期化します。
まず、*rsCreate
*構造体を以下のように更新して、命令に必要なアカウントを定義します:
#[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
関数を更新して、*rscreate
*命令のビジネスロジックを追加します:
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
削除命令を追加する
次に、MessageAccount
を閉じるための delete
命令を追加します。
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プログラムが完成しました。Playgroundターミナルで deploy
を実行してプログラムをデプロイします。
この例では、開発テスト用のSolanaクラスターであるdevnetにプログラムをデプロイします。
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 () => {});});
以下のコードを*tsdescribe()
*の中に、*tsit()
*セクションの前に追加してください。
const program = pg.program;const wallet = pg.wallet;const [messagePda, messageBump] = PublicKey.findProgramAddressSync([Buffer.from("message"), wallet.publicKey.toBuffer()],program.programId);
Playgroundターミナルで*shelltest
*を実行して、テストファイルが期待通りに動作することを確認してください。次のステップでは実際のテストを追加します。
$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`);});
Update命令を呼び出す
以下のように2番目のテストを更新します:
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`);});
削除命令を呼び出す
3番目のテストを次のように更新します:
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`);});
テストを実行する
テストの準備が整ったら、Playgroundターミナルで test
を使用してテストファイルを実行します。このコマンドは、devnetにデプロイされたプログラムに対してテストを実行し、トランザクションの詳細を表示するためのSolanaFMへのリンクをログに記録します。
$test
SolanaFMのリンクを確認して、トランザクションの詳細を表示します。
このサンプルでは、テストを再度実行すると、messageAccount
というアカウントがすでに存在するため、create
命令は失敗することに注意してください。特定のPDAに対して存在できるアカウントは1つだけです。
Is this page helpful?