上篇文章,我们使用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_program
pub system_program: Program<'info, System>,
Program<'info, System>
:显式声明依赖 Solana 系统程序。mut
,因为只读访问。init_if_needed
内部会调用系统程序)。system_program
。属性/字段 | 语法作用 | 业务意义 | |
---|---|---|---|
init_if_needed |
按需初始化账户 | 防止重复初始化,节省 rent 费用 | |
payer = owner |
指定支付者 | 调用者承担初始化成本 | ... |
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!