erc721-165學習

ERC165:

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.mdhtml

就是一種發佈並能檢測到一個智能合約實現了什麼接口的標準git

這麼作的緣由:github

 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 withexpress

首先咱們是怎麼代表一個interface的——使用selector:數組

舉例:安全

pragma solidity ^0.4.20;

interface Solidity101 {
    function hello() external pure;
    function world(int) external pure;
}

contract Selector {
    function calculateSelector() public pure returns (bytes4) {
        Solidity101 i;
        return i.hello.selector ^ i.world.selector;
    }
}

在這個例子中有一個接口,代表一個接口的方法就是生成一個interfaceID,即(Solidity101 i;)interfaceID = i.hello.selector ^ i.world.selector,有多少個函數就並多少個函數.selectorapp

而後合約是怎麼發佈它實現的接口的:less

pragma solidity ^0.4.20;

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);
}

咱們在這裏以ERC165 token標準爲例,若是合約兼容了ERC165,那就說明該合約必定實現了上面的接口,由於它只有一個函數,因此它的(ERC165 i;)interfaceID = i.supportsInterface.selector,或者使用bytes4(keccak256('supportsInterface(bytes4)'))也同樣,結果相同ide

Therefore the implementing contract will have a supportsInterface function that returns:函數

就是說那這個兼容ERC165的合約它的supportsInterface函數裏面應該要實現的內容就是:

  由於這個合約可能不只只有兼容ERC165,可能還兼容了其餘接口,那麼它應該在interfaceID0x01ffc9a7或其餘兼容接口的interfaceID做爲它覆寫的supportsInterface函數的參數時,返回true(使用 || 判斷)

  • true when interfaceID is 0x01ffc9a7 (EIP165 interface)
  • true for any other interfaceID this contract implements

而後在interfaceID0xffffffff或者爲其餘沒有兼容的接口的interfaceID時返回false

  • false when interfaceID is 0xffffffff
  • false for any other interfaceID

This function must return a bool and use at most 30,000 gas.

好比:

pragma solidity ^0.4.20;

import "./ERC165.sol";

interface Simpson {
    function is2D() external returns (bool);
    function skinColor() external returns (string);
}

contract Homer is ERC165, Simpson {
    function supportsInterface(bytes4 interfaceID) external view returns (bool) {
        return
          interfaceID == this.supportsInterface.selector || // ERC165
          interfaceID == this.is2D.selector
                         ^ this.skinColor.selector; // Simpson
    }

    function is2D() external returns (bool){}
    function skinColor() external returns (string){}
}

這上面的這個例子就很好地解釋了合約是怎麼發佈(即告訴別人)它實現了什麼接口的

在這裏合約Homer實現了兩個接口ERC165Simpson,(由於當你兼容了一個接口時,你必定是要覆寫它的全部函數的),因此計算它的interfaceID的時候可使用this.函數名.selector來計算interfaceID,this即表明本合約(在該例子中即合約Homer

接下來就是如何檢測一個合約是否使用了某些接口:

 

  1. If you are not sure if the contract implements ERC-165, use the above procedure to confirm.
  2. If it does not implement ERC-165, then you will have to see what methods it uses the old-fashioned way.
  3. If it implements ERC-165 then just call supportsInterface(interfaceID) to determine if it implements an interface you can use.

 

1)首先,你有沒有使用ERC-165接口會致使你檢測的方法有一些不一樣,因此咱們通常先檢測某合約是否是使用了ERC-165接口,即經過使用solidity的彙編語言:

  1. The source contact makes a STATICCALL to the destination address with input data: 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000 and gas 30,000. This corresponds to contract.supportsInterface(0x01ffc9a7).
  2. If the call fails or return false, the destination contract does not implement ERC-165.
  3. If the call returns true, a second call is made with input data 0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000.
  4. If the second call fails or returns true, the destination contract does not implement ERC-165.
  5. Otherwise it implements ERC-165.
      success := staticcall(
        30000,                 // 30k gas,g使用的gas
        _address,              // To addr,a合約地址
     //in,input data,其實就是 
     //前面是合約簽名
encodedParams_data, encodedParams_size, //insize,數據大小 output, //out,輸出指針位置 0x20 // Outputs are 32 bytes long,0x20 == 32,outsize )0x01ffc9a701ffc9a7000000000000000000000000000000000000000000000000000000000x01ffc9a7,表明調用了函數supportsInterface(interfaceID),後面的即參數interfaceID,這裏爲01ffc9a7supportsInterface(interfaceID)01ffc9a7

從上面的步驟咱們能夠看見,要測試是否使用了ERC-165接口,要進行兩步測試,即先測是否使用了接口0x01ffc9a7以及是否沒有使用接口0xffffffff

2)

