ERC-165 Standard Interface Detection
Final · Other · created 2018-01-23
A standard that lets a contract publish which interfaces (sets of functions) it implements, so that other contracts or apps can confirm this with a single call.
Why it is needed
Ethereum contracts work by calling each other, but there was no standard way to ask another contract in advance which functions it has. When dealing with widely used standard interfaces such as the ERC-20 token interface, the caller needs to know whether the other contract supports that interface, and if so which version, before it can match the way it calls. In fact, a separate version identifier was once proposed for ERC-20. Without such a check, the caller can only inspect the other contract's code directly, or call it and see whether the call fails. This proposal solves the problem by standardizing the concept of an interface itself and defining how to give each interface a name (identifier).
What to implement
function supportsInterface(bytes4 interfaceID) external view returns (bool);: Answers true or false on whether this contract implements the given 4-byte interface identifier. It returns true for 0x01ffc9a7 and false for 0xffffffff, and must run within 30,000 gas.
In depth
An interface identifier is the XOR of function selectors
In this standard, an interface is a set of function selectors as defined by the Ethereum ABI. Solidity's interface keyword also defines return types, mutability and events, but here only the function selectors are considered. The interface identifier is the bytes4 value obtained by XORing all function selectors in the interface. For the Solidity101 interface in the original example, i.hello.selector ^ i.world.selector is the identifier. Interfaces do not allow optional functions, so optional functions are not included in the identifier either.
Return rules that supportsInterface must follow
A contract that follows ERC-165 implements the ERC165 interface, that is, the supportsInterface(bytes4 interfaceID) function. The identifier of this interface itself is 0x01ffc9a7, and it can be computed as bytes4(keccak256('supportsInterface(bytes4)')). There are four return rules: true if interfaceID is 0x01ffc9a7, false if it is 0xffffffff, true for any other identifier this contract implements, and false for everything else. The function must return a bool and must use at most 30,000 gas. The original text says there are several ways to implement it and refers readers to the example implementation and the discussion of gas usage.
Two STATICCALLs to detect ERC-165 support
First, send a STATICCALL to the target address with input data 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000 and 30,000 gas. This corresponds to supportsInterface(0x01ffc9a7). If the call fails or returns false, the contract does not implement ERC-165. If true is returned, make a second call with input data 0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000. If this call fails or returns true, the contract is likewise judged not to implement it. It must pass both conditions to be treated as implementing ERC-165. The original text explains that this method using 0xffffffff correctly identifies non-implementation for most contracts created before the standard.
Checking support for a specific interface
If you are not sure the other contract implements ERC-165, first check using the procedure above. A contract that does not implement ERC-165 requires you to work out which functions it uses in the old way. If it implements ERC-165, simply calling supportsInterface(interfaceID) tells you whether the interface you want to use is supported. The original text states that the specification was kept as simple as possible and designed to be compatible with the Solidity versions of the time.
Two implementation approaches and their gas costs
The original text shows two example implementations. The first is ERC165MappingImplementation, which stores supported interfaces in mapping(bytes4 => bool) supportedInterfaces and looks them up in a view function. Execution costs 586 gas for any input, but each interface must be stored with an SSTORE (20,000 gas) at creation. In this approach, 0xffffffff must not be set to true. The second compares identifiers directly inside a pure function (the Homer example), which costs 236 gas in the worst case, but the cost grows in proportion to the number of supported interfaces. With three or more supported interfaces, including ERC-165 itself, the mapping approach is always cheaper than the worst case of the pure approach.
Test cases and revision history
The original Test Cases include an ERC165Query contract that determines whether another contract supports an interface. doesContractImplementInterface(address, bytes4) performs the detection procedure above as is. The internal noThrowCall sends a staticcall with 30,000 gas in assembly and returns the success flag and result without reverting on failure. The input length is 36 bytes (0x24), the 4-byte selector plus the 32-byte argument. PR 1640, finalized on January 23, 2019, corrected this value from the earlier 32 bytes to 36 bytes, because the old code worked silently on Solidity 0.4.x but broke under the new behavior in 0.5.0. According to the original text, ENS already implements this standard.