10. 完整程序代码

完整程序代码

整合所有逻辑

恭喜!你已经掌握了 Solana 原生开发的所有积木。现在,我们将这些积木搭建成一个完整的、生产级的链上存储程序。

这个程序虽然代码行数不多,但它极其健壮,能够自动处理:

  1. 账户初始化:第一次调用时自动创建账户。
  2. 数据更新:后续调用时自动更新内容。
  3. 空间伸缩:数据变长自动扩容,变短自动释放。
  4. 资金多退少补:精确计算每一分钱的租金差额。

核心代码解析

rust
pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { // 1. 账户提取 let accounts_iter = &mut accounts.iter(); let account_user = next_account_info(accounts_iter)?; let account_data = next_account_info(accounts_iter)?; let _system_program = next_account_info(accounts_iter)?; // 2. 准备工作:计算租金和 PDA 种子 let rent_exemption = Rent::get()?.minimum_balance(data.len()); // 注意:这里我们获取 bump 是为了后面签名用 let (_, bump_seed) = Pubkey::find_program_address( &[account_user.key.as_ref()], program_id ); // 3. 分支逻辑 A:如果账户不存在 (余额为0),则创建 if **account_data.try_borrow_lamports()? == 0 { invoke_signed( &system_instruction::create_account( account_user.key, account_data.key, rent_exemption, // 初始租金 data.len() as u64, // 初始空间 program_id, ), accounts, // 签名种子:证明我是 PDA 的主人 &[&[account_user.key.as_ref(), &[bump_seed]]], )?; // 写入数据 account_data.data.borrow_mut().copy_from_slice(data); return Ok(()); } // 4. 分支逻辑 B:如果账户已存在,则更新 // 情况 B1: 新数据更长 -> 补交租金 if rent_exemption > account_data.lamports() { invoke( &system_instruction::transfer( account_user.key, account_data.key, rent_exemption - account_data.lamports(), ), accounts, )?; } // 情况 B2: 新数据更短 -> 退还租金 else if rent_exemption < account_data.lamports() { let diff = account_data.lamports() - rent_exemption; **account_user.try_borrow_mut_lamports()? += diff; **account_data.try_borrow_mut_lamports()? -= diff; } // 5. 调整空间并写入 account_data.realloc(data.len(), false)?; account_data.data.borrow_mut().copy_from_slice(data); Ok(()) }

为什么这段代码很优雅?

  1. 原子性:无论走哪个分支,只要中间任何一步失败(如用户余额不足),整个交易回滚。不会出现“钱扣了但数据没存上”的情况。
  2. 零浪费:通过 realloc 和租金退还逻辑,确保用户永远只为实际使用的字节付费。
  3. 无状态逻辑:程序本身不存任何状态,它只是一个逻辑处理器,根据传入的账户状态决定执行路径。

这就是 Solana 编程模型的精髓:Explicit State, Deterministic Execution (显式状态,确定性执行)。

RUSTPlayground
EDITOR ACTIVE
Initializing RUST Environment...

代码执行流模拟器

lib.rs
pub fn process_instruction(...) {
// 1. Extract Accounts & Calc Rent
if **account.lamports() == 0 {
// Branch A: New Account
invoke_signed(create_account, ...)?;
} else {
// Branch B: Update Existing
if required_rent > current_balance {
invoke(transfer, ...)?;
} else if required_rent < current_balance {
**user.lamports += diff;
**account.lamports -= diff;
}
}
// Common: Resize & Write
account.realloc(len, false)?;
account.data.copy_from_slice(data);
}
Len: 5 bytesRent: 500 lamports
User Wallet
Signer
10000
PDA Storage
Empty
Data Content
-
Waiting for execution...