在我上一篇文章中,我们开始编写ERC721合同。 在前三篇文章( 第1部分, 第2部分 , 第3部分 )中有很多序言和解释,所以我会假设你在家中玩耍并直接跳回到代码中。
代码!
在上一篇文章的最后,在我的burnToken
函数中,您可能注意到我引用了我们在合同开始时声明的两个映射, allowance
和authorised
。 这两个都用于管理批准,这基本上是一个系统,用于控制您的令牌给其他人,同时保留它的所有权。
有两种形式的批准 ,一种授予某人权限来控制一个特定的令牌 ( allowance
) ,另一种授予他们控制所有令牌( authorised
)的authorised
。 这两种批准都有自己的功能,并且它们的工作方式彼此不同,因此我们将分别进行介绍。
合法
授权在ERC721标准中由两个功能描述,
函数 setApprovalForAll( 地址 _operator, 布尔 _批准) 外部 ;
它设置谁有权控制所有令牌(让这些人员操作员 ),以及
函数 isApprovedForAll( 地址 _owner, 地址 _operator) 外部视图返回 ( bool ){ 返回授权[_owner] [_运营商]; }
它读取数据。 这是我们在合同开始时宣布的authorised
映射派上用场的地方。 如果你忘记了,那个映射被声明为:
映射 ( 地址 => 映射 ( 地址 => 布尔 )) 内部授权;
如果你以前从未见过嵌套映射,那实际上很简单。 在这种情况下,每个拥有者地址映射到它自己的映射,映射将操作员地址 es映射到bool,说明它们是否实际上是操作员。
所以从这个嵌套映射读取只需要以下结构,
授权[所有者] [操作]
如果operator
是owner
的操作员,则评估为真 ,否则为false 。
事实上,这正是我们的isApprovedForAll
函数的工作原理,它只是从这个映射中返回一个值:
函数 isApprovedForAll( 地址 _owner, 地址 _operator) 外部视图返回 ( bool ){ 返回授权[_owner] [_运营商]; }
同样,我们的setApprovalForAll
函数只是在这个映射中设置一个值。 唯一的另一个要求是我们的函数“发出ApprovalForAll事件” ,它在标准接口中定义为
事件 ApprovalForAll( 地址索引_owner, 地址索引_operator, 布尔 _批准);
所以我们的setApprovalForAll
函数很简单,
函数 setApprovalForAll( 地址 _operator, 布尔 _批准) 外部 { 发出 ApprovalForAll(msg.sender,_operator,_approved); 授权[msg.sender] [_运营商] = _批准; }
津贴
津贴也由两个功能来描述,
功能批准( 地址 _approved, uint256 _tokenId) 外部应付款 ;
其中设置了谁被批准用于给定的令牌,并且
函数 getApproved( uint256 _tokenId) 外部视图返回 ( 地址 );
读取这些数据。 最重要的是要注意的是,对于任何给定的令牌,在任何给定时间只能有一个授权地址。 如果您为某个令牌批准某人,然后批准其他人,则第一个人将不再被授权使用该令牌。
与我们处理运营商的authorised
映射类似,我们authorised
处理单令牌批准。 刷新你的记忆:
映射 ( uint256 => 地址 ) 内部补贴;
这里没有花哨的技巧,它只是每个tokenId
到其认可地址的映射。 如果它没有一个,它的计算结果为0x0
。
我们的getApproved
函数的两个要求是, 如果 _tokenId
不是有效的NFT ,它会抛出 ,并返回“此NFT的批准地址或无地址的零地址” ,这给了我们:
函数 getApproved( uint256 _tokenId) 外部视图返回 ( 地址 ){ 要求(isValidToken(_tokenId)); 返回津贴[_tokenId]; }
为了取得批准,我们有我们的approve
功能。 请注意,我已将可变性从应付款更改为隐式不付款 - 我也在界面副本中进行了相同的更改。 我们不需要从这个功能付款。
函数批准( 地址 _approved, uint256 _tokenId) external {
该标准对于我们的approve
功能具有以下条件,
“除非`msg.sender`是当前的NFT所有者或当前所有者的授权运营商,否则会抛出。”
我们首先获取ownerOf
的结果并将其存储在一个临时变量中。 如果你从前面记得, ownerOf
比读取变量稍微复杂一点,所以这比每次我们需要所有者地址时重新调用ownerOf
便宜。
地址所有者= ownerOf(_tokenId);
那么我们根据需求检查msg.sender
是否是所有者或操作员。 请注意,我直接读取authorised
数组而不是调用isApprovedForAll
,这只是另一种节省燃料的措施。
要求 (所有者== msg.sender || 授权[所有者] [msg.sender] );
最后我们要发布一个事件 ,我们必须实际更新allowance
映射 。
发出批准(所有者,_批准,_tokenId); 津贴[_tokenId] = _批准;
传递函数
我们合同中最后也是最重要的部分是转让职能。 技术上有三种,但他们基本上都是做同样的事情,除了其中两个有一些额外的功能。 让我们从最简单的开始,
从转移
顾名思义, transferFrom
函数用于将令牌从一个地址传送到另一个地址。在我们进一步讨论之前,请注意我已将可变性从支付变为隐性不支付(因为我们不需要用这个支付),并且我还将外部公开性的可见性更改为(因为transferFrom
将被我们的其他传输功能重新使用,因此公开将节省燃气)。
函数 transferFrom( 地址 _from, 地址 _to, uint256 _tokenId) public {
接下来是这个函数的一些要求,如果它们没有被满足,会导致它抛出。 让我们开始获取令牌的owner
因为我们将在检查中使用它,而且我们的ownerOf
函数实际上包含检查tokenId
是否有效的检查,这是transferFrom
的要求之一:
地址所有者= ownerOf(_tokenId);
接下来,我们有“ 抛出,除非`msg.sender`是当前所有者,授权运营商或此NFT的批准地址”,即:
要求 (所有者== msg.sender || 津贴[_tokenId] == msg.sender || 授权[所有者] [msg.sender] );
我再次直接访问映射,而不是使用我们之前编写的批准函数,因为它更便宜。
接下来, “如果`_from`不是当前所有者 ,则抛出。”只是:
需要 (所有者== _from);
而我们的最后一个检查,“ 如果_to`抛出是零地址。” :
require (_to!= 0x0);
就我个人而言,我喜欢在做检查后立即发布任何事件 ,但是现在或最后做这些事情并不重要。
发出转移(_from,_to,_tokenId);
接下来我们更新我们的owners
映射以反映新的所有者,
所有者[_tokenId] = _to;
并调整_to
和_from
地址的余额,
结余[_from] - ; 结余[_to] ++;
请注意,在这种情况下不需要SafeMath,我们的合约逻辑可确保没有任何余额为0的人可以达到此点,这意味着他们无法溢出余额。
剩下的唯一要做的就是重置这个令牌的allowance
。 现在令牌已经转移,新的所有者需要决定谁能控制它。
if (allowance [_tokenId]!= 0x0){ 删除津贴[_tokenId]; }
Transfer
事件意味着我们重置了配额,所以不需要发出Approval
事件 。
safeTransferFrom
safeTransferFrom
函数与transferFrom
几乎相同,只是它们检查收件人是否是有效的ERC721接收方合同,如果是,则让您将某些数据传递给该合同。这个函数有3个参数的版本和4个参数的版本,但是3个参数只是调用4个参数,最后一个参数为空,所以我们将从4个参数开始。
再次注意,我已经将外部应付款的可变性和可见性修改为公开的和隐含的不付款,这是因为气体原因,并且我们不需要付款。
函数 safeTransferFrom( 地址 _from, 地址 _to, uint256 _tokenId, 字节数据) public {
我们要做的第一件事就是调用我们的transferFrom
函数,这将对我们执行大部分检查并转移令牌。
transferFrom(_from,_to,_tokenId);
接下来我们必须满足safeTransferFrom
的附加条件,
传输完成后,此函数检查`_to`是否是智能合约(代码大小> 0)。 如果是这样,它调用`_to`上的`onERC721Received`,并在返回值不是`bytes4(keccak256(“onERC721Received(address,uint256,bytes)”))`时抛出。
我们将不得不在这里使用一些程序集 ,幸运的是有一个操作码可以返回给定地址的代码大小。 我从来没有使用过汇编,因为所有这一切都是在检查_to
的代码大小并将其存储在size
。 然后我们安全地回到Solidity的界限。
uint32大小; 组件 { size:= extcodesize(_to) }
如果大小为0,则该地址不属于合同。 但是如果确实如此,我们需要在本合同上致电onERC721Received
,并要求它给我们正确的回应:
if (size> 0){ ERC721TokenReceiver接收器= ERC721TokenReceiver(_to); require(receiver.onERC721Received(_from,_tokenId,data)== bytes4 (keccak256(“onERC721Received(address,uint256,bytes)”))); }
这是transferFrom
和safeTransferFrom
之间的唯一区别。 唯一剩下的就是3参数版本,它只是将最终参数的4参数版本调用为空字符串。 再次注意到我已将可变性从支付变更为隐式不支付:
函数 safeTransferFrom( 地址 _from, 地址 _to, uint256 _tokenId) 外部 { safeTransferFrom(_from,_to,_tokenId, “”); }
包起来
瞧! 您刚完成编写您自己的ERC721实施合同,很不错! 如果您没有在家玩过, 可以在我的GitHub上找到这份合同的副本供您使用。 我希望你到目前为止已经发现了这个系列的信息,并且不要失望,因为还有更多!
下一次我们将测试我们的合同,因为如果代码没有经过测试,代码就不好,我也将在以后的文章中运行元数据和枚举扩展。
https://medium.com/coinmonks/jumping-into-solidity-the-erc721-standard-part-4-ad21e3a5d9c