1》而後若是你查出該合約使用了ERC-165接口,那麼你就能夠直接調用這個合約的supportsInterface(interfaceID)函數去判斷是否使用了其餘的接口,由於通常你的supportsInterface(interfaceID)函數就是用來實現這個功能的

2》若是你發現它沒有使用ERC-165接口,那麼你就只能使用比較笨的辦法,就是人工對比查看了

因此通常你看見一個合約中使用了ERC-165接口的話,功能就是用來發布並測試使用了什麼接口

上面的過程的實現例子:

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/ERC165Checker.sol

pragma solidity ^0.4.24;


/**
 * @title ERC165Checker
 * @dev Use `using ERC165Checker for address`; to include this library
 * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
 */
library ERC165Checker {
  // As per the EIP-165 spec, no interface should ever match 0xffffffff
  bytes4 private constant InterfaceId_Invalid = 0xffffffff;

  bytes4 private constant InterfaceId_ERC165 = 0x01ffc9a7;
  /**
   * 0x01ffc9a7 ===
   *   bytes4(keccak256('supportsInterface(bytes4)'))
   */

  
  /**
   * @notice Query if a contract implements an interface, also checks support of ERC165
   * @param _address The address of the contract to query for support of an interface
   * @param _interfaceId The interface identifier, as specified in ERC-165
   * @return true if the contract at _address indicates support of the interface with
   * identifier _interfaceId, false otherwise
   * @dev Interface identification is specified in ERC-165.
   */
  //就是查看一個合約(使用合約地址address代表),是否支持某個接口(使用接口ID代表,該接口該接口是出了ERC165的supportsInterface(bytes4)即ID爲0x01ffc9a7的其餘接口)
  function supportsInterface(address _address, bytes4 _interfaceId)
    internal
    view
    returns (bool)
  {
    // query support of both ERC165 as per the spec and support of _interfaceId
   //根據上面的過程,首先先查看是否支持ERC165,而後再使用supportsInterface(bytes4)去查看是否使用了接口_interfaceId
   //這裏是&&,因此是以實現了ERC165爲前提在檢測接口的
//當你合約支持ERC165且支持ERC165的接口時,就說明你支持 return supportsERC165(_address) && supportsERC165Interface(_address, _interfaceId); } /** * @notice Query if a contract implements interfaces, also checks support of ERC165 * @param _address The address of the contract to query for support of an interface * @param _interfaceIds A list of interface identifiers, as specified in ERC-165 * @return true if the contract at _address indicates support all interfaces in the * _interfaceIds list, false otherwise * @dev Interface identification is specified in ERC-165. */ //就是查看一個合約(使用合約地址address代表),是否支持多個接口(使用接口ID代表) function supportsInterfaces(address _address, bytes4[] _interfaceIds) internal view returns (bool) { // query support of ERC165 itself if (!supportsERC165(_address)) {//從這裏就更能明顯看出,若是你沒有使用ERC165接口,就直接返回false了,因此是以實現了ERC165爲前提在檢測接口的 return false; } // query support of each interface in _interfaceIds for (uint256 i = 0; i < _interfaceIds.length; i++) { if (!supportsERC165Interface(_address, _interfaceIds[i])) { return false; } } // all interfaces supported return true; } /** * @notice Query if a contract supports ERC165 * @param _address The address of the contract to query for support of ERC165 * @return true if the contract at _address implements ERC165 */ //查看一個合約是否支持ERC165 function supportsERC165(address _address) internal view returns (bool) { // Any contract that implements ERC165 must explicitly indicate support of // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid //支持ERC165的合約都會顯示地表現它支持ERC165的supportsInterface(bytes4)函數ID而且不支持接口ID爲0xffffffff return supportsERC165Interface(_address, InterfaceId_ERC165) && !supportsERC165Interface(_address, InterfaceId_Invalid); } /** * @notice Query if a contract implements an interface, does not check ERC165 support * @param _address The address of the contract to query for support of an interface * @param _interfaceId The interface identifier, as specified in ERC-165 * @return true if the contract at _address indicates support of the interface with * identifier _interfaceId, false otherwise * @dev Assumes that _address contains a contract that supports ERC165, otherwise * the behavior of this method is undefined. This precondition can be checked * with the `supportsERC165` method in this library. * Interface identification is specified in ERC-165. */ function supportsERC165Interface(address _address, bytes4 _interfaceId) private view returns (bool) { // success determines whether the staticcall succeeded and result determines // whether the contract at _address indicates support of _interfaceId (bool success, bool result) = callERC165SupportsInterface( _address, _interfaceId); return (success && result); } /** * @notice Calls the function with selector 0x01ffc9a7 (ERC165) and suppresses throw * @param _address The address of the contract to query for support of an interface * @param _interfaceId The interface identifier, as specified in ERC-165 * @return success true if the STATICCALL succeeded, false otherwise * @return result true if the STATICCALL succeeded and the contract at _address * indicates support of the interface with identifier _interfaceId, false otherwise */
  //調用staticcall來使用supportsInterface(bytes4)函數去查看使用接口的狀況
function callERC165SupportsInterface( address _address, bytes4 _interfaceId ) private view returns (bool success, bool result) { //在使用ABI調用合約函數時,傳入的ABI會被編碼成calldata(一串hash值)。calldata由function signature和argument encoding兩部分組成。經過讀取call data的內容, EVM能夠得知須要執行的函數,以及函數的傳入值,並做出相應的操做。
  //即形如
//額外添加知識:Call Data: 是除了storage,memory的另外一個數據保存位置,保存了inputdata。長度是4bytes+32bypes*n,n爲參數個數,可經過CALLDATALOAD,CALLDATASIZE, CALLDATACOPY等指令進行操做 //首先要生成input data,一開始爲4bytes,爲函數supportsInterface(bytes4)的簽名0x01ffc9a7,而後後面爲32bypes的參數_interfaceId, bytes memory encodedParams = abi.encodeWithSelector(//對給定的參數進行ABI編碼——從第二個預置給定的四字節選擇器開始 InterfaceId_ERC165, _interfaceId );
//encodedParams = abi.encodeWithSelector(/
      0x01ffc9a7,
      0x01ffc9a6
    );
返回:0x01ffc9a70000000000000000000000000000000000000000000000000000000001ffc9a6
//solidity的彙編語言 // solium-disable-next-line security/no-inline-assembly assembly { let encodedParams_data := add(0x20, encodedParams)//由於內存前32bits存儲的是數據的長度,因此要想獲得數據,要日後32bits讀取 let encodedParams_size := mload(encodedParams)//獲得該input data的大小,由於內存存儲前32bits存儲的是數據的長度 //solidity管理內存方式:內部存在一個空間內存的指針在內存位置0x40。若是你想分配內存,能夠直接使用從那個位置的內存,並相應的更新指針。 //獲得指向空內存位置0x40的指針 let output := mload(0x40) // Find empty storage location using "free memory pointer" //mem [a ... b]表示從位置a開始到(不包括)位置b的存儲器的字節 //mstore(p, v)即mem[p..(p+32)] := v,在指針指向的內存位置後32個字節中填0,以避免在過程當中該內存位置被別的操做使用 mstore(output, 0x0) //staticcall(g, a, in, insize, out, outsize):identical to call(g, a, 0, in, insize, out, outsize) but do not allow state modifications,這個操做不會改變合約的狀態 //call(g, a, v, in, insize, out, outsize) : call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and // output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success success := staticcall( 30000, // 30k gas,g _address, // To addr,a encodedParams_data, //in encodedParams_size, //insize output, //out,指針位置 0x20 // Outputs are 32 bytes long,0x20 == 32,outsize ) //就是在地址_address(from)出調用合約,輸入是mem[in..(in+insize)),即取得input data,即調用函數後,將獲得的值放到內存位置mem[out..(out+outsize))//過程當中使用了30000 gas和0 wei result := mload(output) // Load the result,獲得調用合約獲得的結果 } } }0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000

下面還有一種更簡單的方法,使用mapping存儲使用接口狀況:

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/SupportsInterfaceWithLookup.sol

pragma solidity ^0.4.24;

import "./ERC165.sol";


/**
 * @title SupportsInterfaceWithLookup
 * @author Matt Condon (@shrugs)
 * @dev Implements ERC165 using a lookup table.
 */
contract SupportsInterfaceWithLookup is ERC165 {

  bytes4 public constant InterfaceId_ERC165 = 0x01ffc9a7;
  /**
   * 0x01ffc9a7 ===
   *   bytes4(keccak256('supportsInterface(bytes4)'))
   */

  /**
   * @dev a mapping of interface id to whether or not it's supported
   */
  mapping(bytes4 => bool) internal supportedInterfaces_;

  /**
   * @dev A contract implementing SupportsInterfaceWithLookup
   * implement ERC165 itself
   */
  constructor()
    public
  {
    _registerInterface(InterfaceId_ERC165);//函數一開始構建的時候就會將其使用的接口ID寫到supportedInterfaces_數組中
  }

  /**
   * @dev implement supportsInterface(bytes4) using a lookup table
   */
  function supportsInterface(bytes4 _interfaceId)//而後後面想要檢測接口時,直接調用數組,調用了接口返回true,不然爲false
    external
    view
    returns (bool)
  {
    return supportedInterfaces_[_interfaceId];
  }

  /**
   * @dev private method for registering an interface
   */
  function _registerInterface(bytes4 _interfaceId)
    internal
  {
    require(_interfaceId != 0xffffffff);
    supportedInterfaces_[_interfaceId] = true;
  }
}

因此erc165只是一個標準,要求如何使用一種標準的方法去發佈或者檢測(supportsInterface)一個智能合約所實現的接口
For this standard, an interface is a set of function selectors as defined by the Ethereum ABI.

ABI的相關知識:https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector

 

ERC721

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md

A standard interface for non-fungible tokens, also known as deeds.即非同質代幣的標準接口

NFTs are distinguishable and you must track the ownership of each one separately.就是每個NFT token都是第一無二,不可替代的

 

Every ERC-721 compliant contract must implement the ERC721 and ERC165 interfaces (subject to "caveats" below):

這個接口裏寫的函數並不表明這你都要用,你能夠根據本身設計的token須要用到的功能來選擇性地使用,好比若是你只須要transfer,那你就只寫safeTransferFrom()便可,,下面這個只是一個標準

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x80ac58cd.而不是咱們上面所說的0x01ffc9a7
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);
}

 

