ERC721和加密收藏品(ERC721 & Crypto-Collectibles)

这篇具有很好参考价值的文章主要介绍了ERC721和加密收藏品(ERC721 & Crypto-Collectibles)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ERC721和加密收藏品

唷!这里的气氛开始升温了……
在这节课中,我们将更深入一些。
我们将讨论代币、ERC721标准和加密可收集资产。
换句话说,我们要做的是让你可以和朋友交换僵尸。

一、以太坊上的代币

我们来谈谈代币。
如果你在以太坊领域呆过一段时间,你可能听过人们谈论代币——特别是ERC20代币
以太坊上的token基本上只是一个遵循一些通用规则的智能合约,即它实现了所有其他令牌合约共享的一组标准函数,例如transferFrom(addres_from, addres_to, uint256 _amount)balanceOf(addres_owner)
在内部,智能合约通常有一个映射,映射(address=> uint256)余额,跟踪每个地址有多少余额。

基本上,代币只是一个合约,它记录了谁拥有多少代币,以及一些功能,以便这些用户可以将他们的代币转移到其他地址

它为什么重要

由于所有ERC20令牌共享具有相同名称的相同函数集,因此它们都可以以相同的方式进行交互。
这意味着如果您构建的应用程序能够与一个ERC20令牌进行交互,那么它也能够与任何ERC20令牌进行交互。这样就可以在未来轻松地将更多令牌添加到应用程序中,而无需自定义编码。你可以简单地插入新的令牌合约地址,然后,你的应用程序就有了另一个可以使用的令牌。

其中一个例子就是交易所。当交易所添加新的ERC20令牌时,实际上它只需要添加另一个与之对话的智能合约。用户可以告诉合约将代币发送到交易所的钱包地址,而交易所可以告诉合约在用户请求提现时将代币发送回给用户。
交易所只需要实现此传输逻辑一次,然后当它想要添加新的ERC20令牌时,只需将新的合约地址添加到其数据库中即可。

其他token标准

ERC20代币对于像货币一样的代币来说真的很酷。但在我们的僵尸游戏中,它们并不能代表僵尸。
首先,僵尸不像货币那样可分割——我可以给你0.237个ETH,但给你0.237个僵尸真的没有意义。
其次,并非所有僵尸都生来平等。你的2级僵尸“Steve”完全不等于我的732级僵尸“H4XF13LD MORRIS💯💯😎💯💯”。(差远了,史蒂夫)。
还有另一种代币标准,更适合像CryptoZombies这样的加密收藏品,它们被称为ERC721 token

ERC721 token是不可互换的,因为每个令牌都被认为是唯一的,并且不可分割。你只能以整个单位交易它们,每个单位都有一个唯一的ID。所以这些非常适合让我们的僵尸可以交易。

注意,使用像ERC721这样的标准有一个好处,那就是我们不必在合同中执行拍卖或托管逻辑,这些逻辑决定了玩家如何交易/出售我们的僵尸。如果我们符合规范,其他人可以为可加密交易的ERC721资产构建交换平台,而我们的ERC721僵尸将在该平台上可用。因此,使用代币标准而不是滚动自己的交易逻辑有明显的好处

实战演习

我们将在下一章深入研究ERC721的实现。但首先,让我们为本课设置文件结构。
我们将把所有ERC721逻辑存储在一个名为ZombieOwnership的合约中。
译文:

  1. 在文件的顶部声明我们的pragma版本(查看前面课程的文件以了解语法)。
  2. 这个文件应该从zombieattack.sol中导入。
  3. 声明一个新的合约,ZombieOwnership,继承自ZombieAttack。暂时把合同的主体部分空着。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";

contract ZombieOwnership is ZombieAttack {

}

ERC721标准,多重继承

我们来看一下ERC721标准:

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

  function balanceOf(address _owner) external view returns (uint256);
  function ownerOf(uint256 _tokenId) external view returns (address);
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  function approve(address _approved, uint256 _tokenId) external payable;
}

这是我们需要实现的方法列表,我们将在接下来的章节中逐个实现。
看起来很多,但不要不知所措!我们来帮你解决。

实现代币合约

在实现代币合约时,我们要做的第一件事是将接口复制到它自己的Solidity文件中并导入,import “./erc721.sol”;然后我们从它继承我们的契约,我们用函数定义覆盖每个方法。
但是等等-ZombieOwnership已经继承了ZombieAttack它怎么能继承ERC721?

