Creates a standard method to publish and detect what interfaces a smart contract implements.
Herein, we standardize the following:
For some “standard interfaces” like the OIP-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 OIP-20, a version identifier has already been proposed. This proposal standardizes the concept of interfaces and standardizes the identification (naming) of interfaces.
For this standard, an interface is a set of function selectors as defined by the Ethereum ABI. This a subset of Solidity’s concept of interfaces 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:
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.
A contract that is compliant with OIP-165 shall implement the following interface (referred as OIP165.sol
):
pragma solidity ^0.4.20;
interface OIP165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in OIP-165
/// @dev Interface identification is specified in OIP-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 implementsfalse
for any other interfaceID
This function must return a bool and use at most 30,000 gas.STATICCALL
to the destination address with input data: 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
and gas 30,000. This corresponds to contract. supportsInterface(0x01ffc9a7)
.0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000
.supportsInterface(interfaceID)
to determine if it implements an interface you can use.We tried to keep this specification as simple as possible. This implementation is also compatible with the current Solidity version.
Following is a contract that detects which interfaces other contracts implement.
pragma solidity ^0.4.20;
contract OIP165Query {
bytes4 constant InvalidID = 0xffffffff;
bytes4 constant OIP165ID = 0x01ffc9a7;
function doesContractImplementInterface(address _contract, bytes4 _interfaceId) external view returns (bool) {
uint256 success;
uint256 result;
(success, result) = noThrowCall(_contract, OIP165ID);
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 OIP165ID = OIP165ID;
assembly {
let x := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(x, OIP165ID) // 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
}
}
}
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 OIP165MappingImplementation
contract is generic and reusable.
pragma solidity ^0.4.20;
import "./OIP165.sol";
contract OIP165MappingImplementation is OIP165 {
/// @dev You must not set element 0xffffffff to true
mapping(bytes4 => bool) internal supportedInterfaces;
function OIP165MappingImplementation() 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 OIP165MappingImplementation, 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.
pragma solidity ^0.4.20;
import "./OIP165.sol";
interface Simpson {
function is2D() external returns (bool);
function skinColor() external returns (string);
}
contract Homer is OIP165, Simpson {
function supportsInterface(bytes4 interfaceID) external view returns (bool) {
returninterfaceID == this.supportsInterface.selector || // OIP165
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 OIP165 itself as a required supported interface), the mapping approach (in every case) costs less gas than the pure approach (at worst case).
Copyright and related rights waived via CC0.