Update caching contract

This commit is contained in:
William Entriken 2018-02-14 22:29:01 -05:00 committed by GitHub
parent 4b7b91691a
commit 6e4394d6d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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";
interface Simpson {
function is2D() external returns (bool);
function skinColor() external returns (string);
}
contract ERC165MappingImplementation is ERC165 {
/// @dev You must not set element 0xffffffff to true
mapping(bytes4 => bool) internal supportedInterfaces;
contract Lisa is ERC165, Simpson {
mapping(bytes4 => bool) supportedInterfaces;
function Lisa() public {
function ERC165MappingImplementation() internal {
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
interface Simpson {
function is2D() external returns (bool);
function skinColor() external returns (string);
}
contract Lisa is ERC165MappingImplementation, Simpson {
function Lisa() public {
supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true;
}
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/).