资讯专栏INFORMATION COLUMN

以太坊开发实战学习-高级Solidity理论 (六)

qc1iu / 507人阅读

摘要:接上篇文章,这里继续学习高级理论。将这个函数的定义修改为其使用修饰符。我们将用一个到的随机数来确定我们的战斗结果。在这个教程中,简单起见我们将这个状态保存在结构体中,将其命名为和。在第六章我们计算出来一个到的随机数。

接上篇文章,这里继续学习Solidity高级理论。
一、重构通用逻辑

不管谁调用我们的 attack 函数 —— 我们想确保用户的确拥有他们用来攻击的僵尸。如果你能用其他人的僵尸来攻击将是一个很大的安全问题。

你能想一下我们如何添加一个检查步骤来看看调用这个函数的人就是他们传入的 _zombieId 的拥有者么?

想一想,看看你能不能自己找到一些答案。

花点时间…… 参考我们前面课程的代码来获得灵感。

答案

我们在前面的课程里面已经做过很多次这样的检查了。 在 changeName(), changeDna(), 和 feedAndMultiply()里,我们做过这样的检查:

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

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

实战演练

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

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

它的函数体应该 require msg.sender 等于 zombieToOwner[_zombieId], 然后继续这个函数剩下的内容。 如果你忘记了修饰符的写法,可以参考 zombiehelper.sol

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

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

zombiefeeding.sol

pragma solidity ^0.4.19;

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. 在这里创建 modifier
  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. 在函数定义时增加 modifier :
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    // 3. 移除这一行
    // 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);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }
}
更多重构

zombiehelper.sol 里有几处地方,需要我们实现我们新的 modifier—— ownerOf

实战演练:

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

2、修改 changeDna() 使其使用 ownerOf

zombiehelper.sol

pragma solidity ^0.4.19;

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 {
    owner.transfer(this.balance);
  }

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

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

  // 1. 使用 `ownerOf` 修改这个函数:
  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    // require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  // 2. 对这个函数做同样的事:
  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    // require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].dna = _newDna;
  }

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

}

二、回到攻击

重构完成了,回到我们上节博文学习的游戏对战 zombieattack.sol

继续来完善我们的 attack 函数, 现在我们有了 ownerOf 修饰符来用了。

实战演练

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

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

a. 定义一个 Zombie storage 命名为 myZombie,使其值等于 zombies[_zombieId]。

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

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

zombieattack.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

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

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

  // 1. 在这里增加 modifier
  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    // 2. 在这里开始定义函数
    Zombie storage myZombie = zombies[_zombieId];

    Zombie storage enemyZombie = zombies[_targetId];

    uint rand = randMod(100);

  }
}
三、输赢排行榜

对我们的僵尸游戏来说,我们将要追踪我们的僵尸输赢了多少场。有了这个我们可以在游戏里维护一个 "僵尸排行榜"。

有多种方法在我们的DApp里面保存一个数值 — 作为一个多带带的映射,作为一个“排行榜”结构体,或者保存在 Zombie 结构体内。

每个方法都有其优缺点,取决于我们打算如何和这些数据打交道。在这个教程中,简单起见我们将这个状态保存在 Zombie 结构体中,将其命名为 winCountlossCount

我们跳回 zombiefactory.sol, 将这些属性添加进 Zombie 结构体.

实战演练

实战演习

1、修改 Zombie 结构体,添加两个属性:

a. winCount, 一个 uint16

b. lossCount, 也是一个 uint16

注意: 记住, 因为我们能在结构体中包装uint, 我们打算用适合我们的最小的 uint。 一个 uint8 太小了, 因为 2^8 = 256 —— 如果我们的僵尸每天都作战,不到一年就溢出了。但是 2^16 = 65536 (uint16)—— 除非一个僵尸连续179年每天作战,否则我们就是安全的。

2、现在我们的 Zombie 结构体有了新的属性, 我们需要修改 _createZombie() 中的函数定义。

修改僵尸生成定义,让每个新僵尸都有 0 赢和 0 输。

zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
    uint cooldownTime = 1 days;

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      // 1. 在这里添加新的属性
      uint16 winCount;
      uint16 lossCount;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        // 2. 在这里修改修改新僵尸的创建:
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}
四、更新输赢状态

有了 winCountlossCount,我们可以根据僵尸哪个僵尸赢了战斗来更新它们了。

在第六章我们计算出来一个0到100的随机数。现在让我们用那个数来决定那谁赢了战斗,并以此更新我们的状态。

实战演练

1、创建一个 if 语句来检查 rand 是不是 小于或者等于 attackVictoryProbability

