目录

​一、前言​

​二、安全性​

​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;
}

}