ERC721的Openzeppelin实现
October 19th, 2021

ERC-721是以太坊上非同质化货币的官定标准,目前几乎所以的NFT都遵循这个标准来实现,只有少数NFT使用了ERC-1551标准。而Openzeppelin是Opensea开源的一个合约代码库,实现了大部分官方建议的标准合约,例如ERC-20、ERC-721、ERC1551等。当开发者想开发自己的数字货币或者NFT时,都会先考虑到使用Openzeppelin的实现方案。

ERC-721标准

          ERC-721最初是由Dete编写的一份EIP草案(<https://eips.ethereum.org/EIPS/eip-721>),最初是在Axiom Zen的CryptoKitties(加密猫)项目中实现。目前ERC-721已经脱离了beta版,进入了社区的正式标准,并得到了来自整个加密生态系统的大量项目的支持。简单来说,基于这种标准所生产的货币,就是我们常说的NFT。      首先来看一下ERC-721的官方定义接口有哪些:    
interface ERC721 /* is ERC165 */ {
	event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
     event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
     event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

     function balanceOf(address _owner) external view returns (uint256);
     function ownerOf(uint256 _tokenId) external view returns (address);
     function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
     function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
	function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
	function approve(address _approved, uint256 _tokenId) external payable;
	function setApprovalForAll(address _operator, bool _approved) external;
	function getApproved(uint256 _tokenId) external view returns (address);
    可以看到接口中定义的事件只有三个,分别是Transfer、Approval以及ApprovalForAll,其中ApprovalAll是允许操作者使用全部NFT。定义的函数有八个,分别是balanceOf、ownerOf、safeTransferFrom、transgerFrom、approve、setApprovalForAll、getApproved。其中safeTransferFrom是相对ERC-20中的新增方法。官方文档解释编写这个函数的目的,见图1:
图1.官方文档对safeTransferFrom的解释
图1.官方文档对safeTransferFrom的解释

SafeTransferFrom大意是说当transfer完成之后,这个函数会检查目标地址是否为合约地址,如果是的话会调用该合约上的onERC721Ercevied 方法,并返回值。这样来防止转移到一个不支持NFT的合约上,避免NFT掉入黑洞中。

Openzeppelin如何实现ERC-721

首先开门见山的看一下Openzeppelin的目录结构,如图2:

图2.Openzeppelin中ERC721的目录结构
图2.Openzeppelin中ERC721的目录结构
  • IERC721.sol拷贝了一份官方对ERC721的借口定义;
  • ERC721实现了接口中的方法;
  • IERC721Recevier.sol定义了接受NFT的合约需要实现的接口;
  • extensions里面定义了基于ERC721的扩展方法的接口以及实现,例如:销毁、枚举、暂缓交易、元数据;
  • presets中的ERC721PresetMinterPauserAutoId.sol继承了extensions中的合约,实现了角色管理,定义minter和Pauser的功能;
  • utils中的ERC721Holder.sol是IERC721Recevier.sol的实现;
  在这里可以发现,Openzeppelin为NFT开发者提供了相当全面的工具类,想了解的读者可以去https://docs.openzeppelin.com/contracts/api/token/erc721中查看更多细节。

  接下来我们学习一下ERC721.sol的设计与实现。合约第一部分定义了地址、名称、标识、所有者映射关系等变量,以及初始化方法constructor。
图3.合约第一部分
图3.合约第一部分
合约第二部分依次实现了ERC-721的方法,下图是balanceOf方法的实现。逻辑是先检查owner地址是否为空,非空在balcances的Map映射中查询地址(key)对应的value并返回。
图4.合约第二部分
图4.合约第二部分
合约第三部分是实现一些ERC-721标准之外的,但是NFT在运行中约定俗成必须有的功能,例如下图中的mint方法。其逻辑是先检查地址&tokenID是否为空,非空执行mint。注意到这里,具体的mint方法需要项目开发人员按照项目的本身开发,也就是并未给到公开通用的mint方法。
图5.合约第三部分
图5.合约第三部分
还有一个比较重要的功能是枚举,目的是给用户提供一个快速查询 NFT 的方法。接口设计上是让用户可以根据用户自己的索引查询所拥有的 NFT 对应的 tokenId,另一个是根据索引查询合约中的 NFT 的 tokenId, 然后是总的供给量查询,很多的 NFT 合约的总供给量反应的是现在所有的 NFT 的数量。简单来讲就是维护两个索引,一个索引用来索引整个合约中的 NFT,另一个索引是用来索引用户所拥有的 NFT。这个功能被Openzeppelin单独拿出来开发,没有放在ERC-721.sol的主体文件里。

参考文档:

[1] EIP-721: Non-Fungible Token Standard (ethereum.org): https://eips.ethereum.org/EIPS/eip-721

[2] ERC-721: http://erc721.org/

[3] EIP-721&Openzeppelin: https://www.mytokencap.com/news/337928.html

[4] Openzeppelin-API:https://docs.openzeppelin.com/contracts/api/token/erc721

Subscribe to satoshi
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from satoshi

Skeleton

Skeleton

Skeleton