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
1,我擁有的token
// Mapping from owner to list of owned token IDs。例如:_ownedTokens[owner],值是一個數組[index1, indexN] mapping(address => uint256[]) private _ownedTokens; // Mapping from token ID to index of the owner tokens list。_ownedTokensIndex存儲 token id => 索引值index,即這個 token id在我的token列表中的順序位置。 mapping(uint256 => uint256) private _ownedTokensIndex;
2,所有tokens // Array with all token ids, used for enumeration。所有的tokens,是一個數組,索引是token id uint256[] private _allTokens; // Mapping from token id to position in the allTokens array。在 _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);
// 返回我的余額 function balanceOf(address owner) external view returns (uint256 balance); // 返回token的owner function ownerOf(uint256 tokenId) external view returns (address owner);
// 將NFT的所有權從一個地址轉移到另一個地址。// @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}),tokenID function safeTransferFrom(address from, address to, uint256 tokenId) external;
// 授權轉賬,從from到同。附加額外的參數(沒有指定格式),傳遞給接收者。 function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
// 對to沒有要求。本方法和上面方法都觸發{Transfer} event function transferFrom(address from, address to, uint256 tokenId) external; // 給to授權執行transfer給另一個account。觸發{Approval} event. function approve(address to, uint256 tokenId) external; // 返回tokenId,已被approve的account function 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;).