合约安全增强: 溢出和下溢
什么是 溢出 (overflow)
?
假设我们有一个 uint8
, 只能存储8 bit
数据。这意味着我们能存储的最大数字就是二进制 11111111
(或者说十进制的 2^8 - 1 = 255
).
来看看下面的代码。最后 number
将会是什么值?
uint8 number = 255;
number++;
在这个例子中,我们导致了溢出 — 虽然我们加了1, 但是number
出乎意料地等于 0
了。
下溢(underflow)
也类似,如果你从一个等于 0
的 uint8
减去 1
, 它将变成 255
(因为 uint
是无符号的,其不能等于负数)。
使用 SafeMath
为了防止这些情况,OpenZeppelin
建立了一个叫做 SafeMath
的 库(library)
,默认情况下可以防止这些问题。
一个库
是 Solidity
中一种特殊的合约。其中一个有用的功能是给原始数据类型增加一些方法。
比如,使用 SafeMath
库的时候,我们将使用 using SafeMath for uint256
这样的语法。 SafeMath
库有四个方法 — add
, sub
, mul
, 以及 div
。现在我们可以这样来让 uint256
调用这些方法:
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10
来看看 SafeMath
的部分代码:
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
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) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
首先我们有了 library
关键字, 库允许我们使用 using
关键字,它可以自动把库的所有方法添加给一个数据类型:
using SafeMath for uint;
// 这下我们可以为任何 uint 调用这些方法了
uint test = 2;
test = test.mul(3); // test 等于 6 了
test = test.add(5); // test 等于 11 了
assert 和 require
assert
和 require
相似,若结果为否它就会抛出错误。 assert
和 require
区别在于,require
若失败则会返还给用户剩下的 gas
, assert
则不会。所以大部分情况下,你写代码的时候会比较喜欢 require
,assert
只在代码可能出现严重错误的时候使用,比如 uint
溢出。