这篇文章详细介绍了Anchor框架中高级功能的实现,包括属性宏和派生宏的使用,账户类型的介绍,以及账户约束检查的实现等。文章提供了大量的示例代码,深入探讨了如何在Solana程序中使用这些高级特性,以便开发者能够更好地理解和利用Anchor进行Solana智能合约的开发。
程序指令入口调度
entry
函数。try_entry
函数。
/// 1.
entrypoint!(entry);
pub fn entry<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::solana_program::entrypoint::ProgramResult {
try_entry(program_id, accounts, data)
.map_err(|e| {
e.log();
e.into()
})
}
/// 2.
fn try_entry<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::Result<()> {
/// 3.
if *program_id != ID {
return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into());
}
/// 3.
if data.len() < 8 {
return Err(anchor_lang::error::ErrorCode::InstructionMissing.into());
}
dispatch(program_id, accounts, data)
}
fn dispatch<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::Result<()> {
/// 4.
let mut ix_data: &[u8] = data;
let sighash: [u8; 8] = {
let mut sighash: [u8; 8] = [0; 8];
sighash.copy_from_slice(&ix_data[..8]);
ix_data = &ix_data[8..];
sighash
};
use anchor_lang::Discriminator;
/// 5.
match sighash {
instruction::Initialize::DISCRIMINATOR => {
__private::__global::initialize(program_id, accounts, ix_data)
}
anchor_lang::idl::IDL_IX_TAG_LE => {
__private::__idl::__idl_dispatch(program_id, accounts, &ix_data)
}
anchor_lang::event::EVENT_IX_TAG_LE => {
Err(anchor_lang::error::ErrorCode::EventInstructionStub.into())
}
_ => Err(anchor_lang::error::ErrorCode::InstructionFallbackNotFound.into()),
}
}
mod __private {
use super::*;
/// __idl 模块定义了注入的 Anchor IDL 指令的处理程序。
pub mod __idl { ... }
/// __global 模块定义了全局指令的包装处理程序。
pub mod __global {
use super::*;
#[inline(never)]
pub fn initialize<'info>(
__program_id: &Pubkey,
__accounts: &'info [AccountInfo<'info>],
__ix_data: &[u8],
) -> anchor_lang::Result<()> {
::solana_program::log::sol_log("指令: 初始化");
...
/// 6.
let mut __accounts = Initialize::try_accounts(
__program_id,
&mut __remaining_accounts,
__ix_data,
&mut __bumps,
&mut __reallocs,
)?;
...
}
}
}
Accounts
在给定的结构上实现一个账户反序列化器。可以通过账户约束提供进一步的功能。
__private::__global::initialize
函数调用。try_accounts
函数实现。
try_accounts
对于数据账户类型
init
-> 初始化该账户)。anchor_solana-expanded.rs
中看到)。try_accounts
实现
try_accounts
实现
...
impl<'info> anchor_lang::Accounts<'info, InitializeBumps> for Initialize<'info>
where
'info: 'info,
{
/// 1.
#[inline(never)]
fn try_accounts(
__program_id: &anchor_lang::solana_program::pubkey::Pubkey,
__accounts: &mut &'info [anchor_lang::solana_program::account_info::AccountInfo<
'info,
>],
__ix_data: &[u8],
__bumps: &mut InitializeBumps,
__reallocs: &mut std::collections::BTreeSet<
anchor_lang::solana_program::pubkey::Pubkey,
>,
) -> anchor_lang::Result<Self> {
/// 2.
let signer: Signer = anchor_lang::Accounts::try_accounts(
__program_id,
__accounts,
__ix_data,
__bumps,
__reallocs,
)
.map_err(|e| e.with_account_name("signer"))?;
/// 2.
let system_program: anchor_lang::accounts::program::Program<System> = anchor_lang::Accounts::try_accounts(
__program_id,
__accounts,
__ix_data,
__bumps,
__reallocs,
)
.map_err(|e| e.with_account_name("system_program"))?;
/// 3. 如果指定了账户约束
Ok(Initialize {
signer,
system_program,
})
}
}
...
#[account]
表示 Solana 账户的数据结构的属性。为给定的特征生成实现
/// 作为参考 - 原始定义
/// #[account]
/// pub struct DataAccount {
/// pub authority: Pubkey,
/// pub counter: u64,
/// }
/// 账户序列化的实现
impl anchor_lang::AccountSerialize for DataAccount {
fn try_serialize<W: std::io::Write>(
&self,
writer: &mut W,
) -> anchor_lang::Result<()> {
if writer.write_all(&[85, 240, 182, 158, 76, 7, 18, 233]).is_err() {
return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
}
if AnchorSerialize::serialize(self, writer).is_err() {
return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
}
Ok(())
}
}
/// 账户反序列化的实现
impl anchor_lang::AccountDeserialize for DataAccount {
fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
if buf.len() < [85, 240, 182, 158, 76, 7, 18, 233].len() {
return Err(
anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into(),
);
}
let given_disc = &buf[..8];
if &[85, 240, 182, 158, 76, 7, 18, 233] != given_disc {
/// 错误信息过长 -> 删除
return Err( ... )
}
Self::try_deserialize_unchecked(buf)
}
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
let mut data: &[u8] = &buf[8..];
AnchorDeserialize::deserialize(&mut data)
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
}
}
/// Discriminator 的实现
impl anchor_lang::Discriminator for DataAccount {
const DISCRIMINATOR: [u8; 8] = [85, 240, 182, 158, 76, 7, 18, 233];
}
/// DataAccount 的实现
impl anchor_lang::Owner for DataAccount {
fn owner() -> Pubkey {
crate::ID
}
}
考虑一个管理两种类型账户的程序,账户 A 和账户 B。这两个账户都由相同的程序拥有,并且具有相同的字段。现在,假设你有一个指令叫做 foo,只能在账户 A 上操作。
然而,用户错误地将账户 B 作为参数传递给 foo 指令。由于账户 B 与账户 A 具有相同的所有者和相同的字段,程序如何检测到这个错误并抛出错误?
这就是 discriminator 的作用。它唯一标识账户的类型。即使账户 A 和账户 B 在结构上是相同的并且共享相同的所有者,它们也具有不同的 discriminator。
[!注意] DISCRIMINATOR 的长度是 8 字节。
[!警告] 零拷贝反序列化是一个实验性特性。建议仅在必要时使用,即当你有无法在不触及栈或堆限制的情况下进行 Borsh 反序列化的极大账户时。
要启用零拷贝反序列化,可以将 zero_copy 参数传递给宏,如下所示:
##[account(zero_copy)]
[!注意] 除了更高效外,此提供的最显著好处是能够定义大于最大栈或堆大小的账户类型。当使用 borsh 时,账户必须被拷贝并反序列化到新的数据结构中,因此受到 BPF 虚拟机 imposed 的栈和堆限制的约束。使用零拷贝反序列化时,账户的后备 RefCell<&mut [u8]> 中的所有字节只是被重新解释为数据结构的引用。不需要分配或拷贝。因此有能力绕过栈和堆的限制。
可以查看所有扩展的 Anchor 宏。
[!提示]
- 使用
anchor expand
命令查看在 Solana 程序中扩展的所有宏。
##[derive(Accounts)]
pub struct Context<'info> {
pub account: Account<'info, CustomAccount>,
}
[!谨慎] 此类型未执行任何验证检查。
##[derive(Accounts)]
pub struct Context<'info> {
/// CHECK: AccountInfo 是未检查的账户
pub account: AccountInfo<'info>,
}
[!注意] 注意,以这种方式使用账户明显不同于使用例如账户。即,必须调用
- load_init 在初始化账户后(这将忽略用户指令代码中仅在用户指令代码后添加的缺失账户 discriminator)
- load 当账户不可变时
- load_mut 当账户是可变的
##[derive(Accounts)]
pub struct Context<'info> {
pub account: AccountLoader<'info, CustomAccount>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub account: Box<Account<'info, CustomAccount>>,
}
[!注意] 程序 ID 验证来自程序 ID 集。
[!提示] TokenInterface 的示例
##[derive(Accounts)]
pub struct Context<'info> {
pub program: Interface<'info, TokenInterface>,
}
[!注意] 所有者验证来自程序 ID 集合。
[!提示] Mint Account 的示例
##[derive(Accounts)]
pub struct Context<'info> {
pub account: InterfaceAccount<'info, TokenAccount>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub account: Option<Account<'info, CustomAccount>>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub program: Program<'info, System>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub account: Signer<'info>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub account: SystemAccount<'info>,
}
##[derive(Accounts)]
pub struct Context<'info> {
pub sysvar: Sysvar<'info, Clock>,
}
[!谨慎] 此类型未执行任何验证检查。
##[derive(Accounts)]
pub struct Context<'info> {
/// CHECK: 对 AccountInfo 类型的显式包装以强调未执行任何检查
pub account: UncheckedAccount<'info>,
}
##[account(mut)]
##[account(
init,
payer = <target_account>,
space = <num_bytes>
)]
[!谨慎] 你需要确保自己妥善防止再初始化攻击,即如果账户已被初始化,你必须确保账户的字段不会重置为初始状态。
##[account(
init_if_needed,
payer = <target_account>,
space = <num_bytes>
)]
##[account(
seeds = <seeds>,
bump = <expr>,
seeds::program = <expr>
)]
##[account(
has_one = <target_account> @ <custom_error>
)]
##[account(
address = <expr> @ <custom_error>
)]
##[account(
owner = <expr> @ <custom_error>
)]
##[account(executable)]
##[account(rent_exempt = skip)]
##[account(rent_exempt = enforce)]
[!提示] 如果你希望在以前的指令中创建账户,然后在你的指令中初始化它而不是使用 init,则使用此约束。这对于大于 10 Kibibyte 的账户是必要的,因为这些账户无法通过 CPI 创建(这也是 init 的功能)。
##[account(zero)]
通过以下方式关闭账户:
需要账户具备 mut。
##[account(close = <target_account>)]
##[account(constraint = <expr> @ <custom_error>)]
[!警告] 由于缺乏原生的运行时检查,以防止重新分配超过
MAX_PERMITTED_DATA_INCREASE
限制(这可能无意中导致账户数据覆盖其他账户),因此不推荐手动使用AccountInfo::realloc
,而应使用 realloc 约束组。该约束组还通过在单个 ix 内检查和限制重复账户重新分配来确保账户重新分配的幂等性。
##[account(
realloc = <space>,
realloc::payer = <target>,
realloc::zero = <bool>
)]
use anchor_spl::{mint, token::{TokenAccount, Mint, Token}};
...
##[account(
init,
payer = payer,
token::mint = mint,
token::authority = payer,
)]
pub token: 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>
use anchor_spl::token::{Mint, Token};
...
##[account(
init,
payer = payer,
mint::decimals = 9,
mint::authority = payer,
)]
pub mint_one: Account<'info, Mint>,
##[account(
init,
payer = payer,
mint::decimals = 9,
mint::authority = payer,
mint::freeze_authority = payer
)]
pub mint_two: Account<'info, Mint>,
##[account(mut)]
pub payer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>
use anchor_spl::{
associated_token::AssociatedToken,
mint,
token::{TokenAccount, Mint, Token}
};
...
##[account(
init,
payer = payer,
associated_token::mint = mint,
associated_token::authority = payer,
)]
pub token: Account<'info, TokenAccount>,
##[account(
associated_token::mint = mint,
associated_token::authority = payer,
)]
pub second_token: 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, AssociatedToken>,
pub system_program: Program<'info, System>
可以选择重写 token_program。
##[account(
mint::token_program = token_program,
)]
pub mint_account: InterfaceAccount<'info, Mint>,
##[account(
token::token_program = token_program,
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,
类型 | 大小(字节) | 描述 |
---|---|---|
bool |
1 | 只需要 1 位,但仍使用 1 字节 |
u8 /i8 |
1 | |
u16 /i16 |
2 | |
u32 /i32 |
4 | |
u64 /i64 |
8 | |
u128 /i128 |
16 | |
[T; amount] |
space(T) * amount |
例如 space([u16; 32]) = 2 * 32 = 64 |
Pubkey |
32 | |
Vec<T> |
4 + (space(T) * amount) |
|
String |
4 + 字符串长度(字节) |
|
Option<T> |
1 + space(T) |
|
Enum |
1 + 最大变量大小 |
例如 Enum { A, B { val: u8 }, C { val: u16 } } -> 1 + space(u16) = 3 |
f32 |
4 | |
f64 |
8 |
[!提示]
如果要增加/减少数据账户的大小,请使用
realloc
约束。如果由于部署时空间不足而无法部署程序(“账户数据对于指令太小”),请使用
solana program extend <PROGRAM_ID> <MORE_BYTES>
CLI 命令。登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!