幸运的是,在Solidity中,你的合约可以继承多个合约,如下所示

contract SatoshiNakamoto is NickSzabo, HalFinney {
  // Omg, the secrets of the universe revealed!
}

正如你所看到的,当使用多重继承时,你只需要用逗号,,来分隔你要继承的多个契约。在这种情况下,我们的合同继承自NickSzabo and HalFinney.

让我们试一试。

实战演习

我们已经用上面的接口创建了erc721.sol

  1. ombieownership.sol 里导入 erc721.sol
  2. 声明ZombieOwnership继承自ZombieAttackERC721

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";
// Import file here

// Declare ERC721 inheritance here
contract ZombieOwnership is ZombieAttack, ERC721 {

}

三、balanceOf & ownerOf

很好,让我们深入了解ERC721的实现!
我们已经复制了本课中将要实现的所有函数的空shell。
在本章中,我们将实现前两个方法:balanceOfownerOf

balanceOf

function balanceOf(address _owner) external view returns (uint256 _balance);

该函数只接受一个address,并返回该地址拥有的令牌数量。
在我们的例子中,我们的“代币”是僵尸。你还记得我们在DApp中存储了多少僵尸吗?

ownerOf

function ownerOf(uint256 _tokenId) external view returns (address _owner);

这个函数接受一个 token ID(在我们的例子中是一个Zombie ID),并返回拥有它的人的address
同样,这对我们来说实现起来非常简单,因为我们在DApp中已经有了一个存储这些信息的映射。我们可以在一行中实现这个函数,只需要一个return语句。

注意:记住,uint256等价于int。到目前为止,我们一直在代码中使用int,但我们在这里使用uint256,因为我们从规范中复制/粘贴。

实战演习

我将把如何实现这两个函数的问题留给您自己去解决。
每个函数应该只有一行代码,一个返回语句。看一下前几课的代码,看看我们在哪里存储这些数据。如果你想不出来,你可以点击“show me the answer”按钮寻求帮助。

  1. 实现balanceOf返回_owner拥有的僵尸数。

  2. 实现balanceOf返回_owner拥有的僵尸数。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {

  }

  function approve(address _approved, uint256 _tokenId) external payable {

  }

}

四、重构

哦哦!我们刚刚在代码中引入了一个错误,使它无法编译。你注意到了吗?
在前一章中,我们定义了一个名为ownerOf的函数。但是如果您还记得第4课,我们还在zombiefeeder .sol中创建了一个同名的修饰符ownerOf
如果你尝试编译这段代码,编译器会给你一个错误,说你不能有一个修饰符和一个同名的函数。
那么我们是否应该将ZombieOwnership中的函数名更改为其他内容?
不,我们不能那样做!!请记住,我们使用的是ERC721 token标准,这意味着其他合约将期望我们的合约具有定义了这些确切名称的函数。这就是这些标准有用的地方——如果另一个合约知道我们的合约是ERC721兼容的,它可以简单地与我们对话,而不需要知道我们内部实现决策的任何信息。
因此,这意味着我们必须重构第4课中的代码,将modifier的名称更改为其他名称

实战演习

回到zombiefeeding.sol我们要把修饰符的名字从ownerOf改成onlyOwnerOf

  1. 将修饰符定义的名称更改为onlyOwnerOf向下滚动到feedAndMultiply函数,它使用了这个modifier。我们还需要把这里的名字也改一下。

注意:我们也在zombiehelper.solzombieattack.sol中使用这个modifier。但我们不会花太多时间在重构上,我们已经为你改变了这些文件中的修饰符名。

合约修改

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;

  modifier onlyOwnerOf(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);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) internal onlyOwnerOf(_zombieId) {
    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");
  }
}

五、ERC721: Transfer Logic

太好了,我们解决了冲突!
现在,我们将通过将所有权从一个人转移到另一个人来继续我们的ERC721实现。
请注意,ERC721规范有两种不同的方式来传输tokens

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

