Solana 合约开发
最近进行了大量 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 方案重构下。
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 调用前后的变化。