6

As I understand there are two main ways to call a function in another contract.

1.) Using the 4 byte methodID ----> address.call(bytes4(sha3('foo()'))

2.) Using the contract ABI ---> contractInstance.foo()

I was wondering if anyone could explain why making a call with the bytes4 method ID can only indicate an out-of-gas error while making a call with the ABI can return actual values. I'm guessing it has something to do with the ABI, but I'd like to understand what is going on under the hood here. Thanks.

3

I'm not sure what you mean by using the contract ABI within a contract. I think you mean when we import a contract into another contract and access an instance of it by creating a reference to it eg contractInstance = externalContract(contractAddress).

Ultimately the reason there are return values when we have imported a contract is because we know at compilation what type and size of data each function call is going to return. We can then put the return data in the correct variable type. However, when we call a function on an address directly, we don't know what the type and size of return values are because these details are not stored on the blockchain. The function just returns raw data. This means we don't know what type of variable to store the return value in after calling that function, so it just isn't allowed.

As a side note, since the Byzantium fork you can actually get return values from doing address.call() if you execute some assembly instructions directly after it. Specifically the returndatacopy() instruction which can be seen in the doc. And an example usage can be seen in an upgradable proxy contract I wrote (Note the example uses delegatecall but it should behave the same as using call). This approach still only returns raw data, it doesn't create a type of variable for it to live in.

  • Ok that was great thanks. Can you explain what makes up a contract "instance" under the hood? Is it the bytecode? – arete Jan 18 '18 at 11:22
  • Also I'm digging your solidity-playground repo :-) – arete Jan 18 '18 at 11:29
  • 1
    I'm glad! Maybe it can be useful! Regarding the contract instance. In the context of Solidity it's just a high-level representation of something which ultimately gets compiled/converted to bytecode, as you've suggested. During compilation, when the compiler reaches a line that calls another contract, it goes to the file specified in the import statement that represents that contract and checks all the types (of args and return vals) are the same as those being called. It will raise an error if they're not or there's a compatibility issue otherwise it will convert it to bytecode for deployment. – willjgriff Jan 18 '18 at 13:14

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.