function approve(address _approved, uint256 _tokenId) external payable;

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  1. 第一种方法是令牌的所有者调用transferFrom,使用他的地址作为_from参数,他想要传输的地址作为_to参数,以及他想要传输的令牌的_tokenId
  2. 第二种方法是令牌的所有者首先使用他想要转移到的地址和_tokenID调用approve。然后合约存储谁被批准接受令牌,通常在mapping (uint256 => address).中。然后,当所有者或批准的地址调用transferFrom时,合约检查msg.sender 是否是所有者,或者被所有者批准接受令牌,如果是这样,它将令牌转移给他。

注意,这两个方法包含相同的传输逻辑。在一种情况下,令牌的发送方调用transferFrom函数;在另一种情况下,令牌的所有者或批准的接收者调用它。
因此,将这个逻辑抽象到它自己的私有函数_transfer中是有意义的,然后由transferFrom调用该函数。

实战演习

让我们定义_transfer的逻辑。

  1. 定义一个名为_transfer的函数。它将接受3个参数,addres_from, addres_touint256 _tokenId。这应该是private函数。
  2. 当所有权发生变化时,我们有2个映射会发生变化:ownerZombieCount(跟踪所有者拥有多少僵尸)和zombieToOwner(跟踪谁拥有什么)。
    我们的函数应该做的第一件事是为接收僵尸的人增加ownerZombieCount (addres_to)。用++递增。
  3. 接下来,我们需要减少发送僵尸的人(addres_from)的ownerZombieCount。使用--来递减。
  4. 最后,我们想要改变这个_tokenIdzombieToOwner映射,所以它现在指向_to
  5. 我们还有一件事要做。
    ERC721规范包括一个Transfer事件。这个函数的最后一行应该用正确的信息触发Transfer 检查erc721。看看它期望用什么参数被调用并在这里实现它。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  // Define _transfer() here
  function _transfer(address _from, address _to, uint256 _tokenId) private{
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {

  }

  function approve(address _approved, uint256 _tokenId) external payable {

  }
}

六、ERC721:转移控制

太棒了!这是比较困难的部分——现在实现外部transferFrom函数会很容易,因为_transfer函数已经完成了几乎所有繁重的工作。

实战演习

  1. 首先,我们要确保只有token/僵尸的所有者或批准的地址才能传输它。让我们定义一个名为zombieApprovals的映射。它应该把一个int映射到一个address。这样,当非所有者使用_tokenId调用transferFrom时,我们可以使用此映射快速查找他是否被批准使用该token。
  2. 接下来,让我们给transferFrom添加一个require语句。它应该确保只有令牌/僵尸的所有者或批准的地址才能传输它。
  3. 最后,不要忘记调用_transfer

注意:检查只有token/僵尸的所有者或批准的地址才能转移它,意味着必须至少满足以下条件之一:
ombieToOwner for _tokenId is equal to msg.sender
or
zombieApprovals for _tokenId is equal to msg.sender

zombieApprovals映射中填充数据,我们将在下一章中完成。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
    require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
    _transfer(_from, _to, _tokenId);
  }

  function approve(address _approved, uint256 _tokenId) external payable {
  }


}

七、 ERC721: Approve

现在,让我们实现approve
记住,使用approve后,转账分两个步骤进行:

  1. 所有者,调用approve并给它新所有者的_approved地址,以及你想让他们接受的_tokenId
  2. 新的所有者使用_tokenId调用transferFrom。接下来,合约检查以确保新所有者已经获得批准,然后将令牌转移给他们。
    因为这发生在两个函数调用中,所以我们需要使用zombieApprovals数据结构来存储在函数调用之间谁被批准了什么。

实战演习

  1. approve函数中,我们希望确保只有令牌的所有者可以批准某人使用它。所以我们需要添加onlyOwnerOf修饰符来批准
  2. 对于函数体,设置_tokenIdzombieApprovals等于_approved的地址。

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
    require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
    _transfer(_from, _to, _tokenId);
  }

  function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _approved;
  }


}

八、ERC721: Approve

太好了,我们差不多完成了!
还有一件事要做——ERC721规范中有一个Approval事件。所以我们应该在approve函数的末尾触发这个事件。

实战演习

pragma solidity >=0.5.0 <0.6.0;

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

  function balanceOf(address _owner) external view returns (uint256);
  function ownerOf(uint256 _tokenId) external view returns (address);
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  function approve(address _approved, uint256 _tokenId) external payable;
}

