Program Derived Address
在本节中,您将学习如何构建一个基本的创建、读取、更新、删除(CRUD)程序。
本指南演示了一个简单的程序,用户可以创建、更新和删除消息。每条消息都存在于一个由程序本身(Program Derived Address 或 PDA)派生的确定性地址的账户中。
本指南将带您逐步构建和测试一个使用 Anchor 框架的 Solana 程序,同时演示 Program Derived Addresses (PDAs)。有关更多详细信息,请参阅 Program Derived Addresses 页面。
作为参考,您可以在完成 PDA 和 Cross-Program Invocation (CPI) 部分后查看最终代码。
初始代码
首先打开此Solana Playground 链接,其中包含初始代码。然后点击“导入”按钮,将程序添加到您的 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 终端中运行
build
,以检查初始程序是否成功构建。
$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 程序。通过在 Playground 终端中运行 deploy
部署程序。
在此示例中,您将程序部署到 devnet,这是一个用于开发测试的 Solana 集群。
Playground 钱包默认连接到 devnet。确保您的 Playground 钱包中有 devnet SOL 以支付程序部署费用。从 Solana Faucet 获取 devnet SOL。
$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);
通过在 Playground 终端中运行 test
来运行测试文件,以检查其是否按预期运行。接下来的步骤将添加实际测试。
$test
调用创建指令
使用以下内容更新第一个测试:
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`);});
运行测试
准备好测试后,在 Playground 终端中使用 test
运行测试文件。此命令会针对部署在 devnet 上的程序运行测试,并记录指向 SolanaFM 的链接以查看交易详情。
$test
检查 SolanaFM 链接以查看交易详情。
请注意,在此示例中,如果再次运行测试,create
指令会失败,因为
messageAccount
已作为账户存在。对于给定的 PDA,只能存在一个账户。
Is this page helpful?