了解 Anchor 程序的结构,包括关键宏及其在简化 Solana 程序开发中的作用。
Anchor 框架使用 Rust 宏 来减少样板代码,并简化编写 Solana 程序所需的常见安全检查的实现。
Anchor 程序中主要的宏包括:
declare_id
:指定程序的链上地址。#[program]
:指定包含程序指令逻辑的模块。#[derive(Accounts)]
:用于结构体,表示指令所需的账户列表。#[account]
:用于结构体,为程序创建自定义账户类型。
示例程序
以下是一个简单程序,展示了上述宏的用法,以帮助理解 Anchor 程序的基本结构。
该程序包含一个名为 initialize
的指令,用于创建一个新账户(NewAccount
),并使用 u64
值初始化。
// 引入 Anchor 框架的预置模块(包含常用宏、类型和功能)
use anchor_lang::prelude::*;
// 声明本程序的程序 ID(Program ID),部署到链上时需替换为真实值
declare_id!("11111111111111111111111111111111");
#[program] //Anchor 程序的主入口模块,定义一个叫 hello_anchor 的 Solana 程序(Program),里面会包含这个程序暴露给前端调用的所有处理逻辑
mod hello_anchor {
use super::*;
// 主入口函数之一:initialize
// 接收一个 Context(上下文对象)和一个参数 data(u64 类型)
// 创建并初始化一个 NewAccount 类型的账户,并设置其 data 字段
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
// 设置新账户的 data 字段为传入值
ctx.accounts.new_account.data = data;
// 打印一条日志信息(在 Solana Explorer 中可见)
msg!("Changed data to: {}!", data);
Ok(())
}
}
// 定义「指令所需要的账户」,用于上下文验证和账户检查
#[derive(Accounts)]
//'info 保证账户引用在程序执行期间是安全有效的
pub struct Initialize<'info> {
// 创建一个新账户,类型为 NewAccount
#[account(
// - init:表示这是一个新账户,要初始化
init,
// - payer = signer:签名者负责支付创建账户所需的租金
payer = signer,
// - space = 8 + 8:账户所需的存储空间,8 是 Anchor 的账户标识符(discriminator),另 8 是 u64 的字节数
space = 8 + 8
)]
pub new_account: Account<'info, NewAccount>, //Anchor 提供的账户封装,用于访问 Solana 上的数据账户并自动反序列化为 T 类型
// 签名者账户(mutable,因为它需要支付租金)
#[account(mut)]
pub signer: Signer<'info>,
// 引用系统程序(用于创建账户)
pub system_program: Program<'info, System>,
}
// 定义「账户里的数据结构」,用于储存在区块链上的状态
// 默认使用 Anchor 的 #[account] 宏自动添加标识符等元数据
#[account]
pub struct NewAccount {
// 存储一个 u64 类型的数据
data: u64,
}
这个程序实现了一个非常典型的 Anchor 初始化账户逻辑。重点是:
#[account(init)]
的用法。space = 8 + 8
中的8
是 Anchor 自动为每个账户分配的前缀 discriminator。Context<Initialize>
会自动帮你打包账户结构体。Account
是一个泛型结构体,它包含:- 账户元数据(如账户地址、公钥、余额等)
- 账户数据反序列化后得到的 Rust 结构体,这里就是
NewAccount
📦 它的作用是:
- 自动帮你反序列化账户数据为
NewAccount
- 自动做一些安全检查(比如检查账户是否是预期类型)
- 允许你像操作普通 Rust 结构体那样操作 Solana 上的数据
'info
生命周期告诉 Rust:这个账户在程序运行时有效
declare_id! 宏
declare_id!
宏用于指定程序的链上地址(程序 ID)。
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
默认情况下,程序 ID 是生成的密钥对的公钥,路径为 /target/deploy/your_program_name.json
。
使用以下命令可以将 declare_id
中的值与实际生成的密钥同步:
anchor keys sync
这在你克隆了一个仓库,但该仓库的 declare_id!
值与你本地生成的不一致时非常有用。
#[program] 属性
#[program]
属性用于标注包含所有指令处理函数的模块。模块内每个 pub fn
对应一个可被调用的指令。
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
指令上下文(Instruction Context)
每个指令函数的第一个参数是 Context<T>
,其中 T
是实现了 Accounts
trait 的结构体,指定该指令所需的账户。
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
Context
提供以下字段:
ctx.accounts
: 指令所需的账户ctx.program_id
: 当前程序的 IDctx.remaining_accounts
: 额外传入但未声明的账户ctx.bumps
: PDA 的 bump 种子
#[derive(Accounts)] 宏
该宏用于结构体,自动实现 Accounts
trait,以声明指令所需的账户,并负责账户验证和序列化。
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
// 签名者账户,必须 mutable(因为它将支付 lamports,因此余额会发生变化)
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
账户验证
Anchor 通过两种方式验证账户的合法性:
- 账户约束(Account Constraints):通过
#[account(...)]
属性添加约束,如init
、mut
、seeds
等,用于确保账户满足特定条件。 - 账户类型(Account Types):Anchor 提供内建账户类型(如
Account<T>
、Signer
等),用于类型检查和自动验证。
#[account] 属性
用于为程序创建自定义账户结构,宏将实现自动序列化、反序列化、添加 discriminator(用于标识账户类型)等功能。
#[account]
pub struct NewAccount {
data: u64,
}
此宏还会在账户初始化时设置账户拥有者为当前程序 ID。