EIPs/EIPS/eip-165.md

7.0 KiB

Preamble

EIP: <to be assigned>
Title: ERC-165 Standard Interface Detection
Author: Christian Reitwießner @chriseth, Nick Johnson @Arachnid, RJ Catalano @VoR0220, Fabian Vogelsteller @frozeman, Hudson Jameson @Souptacular, Jordi Baylina @jbaylina, Griff Green @griffgreen, William Entriken <github.com@phor.net>
Type: Standard Track
Category: ERC
Status: Draft
Created: 2018-01-23

Simple Summary

Creates a standard method to publish and detect what interfaces a smart contract implements.

Abstract

Herein, we standardize the following:

  1. How interfaces are identified
  2. How a contract will publish the interfaces it implements
  3. How to detect if a contract implements ERC-165
  4. How to detect if a contract implements any given interface

Motivation

For some "standard interfaces" like the ERC-20 token interface, 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 interfaced with. Specifically for ERC-20, a version identifier has already been proposed. This proposal stadardizes the concept of interfaces and standardizes the identification (naming) of interfaces.

Specification

How Interfaces are Identified

For this standard, an interface is a set of function selectors as calculated in Solidity. This a subset of Solidity's concept of interfaces and the interface keyword definition which also define return types, mutability and events.

We define the interface identifier as the XOR of all function selectors in the interface. This code example shows how to calculate an interface identifier:

pragma solidity ^0.4.19;

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

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

Note: interfaces do not permit optional functions, therefore, the interface identity will not them.

How a Contract will Publish the Interfaces it Implements

A contract that is compliant with ERC-165 shall implement the following interface (referred as ERC165.sol):

pragma solidity ^0.4.19;

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
    ///  use less than 30000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

The interface identifier for this interface is 0x01ffc9a7. You can calculate this by running bytes4(keccak256('supportsInterface(bytes4)')); or using the Selector contract above.

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

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

This function must return a bool and use at most 30000 gas.

Implementation note, there are several logical ways to implement this function. Please see the example implementations and the discussion on gas usage.

How to Detect if a Contract Implements ERC-165

  1. The source contact makes a CALL to the destination address with input data: 0x01ffc9a701ffc9a7 value: 0 and gas 30000. 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 0x01ffc9a7ffffffff.
  4. If the second call fails or returns true, the destination contract does not implement ERC-165.
  5. Otherwise it implements EIP165.

How to Detect if a Contract Implements any Given Interface

  1. If you are not sure if the contract implements ERC-165 Interface, use the previous 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.

Rationale

We tried to keep this specification as simple as possible. This implementation is also compatible with the current Solidity version.

Backwards Compatibility

The mechanism described above (with 0xffffffff) should work with most of the contracts previous to this standard to determine that they do not implement ERC-165.

Also the ENS already implements this EIP.

Test Cases

XXXXXXXX HELP NEEDED XXXXXXXXX

Implementation

This approach uses a view function implementation of supportsInterface. The execution cost is 478 gas for any input. But contract initialization requires storing each interface (SSTORE is 20,000 gas).

pragma solidity ^0.4.19;

import "./ERC165.sol";

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

contract Lisa is ERC165, Simpson {
    mapping(bytes4 => bool) supportedInterfaces;
    
    function Lisa() public {
        supportedInterfaces[this.supportsInterface.selector] = true;
        supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true;
    }
    
    function supportsInterface(bytes4 interfaceID) external view returns (bool) {
        return supportedInterfaces[interfaceID];
    }
    
    // ... is usually 2D
    // skin color is yellow
}

Following is a pure function implementation of supportsInterface. The worst-case execution cost is 236 gas, but increases linearly with a higher number of supported interfaces.

pragma solidity ^0.4.19;

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

With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping table approach (for any case) costs less gas than the worst case for pure approach.

XXXXXX IN PROGRES XXXXXX https://github.com/jbaylina/EIP165Cache

Copyright and related rights waived via CC0.