【CryptoZombies - 2 Solidity 进阶】002 Gas & Time Units

目录

一、前言

二、Gas - 驱动以太坊DApps的能源

1、讲解

1.以太币

2.Gas是啥

3.Gas能干嘛?

4.如何节省Gas

2、实战

1.要求

2.代码

三、Time Units 

1、讲解

2、实战1

1.要求

2.代码

3、实战2——僵尸冷却

1.要求

2.代码


一、前言

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

本次要为大家讲解有关于Gas的相关知识。

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

二、Gas - 驱动以太坊DApps的能源

1、讲解

Gas是solidity编程语言中非常与众不同的特征,我猜想,Gas是受到了比特币的启发。用户每次执行你的DAPP都需要支付一定的Gas,Gas可以使用以太币购买。

1.以太币

在讲Gas之前,我们先来简单了解一下什么是以太币。

以太币(ETH)是以太坊(Ethereum)的一种数字代币,被视为“比特币2.0版”,以太币涉及到的技术是与比特币不同的区块链技术“以太坊”(Ethereum),以太坊是一个开源的有智能合约成果的民众区块链平台。

在此,膜拜V神!

在此,膜拜V神!

在此,膜拜V神!

但是我们只是在此做个简单了解,因为我们这个博客是为了做CryptoZombies的学习记录,以后,我会逐步完善有关区块链和以太坊的内容。

2.Gas是啥

Gas是测量以太坊网络中运行交易或智能合约的计算工作的单位。该系统类似于使用千瓦(kW)来测量房屋内的电力;您使用的电力不是以美元和美分计量,而是以每小时千瓦时或千瓦计。

Gas用来衡量执行某些动作需要多少“工作量”,这些“工作量”就是为了执行该动作支付给网络的费用额。通俗理解,Gas是给矿工的佣金,以ETH 支付,无论是交易、执行智能合约并启动 DApps,还是支付数据存储费用,都需要用到 Gas。

3.Gas能干嘛?

Gas常用来作为激励矿工,那为什么要用Gas来作为激励呢?

区块链中有很多矿工,他们耗费大量的算力进行挖矿,无非就是为了抢夺nonce值,谁最先计算出来得到全网认可的nonce值,谁就获得了记账权,并能够获得对应的比特币。

以太坊就像一个巨大、缓慢、但非常安全的电脑。当你运行一个程序的时候,网络上的每一个节点都在进行相同的运算,以验证它的输出 —— 这就是所谓的“去中心化” 由于数以千计的节点同时在验证着每个功能的运行,这可以确保它的数据不会被被监控,或者被刻意修改。

可能会有用户用无限循环堵塞网络,抑或用密集运算来占用大量的网络资源,为了防止这种事情的发生,以太坊的创建者为以太坊上的资源制定了价格,想要在以太坊上运算或者存储,你需要先付费。

注:如果你使用侧链,倒是不一定需要付费,比如咱们在 Loom Network 上构建的 CryptoZombies 就免费。你不会想要在以太坊主网上玩儿“魔兽世界”吧? - 所需要的 gas 可能会买到你破产。但是你可以找个算法理念不同的侧链来玩它。我们将在以后的课程中咱们会讨论到,什么样的 DApp 应该部署在太坊主链上,什么又最好放在侧链。

更多详细的有关Gas的内容,我们在介绍以太坊相关概念的时候,会着重介绍,在这里我们主要还是来聊一下技术吧!

4.如何节省Gas

我们了解到用户执行DAPP就会消耗Gas,那我们肯定是希望Gas消耗的越少越好,那我们如何节省Gas呢?

在第1课中,我们提到除了基本版的 uint 外,还有其他变种 uintuint8uint16uint32等。

通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uintuint256)不会为你节省任何 gas。

如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。例如:

struct NormalStruct {
  uint a;
  uint b;
  uint c;
}

struct MiniMe {
  uint32 a;
  uint32 b;
  uint c;
}

// 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30); 

所以,当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 struct

uint c; uint32 a; uint32 b; 

和 

uint32 a; uint c; uint32 b;

前者比后者需要的gas更少,因为前者把uint32放一起了。

 

2、实战

1.要求

为 Zombie 结构体添加两个属性:leveluint32)和readyTimeuint32)。因为希望同类型数据打成一个包,所以把它们放在结构体的末尾。32位足以保存僵尸的级别和时间戳了,这样比起使用普通的uint(256位),可以更紧密地封装数据,从而为我们省点 gas。

