ERC721:全生命周期精析,妈妈再也不用担心我不会玩NFT合约啦
由于篇幅有限,本博客将围绕ERC721核心展开介绍,文章内容尽量做到通俗易懂,但其中不可避免地可能涉及一些新手不友好的概念,您可以查阅相关博客做进一步了解,本系列博客也会不断扩充、提升及优化,尽量做到不留死角,人人都能上手Solidity标准开发。
0. ERC 是什么鬼?
ERC 全称 Ethereum Request For Comment (以太坊意见征求稿), 是以太坊上应用级的开发标准和协议(application-level standards and conventions),为以太坊开发人员提供了实施标准,开发人员可以使用这些标准来构建智能合约。
ERC 的雏形是开发人员提交的EIP(Ethereum Improvement Proposals),即新的ERC标准提案,一旦其得到以太坊委员会的批准并最终确定,新的ERC便由此诞生。
1. 初识 ERC 721
ERC 721 是同质资产 NFT 的 API 标准,提供了资产铸造、转移、归属查询、授权第三方转移等资产花费的实用接口。本文将按照 NFT 资产从诞生到历经多种不同方式的流转、最后再到被销毁这一整个生命周期,逐一介绍 ERC721 标准核心接口的含义及开发示例。由于作者经验有限,欢迎广大读者批评指正。
2. NFT 资产跌宕起伏的一生
2.0 ERC721 自画像
ERC721 总共包含四个私有映射,分别记录 NFT 的所有者、特定地址拥有的 NFT 数量、可代表 NFT 所有者操作的第三方、以及特定地址是否授权某个第三方作为操作代表。
ERC721 的核心方法是铸造、销毁、和流转。其中流转包括两种方式,安全的流转要求在接收地址是合约的情况下,接收合约实现了处理该 NFT 资产的相关接口,以免 NFT 永久的锁在接收合约中。
ERC721 共有五个查询函数,分别基于前面提到的几个映射,返回NFT 的所有者、NFT是否存在(即是否属于某个非 0 地址)、特定地址拥有的 NFT 数量、可代表 NFT 所有者操作的第三方、以及特定地址是否授权某个第三方作为操作代表。
ERC721 与授权相关的函数共有两个,分别是为特定 NFT 设置第三方操作代表和授予/取消特定地址作为所有者全部 NFT 资产的操作权限。
除此之外, ERC721 中还有几个实用函数,他们分别用于检查 NFT 资产的操作者是否具备操作权限、检查接收合约是否实现处理 NFT 资产的相关接口、以及清除可代表 NFT 所有者操作的第三方。
最后,还要介绍一下 ERC721 中的三个核心事件,他们分别标志着 NFT 资产的转移、特定 NFT 资产第三方操作的授权、 以及特定地址下所有 NFT 资产第三方操作的授权。
2.1 敲开我的蛋壳,给我找一个新家吧~
NFT 通过 _mint 函数铸造,这是一个仅可供内部调用的函数。调用时需要指定 NFT 的新家地址,以及 NFT 的专属 ID 编号(就像身份证号一样哟)。
铸造 NFT 资产的首要条件是不能将这个新铸造的 NFT 资产发往 0 地址,因为只有销毁的 NFT 资产才会被发往 0 地址。
其次是不能重复铸造已经存在的 NFT 资产,如果允许重复铸造,那么某地址拥有的 NFT 资产就会变成多人共有财产,这可不是 NFT 所有者想看到的场面。
以上两个条件都通过后,新生的 NFT 资产就会飞往它的新家,也就是被发到它所有者的地址中,相应地,其所有者拥有的 NFT 数量会增加 1 。
最后,触发一个 Transfer 转账事件,表明一个新生的 NFT 从 0 地址去往新家啦 ~
下图是调用 _mint 函数铸造一个 NFT 的实例,这是一个 public 函数,因为添加了 onlyMinter 修饰符,因此仅 minter 可以调用,该修饰符实现在合约
ERC721Mintable
中,后续会增加相应的文章对其进行详细介绍。
2.2 检查一下,新生的 NFT 资产送货到家了嘛?
2.2.1 任何人都能通过查 NFT 所有者的方式进行检查
任何人都能输入 NFT 的 ID , 使用 ownerOf 函数查询特定的 NFT 资产当前归属地址。如果该 NFT 未被铸造(即属于 0 地址),您将无法查询它的归属,否则,该函数会返回查询 NFT 资产的所有者地址。
2.2.2 这个内部方法将返回特定 NFT 是否正在流通中
该方法会检查特定 NFT 的所有者地址是不是 0 地址,如果不是,返回 true,表示该 NFT 正在流通中。
2.3 NFT 资产财富窥探器
如果您想快速了解自己或任何地址拥有的 NFT 数量,仅需调用 balanceOf 函数,并传入您想了解的地址即可。
该函数要求传入的地址是非 0 地址,并通过查 _ownedTokensCount 映射的方式,返回特定地址拥有的 NFT 数量。
2.4 让你花我的 NFT,是爱你的表现吗?可能是我懒吧~
2.4.1 我是小气鬼,先授权你花一个
任何人都可以调用 approve 函数允许其他地址花费自己特定的 NFT 资产。该函数首先检查传入 NFT 的所有者,确保不会有无聊的人授权自己花费自己的资产,接着会检查调用该函数进行授权的地址是否是该 NFT 的所有者或有操作权限的第三方,因为没有人希望自己的 NFT 资产被莫名的授权给任何人花费。
以上两个条件通过后,该 NFT 的操作权(花费权)就被正式地赋予传入地址啦~
最后,授权事件 Approval 触发,表明一个 NFT 的第三方操作权限发生变更 ~
2.4.2 TA 真的授权给我花 TA 其中的一个 NFT 了吗?
任何人都可以调用 getApproved 函数检查特定 NFT 允许被哪个地址花费。传入 NFT 资产的 ID ,该函数会返回授权花费的地址,如果返回 0 地址,则代表该 NFT 未授权任何地址花费。
2.4.3 我是土豪,我的就是你的,全让你花
任何人都可以调用 setApproveForAll 函数授权或撤销其他地址花费自己所有的 NFT 资产的权力。该函数首先要求传入地址不与调用地址相同,然后通过映射 _operatorApprovalForAll 保存授权状态,如果要授权特定地址花费调用者所有的 NFT 资产,传入授权地址和 true 即可,反之传入要撤销授权的地址和 false。
最后,批量授权事件 ApprovalForAll 触发,表明特定地址持有的全部 NFT 的第三方操作权限发生变更 ~
2.4.5 TA 真的为我倾尽所有了吗?
任何人都可以调用 isApprovedForAll 函数检查特定地址持有的全部 NFT 对第三方地址的花费授权状态。传入持有地址和第三方地址即可,返回 true 代表授权花费,反之表示未授权花费。
2.5 流通吧!NFT
2.5.1 随意地发起一笔 NFT 转移
2.5.1.1 转移入口
任何人都能通过调用 transferFrom 函数发起一笔 NFT 转移,参数分别是 NFT 的转出地址、转入地址,和 ID 。该函数首先会通过 _isApproveOrOwner 函数检查发起者是否具备转移该 NFT 的资格,如是,则通过内部函数 _transferFrom 实现 NFT 转移。
2.5.1.2 转移条件
该内部函数首先通过前面介绍过的 ownerOf 函数获得特定 NFT 的所有者,然后检查转出该 NFT 的地址是否是该 NFT 的所有者,或有操作该 NFT 权力的第三方(授权方式见2.4.2、2.4.3),如是,返回 true,表示允许转移。
2.5.1.3 转移执行
该函数要求被转移的 NFT 必须从其所有者地址处转出,并且转入一个非 0 地址。
满足以上两个条件,首先调用内部函数 _clearApproval 将有权操作该 NFT 的第三方清除,然后通过修改映射 _ownedTokensCount 的方式将转出地址的 NFT 数量 -1 、转入地址的 NFT 数量 +1 ,接着将该 NFT 的所有者地址设为转入地址,最后触发一个转账事件,表明一个流通中的 NFT 去往新家啦 ~
2.5.1.4 权限清除
由于 NFT 的所有权发生变更,其所属的操作授权方也应该重置。下图函数为一个流通中的 NFT 资产实现操作授权方的重制功能,被授权地址设为 0 地址则表示没有任何地址有权限操作该 NFT 资产。
2.5.2 安全地发起一笔 NFT 转移
2.5.2.1 沉默寡言式安全转移
安全转移总共有两种实现方式,调用的分别是两个同名函数,区别是是否传入转账说明。下图这个函数仅需传入转出地址、转入地址、和 NFT 的 ID 即可。该函数会传入一个空字符串作为转账说明,自动调用另一个需要传入转账说明的安全转移函数。
2.5.2.2 啰哩啰嗦式安全转移
接上节,安全转移函数最终会走一遍 2.5 中的流程,不同的是会在流程的最后检查一下接收地址是否是一个合约地址,如是,判断其是否实现了处理 NFT 的接口(见下节),如未实现,则交易回滚。这么做的目的是避免 NFT 资产被意外地永久锁定在某个合约当中。除此之外,附带的转账说明只用作触发事件中的参数之一(见下节),是一个可选参数,可有可无。
2.5.2.3 安全转移的安检规则
首先,该函数调用 isContract 函数判断转入地址是否是一个合约,判断方式是看该地址的 size 是否大于0,如是则说明其是一个合约,函数的实现方式详见 2.5.2.3.1,
具体的实现原理请参考这个链接
。
如果转入地址不是合约,正常转入即可,返回 true;
如是,则该函数会尝试从接收合约取一个返回值(详见 2.5.2.3.3),放在变量 retval 中,该值会跟自己合约中存储的 _ERC_RECEIVED (详见 2.5.2.3.2)做对比,如果相同,则返回 true ,代表转入合约实现了 NFT 资产的处理方法,NFT 资产可以放心地转入而不必担心被意外锁定。
2.5.2.3.1 iscontract 函数实现方式
【实现合约的github链接】
2.5.2.3.2 _ERC_RECEIVED 是什么鬼
2.5.2.3.3 宣告全世界我能接受 NFT
下图是一个可接收 NFT 资产的合约示例,合约部署时初始化 _retval 和 _reverts 两个参数,前者的值类似 2.5.2.3.2 ,后者为 false ,表示该合约能够接收 NFT 资产。当 _reverts 为 false 时,onERC721Received 函数返回 _retval 的值,供 _checkOnERC721Received 函数做比较,如果等于 2.5.2.3.2 中的值,转账正常进行,否则会滚该转账交易。
2.6 垃圾回收
不再需要的 NFT ,不能卖废品换钱,只能转让给 0 地址。(突然很想知道拥有这么多“废弃 Token”的 0 地址的财富值到底有多少)。ERC721 实现了两个仅供内部调用的 _burn 函数实现 NFT 回收。
第一个 _burn 函数只需传入 NFT 的 ID ,它会把传入的 NFT 资产的所有者地址一起传给第二个 _burn 函数以执行回收操作。
第二个 _burn 函数需要指定 NFT 资产的所有者地址和资产 ID。函数首先判断传入的 NFT 资产所有者是否正确,接着清除该 NFT 的授权操作方,详见2.5.1.4,然后将该 NFT 资产所有者拥有的 NFT 数量 -1,最后将该 NFT 资产的所有者地址设为 0 地址并触发一个 Transfer 转账事件。
下图是调用 _burn 函数回收一个 NFT 的实例,这是一个 public 函数,但仅可供传入 NFT 资产的所有者或有权第三方调用
【合约实现的Github链接】
。
至此,NFT 的从铸造、到流转、授权第三方消费及回收等全流程接口实现都已介绍完毕,你学会了嘛~