
Solana 多行业应用的技术共性账户模型、PDA 模式与 CPI 调用的可复用范式一、引言Solana 生态在 2026 年已经覆盖了 DeFiJupiter、Marinade、NFTMagic Eden、Tensor、DePINHelium、Hivemapper、支付Solana Pay四类场景。表面上看这些项目的合约逻辑截然不同——DeFi 处理的是 AMM 曲线和借贷因子NFT 处理的是元数据和版税分配DePIN 处理的是物理设备验证和激励机制——但它们在 Solana 的编程模型层面共享着同一套底层范式账户模型做数据隔离、PDAProgram Derived Address做确定性寻址、CPICross-Program Invocation做合约组合。理解这三项技术共性是跨行业开发 Solana 程序的基础。掌握后从一个 DeFi 项目切换到 NFT 项目的学习曲线会从学一套新工具变为学一套新的业务逻辑发在已经熟悉的原语上。本文不是 Solana 入门教程而是针对已有 EVM 开发经验的工程师梳理 Solana 编程模型中的可复用范式展示四个场景如何基于相同的底层技术构建出差异化的业务逻辑。二、三大范式架构Solana 的账户模型和 EVM 的存储模型有根本差异这个差异决定了开发范式的不同核心差异EVM 中合约的数据存储在合约自身的 storage 中每个合约管理自己的状态。Solana 中程序代码和数据是完全分离的——程序只包含可执行逻辑BPF 字节码数据存储在与程序关联、由用户创建并支付 rent 的独立 Account 中。这意味着 Solana 程序天然支持数据可以在不修改程序的情况下升级结构——因为数据格式由 Account 的序列化方式决定不需要像 EVM 那样做存储布局迁移。PDA 是 Solana 最具特色的设计。它是由程序 ID 和一组 seed 确定性派生出的地址没有对应私钥因此只有关联的程序可以通过invoke_signed进行程序签名。PDA 的典型用途是程序托管的资产池——用户将 token 转入 PDA只有程序逻辑验证通过后才能从 PDA 中转出 token。这比 EVM 中通过mapping(address uint256)记账更安全因为 token 真正地在程序的账户里而不是在合约的 accounting 中。三、可复用范式实现Anchor 框架以下代码展示四个场景共享的三段核心范式代码// programs/shared-paradigms/src/lib.rs use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount, Transfer}; declare_id!(Para1111111111111111111111111111111111111); /** * 范式一PDA 作为程序托管账户 * 四个场景都用到DeFi 的池子、NFT 的市场托管、DePIN 的奖励池、支付的中间账户 * 设计决策 * - seed 中纳入 authority 的 pubkey确保每个用户有独立 PDA * - seed 中纳入场景标识pool / market避免同用户的不同场景 PDA 碰撞 * - PDA 的 bump 存储在 account 内而非每次重新计算节省 CU */ #[derive(Accounts)] pub struct InitializeVaultinfo { /// 场景通用程序托管账户 /// 确定性地址 PDA(seed[bvault, scene.as_bytes(), authority.key().as_ref()], program_id) #[account( init, payer authority, space 8 Vault::INIT_SPACE, seeds [bvault, scene.as_ref(), authority.key().as_ref()], bump )] pub vault: Accountinfo, Vault, #[account(mut)] pub authority: Signerinfo, pub system_program: Programinfo, System, /// 场景标识用于区分同一个程序的多个场景 /// DeFi - bdefi_pool NFT - bnft_market DePIN - bdepin_reward 支付 - bpayment pub scene: String, } #[account] #[derive(InitSpace)] pub struct Vault { pub authority: Pubkey, pub bump: u8, pub total_deposited: u64, /// 场景标签在 deposit/withdraw 时做场景校验 pub scene_tag: [u8; 32], } /** * 范式二CPI 调用 SPL Token Program * Solana 的 token 转账必须通过 CPI 调用 Token Program * 这是所有 DeFi/NFT/DePIN/支付项目都需要的基础操作。 * 设计决策 * - 使用 anchor_spl 的高层封装而非手动构造 Instruction * 减少手动管理 account_info 出错的概率 * - 关键风险在 signer_seedsPDA 签名的 seed 必须与创建 PDA 时使用的 seed 完全一致 * 任何 seed 参数的顺序、大小写差异都会导致签名验证失败 */ implinfo InitializeVaultinfo { pub fn deposit_tokens( self, from: Accountinfo, TokenAccount, to: Accountinfo, TokenAccount, amount: u64, ) - Result() { let cpi_ctx CpiContext::new( ctx.accounts.token_program.to_account_info(), Transfer { from: from.to_account_info(), to: to.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }, ); token::transfer(cpi_ctx, amount)?; Ok(()) } pub fn withdraw_tokens_with_pda( self, vault: Accountinfo, Vault, from: Accountinfo, TokenAccount, // PDA 关联的 token account to: Accountinfo, TokenAccount, amount: u64, scene: str, ) - Result() { let seeds [ bvault, scene.as_bytes(), vault.authority.as_ref(), [vault.bump], ]; let signer_seeds [seeds[..]]; let cpi_ctx CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), Transfer { from: from.to_account_info(), to: to.to_account_info(), // 关键authority 是 PDA使用 signer_seeds 签名 authority: from.to_account_info(), }, signer_seeds, ); token::transfer(cpi_ctx, amount)?; Ok(()) } } /** * 范式三跨程序组合 * DeFi 聚合器调用多个 DEX、NFT 市场调用 Metaplex、DePIN 调用 oracle * 本质上都是通过 CPI 组合多个程序的能力 * 设计决策 * - 使用 anchor 的 CpiContext 而非 solana_program::invoke * 前者做编译期 account 校验后者只在运行时报错 * - 调用深度限制 4 层是硬约束复杂编排需要扁平化调用层级 */ pub fn composable_swapinfo( ctx: Context_, _, _, info, ComposableSwapinfo, amount_in: u64, min_amount_out: u64, ) - Result() { // Step 1: 调用 DEX A 做第一次 swap let cpi_ctx_a CpiContext::new( ctx.accounts.dex_a_program.to_account_info(), dex_a::Swap { user: ctx.accounts.user.to_account_info(), pool: ctx.accounts.pool_a.to_account_info(), // ... }, ); dex_a::swap(cpi_ctx_a, amount_in, 0)?; // 先全量换出 // Step 2: 用 DEX A 的输出调用 DEX B 做第二次 swap let cpi_ctx_b CpiContext::new( ctx.accounts.dex_b_program.to_account_info(), dex_b::Swap { user: ctx.accounts.user.to_account_info(), pool: ctx.accounts.pool_b.to_account_info(), // ... }, ); dex_b::swap(cpi_ctx_b, intermediate_amount, min_amount_out)?; Ok(()) } #[derive(Accounts)] pub struct ComposableSwapinfo { pub user: Signerinfo, /// 跨程序调用的关键把被调用程序的 Account 声明在这里 /// Anchor 在 #[derive(Accounts)] 阶段就会校验这些 Account 的存在性和所有权 pub dex_a_program: Programinfo, dex_a::program::DexA, pub dex_b_program: Programinfo, dex_b::program::DexB, pub pool_a: Accountinfo, dex_a::Pool, pub pool_b: Accountinfo, dex_b::Pool, }四、边界与 EVM 迁移陷阱账户数据的序列化成本。在 EVM 中读写 storage 直接操作 256-bit slot。Solana 中读取 Account 数据需要反序列化全部数据到一个 Rust struct 中写回需要序列化整个 struct。一个 10KB 的 Account 每次 CPI 调用都需要额外消耗大约 5000 CU 的序列化/反序列化开销。对于高频交易场景需要将热数据拆分为多个小 Account 以减少序列化成本。PDA 的确定性与灵活性矛盾。PDA 的确定性地址是双刃剑——它让你可以计算一个地址而无需存储但这也意味着 PDA 的 seed 必须在设计阶段就确定后续无法更改。如果初始设计中没有在 seed 中包含某个业务维度如场景版本后期想加入时不得不部署新程序并使用新的 seed 前缀旧 PDA 中的数据需要迁移。EVM 开发者习惯在 mapping 中动态添加 key这种自由度在 Solana 的 PDA 模型中需要通过更谨慎的前期设计来弥补。账户 rent 的经济成本。Solana 上创建 Account 需要存入 rent-exempt 的最低 lamports。一个简单的 vault Account约 80 bytes需要约 0.0015 SOL 的 rent。对于需要为每个用户创建 PDA 的应用每 100 万用户需要约 1500 SOL 的 rent 成本——且这笔费用由用户或应用支付。对于大规模 C 端应用需要在设计阶段就评估 rent 的经济可行性。CPI 深度限制 4 层。这意味着调用链 A→B→C→D 是极限A→B→C→D→E 会直接失败。对于聚合器、路由器这种需要组合多个程序的场景需要在设计时做好扁平化——将多步操作合并为一个程序的内部逻辑通过减少 CPI 层数来绕过深度限制。Anchor 版本迭代导致的 IDL 不兼容。Anchor 框架的升级从 0.28 到 0.29 到 0.30引入了 IDL 格式变化、account serialization 变化等 breaking changes。跨场景复用时需要确保所有依赖同一个程序的其他程序使用相同的 Anchor 版本。五、总结Solana 的 Account 模型、PDA 和 CPI 是三个正交但相互配合的范式。理解它们的组合方式后DeFi、NFT、DePIN、支付四个场景的差异就回到了业务逻辑层面——AMM 曲线、版税分配、设备验证、结算流程——而不是在底层编程模型上重新学习。从 EVM 迁移到 Solana 时最重要的心态调整是从合约拥有数据切换到程序处理账户数据。这个观念转变后PDA 和 CPI 的运用会变得自然。最大的陷阱不是技术细节这些文档都有写而是带着 EVM 的设计习惯来设计 Solana 程序的架构——比如习惯性地把不同用户的数据放在同一个数组中而不是为每个用户创建独立的 PDA。