1. 程式人生 > 其它 >初探區塊鏈數字加密資產標準ERC721

初探區塊鏈數字加密資產標準ERC721

ERC721介紹

數字加密貨幣大致可以分為原生幣(coin)和代幣(token)兩大類。前者如BTC、ETH等,擁有自己的區塊鏈。後者如Tether、TRON、ONT等,依附於現有的區塊鏈。市場上流通的基於以太坊的代幣大都遵從ERC20協議。最近出現了一種被稱為ERC721的數字加密資產,例如CryptoKitties。

ERC20可能是其中最廣為人知的標準了。它誕生於2015年,到2017年9月被正式標準化。協議規定了具有可互換性(fungible)代幣的一組基本介面,包括代幣符號、發行量、轉賬、授權等。所謂可互換性(fungibility)指代幣之間無差異,同等數量的兩筆代幣價值相等。交易所裡流通的絕大部分代幣都是可互換的,一單位的幣無論在哪兒都價值一單位。

與之相對的則是非互換性(non-fungible)資產。比如CryptoKitties中的寵物貓就是典型的非互換性資產,因為每隻貓各有千秋,而且由於不同輩分的稀缺性不同,市場價格也差異巨大。這種非標準化資產很長時間內都沒有標準協議,直到2017年9月才出現ERC721提案,定義了一組常用的介面。ERC721至今仍舊處於草案階段,但已經被不少dApp採用,甚至出現了專門的交易所。

ERC721標準

下面先給出ERC721標準的具體內容,後面會講解。

每個符合ERC721標準的合約必須實現ERC721ERC165介面。

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
///  Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface ERC721 /* is ERC165 */ {
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

    /// @dev This emits when the approved address for an NFT is changed or
    ///  reaffirmed. The zero address indicates there is no approved address.
    ///  When a Transfer event emits, this also indicates that the approved
    ///  address for that NFT (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

    /// @dev This emits when an operator is enabled or disabled for an owner.
    ///  The operator can manage all NFTs of the owner.
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @notice Count all NFTs assigned to an owner
    /// @dev NFTs assigned to the zero address are considered invalid, and this
    ///  function throws for queries about the zero address.
    /// @param _owner An address for whom to query the balance
    /// @return The number of NFTs owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256);

    /// @notice Find the owner of an NFT
    /// @dev NFTs assigned to zero address are considered invalid, and queries
    ///  about them do throw.
    /// @param _tokenId The identifier for an NFT
    /// @return The address of the owner of the NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
    ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to "".
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Change or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    ///  Throws unless `msg.sender` is the current NFT owner, or an authorized
    ///  operator of the current owner.
    /// @param _approved The new approved NFT controller
    /// @param _tokenId The NFT to approve
    function approve(address _approved, uint256 _tokenId) external payable;

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all of `msg.sender`'s assets
    /// @dev Emits the ApprovalForAll event. The contract MUST allow
    ///  multiple operators per owner.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT.
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId) external view returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

如果錢包程式接受安全轉賬,它必須實現錢包介面。

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface ERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert and reject the
    ///  transfer. Return of other than the magic value MUST result in the
    ///  transaction being reverted.
    ///  Note: the contract address is always the message sender.
    /// @param _operator The address which called `safeTransferFrom` function
    /// @param _from The address which previously owned the token
    /// @param _tokenId The NFT identifier which is being transferred
    /// @param _data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    ///  unless throwing
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

ERC721介面解析

先來一波名詞解釋:

  • NFT: 非互換性資產(non-fungible token)
  • tokenId: NFT的id,型別為uint256
  • owner: NFT的擁有者,型別為address
  • balance: 使用者擁有的NFT數量,型別為uint256
  • approved: NFT的管理者,只有NFT的owner和approved可以對NFT進行操作,型別為address
  • operator:operator擁有一個使用者所有NFT的管理許可權,型別為address

接下來解釋下介面內的事件和方法:

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

當NFT被轉移時會觸發Transfer事件,在NFT被建立和銷燬時也會觸發此事件。在NFT被轉移時,他的approved會被重置為零地址。

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

當NFT的管理者,也就是approved被改變的時候,會觸發Approval事件。如果approved是零地址,說明NFT沒有管理者。

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

當NFT的owner指定一個使用者擁有他所有NFT的管理許可權時,會觸發ApprovalForAll事件。operator擁有owner所有NFT的管理許可權。

function function balanceOf(address _owner) external view returns (uint256);

查詢一個使用者擁有的NFT數量。

function ownerOf(uint256 _tokenId) external view returns (address);

查詢一個NFT的擁有者。

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

NFT的擁有者或管理者把一個NFT轉移給別人,當to是一個合約的地址時,這個方法會呼叫onERC721Received方法。

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

NFT的擁有者或管理者把一個NFT轉移給別人,這個方法有一個額外的data引數,上面那個safeTransferFrom方法會呼叫這個方法,然後把data置為""。

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

NFT的擁有者或管理者把一個NFT轉移給別人,可以看到,這個方法比起上面兩個要少了個safe,說明是不安全的轉移,轉移者要自己驗證to是不是一個正確的地址,如果不是的話,NFT可能會永久丟失。

function approve(address _approved, uint256 _tokenId) external payable;

NFT的擁有者把NFT的管理許可權授予一個使用者。

function setApprovalForAll(address _operator, bool _approved) external;

NFT的擁有者授權一個使用者是否擁有自己所有NFT的管理許可權。合約必須允許一個使用者有多個operator。

function getApproved(uint256 _tokenId) external view returns (address);

查詢一個NFT的管理者

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

查詢指定的operator是否擁有owner所有NFT的管理許可權。

function supportsInterface(bytes4 interfaceID) external view returns (bool);

查詢一個合約是否實現了一個介面。

function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);

這個方法處理NFT的轉移,當一筆轉移被髮起時,這個方法可能會被呼叫,目的是拒絕這次轉移。

ERC721的實現

Github上已經有ERC721的實現了,地址:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol

不過這是solidity的實現,並且基於公鏈,如果要基於聯盟鏈,可以看一下我的這篇文章基於Hyperledger Fabric實現ERC721

參考

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
https://zhuanlan.zhihu.com/p/112275276