【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);
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章