cryptozombies全攻略五
第一章.以太坊代币
一个代币,在以太坊基本上就是遵循一些共同规则的智能合约
也就是它实现了所有其他代币共享的一组标准函数
例如
transfer(address _to, uint256 _value)
balanceOf(address _owner)
在智能合约内部,通常有一个映射
mapping(address => uint256) balances
用于追踪每个地址还有多少余额
所以基本上一个代币只是一个追踪
谁拥有多少该代币的合约
和一些可以让那些用户
将他们的代币转移到其他的地址的函数
pragma solidity ^0.4.19;
import "./zombieattack.sol";
contract ZombieOwnership is ZombieAttack {
}
第二章.ERC721标准,多重继承
在实现一个代币合约的时候,
我们要做的是将接口复制到它自己的solidity文件并导入
import "./erc721.sol"
然后,让我们的合约继承它
然后我们用一个函数定义来重写每个方法
pragma solidity ^0.4.19;
import "./zombieattack.sol";
import "./erc721.sol";
contract ZombieOwnership is ZombieAttack, ERC721 {
function balanceOf(address _owner) public view returns (uint256 _balance) {
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) {
}
function transfer(address _to, uint256 _tokenId) public {
}
function approve(address _to, uint256 _tokenId) public {
}
function takeOwnership(uint256 _tokenId) public {
}
}
第三章.balanceOf和ownerOf
balanceOf
function balanceOf(address _owner) public view returns (uint256 _balance) {
}
传入一个address,然后返回这个address有多少个代币
在我们的例子中,我们的代币就是 僵尸
ownerOf
function ownerOf(uint256 _tokenId) public view returns (address _owner) {
}
传入一个代币id作为参数,
我们的例子中,就是一个僵尸id
然后返回这个代币的拥有者的address地址
在我们的DApp中已经有了一个mapping映射存储了这个信息
所以我们只用一行return语句来实现这个函数
function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}
第四章.重构
我们需要把我们自己写的modifier,ownerOf修改成别的名字
不然跟ERC721里的ownerOf就重复了
modifier onlyOwnerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}
第五章.ERC721标准转移
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
第一种方法
代币的拥有者调用transfer方法,
传入他想转移到的address和他想转移的代币的_tokenId
第二种方法
代币拥有者首先调用approve,
然后该合约会存储谁被允许提取代币,
通常存储到一个mapping(uint256 => address)里面
然后,当有人调用takeOwnership时
合约会检查msg.sender是否得到拥有者的批准来提取代币
如果是,就将代币转移给他
transfer和takeOwnership都将包含相同的转移逻辑
只是以相反的顺序
一种是代币的发送者调用函数
一种是代币的接收者调用它
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to]++;
ownerZombieCount[_from]--;
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
_to接收者数量++
_from发送者数量--
根据_tokenId找到用户地址address,改成接收者的地址_to
这样的话,这个僵尸_tokenId,就属于_to这个用户了
然后调用Transfer(_from, _to, _tokenId);
第六章.转移
function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}
第七章.批准
现在我们来实现approve
我们使用approve或者takeOwnership的时候
转移有2个步骤
1.作为所有者,用新主人的address和我们希望他获取的_tokenId来调用approve
2.新主人用_tokenId来调用takeOwnership,合约会检查确保他获得了批准,然后把代币转移给他
contract ZombieOwnership is ZombieAttack, ERC721 {
mapping (uint => address) zombieApprovals;
function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to]++;
ownerZombieCount[_from]--;
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}
function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
function takeOwnership(uint256 _tokenId) public {
}
}
zombieApprovals映射,_tokenId对应一个address
approve方法,参数传入_to和_tokenId
首先,把zombieApprovals中_tokenId对应的地址改成_to
然后调用Approval,
from -- msg.sender
to -- _to
tokenId -- _tokenId
第八章.takeOwnership
takeOwnership,只是简单地检查以确保msg.sender已经被批准来提取这个代币
如果确认了,就执行_transfer
function takeOwnership(uint256 _tokenId) public {
require(zombieApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
}
第一步,确定msg.sender也就是当前调用者,是否是_tokenId对应的approvals地址
第二步,根据ownerOf方法,找到_tokenId的主人
然后调用_transfer
from -- owner
to -- msg.sender
_tokenId -- _tokenId
第九章.预防溢出
什么是溢出
假设我们有一个uint8,只能存储8bit的数据
这意味着我们能存的最大数字就是 十进制255
uint8 number = 255;
number++;
如果我们这样加1了,那么number就等于0了
或者 下溢
uint8 number = 0;
number--;
这样的话,number就等于255了
使用SafeMath
为了防止这些情况,OpenZeppelin建立了一个叫做SafeMath的库
默认情况下可以防止这些问题
什么是库呢
一个库就是solidity中一种特殊的合约,
其中一个有用的功能就是给原始数据类型增加一些方法
比如SafeMath
我们可以使用 using SafeMath for uint256这样的语法
SafeMath有4个方法
add,sub,mul,div
代码:
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3);
uint256 c = a.mul(2);
第十.SafeMath
把代码中的++和--修改成add和sub
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}