A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.

這個必定要用嗎,做用是啥

/// @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);
}

 

This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.

這個只是用於顯示一些信息,好比你token的名字(name)等,可用可不用(option)

/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata /* is ERC721 */ {
    /// @notice A descriptive name for a collection of NFTs in this contract
    function name() external view returns (string _name);

    /// @notice An abbreviated name for NFTs in this contract
    function symbol() external view returns (string _symbol);

    /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
    /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
    ///  3986. The URI may point to a JSON file that conforms to the "ERC721
    ///  Metadata JSON Schema".
    function tokenURI(uint256 _tokenId) external view returns (string);
}

This allows your contract to publish its full list of NFTs and make them discoverable.

這個就是用於展現你的token的各類信息,好比經過index獲得token或者獲得owner等,可用可不用(option)

/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x780e9d63.
interface ERC721Enumerable /* is ERC721 */ {
    /// @notice Count NFTs tracked by this contract
    /// @return A count of valid NFTs tracked by this contract, where each one of
    ///  them has an assigned and queryable owner not equal to the zero address
    function totalSupply() external view returns (uint256);

    /// @notice Enumerate valid NFTs
    /// @dev Throws if `_index` >= `totalSupply()`.
    /// @param _index A counter less than `totalSupply()`
    /// @return The token identifier for the `_index`th NFT,
    ///  (sort order not specified)
    function tokenByIndex(uint256 _index) external view returns (uint256);

