【CryptoZombies - 2 Solidity 进阶】003 安全与函数修饰符进阶

目录

一、前言

二、安全性

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个参数:_zombieIduint类型)以及 _newNamestring类型,数据位置设置为calldata),可见性为 external。它带有一个 aboveLevel 修饰符,调用的时候通过 _level 参数传入2, 当然,别忘了同时传 _zombieId 参数。

注:calldata:和memory类似,但是只存在于external函数中。

2.在这个函数中,首先我们用 require 语句,验证 msg.sender 是否就是 zombieToOwner [_zombieId]

3.然后函数将 zombies[_zombieId] .name 设置为 _newName

4.在 changeName 下创建另一个名为 changeDna 的函数。它的定义和内容几乎和 changeName 相同,不过它第二个参数是 _newDnauint类型),在修饰符 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;
  }

}
发布了262 篇原创文章 · 获赞 524 · 访问量 52万+

猜你喜欢

转载自blog.csdn.net/shuiyixin/article/details/104455193
今日推荐