在本章之前,不得不说说运行在以太坊的dapp和我们普通的app有着天壤之别,即智能协议的永固性,我们编译的程序会永久的,不可更改的存在在以太网上,不存在像我们中心化的系统上不断的版本迭代的可能。某种程度上,这是对开发者极其不友好的,在我们过去的开发经验中,我们总是在不断迭代我们的系统使其更加稳定健壮,而dapp就只有一次机会,它极大的考验了开发者的代码技巧和细心程度,所以有的人会说区块链某种程度上是和互联网是呈镜像关系的,但正因为如此反而成就了dapp的独一无二的优势,一旦合约被部署确认,任何人将无权更改它,程序会按照原有的代码一丝不苟的执行,如果你的合约产生漏洞,对不起,你只有让你的用户转移到新修复的合约上面去,但是用户大概率是不可能买账开发出漏洞的dapp团队的二次产品,著名的DAO事件还让人历历在目!
还记得之前我们在ZombieFeeding 合约中使用了kittyContract的接口,将address“硬编码”到我们的合约中,假如有天,kittyContract不存在,那么我们的合约也将不能使用,这种将生杀大权交给别人的dapp显然是致命的!
所以一开始的时候我们不会把地址通过变量写入合约,而是通过函数的方式来实现注入!当然,我们不可能让任何人都来修改地址,我们应该只能让合约地址的拥有人可以修改地址。
我们可以通过OpenZeppelin 库的 Ownable 的合约来实现这一目的,我们先来看看ownable.sol的源代码,有了之前的基础,我想看起来还是很简单的:
/** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */
contract Ownable {
address public owner;
//这里是个事件,供前端监听
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account.这是一个构造函数,在合约启动时只运行一次,将合约的地址赋给地址owner */
function Ownable() public {
owner = msg.sender;
}
/** * @dev Throws if called by any account other than the owner。这里就是modifier onlyOwner的修饰符,用来判定是否是合约的发布者 * */
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. * 让合约拥有者修改指定新的合约拥有者,并调用事件来监听 */
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
好,那么我们引入一个函数来指定小猫的地址,在ZombieFactory引用ownable合约,在ZombieFeeding合约中创建setKittyContractAddress函数,并用onlyOwner修饰:
pragma solidity ^0.4.19;
import "./ownable.sol";
contract ZombieFactory is Ownable {
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
// Add new data here
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}
function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
不要告诉我你忘了external关键字是用来干什么的,它表示这个函数只能被合约外的合约调用哦!!!
有没有感觉这个onlyOwner是后门的感觉,所以你的只能合约想被大众认可,这些后门都是必要的,而且记得必须在Github上开源他们!!!