# 以太坊开发实战学习-合约安全(八)

## 一、预防溢出

### 合约安全增强:溢出和下溢

#### 什么是溢出(overflow)?

``````uint8 number = 255;
number++;``````

`下溢(underflow)`也类似，如果你从一个等于 0 的 uint8 减去 1, 它将变成 255 (因为 uint 是无符号的，其不能等于负数)。

#### 使用 SafeMath

``````using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10``````

#### 实战演练

• 1、将 `safemath.sol` 引入到 `zombiefactory.sol`.
• 2、添加定义： `using SafeMath for uint256;`.

`zombiefactory.sol`

``````pragma solidity ^0.4.19;

import "./ownable.sol";
// 1. 在这里引入
import "./safemath.sol";

contract ZombieFactory is Ownable {

// 2. 在这里定义 using safemath
using SafeMath for uint 256;
event NewZombie(uint zombieId, string name, uint dna);

uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;

struct Zombie {
string name;
uint dna;
uint32 level;
uint16 winCount;
uint16 lossCount;
}

Zombie[] public zombies;

mapping (uint => address) public zombieToOwner;

function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}

function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}

function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}

}
``````

## 二、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;
}
}
``````

``````using SafeMath for uint;
// 这下我们可以为任何 uint 调用这些方法了
uint test = 2;
test = test.mul(3); // test 等于 6 了
test = test.add(5); // test 等于 11 了``````

``````function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}``````

#### assert和require区别

`assert``require` 相似，若结果为否它就会抛出错误。 assert 和 require 区别在于，`require` 若失败则会返还给用户剩下的 gas， assert 则不会。所以大部分情况下，你写代码的时候会比较喜欢 `require``assert` 只在代码可能出现严重错误的时候使用，比如 uint 溢出。

#### 在我们的代码里使用SafeMath。

``myUint++;``

``myUint = myUint.add(1);``

#### 实战演练

`ZombieOwnership` 中有两个地方用到了数学运算，来替换成 SafeMath 方法把。

• 1、将 ++ 替换成 SafeMath 方法。
• 2、将 -- 替换成 SafeMath 方法。

`ZombieOwnership`

``````pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

using SafeMath for uint256;

function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}

function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}

// 1. 替换成 SafeMath 的 `add`

// 2. 替换成 SafeMath 的 `sub`
// ownerZombieCount[_from].sub(1); // 这种写法错误
ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);

zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}

function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}

function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}

function takeOwnership(uint256 _tokenId) public {
require(zombieApprovals[_tokenId] == msg.sender);
_transfer(owner, msg.sender, _tokenId);
}
}

``````

### 其他类型

``````myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;``````

``````function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}

// 所以它不会在 2^8 时溢出，因为 256 是一个有效的 `uint256`.``````

`safemath.sol`

``````pragma solidity ^0.4.18;

/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {

/**
* @dev Multiplies two numbers, throws on overflow.
*/
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;
}

/**
* @dev Integer division of two numbers, truncating the quotient.
*/
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;
}

/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}

/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}

/**
* @title SafeMath32
* @dev SafeMath library implemented for uint32
*/
library SafeMath32 {

function mul(uint32 a, uint32 b) internal pure returns (uint32) {
if (a == 0) {
return 0;
}
uint32 c = a * b;
assert(c / a == b);
return c;
}

function div(uint32 a, uint32 b) internal pure returns (uint32) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint32 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}

function sub(uint32 a, uint32 b) internal pure returns (uint32) {
assert(b <= a);
return a - b;
}

function add(uint32 a, uint32 b) internal pure returns (uint32) {
uint32 c = a + b;
assert(c >= a);
return c;
}
}

/**
* @title SafeMath16
* @dev SafeMath library implemented for uint16
*/
library SafeMath16 {

function mul(uint16 a, uint16 b) internal pure returns (uint16) {
if (a == 0) {
return 0;
}
uint16 c = a * b;
assert(c / a == b);
return c;
}

function div(uint16 a, uint16 b) internal pure returns (uint16) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint16 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}

function sub(uint16 a, uint16 b) internal pure returns (uint16) {
assert(b <= a);
return a - b;
}

function add(uint16 a, uint16 b) internal pure returns (uint16) {
uint16 c = a + b;
assert(c >= a);
return c;
}
}
``````

#### 实战演练

