完整程序代码
整合所有逻辑
恭喜!你已经掌握了 Solana 原生开发的所有积木。现在,我们将这些积木搭建成一个完整的、生产级的链上存储程序。
这个程序虽然代码行数不多,但它极其健壮,能够自动处理:
- 账户初始化:第一次调用时自动创建账户。
- 数据更新:后续调用时自动更新内容。
- 空间伸缩:数据变长自动扩容,变短自动释放。
- 资金多退少补:精确计算每一分钱的租金差额。
核心代码解析
rustpub 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(()) }
为什么这段代码很优雅?
- 原子性:无论走哪个分支,只要中间任何一步失败(如用户余额不足),整个交易回滚。不会出现“钱扣了但数据没存上”的情况。
- 零浪费:通过
realloc和租金退还逻辑,确保用户永远只为实际使用的字节付费。 - 无状态逻辑:程序本身不存任何状态,它只是一个逻辑处理器,根据传入的账户状态决定执行路径。
这就是 Solana 编程模型的精髓:Explicit State, Deterministic Execution (显式状态,确定性执行)。