【CryptoZombies - 2 Solidity 进阶】007 随机数keccak256与逻辑重构

目录

一、前言

二、随机数

1、引入

2、keccak256

3、实战1

1.要求

2.代码

4、实战2-僵尸对战

1.要求

2.代码

三、逻辑重构

1、讲解

2、实战1

1.要求

2.代码

3、实战2-攻击完善

1.要求

2.代码


一、前言

看了一些区块链的教程,论文,在网上刚刚找到了一个项目实战,CryptoZombies。

前面我们新建了僵尸战斗,现在我们继续吧。

如果你想了解更多有关于机器学习、深度学习、区块链、计算机视觉等相关技术的内容,想与更多大佬一起沟通,那就扫描下方二维码加入我们吧!

二、随机数

1、引入

我们之前新建了一个合约,表示僵尸战斗,在战斗中,我们都需要干什么呢?

优秀的游戏都需要一些随机元素,那么我们在 Solidity 里如何生成随机数呢?

2、keccak256

我们可以使用keccak256来生成随机数:

uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;

这个方法首先拿到 now 的时间戳、 msg.sender、 以及一个自增数 nonce (一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。 然后利用 keccak 把输入的值转变为一个哈希值, 再将哈希值转换为 uint, 然后利用 % 100 来取最后两位, 就生成了一个0到100之间随机数了。

这看着非常好,但是这样有问题吗?

在以太坊上, 当你在和一个合约上调用函数的时候, 你会把它广播给一个节点或者在网络上的 transaction 节点们。 网络上的节点将收集很多事务, 试着成为第一个解决计算密集型数学问题的人,作为“工作证明”,然后将“工作证明”(Proof of Work, PoW)和事务一起作为一个 block 发布在网络上。

一旦一个节点解决了一个PoW, 其他节点就会停止尝试解决这个 PoW, 并验证其他节点的事务列表是有效的,然后接受这个节点转而尝试解决下一个节点。

这就让我们的随机数函数变得可利用了。

我们假设我们有一个硬币翻转合约——正面你赢双倍钱,反面你输掉所有的钱。假如它使用上面的方法来决定是正面还是反面 (random >= 50 算正面, random < 50 算反面)。

如果我正运行一个节点,我可以 只对我自己的节点 发布一个事务,且不分享它我可以运行硬币翻转方法来偷窥我的输赢 — 如果我输了,我就不把这个事务包含进我要解决的下一个区块中去。我可以一直运行这个方法,直到我赢得了硬币翻转并解决了下一个区块,然后获利

也就是说,我们这样是存在安全问题的,但是我们都知道,开采一个区块的计算量是很大的,对于一个小游戏来说,开采区块的花费高于游戏获胜赢得的花费。(排除那些不差钱就为了赢着爽的人)。所以大部分人都是愿意去开采区块的,这样获利更大。

所以我们要知道,这个是不安全的,但是因为我们这个小游戏,玩家遵守规则获利更大,所以我们先暂时不考虑这个不安全性。

3、实战1

1.要求

我们来实现一个随机数生成函数。

1.给我们合约一个名为 randNonce 的 uint,将其值设置为 0

2.建立一个函数,命名为 randMod (random-modulus)。它将作为internal 函数,传入一个名为 _modulus的 uint,并 returns 一个 uint

3.这个函数首先将为 randNonce加一, (使用 randNonce++ 语句)。

4.最后,它应该 (在一行代码中) 计算 nowmsg.sender, 以及 randNonce 的 keccak256 哈希值并转换为 uint—— 最后 return % _modulus 的值。

 

2.代码

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  // Start here
  uint randNonce = 0;
  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }
}

4、实战2-僵尸对战

1.要求

我们的合约已经有了一些随机性的来源,可以用进我们的僵尸战斗中去计算结果。僵尸战斗流程如下:

1.你选择一个自己的僵尸,然后选择一个对手的僵尸去攻击。

