0
点赞
收藏
分享

微信扫一扫

【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​ 则不会。所以大部分情况下,你写代码的时候会比较喜欢 ​​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);
}
}
}

举报

相关推荐

0 条评论