让我们启动 Approval 事件。看看erc721.sol文件中的参数,并确保使用msg.sender作为_owner

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
    require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
    _transfer(_from, _to, _tokenId);
  }

  function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _approved;
    emit Approval(msg.sender, _approved, _tokenId);
  }
}

九、 防止溢出

祝贺你,完成了ERC721和ERC721x的实现!
也没那么难吧?当你听到人们谈论它的时候,很多以太坊的东西听起来真的很复杂,所以理解它的最好方法是自己实际实现它。
请记住,这只是一个最小的实现。我们可能想在实现中添加一些额外的功能,比如一些额外的检查,以确保用户不会意外地将他们的僵尸转移到地址0(这被称为“燃烧”一个token——基本上它被发送到一个没有人拥有私钥的地址,基本上使它无法恢复)。或者在DApp中放入一些基本的拍卖逻辑。(你能想到一些我们可以实现的方法吗?)
但是我们想让这个课程易于管理,所以我们使用了最基本的实现。如果您想查看更深入实现的示例,可以在本教程结束后查看OpenZeppelin ERC721契约。

合约安全性增强:溢出和下溢

我们将研究在编写智能合约时应该注意的一个主要安全特性:防止溢出和下溢。
什么是溢出?
假设我们有一个uint8类型,它只能有8位。这意味着我们可以存储的最大数字是二进制11111111(或者十进制,2^8 - 1 = 255)。
看看下面的代码。最后number等于多少?

uint8 number = 255;
number++;

在这种情况下,我们导致了它溢出,所以number现在等于0,尽管我们增加了它。(如果在二进制11111111上加1,它会复位为00000000,就像时钟从23:59转到00:00一样)。
下溢出是类似的,如果你从等于0uint8中减去1,它现在等于255(因为单位是无符号的,不能是负数)。
虽然我们在这里不使用uint8,而且每次增加1(2^256是一个非常大的数字)时,uint256似乎不太可能溢出,但在我们的合约中放入保护措施仍然是很好的,这样我们的DApp将来就不会有意外的行为。

使用SafeMath

为了防止这种情况,OpenZeppelin创建了一个名为SafeMath的库,默认情况下可以防止这些问题。
但在我们讨论这个之前…library是什么?
library是solid中的一种特殊类型的合约。它的用处之一是将函数附加到本机数据类型。
例如,对于SafeMath库,我们将使用使用SafeMath为uint256的语法。SafeMath库有4个函数- add, sub, mul和div。现在我们可以通过如下函数使用uint256:

使用SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

我们将在下一章中看到这些函数的作用,但是现在让我们将SafeMath库添加到我们的合约中。

实战演习

我们已经在SafeMath .sol中为您包含了OpenZeppelin的SafeMath库。如果您愿意,现在可以快速浏览一下代码,但我们将在下一章中深入研究它。
首先让我们告诉我们的合约使用SafeMath。我们将在ZombieFactory中这样做,这是我们的基本合约——这样我们就可以在继承这个合约的任何子合约中使用它。

  1. zombiefactory.sol导入 safemath.sol .
  2. SafeMathuint256添加声明;.

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  using SafeMath for uint256;

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
    require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
    _transfer(_from, _to, _tokenId);
  }

  function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _approved;
    emit Approval(msg.sender, _approved, _tokenId);
  }

}

十、SafeMath Part 2

让我们看一下SafeMath背后的代码:

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

首先是library关键字——库类似于合约,但有一些不同之处。出于我们的目的,标准库允许使用using关键字,它自动将标准库的所有方法附加到另一个数据类型:

using SafeMath for uint;
// now we can use these methods on any uint
uint test = 2;
test = test.mul(3); // test now equals 6
test = test.add(5); // test now equals 11

注意,mul和add函数都需要2个参数,但是当我们使用SafeMath for uint声明时,我们调用函数的uint (test)会自动作为第一个参数传递进来。
让我们来看看add背后的代码,看看SafeMath是做什么的:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

基本上add只是将2个单位相加,但它也包含一个assert语句,以确保总和大于a。这可以保护我们免受溢出。
Assert类似于require,如果为false则抛出错误。assert和require之间的区别在于,当函数失败时,require会退还用户剩余的gas,而assert则不会。所以大多数时候你想在代码中使用require;Assert通常在代码出现严重错误(如int溢出)时使用。
因此,简单地说,SafeMath的add、sub、mul和div都是执行基本数学操作的函数,但是如果发生溢出或下溢则 抛出错误

