Created
May 30, 2019 21:09
-
-
Save wighawag/9404f81a2a6d0cfa2134549f186d4d46 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.4.20+commit.3155dd80.js&optimize=false&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.20; | |
contract ERC165Query { | |
bytes4 constant InvalidID = 0xffffffff; | |
bytes4 constant ERC165ID = 0x01ffc9a7; | |
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 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 erc165ID = ERC165ID; | |
assembly { | |
let x := mload(0x40) // Find empty storage location using "free memory pointer" | |
mstore(x, erc165ID) // 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.20; | |
import "./ERC165Query.sol"; | |
interface ERC165 { | |
function supportsInterface(bytes4 _id) external returns (bool); | |
} | |
// An example contract that support an interface (0xeeeeeeee) and is valid as per EIP-165 rules | |
// When called on this interface, it throw all the time (not implemented), we could imagine that to indicate it refuse to receive some sort of tokens | |
// it has the property that its `supportsInterface` implementation require an important amount of gas (specified approximatively by its constructor) to show case the issue | |
// Note : we could make variant of this contract that throws on some condition, etc... that would not remove the issue, just increase the threshold | |
contract ValidExampleAsPerEIP165 is ERC165{ | |
uint256 gas; | |
function ValidExampleAsPerEIP165(uint256 _gas) public { | |
gas = _gas; | |
} | |
function supportsInterface(bytes4 _id) external returns (bool) { | |
uint256 start = msg.gas; | |
while(start - msg.gas < gas) {} | |
return _id == 0xeeeeeeee || _id == 0x01ffc9a7; | |
} | |
} | |
// the example show the issue of using the ERC165Query implementation posted on EIP-165 | |
contract FailureCase { | |
ERC165Query query; | |
function FailureCase(ERC165Query _query) public { | |
query = _query; | |
} | |
// it is a contract that use an interface as a mechanism for refusal. | |
function test(address to) external returns (bool) { | |
// ... here we could have logic for token transfer, whatever, it will not affect the issue since these operations happen before | |
if (query.doesContractImplementInterface(to, 0xeeeeeeee)) { // it checks if the recipient accept call to an interface (0xeeeeeeee) | |
bool success = to.call(0xeeeeeeee); // if it does call it | |
require(success); // if such call throws it means the receiver refuse to receive the tokens, the call thus abort | |
} // if the recipient does not implement the interface 0xeeeeeeee, the contract consider it as acceptance, it thus continue the call | |
} | |
} | |
// now as per EIP-165, The contract should throw in all condition since ValidExampleAsPerEIP165 | |
// a ) is supporting the interface 0xeeeeeeee | |
// b) is throwing when called on 0xeeeeeeee | |
// and as per the logic of the method 'test' the require should be executed all the time | |
// But with the ERC165Query used here (the one specified in EIP-165) the call do not throw when : | |
// - ValidExampleAsPerEIP165 is deployed with a valid of at least 21000 as constructor parameter | |
// - FailureCase.test is executed on that contract with gasLimit = 87500 | |
// This is the bug since it should throw. | |
// increasing the gasLimit make it fails as expected, try with gasLimit = 875000 | |
// STEPS TO REPRODUCE : | |
// - deploy ERC165Query (save its address: A1) | |
// - deploy ValidExampleAsPerEIP165 with param = 21000 (save its address A2) | |
// - deploy FailureCase with param = <A1> | |
// execute FailureCase.test with param = <A2> and gasLimit = 87500 => it succeed (THIS IS THE BUG) | |
// execute FailureCase.test with param = <A2> and gasLimit = 875000 => it throws | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment