本文介绍了如何在Solana的Anchor框架中使用Account结构体的约束条件,以提高程序的安全性和代码可读性。文章详细解释了各种约束的使用方法和示例,并提供了相关的代码片段。
Anchor 是一个加速在 Solana 上构建安全 Rust 程序的框架。它提供了一套工具,让你能够快速轻松地编写、测试和部署程序。Accounts 结构体是 Solana 编程中 Anchor 框架的一部分,它允许你定义程序与之交互的账户。在创建 Accounts 结构体时,开发者可以选择在账户上定义某些约束,以强制执行某些条件(例如,指定账户所有权)。本指南将探讨如何定义这些约束,以帮助提高程序的安全性和代码的可读性。
在深入探讨约束之前,让我们先来看一个简单的 Anchor Account 结构体示例(来源:anchor-lang.com):
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
ctx.accounts.my_account.data = data;
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
data: u64
}
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>
}
Account 是 AccountInfo 的包装器,用于验证程序所有权并将底层数据反序列化为 Rust 类型。
在示例代码中,#[derive(Accounts)]
是一个过程宏,它自动生成 SetData 结构体的定义,表示 set_data 函数所需的输入账户。Rust 中的 #[derive]
属性是一种语言特性,允许你基于结构体或枚举的定义自动生成代码。在这种情况下,#[derive(Accounts)]
宏由 anchor-lang crate 提供,它根据字段的属性为 SetData 结构体生成代码。
SetData 结构体定义了 set_data
函数所需的账户。在本例中,它有一个名为 my_account
的字段,这是一个 Account 结构体,表示下面定义的 MyAccount 类型的一个实例。my_account
字段上的 #[account(mut)]
属性表示它是一个可变账户,意味着函数将修改其状态。
在构建 Solana 程序时,安全性对程序的可行性至关重要。Anchor 的 Accounts 结构体提供了一个方便的内置功能,以简化常见的安全检查。约束允许你验证传入的账户(或其底层数据)是否符合某些预定义的要求。要添加约束,请使用以下格式:
#[account(<constraints>)]
pub account: AccountType
如果你在 Anchor 中进行过任何构建,你可能已经在不知不觉中使用过约束。例如,mut
约束用于确保指定的账户必须是可变的:
#[account(mut)]
pub my_account: Account<'info, MyAccount>
在上面的示例中,如果用户尝试将不可变账户作为 my_account
传递,程序将抛出错误。
让我们来看一些可以添加到 Accounts 结构体中的其他常见约束示例。
属性 | 示例实现 | 描述 |
---|---|---|
mut | rust #[account(mut)] pub mutable_account: Account<'info, MyData>, |
验证账户是否可变 |
signer | rust #[account(signer)] pub signer_account: Account<'info, MyData>, |
验证账户是否签署了交易 |
init, payer, space | rust #[account(init, payer = bank, space = 200)] pub new_account: Account<'info, MyData>, #[account(mut)] pub bank: Signer<'info>, pub new_account: Account<'info, MyData>, system_program: Program<'info, System>, |
初始化一个新账户。init 必须与 payer(支付新账户租金的另一个账户的引用)和 space(分配给新账户的字节数)一起使用。注意:你必须将系统程序作为 system_program 传递到你的账户中,以创建新账户。 |
init_if_needed 可以代替 init 使用,以在账户尚不存在时创建账户;然而,Anchor 警告说:“使用 init_if_needed 时,你需要确保自己免受重新初始化攻击。你需要在代码中包含检查,确保初始化后的账户不能在第一次初始化后被重置为其初始设置(除非这是你想要的)。”init_if_needed 可以通过导入带有 init-if-needed cargo 功能的 anchor-lang 来启用。 | ||
seeds, bump | rust #[account(seeds = [ b"example", user.key().as_ref() ], bump)] pub user_ex_pda: Account<'info, MyData>, pub user: Signer<'info>, |
验证传递的账户是否派生自当前执行的程序和定义的种子。注意:必须提供 bump。如果未设置为表达式,Anchor 使用规范的 bump。我们将在下面的指令数据部分讨论 bump 表达式。 |
要为当前执行程序之外的程序派生 PDA,你可以使用 seeds::program = other_program.key()。 | ||
has_one | rust #[account(has_one = authority)] pub data: Account<'info, MyData>, pub authority: Signer<'info>, |
验证指定的账户上有一个字段等于指定同名账户的 key。在本例中,data.authority 必须与 authority.key() 相同。 |
address | rust #[account(address = SOME_PUBKEY)] pub authority: Signer<'info>, |
验证账户 key 是否与传递的 pubkey 匹配(例如,如果你将某个权限限制为指定的 SOME_PUBKEY)。 |
owner | rust #[account(owner = token_program.key())] pub data: Account<'info, MyData>, pub token_program: Account<'info, Token>, |
验证账户所有者是否与传递的 pubkey 匹配(例如,示例要求 data 账户由 Token 程序拥有)。 |
executable | rust #[account(executable)] pub some_program: AccountInfo<'info>, |
验证账户是否是一个程序。 |
rent_exempt | rust #[account(rent_exempt = skip)] pub skipped_account: Account<'info, MyData>, #[account(rent_exempt = enforce)] pub enforced_account: Account<'info, MyData>, |
跳过或强制执行通常由其他约束(例如,init)完成的租金豁免检查。 |
zero | rust #[account(zero)] pub zero_account: Account<'info, MyData>, |
检查账户的 discriminator 是否为零。这对于大于 10kb 的账户是必要的。这必须通过两步完成:创建账户,然后用 zero 初始化它。 |
close | rust #[account(close = receiver)] pub closing: Account<'info, MyData>, pub receiver: SystemAccount<'info>, |
在指令结束时将账户标记为已关闭,并将其 lamports 发送到指定账户。 |
constraint | rust #[account( constraint = acct_one.age == acct_two.info.age )] pub acct_one: Account<'info, MyData>, pub acct_two: Account<'info, OtherData>, |
验证给定表达式是否评估为 true。 |
realloc | rust #[account( mut, realloc = 100, realloc::payer = bank realloc::zero = false )] pub update_acct: Account<'info, MyData>, #[account(mut)] pub bank: Signer<'info>, system_program: Program<'info, System>, |
更新分配给账户的空间量(字节)。与新空间相关的任何成本将由 realloc::payer 支付。减少空间所恢复的 lamports 将由 realloc::payer 接收。使用 realloc::zero 来确定重新分配后是否应将新内存初始化为零。 |
除了上述约束外,Anchor SPL 库还包含几个特定于 SPL Token 程序的约束:
属性 | 示例实现 | 描述 |
---|---|---|
token::mint, token::authority | rust #[account( init, payer = payer, token::mint = mint, token::authority = payer, )] pub new_ata: Account<'info, TokenAccount>, #[account(address = mint::USDC)] pub mint: Account<'info, Mint>, #[account(mut)] pub payer: Signer<'info>, pub token_program: Program<'info, Token>, pub system_program: Program<'info, System> |
验证 TokenAccount 的 mint 地址和权限。如果使用 init,你必须同时传递 mint 和 authority。如果只是检查账户,你可以使用其中一个选项。 |
mint::authority, mint::decimals, mint::freeze_authority | rust #[account( init, payer = payer, mint::decimals = 9, mint::authority = payer, mint::freeze_authority = payer, )] pub new_ata: Account<'info, Mint>, #[account(mut)] pub payer: Signer<'info>, pub token_program: Program<'info, Token>, pub system_program: Program<'info, System> |
使用给定的 mint decimals 和 mint authority 创建或检查一个 mint 账户。如果使用 init,你必须同时传递 decimals 和 authority。freeze authority 是可选的。如果只是检查账户,你可以使用其中一个选项。 |
associated_token::mint, associated_token::authority | rust #[account( init, payer = payer, associated_token::mint = mint, associated_token::authority = payer, )] pub new_ata: Account<'info, TokenAccount>, #[account(address = mint::USDC)] pub mint: Account<'info, Mint>, #[account(mut)] pub payer: Signer<'info>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, Token>, pub system_program: Program<'info, System> |
验证关联 token 账户的 mint 地址和权限。如果使用 init,你必须同时传递 mint 和 authority。如果只是检查账户,你可以使用其中一个选项。 |
Anchor 约束使得抛出自定义错误变得容易。如果约束被违反,你可以通过在约束后添加 @
来调用错误,如下所示:
#[account(mut @ MyError::AccountNotMutable)]
pub my_account: Account<'info, MyData>
上面的示例将抛出你的 AccountNotMutable
错误,如果用户在没有将其标记为可变的情况下传递了 my_account
。
@
错误处理程序适用于 mut
、signer
、has_one
、address
、owner
和 constraint
。
有时你需要从账户指令中获取数据以传递到约束中(例如,你可能希望传递 bump
以增强安全性,或基于用户输入派生 PDA,例如餐厅评论的 restaurant_name
)。要访问指令的参数,你必须使用 #[instruction(..)]
属性。以下是如何使用它的示例:
#[derive(Accounts)]
#[instruction(entered_bump: u8, restaurant: String)]
pub struct InitializeReview<'info> {
#[account(\
init,\
payer = payer,\
space = 100\
seeds = [restaurant.as_bytes().as_ref(), signer.key().as_ref()],\
bump = entered_bump\
)]
pub pda_data_account: Account<'info, RestaurantReview>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
掌握约束可能会有些棘手,所以如果一开始没有理解,不要灰心。像任何事情一样,这需要一点练习。随时可以收藏此页面,并在程序中定义新的账户上下文时回来参考。约束和适当的错误处理可以使你的代码更安全和更易读!
如果你遇到困难,有问题,或者只是想聊聊你在构建什么,请在 Discord 或 Twitter 上给我们留言!
如果你对本指南有任何反馈,请告诉我们。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!