    /// @notice Enumerate NFTs assigned to an owner
    /// @dev Throws if `_index` >= `balanceOf(_owner)` or if
    ///  `_owner` is the zero address, representing invalid NFTs.
    /// @param _owner An address where we are interested in NFTs owned by them
    /// @param _index A counter less than `balanceOf(_owner)`
    /// @return The token identifier for the `_index`th NFT assigned to `_owner`,
    ///   (sort order not specified)
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

警告(caveat),這是solidity 0.4.20的版本出的警告,如今已是0.4.24版本了,有些警告已經解決

1)Solidity issue #3412

在remix中編譯下面的兩個例子

pragma solidity ^0.4.19;

interface NamedThing {
    function name() public view returns (string _name);
}

contract Bob is NamedThing {
    string public constant name = "Bob";
}

報錯:

DeclarationError:Identifier already declared:
 string public constant name = "Bob";
The previous declaration is here:
function name() public view returns (string _name);

因此實現interface的函數的時候是可以這樣子寫的嗎????

警告:

warning:function in interface should be declared external

 

pragma solidity ^0.4.19;

interface NamedThing {
    function name() public view returns (string _name);
}

contract Bob is NamedThing {
    function name() public pure returns (string) {
        return "Bob";
    }
}

上面這兩個都是實現了使用接口的例子

