0

If I'm writing an Ethereum smart contract in Solidity and I know I have an ERC20 address variable (let's call it some_erc20_token), but I don't know whether this address implements the interface of an ERC20 extension EIP (for instance ERC1404 with its detectTransferRestriction function) how do I check if the address implements that function so I can call it?

If this were Java I would use the instanceof operator to check for this, something like

void wagTail(Animal animal) {
  if (animal instanceof Dog) {
    animal = (Dog)animal;
    animal.wagDogTail();
  } else if (animal instance of Cat) {
    animal = (Cat)animal;
    animal.wagCatTail();
  }
}
  

In my solidity contract I want to do something of the form

if (some_erc20_token.detectTransferRestriction != 0) { // this function exists!
  some_erc20_token.detectTransferRestriction(to, from , amount);
}

However that doesn't compile and I get the compiler error: TypeError: Member "detectTransferRestriction" not found or not visible after argument-dependent lookup in contract MyContract.

4

You can implement a helper function in your contract, which will try to call detectTransferRestriction and return success or failure:

  • Success will indicate that the function exists AND its execution completed successfully
  • Failure will indicate that the function does not exist OR its execution did not complete successfully

There you go:

pragma solidity 0.4.25;

contract MyContract {
    bytes4 private constant FUNC_SELECTOR = bytes4(keccak256("detectTransferRestriction(address,address,uint256)"));

    function callDetectTransferRestriction(address _token, address _to, address _from, uint256 _amount) public returns (bool) {
        bool success;
        bytes memory data = abi.encodeWithSelector(FUNC_SELECTOR, _to, _from, _amount);

        assembly {
            success := call(
                gas,            // gas remaining
                _token,         // destination address
                0,              // no ether
                add(data, 32),  // input buffer (starts after the first 32 bytes in the `data` array)
                mload(data),    // input length (loaded from the first 32 bytes in the `data` array)
                0,              // output buffer
                0               // output length
            )
        }

        return success;
    }
}

I declared the helper function public in order to test it, but you don't have to do that of course.

Here is a Truffle 4.x test:

contract("MyContract", accounts => {
    const TO = "0x".padEnd(42, "1");
    const FROM = "0x".padEnd(42, "2");
    const AMOUNT = "0x".padEnd(66, "3");

    it("test", async () => {
        const myContract = await artifacts.require("MyContract").new();
        const goodToken = await artifacts.require("GoodToken").new();
        const badToken = await artifacts.require("BadToken").new();
        await myContract.callDetectTransferRestriction(goodToken.address, TO, FROM, AMOUNT);
        await myContract.callDetectTransferRestriction(badToken.address, TO, FROM, AMOUNT);
        const goodToken_dummy1 = await goodToken.dummy1();
        const goodToken_dummy2 = await goodToken.dummy2();
        const goodToken_dummy3 = await goodToken.dummy3();
        const badToken_dummy1 = await badToken.dummy1();
        const badToken_dummy2 = await badToken.dummy2();
        const badToken_dummy3 = await badToken.dummy3();
        console.log();
        console.log("goodToken_dummy1 =", goodToken_dummy1);
        console.log("goodToken_dummy2 =", goodToken_dummy2);
        console.log("goodToken_dummy3 =", goodToken_dummy3.toString(16));
        console.log("badToken_dummy1 =", badToken_dummy1);
        console.log("badToken_dummy2 =", badToken_dummy2);
        console.log("badToken_dummy3 =", badToken_dummy3.toString(16));
    });
});

And a couple of dummy contracts that the test above relies on:

contract GoodToken {
    address public dummy1;
    address public dummy2;
    uint256 public dummy3;
    function detectTransferRestriction(address x, address y, uint256 z) public {
        dummy1 = x;
        dummy2 = y;
        dummy3 = z;
    }
}

contract BadToken {
    address public dummy1;
    address public dummy2;
    uint256 public dummy3;
    function someOtherFunc(address x, address y, uint256 z) public {
        dummy1 = x;
        dummy2 = y;
        dummy3 = z;
    }
}

I believe that in solc 0.5.x or higher, you can do it in a single Solidity line (no assembly involved).


An update following your request (in a comment) on how this can be done off-chain:

const FUNC_SIGNATURE = "detectTransferRestriction(address,address,uint256)";
const funcSelector = web3.sha3(FUNC_SIGNATURE).slice(2,10); // Truffle v4.x / Web3 v0.x
const funcSelector = web3.utils.keccak256(FUNC_SIGNATURE).slice(2,10); // Truffle v5.x / Web3 v1.x
const bytecode = await web3.eth.getCode(tokenContract.address);
console.log(bytecode.includes(funcSelector));
8
  • Can this approach be done offline? i.e. I have the bytecode of a deployed smart contract, can I use such truffle test to check if the given contract has implemented one function or not? – Aaron Jun 5 '20 at 6:47
  • @Aaron: Yes, and much more easily. I thought you explicitly stated that you wanted it on-chain. – goodvibration Jun 5 '20 at 8:03
  • @Aaron: Please see an update at the bottom of this answer on how to do this off-chain. – goodvibration Jun 5 '20 at 11:19
  • Thanks! very appreciate – Aaron Jun 8 '20 at 6:03
  • 1
    @goodvibration you are correct that in Solidity v0.5+ you can use the .call address method. No need for assembly! See solidity.readthedocs.io/en/v0.5.15/… – almel Jun 9 '20 at 16:49
0

Unfortunately it is not possible to check if an address implements a function without help from the contract.

There's a proposal EIP 165: Standard Interface Detection that allows to query contracts for the methods they implement. But ERC-20 was defined before EIP-165 and most tokens doesn't implement it.

3
  • Not quite sure why you're saying that. Sure it's possible. On solc 0.4.x, with assembly-level call. On solc 0.5.x or higher, I think even with solidity-level call. – goodvibration Jun 5 '20 at 6:10
  • @goodvibration I understand that they were asking how to determine if a function exists without executing it. – Ismael Jun 6 '20 at 4:21
  • You're right, so did I at first. And of course, for a constant function, it is alright to find out if it exists via executing it, so I immediately responded to your answer. Then I realized that it wasn't a constant function, so there was a problem with my comment. But then I noticed, that this dude was interested in executing it only if it existed, which makes the request itself equivalent to "finding out if the function exists via executing it". And that includes the scenario in which the function execution reverts (as I emphasized in my answer). – goodvibration Jun 6 '20 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.