Creates a standard method to publish and detect what interfaces a smart contract implements.
Abstract
Herein, we standardize the following:
How interfaces are identified
How a contract will publish the interfaces it implements
How to detect if a contract implements ERC-165
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 interacted with. Specifically for ERC-20, a version identifier has already been proposed. This proposal standardizes the concept of interfaces and standardizes the identification (naming) of interfaces.
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:
Note: interfaces do not permit optional functions, therefore, the interface identity will not include 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):
pragmasolidity^0.4.20;interfaceERC165{/// @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
functionsupportsInterface(bytes4interfaceID)externalviewreturns(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 30,000 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
The source contract makes a STATICCALL to the destination address with input data: 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000 and gas 30,000. This corresponds to contract.supportsInterface(0x01ffc9a7).
If the call fails or return false, the destination contract does not implement ERC-165.
If the call returns true, a second call is made with input data 0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000.
If the second call fails or returns true, the destination contract does not implement ERC-165.
Otherwise it implements ERC-165.
How to Detect if a Contract Implements any Given Interface
If you are not sure if the contract implements ERC-165, use the above procedure to confirm.
If it does not implement ERC-165, then you will have to see what methods it uses the old-fashioned way.
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.
Following is a contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina.
pragmasolidity^0.4.20;contractERC165Query{bytes4constantInvalidID=0xffffffff;bytes4constantERC165ID=0x01ffc9a7;functiondoesContractImplementInterface(address_contract,bytes4_interfaceId)externalviewreturns(bool){uint256success;uint256result;(success,result)=noThrowCall(_contract,ERC165ID);if((success==0)||(result==0)){returnfalse;}(success,result)=noThrowCall(_contract,InvalidID);if((success==0)||(result!=0)){returnfalse;}(success,result)=noThrowCall(_contract,_interfaceId);if((success==1)&&(result==1)){returntrue;}returnfalse;}functionnoThrowCall(address_contract,bytes4_interfaceId)constantinternalreturns(uint256success,uint256result){bytes4erc165ID=ERC165ID;assembly{letx:=mload(0x40)// Find empty storage location using "free memory pointer"
mstore(x,erc165ID)// Place signature at beginning of empty storage
mstore(add(x,0x04),_interfaceId)// Place first argument directly next to signature
success:=staticcall(30000,// 30k gas
_contract,// To addr
x,// Inputs are stored at location x
0x24,// Inputs are 36 bytes long
x,// Store output over input (saves space)
0x20)// Outputs are 32 bytes long
result:=mload(x)// Load the result
}}}
Implementation
This approach uses a view function implementation of supportsInterface. The execution cost is 586 gas for any input. But contract initialization requires storing each interface (SSTORE is 20,000 gas). The ERC165MappingImplementation contract is generic and reusable.
pragmasolidity^0.4.20;import"./ERC165.sol";contractERC165MappingImplementationisERC165{/// @dev You must not set element 0xffffffff to true
mapping(bytes4=>bool)internalsupportedInterfaces;functionERC165MappingImplementation()internal{supportedInterfaces[this.supportsInterface.selector]=true;}functionsupportsInterface(bytes4interfaceID)externalviewreturns(bool){returnsupportedInterfaces[interfaceID];}}interfaceSimpson{functionis2D()externalreturns(bool);functionskinColor()externalreturns(string);}contractLisaisERC165MappingImplementation,Simpson{functionLisa()public{supportedInterfaces[this.is2D.selector^this.skinColor.selector]=true;}functionis2D()externalreturns(bool){}functionskinColor()externalreturns(string){}}
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.
With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping approach (in every case) costs less gas than the pure approach (at worst case).
Version history
PR 1640, finalized 2019-01-23 – This corrects the noThrowCall test case to use 36 bytes rather than the previous 32 bytes. The previous code was an error that still silently worked in Solidity 0.4.x but which was broken by new behavior introduced in Solidity 0.5.0. This change was discussed at #1640.
EIP 165, finalized 2018-04-20 – Original published version.