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ファイルには、次のステップで追加するcreateupdate、および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 {}

始める前に、Playgroundターミナルで*shellbuild*を実行して、スタータープログラムが正常にビルドされることを確認してください。

Terminal
$
build

メッセージアカウントタイプの定義

まず、プログラムが作成するメッセージアカウントの構造を定義します。この構造は、プログラムによって作成されるアカウントに保存するデータを定義します。

lib.rsでは、*rsMessageAccount*構造体を以下のように更新してください:

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

ターミナルで*shellbuild*を実行して、プログラムを再度ビルドしてください。

Terminal
$
build

このコードは、メッセージアカウントに保存するデータを定義しています。次に、プログラムの命令を追加します。

Create命令を追加する

次に、*rscreate*命令を追加して *rsMessageAccount*を作成し初期化します。

まず、*rsCreate*構造体を以下のように更新して、命令に必要なアカウントを定義します:

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関数を更新して、*rscreate*命令のビジネスロジックを追加します:

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

削除命令を追加する

次に、MessageAccount を閉じるための delete 命令を追加します。

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プログラムが完成しました。Playgroundターミナルで deploy を実行してプログラムをデプロイします。

この例では、開発テスト用のSolanaクラスターであるdevnetにプログラムをデプロイします。

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 () => {});
});

以下のコードを*tsdescribe()*の中に、*tsit()*セクションの前に追加してください。

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

Playgroundターミナルで*shelltest*を実行して、テストファイルが期待通りに動作することを確認してください。次のステップでは実際のテストを追加します。

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`
);
});

Update命令を呼び出す

以下のように2番目のテストを更新します:

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`
);
});

削除命令を呼び出す

3番目のテストを次のように更新します:

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`
);
});

テストを実行する

テストの準備が整ったら、Playgroundターミナルで test を使用してテストファイルを実行します。このコマンドは、devnetにデプロイされたプログラムに対してテストを実行し、トランザクションの詳細を表示するためのSolanaFMへのリンクをログに記録します。

Terminal
$
test

SolanaFMのリンクを確認して、トランザクションの詳細を表示します。

このサンプルでは、テストを再度実行すると、messageAccount というアカウントがすでに存在するため、create 命令は失敗することに注意してください。特定のPDAに対して存在できるアカウントは1つだけです。

Is this page helpful?