5

I'm trying to invoke another contract in solidity using raw calls but I'm encountering some problems.

instance.call.gas(gas).value(price)(bytes4(keccak256("method(string)")), param);

this instruction is inside my second contract and while I try to get the value of the param from my first contract function its empty but instead its value is passed into msg.data.

I added an event on my first contract to see how the parameters has been sent.

function method(string param) payable external {
 Debug(param, msg.sender, msg.value, msg.data);
}

basically param is always empty while the data inside the msg are always sent and in msg.data there is my parameter value (in bytes).

I saw this discussion already and I tried to cast my string to bytes32 or passing the value without storing it into a local variable but the result is the same.

Calling a method directly with a parameter eg. instance.method2(stringParam) will work, but I can't use it in my case since I'm trying to call a payable method and I need to sent eth.

3 Answers 3

3

You have to properly encode the parameter using the ABI encoding. Something like

instance.call.gas(gas).value(price)(
    bytes4(keccak256("method(string)")),
    u256(0x20),   // pointer to data
    param.length, // length of data
    param         // actual data
);

could work, but it would probably be better to either use inline assembly for that (because it does not include any magic) or wait a little more and use the function abi.encode which should hopefully be available soon.

In general, using low-level calls is discouraged and should only be used of there is no other way, for example if you do not want to propagate a revert condition.

You can do the same (apart from not propagating the revert) using

interface MethodStringInterface {
    function method(string);
}
contract MyContract {
    function myFunction(address _a) {
        MethodStringInterface(_a).method("abc");
    }
}

or even better:

interface MethodStringInterface {
    function method(string);
}
contract MyContract {
    function myFunction(MethodStringInterface _a) {
        _a.method("abc");
    }
}
0

I don't have an answer for why you can't get the parameter to work with call, but you can just do instance.method2.value(price)(param) instead.

6
  • actually method2 is not payable. But if I try to do that in a payable method (which in my case is method) I get the following error TypeError: Not enough components (0) in value to assign all variables (1).
    – qbsp
    Commented Feb 24, 2018 at 10:15
  • Oh, I think you may need to pass a bytes32.
    – user19510
    Commented Feb 24, 2018 at 10:17
  • Ok. I'll try it later. But I couldn't go for this solution anyway. Because in case the method will throw and exception (via asset, require) I'm not able to catch it and handle. That was one of the reason why I used call.
    – qbsp
    Commented Feb 24, 2018 at 10:36
  • I'm experiencing the exact same issue. How can I pass in arbitrary length string (or unknown length) if using bytes32 instead?
    – nrek
    Commented Mar 1, 2018 at 12:11
  • What if I don't know the contract type of instance (it's just an address), but I do know it has a function of signature say func(string)? Then I could only use instance.call(bytes4(keccak256("func(string)")), raw_str)....
    – nrek
    Commented Mar 1, 2018 at 12:19
0

You can now (^0.4.24) change your old code:

instance.call.gas(gas).value(price)(bytes4(keccak256("method(string)")), param);

And start using abi.encode functions like this:

instance.call.gas(gas).value(price)(abi.encodeWithSignature("method(string)", param));

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.