Skip to main content

🧱 使用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

💪 挑战任务

🎯 扩展你的计数器!

现在轮到你大显身手了!基于我们刚刚构建的计数器,添加以下功能:

📋 任务清单

  1. ➖ 添加decrement指令

    pub fn decrement(ctx: Context<Decrement>) -> Result<()> {
    // 实现递减逻辑
    // 记住:不能减到负数!
    }
  2. 🔧 添加reset指令

    pub fn reset(ctx: Context<Reset>) -> Result<()> {
    // 重置计数器为0
    }
  3. 🎯 添加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(推荐做法)

  1. 使用checked运算 - 防止溢出
  2. 添加日志 - 方便调试
  3. 验证输入 - 永远不要相信用户输入
  4. 编写测试 - 测试是你的朋友

❌ Don'ts(避免做法)

  1. 不要使用unwrap()在生产代码 - 使用?ok_or
  2. 不要忽略错误 - 处理所有可能的错误
  3. 不要硬编码值 - 使用常量或参数
  4. 不要跳过测试 - 总是测试你的代码

🎓 知识总结

📚 你学到了什么

┌────────────────────────────────────┐
│ 🏆 Anchor开发技能解锁 │
├────────────────────────────────────┤
│ ✅ 创建Anchor程序 │
│ ✅ 定义指令和上下文 │
│ ✅ 账户验证和约束 │
│ ✅ 数据结构定义 │
│ ✅ 错误处理 │
│ ✅ 编写测试 │
│ ✅ 部署和调试 │
└────────────────────────────────────┘

🚀 下一步

恭喜你!🎉 你已经成功构建了你的第一个Anchor程序!现在你可以:

  1. 扩展功能 - 添加更多指令
  2. 优化代码 - 改进错误处理
  3. 构建UI - 创建前端界面
  4. 部署主网 - 让世界使用你的程序

💬 记住: 每个伟大的程序都是从一个简单的计数器开始的!继续构建,继续学习!


准备好构建更复杂的程序了吗?让我们继续前进! 🚀🧱✨