目錄
二、上溢出(overflow)與下溢出(underflow)
一、前言
看了一些區塊鏈的教程,論文,在網上剛剛找到了一個項目實戰,CryptoZombies。
今天我們來講有關溢出的內容。
二、上溢出(overflow)與下溢出(underflow)
1、上溢出overflow
溢出這個概念,很多人都知道,在很多編程語言都會涉及到溢出。這是因爲當我們定義變量的時候,需要指定其類型,當其類型確定後,其值的範圍也就確定了,一旦超出範圍,那我們就說溢出了。
同樣在以太坊solidity中也會存在溢出的情況,以uint8爲例:
uint8 number = 255;
number++;
我們定義一個uint8的數據,給他賦值爲255。我們知道uint8的類型最大值只能是255,如果我們給這個值加一,那就變成了256:
uint8說明這個數據只能是八位,所以255轉化成二進制爲:1111 1111;
我們加一之後就變成:1 0000 0000。但我們知道因爲只能存8位,那麼最前面的1就超出範圍,無法存到數據裏面了,很像我們在盆子裏裝滿水,再加一點,最高的地方就溢出了,所以我們只能存低八位的數據也就是0000 0000,所以這個時候,我們就說數據上溢出(overflow)了。
2、下溢出underflow
那麼下溢出是什麼呢?如果我們給數據賦值爲0,也就是uint8的最小值,然後我們再減一:
uint8 number = 0;
number--;
同樣的,我們的數據剛開始對應的二進制爲0000 0000,然後我們減一,就變成了1111 1111。這樣就變成了255。這個叫做下溢出(underflow)。
使用 approve
或者 takeOwnership
的時候,轉移有2個步驟:
1.所有者用新主人的
address
和所有者希望新主人獲取的_tokenId
來調用approve。
2.新主人用
_tokenId
來調用takeOwnership
,合約會檢查確保他獲得了批准,然後把代幣轉移給他。
因爲這發生在2個函數的調用中,所以在函數調用之間,我們需要一個數據結構來存儲什麼人被批准獲取什麼。
三、SafeMath
1、講解
有溢出的存在,我們就要解決溢出。因此,我們提供了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庫的方法。
2、實戰1
1.要求
1.將
safemath.sol
引入到zombiefactory.sol
.。
2.
添加定義:using SafeMath for uint256;
。3.函數的正文部分,將
_tokenId
的zombieApprovals
設置爲和_to
相等。4.最後,在 ERC721 規範裏有一個
Approval
事件。所以我們應該在這個函數的最後觸發這個事件。(參考erc721.sol
來確認傳入的參數,並確保_owner
是msg.sender
)
2.代碼
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);
}
}
3、實戰2
1.要求
1.將++操作和--操作替換爲SafeMath方法。
2.代碼
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);
}
}
4、實戰3
1.要求
1.聲明爲uint32和uint64分別使用SafeMath32和SafeMath64。
2.將++與--改爲SafeMath操作
2.代碼
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);
}
}
}