【CryptoZombies - 2 Solidity 進階】011 SafeMath:合約安全增強解決上溢出與下溢出

目錄

一、前言

二、上溢出(overflow)與下溢出(underflow)

1、上溢出overflow

2、下溢出underflow

三、SafeMath

1、講解

2、實戰1

1.要求

2.代碼

3、實戰2

1.要求

2.代碼

4、實戰3

1.要求

2.代碼


一、前言

看了一些區塊鏈的教程,論文,在網上剛剛找到了一個項目實戰,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 則不會。所以大部分情況下,你寫代碼的時候會比較喜歡 requireassert 只在代碼可能出現嚴重錯誤的時候使用,比如 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);
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章