基本概念这篇文章,我们从一个简单的solana计数器程序开始,深入剖析其中的书写规则和代码规范,其中还附带了一些新手的常见问题的解答,让我们开始吧。
这篇文章,我们从一个简单的solana计数器程序开始,深入剖析其中的书写规则和代码规范,其中还附带了一些新手的常见问题的解答,让我们开始吧
use anchor_lang::prelude::*;
declare_id!("Hfd7V12kj9AENQjLpTozaPW6aT2rhPm3LSyjXZ5AbWH");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
msg!("Counter initialized with value: {}", counter.count);
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("Counter incremented to: {}", counter.count);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
#[account]
pub struct Counter {
pub count: u64,
}
use anchor_lang::prelude::*;
这是 Rust 的导入语句,引入了 Anchor 框架的预导入模块(prelude),包含了编写 Solana 程序所需的基本工具和类型。
declare_id!("Hfd7V12kj9AENQjLpTozaPW6aT2rhPm3LSyjXZ5AbWH");
声明这个程序在 Solana 区块链上的唯一程序ID。每个 Solana 程序都需要一个唯一的公钥作为标识,这里是一个固定的公钥字符串。
#[program]
pub mod counter {
use super::*;
#[program]
是 Anchor 的宏,表示这是一个 Solana 程序模块。
定义了一个名为 counter
的公开模块,里面包含程序的指令逻辑。
导入父作用域的所有内容。这里是为了方便使用外部定义的类型和函数(比如 Result 和上下文结构体)。
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
定义了一个公开函数 initialize
,用于初始化计数器。
参数 ctx
是 Context<Initialize>
类型,包含了函数需要的账户信息(后面会定义 Initialize
结构体)。
返回类型 Result<()>
是 Anchor 中常用的返回类型,表示成功时返回 Ok(())
,失败时返回错误。
let counter = &mut ctx.accounts.counter;
从上下文 ctx
中获取 counter
账户,并将其作为可变引用(&mut
)赋值给 counter
。
ctx.accounts
包含所有通过 Initialize
结构体指定的账户。
counter.count = 0;
将 counter
账户中的 count
字段设置为 0,初始化计数器。
msg!("Counter initialized with value: {}", counter.count);
使用 msg!
宏记录日志,输出计数器初始化的值。这会在 Solana 的程序日志中显示,方便调试。
Ok(())
函数成功执行后返回 Ok(())
,表示没有错误。
}
initialize
函数结束。
pub fn increment(ctx: Context<Increment>) -> Result<()> {
定义了一个公开函数 increment
,用于将计数器值加 1。
参数 ctx
是 Context<Increment>
类型,包含需要的账户信息。
返回类型同上。
let counter = &mut ctx.accounts.counter;
从上下文 ctx
中获取 counter
账户,作为可变引用。
counter.count += 1;
将 counter
的 count
字段加 1。
msg!("Counter incremented to: {}", counter.count);
记录日志,显示计数器的新值。
Ok(())
返回成功结果。
}
increment
函数结束。
}
counter
模块结束。
#[derive(Accounts)]
pub struct Initialize<'info> {
#[derive(Accounts)]
是 Anchor 的宏,用于定义一个账户验证结构体。
定义了一个名为 Initialize
的结构体,用于 initialize
函数的账户验证。
'info
是 Rust 的生命周期参数,表示这些账户的引用与程序执行的上下文相关。
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
定义 counter
账户:
#[account()]
是 Anchor 的宏,用于指定账户的约束。init
:表示这个账户将在函数中被初始化(创建一个新账户)。payer = user
:指定 user
账户支付创建 counter
账户的租金(rent)。space = 8 + 8
:为账户分配存储空间。8 字节是账户的鉴别器(discriminator,由 Anchor 自动使用),另外 8 字节用于存储 Counter
结构体的 count
字段( u64
类型)。Account<'info, Counter>
,表示这是一个存储 Counter
结构体数据的账户。 #[account(mut)]
pub user: Signer<'info>,
定义 user
账户:
#[account(mut)]
:表示这个账户是可变的(需要修改,比如支付租金)。Signer<'info>
:表示这个账户必须是交易的签名者(通常是调用程序的用户)。 pub system_program: Program<'info, System>,
}
定义 system_program
账户:
Program<'info, System>
,表示 Solana 的系统程序,用于创建新账户。#[account]
约束,因为它是内置程序。Initialize
结构体结束。
#[derive(Accounts)]
pub struct Increment<'info> {
定义 Increment
结构体,用于 increment
函数的账户验证。
#[account(mut)]
pub counter: Account<'info, Counter>,
}
定义 counter
账户:
#[account(mut)]
:表示这个账户是可变的,因为我们要修改它的 count
字段。Account<'info, Counter>
,与上面一致。Increment
结构体结束。
#[account]
pub struct Counter {
#[account]
是 Anchor 的宏,表示这是一个可序列化的账户数据结构。Counter
结构体,用于存储计数器的状态。 pub count: u64,
定义一个公开字段 count
,类型是 u64
(64 位无符号整数),用于存储计数器的值。
}
Counter
结构体结束。
这个程序是一个简单的 Solana 智能合约,使用 Anchor 框架实现了一个计数器功能:
initialize
:创建一个新的计数器账户并将其值设为 0。increment
:将已有计数器的值加 1。Initialize
:指定初始化所需的账户(新账户、签名者、系统程序)。Increment
:只需要一个可变的计数器账户。Counter
:定义了计数器的数据结构,只包含一个 count
字段。在你的 Solana 程序中,counter
主程序中的两个函数(initialize
和 increment
)之所以都需要对应的账户结构体(Initialize
和 Increment
),是由于 Solana 和 Anchor 框架的设计理念和运行机制。让我详细解释原因:
Solana 是一个无状态的区块链平台,程序本身不存储任何持久状态。所有数据都存储在账户(Accounts)中,而程序只是定义了操作这些账户的逻辑。每次调用程序时,客户端需要通过交易(Transaction)传递所需的账户,程序通过这些账户来读取或修改数据。
因此,每个函数(指令)需要明确指定它要操作的账户集合。这是由 Solana 的指令执行模型决定的:程序的入口函数接收一个上下文(Context
),其中包含所有相关账户的引用。
Anchor 是一个为 Solana 开发提供便利的框架,它通过 Context<T>
类型封装了账户的验证和访问逻辑。Context<T>
中的 T
是一个账户结构体(比如 Initialize
或 Increment
),用于:
#[account()]
宏定义的约束,确保传入的账户满足条件(比如是否可变、是否签名者等)。在你的代码中:
initialize
函数需要 Initialize
结构体来定义初始化所需的账户。increment
函数需要 Increment
结构体来定义递增操作所需的账户。如果没有这些账户结构体,程序就无法明确知道要操作哪些账户,也无法在编译时验证账户的有效性。
每个函数的逻辑不同,需要操作的账户集合和约束条件也不同,因此需要独立的账户结构体来描述这些差异。让我们具体分析这两个函数:
initialize
函数counter
:需要被初始化的新账户(init
约束)。user
:支付账户创建费用的签名者(mut
和 Signer
)。system_program
:Solana 系统程序,用于创建账户。Initialize
,定义了这三个账户及其约束。increment
函数counter
:已有计数器账户,需要修改(mut
约束)。Increment
,只定义了一个可变的 counter
账户。差异点:
initialize
需要创建账户,所以需要额外的 user
和 system_program
,而 increment
只修改已有账户,不需要这些。Initialize
),increment
会被迫传入不必要的账户(user
和 system_program
),这不仅浪费资源,还可能导致逻辑错误或安全问题。因此,每个函数都需要一个专门的账户结构体来精确匹配其逻辑需求。
Anchor 通过 #[derive(Accounts)]
宏在编译时生成代码,用于:
init
、mut
、signer
等)是否满足。如果没有对应的账户结构体,Anchor 无法生成这些验证逻辑,程序就无法安全运行。
counter
程序中的两个函数需要对应的账户结构体,是因为:
Context<T>
需要一个账户结构体来定义和验证账户。initialize
和 increment
操作的账户集合和约束不同,必须分别定义。这种设计是 Solana 和 Anchor 的核心特性,既保证了程序的灵活性,也提高了开发的安全性和可维护性。如果你还有其他疑问,欢迎继续问我!
在 Increment
结构体中没有 payer = user
,是因为 increment
函数不需要创建新的账户,而 payer
这个约束只在账户创建(init
)时有意义。让我详细解释为什么会这样:
payer
的作用在 Anchor 中,payer
是 #[account(init, ...)]
约束的一部分,用于指定支付账户创建费用的账户。创建账户需要:
space
参数指定)。例如,在 Initialize
结构体中:
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
init
表示创建一个新账户。payer = user
指定由 user
账户支付创建 counter
账户的费用。Increment
的功能再看看 Increment
结构体:
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
increment
函数的作用是将已有 counter
账户的 count
值加 1。counter
账户,并且这个账户是可变的(mut
),因为我们要修改它的数据。这里没有 init
,因为 increment
不创建新账户,而是操作一个已经存在的账户。因此,不需要指定 payer
,因为没有创建费用需要支付。
在 Solana 中,账户的操作可以分为两种情况:
创建账户( init
) :
System Program
)来分配空间并初始化账户。payer
。initialize
函数中,counter
是新创建的,所以需要 payer = user
和 system_program
。修改账户( mut
) :
increment
函数中,counter
是已存在的账户,只需要标记为 mut
即可。由于 increment
不涉及账户创建,payer
这个约束就没有意义,所以 Increment
结构体中没有 payer = user
。
Initialize
需要 user
而 Increment
不需要?Initialize
:
user
作为 Signer
和 payer
,因为它支付创建费用。user
和 system_program
,以完成账户创建。Increment
:
payer
或 system_program
。counter
账户,客户端传入这个账户的地址即可。increment
的交易仍然需要一个签名者(通常是客户端的钱包),但这个签名者不需要在程序逻辑中显式使用,因此 Increment
结构体中不需要定义 user
。调用 initialize
:
counter
(新账户)、user
(签名者和支付者)、system_program
。user
的余额会减少,用于支付 counter
的租金。调用 increment
:
counter
(已有账户)和一个签名者(客户端钱包)。Increment
结构体中没有 payer = user
,因为:
increment
不创建账户:没有 init
约束,所以不需要支付租金,也就不需要 payer
。mut
约束来标记 counter
是可变的。payer
只用于 init
:payer
是账户创建时的专用属性,与修改操作无关。简单来说,payer = user
只在需要创建新账户时出现,而 increment
是操作已有账户的逻辑,所以不需要这个约束。如果你还有其他问题,随时告诉我!
在实际运行时:
例如:
所以,user 是调用 initialize 的用户,而不是固定的“部署程序的用户”。
在 Solana 中,创建新账户需要支付租金(rent),这是为了防止区块链被垃圾账户填满。租金的支付者通常是交易的发起者,因为:
在你的代码中,user 被指定为 payer,因为它是交易的签名者,Anchor 会自动从 user 的余额中扣除创建 counter 账户所需的 lamports(Solana 的最小货币单位)。
在 Solana 中,initialize 函数不能直接设计成像以太坊中的构造函数那样,在部署程序时自动执行。这是因为 Solana 和以太坊的架构和运行机制有根本性的不同。
Solana 的设计有以下限制:
因此,在 Solana 中,initialize 必须作为一个独立的指令,由客户端在部署后手动调用。
PDA 是一种特殊的账户地址,由程序 ID 和一组种子(seeds)通过加密算法生成,而不是由传统的公私钥对控制。
<!---->
use anchor_lang::prelude::*;
declare_id!("Hfd7V12kj9AENQjLpTozaPW6aT2rhPm3LSyjXZ5AbWH");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
msg!("Counter initialized with value: {}", counter.count);
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("Counter incremented to: {}", counter.count);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init_if_needed,
payer = user,
space = 8 + 8,
seeds = [b"counter"],
bump
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [b"counter"],
bump
)]
pub counter: Account<'info, Counter>,
}
#[account]
pub struct Counter {
pub count: u64,
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!