这篇文档旨在为Solidity开发人员提供一些智能合约的安全准则(security baseline)。当然也**包括智能合约的安全开发理念、bug赏金计划指南、文档例程以及工具。**对该文档提出修改或增补建议,请点击“阅读原文”。
1 基本理念以太坊和其他复杂的区块链项目都处于早期阶段并且有很强的实验性质。因此,随着新的bug和安全漏洞被发现,新的功能不断被开发出来,其面临的安全威胁也是不断变化的。这篇文章对于开发人员编写安全的智能合约来说只是个开始。
开发智能合约需要一个全新的工程思维,它不同于我们以往项目的开发。因为它犯错的代价是巨大的,并且很难像传统软件那样轻易的打上补丁。就像直接给硬件编程或金融服务类软件开发,相比于web开发和移动开发都有更大的挑战。因此,仅仅防范已知的漏洞是不够的,你还需要学习新的开发理念:
**对可能的错误有所准备。**任何有意义的智能合约或多或少都存在错误。因此你的代码必须能够正确的处理出现的bug和漏洞。始终保证以下规则: - 当智能合约出现错误时,停止合约,(“断路开关”) - 管理账户的资金风险(限制(转账)速率、最大(转账)额度)
有效的途径来进行bug修复和功能提升
**谨慎发布智能合约。**尽量在正式发布智能合约之前发现并修复可能的bug。 - 对智能合约进行彻底的测试,并在任何新的攻击手法被发现后及时的测试(包括已经发布的合约) - 从alpha版本在测试网(testnet)上发布开始便提供bug赏金计划
阶段性发布,每个阶段都提供足够的测试
**保持智能合约的简洁。**复杂会增加出错的风险。
确保智能合约逻辑简洁
确保合约和函数模块化
使用已经被广泛使用的合约或工具(比如,不要自己写一个随机数生成器)
条件允许的话,清晰明了比性能更重要
只在你系统的去中心化部分使用区块链
**保持更新。**通过下一章节所列出的资源来确保获取到最新的安全进展。
在任何新的漏洞被发现时检查你的智能合约
尽可能快的将使用到的库或者工具更新到最新
使用最新的安全技术
**清楚区块链的特性。**尽管你先前所拥有的编程经验同样适用于以太坊开发,但这里仍然有些陷阱你需要留意:
特别小心针对外部合约的调用,因为你可能执行的是一段恶意代码然后更改控制流程
清楚你的public function是公开的,意味着可以被恶意调用。(在以太坊上)你的private data也是对他人可见的
清楚gas的花费和区块的gas limit
基本权衡:简单性与复杂性
在评估一个智能合约的架构和安全性时有很多需要权衡的地方。对任何智能合约的建议是在各个权衡点中找到一个平衡点。
从传统软件工程的角度出发:一个理想的智能合约首先需要模块化,能够重用代码而不是重复编写,并且支持组件升级。从智能合约安全架构的角度出发同样如此,模块化和重用被严格审查检验过的合约是***策略,特别是在复杂智能合约系统里。
然而,这里有几个重要的例外,它们从合约安全和传统软件工程两个角度考虑,所得到的重要性排序可能不同。当中每一条,都需要针对智能合约系统的特点找到最优的组合方式来达到平衡。
固化 vs 可升级
庞大 vs 模块化
重复 vs 可重用
固化 vs 可升级
在很多文档或者开发指南中,包括该指南,都会强调延展性比如:可终止,可升级或可更改的特性,不过对于智能合约来说,延展性和安全之间是个基本权衡。
延展性会增加程序复杂性和潜在的攻击面。对于那些只在特定的时间段内提供有限的功能的智能合约,简单性比复杂性显得更加高效,比如无管治功能,有限短期内使用的代币发行的智能合约系统(governance-fee,finite-time-frame token-sale contracts)。
庞大 vs 模块化
一个庞大的独立的智能合约把所有的变量和模块都放到一个合约中。尽管只有少数几个大家熟知的智能合约系统真的做到了大体量,但在将数据和流程都放到一个合约中还是享有部分优点--比如,提高代码审核(code review)效率。
和在这里讨论的其他权衡点一样,传统软件开发策略和从合约安全角度出发考虑,两者不同主要在对于简单、短生命周期的智能合约;对于更复杂、长生命周期的智能合约,两者策略理念基本相同。
重复 vs 可重用