文章背景:上篇文章,我们使用Anchor工程化环境,从初始化项目、编译、测试、部署各个环节演示了一个真实的solana链上程序的开发流程。这篇文章,我们从语法和业务的角度来梳理下我们实现的合约的源码
上篇文章,我们使用 Anchor 工程化环境,从初始化项目、编译、测试、部署各个环节演示了一个真实的 solana 链上程序的开发流程。这篇文章,我们从语法和业务的角度来梳理下我们实现的合约的源码。
use anchor_lang::prelude::*;
// Our program's address!
// This matches the key in the target/deploy directory
declare_id!("BYBFmxjHn48LVAjKfo7dX6kPTw62HNPTktMqnpNeeiHu");
// Anchor programs always use 8 bits for the discriminator
pub const ANCHOR_DISCRIMINATOR_SIZE: usize = 8;
// Our Solana program!
#[program]
pub mod solana_business_card {
use super::*;
// Our instruction handler! It sets the user's favorite number and color
pub fn set_favorites(
context: Context<SetFavorites>,
number: u64,
color: String,
hobbies: Vec<String>,
) -> Result<()> {
let user_public_key = context.accounts.user.key();
msg!("Greetings from {}", context.program_id);
msg!("User {user_public_key}'s favorite number is {number}, favorite color is: {color}",);
// 验证颜色长度限制
require!(color.len() <= 50, CustomError::ColorTooLong);
// 验证爱好数量和每个爱好的长度限制
require!(hobbies.len() <= 5, CustomError::TooManyHobbies);
for hobby in &hobbies {
require!(hobby.len() <= 50, CustomError::HobbyTooLong);
}
msg!("User's hobbies are: {:?}", hobbies);
context.accounts.favorites.set_inner(Favorites {
number,
color,
hobbies,
});
Ok(())
}
// We can also add a get_favorites instruction handler to return the user's favorite number and color
}
// What we will put inside the Favorites PDA
#[account]
#[derive(InitSpace)]
pub struct Favorites {
pub number: u64,
#[max_len(50)]
pub color: String,
#[max_len(5, 50)]
pub hobbies: Vec<String>,
}
// When people call the set_favorites instruction, they will need to provide the accounts that will be modifed. This keeps Solana fast!
#[derive(Accounts)]
pub struct SetFavorites<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init_if_needed,
payer = user,
space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
seeds=[b"solana_business_card", user.key().as_ref()],
bump)]
pub favorites: Account<'info, Favorites>,
pub system_program: Program<'info, System>,
}
#[error_code]
pub enum CustomError {
#[msg("Color string is too long (max 50 characters)")]
ColorTooLong,
#[msg("Too many hobbies (max 5)")]
TooManyHobbies,
#[msg("Hobby string is too long (max 50 characters)")]
HobbyTooLong,
}
程序的主要功能是通过 set_favorites
指令允许用户在区块链上存储和更新以下信息:
number: u64
):一个无符号 64 位整数。color: String
):一个字符串,长度限制为最多 50 个字符。hobbies: Vec<String>
):一个字符串向量,最多包含 5 个爱好,每个爱好的长度限制为 50 个字符。这些信息存储在一个 PDA(Program Derived Address) 中,PDA 的种子基于字符串 "solana_business_card"
和用户的公钥,确保每个用户有唯一的存储空间。
set_favorites
:输入参数:
number: u64
:用户设置的最喜欢的数字。color: String
:用户设置的最喜欢的颜色。hobbies: Vec<String>
:用户设置的爱好列表。功能:
<!---->
<!---->
number
、color
和 hobbies
存储到 Favorites
账户中。返回:成功执行返回 Ok(())
,失败则抛出自定义错误。
Favorites
账户:存储用户的最喜欢的数字、颜色和爱好。
使用 #[account]
和 #[derive(InitSpace)]
宏定义,确保账户空间计算准确。
字段:
number: u64
(8 字节)。color: String
(最大 50 字符,包含 4 字节长度前缀)。hobbies: Vec<String>
(最多 5 个字符串,每个字符串最大 50 字符,包含向量长度前缀)。空间计算:ANCHOR_DISCRIMINATOR_SIZE
(8 字节)+ Favorites::INIT_SPACE
。
SetFavorites
账户上下文:
user: Signer<'info>
:调用指令的签名者(用户),需要支付账户初始化费用。
favorites: Account<'info, Favorites>
:
b"solana_business_card"
和用户公钥。init_if_needed
)。ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE
。system_program: Program<'info, System>
:用于账户创建和初始化的系统程序。
自定义错误类型 CustomError
:
ColorTooLong
:颜色字符串超过 50 个字符。TooManyHobbies
:爱好数量超过 5 个。HobbyTooLong
:单个爱好字符串超过 50 个字符。使用 require!
宏进行输入验证,失败时抛出相应错误。
set_favorites
指令,传入 number
、color
和 hobbies
。Favorites
PDA 中。在上面 Solana 程序中,struct Favorites
和 struct SetFavorites
是两个不同用途的结构体,分别用于不同的场景。
Favorites 结构体
定义:
#[account]
#[derive(InitSpace)]
pub struct Favorites {
pub number: u64,
#[max_len(50)]
pub color: String,
#[max_len(5, 50)]
pub hobbies: Vec<String>,
}
用途:
Favorites
是一个账户数据结构,定义了存储在链上账户(PDA)中的数据格式。number
、color
和 hobbies
)。#[account]
宏标记,告诉 Anchor 这是一个账户结构体,Anchor 会自动处理其序列化和反序列化。#[derive(InitSpace)]
宏用于自动计算账户所需的存储空间。存储位置:
favorites
PDA),每次调用 set_favorites
指令时会更新该账户的数据。生命周期:
作用:
color
和 hobbies
的最大长度)。SetFavorites 结构体
定义:
#[derive(Accounts)]
pub struct SetFavorites<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init_if_needed,
payer = user,
space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
seeds=[b"solana_business_card", user.key().as_ref()],
bump
)]
pub favorites: Account<'info, Favorites>,
pub system_program: Program<'info, System>,
}
用途:
SetFavorites
是一个账户上下文结构体,定义了调用 set_favorites
指令时需要提供的账户列表及其约束。user
、favorites
和 system_program
)以及它们的角色和验证规则。#[derive(Accounts)]
宏,Anchor 会自动生成代码来验证这些账户是否符合约束(例如,user
必须是签名者,favorites
必须是有效的 PDA)。存储位置:
生命周期:
作用:
mut
、init_if_needed
、seeds
等)确保账户的正确性和安全性。Favorites
结构体(通过 favorites: Account<'info, Favorites>
),将指令的输入数据存储到链上的 Favorites
账户。特性 | Favorites | SetFavorites |
---|---|---|
类型 | 账户数据结构(#[account] ) |
账户上下文结构(#[derive(Accounts)] ) |
用途 | 定义链上存储的数据格式 | 定义指令执行时需要的账户及其约束 |
存储位置 | 存储在链上账户(PDA) | 仅存在于指令调用上下文,临时使用 |
生命周期 | 持久化,账户存在期间一直保留 | 临时,仅在指令执行期间有效 |
功能 | 存储用户数据(number 、color 等) |
验证和提供指令所需的账户(如签名者、PDA) |
Anchor 宏 | #[account] , #[derive(InitSpace)] |
#[derive(Accounts)] |
与链上交互 | 直接存储在链上 | 间接通过 favorites 字段操作链上数据 |
context
是 Anchor 程序中每个指令处理函数的第一个必需参数。它包含了执行这个指令所需的所有上下文信息,包括:
SetFavorites
> 的含义这是一个泛型类型,其中:
Context<T>
是 Anchor 提供的通用上下文类型SetFavorites
是类型参数,定义了这个指令需要哪些账户从代码中可以看到 SetFavorites
结构体定义了三个账户:
#[derive(Accounts)]
pub struct SetFavorites<'info> {
#[account(mut)]
pub user: Signer<'info>, // 用户账户(签名者)
#[account(
init_if_needed,
payer = user,
space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
seeds=[b"solana_business_card", user.key().as_ref()],
bump
)]
pub favorites: Account<'info, Favorites>, // 存储收藏信息的 PDA 账户
pub system_program: Program<'info, System>, // 系统程序
}
在函数中,你可以通过 context.accounts
来访问这些账户:
let user_public_key = context.accounts.user.key(); // 获取用户公钥
context.accounts.favorites.set_inner(Favorites { ... }); // 设置收藏数据
上面说的这个 context 是 Anchor 框架 的特性,不是 Rust 的特性
Rust 语言本身提供的:
<T>
- 这是 Rust 的核心特性Anchor 框架提供的:
Context<T>
类型 - 这是 Anchor 专门为 Solana 程序开发设计的#[derive(Accounts)]
宏 - 自动生成账户验证代码在原生 Solana 程序中,你需要手动处理所有账户:
// 原生 Solana 写法(复杂且容易出错)
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 手动解析和验证每个账户
let accounts_iter = &mut accounts.iter();
let user_account = next_account_info(accounts_iter)?;
let favorites_account = next_account_info(accounts_iter)?;
// ... 大量手动验证代码
}
而 Anchor 把这些复杂性抽象化了:
// Anchor 写法(简洁且类型安全)
pub fn set_favorites(
context: Context<SetFavorites>, // Anchor 提供的上下文
number: u64,
color: String,
hobbies: Vec<String>,
) -> Result<()> {
// 直接使用,Anchor 已经验证了所有账户
context.accounts.user.key();
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!