在本节中,您将学习如何构建基本的 Create, Read, Update, Delete (CRUD) 程序。
本指南演示了一个简单的程序,用户可以在其中创建、更新和删除消息。每条消息都存在于一个帐户中,该帐户具有从程序本身派生的确定性地址(Program Derived Address 或 PDA)。
目录
(PDA:我的理解是,代表一个账户)
本指南将引导您使用 Anchor 框架构建和测试 Solana 程序,同时演示程序派生地址 (PDA)。有关更多详细信息,请参阅 Program Derived Addresses 页面。
作为参考,您可以查看 完成 PDA 和跨程序调用 (CPI) 部分后的最终代码。
1.开始代码
首先打开这个 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 终端中运行 build
以检查入门程序构建是否成功。
2.定义消息帐户类型
首先,定义程序创建的消息帐户的结构。此结构定义要存储在程序创建的帐户中的数据。
在 lib.rs
中,使用以下命令更新 MessageAccount
结构:
#[account]
pub struct MessageAccount {
pub user: Pubkey,
pub message: String,
pub bump: u8,
}
解释:
Anchor 程序中的 #[account]
属性对表示帐户数据(要存储在 Account 数据字段中的数据类型)的结构进行注释。
在此示例中,MessageAccount
结构存储由用户创建的消息,其中包含三个字段:
user
- 标识创建消息账户的用户的公钥
。
message
- 包含用户消息的字符串
。
bump
- 存储 “bump” 种子的 u8
用于派生程序派生地址 (PDA)。存储此值可节省 计算,无需在后面的说明中重新计算它。
创建帐户时,程序会序列化 MessageAccount
数据并将其存储在新帐户的 data 字段中。
稍后,当从帐户读取时,程序将此数据反序列化回 MessageAccount
数据类型。测试部分演示了创建和读取账户数据的过程。
通过在终端中运行 build
再次构建程序。
此代码定义要在消息帐户上存储的数据。接下来,您将添加程序说明。
3.添加创建指令
现在,添加创建并初始化 MessageAccount
的 API 中。
首先,通过更新 使用Create
struct:
#[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>,
}
解释:
Anchor 程序中的 #[derive(Accounts)]
属性对定义指令所需帐户的结构进行注释。
结构中的每个字段都表示一个以两种方式验证的帐户:
a. 指定程序所需的帐户类型的帐户类型(如 Signer<'info>
或 Account<'info, T>
)
b. 定义额外要求的可选约束(如 #[account(mut)]
或 #[account(init)]
)
总之,这些使 Anchor 能够自动验证传递给指令的帐户并保护程序。
结构中的字段名称提供对程序代码中账户的访问,但不会影响验证。为清楚起见,您应该使用描述性名称。
我这边有一些疑问解答一下。
问:#[instruction(message: String)]有什么用?
答:#[instruction(message: String)]允许你在调用指令时传递参数。message是一个字符串参数,表示要创建的消息内容。这个参数会在调用指令时被传递给函数。
问:结构体 Create<'info>为什么后面要加上<'info>?
答:在Rust中,
Create<'info>
中的<'info>
表示一个生命周期参数。生命周期是Rust的一种特性,用于确保引用的有效性,防止悬垂引用和数据竞争。问:可以不加<'info>吗?
答:是否需要添加生命周期参数(如
<'info>
)取决于结构体的具体定义和使用场景。如果结构体中没有引用类型的字段,那么就不需要添加生命周期参数。问:#[account(mut)]
pub user: Signer<'info>,
为什么前面要加 #[account(mut)]?答:
#[account(mut)]
是一个属性宏,用于标记一个账户(在这个例子中是user
)为可变的。这意味着在合约执行期间,该账户的状态可以被修改。问:pub system_program: Program<'info, System>,为什么后面加<'info, System>?还有两个?
答:在Rust的Anchor框架中,
Program<'info, System>
是一个泛型类型,用于表示一个与特定程序(在这里是System
程序)交互的程序账户。这个类型的两个生命周期参数'info
和System
分别有不同的含义。问:Anchor框架是用来干嘛的,可以不用吗?
答:可以不用,但不好。Anchor框架是一个用于构建Solana智能合约的开发框架,它提供了一系列工具和库,以简化Solana程序的开发过程。
接下来,通过更新 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
问:为什么ctx里面有accounts、bumps?
答:accounts是一个结构体,包含了在当前上下文中所有需要的账户。它们是通过#[derive(Accounts)]宏定义的,表示在调用该指令时需要的账户。这些账户在执行create函数时会被自动填充,确保它们的状态和权限是正确的。
bumps是在编译时通过#[derive(Accounts)]宏生成的,并在运行时根据账户的种子(seeds)自动计算和填充的。种子是用于生成账户地址的基础,而bump值则是为了确保生成的地址是唯一的。
4.添加更新指令
接下来,添加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(())
}
5.添加删除指令
接下来,添加 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(())
}
代码整合
use anchor_lang::prelude::*;
declare_id!("2T5oHSgS6sGrjqKAzjtnycPG4qxnGs5fCTdu9AeLpiSr");
#[program]
pub mod pda {
use super::*;
pub fn create(ctx: Context<Create>, message: String) -> Result<()> {
msg!("Create Messsage: {}", 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(())
}
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(())
}
pub fn delete(_ctx: Context<Delete>) -> Result<()> {
msg!("Delete Message");
Ok(())
}
}
#[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>,
}
#[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>,
}
#[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>,
}
#[account]
pub struct MessageAccount {
pub user: Pubkey,
pub message: String,
pub bump: u8,
}
参考:链接