在代码中使用SafeMath

为了防止溢出和下溢,我们可以在代码中查找使用+、-、*或/的位置,并用add、sub、mul、div替换它们。
例:

myUint++;

替代为

myUint = myUint.add(1);

实战演习

我们在 ZombieOwnership中有2个地方使用了数学运算。让我们用SafeMath方法替换它们。

  1. Replace ++ with a SafeMath method.

  2. Replace -- with a SafeMath method.

合约修改

unction _transfer(address _from, address _to, uint256 _tokenId) private {
    // 1. Replace with SafeMath's `add`
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    //wnerZombieCount[_to]++;
    // 2. Replace with SafeMath's `sub`
    //ownerZombieCount[_from]--;
    ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);
    zombieToOwner[_tokenId] = _to;
    emit Transfer(_from, _to, _tokenId);
  }

十一、SafeMath Part 3

很好,现在我们的ERC721实现可以避免溢出和下溢了!
回顾我们在前几课中编写的代码,我们的代码中还有其他一些地方可能容易出现溢出或下溢。
例如,在ZombieAttack中,我们拥有:

myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;

为了安全起见,我们也应该防止这里的溢出。(一般来说,只使用SafeMath而不使用基本的数学运算是个好主意。也许在未来的Solidity版本中,这些功能会默认实现,但现在我们必须在代码中采取额外的安全预防措施)。
然而,我们有一个小问题 winCountlossCountuint16level是uint32。因此,如果我们使用SafeMath的add方法将这些作为参数,它实际上不会保护我们免受溢出,因为它会将这些类型转换为uint256:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

// If we call `.add` on a `uint8`, it gets converted to a `uint256`.
// So then it won't overflow at 2^8, since 256 is a valid `uint256`.

这意味着我们需要实现2个库来防止uint16uint32类型的溢出/下溢。我们可以叫它们SafeMath16和SafeMath32。
代码将与SafeMath完全相同,除了uint256的所有实例将被替换为uint32或uint16。
我们已经为你实现了这些代码-继续看看safemath。请查看代码。
现在我们需要在ZombieFactory中执行它。

实战演习

  1. 声明我们对uint32使用SafeMath32。
  2. 声明我们使用SafeMath16作为uint16类型。
  3. ZombieFactory中还有一行代码需要使用SafeMath方法。我们已经留下了评论来指出在哪里

合约修改

pragma solidity >=0.5.0 <0.6.0;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;
  using SafeMath32 for uint32;
  using SafeMath16 for uint16;

  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;
    uint16 winCount;
    uint16 lossCount;
  }

  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, uint32(now + cooldownTime), 0, 0)) - 1;
    zombieToOwner[id] = msg.sender;
    ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
    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);
  }

}

十二、SafeMath Part 4

很好,现在我们可以在DApp中使用的所有类型的单位上实现SafeMath了!
让我们修复《僵尸攻击》中的所有潜在问题。(还有一个zombies[_zombieId].level++;这需要在 ZombieHelper中修复,但我们已经为您解决了这个问题,所以我们不需要额外的章节来做😉)。

实战演习

继续在ZombieAttack中的所有增量上执行SafeMath方法。我们在代码中留下了注释,以便于查找。

合约修改

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) {
    // Here's one!
    randNonce = randNonce.add(1);
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      // Here's 3 more!
      myZombie.winCount = myZombie.winCount.add(1);
      myZombie.level = myZombie.level.add(1);
      enemyZombie.lossCount = enemyZombie.lossCount.add(1);
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else {
      // ...annnnd another 2!
      myZombie.lossCount = myZombie.lossCount.add(1);
      enemyZombie.winCount = enemyZombie.winCount.add(1);
      _triggerCooldown(myZombie);
    }
  }
}

十三、 Comments

我们僵尸游戏的Solidity代码终于完成了!
在接下来的课程中,我们将了解如何将代码部署到以太坊,以及如何使用Web3.js与之交互。
但是在我们开始第5课之前还有最后一件事:让我们讨论一下注释代码。

注释语法

在Solidity中注释就像JavaScript一样。在CryptoZombies课程中,你已经看到了一些单行注释的例子:

// This is a single-line comment. It's kind of like a note to self (or to others)

