erc721-165學習


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

首先我們是怎么表明一個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,有多少個函數就並多少個函數.selector

然后合約是怎么發布它實現的接口的:

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)'))也一樣,結果相同

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,其實就是 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
     //前面是合約簽名0x01ffc9a7,代表調用了函數supportsInterface(interfaceID),后面的即參數interfaceID,這里為01ffc9a7
encodedParams_data, encodedParams_size, //insize,數據大小 output, //out,輸出指針位置 0x20 // Outputs are 32 bytes long,0x20 == 32,outsize )

從上面的步驟我們可以看見,要測試是否使用了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可以得知需要執行的函數,以及函數的傳入值,並作出相應的操作。
  //即形如0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
//額外添加知識: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,得到調用合約得到的結果 } } }

下面還有一種更簡單的方法,使用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 } }































免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM