9.1 openzeppelin 库简介
现实世界中的智能合约其要完成的业务功能大都是直接或间接与金钱相关的业务,而非教科书上的只是为了演示该语言具有哪些特性做引入的假想范例,这就要求我们的合约代码在完成既定业务目标的同时还要高度安全。如果所有代码我们都从头开始自己编写,无论我们多么小心总会存在一定的安全漏洞或者疏忽,并随着业务复杂度的提升合约代码量线性增长,安全方面的隐患会逐渐加大。
如果能利用现成的经过第三方大量检测的代码库不仅能大大加速我们的编码进度,同时能利用库提供的通过验证的安全保障,也能提高我们合约的稳定性和可靠性。Zeppelin Solutions团队作为一个专业的智能合约审查服务提供商,在智能合约安全方面经验丰富,并在他们合约审查的项目经验之上,将一些最佳安全编程实践整理汇总到了OpenZeppelin这个开源库的实现中(以下简称zeppelin库)。这样我们可以在这个开源solidity库的基础上进行功能扩展甚至是直接使用,将会使我们合约的安全性更上一个台阶。同时笔者始终坚信对于编程类学习来说,当基础性的知识入门后要想相关编程知识整体的更上一层台阶,最快的方式就研读经典源码实现并应用到自己的编程实践中。因此本章剖析solidity中的经典库zeppelin的架构和实现并通过一些简单的实例演示如何在实践中应用。
截止本书写作时zeppelin库最新版本为1.10.0(项目主页:https://github.com/OpenZeppelin/openzeppelin-solidity),如果后续版本升级后导致本章内容有一定出入以github最新项目版本为准。
zeppelin开源库包含了各种常用的基础功能合约并按照逻辑关系进行分类存放。其中access主要包含各种权限控制相关;crowsale主要包含各种众筹的基础概念;introspection主要包括动态查询目标合约对指定接口的支持情况;lifecylce主要包含生命周期管理;math主要包括安全数学库操作;ownership主要包含所有权控制与转移如白名单等功能;payment主要包含分红相关基础功能合约;proposal主要包括对ERC20协议的扩展使得ERC20代币可以一定程度上支持类似ERC721的元信息接口;token分类主要存放ERC20代币协议以及ERC721不可置换token协议;example主要包含几个基于zeppelin库的综合实例;mocks主要包括如何在继承中使用zeppelin库的各种基础合约;其它还有些未分类但偶尔会用的功能合约。
zeppelin库整体分类结构图如下:
image.png
对zeppelin库的引用支持本地引用并配合truffle框架进行开发,也支持在remix中直接在线引用github项目库,本章实例全部采用在线引用方式。关于如何在线引用github合约库请参见库与代码重用章节。
9.2 math运算模块
math模块主要是对常见的uint类型的四则运算进行了安全性强化,另外提供了几个辅助函数用于返回两者中的最大值或最小值。该模块内部主要包含两个辅助合约:Math和SafeMath。
9.2.1 Math
Math合约的内部实现相对简单,主要功能是对uint64和uint256类型变量进行比较并返回两者中的最大或最小值,以常用的uint256类型为例,其实现代码如下:
function max256(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal pure returns (uint256)
return a < b ? a : b;
}
针对类型uint64的实现与此类似此处不再重复。需要注意的是该库在使用时用using for指令反而不如直接用库.函数的方式直观。对该功能合约的使用还是比较简单直接,示意代码如下:
contract MathTest {
function doTest(uint itype, uint a, uint b) public pure returns(uint){
return (1 == itype) ? Math.max256(a, b) : Math.min256(a, b);
}
}
需要注意的是为了节约篇幅笔者对zeppelin库的外部导入以及注释等都进行了删除,后面不再对此调整进行说明。
9.2.2 SafeMath
SafeMath合约在实际合约开发过程中是比较重要也比较常见的。在前面介绍库与代码重用章节时大致介绍过,合约大部分的运算操作其实就是整数的四则运算而这也是溢出漏洞产生的温床。很多著名的丢币事件其根本原因就是黑客利用了运算溢出的漏洞,但预防运算溢出漏洞却很简单在运算前和运算后都进行检测。
SafeMth库合约应运而生,其主要目的是对uint类型内置的四则运算进行了强化并加强了溢出检查和处理。由于现在新版的solidity编译器自动对除数为0的情况进行了异常处理,所以该合约最新版本主要对加法、减法和乘法进行强化,核心实现代码如下:
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
if (a == 0) { return 0; }
c = a * b;
assert(c / a == b);
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
通过using for指令可以简化该库的使用就像其属于uint类型的内置函数一样,使用范例如下:
contract MathTest1 {
using SafeMath for uint;
function doTest(uint a, uint b) public pure returns(uint, uint, uint, uint) {
return (a.add(b), a.sub(b), a.mul(b), a.div(b));
}
}
笔者建议在对uint类型进行四则运算时尽量使用该库以提高安全性,由于目前的solidity还不支持模板之类的技术,对非uint类型就需要读者自己动手实现类似的功能库。
网友评论