2

I am specifically interested in calling a function whose return-type is unknown to me.

More precisely:

  • The contract which owns the function has had several versions over time
  • My contract may call this function on any of the instances deployed to the network
  • The function's return-type is (uint256) some cases and (uint256, uint256) in other cases

I've figured that I can call the function without storing its returned-value, so compilation by itself will not be a problem. Then, assuming that the actual values are still on the stack (is this assumption possibly wrong, due to compiler optimization or other reasons?), I can read the two topmost slots in the stack and continue as planned from that point.

Alternatively, I thought I could declare an interface of the contract in doubt, with the function in doubt returning (uint256, uint256), and then use that interface in order to call the function.

However, I tried it, and the function which returns a single uint256 has reverted:

pragma solidity 0.4.25;

contract NotMyContract1 {
    uint256 a = 1;
    function func() external view returns (uint256) {
        return a;
    }
}

contract NotMyContract2 {
    uint256 a = 1;
    uint256 b = 2;
    function func() external view returns (uint256, uint256) {
        return (a, b);
    }
}

interface INotMyContract {
    function func() external view returns (uint256, uint256);
}

contract MyContract {
    INotMyContract private notMyContract;
    constructor(INotMyContract _notMyContract) public {
        notMyContract = _notMyContract;
    }
    function test() public view returns (uint256, uint256) {
        return notMyContract.func();
    }
}

contract("MyContract", function(accounts) {
    it("", async function() {
        await test("1");
        await test("2");
    });
    async function test(id) {
        const notMyContract = await artifacts.require("NotMyContract" + id).new();
        const myContract = await artifacts.require("MyContract").new(notMyContract.address);
        try {
            const [a, b] = await myContract.test();
            console.log(id, a.toString(), b.toString());
        }
        catch (error) {
            console.log(id, error.message);
        }
    }
});

So I would be happy to hear any idea on how to do this in assembly, as well as any alternative suggestions.

The assembly operations which I had in mind are dup1 … dup16 and swap1 … swap16.

Thank you!

2
  • Sorry if I don't understand the question correctly, but what you are trying to do is to call a function of a deployed contract to get the returned value? Do you have knowledge about the function (signature)?
    – alberto
    Commented Jun 23, 2019 at 10:45
  • @alberto: Yes, of course, everything but the type of the returned value (which is not part of the signature by definition). Commented Jun 23, 2019 at 11:21

2 Answers 2

3

You'll have a much easier time of this if you can use a recent version of Solidity. Starting with 0.5.x, you can get back the return value as a byte array and then decode it yourself depending on its length.

Here's some code that handles both return values (a single uint256 or a pair of them) and returns a 0 as the second value if it wasn't returned from the external contract:

function test() public view returns (uint256, uint256) {
    (bool success, bytes memory returnValue) = address(notMyContract).staticcall(
        abi.encodeWithSelector(notMyContract.func.selector));

    require(success, "Call failed.");

    if (returnValue.length > 32) {
        return abi.decode(returnValue, (uint256, uint256));
    }
    else {
        uint256 a = abi.decode(returnValue, (uint256));
        return (a, 0);
    }
}

EDIT

Here's a Solidity 0.4.x version that uses assembly to read the return data:

function test() public view returns (uint256, uint256) {
    bool success = address(notMyContract).call(abi.encodeWithSelector(notMyContract.func.selector));
    require(success);
    uint256[2] memory ret;
    assembly {
        let size := returndatasize
        if gt(size, 64) {
            revert(0, 0)
        }
        returndatacopy(ret, 0, size)
    }
    return (ret[0], ret[1]);
}
16
  • Thanks. What you wrote (the first two lines) looks very similar to a valid assembly for solc v0.4.x (which I have actually just tried, by the way, and seems to work, though it is not as clean as your code). I am unable to switch to 0.5.x at this point, because it would imply changing the entire system (contracts and truffle tests). Commented Jun 23, 2019 at 20:07
  • You can do it in 0.4.x too, but you'll need to write assembly to copy the return data. (The values are not on the stack, but the return data is available from assembly.)
    – user19510
    Commented Jun 23, 2019 at 23:59
  • I added a 0.4.x version in an edit.
    – user19510
    Commented Jun 24, 2019 at 1:14
  • Wouldn't there be a warning on call here, since it is not staticcall? How would you do the whole thing in assembly then? Thank you very much by the way. Commented Jun 24, 2019 at 5:50
  • Yes, there should be a warning about doing a call from a view function. I would just ignore the warning. The other answer has an all-assembly solution.
    – user19510
    Commented Jun 24, 2019 at 14:08
1

Reading the comments, maybe you are looking for something like the following. I've changed the values to see more easily the outputs.

To test, you need to pass the address of NotMyContract1 or NotMyContract2 as a parameter of the getValue() function.

pragma solidity ^0.4.25;

contract NotMyContract1 {
    uint256 a = 721;
    function func() external view returns (uint256) {
        return a;
    }
}

contract NotMyContract2 {
    uint256 a = 9;
    uint256 b = 8;
    function func() external view returns (uint256, uint256) {
        return (a, b);
    }
}

contract MyContract {

    constructor() public {}

    function getValue(address _address) public view returns (uint, uint) {
        bytes4 sig = bytes4(keccak256("func()"));
        assembly {
            let ptr := mload(0x40)
            mstore(ptr,sig)

            let result := staticcall(not(0), _address, ptr, 0x04, ptr, 0x20)

            let size := returndatasize
            returndatacopy(ptr, 0, size)

            switch result case 0 { revert(0, 0) }
            default {
                if lt(size,64) { return (ptr, 0x40) }
                if gt(size,32) { return (ptr,size) }
            }
        }
    }
}
0

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.