From 6e4394d6d127ddd447fd7991af871fb0a8800472 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Wed, 14 Feb 2018 22:29:01 -0500 Subject: [PATCH] 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 6b14cb1b..c843e195 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/).