警告:

warning:function in interface should be declared external

報錯:

TypeError:overriding changes states mutability from "view" to "pure"

只看第二個例子,這個錯誤產生的緣由是mutability guarantees是有程度排序的:

Mutability is expressed in these forms:

  1. Payable(強)
  2. Nonpayable
  3. View
  4. Pure(弱)

因此上面錯誤發生時由於接口的函數爲view,因此不能在實現函數時把函數聲明爲pure

而後後面覺得反過來實現應該沒問題,即接口函數聲明爲pure時,實現聲明爲view,後面發現仍是報錯:

TypeError:overriding changes states mutability from "pure" to "view

2)Solidity issue #3419: A contract that implements ERC721Metadata or ERC721Enumerable SHALL also implement ERC721. ERC-721 implements the requirements of interface ERC-165.

就是interface是不能繼承自另外一個interface的

好比:

pragma solidity ^0.4.22;

interface I1 {
    function a() public;
}

interface I2 is I1 {
    function a() public;
}

interface I3 is I1 {
    function a() public;
}

contract C is I2, I3 {
    function a() external;
}

會報錯:

TypeError:Interfaces cannot inherit:
interface I2 is I1{...}
TypeError:Interfaces cannot inherit:
interface I3 is I1{...}
 
 
TypeError:Overriding function visibility differs:
function a() external;
overrinden function is here:
function a() public;
 

除此以外還有interface中是不能寫變量的,如:

pragma solidity ^0.4.24;

interface A {
    uint32[] public contracts;
}

interface B is A {
    function lastContract() public returns (uint32 _contract);
}

