引言
CryptoKitties是第一个基于Etherum的游戏Dapp,2017年下半年风靡一时,一度造成了Etherum网络的堵塞。虽然现在这款游戏的热度已经开始消退,但是作为技术学习而言,CryptoKitties确实是一个很好的案例参考教程。本节内容作为专栏的开篇,先以最基本的几个合约开始入手讲解。
目录
Ownable合约
这是来自OpenZeppelin Solidity 库的 Ownable
合约,提供了基本的权限控制功能。大多数人开发自己的 DApp,都是从复制/粘贴 Ownable
开始的,从它再继承出的子合约,并在之上进行功能开发。源码先贴上:
contract Ownable {
address public owner;
function Ownable() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
成员变量:
owner
:address
类型,指向合约拥有者的账户地址
构造函数:
function Ownable()
:仅在合约最初被部署时执行一次,并将部署者的账户地址赋值给owner
变量
函数修饰符:
modifier onlyOwner()
:修饰符跟函数很类似,不过是用来修饰其他已有函数用的,在其他语句执行前,为它检查下先验条件。
一般函数:
function transferOwnership(address newOwner) onlyOwner
:该函数的作用是将合约的拥有权转交给另外一个账户地址,由于加上了上述的修饰符onlyOwner
,意味着只有合约当前的拥有者才有权限调用此函数。
ERC721合约
这个合约涉及到ERC标准的内容,以及ERC20同质化货币和ERC721非同质化货币的区别。首先同质化货币比较好理解,我们日常生活用的货币就是同质化货币,我手里的一块钱和你手里的一块钱没有任何区别,我钱包账户里的1 ether和任何人钱包里的1 ether 也是完全等同的。而非同质化货币就类似于收藏品了,你的Kitty猫的基因性格外观和我的Kitty猫绝对不会一模一样。另外同质化货币是可分割的,一块钱可以拆成两枚五毛,但是一只kitty就是一只,不可能送给你半只。。。
游戏里的猫其实就是基于ERC721的加密货币,而ERC721作为一个合约标准,提供了在实现ERC721代币时必须要遵守的协议,要求每个ERC721标准合约需要实现ERC721和ECR165接口,接口定义如下:
contract ERC721 {
function totalSupply() public view returns (uint256 total);
function balanceOf(address _owner) public view returns (uint256 balance);
function ownerOf(uint256 _tokenId) external view returns (address owner);
function approve(address _to, uint256 _tokenId) external;
function transfer(address _to, uint256 _tokenId) external;
function transferFrom(address _from, address _to, uint256 _tokenId) external;
event Transfer(address from, address to, uint256 tokenId);
event Approval(address owner, address approved, uint256 tokenId);
function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}
totalSupply() returns (uint256 total)
:返回代币总供应量balanceOf(address _owner) returns (uint256 balance)
:传入address
参数,然后返回这个address
拥有多少代币ownerOf(uint256 _tokenId) returns (address owner)
:传入一个代币ID
作为参数 ,然后返回该代币拥有者的address
。approve(address _to, uint256 _tokenId)
:授予地址_to
具有_tokenId
的控制权,方法成功后需触发Approval
事件。transfer(address _to, uint256 _tokenId)
:代币的拥有者调用transfer
方法,传入他想转移到的address
和他想转移的代币的_tokenId
,代币直接传入对方地址。transferFrom(address _from, address _to, uint256 _tokenId)
:转移代币所有权,一次成功的转移操作必须发起Transer
事件。函数的实现需要做一下几种检查:调用者msg.sender
应该是当前tokenId
的所有者或被授权的地址;_from
必须是_tokenId
的所有者;_tokenId
应该是当前合约正在监测的NFTs
中的任何一个;_to
地址不应该为 0。supportsInterface(bytes4 _interfaceID) returns (bool)
:基于ECR165的自检接口。合约实现了任何标准化接口则返回true
。
GeneScienceInterface合约
这个合约很简单,直接贴代码:
contract GeneScienceInterface {
function isGeneScience() public pure returns (bool);
function mixGenes(uint256 genes1, uint256 genes2, uint256 targetBlock) public returns (uint256);
}
isGeneScience() returns (bool)
:单纯返回一个bool
值,用来判断这个合约不是期望的那个。注:该合约即基因算法的实现部署在另一个地址,这样实现了业务逻辑的解耦,方便更新业务逻辑。mixGenes(uint256 genes1, uint256 genes2, uint256 targetBlock) returns (uint256)
:传入妈妈的基因genes1
、爸爸的基因genes2
以及目标区块的targetBlock
来计算生成后代的基因组。
KittyAccessControl合约
KittyAccessControl
合约定义了访问控制权限,共有CEO、CFO、COO三种角色权限,代码如下:
contract KittyAccessControl {
event ContractUpgrade(address newContract);
address public ceoAddress;
address public cfoAddress;
address public cooAddress;
bool public paused = false;
modifier onlyCEO() {
require(msg.sender == ceoAddress);
_;
}
modifier onlyCFO() {
require(msg.sender == cfoAddress);
_;
}
modifier onlyCOO() {
require(msg.sender == cooAddress);
_;
}
modifier onlyCLevel() {
require(
msg.sender == cooAddress ||
msg.sender == ceoAddress ||
msg.sender == cfoAddress
);
_;
}
function setCEO(address _newCEO) external onlyCEO {
require(_newCEO != address(0));
ceoAddress = _newCEO;
}
function setCFO(address _newCFO) external onlyCEO {
require(_newCFO != address(0));
cfoAddress = _newCFO;
}
function setCOO(address _newCOO) external onlyCEO {
require(_newCOO != address(0));
cooAddress = _newCOO;
}
modifier whenNotPaused() {
require(!paused);
_;
}
modifier whenPaused {
require(paused);
_;
}
function pause() external onlyCLevel whenNotPaused {
paused = true;
}
/// @dev Unpauses the smart contract. Can only be called by the CEO, since
/// one reason we may pause the contract is when CFO or COO accounts are
/// compromised.
/// @notice This is public rather than external so it can be called by
/// derived contracts.
function unpause() public onlyCEO whenPaused {
// can't unpause if contract was upgraded
paused = false;
}
}
成员变量:
address ceoAddress
:CEO账户的address
address cfoAddress
:CFO账户的address
address cooAddress
:COO账户的address
bool paused
:合约是否被暂停,如果为true
,大部分的功能将不可用
函数修饰符:
modifier onlyCEO()
:CEO账户专属的修饰符,被该修饰符修饰的函数只有CEO才能访问modifier onlyCFO()
:CFO账户专属的修饰符,被该修饰符修饰的函数只有CFO才能访问modifier onlyCOO()
:COO账户专属的修饰符,被该修饰符修饰的函数只有COO才能访问modifier onlyCLevel()
:CEO、CFO、COO的修饰符,被该修饰符修饰的函数只有是CXO们才可以访问modifier whenNotPaused()
:修饰符,被修饰的函数只有成员变量pause
为false
才能调用modifier whenPaused()
:修饰符,被修饰的函数只有成员变量pause
为true
才能调用
一般函数:
setCEO(address _newCEO) external onlyCEO
:设置新的CEO,只有当前CEO才能调用setCFO(address _newCFO) external onlyCEO
:设置新的CFO,只有当前CEO才能调用setCOO(address _newCOO) external onlyCEO
:设置新的COO,只有当前CEO才能调用pause() external onlyCLevel whenNotPaused
:合约状态不是暂停时,将合约状态设为暂停,即成员变量pause
变为true
,只有CXO们可以调用。一般只在系统出现bug时使用来减少损失unpause() public onlyCEO whenPaused
:解除合约的暂停状态,只有CEO才能调用,因为暂停合约的一个可能的原因就是CFO或COO账户被泄露