只要在任何地方加上双引号//,你就在注释了。这很容易,你应该一直做。
但我明白你的意思,有时候一句话是不够的。毕竟,你天生就是个作家!
因此我们也有多行注释:

contract CryptoZombies {
  /* This is a multi-lined comment. I'd like to thank all of you
    who have taken your time to try this programming course.
    I know it's free to all of you, and it will stay free
    forever, but we still put our heart and soul into making
    this as good as it can be.

    Know that this is still the beginning of Blockchain development.
    We've come very far but there are so many ways to make this
    community better. If we made a mistake somewhere, you can
    help us out and open a pull request here:
    https://github.com/loomnetwork/cryptozombie-lessons

    Or if you have some ideas, comments, or just want to say
    hi - drop by our Telegram community at https://t.me/loomnetworkdev
  */
}

特别是,注释代码以解释合约中每个函数的预期行为是一种很好的做法。这样,其他开发人员(或者你,在项目中断6个月之后!)就可以快速浏览和理解你的代码做了什么,而不必阅读代码本身。
Solidity社区的标准是使用一种叫做natspec的格式,它看起来是这样的

/// @title A contract for basic math operations
/// @author H4XF13LD MORRIS 💯💯😎💯💯
/// @notice For now, this contract just adds a multiply function
contract Math {
  /// @notice Multiplies 2 numbers together
  /// @param x the first uint.
  /// @param y the second uint.
  /// @return z the product of (x * y)
  /// @dev This function does not currently check for overflows
  function multiply(uint x, uint y) returns (uint z) {
    // This is just a normal comment, and won't get picked up by natspec
    z = x * y;
  }
}

@title@author很简单。
@notice向用户解释合约/函数的作用。@dev用于向开发人员解释额外的细节。
@param@return用于描述函数的每个参数和返回值的作用。
注意,您不必总是为每个函数使用所有这些标记—所有标记都是可选的。但至少,要留下@dev注释,解释每个函数的作用。

实战演习

如果你现在还没有注意到,CryptoZombies答案检查器在检查你的答案时会忽略评论。所以我们实际上不能检查这一章的natspec代码;)
然而,到目前为止,你是一个坚实的能手-我们只是假设你有这个!
尝试一下,并尝试添加一些natspec标签到ZombieOwnership:

  1. @title -例如,管理僵尸所有权转移的合约
  2. @author -你的名字!
  3. @dev -例如:符合OpenZeppelin对ERC721规范草案的实现

十四、 结束演讲

恭喜你!第五课结束了。
作为奖励,我们已经转移了你自己的10级H4XF13LD莫里斯💯💯😎💯💯僵尸!
(Omg,传说中的H4XF13LD MORRIS💯💯😎💯💯僵尸!!!111)
现在你的军队里有4个僵尸。
在继续之前,如果您愿意,可以通过单击它们的右侧并输入新名称来重命名它们中的任何一个。(虽然我不知道为什么你会想重命名H4XF13LD MORRIS💯💯😎💯💯,这显然是有史以来最好的名字)。

我们来总结一下:

本节课我们学习了:

  • 代币、ERC721标准和可交易资产/僵尸

  • 库以及如何使用它们

  • 如何使用SafeMath库防止溢出和下溢

  • ERC721和加密收藏品(ERC721 & Crypto-Collectibles),区块链
    注释代码和natspec标准
    本课总结了我们游戏的solid代码!(现在-我们可能会在未来增加更多的课程)。
    在接下来的2节课中,我们将学习如何使用web3.js部署合约并与之交互(这样你就可以为你的DApp构建一个前端)。
    继续并重命名任何你的僵尸,如果你喜欢,然后继续到下一章完成文章来源地址https://www.toymoban.com/news/detail-838545.html

