cryptozombies全攻略三
第一章.智能协议的永固性
我们看下这段代码
contract ZombieFeeding is ZombieFactory {
address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
KittyInterface kittyContract = KittyInterface(ckAddress);
......
}
这里的ckAddress是写死的,
那么如果CryptoKitties的合约的地址更改了(虽然几乎不可能发生)
那么我们的合约也完全错误了
所以我们把这里的ckAddress写成动态的
改成运行时再设定地址
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
function setKittyContractAddress(address _address) external{
kittyContract=KittyInterface(_address);
}
......
}
第二章.ownable contracts
我们刚刚的方法setKittyContractAddress是external
所以是不安全的
所以我们需要制定合约的"所有权"
构造函数
function Ownable()是一个constructor构造函数
构造函数不是必须的,它与合约同名,
构造函数只有合约被创建的时候,才执行一次
函数修饰符
modifier onlyOwner()
修饰符和函数很类似
但是是用来修饰其他的已有函数的
在其他语句执行前,先检查一下条件
Ownable合约基本都会这样
1.创建合约,构造函数,将owner设置为msg.sender
2.加上一个修饰符onlyOwner,限制陌生人访问,将访问某些函数的权限锁定在owner上.
3.允许将合约所有权转让给他人
大多数人开发solidity DApp,都会复制Ownable
然后从它继承出子类,然后进行开发
现在我们需要把setKittyContractAddress限制为onlyOwner
现在我们让ZombieFactory继承Ownable
然后导入一下
import "./ownable.sol";
contract ZombieFactory is Ownable{
...
}
第三章.onlyOwner
我们已经继承了Ownable
然后用onlyOwner来修饰setKittyContractAddress方法
function setKittyContractAddress(address _address) external onlyOwner{
kittyContract = KittyInterface(_address);
}
第四章.Gas
我们给僵尸添加2个新的属性
level 和 readyTime
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
}
第五章.时间单位
我们添加一个冷却周期的设定
让僵尸两次攻击或捕猎之间必须等待1天
我们定义一个uint
uint cooldownTime = 1 days;
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}
现在,当我们创建Zombie并添加到zombies的时候
需要_name,_dna,1,uint32(now + cooldownTime)
1指的是level,等级
uint32(now + cooldownTime)指的是readyTime
也就是把readyTime设置为当前时间再加上1天.
第六章.冷却
我们创建2个函数
function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}
function _isReady(Zombie storage _zombie) internal view returns (bool) {
return (_zombie.readyTime <= now);
}
先看_triggerCooldown,传入Zombie storage
这相当于传入引用,可以直接操作
代替了传递id,然后再去查找僵尸
然后设置zombie的readyTime为当前时间加上1天
再看isReady,也是传入Zombie storage
我们比较zombie的readyTime
如果小于等于当前时间
那么就是已经冷却完成了,返回true
反之则是还没有冷却完,还没有准备好,返回false
这个方法没有操作其他东西,只是获取
所以我们加上一个view
第七章.公有函数和安全性
我们需要对冷却时间进行检查
function feedAndMultiply(uint _zombieId, uint _targetDna, string species) internal {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
_triggerCooldown(myZombie);
}
1.对用户进行检查
2.根据id获取zombie对象
3.检查冷却时间,如果为false,说明这个僵尸还未冷却
...
最后,创建新的zombie,
然后需要设置一下这个新zombie的冷却时间
第八章.函数修饰符
先来个例子
mapping (uint => uint) public age;
modifier olderThan(uint _age, uint _userId) {
require(age[_userId] >= _age);
_;
}
function driveCar(uint _userId) public olderThan(16, _userId) {
//...
}
我们来看这段代码
olderThan是一个modifier函数修饰符
然后修饰了driveCar这个函数
那么就先执行olderThan里的代码,判断age
满足条件后,再执行driveCar中的代码
那么我们来写一下modifier
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
参数传入_level等级,_zombieId
根据zombieId取出zombie对象,判断这个zombie的等级
是否大于等于参数_level
如果满足,则继续执行
如果不满足,则终止执行
第九章.修饰符
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].dna = _newDna;
}
}
首先来一个modifier函数修饰符aboveLevel
这个函数修饰符的意思就是在某个等级之上
也就是如果在指定的等级之上,则继续执行
否则,就停止执行
然后来看changeName方法,修改名字
参数是zombieId,和newName
用aboveLevel来修饰这个函数
所以,满足了大于2,才会继续执行
首先判断这个zombie的主人是不是该用户
然后修改名字
修改dna也是一样的道理
用aboveLevel修饰,
如果大于20,才会继续执行
判断主人
然后修改dna
第十章.利用'view'函数节省Gas
现在我们添加一个功能getZombiesByOwner
也就是查看这个用户的所有僵尸
实现这个功能只需要从区块链中读取数据
所以可以是一个view函数
view函数不花gas
因为view函数不会真正改变区块链上的任何数据,只是读取
所以用view来标记一个函数
就是告诉web3.js,
运行这个函数只需要查询本地以太坊节点
不需要在区块链上创建一个事务
事务需要运行在每个节点上,所以花费gas
现在创建getZombiesByOwner函数
function getZombiesByOwner(address _owner) external view returns(uint[]) {
}
第十一章.存储storage
solidity使用storage存储是非常昂贵的
写入的操作尤其贵
这是因为,
无论是写入还是更改一段数据,
都将永久性的写入区块链
所有的节点上都需要去存入这些数据,这是需要成本的
在内存中声明数组
在数组后面加上memory关键字
表明这个数组仅仅在内存中创建
不需要写入外部存储
而且在函数调用结束的时候,它就释放了
举个例子
function getArray() external pure returns(uint[]) {
// 初始化一个长度为3的内存数组
uint[] memory values = new uint[](3);
// 赋值
values.push(1);
values.push(2);
values.push(3);
// 返回数组
return values;
}
注意,内存数组必须用长度参数创建
目前不支持array.push()之类的方法调整数组大小
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
return result;
}
创建getZombiesByOwner方法,参数传入地址address,
external view来进行修饰
返回uint[] 数组
之前我们定义过一个mapping映射ownerZombieCount
我们根据key键_owner,来获取count
然后创建uint[]数组,用memory来修饰
uint[] memory result= new uint[]();
十二章.for循环
我们现在要实现getZombiesByOwner函数
一种简单的解决方案就是
在ZombieFactory中存入主人和僵尸数组的映射
也就是这样
mapping (address => uint[]) public ownerToZombies;
然后我们每次创建僵尸的时候,
就执行ownerToZombies[owner].push(zombieId)
然后getZombiesByOwner可以这样
function getZombiesByOwner(address _owner) external view returns(uint[]){
return ownerToZombies[_owner];
}
也就是从ownerToZombies映射中
直接取出uint[]数组
也就是zombieId数组
但是这个做法是有问题的
每一次都需要做写的操作
使用for循环
举个例子先,创建一个偶数数组
function getEvens() pure external returns(uint[]) {
uint[] memory evens = new uint[](5);
// 在新数组中记录序列号
uint counter = 0;
// 在循环从1迭代到10:
for (uint i = 1; i <= 10; i++) {
// 如果 `i` 是偶数...
if (i % 2 == 0) {
// 把它加入偶数数组
evens[counter] = i;
//索引加一, 指向下一个空的‘even’
counter++;
}
}
return evens;
}
现在我们来重新写getZombiesByOwner函数
通过一个for循环遍历所有的僵尸
将用户地址address和僵尸的主人进行比较
把它们放到result数组里面
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for(uint i = 0; i < zombies.length; i++){
if(zombieToOwner[i] == _owner){
result[counter] = i;
counter++;
}
}
return result;
}