Solidity Web3.js – How Does Web3.js Determine Argument Types?

solidityweb3js

Suppose I have a contract with a function name overloaded to take different types:

contract MyContract {
    event LogString(string str);
    event LogAddress(address addr);

    function logData(string str) {
        LogString(str);
    }
    function logData(address addr) {
        LogAddress(addr);
    }
}

These two functions will have different function signatures depending on the data I pass in. However, (I think) I would normally pass in an address from JavaScript as something like:

mycontract.logData("0xdeadbeef")

How does web3.js decide whether I intended the argument "0xdeadbeef" to be an address or a string? If this turns out to contain traps for the unwary, what's the best practice for naming the functions? Should I drop the overloading and give the functions different names depending on their expected data types?

Best Answer

I was wondering about this exact behaviour the other day but didn't have time to investigate it. The short answer is that Javascript itself doesn't allow for function overloading, so when the contract object is being built from the ABI, functions of the same name get overwritten.

...

The following 'homosignet' (?) contract contains three overloaded functions of the same name, the same JS argument type typeof "0x123" == "string" but with different ABI argument types, string, bytes, address

contract homosignet {
    function f(string s) constant returns (string){ return "was string";}
    function f(bytes b) constant returns (string){ return "was bytes";}
    function f(address a) constant returns (string){return "was address";}
}

We can see in Remix that the three are unique ABI functions having unique function signatures of:

fc68521a f(address)
d45754f8 f(bytes)
91e145ef f(string)

And that all three functions return the expected strings of "was string", "was bytes" and "was address".

However, if I hook the contract using web3.js and call the functions of the resulting JS object from the console, as in the following testcode and case:

<html> <head><script src="web3.js"></script>
<script>
    web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));

var homosignetContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"s","type":"string"}],"name":"f","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"b","type":"bytes"}],"name":"f","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}]);
k = homosignetContract.at("0x3b3d3de7a3271f0f6bea0b6b99e6f26a64dd3617");

</script></head></html>


> k.f(123)
"was string"
> k.f(0x123)
"was string"
> k.f("0x123")
"was string"
> k.f("test")
"was string"

The return values are all the same and in this case, all arguments are being parsed as type string, even the number literal.

This makes sense because Javascript does not allow function overloading and when you look at the contract object, indeed only one k.f() exists.

So it seems that calling overloaded functions requires a low level call with pre-encoded call object :

web3.eth.call(callObject [, defaultBlock] [, callback])

Which looks something like this when calling f(bytes b). Then you need to decode the raw return data.

web3.eth.call({to:"0x7db0be9069df82feac77f810b26a6d8011b89a8c", data:"d45754f80000000000000000000000000000000000000000000000000000000000000123"})