目录
一、前言
二、安全性
1、讲解
2、实战
1.要求
2.代码
三、函数修饰符进阶
1、讲解
2、实战1
1.要求
2.代码
3、实战2——僵尸升级
1.要求
2.代码
一、前言
看了一些区块链的教程,论文,在网上刚刚找到了一个项目实战,CryptoZombies。
二、安全性
1、讲解
区块链的实现了去中心化,并且是自动运作,无法篡改,这本身是极大的安全,以太坊作为区块链2.0,安全性也是必须要考虑的。以太坊一样,属于公有链,不可篡改,所以想要实现51%的攻击力是几乎不可能的。
所以我们强调区块链的安全性,就是在我们写合约的过程中,要保证合约本身尽可能的安全。对于合约中函数的权限要根据具体情况,来选择其访问权限,合理利用public和external。防止外部用户滥用函数,从而排除安全漏洞。如果没有onlyOwner这样的函数修饰符,用户能利用各种可能的参数去调用他们。
2、实战
1.要求
1.将函数 feedAndMultiply
可见性由 public
改为 internal
以保障合约安全。因为我们不希望用户调用它的时候塞进一堆乱七八糟的 DNA。
2.feedAndMultiply
过程需要参考 cooldownTime
。首先,在找到 myZombie
之后,添加一个 require
语句来检查 _isReady()
并将 myZombie
传递给它。这样用户必须等到僵尸的 冷却周期
结束后才能执行 feedAndMultiply
功能。
3.在函数结束时,调用 _triggerCooldown(myZombie)
,标明捕猎行为触发了僵尸新的冷却周期。
对于1,为什么要将public修改为internal呢?
因为在使用过程中,该函数只会被feedOnKitty()
调用,同时我们也希望其子合约能够访问,所以我们就可以将其可见性修改为internal。
2.代码
pragma solidity >=0.5.0 <0.6.0;
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 _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}
function _isReady(Zombie storage _zombie) internal view returns (bool) {
return (_zombie.readyTime <= now);
}
// 1. Make this function internal
function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) internal {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
// 2. Add a check for `_isReady` here
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
// 3. Call `triggerCooldown`
_triggerCooldown(myZombie);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
三、函数修饰符进阶
1、讲解
在之前,我们已经入门了解过函数修饰符。
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
这个函数修饰符比较简单,也没有参数。
所以我想大家也应该知道,我们今天要了解的就是带有参数的函数修饰符。先给大家看一个具体的示例,然后我们再详细讲解。
// 存储用户年龄的映射
mapping (uint => uint) public age;
// 限定用户年龄的修饰符
modifier olderThan(uint _age, uint _userId) {
require(age[_userId] >= _age);
_;
}
// 必须年满16周岁才允许开车 (至少在美国是这样的).
// 我们可以用如下参数调用`olderThan` 修饰符:
function driveCar(uint _userId) public olderThan(16, _userId) {
// 其余的程序逻辑
}
在这个示例里,我们建立了一个修饰符:olderThan。这个修饰符可以像普通函数一样接受参数。
modifier olderThan(uint _age, uint _userId) {
require(age[_userId] >= _age);
_;
}
这个修饰符的作用是限制了年龄,当用户的年龄大于等于给定的参数时,才能进行后续的操作。比如年满多少岁,才能考驾照,开车。年满多少岁,才能办理身份证,年满多少岁才能结婚等等。
通过前面的学习,我们也知道,我们定义了修饰符之后,要在“宿主函数”(调用该修饰符)中使用。其参数也是通过其宿主函数进行传递的。
// 必须年满16周岁才允许开车 (至少在美国是这样的).
// 我们可以用如下参数调用`olderThan` 修饰符:
function driveCar(uint _userId) public olderThan(16, _userId) {
// 其余的程序逻辑
}
2、实战1
1.要求
定义一个修饰符,通过传入的level
参数来限制僵尸使用某些特殊功能。
1.在ZombieHelper
中,创建一个名为 aboveLevel
的modifier
,它接收2个参数, _level
(uint
类型) 以及 _zombieId
(uint
类型)。
2.运用函数逻辑确保僵尸 zombies[_zombieId].level
大于或等于 _level
。
3.记住,修饰符的最后一行为 _;
,表示修饰符调用结束后返回,并执行调用函数余下的部分。
2.代码
pragma solidity >=0.5.0 <0.6.0;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
// Start here
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
}
3、实战2——僵尸升级
我们可以设置一些激励玩家去升级僵尸的措施。比如:
1. 2级以上的僵尸,玩家可给他们改名。
2. 20级以上的僵尸,玩家能给他们定制的 DNA。
1.要求
1.创建一个名为 changeName
的函数。它接收2个参数:_zombieId
(uint
类型)以及 _newName
(string
类型,数据位置设置为calldata),可见性为 external
。它带有一个 aboveLevel
修饰符,调用的时候通过 _level
参数传入2
, 当然,别忘了同时传 _zombieId
参数。
注:calldata:和memory类似,但是只存在于external函数中。
2.在这个函数中,首先我们用 require
语句,验证 msg.sender
是否就是 zombieToOwner [_zombieId]
。
3.然后函数将 zombies[_zombieId] .name
设置为 _newName
。
4.在 changeName
下创建另一个名为 changeDna
的函数。它的定义和内容几乎和 changeName
相同,不过它第二个参数是 _newDna
(uint
类型),在修饰符 aboveLevel
的 _level
参数中传递 20
。现在,他可以把僵尸的 dna
设置为 _newDna
了。
2.代码
pragma solidity >=0.5.0 <0.6.0;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
// Start here
function changeName(uint _zombieId, string calldata _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;
}
}