1

I would like to detect if an address has a particular function implemented. In this case isSafe:

interface ISafe {
   function isSafe() public pure returns (bool _true);
   // other functions I want to call: function doStuff()...
}

contract Foo {

   function foo(address _addr) public {
      ISafe iSafe = ISafe(_addr);

      // this throws... but I'd like it to return a bool instead.
      if (iSafe.isSafe()) {
          // ...
      }
   }

}

I'm finding the above causes a revert error if isSafe is not implemented on _addr. I'd like, instead, to obtain a boolean value. Is there a way to catch the error?

For my particular use case, it's not even important what the return value is. I just need some way to identify that _addr has some expected function defined.

Note: The solution should not return true if the function is not defined and there is no fallback function.

3 Answers 3

1

Other answers return false positives for contracts with no fallback function defined. Assembly code seems like the only way to go.. combined Tjaden Hess's answer with How do I construct a call to another contract using inline assembly?

Answer:

function testIsSafe(address _addr)
    private
    returns (bool _isSafe)
{
    bytes32 sig = bytes4(keccak256("isSafe()"));
    bool _success = false;
    assembly {
        let x := mload(0x40)    // get free memory
        mstore(x, sig)          // store signature into it
        _success := call(
            5000,   // 5k gas
            _addr,  // to _addr
            0,      // 0 value
            x,      // input is x
            4,      // input length is 4
            x,      // store output to x
            32      // 1 output, 32 bytes
        )
        // _isSafe is: _success && output
        _isSafe := and(_success, mload(x))
    }
}

This tests various contracts against the above:

pragma solidity ^0.4.19;

contract Safe {
    function isSafe() public pure returns (bool) { return true; }
}
contract FakeSafe {
    function isSafe() public pure returns (uint) { return 12345; }
}
contract NotSafe {
    function isSafe() public pure returns (bool) { return false; }
}
contract NoFallback {
    // function() public {}
}
contract EmptyFallback{
    function() public {}
}

contract Caller {
    Safe safeInstance;
    NotSafe notSafeInstance;
    FakeSafe fakeSafeInstance;
    NoFallback noFallbackInstance;
    EmptyFallback emptyFallbackInstance;

    function Caller() public {
        safeInstance = new Safe();
        fakeSafeInstance = new FakeSafe();
        notSafeInstance = new NotSafe();
        noFallbackInstance = new NoFallback();
        emptyFallbackInstance = new EmptyFallback();
    }

    // returns true
    function testSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(safeInstance));
    }

    // returns false
    function testNotSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(notSafeInstance));
    }

    // returns true
    function testFakeSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(fakeSafeInstance));
    }

    // returns false
    function testNoFallback()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(noFallbackInstance));
    }

    // returns false
    function testEmptyFallback()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(emptyFallbackInstance));
    }



    function testIsSafe(address _addr)
        private
        returns (bool _isSafe)
    {
        bytes32 sig = bytes4(keccak256("isSafe()"));
        bool _success = false;
        assembly {
            let x := mload(0x40)    // get free memory
            mstore(x, sig)          // store signature into it
            _success := call(
                5000,   // 5k gas
                _addr,  // to _addr
                0,      // 0 value
                x,      // input is x
                4,      // input length is 4
                x,      // store output to x
                32      // 1 output, 32 bytes
            )
            // _isSafe is: _success && output
            _isSafe := and(_success, mload(x))
        }
    }
}
0

you can use a raw level call to achieve it:

interface ISafe {
   function isSafe() public pure returns (bool _true);
   // other functions I want to call: function doStuff()...
}

contract Foo {

    function foo(address _addr) public {
        if(_addr.call(bytes4(keccak256("isSafe()")))) {

        }
    }

}

But consider that you will have the following side effects:

call returns a boolean indicating whether the invoked function terminated (true) or caused an EVM exception (false). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).

Since you don't need to access to the return value, call might be a good solution for you. It will be false if the function is not present (or if it caused an exception during the execution - but since isSafe is a pure function shouldn't generate and exception and revert the state) but will return true if it's available.

6
  • Note: If you want to actually check the returned value, you'll need to execute it again the normal way after you've checked to see that it exists and doesn't exception, i.e. if (ISafe(_addr).isSafe()) { // Is safe }
    – supakaity
    Commented Mar 19, 2018 at 8:57
  • Will the raw-level call return true if the fallback function is defined?
    – JS_Riddler
    Commented Mar 19, 2018 at 9:32
  • It will return true if the function as input (in this case isSafe, is defined and doesn't generate and exception while it's executed.
    – qbsp
    Commented Mar 19, 2018 at 9:43
  • This will return true even if the contract that you are calling is empty or has a fallback function Commented Mar 19, 2018 at 15:11
  • @mirg It (undesirably) returns true if there is no fallback function defined. Is there any way around this?
    – JS_Riddler
    Commented Mar 20, 2018 at 6:27
0

You can either use two calls, like so:

if (iSafe.addr.call(bytes4(keccak256("isSafe()"))) && iSafe.isSafe()){
    ...
}

or you can use one call with inline assembly (cheaper but less readable):

bytes32 function_code = bytes4(keccak256("isSafe()"));
bool isSafe = false;
assembly {
    mstore(0, function_code)
    sucess := call(sub(gas, 1000), iSafe, 0,0, 4, 0, 32)
    isSafe := and(success, mload(0))
}
3
  • This (undesirably) returns true if the contract has no fallback function.
    – JS_Riddler
    Commented Mar 20, 2018 at 6:26
  • Also, I get an error about "call" expecting 7 arguments
    – JS_Riddler
    Commented Mar 20, 2018 at 6:38
  • I fixed the call. The first part should return false if there's no fallback, but I can check. Commented Mar 20, 2018 at 16:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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