目录
一、前言
二、上溢出(overflow)与下溢出(underflow)
1、上溢出overflow
2、下溢出underflow
三、SafeMath
1、讲解
2、实战1
1.要求
2.代码
3、实战2
1.要求
2.代码
4、实战3
1.要求
2.代码
看了一些区块链的教程,论文,在网上刚刚找到了一个项目实战,CryptoZombies。
今天我们来讲有关溢出的内容。
溢出这个概念,很多人都知道,在很多编程语言都会涉及到溢出。这是因为当我们定义变量的时候,需要指定其类型,当其类型确定后,其值的范围也就确定了,一旦超出范围,那我们就说溢出了。
同样在以太坊solidity中也会存在溢出的情况,以uint8为例:
uint8 number = 255;
number++;
我们定义一个uint8的数据,给他赋值为255。我们知道uint8的类型最大值只能是255,如果我们给这个值加一,那就变成了256:
uint8说明这个数据只能是八位,所以255转化成二进制为:1111 1111;
我们加一之后就变成:1 0000 0000。但我们知道因为只能存8位,那么最前面的1就超出范围,无法存到数据里面了,很像我们在盆子里装满水,再加一点,最高的地方就溢出了,所以我们只能存低八位的数据也就是0000 0000,所以这个时候,我们就说数据上溢出(overflow)了。
那么下溢出是什么呢?如果我们给数据赋值为0,也就是uint8的最小值,然后我们再减一:
uint8 number = 0;
number--;
同样的,我们的数据刚开始对应的二进制为0000 0000,然后我们减一,就变成了1111 1111。这样就变成了255。这个叫做下溢出(underflow)。
使用 approve
或者 takeOwnership
的时候,转移有2个步骤:
1.所有者用新主人的
address
和所有者希望新主人获取的_tokenId
来调用approve。
2.新主人用
_tokenId
来调用takeOwnership
,合约会检查确保他获得了批准,然后把代币转移给他。
因为这发生在2个函数的调用中,所以在函数调用之间,我们需要一个数据结构来存储什么人被批准获取什么。
有溢出的存在,我们就要解决溢出。因此,我们提供了SafeMath库来防止溢出问题。
在以太坊中,库就是一种特殊类型的合约,这种类型的合约一般是为给原始数据类型增添方法。
SafeMath库有四个方法:
add:加法
sub:减法
mul:乘法
div:除法
其具体实现如下:
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;
}
}
相比较正常的四则运算,SafeMath库进行了验证机制,保证数据不会溢出。
在这里我们注意到我们使用了assert,之前我们做验证使用的是require。我们要注意如下两点:
1.assert
和require
相似,若结果为否它就会抛出错误。
2.assert
和require
区别在于,require
若失败则会返还给用户剩下的 gas,assert
则不会。所以大部分情况下,你写代码的时候会比较喜欢require
,assert
只在代码可能出现严重错误的时候使用,比如uint
溢出。所以简而言之, SafeMath 的add
,sub
,mul
, 和div
方法只做简单的四则运算,然后在发生溢出或下溢的时候抛出错误。
SafeMath库的使用方法如下:
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10
其中第一条语句是为uint256类型的数据添加SafeMath库,后两个是调用SafeMath库的方法。
1.将
safemath.sol
引入到zombiefactory.sol
.。
2.
添加定义:using SafeMath for uint256;
。3.函数的正文部分,将
_tokenId
的zombieApprovals
设置为和_to
相等。4.最后,在 ERC721 规范里有一个
Approval
事件。所以我们应该在这个函数的最后触发这个事件。(参考erc721.sol
来确认传入的参数,并确保_owner
是msg.sender
)
pragma solidity >=0.5.0 <0.6.0;
import "./ownable.sol";
// 1. Import here
import "./safemath.sol";
contract ZombieFactory is Ownable {
// 2. Declare using safemath here
using SafeMath for uint256;
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string memory _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]++;
emit NewZombie(id, _name, _dna);
}
function _generateRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
function createRandomZombie(string memory _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
1.将++操作和--操作替换为SafeMath方法。
pragma solidity >=0.5.0 <0.6.0;
import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";
contract ZombieOwnership is ZombieAttack, ERC721 {
using SafeMath for uint256;
mapping (uint => address) zombieApprovals;
function balanceOf(address _owner) external view returns (uint256) {
return ownerZombieCount[_owner];
}
function ownerOf(uint256 _tokenId) external view returns (address) {
return zombieToOwner[_tokenId];
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
// 1. Replace with SafeMath's `add`
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
// 2. Replace with SafeMath's `sub`
ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);
zombieToOwner[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}
function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
_transfer(_from, _to, _tokenId);
}
function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _approved;
emit Approval(msg.sender, _approved, _tokenId);
}
}
1.声明为uint32和uint64分别使用SafeMath32和SafeMath64。
2.将++与--改为SafeMath操作
zombiefactory.sol文件:
pragma solidity >=0.5.0 <0.6.0;
import "./ownable.sol";
import "./safemath.sol";
contract ZombieFactory is Ownable {
using SafeMath for uint256;
// 1. Declare using SafeMath32 for uint32
// 2. Declare using SafeMath16 for uint16
using SafeMath32 for uint32;
using SafeMath16 for uint16;
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string memory _name, uint _dna) internal {
// Note: We chose not to prevent the year 2038 problem... So don't need
// worry about overflows on readyTime. Our app is screwed in 2038 anyway ;)
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
// 3. Let's use SafeMath's `add` here:
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
emit NewZombie(id, _name, _dna);
}
function _generateRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
function createRandomZombie(string memory _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
zombieattack.sol文件:
pragma solidity >=0.5.0 <0.6.0;
import "./zombiehelper.sol";
contract ZombieAttack is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;
function randMod(uint _modulus) internal returns(uint) {
// Here's one!
randNonce = randNonce.add(1);
return uint(keccak256(abi.encodePacked(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) {
// Here's 3 more!
myZombie.winCount = myZombie.winCount.add(1);
myZombie.level = myZombie.level.add(1);
enemyZombie.lossCount = enemyZombie.lossCount.add(1);
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
// ...annnnd another 2!
myZombie.lossCount = myZombie.lossCount.add(1);
enemyZombie.winCount = enemyZombie.winCount.add(1);
_triggerCooldown(myZombie);
}
}
}