# Solana 合约开发

Original: <https://lifelonglearn.ing/solana-program-development>

Author: [奶爸](https://lifelonglearn.ing/users/3b04650c-8231-499b-8ef0-381fc4d7efb0)

Published: 2025-03-25T02:48:47Z

Updated: 2026-09-27T06:57:43Z

Version: 1

最近进行了大量 Solana 智能合约开发，积累了一些经验，分享给大家。

## 小提示 Tips

### CPI 调用

在进行 CPI 调用时可能会遇到 account infos 超出数量限制的问题，可以在 CPI 调用之前对 account infos 进行去重合并 AccountMeta 数据，instruction 的 accounts 还是传原始数量的 accounts，因为 CPI 调用的 keys 限制只针对于 account infos，去重后一般不会超限。

### Out of memory

Solana VM 堆内存限制32K，栈内存限制4K。

当执行期间 program 报错 `out of memory` 一般是栈内存溢出，这种错误可以给你的 context 里面的 account 数据放到 box 里面讲内存转移到堆中，或者使用 anchor 0.31.0 开始有的 `LazyAccount` 来加载 account。

错误信息中含内存地址 `0xfffff` 之类的报错一般是堆内存溢出，这种错误只能重构合约，比如说你有个超大account需要加载，放到堆里也放不下，可以用 [zero\_copy](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly93d3cuYW5jaG9yLWxhbmcuY29tL2RvY3MvZmVhdHVyZXMvemVyby1jb3B5) 方案重构下。

可以着重注意用到 vec 的地方，可以确定长度的 vec 在初始化时就分配长度，避免 2 倍扩容。

### PDA

PDA 是十分灵活的，任何属于当前 program 的 PDA 都可以在 program 内作为 signer 进行 CPI 调用。这里有一个特殊的点就是假设你需要通过 SystemProgram 通过代码而不是 anchor 来创建和初始化账户，你可将 pda 作为 `new_account` 并作为 signer 调用 `SystemProgram::CreateAccount`，而不是必须使用 anchor 来创建 PDA。

## 安全检查 Security Checklist

### realloc 陷阱

PDA 账户每次增长的空间只能在10240 内，我们在 `realloc` PDA 账户时可以从 10240 扩容到 20480，然后从 20480 扩容到 30720，这没关系，咱们的数据不会出现问题。但是不要忽略一点，如果账户被缩小呢？realloc 可以直接从 30720 降到 100，PDA 中储存的数据就会被回收，造成安全问题。所以在使用外部传入的 `realloc::size` 时​**务必小心**​。

### bump 陷阱

同样的 seeds 可以通过不同的 bump 创建出多个相同 seed 的账户，比如说 seed `seed = ["nonce_used", order_id, [args.bump]]` 可以通过计算出多个 seed 相同但是 bump 不同的账户来，如果你的合约中用的是外部传入的 **bump** 会导致你的一些以来这个 account 的逻辑有安全风险。在使用外部传入 `bump` 时​**务必小心**​。尽量不要外部传入 bump，除了每次运行时计算还可以使用自己存到合约中的通过 `Pubkey::find_program_address` 生成的 bump 来优化 gas。

### writable 陷阱

即便你在合约里面没有将 `account` 标记为 `account(mut)` 但是在交易构造时传递的 `AccountMeta` 是可写的，且你的账户的 owner 是 signer，你的账户就会在执行时可写，所以如有必要检查你的 account 在 CPI 调用前后的变化。