報錯:

TypeError:Variables cannot be declared in interfaces:(可是能夠聲明常量constant)
uint32[] public contracts;
TypeError:Interfaces cannot inherit:
interface I3 is I1{...}

 

The problem is that we want to properly fix inheritance and function overloading, and this requires some discussion. Please take a look here:

https://github.com/ethereum/solidity/pull/3729

這裏寫了但願改進的有關override和overload的內容,尚未實現

 

3)Solidity issue #2330: If a function is shown in this specification as external then a contract will be compliant if it uses public visibility. As a workaround for version 0.4.20, you can edit this interface to switch to public before inheriting from your contract.

pragma solidity ^0.4.24;

interface NamedThing {
    function name() public view returns (string _name);
}

contract Bob is NamedThing {
    function name() public pure returns (string) {
        return "Bob";
    }
}

不會報錯,如今是可以這樣override了

 

4)Solidity issues #3494, #3544: Use of this.*.selector is marked as a warning by Solidity, a future version of Solidity will not mark this as an error.

pragma solidity ^0.4.24;

interface Solidity101 {
    function hello() external pure;
    function world(int) external pure;
}

contract Selector {
    function calculateSelector() public pure returns (bytes4) {
        return this.hello.selector ^ this.world.selector;
    }
    function hello() external pure;
    function world(int) external pure;
}

以前會報錯:

TypeError:Function declared as pure,but this expression reads from the environment or state and thus requires "view"

The problem is that this is disallowed in a view context - reading the current address is not a pure action.就是this不能在聲明爲pure的函數中使用,可是如今能夠了(Allow `this.f.selector` to be pure. #3498,https://github.com/ethereum/solidity/pull/3498)

 

others:

Creating of NFTs ("minting") and destruction NFTs ("burning") is not included in the specification. Your contract may implement these by other means. Please see the event documentation for your responsibilities when creating or destroying NFTs.

NFT token是能夠被destruct的

 

在這裏使用ERC-165 Interface的目的:

We chose Standard Interface Detection (ERC-165) to expose the interfaces that a ERC-721 smart contract supports.

就是用來發布這個NFT合約使用的接口

使用ERC721 接口的函數也是要實現ERC165的接口的

 

舉例說明上面提到的接口的使用:

完整代碼在:https://github.com/0xcert/ethereum-erc721/tree/master/contracts/tokens

1)SupportsInterface.sol爲對ERC165接口中的supportsInterface函數的實現:

pragma solidity ^0.4.24;

import "./ERC165.sol";

/**
 * @dev Implementation of standard for detect smart contract interfaces.
 */
contract SupportsInterface is
  ERC165
{

  /**
   * @dev Mapping of supported intefraces.
   * @notice You must not set element 0xffffffff to true.
   */
  mapping(bytes4 => bool) internal supportedInterfaces;

  /**
   * @dev Contract constructor.
   */
  constructor()
    public
  {
    supportedInterfaces[0x01ffc9a7] = true; // ERC165
  }

  /**
   * @dev Function to check which interfaces are suported by this contract.
   * @param _interfaceID Id of the interface.
   */
  function supportsInterface(
    bytes4 _interfaceID
  )
    external
    view
    returns (bool)
  {
    return supportedInterfaces[_interfaceID];
  }

}

2)ERC721.sol就是正常的接口,寫了本身想要實現的函數功能和事件

ERC721TokenReceiver.sol,是當你調用了safeTransferFrom函數時使用的接口

由於有一些狀況你transfer token,可能會致使token的丟失,好比

If you will send 100 ERC20 tokens to a contract that is not intended to work with ERC20 tokens, then it will not reject tokens because it cant recognize an incoming transaction. As the result, your tokens will get stuck at the contracts balance.

pragma solidity ^0.4.24;