到了这里,关于ERC721和加密收藏品(ERC721 & Crypto-Collectibles)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • NFT 的基础知识:区块链上的数字艺术和收藏品

    在这张信息图中,团队探讨了 NFT 的核心基础知识,我们试图以初学者可以理解的方式解释这个概念。要更深入地了解 NFT 的功能细节,请务必查看我们关于NFT如何工作的独家专题文章。 2021 年上半年,不可替代代币 (NFT) 的销售额为 25 亿美元,高于一年前的 1370 万美元 NFT 的

    2024年01月21日
    浏览(56)
  • NFT七大种类介绍,除了艺术收藏品及游戏道具外,还能有哪些用途?

    1.艺术 此种类包含 CryptoPunks 及无聊猿 (BAYC) 等的收藏品,或是 Foundation 及 SuperRare 平台上的 1/1 数位艺术品。人们喜爱收集数位艺术品的原因与喜爱实体艺术品、时尚精品及棒球卡的原因相同 ── NFT 艺术品是美学、赞助、地位、收藏及社交的混合体。 2.音乐 音乐在 NFT 领域来

    2024年02月05日
    浏览(39)
  • ERC721标准与加密猫

    2017 年 11 月 28 日,“加密猫”游戏出现在互联网上。这是基于以太坊的 ERC721 标准(不可互换通证)发行的加密数字宠物,每一只猫咪各不相同。 加密猫的正式中文名叫“谜恋猫”,游戏的官网称这些加密猫是“可收藏、可繁殖、讨人喜欢的”。用户可以用以太币换购这种猫

    2024年01月16日
    浏览(47)
  • ERC20 | ERC-20/ERC-721/ERC-1155/ERC-3525 区别

    EIP 20 的地址:https://eips.ethereum.org/EIPS/eip-20 ERC 是 Ethereum Request for Comment 的缩写,也就是以太坊改进建议。提交 ERC 后,以太坊社区会对这个草案进行评估,最终会接受或者拒绝该建议。如果接受的话,ERC 会被确认为 EIP。 EIP 是 Ethereum Improvement Proposals 的缩写,也就是被接纳的

    2024年02月05日
    浏览(40)
  • 【ERC-721与ERC-1155有什么区别?】

    无论您是加密爱好者还是第一次使用密码,您都可能听说过以太坊。它是最具影响力的开源区块链平台之一,用于创建智能合约、加密货币和去中心化应用程序。以太坊的智能合约改变了加密货币的世界,区块链技术现在渴望成为全球多个行业的标准。 大多数区块链发烧友已

    2024年02月01日
    浏览(46)
  • Eth Of Erc20 And Erc721

    public,可以修饰变量和函数, 被修饰的函数或变量可以被任何合约调用(或访问),默认的变量和函数使用该属性。 private,可以修饰变量和函数,被修饰者只能被当前合约内部的代码所调用(或访问),不能被外部合约调用或继承它的子合约调用(或访问)。 extermal,只能修饰函数

    2024年02月15日
    浏览(43)
  • 区块链 | ERC721 标准

    目录 正文 1  ERC721 接口 事件 方法 2  ERC165 接口 3  可选实现接口:ERC721Metadata 4  可选实现接口:ERC721Enumerable 补充说明 1  NTF IDs 2  与 ERC-20 的兼容性 3  交易、挖矿、销毁 🥕 原文: 剖析非同质化代币 ERC721 标准 🥕 写在前面: 本文属搬运博客,自己留存学习。 ERC721 作为

    2024年04月28日
    浏览(38)
  • OpenZeppelin——ERC721

    ERC721是一个代币标准,ERC721官方简要解释是Non-Fungible Tokens,简写为NFT,多翻译为非同质化代币。 那怎么理解 非同质化 代币呢? 非同质化代表独一无二,以卡牌游戏为例,盲盒开出的英雄属性是随机生成,尽管职业相同,但每个英雄不一样,一个英雄对应一个TokenId,就是一个

    2024年02月22日
    浏览(52)
  • Solidity合约标准----ERC721

    非同质化token,它依赖于ERC-165 参照官方提供的案例,直接部署到remix,自动下载依赖 https://docs.openzeppelin.com/contracts/4.x/erc721 部署成功后拥有以下功能 1. 设置待测试的4个账户 2. 铸造NFT 3. 查询NFT数量 4. 依据tokenid查询NFT属主 5.部分NFT委托授权第三者 6.查询NFT是否已被授权 7.由第

    2024年02月02日
    浏览(36)
  • ERC20协议、IERC721协议

    ERC是Ethereum Request for Comments的首字母缩写。它就像技术文档,定义了适用于一群想要利用以太坊生态系统的开发者和用户的方法、行为、创新和研究。ERC-20介绍了在以太坊区块链上创建可互换代币的代币标准,在该协议下的相同的代币完全一致。 / SPDX-License-Identifier: MIT // Op

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包