Solidity Contract Development – How Does the Function Call Work?

contract-developmentsolidity

I've come to understanding that in order to write a solidity function in the contract that allows me to send money from the contract to some address I have to use something like this:

function transfer(address payable _to, uint _amount) public {
    (bool success, ) = _to.call{value: _amount}("");
    require(success, "Failed to send Ether");
}

But what is this? First thing: is .call{…}("") even a function at all? Why do I have to put an empty string there? And what's the deal with the curly brackets before the parameter list of the function call?

I've also found a different way to do it, like this:

addr.call{value: msg.value, gas: 5000}(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));

So, do I need to specify the gas value for the transaction or not? And what's inside the parameter list (that abi.encodeWithSignature(…))?

Also why the definition of the variable success is in brackets? is it the same as doing the following in Python?

# ignoring value_2
value_1, _ = my_function()

Sorry for asking so many question, but I've had a hard time trying to find/understand the solidity documentation.

Best Answer

  1. {...} in .call{...} is a function modifier. It modifies the future call by adding metadata to it, like an ETH value to attach or max gas to send along.

  2. If you do not modify the metadata, the compiler will anyway put 0 ETH and the gas that makes sense to it.

  3. The call is effective on (...). What you put in there is the data sent along the call.

  4. If you send no data, then you write ("").

  5. A function signature, or better called a function selector, is the 4 bytes that is prepended to the call data to indicate which function to call. It is calculated as the hash of the function signature. The called smart contract has a table with the selectors it implements with where to jump in the code. If none is known, it calls the fallback function(s). The selector for transferFrom on an ERC20 is byte4(keccak256("transferFrom(address,address,uint256)")). Notice the absence of spaces and the canonical uint256 instead of the shorter uint. It can also be accessed with ERC20.transferFrom.selector.

  6. abi.encodeWithSignature takes the parameters and, depending on their types, pads them to 32 bytes and packs them together. Then prepends the lot with the 4-byte selector.

  7. _to.call is a bit low-level. In fact, you usually do, for instance ERC20(_to).transferFrom(...) instead of:

    _to.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", me, you, 123))
    
  8. .call returns 2 variables. The success, and IIRC, where to find the return value in memory. If you don't care about the return value, you can omit it. But it still needs the right type, hence the (success, ) =.