程序结构
了解 Anchor 程序的结构,包括关键宏及其在简化 Solana 程序开发中的作用
Anchor 框架使用 Rust 宏 来减少 样板代码并简化进行 Solana 程序开发所需的常见安全检查的实现。
Anchor 程序中主要的宏包括:
declare_id
: 指定程序的链上地址#[program]
: 指定包含程序指令逻辑的模块#[derive(Accounts)]
: 应用于结构体以指示 指令所需的账户列表#[account]
: 应用于结构体以创建程序的自定义 账户类型
示例程序
让我们查看一个简单程序,它演示了上述宏的用法,以理解 Anchor 程序的基本结构。
下面的程序包含一个名为 initialize
的指令,该指令创建
一个新账户(NewAccount
)并用一个 u64
值初始化它。
declare_id! 宏
declare_id
宏指定程序的链上地址,称为程序 ID。
你可以在 这里
找到由 declare_id!
宏生成的代码的实现。
默认情况下,程序 ID 是生成的密钥对公钥,位于
/target/deploy/your_program_name.json
。
要将 declare_id
宏中的程序 ID 值更新为 /target/deploy/your_program_name.json
文件中密钥对的公钥,请运行以下命令:
anchor keys sync
命令在克隆一个仓库时非常有用,因为在被克隆的仓库中
declare_id
宏中的程序 ID 值将与在本地运行 anchor build
生成的值不匹配。
#[program] 属性
#[program]
属性注释包含你程序所有指令处理程序的模块。该模块中的每个公共函数对应于一个
可以被调用的指令。
你可以在 这里
找到由 #[program]
属性生成的代码的实现。
指令上下文
指令处理程序是定义在调用指令时执行逻辑的函数。每个处理程序的第一个参数是一个 Context<T>
类型,
其中 T
是一个实现了
Accounts
特性的结构体,该结构体指定指令所需的账户。
Context
类型为指令提供以下非参数输入的访问:
在指令中可以使用点标记法访问 Context
字段:
ctx.accounts
: 指令所需的账户ctx.program_id
: 程序的公钥(地址)ctx.remaining_accounts
: 在Accounts
结构中未指定的额外账户。ctx.bumps
: 指定在Accounts
结构中的任何程序衍生地址(PDA)账户的 Bump 种子
额外的参数是可选的,可以包含以指定在调用指令时必须提供的参数。
在此示例中,Initialize
结构实施了 Accounts
特性,其中
结构中的每个字段代表 initialize
指令所需的账户。
#[derive(Accounts)] 宏
#[derive(Accounts)]
宏应用于结构体以指定在调用指令时必须提供的账户。该宏实现了
Accounts
特性,从而简化账户验证以及账户数据的序列化和反序列化。
你可以在 这里
找到由 #[derive(Accounts)]
宏生成的代码的实现。
结构中的每个字段代表指令所需的账户。每个字段的命名是任意的,但建议使用一个 描述性的名称来指示账户的目的。
账户验证
为了防止安全漏洞,验证提供给指令的账户是否为预期账户非常重要。 在 Anchor 程序中,账户通过通常一起使用的两种方式进行验证:
-
账户约束: 约束定义了账户必须满足的额外条件, 以被视为对该指令有效。约束通过
#[account(..)]
属性应用,该属性放在实现了Accounts
特性的结构体字段上方。 -
账户类型: Anchor 提供各种账户类型, 以帮助确保客户端提供的账户与程序预期的账户匹配。
你可以在 这里 找到账户类型的实现。
当在 Anchor 程序中调用指令时,程序首先验证提供的账户,
然后才执行指令的逻辑。验证后,这些账户可以在指令内使用
ctx.accounts
语法访问。
#[account] 属性
#[account]
属性应用于定义由程序创建的自定义账户中存储的数据结构的结构体。
此宏实现了多种特性
详见此处。
#[account]
宏的关键功能包括:
- 分配程序所有者:
创建帐户时,帐户的程序所有者会自动设置为在
declare_id
中指定的程序。 - 设置鉴别符: 在初始化期间,作为账户类型相关的唯一 8 字节鉴别符,添加为账户数据的前 8 字节。 这有助于区分账户类型,并用于账户验证。
- 数据序列化和反序列化: 账户数据会被自动序列化和反序列化为账户类型。
账户鉴别符
Anchor 程序中的账户鉴别符是指独特的 8 字节标识符,针对每种账户类型。你可以在 这里 找到账户鉴别符的实现。
鉴别符是字符串 account:<AccountName>
的 SHA256 哈希的前 8 字节。此鉴别符在创建账户时
作为账户数据的前 8 字节进行存储。
在 Anchor 程序中创建账户时,必须为鉴别符分配 8 字节。
鉴别符在以下两种情况下使用:
- 初始化:当创建账户时,鉴别符设置为账户数据的前 8 字节。
- 反序列化:在反序列化账户数据时,会检查账户数据的前 8 字节是否与期望账户类型的鉴别符匹配。
如果不匹配,则表示客户端提供了意外的账户。该机制在 Anchor 程序中充当账户验证检查。