/**
 * @dev ERC-721 interface for accepting safe transfers. See https://goo.gl/pc9yoS.
* 因此這個接口的目的就是保證接受的是安全的transfer,這個接口的做用就是給了你的transfer進行一些條件限制,好比說不能transfer給的地址_to是你自己的合約地址。
* 由於在非safe的transfer中是沒有這些限制的,基本上_to能夠是任意地址,ERC721TokenReceiver接口的實現讓transfer更safe了
*/ interface ERC721TokenReceiver { /** * @dev Handle the receipt of a NFT. 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. * Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` unless throwing. * @notice The contract address is always the message sender. A wallet/broker/auction application * MUST implement the wallet interface if it will accept safe transfers. * @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. */ function onERC721Received( address _operator, address _from, uint256 _tokenId, bytes _data ) external returns(bytes4); }
AddressUtils.sol是用來覈查一個地址是否是合約地址的庫,是則返回true,不然爲false
pragma solidity ^0.4.24;

/**
 * @dev Utility library of inline functions on addresses.
 */
library AddressUtils {

  /**
   * @dev Returns whether the target address is a contract.
   * @param _addr Address to check.
   */
  function isContract(
    address _addr
  )
    internal
    view
    returns (bool)
  {
    uint256 size;

    /**
     * XXX Currently there is no better way to check if there is a contract in an address than to
     * check the size of the code at that address.
     * See https://ethereum.stackexchange.com/a/14016/36603 for more details about how this works.
     * TODO: Check this again before the Serenity release, because all addresses will be
     * contracts then.
     */

//如何查看一個地址是EOS仍是智能合約地址,就在於查看該帳戶代碼的長度。EOS下是不能存放代碼的,因此它的長度是0,只有合約帳戶的地址是可以存放代碼的
assembly { size := extcodesize(_addr) } // solium-disable-line security/no-inline-assembly return size > 0; } }

EOS的全稱爲「Enterprise Operation System」,是一條高可用性的公鏈,交易幾乎能夠在一秒內確認

3)NFToken.sol實現過程即:

pragma solidity ^0.4.24;

import "./ERC721.sol";
import "./ERC721TokenReceiver.sol";
import "@0xcert/ethereum-utils/contracts/math/SafeMath.sol";
import "@0xcert/ethereum-utils/contracts/utils/SupportsInterface.sol";
import "@0xcert/ethereum-utils/contracts/utils/AddressUtils.sol";

/**
 * @dev Implementation of ERC-721 non-fungible token standard.
 */
contract NFToken is
  ERC721,
  SupportsInterface
{
//這兩個是library
  using SafeMath for uint256;
  using AddressUtils for address;

  /**
   * @dev Magic value of a smart contract that can recieve NFT.
   * Equal to: bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")).
   */
  bytes4 constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;

  constructor()
    public
  {
    //代表使用了ERC721接口,就是當你的合約使用了什麼接口的時候,均可以經過這個方法將獲得的interface ID寫到數組中進行記錄,這樣檢測也就只用查詢數組便可
    supportedInterfaces[0x80ac58cd] = true; // ERC721
  }

  /**
   * @dev Transfers the ownership of an NFT from one address to another address.
   * @notice 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
  {
    _safeTransferFrom(_from, _to, _tokenId, "");
  }

  /**
   * @dev Actually perform the safeTransferFrom.
   * @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
  )
    internal
    canTransfer(_tokenId)
    validNFToken(_tokenId)
  {
    address tokenOwner = idToOwner[_tokenId];
    require(tokenOwner == _from);
    require(_to != address(0));

    _transfer(_to, _tokenId);
    //即若是transfer的_to地址是一個合約地址的話,該transfer將會失敗,由於在ERC721TokenReceiver接口中,
//onERC721Received函數的調用什麼都沒有返回,默認爲null
//這就是對transfer的一種限制,因此爲safe
if (_to.isContract()) {//在library AddressUtils中實現 bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data); require(retval == MAGIC_ON_ERC721_RECEIVED);//該require將revert這個transfer transaction } }
相關文章
相關標籤/搜索