Cover photo

智能合约开发的最佳实践——通用哲学

通用设计哲学

随着以太坊的越来越复杂,智能合约作为以太坊提供的能力之一具有高度的实验性。那么作为一名开发者如何开发好智能合约,考虑哪些通用性的哲学,从宏观上去把握智能合约的开发方向和方法论,就是重中之重。

合约开发与传统的软件开发相比,需要不同的思维方式。因为合约的失败的成本非常高,而且合约的改动升级非常难。合约的开发更像硬件编程或者金融服务软件的开发,而不是web开发或者移动app的开发。因而,作为web开发和app开发,需要了解一些新的开发设计哲学。

设计哲学一: 为合约执行失败做好准备

任何合约都可能会产生错误,因此,合约必须能对bug和漏洞做出适当的反馈。

  • 当合约出了问题或者漏洞的时候能够停止,就是所谓的短路保护器。在后端开发开发中,也经常会用到类似的方式,如Hystrix的降级服务功能或者断路保护功能(circuit breaker)

  • 合约要管理好账户功能,类似中心化账户经常会采用单个账户限额,限次,兑换比率限制,专款专用等策略。

  • 为bug修复和升级提供一套行之有效的方式和方法。(要经过演练和测试)

设计哲学二:更新最新方法和知识

  • 当新的系统性bug出现时,要能够尽快发现,这需要订阅最新的bug通讯。

    在后端开发中也经常遇到这样的问题,比如SSL的漏洞出现时,要尽快评估和更新到涉及到服务。

  • 尽快更新到最新的工具和依赖库。

    现在的软件开发很少从零开始,也不应该从零开始,但是很多依赖的工具或者库或多或少的会出现问题,尽快的更新到最新版本去修复这些问题,就成为日常维护工作的重要组成部分。合约开发也是一样。

  • 当新的安全工具出现,并被证明有用的时候,需要尽快采用。

    一般合约开发完毕在测试网上进行测试的时候,都会用一些工具进行模拟性扫描或者攻击,当出现新的工具的时候,并且证明有效时,就需要尽快采用。

设计哲学三:保持简单

复杂会导致更多的错误

  • 确保合约的逻辑是简单的

    合约作为服务的一部分,应该把复杂性和异变性放在链下系统中,合约只处理最简单,最本质的部分。

  • 模块化合约,保持函数调用尽量简单(对于层级和规模都是这样)。

    首先,简单了很多错误能够一目了然的发现。其次,区块链上任何记账行为都是有成本的,操作越复杂,成本越高。

  • 不要重复造轮子,尽量使用已经写好的工具和代码。

    重新造轮子,总会经历一个代码老化的过程,这个过程会有很多坑要填,所以尽量使用已经证明可靠的工具和代码会使得代码更可靠。

  • 要经常去搞清楚代码的执行效率。

    因为合约执行成本比较高,所以搞清楚每个合约执行效率是非要有必要的事情,这直接关系到执行成本。

  • 只在需要使用去中心化的地方才使用区块链,区块链不是银弹,不能什么地方都用。

设计哲学四:发布之前做好准备

很多互联网开发的哲学是,先射击,再瞄准,这个方法在合约开发中是大忌,因为合约失败的成本有时是致命性的。还记得2019年很多ERC20的合约出现了加减法运算溢出错误而导致整个项目的token一夜之间归零的事件吗?

  • 每次合约升级或者发布的时候都要对合约进行全方位的测试,模拟上线升级的事前,事中,事后。当新的攻击出现之后,也需要把这种攻击加到合约测试的步骤中去。

  • 在测试链的的时候提供bug bounty,这时候可以邀请一些比较有名的白帽黑客进行测试,因为一旦进入生产阶段,再出问题就是伤筋动骨。

  • 在发布的每个阶段都要需要进行全方位的试运行和测试。

设计哲学五:了解区块链里的坑

  • 对于合约的外部调用,要非常小心,因为你可能会执行恶意代码或者在不知不觉的下调用不该被调用的函数或者合约。

  • 对于合约中public函数要格外小心,因为函数会被以各种顺序进行调用,即使你声明为private的数据也是公开的,在区块链上数据没有任何秘密。

  • 时刻记住 gas消耗和每个block的gas limit,如果gas消耗过大,每个block的gas limit又比较小,那么交易被区块链确认的难度就越大。因为,每个block中交易的个数跟gas cost成反比。

  • 随机数在区块链中是非常重要的环节,因为大部分随机数的产生在区块链是风险非常大方案,区块链的随机数的产生有时需要引入类似Oracle(预言机)的机制。

设计哲学六:取舍

在设计开发合约过程中,会有很多取舍(tradeoff)。一个理想的合约系统结构,应该是模块化的,可重用的并且支持升级的。对于复杂的合约系统,更加需要对此进行着重的注意。

我们需要从很多方面去评估系统的取舍,以下是一些经常需要注重的方面

  • 写死的逻辑与可以升级的逻辑的权衡。

    只要逻辑可以升级,那么就会有引入问题的可能性。所以,对于可升级的逻辑要进行性价比的权衡,要考虑如果出现问题,如何应对?锻造性攻击就来自于可以被可被销毁,可被升级和可被修改的合约设计模式。

  • 整体化和模块化的权衡。

    模块越多,复杂性越大,但是对于复杂的合约服务,模块化有助于降低复杂度,但是对于简单合约模块化却引入了不必要的复杂度,复杂度又是问题的温床。

  • 复制和重用的权衡

    对于合约最理想的方式是重用而不是复制代码。但是很多时候因为之前的合约无法重用,所以只能复制代码。在实操中任何合约的安全性分析,都必须对之前没有建立信任的或者信任度过低的可重用代码进行重新评估,这会增加安全评估的复杂度。