- A+
EIP 721: ERC-721 非同质化代币标准
参考:https://learnblockchain.cn/docs/eips/eip-721.html
ERC721 合约重要信息:
// Mapping from token ID to owner
mapping(uint256 => address) private _tokenOwner;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to number of owned token
mapping(address => Counters.Counter) private _ownedTokensCount;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
批注:第一个address是owner,第二个address是operator,即 _operatorApprovals[owner][operator]
增发:
/** * @dev Internal function to mint a new token. * Reverts if the given token ID already exists. * @param to The address that will own the minted token * @param tokenId uint256 ID of the token to be minted */
function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _tokenOwner[tokenId] = to; _ownedTokensCount[to].increment(); emit Transfer(address(0), to, tokenId); }
ERC721Enumerable
mapping(address =>mapping(uint256 =>_allTokens,即 某一个token id => _allTokens 数组中的位置(索引值)
mapping(uint256 => uint256) private _allTokensIndex;
看完了erc721,有如下几个要点:
erc165
erc721主合约,一堆变量,还包含了mint和burn
erc721枚举Enumerable,一堆变量,还包含了_removeTokenFromOwnerEnumeration(address from, uint256 tokenId),_removeTokenFromAllTokensEnumeration(uint256 tokenId)
其他library库:Counters,Address,SafeMath
其他合约:ERC721MetaData,ERC721Receiver,ERC721WithCopyright,Ownerable,Context
接口721
// 当任何NFT的所有权更改时(不管哪种方式),就会触发此事件。
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
// 当更改或确认NFT的授权地址时触发。
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
// 所有者启用或禁用操作员时触发。(操作员可管理所有者所持有的NFTs)
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// @dev 如果`msg.sender` 不是当前的所有者(或授权者)抛出异常 // 如果 `_from` 不是所有者、`_to` 是零地址、`_tokenId` 不是有效id 均抛出异常。 // 当转移完成时,函数检查 `_to` 是否是合约,如果是,调用 `_to`的 `onERC721Received` 并且检查返回值是否是 `0x150b7a02` (即:`bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`) 如果不是抛出异常。
// from(必须{approve} or {setApprovalForAll}),to(必须实现{IERC721Receiver-onERC721Received}),tokenIDfunction safeTransferFrom(address from, address to, uint256 tokenId) external;
// 授权转账,从from到同。附加额外的参数(没有指定格式),传递给接收者。 function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
// 对to没有要求。本方法和上面方法都触发{Transfer} eventfunction transferFrom(address from, address to, uint256 tokenId) external;// 给to授权执行transfer给另一个account。触发{Approval} event.function approve(address to, uint256 tokenId) external;// 返回tokenId,已被approve的accountfunction getApproved(uint256 tokenId) external view returns (address operator);// Approve or remove `operator` as an operator for the caller.function setApprovalForAll(address operator, bool _approved) external;// 返回operator是否被允许管理owner的所有资产。function isApprovedForAll(address owner, address operator) external view returns (bool);
如果合约要接收NFT的安全转账,必须实现以下接口:
(ERC721智能合约在`transfer`完成后,在接收这地址上调用这个函数。 函数可以通过revert 拒绝接收。返回非`0x150b7a02` 也同样是拒绝接收。)
/// @dev 按 ERC-165 标准,接口id为 0x150b7a02.interface ERC721TokenReceiver { /// @notice 处理接收NFT /// @dev ERC721智能合约在`transfer`完成后,在接收这地址上调用这个函数。 /// 函数可以通过revert 拒绝接收。返回非`0x150b7a02` 也同样是拒绝接收。 /// 注意: 调用这个函数的 msg.sender是ERC721的合约地址 /// @param _operator :调用 `safeTransferFrom` 函数的地址。 /// @param _from :之前的NFT拥有者 /// @param _tokenId : NFT token id /// @param _data : 附加信息 /// @return 正确处理时返回 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); }
We questioned if the operator
parameter on onERC721Received
was necessary.
In all cases we could imagine, if the 操作员 was important then that 操作员 could transfer the token to themself and then send it -- then they would be the from
address.
This seems contrived(adv 预谋的;不自然的;人为的;矫揉造作的;做作的) because we consider the 操作员 to be a temporary owner of the token (and transferring to themself is redundant).
When the 操作员 sends the token, it is the 操作员 acting on their own accord, NOT the 操作员 acting on behalf of(代表) the token holder.
This is why the 操作员 and the previous token owner are both significant to the token recipient.
以下元信息扩展是可选的(查看后面的“说明”部分),但是可以提供一些资产代表的信息以便查询。
/// @title ERC-721非同质化代币标准, 可选元信息扩展/// @dev See https://learnblockchain.cn/docs/eips/eip-721.html/// Note: 按 ERC-165 标准,接口id为 0x5b5e139f.interface ERC721Metadata /* is ERC721 */ { /// @notice NFTs 集合的名字 function name() external view returns (string _name); /// @notice NFTs 缩写代号 function symbol() external view returns (string _symbol); /// @notice 一个给定资产的唯一的统一资源标识符(URI) /// @dev 如果 `_tokenId` 无效,抛出异常. URIs在 RFC 3986 定义, /// URI 也许指向一个 符合 "ERC721 元数据 JSON Schema" 的 JSON 文件 function tokenURI(uint256 _tokenId) external view returns (string); }
以下是 "ERC721 元数据 JSON Schema" 描述:
{ "title": "Asset Metadata", "type": "object", "properties": { "name": { "type": "string", "description": "指示NFT代表什么" }, "description": { "type": "string", "description": "描述NFT 代表的资产" }, "image": { "type": "string", "description": "指向NFT表示资产的资源的URI(MIME 类型为 image/*) , 可以考虑宽度在320到1080像素之间,宽高比在1.91:1到4:5之间的图像。 } } }
以下枚举扩展信息是可选的(查看后面的“说明”部分),但是可以提供NFTs的完整列表,以便NFT可被发现。
/// @title ERC-721非同质化代币标准枚举扩展信息/// @dev See https://learnblockchain.cn/docs/eips/eip-721.html/// Note: 按 ERC-165 标准,接口id为 0x780e9d63.interface ERC721Enumerable /* is ERC721 */ { /// @notice NFTs 计数 /// @return 返回合约有效跟踪(所有者不为零地址)的 NFT数量 function totalSupply() external view returns (uint256); /// @notice 枚举索引NFT /// @dev 如果 `_index` >= `totalSupply()` 则抛出异常 /// @param _index 小于 `totalSupply()`的索引号 /// @return 对应的token id(标准不指定排序方式) function tokenByIndex(uint256 _index) external view returns (uint256); /// @notice 枚举索引某个所有者的 NFTs /// @dev 如果 `_index` >= `balanceOf(_owner)` 或 `_owner` 是零地址,抛出异常 /// @param _owner 查询的所有者地址 /// @param _index 小于 `balanceOf(_owner)` 的索引号 /// @return 对应的token id (标准不指定排序方式) function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); }
说明
Solidity 0.4.20 接口语法不足以表达 ERC-721 标准文档,ERC-721标准的合约还必须遵守以下规则:
-
Solidity issue #3412: 上面的接口为每个函数包括明确的可变性声明。 可变性声明从弱到强依次为:
payable
,隐含不可支付,view
和pure
。 实现必须满足此接口中的可变性声明,或使用更强的可变性声明。 例如,接口中的payable
函数可以在合约中实现为不可支付(即未指定)。 -
Solidity issue #3419: 实现
ERC721Metadata
和ERC721Enumerable
接口同时也需要实现ERC721
, ERC-721 要求实现ERC-165接口。 -
Solidity issue #2330: 标记为
external
的函数也可以使用public
可见性。
原理:
在这些情况下,这些项目不能像账本中的数字那样“集中”在一起,而是每个资产必须单独和原子地跟踪所有权。不管资产的性质如何,如果具有允许跨职能资产管理和销售平台的标准化接口,那么生态系统将变得更加强大。
NFT 身份ID
每个NFT都由ERC-721智能合约内部的唯一 uint256
ID标识。 该识别码在整个协议期内均不得更改。 (合约地址, tokenId)
对将成为以太坊链上特定资产的全球唯一且完全合格的标识符。 尽管某些ERC-721智能合约可能会方便地以ID为0起始并为每个新的NFT加1,但调用者不得假设ID号具有任何特定的模式,并且必须将ID视为“黑匣子” 。 另请注意,NFT可能会变得无效(被销毁)。 请参阅支持的枚举接口的枚举功能。
由于UUIDs和sha3哈希可以直接转换为 uint256
,因此使用 uint256
可实现更广泛的应用。
ERC165:
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
就是一种发布并能检测到一个智能合约实现了什么接口的标准
这么做的原因:
it is sometimes useful to query whether a contract supports the interface and if yes, which version of the interface, in order to adapt the way in which the contract is to be interacted with
如何检测合约是否实现了 ERC-165
-
在合约地址上使用附加数据(input data)
0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
和 gas 30,000 进行STATICCALL
调用,相当于contract.supportsInterface(0x01ffc9a7)
。 -
如果调用失败或返回false , 说明合约不兼容ERC-165标准
-
如果返回true,则使用输入数据
0x01ffc9a7ffffffff000000000000000000000000000000000000000000000000000000000000进行第二次调用
。 -
如果第二次调用失败或返回true,则目标合约不会实现ERC-165。
-
否则它实现了ERC-165。
#如何检测合约是否实现了某个接口
-
如果不确定合约是否实现ERC-165,请使用上面的方法进行确认。
-
如果没有实现ERC-165,那么你将不得不看看它采用哪种老式方法。
-
如果实现了ERC-165,那么只需调用
supportsInterface(interfaceID)
来确定它是否实现了对应的接口。
https://learnblockchain.cn/docs/eips/eip-165.html#%E7%AE%80%E8%A6%81%E8%AF%B4%E6%98%8E
https://www.cnblogs.com/wanghui-garcia/p/9507128.html
SafeERC20合约,主要增加了一个方法:_callOptionalReturn
模仿high-level调用,实际是一个底层内测调用方法。
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
我们需要在这里执行一个低级调用,绕过Solidity的返回数据大小检查机制,因为我们是自己实现的。
我们使用{Address.functionCall}来执行这个调用,它验证目标地址是否包含合约代码,并断言在低级调用中是否成功。
在0.6.0,新增关键字 virtual
和 override 含义
答案:The purpose of these keywords is to be more explicit when overriding a function.
Base functions can be overridden by inheriting contracts to change their behavior if they are marked as virtual
.
The overriding function must then use the override
keyword in the function header.
pragma experimental ABIEncoderV2;
This feature came in Solidity version 0.4.19, which states:
Code Generator: New ABI decoder which supports structs and arbitrarily nested arrays and checks input size (activate using pragma experimental ABIEncoderV2;).