上篇文章,我们使用Anchor工程化环境,从初始化项目、编译、部署、测试各个环节演示了一个真实的solana链上程序的开发流程。这篇文章,我们从语法和业务的角度来梳理下我们实现的Bank合约的源码。基于对源码和业务的的理解,我们后续可以扩展这个合约,设置一些更加复杂的功能。
上篇文章,我们使用 Anchor 工程化环境,从初始化项目、编译、部署、测试各个环节演示了一个真实的 solana 链上程序的开发流程。这篇文章,我们从语法和业务的角度来梳理下我们实现的 Bank 合约的源码。
基于对源码和业务的的理解,我们后续可以扩展这个合约,设置一些更加复杂的功能。
use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_instruction;
declare_id!("ditw8dH7D93kotkJgokM6WLbJHNdrbK9fJfLR74NJ7h");
#[program]
pub mod solana_bank {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Initializing bank contract");
let bank = &mut ctx.accounts.bank;
bank.owner = ctx.accounts.owner.key();
bank.total_balance = 0;
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
const MIN_DEPOSIT: u64 = 10_000_000; // 0.01 SOL
msg!(
"Processing deposit of {} lamports from user: {}",
amount,
ctx.accounts.user.key()
);
require!(amount >= MIN_DEPOSIT, BankError::DepositTooSmall);
let transfer_instruction = system_instruction::transfer(
&ctx.accounts.user.key(),
&ctx.accounts.bank.key(),
amount,
);
anchor_lang::solana_program::program::invoke(
&transfer_instruction,
&[
ctx.accounts.user.to_account_info(),
ctx.accounts.bank.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
let user_account = &mut ctx.accounts.user_account;
let old_balance = user_account.balance;
user_account.balance = user_account.balance.checked_add(amount).unwrap();
let bank = &mut ctx.accounts.bank;
bank.total_balance = bank.total_balance.checked_add(amount).unwrap();
msg!(
"Deposit successful. User balance: {} -> {}, Bank total: {}",
old_balance,
user_account.balance,
bank.total_balance
);
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let user_account = &mut ctx.accounts.user_account;
msg!(
"Processing withdrawal of {} lamports for user: {}",
amount,
ctx.accounts.user.key()
);
require!(user_account.balance >= amount, BankError::InsufficientFunds);
let old_balance = user_account.balance;
let old_bank_balance = ctx.accounts.bank.total_balance;
**ctx
.accounts
.bank
.to_account_info()
.try_borrow_mut_lamports()? -= amount;
**ctx
.accounts
.user
.to_account_info()
.try_borrow_mut_lamports()? += amount;
user_account.balance = user_account.balance.checked_sub(amount).unwrap();
let bank = &mut ctx.accounts.bank;
bank.total_balance = bank.total_balance.checked_sub(amount).unwrap();
msg!(
"Withdrawal successful. User balance: {} -> {}, Bank total: {} -> {}",
old_balance,
user_account.balance,
old_bank_balance,
bank.total_balance
);
Ok(())
}
pub fn get_balance(ctx: Context<GetBalance>) -> Result<u64> {
let balance = ctx.accounts.user_account.balance;
msg!(
"Queried balance for user {}: {} lamports",
ctx.accounts.user.key(),
balance
);
Ok(balance)
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init_if_needed,
payer = owner,
space = 8 + 32 + 8,
seeds = [b"bank"],
bump
)]
pub bank: Account<'info, Bank>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut, seeds = [b"bank"], bump)]
pub bank: Account<'info, Bank>,
#[account(
init_if_needed,
payer = user,
space = 8 + 8,
seeds = [b"user", user.key().as_ref()],
bump
)]
pub user_account: Account<'info, UserAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut, seeds = [b"bank"], bump)]
pub bank: Account<'info, Bank>,
#[account(mut, seeds = [b"user", user.key().as_ref()], bump)]
pub user_account: Account<'info, UserAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct GetBalance<'info> {
#[account(seeds = [b"user", user.key().as_ref()], bump)]
pub user_account: Account<'info, UserAccount>,
pub user: Signer<'info>,
}
#[account]
pub struct Bank {
pub owner: Pubkey,
pub total_balance: u64,
}
#[account]
pub struct UserAccount {
pub balance: u64,
}
#[error_code]
pub enum BankError {
#[msg("Deposit amount must be at least 0.01 SOL")]
DepositTooSmall,
#[msg("Insufficient funds for withdrawal")]
InsufficientFunds,
}
初始化银行( initialize )
b"bank"),设置 owner 并初始化总余额 total_balance = 0。owner 支付账户初始化费用(rent)。存款( deposit )
10_000_000 lamports)。system_instruction::transfer 完成转账。UserAccount 余额和银行总余额。取款( withdraw )
invoke,更高效)。查询余额( get_balance )
UserAccount.balance。程序的核心业务并不复杂,如果说对于新手有难度和门槛的应该是对账户模型的理解,下面我们按照指令和账户约束的层面,从技术层面和业务层面来分析下源码:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Initializing bank contract");
let bank = &mut ctx.accounts.bank;
bank.owner = ctx.accounts.owner.key();
bank.total_balance = 0;
Ok(())
}
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
pub fn initialize:定义一个公开的(pub)函数 initialize。ctx: Context<Initialize>:接收一个 Context 参数,泛型类型是 Initialize(表示该函数只能由 Initialize 结构体定义的账户调用)。-> Result<()>:返回 Anchor 的 Result 类型,() 表示无具体返回值(仅返回成功/错误状态)。owner)调用。Context<Initialize> 确保调用时必须传入符合 Initialize 结构体定义的账户(如 bank、owner 等)。msg!("Initializing bank contract");
msg! 是 Anchor 提供的宏,用于在 Solana 链上打印日志(类似 println!)。let bank = &mut ctx.accounts.bank;
ctx.accounts.bank:从 Context 中获取 bank 账户(定义在 Initialize 结构体)。&mut:获取可变引用(因为要修改 bank 的数据)。Bank 结构体实例),后续会设置 owner 和 total_balance。bank.owner = ctx.accounts.owner.key();
bank.owner:Bank 结构体的 owner 字段(类型是 Pubkey)。ctx.accounts.owner.key():获取 owner 账户的公钥(Signer 类型的账户)。Bank 的 owner 设置为调用者(ctx.accounts.owner),表示该银行合约的管理者。<!---->
owner 在后续可用于权限控制(如仅允许 owner 调用某些管理函数)。bank.total_balance = 0;
bank.total_balance:Bank 结构体的 total_balance 字段(类型是 u64)。= 0:初始化为 0(表示银行初始资金为 0)。total_balance)应为 0。deposit/withdraw 会更新这个值。Ok(())
Ok(()):返回 Result::Ok,表示函数执行成功,无返回值。Err(BankError::SomeError)。#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init_if_needed,
payer = owner,
space = 8 + 32 + 8,
seeds = [b"bank"],
bump
)]
pub bank: Account<'info, Bank>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[derive(Accounts)]:宏标记,表示该结构体是 Anchor 的 账户验证容器,用于定义指令的账户约束。<'info>:生命周期泛型,表示这些账户引用在交易执行期间有效。initialize 指令所需的账户集合,Anchor 会自动验证传入的账户是否符合这些约束。bank#[account(
init_if_needed,
payer = owner,
space = 8 + 32 + 8,
seeds = [b"bank"],
bump
)]
pub bank: Account<'info, Bank>,
#[account(...)]:属性宏,定义账户的初始化规则和安全约束。init_if_needed:如果账户未初始化,则自动初始化;否则跳过(防重复初始化)。payer = owner:初始化费用(rent)由 owner 账户支付。space = 8 + 32 + 8:分配存储空间:<!---->
8:Anchor 的账户标识头。32:Bank.owner(Pubkey 类型,固定 32 字节)。8:Bank.total_balance(u64 类型,固定 8 字节)。<!---->
seeds = [b"bank"]:定义 PDA(Program Derived Address)的种子,此处为静态字符串 "bank"。bump:自动计算 PDA 的 bump 值(避免地址冲突)。owner 和 total_balance。program_id + seeds 派生)。owner#[account(mut)]
pub owner: Signer<'info>,
#[account(mut)]:标记该账户为 可变(因为要支付 rent,需修改 lamports)。Signer<'info>:要求 owner 必须对当前交易签名。system_programpub system_program: Program<'info, System>,
Program<'info, System>:显式声明依赖 Solana 系统程序。mut,因为只读访问。init_if_needed 内部会调用系统程序)。system_program。| 属性/字段 | 语法作用 | 业务意义 | |
|---|---|---|---|
init_if_needed |
按需初始化账户 | 防止重复初始化,节省 rent 费用 | |
payer = owner |
指定支付者 | 调用者承担初始化成本 | ... |
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!