• 1、声明我们将为 uint32 使用SafeMath32。
• 2、声明我们将为 uint16 使用SafeMath16。
• 3、在 ZombieFactory 里还有一处我们也应该使用 SafeMath 的方法， 我们已经在那里留了注释提醒你。

`zombiefactory.sol`

``````pragma solidity ^0.4.19;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

using SafeMath for uint256;
// 1. 为 uint32 声明 使用 SafeMath32
using SafeMath32 for uint32;
// 2. 为 uint16 声明 使用 SafeMath16
using SafeMath16 for uint16;

event NewZombie(uint zombieId, string name, uint dna);

uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;

struct Zombie {
string name;
uint dna;
uint32 level;
uint16 winCount;
uint16 lossCount;
}

Zombie[] public zombies;

mapping (uint => address) public zombieToOwner;

function _createZombie(string _name, uint _dna) internal {
// 反正在2038年我们的APP早完蛋了
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
// 3. 在这里使用 SafeMath 的 `add` 方法:
// ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}

function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}

function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}

}
``````

`zombieattack.sol`

``````pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;

function randMod(uint _modulus) internal returns(uint) {
// 这儿有一个
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}

function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
if (rand <= attackVictoryProbability) {
// 这里有三个
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
// 这儿还有俩哦
_triggerCooldown(myZombie);
}
}
}
``````

## 三、注释

### 注释语法

Solidity 里的注释和 JavaScript 相同。在我们的课程中你已经看到了不少单行注释了：

``// 这是一个单行注释，可以理解为给自己或者别人看的笔记``

``````contract CryptoZombies {
/* 这是一个多行注释。我想对所有花时间来尝试这个编程课程的人说声谢谢。
它是免费的，并将永远免费。但是我们依然倾注了我们的心血来让它变得更好。

要知道这依然只是区块链开发的开始而已，虽然我们已经走了很远，
仍然有很多种方式来让我们的社区变得更好。
如果我们在哪个地方出了错，欢迎在我们的 github 提交 PR 或者 issue 来帮助我们改进：
https://github.com/loomnetwork/cryptozombie-lessons

或者，如果你有任何的想法、建议甚至仅仅想和我们打声招呼，欢迎来我们的电报群：
https://t.me/loomnetworkcn
*/
}``````

``````contract CryptoZombies {
/* 这是一个多行注释。我想对所有花时间来尝试这个编程课程的人说声谢谢。
它是免费的，并将永远免费。但是我们依然倾注了我们的心血来让它变得更好。

要知道这依然只是区块链开发的开始而已，虽然我们已经走了很远，
仍然有很多种方式来让我们的社区变得更好。
如果我们在哪个地方出了错，欢迎在我们的 github 提交 PR 或者 issue 来帮助我们改进：
https://github.com/loomnetwork/cryptozombie-lessons

或者，如果你有任何的想法、建议甚至仅仅想和我们打声招呼，欢迎来我们的电报群：
https://t.me/loomnetworkcn
*/
}``````

Solidity 社区所使用的一个标准是使用一种被称作 `natspec` 的格式，看起来像这样：

``````/// @title 一个简单的基础运算合约
/// @author H4XF13LD MORRIS
/// @notice 现在，这个合约只添加一个乘法
contract Math {
/// @notice 两个数相乘
/// @param x 第一个 uint
/// @param y  第二个 uint
/// @return z  (x * y) 的结果
/// @dev 现在这个方法不检查溢出
function multiply(uint x, uint y) returns (uint z) {
// 这只是个普通的注释，不会被 natspec 解释
z = x * y;
}
}``````

`@title`（标题） 和 `@author` （作者）很直接了.

`@notice` （须知）向 用户 解释这个方法或者合约是做什么的。`@dev` （开发者） 是向开发者解释更多的细节。

`@param` （参数）和 `@return` （返回） 用来描述这个方法需要传入什么参数以及返回什么值。

#### 实战演练

`ZombieOwnership` 加上一些 `natspec` 标签:

`zombieownership.sol`

``````pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

/// TODO: 把这里变成 natspec 标准的注释把
/// @title 一个管理转移僵尸所有权的合约
/// @author Corwien
/// @dev 符合 OpenZeppelin 对 ERC721 标准草案的实现
/// @date 2018/06/17
contract ZombieOwnership is ZombieAttack, ERC721 {

using SafeMath for uint256;

function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}

function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}

ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}

function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}

function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}

function takeOwnership(uint256 _tokenId) public {
require(zombieApprovals[_tokenId] == msg.sender);