2、如果以上条件为 true, 我们的僵尸就赢了!所以:

a. 增加 myZombie 的 winCount。

b. 增加 myZombie 的 level。 (升级了啦!!!!!!!)

c. 增加 enemyZombie 的 lossCount. (输家!!!!!!)

d. 运行 feedAndMultiply 函数。 在 zombiefeeding.sol 里查看调用它的语句。 对于第三个参数 (_species),传入字符串 "zombie". (现在它实际上什么都不做,不过在稍后, 如果我们愿意,可以添加额外的方法,用来制造僵尸变的僵尸)。

zombieattack.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

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

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

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    // 在这里开始
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;

      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");

    }
  }
}
五、失败触发冷却

我们已经编写了你的僵尸赢了之后会发生什么, 该看看 输了 的时候要怎么做了。

在我们的游戏中,僵尸输了后并不会降级 —— 只是简单地给 lossCount 加一,并触发冷却,等待一天后才能再次参战。

要实现这个逻辑,我们需要一个 else 语句。

else 语句和 JavaScript 以及很多其他语言的 else 语句一样。

if (zombieCoins[msg.sender] > 100000000) {
  // 你好有钱!!!
} else {
  // 我们需要更多的僵尸币...
}
实战演练

1、添加一个 else 语句。 若我们的僵尸输了:

a. 增加 myZombie 的 lossCount。

b. 增加 enemyZombie 的 winCount。

2、在 else 最后, 对 myZombie 运行 _triggerCooldown 方法。这让每个僵尸每天只能参战一次。

zombieattack.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

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

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

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } 
    else 
    {
      // 在这里开始
       myZombie.lossCount++;
       enemyZombie.winCount++;
        _triggerCooldown(myZombie);
    }

    
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/24130.html

相关文章

  • 以太开发实战学习-高级Solidity理论(四)

    摘要:第一个例子,在你把智能协议传上以太坊之后,它就变得不可更改这种永固性意味着你的代码永远不能被调整或更新。允许将合约所有权转让给他人。为何要来驱动以太坊就像一个巨大缓慢但非常安全的电脑。 通过前边的 Solidity 基础语法学习,我们已经有了Solidity编程经验,在这节就要学学 Ethereum 开发的技术细节,编写真正的 DApp 时必知的:智能协议的所有权,Gas的花费,代码优...

    feng409 评论0 收藏0
  • 以太开发实战学习-高级Solidity理论 (五)

    摘要:接上篇文章,这里继续学习高级理论。实战演练我们来写一个返回某玩家的整个僵尸军团的函数。但这样每做一笔交易,都会改变僵尸军团的秩序。在这里开始五可支付截至目前,我们只接触到很少的函数修饰符。 接上篇文章,这里继续学习Solidity高级理论。 一、深入函数修饰符 接下来,我们将添加一些辅助方法。我们为您创建了一个名为 zombiehelper.sol 的新文件,并且将 zombiefee...

    sushi 评论0 收藏0
  • 区块链技术学习指引

    摘要:引言给迷失在如何学习区块链技术的同学一个指引,区块链技术是随比特币诞生,因此要搞明白区块链技术,应该先了解下比特币。但区块链技术不单应用于比特币,还有非常多的现实应用场景,想做区块链应用开发,可进一步阅读以太坊系列。 本文始发于深入浅出区块链社区, 原文:区块链技术学习指引 原文已更新,请读者前往原文阅读 本章的文章越来越多,本文是一个索引帖,方便找到自己感兴趣的文章,你也可以使用左侧...

    Cristic 评论0 收藏0
  • SegmentFault 技术周刊 Vol.41 - 深入学习区块链

    摘要:和比特币协议有所不同的是,以太坊的设计十分灵活,极具适应性。超级账本区块链的商业应用超级账本超级账本是基金会下的众多项目中的一个。证书颁发机构负责签发撤 showImg(https://segmentfault.com/img/bV2ge9?w=900&h=385); 从比特币开始 一个故事告诉你比特币的原理及运作机制 这篇文章的定位会比较科普,尽量用类比的方法将比特币的基本原理讲出来...

    qianfeng 评论0 收藏0
  • 以太开发实战学习-solidity语法(二)

    摘要:以太坊开发高级语言学习。地址以太坊区块链由账户组成,你可以把它想象成银行账户。使用很安全,因为它具有以太坊区块链的安全保障除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。 以太坊开发高级语言学习。 一、映射(Mapping)和地址(Address) 我们通过给数据库中的僵尸指定主人, 来支持多玩家模式。 如此一来,我们需要引入2个新的数据类型:mapping(映射)...

    wemall 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<