2.如果你是攻击方,你将有70%的几率获胜,防守方将有30%的几率获胜。

3.所有的僵尸(攻守双方)都将有一个 winCount 和一个 lossCount,这两个值都将根据战斗结果增长。

4.若攻击方获胜,这个僵尸将升级并产生一个新僵尸。

5.如果攻击方失败,除了失败次数将加一外,什么都不会发生。

6.无论输赢,当前僵尸的冷却时间都将被激活。

接下来我们需要先完成如下功能:

遵循上一章的格式,我们新建一个攻击功能合约,并将代码放进新的文件中,引入上一个合约。

1.给我们合约一个 uint 类型的变量,命名为 attackVictoryProbability, 将其值设定为 70

2.创建一个名为 attack的函数。它将传入两个参数: _zombieId (uint 类型) 以及 _targetId (也是 uint)。它将是一个 external 函数,函数体先留空。

2.代码

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  // Create attackVictoryProbability here
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

  // Create new function here
  function attack(uint _zombieId, uint _targetId) external {

  }
}

三、逻辑重构

1、讲解

不管谁调用我们的 attack 函数 —— 我们想确保用户的确拥有他们用来攻击的僵尸。如果你能用其他人的僵尸来攻击将是一个很大的安全问题。所以我们需要添加一个检查步骤来查看其ID,确保其调用的僵尸确实是属于他们的。

检查方式也很简单,我们之前经常用到:

require(msg.sender == zombieToOwner[_zombieId]);

这和我们 attack 函数将要用到的检查逻辑是相同的。 正因我们要多次调用这个检查逻辑,让我们把它移到它自己的 modifier 中来清理代码并避免重复编码。

2、实战1

1.要求

回到了 zombiefeeding.sol, 因为这是我们第一次调用检查逻辑的地方。让我们把它重构进它自己的 modifier

1.创建一个 modifier, 命名为 ownerOf。它将传入一个参数, _zombieId (一个 uint)。

2.将这个函数的 feedAndMultiply 定义修改为其使用修饰符 ownerOf

3.现在我们使用 modifier,你可以删除这: require(msg.sender == zombieToOwner[_zombieId]);

 回到了 zombiehelper.sol, 因为这是我们第一次调用检查逻辑的地方。让我们把它重构进它自己的 modifier

1.修改 changeName() 使其使用 ownerOf

2.修改 changeDna() 使其使用 ownerOf。

 

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;

  // 1. Create modifier here
  modifier ownerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }
  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);
  }

  // 2. Add modifier to function definition:
  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) internal ownerOf(_zombieId) {
    // 3. Remove this line
    Zombie storage myZombie = zombies[_zombieId];
    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);
    _triggerCooldown(myZombie);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }
}
pragma solidity >=0.5.0 <0.6.0;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function withdraw() external onlyOwner {
    address _owner = owner();
    _owner.transfer(address(this).balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  // 1. Modify this function to use `ownerOf`:
  function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId)  ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  // 2. Do the same with this function:
  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId)  ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[] memory) {
    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;
  }

}

3、实战2-攻击完善

1.要求

根据我们今天学的内容,我们就可以完善一下我们的攻击了。

1.将 ownerOf 修饰符添加到 attack 来确保调用者拥有_zombieId

2.我们的函数所需要做的第一件事就是获得一个双方僵尸的 storage 指针, 这样我们才能很方便和它们交互

(1)定义一个 Zombie storage 命名为 myZombie,使其值等于 zombies[_zombieId]

(2)定义一个 Zombie storage 命名为 enemyZombie, 使其值等于 zombies[_targetId]

3.我们将用一个0到100的随机数来确定我们的战斗结果。 定义一个 uint,命名为 rand, 设定其值等于 randMod 函数的返回值,此函数传入 100作为参数。

2.代码

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;

  }

  // 1. Add modifier here
  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    // 2. Start function definition here
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
  }
}

 

你可能感兴趣的:(Blockchain)