Pseudo-Introspection, or standard interface detection #165
Description
This EIP is being further developed as #881
For some "standard interfaces" like the token interface ( #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 interfaced with. Specifically for #20, a version identifier has already been proposed. This proposal wants to generalize the concept of interfaces and interface versions to interface identifiers:
Every "standard interface" should be assigned a unique identifier (the sha3 hash of its source representation would be suitable) as a bytes32 value. Interfaces that support pseudo-introspection should provide the following methods. Note that this is especially targeted at contracts that can also provide multiple non-conflicting interfaces. This is useful if e.g. a standard token contract also allows a "cheque" functionality, which would have its own interface ID, or if the token does not support allowances.
/// @returns true iff the interface is supported
function supportsInterface(bytes32 interfaceID) constant returns (bool);
/// @returns the (main) interface ID of this contract
function interfaceID() constant returns (bytes32);
/// @returns a bit mask of the supported interfaces.
function supportsInterfaces(bytes32[] interfaceIDs) constant returns (bytes32 r) {
if (interfaceIDs.length > 256) throw;
for (uint i = 0; i < interfaceIDs.length; i++)
if (supportsInterface(interfaceIDs[i]))
r |= bytes32(2**i);
}
Example implementation and usage:
contract GenericInterfaceContract {
function interfaceID() constant returns (bytes32);
function supportsInterface(bytes32 _interfaceID) constant returns (bool);
function supportsInterfaces(bytes32[] interfaceIDs) constant returns (bytes32 r) {
if (interfaceIDs.length > 256) throw;
for (uint i = 0; i < interfaceIDs.length; i++)
if (supportsInterface(interfaceIDs[i]))
r |= bytes32(2**i);
}
}
contract SimpleToken is GenericInterfaceContract {
bytes32 public constant interfaceID = 0xf2a53462c853462ca86b71b97dd84af6a2f7689fc12ea917d9029117d32b9fde;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
function totalSupply() constant returns (uint256 supply);
function balanceOf(address _owner) constant returns (uint256 balance);
function transfer(address _to, uint256 _value) returns (bool success);
function supportsInterface(bytes32 _interfaceID) constant returns (bool) {
return _interfaceID == interfaceID;
}
}
contract Token is SimpleToken {
bytes32 public constant interfaceID = 0xa2f7689fc12ea917d9029117d32b9fdef2a53462c853462ca86b71b97dd84af6;
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
function approve(address _spender, uint256 _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
function supportsInterface(bytes32 _interfaceID) constant returns (bool) {
return _interfaceID == interfaceID || super.supportsInterface(_interfaceID);
}
}
contract C {
function retrieveRemainingAllowance(GenericInterfaceContract c, address owner) returns (uint) {
if (c.supportsInterface(Token.interfaceID)) {
Token t = Token(c);
return Token(c).allowance(owner, msg.sender);
} else if (c.supportsInterface(SimpleToken.interfaceID)) {
return 0;
} else {
throw;
}
}
}
Note:
Since the EVM does not provide any guarantees for the semantics of contracts, any such information returned by other contracts can only be used as a guideline.
Note2:
The convenient access to the interface ID via Token.interfaceID
is not yet supported by solidity ( ethereum/solidity#1290 ).
Credits: This is the result of a discussion with @konradkonrad and @frozeman