mirror of https://github.com/status-im/EIPs.git
232 lines
9.3 KiB
Markdown
232 lines
9.3 KiB
Markdown
## Preamble
|
|
|
|
```
|
|
EIP: 165
|
|
Title: ERC-165 Standard Interface Detection
|
|
Author: Christian Reitwießner <chris@ethereum.org>, Nick Johnson <nick@ethereum.org>, Fabian Vogelsteller <fabian@frozeman.de>, Jordi Baylina <jordi@baylina.cat>, Konrad Feldmeier <konrad.feldmeier@brainbot.com>, 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](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.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. 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 defined by the Ethereum ABI](http://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector). This a subset of [Solidity's concept of interfaces](http://solidity.readthedocs.io/en/develop/abi-spec.html) and the `interface` keyword definition which also defines 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:
|
|
|
|
```solidity
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
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`):
|
|
|
|
```solidity
|
|
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);
|
|
}
|
|
```
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
### How to Detect if a Contract Implements any Given Interface
|
|
|
|
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.
|
|
|
|
## 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](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md) already implements this EIP.
|
|
|
|
## Test Cases
|
|
|
|
Following is a contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina.
|
|
|
|
```solidity
|
|
pragma solidity ^0.4.20;
|
|
|
|
contract ERC165Query {
|
|
bytes4 constant InvalidID = 0xffffffff;
|
|
bytes4 constant ERC165ID = 0x01ffc9a7;
|
|
|
|
function doesContractImplementInterface(address _contract, bytes4 _interfaceId) external view returns (bool) {
|
|
uint256 success;
|
|
uint256 result;
|
|
|
|
(success, result) = noThrowCall(_contract, ERC165ID);
|
|
if ((success==0)||(result==0)) {
|
|
return false;
|
|
}
|
|
|
|
(success, result) = noThrowCall(_contract, InvalidID);
|
|
if ((success==0)||(result!=0)) {
|
|
return false;
|
|
}
|
|
|
|
(success, result) = noThrowCall(_contract, _interfaceId);
|
|
if ((success==1)&&(result==1)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function noThrowCall(address _contract, bytes4 _interfaceId) constant internal returns (uint256 success, uint256 result) {
|
|
bytes4 erc165ID = ERC165ID;
|
|
|
|
assembly {
|
|
let x := mload(0x40) // Find empty storage location using "free memory pointer"
|
|
mstore(x, erc165ID) // Place signature at begining 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
|
|
0x20, // Inputs are 32 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.
|
|
|
|
```solidity
|
|
pragma solidity ^0.4.20;
|
|
|
|
import "./ERC165.sol";
|
|
|
|
contract ERC165MappingImplementation is ERC165 {
|
|
/// @dev You must not set element 0xffffffff to true
|
|
mapping(bytes4 => bool) internal supportedInterfaces;
|
|
|
|
function ERC165MappingImplementation() internal {
|
|
supportedInterfaces[this.supportsInterface.selector] = true;
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) external view returns (bool) {
|
|
return supportedInterfaces[interfaceID];
|
|
}
|
|
}
|
|
|
|
interface Simpson {
|
|
function is2D() external returns (bool);
|
|
function skinColor() external returns (string);
|
|
}
|
|
|
|
contract Lisa is ERC165MappingImplementation, Simpson {
|
|
function Lisa() public {
|
|
supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true;
|
|
}
|
|
|
|
function is2D() external returns (bool){}
|
|
function skinColor() external returns (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.
|
|
|
|
```solidity
|
|
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){}
|
|
}
|
|
```
|
|
|
|
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).
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|