2.代码

pragma solidity >=0.5.0 <0.6.0;

import "./ownable.sol";

contract ZombieFactory is Ownable {

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

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
        // Add new data here
        uint32 level;
        uint32 readyTime;
    }

    Zombie[] public zombies;

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

    function _createZombie(string memory _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

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

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

}

三、Time Units 

1、讲解

在上面的例题中,我们添加了两个uint32类型的数据level和readyTime。

level属性表示僵尸的级别。以后,在我们创建的战斗系统中,打胜仗的僵尸会逐渐升级并获得更多的能力。

重点是readyTime。

我们希望增加一个“冷却周期”,表示僵尸在两次猎食或攻击之之间必须等待的时间。如果没有它,僵尸每天可能会攻击和繁殖1,000次,这样游戏就太简单了。

为了记录僵尸在下一次进击前需要等待的时间,我们使用了 Solidity 的时间单位。

Solidity 使用自己的本地时间单位。

变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)。

注:Unix时间传统用一个32位的整数进行存储。这会导致“2038年”问题,当这个32位的unix时间戳不够用,产生溢出,使用这个时间的遗留系统就麻烦了。所以,如果我们想让我们的 DApp 跑够20年,我们可以使用64位整数表示时间,但为此我们的用户又得支付更多的 gas。真是个两难的设计啊!

Solidity 还包含秒(seconds)分钟(minutes)小时(hours)天(days)周(weeks) 和 年(years) 等时间单位。它们都会转换成对应的秒数放入 uint 中。所以 1分钟 就是 601小时是 3600(60秒×60分钟),1天86400(24小时×60分钟×60秒),以此类推。

下面是一些使用时间单位的实用案例:

uint lastUpdated;

// 将‘上次更新时间’ 设置为 ‘现在’
function updateTimestamp() public {
  lastUpdated = now;
}

// 如果到上次`updateTimestamp` 超过5分钟,返回 'true'
// 不到5分钟返回 'false'
function fiveMinutesHavePassed() public view returns (bool) {
  return (now >= (lastUpdated + 5 minutes));
}

 

2、实战1

1.要求

给DApp添加一个“冷却周期”的设定,让僵尸两次攻击或捕猎之间必须等待 1天

1.声明一个名为 cooldownTime 的uint,并将其设置为 1 days

2.因为在上一章中我们给 Zombie 结构体中添加 level 和 readyTime 两个参数,所以现在创建一个新的 Zombie 结构体时,需要修改 _createZombie(),在其中把新旧参数都初始化一下。

修改 zombies.push 那一行, 添加加2个参数:1(表示当前的 level )和uint32(now + cooldownTime)(现在+冷却时间,表示下次允许攻击的时间 readyTime)。

注:必须使用 uint32(...) 进行强制类型转换,因为 now 返回类型 uint256。所以我们需要明确将它转换成一个 uint32 类型的变量

2.代码

pragma solidity >=0.5.0 <0.6.0;

import "./ownable.sol";

contract ZombieFactory is Ownable {

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

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
    // 1. Define `cooldownTime` here
    uint cooldownTime = 1 days;
    struct Zombie {
        string name;
        uint dna;
        uint32 level;
        uint32 readyTime;
    }

    Zombie[] public zombies;

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

    function _createZombie(string memory _name, uint _dna) internal {
        // 2. Update the following line:
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

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

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

}

3、实战2——僵尸冷却

冷却用于防止僵尸无限制地繁殖和捕猎,也会在以后用于限制僵尸之间的打频率。

1.要求

给DApp添加一个“冷却周期”的设定,让僵尸两次攻击或捕猎之间必须等待 1天

1.先定义一个 _triggerCooldown 函数。它要求一个参数,_zombie,表示一某个Zombie的storage指针。这个函数可见性设置为 internal

2.在函数中,把 _zombie.readyTime 设置为 uint32(now + cooldownTime)

3.创建一个名为 _isReady 的函数。这个函数的参数也是名为 _zombie 的 Zombie storage。这是一个 internal view 函数,并返回一个 bool 值。。

4.函数计算返回(_zombie.readyTime <= now),值为 true 或 false。这个功能的目的是判断下次允许猎食的时间是否已经到了。

 

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

  // 1. Define `_triggerCooldown` function here
  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  // 2. Define `_isReady` function here
  function _isReady(Zombie storage _zombie) internal view returns(bool) {
    return (_zombie.readyTime <= now);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _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);
  }

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

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值