Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eip165 - Pseudo-Introspection, or standard interface detection #639

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions EIPS/eip-165.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
## Preamble

EIP: 165
Title: Pseudo-Introspection, or 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
Type: Standard Track
Category ERC
Status: Draft
Created: 2017-05-26

## Simple Summary

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

## Abstract

This standard consists of the standardization of the following procedures:

1. How a contract will publish the interfaces it implements.

2. How one contract can know if another contract implements a standard interface.

3. A generic method to determine if a contract implements this standard.

4. A unique standard contract in the blockchain where you can ask if a contract implements an interface or not. Creating this contract has two main advantages:

4.1. It caches the answers, so it saves gas.
4.2. Many interfaces can be queried in the same call.

## Motivation

For some "standard interfaces" like the ERC 20 token interface ( [#20](https://github.com/ethereum/EIPs/issues/20) ), 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 #20, a version identifier has already been proposed. This proposal wants to standarize the concept of interfaces and map interface versions to interface identifiers.

## Specification
##### Standard interface identifier.

Any standard interface will be identified by a specific `bytes4` number. This number will be the XOR of all `bytes4` of each method calculated as solidity does.

For example, to calculate the id of a standard interface

* totalSupply() -> Method ID: 0x18160ddd
* balanceOf(address) -> Method ID: 0x70a08231
* transfer(address, uint256) -> Method ID: 0xa9059cbb

InterfaceID = 0x18160ddd ^ 0x70a08231 ^ 0xa9059cbb = 0xc1b31357

##### EIP165 Interface

Any contract that wants to implement a standard interface must also implement this method:

function supportsInterface(bytes4 interfaceID) returns (bool);

This function must return:
* `true` when `interfaceID` is 0x01ffc9a7 (EIP165 interface)
* `false` when `interfaceID` is 0xffffffff
* `true` for any `interfaceID` that this contract implements.
* `false` for any other `interfaceID`

##### Procedure to determine if a contract implements EIP165 standard.

1. The source contact makes a `CALL` to the destination address with input data: 0x01ffc9a701ffc9a7 value: 0 and gas 30000. `0x01ffc9a701ffc9a7` is the data for `supportsInterface(0x01ffc9a7)`. Asks if self implements itself.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this needs to be expressed as an ABI, not as raw bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that in solidity you cannot catch the throw inside a call by just calling in the ABI way. You will have to use the call by hand and check the result. I added the explanation in the same line, but I think is worthy to just explain how the internal call is make. Please fill free to rewrite this paragraph in a way you fill more confortable.

2. If the call fails or return false, the destination contract does not implement EIP165
3. If the call returns true, a second call is made with input data: 0x01ffc9a7ffffffff
4. If the call fails or returns true, the destination contract does not implement EIP165
5. Otherwise it implements EIP165

##### Procedure to determine if a contract implements a standard interface

1. If you are not sure if the contract implements EIP165 Interface, use the previous procedure to confirm.
2. If it does not implement EIP165, then you will have to see what methods it uses the old fashioned way.
3. If it implements EIP165 then just call `supportsInterface(interfaceID)` to determine if it implements an interface you can use.

##### EIP165Cache contract.

The contract on address 0x_________ can optionally be used to determine if a contract implements EIP165, an interface, or a set of interfaces. This contract will use the previous procedure to determine the implementation of those interfaces and will hold a cache to optimize the gas costs of those queries.

This contract will implement the next functions:

function eip165Supported(address _contract) constant returns (bool);

Returns true if `_contract` supports EIP165

function interfaceSupported(address _contract, bytes4 _interfaceId) constant returns (bool);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking if the contract implements EIP165 aside, will this be any more efficient than just asking the contract directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the contract throws (because it is an old one) it will consume all the gas.... So in this case I suspect that it's much more efficient to have a cache. For a new contract (and if we remove the double call), probably it will not be very different.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point on the throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we assume that we will use a cache, then may be it's not so important to remove the "double call" in terms of performance (It will be done only once by the cache contract). And this way we don't risk for edge cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True; how about rewording it such that it's optional? In cases where you already know the target contract implements supportsInterface, there's no point in calling it more than once.


Returns true if `_contract` supports the interface with interface id `_interfaceId`

function interfacesSupported(address _contract, bytes4[] _interfaceIDs) constant returns (bytes32 r);

Returns a map where the LSB is 1 if `_contract` implements interface `_interfacesIDs[0]`, the second LSB is 1 if it implements `_interfacesIDs[1]`, and so on.

This function will throw if `_interfacesIDs.length` >256

## Example implementation

pragma solidity ^0.4.11;

contract EIP165Cache {
function interfaceSupported(address _contract, bytes4 _interfaceId) constant returns (bool);
function eip165Supported(address _contract) constant returns (bool);
function interfacesSupported(address _contract, bytes4[] _interfaceIDs) constant returns (bytes32 r);
}

contract IEIP165 {
bytes4 constant IEIP165ID =
bytes4(sha3('supportsInterface(bytes4)'));

mapping(bytes4 => bool) public supportsInterface;
function IEIP165() {
supportsInterface[IEIP165ID] = true;
}
}

contract IToken is IEIP165 {
bytes4 constant ITokenID =
bytes4(sha3('name()')) ^
bytes4(sha3('symbol()')) ^
bytes4(sha3('decimals()')) ^
bytes4(sha3('totalSupply()')) ^
bytes4(sha3('balanceOf(address)')) ^
bytes4(sha3('transfer(address,uint256)')) ^
bytes4(sha3('transfer(address,uint256,bytes)'));

event Transfer(address indexed from, address indexed to, uint256 value, bytes data);

function name() constant returns(string);
function symbol() constant returns(string);
function decimals() constant returns(uint8);
function totalSupply() constant returns (uint256 supply);
function balanceOf(address _owner) constant returns (uint256 balance);
function transfer(address _to, uint256 _value) returns (bool success);
function transfer(address _to, uint256 _value, bytes) returns (bool success);

function IToken() {
supportsInterface[ITokenID] = true;
}
}

contract ITokenFallback is IEIP165{
bytes4 constant ITokenFallbackID =
bytes4(sha3("tokenFallback(address,uint256,bytes)"));

function tokenFallback(address _from, uint _value, bytes _data);

function ITokenFallback() {
supportsInterface[ITokenFallbackID] = true;
}
}

contract Token is IToken, ITokenFallback {

EIP165Cache constant eip165Cache = EIP165Cache(0x1234567890123456678901234567890);
mapping (address => uint) _balances;
uint _totalSupply;

function Token(uint _initialSupply) {
_totalSupply = _initialSupply;
_balances[msg.sender] = _initialSupply;
}

// Implementation of ISimpleToken
function name() constant returns(string) {
return "Example Token";
}

function symbol() constant returns(string) {
return "EXM";
}

function decimals() constant returns(uint8) {
return 18;
}

function totalSupply() constant returns (uint256 supply) {
return _totalSupply;
}

function balanceOf(address _owner) constant returns (uint256 balance) {
return _balances[_owner];
}

function transfer(address _to, uint256 _value) returns (bool) {
return transfer(_to, _value, "");
}

function transfer(address _to, uint256 _value, bytes _data) returns (bool success) {
if (_value > _balances[msg.sender]) throw;
_balances[msg.sender] -= _value;
_balances[_to] += _value;
if ( eip165Cache.eip165Supported(_to) ) {
if (eip165Cache.interfaceSupported(_to, ITokenFallbackID)) {
ITokenFallback(_to).tokenFallback(msg.sender, _value, _data);
} else {
throw;
}
}
Transfer(msg.sender, _to, _value, _data);

return true;
}


// Implementation of ITokenFallback
function tokenFallback(address , uint , bytes ) {
throw; // The token contract should not accept tokens.
}
}



## 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 should work in most of the contracts previous to this standard to determine that day does not implement EIP165.

Also the ENS already implements this EIP.

## Test Cases

To be tested

## Implementation

[https://github.com/jbaylina/EIP165Cache](https://github.com/jbaylina/EIP165Cache)

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).