这篇文章详细介绍了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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!