From 5e025143345dec2525ec8b777e420fd1ebf83fea Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 13 Feb 2018 02:35:39 -0500 Subject: [PATCH 01/24] A standard for interface detection, fixes #165 --- EIPS/eip-165.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 EIPS/eip-165.md diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md new file mode 100644 index 0000000000000..250764ef6689b --- /dev/null +++ b/EIPS/eip-165.md @@ -0,0 +1,121 @@ +## Preamble + +``` +EIP: +Title: ERC-165 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, William Entriken +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-token-standard.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 interfaced 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 calculated in Solidity](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 define 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.19; + +interface Solidity101 { + function hello() public pure; + function world(int) public 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 them. + +### How a Contract will Publish the Interfaces it Implements + +A contract that is compliant with ERC-165 shall implement the following function: + +```solidity +pragma solidity ^0.4.19; + +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 + /// use less than 30000 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 30000 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 `CALL` to the destination address with input data: `0x01ffc9a701ffc9a7` value: 0 and gas 30000. 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 `0x01ffc9a7ffffffff`. +4. If the second call fails or returns true, the destination contract does not implement ERC-165. +5. Otherwise it implements EIP165. + +### How to Detect if a Contract Implements any Given Interface + +1. If you are not sure if the contract implements ERC-165 Interface, use the previous 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 + +XXXXXXXX HELP NEEDED XXXXXXXXX + +## Implementation + +XXXXXX IN PROGRES XXXXXX [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/). From 755848848742588956ef716c5d3e7a16eaa0ac24 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 13 Feb 2018 03:07:48 -0500 Subject: [PATCH 02/24] Discuss optional interfaces per @dete advice References: https://github.com/ethereum/EIPs/issues/165#issuecomment-345382512 --- EIPS/eip-165.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 250764ef6689b..18b7c91854f71 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -51,7 +51,9 @@ contract Selector { } ``` -Note: interfaces do not permit optional functions, therefore, the interface identity will not them. +Note: interfaces do not permit optional functions, therefore, the interface identity will not include them. + +Note: an ERC standard may define multiple interfaces to separate core functionality from optional features. For example, [one draft standard defines](https://github.com/ethereum/EIPs/pull/841) ERC721, ERC721Metadata and ERC721Enumerable interfaces. ### How a Contract will Publish the Interfaces it Implements From 224822c743be51ff6b2762a1958507b4ea81637c Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 14 Feb 2018 00:05:57 -0500 Subject: [PATCH 03/24] Add two competing implementations --- EIPS/eip-165.md | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 18b7c91854f71..aff72d70552a5 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -51,13 +51,11 @@ contract Selector { } ``` -Note: interfaces do not permit optional functions, therefore, the interface identity will not include them. - -Note: an ERC standard may define multiple interfaces to separate core functionality from optional features. For example, [one draft standard defines](https://github.com/ethereum/EIPs/pull/841) ERC721, ERC721Metadata and ERC721Enumerable interfaces. +Note: interfaces do not permit optional functions, therefore, the interface identity will not them. ### How a Contract will Publish the Interfaces it Implements -A contract that is compliant with ERC-165 shall implement the following function: +A contract that is compliant with ERC-165 shall implement the following interface (referred as `ERC165.sol`): ```solidity pragma solidity ^0.4.19; @@ -116,6 +114,61 @@ XXXXXXXX HELP NEEDED XXXXXXXXX ## Implementation +This approach uses a `view` function implementation of `supportsInterface`. The execution cost is 478 gas for any input. But contract initialization requires storing each interface (`SSTORE` is 20,000 gas). + +```solidity +pragma solidity ^0.4.19; + +import "./ERC165.sol"; + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string); +} + +contract Lisa is ERC165, Simpson { + mapping(bytes4 => bool) supportedInterfaces; + + function Lisa() public { + supportedInterfaces[this.supportsInterface.selector] = true; + supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true; + } + + function supportsInterface(bytes4 interfaceID) external view returns (bool) { + return supportedInterfaces[interfaceID]; + } + + // ... is usually 2D + // skin color is yellow +} +``` + +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.19; + +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 + } +} +``` + +With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping table approach (for any case) costs less gas than the worst case for pure approach. + + + XXXXXX IN PROGRES XXXXXX [https://github.com/jbaylina/EIP165Cache](https://github.com/jbaylina/EIP165Cache) ## Copyright From 4b7b91691aa3ae4417e9b4aeaf3b96544f6119c7 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 14 Feb 2018 00:16:03 -0500 Subject: [PATCH 04/24] Add author email addresses per latest EIP-X Found from git log messages around the world. --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index aff72d70552a5..6b14cb1b15b2a 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -3,7 +3,7 @@ ``` EIP: Title: ERC-165 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, William Entriken +Author: Christian Reitwießner , Nick Johnson , RJ Catalano , Fabian Vogelsteller , Hudson Jameson , Jordi Baylina , Griff Green , William Entriken Type: Standard Track Category: ERC Status: Draft From 6e4394d6d127ddd447fd7991af871fb0a8800472 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 14 Feb 2018 22:29:01 -0500 Subject: [PATCH 05/24] Update caching contract --- EIPS/eip-165.md | 90 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 6b14cb1b15b2a..c843e195c83fb 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -86,7 +86,7 @@ Implementation note, there are several logical ways to implement this function. ### How to Detect if a Contract Implements ERC-165 -1. The source contact makes a `CALL` to the destination address with input data: `0x01ffc9a701ffc9a7` value: 0 and gas 30000. This corresponds to `contract.supportsInterface("0x01ffc9a7")`. +1. The source contact makes a static `CALL` to the destination address with input data: `0x01ffc9a701ffc9a7` value: 0 and gas 30000. 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 `0x01ffc9a7ffffffff`. 4. If the second call fails or returns true, the destination contract does not implement ERC-165. @@ -110,36 +110,96 @@ Also [the ENS](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md) alr ## Test Cases -XXXXXXXX HELP NEEDED XXXXXXXXX +Following is a caching contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina. + +```solidity +pragma solidity ^0.4.19; + +contract ERC165Cache { + bytes4 constant InvalidID = 0xffffffff; + bytes4 constant ERC165ID = 0x01ffc9a7; + + enum ImplStatus { Unknown, No, Yes } + mapping (address => mapping (bytes4 => ImplStatus)) cache; + + // Return value from cache if available + function interfaceSupported(address _contract, bytes4 _interfaceId) external returns (bool) { + ImplStatus status = cache[_contract][_interfaceId]; + if (status == ImplStatus.Unknown) { + return checkInterfaceSupported(_contract, _interfaceId); + } + return status == ImplStatus.Yes; + } + + // Repull result into cache + function checkInterfaceSupported(address _contract, bytes4 _interfaceId) public returns (bool) { + ImplStatus status = determineInterfaceImplementationStatus(_contract, _interfaceId); + cache[_contract][_interfaceId] = status; + return status == ImplStatus.Yes; + } + + function determineInterfaceImplementationStatus(address _contract, bytes4 _interfaceId) internal view returns (ImplStatus) { + if (noThrowCall(_contract, InvalidID)) return ImplStatus.No; + if (!noThrowCall(_contract, ERC165ID)) return ImplStatus.No; + if (noThrowCall(_contract, _interfaceId)) return ImplStatus.Yes; + return ImplStatus.No; + } + + // Update this after the Metropolis hard fork to use staticcall! + function noThrowCall(address _contract, bytes4 _interfaceId) internal view returns (bool 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 + call(30000, // 30k gas + _contract, // To addr + 0, // No value + x, // Inputs are stored at location x + 0x8, // Inputs are 8 byes long + x, // Store output over input (saves space) + 0x20) // Outputs are 32 bytes long + pop // Discard call return value + result := mload(x) // Load the result + } + } +} +``` ## Implementation -This approach uses a `view` function implementation of `supportsInterface`. The execution cost is 478 gas for any input. But contract initialization requires storing each interface (`SSTORE` is 20,000 gas). +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.19; 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 ERC165, Simpson { - mapping(bytes4 => bool) supportedInterfaces; - +contract Lisa is ERC165MappingImplementation, Simpson { function Lisa() public { - supportedInterfaces[this.supportsInterface.selector] = true; supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true; } - function supportsInterface(bytes4 interfaceID) external view returns (bool) { - return supportedInterfaces[interfaceID]; - } - - // ... is usually 2D - // skin color is yellow + function is2D() external returns (bool){} + function skinColor() external returns (string){} } ``` @@ -167,10 +227,6 @@ contract Homer is ERC165, Simpson { With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping table approach (for any case) costs less gas than the worst case for pure approach. - - -XXXXXX IN PROGRES XXXXXX [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/). From d39b3885d405474c7db773fb9983398d44fc2529 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Thu, 15 Feb 2018 12:12:05 -0500 Subject: [PATCH 06/24] Add author Konrad Feldmeier Per @chriseth --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index c843e195c83fb..5769a84f11e99 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -3,7 +3,7 @@ ``` EIP: Title: ERC-165 Standard Interface Detection -Author: Christian Reitwießner , Nick Johnson , RJ Catalano , Fabian Vogelsteller , Hudson Jameson , Jordi Baylina , Griff Green , William Entriken +Author: Christian Reitwießner , Nick Johnson , RJ Catalano , Fabian Vogelsteller , Hudson Jameson , Jordi Baylina , Griff Green , Konrad Feldmeier , William Entriken Type: Standard Track Category: ERC Status: Draft From 50f59da47f40c76c433e9bd54c00a4a44aa78d18 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 06:35:16 -0500 Subject: [PATCH 07/24] Updated to use staticcall --- EIPS/eip-165.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 5769a84f11e99..75082b04bd8d8 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -145,16 +145,14 @@ contract ERC165Cache { return ImplStatus.No; } - // Update this after the Metropolis hard fork to use staticcall! function noThrowCall(address _contract, bytes4 _interfaceId) internal view returns (bool 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 - call(30000, // 30k gas + staticcall(30000, // 30k gas _contract, // To addr - 0, // No value x, // Inputs are stored at location x 0x8, // Inputs are 8 byes long x, // Store output over input (saves space) From 529ce1b2cae64f22df72d8de236b9b85414c18dd Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 06:51:50 -0500 Subject: [PATCH 08/24] Update cache to use explicit return values of 32 byte length --- EIPS/eip-165.md | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 75082b04bd8d8..0586dd35f4528 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -36,11 +36,11 @@ For this standard, an *interface* is a set of [function selectors as calculated 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.19; +pragma solidity ^0.4.20; interface Solidity101 { - function hello() public pure; - function world(int) public pure; + function hello() external pure; + function world(int) external pure; } contract Selector { @@ -58,7 +58,7 @@ Note: interfaces do not permit optional functions, therefore, the interface iden A contract that is compliant with ERC-165 shall implement the following interface (referred as `ERC165.sol`): ```solidity -pragma solidity ^0.4.19; +pragma solidity ^0.4.20; interface ERC165 { /// @notice Query if a contract implements an interface @@ -113,7 +113,7 @@ Also [the ENS](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md) alr Following is a caching contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina. ```solidity -pragma solidity ^0.4.19; +pragma solidity ^0.4.20; contract ERC165Cache { bytes4 constant InvalidID = 0xffffffff; @@ -139,25 +139,42 @@ contract ERC165Cache { } function determineInterfaceImplementationStatus(address _contract, bytes4 _interfaceId) internal view returns (ImplStatus) { - if (noThrowCall(_contract, InvalidID)) return ImplStatus.No; - if (!noThrowCall(_contract, ERC165ID)) return ImplStatus.No; - if (noThrowCall(_contract, _interfaceId)) return ImplStatus.Yes; + uint256 success; + uint256 result; + + (success, result) = noThrowCall(_contract, ERC165ID); + if ((success==0)||(result==0)) { + return ImplStatus.No; + } + + (success, result) = noThrowCall(_contract, InvalidID); + if ((success==0)||(result!=0)) { + return ImplStatus.No; + } + + (success, result) = noThrowCall(_contract, _interfaceId); + if ((success==1)&&(result==1)) { + return ImplStatus.Yes; + } return ImplStatus.No; } - function noThrowCall(address _contract, bytes4 _interfaceId) internal view returns (bool result) { + 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 - staticcall(30000, // 30k gas - _contract, // To addr - x, // Inputs are stored at location x - 0x8, // Inputs are 8 byes long - x, // Store output over input (saves space) - 0x20) // Outputs are 32 bytes long - pop // Discard call return value + + success := staticcall( + 30000, // 5k gas + _contract, // To addr + x, // Inputs are stored at location x + 0x8, // Inputs are 8 byes long + x, // Store output over input (saves space) + 0x20) // Outputs are 32 bytes long + result := mload(x) // Load the result } } From 26c91183dc3a497cac02e3488e641217ac883d32 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 06:56:43 -0500 Subject: [PATCH 09/24] "uses less than" --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 0586dd35f4528..91e05a17c3ec5 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -64,7 +64,7 @@ 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 - /// use less than 30000 gas. + /// uses less than 30000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); From ad70b8d27686de2b324491b1a7f6667963f5aa2e Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 06:58:03 -0500 Subject: [PATCH 10/24] Function selectors are defined by Ethereum, not Solidity --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 91e05a17c3ec5..392ff855547d4 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -31,7 +31,7 @@ For some "standard interfaces" like [the ERC-20 token interface](https://github. ### How Interfaces are Identified -For this standard, an *interface* is a set of [function selectors as calculated in Solidity](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 define return types, mutability and events. +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 define 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: From 39285480caa015bb46b3a373c013a23d3045ace9 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 18:57:27 -0500 Subject: [PATCH 11/24] Remove authors @VoR0220 @Souptacular @GriffGreen --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 392ff855547d4..aefeccb10a58c 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -3,7 +3,7 @@ ``` EIP: Title: ERC-165 Standard Interface Detection -Author: Christian Reitwießner , Nick Johnson , RJ Catalano , Fabian Vogelsteller , Hudson Jameson , Jordi Baylina , Griff Green , Konrad Feldmeier , William Entriken +Author: Christian Reitwießner , Nick Johnson , Fabian Vogelsteller , Jordi Baylina , Konrad Feldmeier , William Entriken Type: Standard Track Category: ERC Status: Draft From b97350cb1ba9fde52ea9776495d5a595b3dbd492 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 16 Feb 2018 19:34:44 -0500 Subject: [PATCH 12/24] Copyediting review --- EIPS/eip-165.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index aefeccb10a58c..a4c4a79b7a3f6 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -31,7 +31,7 @@ For some "standard interfaces" like [the ERC-20 token interface](https://github. ### 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 define return types, mutability and events. +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: @@ -51,7 +51,7 @@ contract Selector { } ``` -Note: interfaces do not permit optional functions, therefore, the interface identity will not them. +Note: interfaces do not permit optional functions, therefore, the interface identity will not include them. ### How a Contract will Publish the Interfaces it Implements @@ -64,7 +64,7 @@ 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 30000 gas. + /// 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); @@ -80,22 +80,22 @@ Therefore the implementing contract will have a `supportsInterface` function tha - `true` for any other `interfaceID` this contract implements - `false` for any other `interfaceID` -This function must return a bool and use at most 30000 gas. +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 static `CALL` to the destination address with input data: `0x01ffc9a701ffc9a7` value: 0 and gas 30000. This corresponds to `contract.supportsInterface("0x01ffc9a7")`. +1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7` 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 `0x01ffc9a7ffffffff`. 4. If the second call fails or returns true, the destination contract does not implement ERC-165. -5. Otherwise it implements EIP165. +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 Interface, use the previous 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. +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 @@ -168,7 +168,7 @@ contract ERC165Cache { mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature success := staticcall( - 30000, // 5k gas + 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x 0x8, // Inputs are 8 byes long @@ -240,7 +240,7 @@ contract Homer is ERC165, Simpson { } ``` -With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping table approach (for any case) costs less gas than the worst case for pure approach. +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 From 881f577abbd5ca9c81d7e6d90d1b59e1446bc9b3 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Mon, 19 Feb 2018 16:43:01 -0500 Subject: [PATCH 13/24] Bytes, version 0.4.20, thanks @veox --- EIPS/eip-165.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index a4c4a79b7a3f6..4ce58d3f14408 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -171,7 +171,7 @@ contract ERC165Cache { 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x - 0x8, // Inputs are 8 byes long + 0x8, // Inputs are 8 bytes long x, // Store output over input (saves space) 0x20) // Outputs are 32 bytes long @@ -186,7 +186,7 @@ contract ERC165Cache { 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.19; +pragma solidity ^0.4.20; import "./ERC165.sol"; @@ -221,7 +221,7 @@ contract Lisa is ERC165MappingImplementation, Simpson { 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.19; +pragma solidity ^0.4.20; import "./ERC165.sol"; From 734d68b6439941bf1577a4f1156b8e906185f7f5 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Mon, 19 Feb 2018 16:54:19 -0500 Subject: [PATCH 14/24] Use 32-byte words --- EIPS/eip-165.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 4ce58d3f14408..740aa2d01a7e8 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -86,9 +86,9 @@ Implementation note, there are several logical ways to implement this function. ### How to Detect if a Contract Implements ERC-165 -1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7` and gas 30,000. This corresponds to `contract.supportsInterface("0x01ffc9a7")`. +1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a700000000000000000000000001ffc9a7` 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 `0x01ffc9a7ffffffff`. +3. If the call returns true, a second call is made with input data `0x01ffc9a7000000000000000000000000ffffffff`. 4. If the second call fails or returns true, the destination contract does not implement ERC-165. 5. Otherwise it implements ERC-165. @@ -171,7 +171,7 @@ contract ERC165Cache { 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x - 0x8, // Inputs are 8 bytes long + 0x20, // Inputs are 8 byes long x, // Store output over input (saves space) 0x20) // Outputs are 32 bytes long From b675b4b3fa453d8fdc218e1351668bfabb459ff5 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 20 Feb 2018 17:22:47 -0500 Subject: [PATCH 15/24] Update zero padding, thank you @veox --- EIPS/eip-165.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 740aa2d01a7e8..ed4b91125ab6a 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -86,9 +86,9 @@ Implementation note, there are several logical ways to implement this function. ### How to Detect if a Contract Implements ERC-165 -1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a700000000000000000000000001ffc9a7` and gas 30,000. This corresponds to `contract.supportsInterface("0x01ffc9a7")`. +1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7000000000000000000000000` 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 `0x01ffc9a7000000000000000000000000ffffffff`. +3. If the call returns true, a second call is made with input data `0x01ffc9a7ffffffff000000000000000000000000`. 4. If the second call fails or returns true, the destination contract does not implement ERC-165. 5. Otherwise it implements ERC-165. From 12fe0b505c730a198b5f99df8b22e7adfd0c07f9 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 20 Feb 2018 17:33:49 -0500 Subject: [PATCH 16/24] Switch from a cache to a query contract, cache is out of scope --- EIPS/eip-165.md | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index ed4b91125ab6a..af48ad201acdc 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -115,48 +115,29 @@ Following is a caching contract that detects which interfaces other contracts im ```solidity pragma solidity ^0.4.20; -contract ERC165Cache { +contract ERC165Query { bytes4 constant InvalidID = 0xffffffff; bytes4 constant ERC165ID = 0x01ffc9a7; - enum ImplStatus { Unknown, No, Yes } - mapping (address => mapping (bytes4 => ImplStatus)) cache; - - // Return value from cache if available - function interfaceSupported(address _contract, bytes4 _interfaceId) external returns (bool) { - ImplStatus status = cache[_contract][_interfaceId]; - if (status == ImplStatus.Unknown) { - return checkInterfaceSupported(_contract, _interfaceId); - } - return status == ImplStatus.Yes; - } - - // Repull result into cache - function checkInterfaceSupported(address _contract, bytes4 _interfaceId) public returns (bool) { - ImplStatus status = determineInterfaceImplementationStatus(_contract, _interfaceId); - cache[_contract][_interfaceId] = status; - return status == ImplStatus.Yes; - } - - function determineInterfaceImplementationStatus(address _contract, bytes4 _interfaceId) internal view returns (ImplStatus) { + 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 ImplStatus.No; + return false; } (success, result) = noThrowCall(_contract, InvalidID); if ((success==0)||(result!=0)) { - return ImplStatus.No; + return false; } (success, result) = noThrowCall(_contract, _interfaceId); if ((success==1)&&(result==1)) { - return ImplStatus.Yes; + return true; } - return ImplStatus.No; + return false; } function noThrowCall(address _contract, bytes4 _interfaceId) constant internal returns (uint256 success, uint256 result) { @@ -171,7 +152,7 @@ contract ERC165Cache { 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x - 0x20, // Inputs are 8 byes long + 0x20, // Inputs are 8 byes long x, // Store output over input (saves space) 0x20) // Outputs are 32 bytes long From 1947d2f676ff4efbb31217636d5223b20b8c97a0 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 20 Feb 2018 17:39:55 -0500 Subject: [PATCH 17/24] Complete Homer implementation --- EIPS/eip-165.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index af48ad201acdc..4eaac2992afc3 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -218,6 +218,9 @@ contract Homer is ERC165, Simpson { interfaceID == this.is2D.selector ^ this.skinColor.selector; // Simpson } + + function is2D() external returns (bool){} + function skinColor() external returns (string){} } ``` From 490ce29eca89bd921ed312d0e902a0bb7fb3e446 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 20 Feb 2018 17:45:23 -0500 Subject: [PATCH 18/24] not a cache --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 4eaac2992afc3..baf9224410780 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -110,7 +110,7 @@ Also [the ENS](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md) alr ## Test Cases -Following is a caching contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina. +Following is a contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina. ```solidity pragma solidity ^0.4.20; From 4ecf5844603f4356e9784d5c5f6fe2591dba042e Mon Sep 17 00:00:00 2001 From: William Entriken Date: Tue, 20 Feb 2018 20:16:39 -0500 Subject: [PATCH 19/24] 32 bytes input --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index baf9224410780..d3e42a27216ff 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -152,7 +152,7 @@ contract ERC165Query { 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x - 0x20, // Inputs are 8 byes long + 0x20, // Inputs are 32 bytes long x, // Store output over input (saves space) 0x20) // Outputs are 32 bytes long From 50ad36b0b1224f1306e9f919a52befe26fd56820 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 21 Feb 2018 13:40:29 -0500 Subject: [PATCH 20/24] Hex, not string --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index d3e42a27216ff..5e5319001ae9b 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -86,7 +86,7 @@ Implementation note, there are several logical ways to implement this function. ### How to Detect if a Contract Implements ERC-165 -1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7000000000000000000000000` and gas 30,000. This corresponds to `contract.supportsInterface("0x01ffc9a7")`. +1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7000000000000000000000000` 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 `0x01ffc9a7ffffffff000000000000000000000000`. 4. If the second call fails or returns true, the destination contract does not implement ERC-165. From e902727c3647263e33327d554efd26cd1d3c16ca Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 21 Feb 2018 13:44:16 -0500 Subject: [PATCH 21/24] Nibbles are not bytes, duh --- EIPS/eip-165.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 5e5319001ae9b..aea9c8d176703 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -86,9 +86,9 @@ Implementation note, there are several logical ways to implement this function. ### How to Detect if a Contract Implements ERC-165 -1. The source contact makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a7000000000000000000000000` and gas 30,000. This corresponds to `contract.supportsInterface(0x01ffc9a7)`. +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 `0x01ffc9a7ffffffff000000000000000000000000`. +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. From d47923bc1b2550e9a22899e0b91e21d83ff2ffe7 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 21 Feb 2018 13:45:51 -0500 Subject: [PATCH 22/24] Update link https://github.com/ethereum/EIPs/pull/881#discussion_r169604965 --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index aea9c8d176703..09009bcb5e670 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -25,7 +25,7 @@ Herein, we standardize the following: ## Motivation -For some "standard interfaces" like [the ERC-20 token interface](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.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 interfaced 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. +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 interfaced 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 From 03c67d2e0867c051de6b500b7d2e55fc92dd99b5 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 21 Feb 2018 13:46:31 -0500 Subject: [PATCH 23/24] interfaced -> interacted https://github.com/ethereum/EIPs/pull/881#discussion_r169605418 --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 09009bcb5e670..d15ef2246d4ab 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -25,7 +25,7 @@ Herein, we standardize the following: ## 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 interfaced 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. +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 From 2cb6f9177fe409b49a4e9654edfb0e0e5f16a6f7 Mon Sep 17 00:00:00 2001 From: Nick Savers Date: Wed, 21 Feb 2018 23:43:12 +0100 Subject: [PATCH 24/24] Update eip-165.md --- EIPS/eip-165.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index d15ef2246d4ab..529b483eabd14 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -1,7 +1,7 @@ ## Preamble ``` -EIP: +EIP: 165 Title: ERC-165 Standard Interface Detection Author: Christian Reitwießner , Nick Johnson , Fabian Vogelsteller , Jordi Baylina , Konrad Feldmeier , William Entriken Type: Standard Track