🧱 使用Anchor框架进行开发 - 从零构建你的第一个程序!
🎯 欢迎来到Anchor实战训练营!
嘿,Solana建设者们!👋 还记得我们的老朋友Ping程序吗?今天我们要用Anchor的魔法重新打造它!这次不是简单的Ping-Pong,而是一个智能计数器!
想象一下:
- 🏓 原生Ping = 简单的回声
- 🎰 Anchor计数器 = 会记数的智能程序!
🎯 今日任务: 构建一个计数器程序,学会Anchor的核心开发流程!
🎮 我们要构建什么?
┌──────────────────────────────────────┐
│ 🎯 智能计数器程序 │
├──────────────────────────────────────┤
│ │
│ 功能1: 初始化计数器 ⚡ │
│ └─ 创建账户,设置初始值为0 │
│ │
│ 功能2: 增加计数 ➕ │
│ └─ 每次调用+1 │
│ │
│ 功能3: 记录历史 📊 │
│ └─ 打印每次操作 │
│ │
└──────────────────────────────────────┘
🏗️ Step 1: 项目初始化
🚀 创建新项目
# 🎯 创建一个新的Anchor项目
anchor init anchor_counter
# 📁 进入项目目录
cd anchor_counter
# 🔍 查看项目结构
tree . -L 2
💡 建议: 虽然可以在Playground上操作,但本地开发能让你更好地理解整个流程!
📝 打开主程序文件
// 📄 programs/anchor_counter/src/lib.rs
use anchor_lang::prelude::*;
// 🆔 声明你的程序ID(Anchor会自动生成)
declare_id!("你的程序ID");
// 🎮 主程序模块开始!
#[program]
pub mod anchor_counter {
use super::*;
// 指令将在这里定义...
}
⚡ Step 2: 实现Initialize指令
🎯 创建初始化函数
// 🎮 在 #[program] 模块内部
#[program]
pub mod anchor_counter {
use super::*;
/// 🚀 初始化指令 - 创建并设置计数器账户
/// 这是程序的第一个功能!
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// 📦 获取计数器账户的可变引用
let counter = &mut ctx.accounts.counter;
// 🎯 设置初始计数值为0
counter.count = 0;
// 📢 打印日志信息(在链上可见!)
msg!("🎉 计数器账户创建成功!");
msg!("📊 当前计数: {}", counter.count);
// ✅ 返回成功
Ok(())
}
}
💡 代码解释:
Context<Initialize>
= 包含所有需要的账户&mut
= 获取可变引用(因为我们要修改数据)msg!
= Anchor的日志宏,比原生的更简洁!Ok(())
= Rust的成功返回
📦 定义Initialize上下文
/// 📋 Initialize指令所需的账户列表
#[derive(Accounts)]
pub struct Initialize<'info> {
/// 🆕 计数器账户 - 将被初始化的新账户
#[account(
init, // 告诉Anchor要初始化这个账户
payer = user, // 谁来支付租金
space = 8 + 8 // 账户大小:8(鉴别器) + 8(u64)
)]
pub counter: Account<'info, Counter>,
/// 👤 用户账户 - 支付创建费用的人
#[account(mut)] // mut = 可变(余额会改变)
pub user: Signer<'info>, // Signer = 必须签名
/// ⚙️ 系统程序 - 用于创建账户
pub system_program: Program<'info, System>,
}
🎨 视觉解析:
Initialize交易流程:
┌─────────┐ ┌─────────┐ ┌──────────┐
│ User │─────▶│ Program │─────▶│ Counter │
│ (付款) │ │ (处理) │ │ (创建) │
└─────────┘ └─────────┘ └──────────┘
💰 🎮 📦
📊 定义Counter数据结构
/// 📦 计数器账户的数据结构
/// 使用 #[account] 让Anchor自动处理序列化/反序列化
#[account]
pub struct Counter {
/// 🔢 存储当前计数值
pub count: u64, // u64 = 无符号64位整数(0到18,446,744,073,709,551,615)
}
💡 空间计算公式:
- Discriminator(账户类型标识): 8字节
- count字段: 8字节
- 总计: 16字节
➕ Step 3: 实现Increment指令
🎯 创建递增函数
/// 🔼 递增指令 - 让计数器+1
/// 每次调用都会增加计数
pub fn increment(ctx: Context<Increment>) -> Result<()> {
// 📦 获取计数器账户
let counter = &mut ctx.accounts.counter;
// 📊 记录之前的值(用于日志)
msg!("📈 之前的计数: {}", counter.count);
// ➕ 安全地增加计数(使用checked_add防止溢出)
counter.count = counter.count
.checked_add(1) // 安全加法
.unwrap(); // 如果溢出会panic(实际中应该用ok_or)
// 🎉 记录成功信息
msg!("✅ 计数器已递增!");
msg!("📊 当前计数: {}", counter.count);
Ok(())
}
⚠️ 安全提示:
checked_add
会在溢出时返回None,比直接用+
更安全!
📦 定义Increment上下文
/// 📋 Increment指令所需的账户
#[derive(Accounts)]
pub struct Increment<'info> {
/// 📦 要递增的计数器账户
#[account(mut)] // mut = 需要修改这个账户
pub counter: Account<'info, Counter>,
/// 👤 交易发起者(支付gas费)
pub user: Signer<'info>, // 不需要mut因为只是签名验证
}
🎯 对比Initialize和Increment:
Initialize: Increment:
- 创建新账户 ✨ - 使用已存在账户 📦
- 需要system_program ⚙️ - 不需要system_program
- user需要mut(付租金)💰 - user不需要mut(只付gas)⛽
🧪 Step 4: 完整程序代码
📝 最终的lib.rs
// 🎯 完整的计数器程序
use anchor_lang::prelude::*;
// 🆔 程序唯一标识符
declare_id!("YOUR_PROGRAM_ID_HERE");
/// 🎮 主程序模块
#[program]
pub mod anchor_counter {
use super::*;
/// 🚀 初始化计数器
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
msg!("🎉 计数器初始化成功!初始值: {}", counter.count);
Ok(())
}
/// ➕ 递增计数器
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
msg!("📈 递增前: {}", counter.count);
counter.count = counter.count
.checked_add(1)
.ok_or(ErrorCode::Overflow)?; // 更好的错误处理
msg!("✅ 递增后: {}", 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>,
pub user: Signer<'info>,
}
/// 💾 计数器数据结构
#[account]
pub struct Counter {
pub count: u64,
}
/// 🚨 自定义错误
#[error_code]
pub enum ErrorCode {
#[msg("计数器溢出!")]
Overflow,
}
🧪 Step 5: 测试程序
📝 创建测试文件
// 📄 tests/anchor-counter.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AnchorCounter } from "../target/types/anchor_counter";
describe("anchor-counter", () => {
// 🔧 配置Provider
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
// 📦 获取程序
const program = anchor.workspace.AnchorCounter as Program<AnchorCounter>;
// 🔑 创建计数器账户密钥对
const counter = anchor.web3.Keypair.generate();
it("✅ 初始化计数器", async () => {
// 🚀 调用initialize指令
const tx = await program.methods
.initialize()
.accounts({
counter: counter.publicKey,
user: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counter])
.rpc();
console.log("📝 交易签名:", tx);
// 🔍 获取账户数据
const account = await program.account.counter.fetch(counter.publicKey);
console.log("📊 初始计数:", account.count.toString());
// ✅ 验证
assert.equal(account.count.toString(), "0");
});
it("➕ 递增计数器", async () => {
// 🚀 调用increment指令
const tx = await program.methods
.increment()
.accounts({
counter: counter.publicKey,
user: provider.wallet.publicKey,
})
.rpc();
console.log("📝 交易签名:", tx);
// 🔍 获取更新后的数据
const account = await program.account.counter.fetch(counter.publicKey);
console.log("📊 递增后计数:", account.count.toString());
// ✅ 验证
assert.equal(account.count.toString(), "1");
});
});
🚀 Step 6: 构建、部署和测试
🔨 构建程序
# 🏗️ 构建程序
anchor build
# ✅ 成功后会看到:
# ✔ Build successful
🚀 部署程序
# 🌐 确保在正确的网络
solana config set --url localhost # 或 devnet
# 💰 确保有足够的SOL
solana airdrop 2
# 🚀 部署
anchor deploy
# ✅ 成功输出:
# Program Id: YOUR_PROGRAM_ID
🧪 运行测试
# 🧪 运行所有测试
anchor test
# 🎯 或者跳过部署直接测试
anchor test --skip-deploy
💪 挑战任务
🎯 扩展你的计数器!
现在轮到你大显身手了!基于我们刚刚构建的计数器,添加以下功能:
📋 任务清单
-
➖ 添加decrement指令
pub fn decrement(ctx: Context<Decrement>) -> Result<()> {
// 实现递减逻辑
// 记住:不能减到负数!
} -
🔧 添加reset指令
pub fn reset(ctx: Context<Reset>) -> Result<()> {
// 重置计数器为0
} -
🎯 添加set_count指令
pub fn set_count(ctx: Context<SetCount>, value: u64) -> Result<()> {
// 设置为指定值
}
🏆 进阶挑战
- 🔐 添加权限控制(只有创建者能reset)
- 📊 记录操作历史
- ⏰ 添加时间戳
- 🎨 自定义初始值
💡 实用提示
// 💡 提示1:安全的减法
counter.count = counter.count
.checked_sub(1)
.ok_or(ErrorCode::Underflow)?;
// 💡 提示2:权限检查
require!(
ctx.accounts.user.key() == counter.owner,
ErrorCode::Unauthorized
);
// 💡 提示3:获取时间戳
let clock = Clock::get()?;
let timestamp = clock.unix_timestamp;
🔗 参考资源
🆘 需要帮助? 查看完整解决方案
🎯 最佳实践总结
✅ Do's(推荐做法)
- 使用checked运算 - 防止溢出
- 添加日志 - 方便调试
- 验证输入 - 永远不要相信用户输入
- 编写测试 - 测试是你的朋友
❌ Don'ts(避免做法)
- 不要使用unwrap()在生产代码 - 使用
?
或ok_or
- 不要忽略错误 - 处理所有可能的错误
- 不要硬编码值 - 使用常量或参数
- 不要跳过测试 - 总是测试你的代码
🎓 知识总结
📚 你学到了什么
┌────────────────────────────────────┐
│ 🏆 Anchor开发技能解锁 │
├────────────────────────────────────┤
│ ✅ 创建Anchor程序 │
│ ✅ 定义指令和上下文 │
│ ✅ 账户验证和约束 │
│ ✅ 数据结构定义 │
│ ✅ 错误处理 │
│ ✅ 编写测试 │
│ ✅ 部署和调试 │
└────────────────────────────────────┘
🚀 下一步
恭喜你!🎉 你已经成功构建了你的第一个Anchor程序!现在你可以:
- 扩展功能 - 添加更多指令
- 优化代码 - 改进错误处理
- 构建UI - 创建前端界面
- 部署主网 - 让世界使用你的程序
💬 记住: 每个伟大的程序都是从一个简单的计数器开始的!继续构建,继续学习!
准备好构建更复杂的程序了吗?让我们继续前进! 🚀🧱✨