LogoAnchor 中文文档

程序结构

了解 Anchor 程序的结构,包括关键宏及其在简化 Solana 程序开发中的作用

Anchor 框架使用 Rust 宏 来减少 样板代码并简化进行 Solana 程序开发所需的常见安全检查的实现。

Anchor 程序中主要的宏包括:

示例程序

让我们查看一个简单程序,它演示了上述宏的用法,以理解 Anchor 程序的基本结构。

下面的程序包含一个名为 initialize 的指令,该指令创建 一个新账户(NewAccount)并用一个 u64 值初始化它。

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[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(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

declare_id! 宏

declare_id 宏指定程序的链上地址,称为程序 ID。 你可以在 这里 找到由 declare_id! 宏生成的代码的实现。

lib.rs
use anchor_lang::prelude::*;
 

declare_id!("11111111111111111111111111111111");

默认情况下,程序 ID 是生成的密钥对公钥,位于 /target/deploy/your_program_name.json

要将 declare_id 宏中的程序 ID 值更新为 /target/deploy/your_program_name.json 文件中密钥对的公钥,请运行以下命令:

Terminal
anchor keys sync

anchor keys sync 命令在克隆一个仓库时非常有用,因为在被克隆的仓库中 declare_id 宏中的程序 ID 值将与在本地运行 anchor build 生成的值不匹配。

#[program] 属性

#[program] 属性注释包含你程序所有指令处理程序的模块。该模块中的每个公共函数对应于一个 可以被调用的指令。

你可以在 这里 找到由 #[program] 属性生成的代码的实现。

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 

#[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(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

指令上下文

指令处理程序是定义在调用指令时执行逻辑的函数。每个处理程序的第一个参数是一个 Context<T> 类型, 其中 T 是一个实现了 Accounts 特性的结构体,该结构体指定指令所需的账户。

Context 类型为指令提供以下非参数输入的访问:

pub struct Context<'a, 'b, 'c, 'info, T: Bumps> {
    /// 当前执行的程序 id。
    pub program_id: &'a Pubkey,
    /// 反序列化的账户。
    pub accounts: &'b mut T,
    /// 已给出但未反序列化或验证的剩余账户。
    /// 使用时要非常小心。
    pub remaining_accounts: &'c [AccountInfo<'info>],
    /// 在约束验证期间找到的 Bump 种子。它作为便利提供
    /// 以便处理程序不必重新计算 bump 种子或将其作为参数传入。
    /// 类型是由 #[derive(Accounts)] 生成的 bumps 结构。
    pub bumps: T::Bumps,
}

在指令中可以使用点标记法访问 Context 字段:

  • ctx.accounts: 指令所需的账户
  • ctx.program_id: 程序的公钥(地址)
  • ctx.remaining_accounts: 在 Accounts 结构中未指定的额外账户。
  • ctx.bumps: 指定在 Accounts 结构中的任何程序衍生地址(PDA)账户的 Bump 种子

额外的参数是可选的,可以包含以指定在调用指令时必须提供的参数。

lib.rs


pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
    ctx.accounts.new_account.data = data;
    msg!("Changed data to: {}!", data);
    Ok(())
}

在此示例中,Initialize 结构实施了 Accounts 特性,其中 结构中的每个字段代表 initialize 指令所需的账户。

lib.rs


#[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(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)] 宏

#[derive(Accounts)] 宏应用于结构体以指定在调用指令时必须提供的账户。该宏实现了 Accounts 特性,从而简化账户验证以及账户数据的序列化和反序列化。

你可以在 这里 找到由 #[derive(Accounts)] 宏生成的代码的实现。


#[derive(Accounts)]

pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

结构中的每个字段代表指令所需的账户。每个字段的命名是任意的,但建议使用一个 描述性的名称来指示账户的目的。

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]

    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]

    pub signer: Signer<'info>,

    pub system_program: Program<'info, System>,
}

账户验证

为了防止安全漏洞,验证提供给指令的账户是否为预期账户非常重要。 在 Anchor 程序中,账户通过通常一起使用的两种方式进行验证:

  • 账户约束: 约束定义了账户必须满足的额外条件, 以被视为对该指令有效。约束通过 #[account(..)] 属性应用,该属性放在实现了 Accounts 特性的结构体字段上方。

    你可以在 这里 找到完整的约束列表,以及其实现 这里

    #[derive(Accounts)]
    pub struct Initialize<'info> {
    
        #[account(init, payer = signer, space = 8 + 8)]
        pub new_account: Account<'info, NewAccount>,
    
        #[account(mut)]
        pub signer: Signer<'info>,
        pub system_program: Program<'info, System>,
    }
  • 账户类型: Anchor 提供各种账户类型, 以帮助确保客户端提供的账户与程序预期的账户匹配。

    你可以在 这里 找到账户类型的实现。

    #[derive(Accounts)]
    pub struct Initialize<'info> {
        #[account(init, payer = signer, space = 8 + 8)]
    
        pub new_account: Account<'info, NewAccount>,
        #[account(mut)]
    
        pub signer: Signer<'info>,
    
        pub system_program: Program<'info, System>,
    }

当在 Anchor 程序中调用指令时,程序首先验证提供的账户, 然后才执行指令的逻辑。验证后,这些账户可以在指令内使用 ctx.accounts 语法访问。

lib.rs


use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[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(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

#[account] 属性

#[account] 属性应用于定义由程序创建的自定义账户中存储的数据结构的结构体。


#[account]
pub struct NewAccount {
    data: u64,
}

此宏实现了多种特性 详见此处#[account] 宏的关键功能包括:

  • 分配程序所有者: 创建帐户时,帐户的程序所有者会自动设置为在 declare_id 中指定的程序。
  • 设置鉴别符: 在初始化期间,作为账户类型相关的唯一 8 字节鉴别符,添加为账户数据的前 8 字节。 这有助于区分账户类型,并用于账户验证。
  • 数据序列化和反序列化: 账户数据会被自动序列化和反序列化为账户类型。
lib.rs

use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[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(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 


#[account]
pub struct NewAccount {
    data: u64,
}

账户鉴别符

Anchor 程序中的账户鉴别符是指独特的 8 字节标识符,针对每种账户类型。你可以在 这里 找到账户鉴别符的实现。

鉴别符是字符串 account:<AccountName> 的 SHA256 哈希的前 8 字节。此鉴别符在创建账户时 作为账户数据的前 8 字节进行存储。

在 Anchor 程序中创建账户时,必须为鉴别符分配 8 字节。


#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,

鉴别符在以下两种情况下使用:

  • 初始化:当创建账户时,鉴别符设置为账户数据的前 8 字节。
  • 反序列化:在反序列化账户数据时,会检查账户数据的前 8 字节是否与期望账户类型的鉴别符匹配。

如果不匹配,则表示客户端提供了意外的账户。该机制在 Anchor 程序中充当账户验证检查。

On this page

在GitHub上编辑