I’ve recently been re-learning solidity, consolidating the details, and writing a “Solidity Minimalist Primer” for beginners to use (programming guys can find another tutorial), updated 1-3 times a week.
All code and tutorials are open source on GitHub: github.com/AmazingAng/WTFSolidity
In Lecture 17: Sending ETH, we talked about using call to send ETH, and in this lecture we will show you how to use it to call contracts.
call is a low-level member function of the address type, which is used to interact with other contracts. It returns (bool, data), which corresponds to the success of the call and the return value of the objective function, respectively.
call is the officially recommended method of sending ETH by triggering the fallback or receive functions by solidity. It is not recommended to call another contract with a call, because when you call the function of an insecure contract, you give it the initiative. The recommended method is still to call the function after declaring the contract variable, see Lecture 19: Calling Other Contracts. If we don’t know the source code or ABI of the other party’s contract, we can’t generate contract variables, but we can still call the other party’s contract functions through call.
The rules for using call are as follows:

The binary encoding uses the structured encoding function abi.encodeWithSignature to obtain:

The function signature is “Function Name (comma-separated parameter type)”. For example, abi.encodeWithSignature(“f(uint256,address)”, _x, _addr).
In addition, when calling the contract, you can specify the amount of ETH and gas to be sent by the transaction:

It seems a bit complicated, so let’s take an example of a call application.
Let’s start by writing a simple target contract, OtherContract, and deploying it, the code is basically the same as in Lecture 19, except with the addition of a fallback function.

This contract contains a state variable x, an event log that is triggered when ETH is received, and three functions:
getBalance(): Returns the ETH balance of the contract. setX(): external payable function, which can set the value of x and send ETH to the contract. getX(): Reads the value of x.
Let’s write a Call contract to call the target contract function. First of all, write a definition of a Response event, and output the success and data returned by the call, so that we can observe the return value.

We define the callSetX function to call setX() of the target contract, transfer the amount of ETH to msg.value, and release the Response event to output success and data:

Next, we call callSetX to change the state variable _x to 5, and the parameters are the OtherContract address and 5, because the objective function setX() does not return a value, so the data output of the Response event is 0x, which is empty.
Let’s call the getX() function, which will return the value of the target contract _x, of type uint256. We can use abi.decode to decode the return value of the call, data, and read out the value.

From the output of the Response event, we can see that the data is 0x0000000000000000000000000000000000000000000000000000000000000005. After abi.decode, the final return value is 5.
If the function we input to the call does not exist in the target contract, then the fallback function of the target contract will be triggered.

In the example above, we called the non-existent foo function. The call can still be executed successfully and return success, but it is actually the fallback function of the target contract called.
In this talk, we showed how to use the low-level function call to call other contracts. call is not the recommended method for calling a contract because it is not secure. But it’s useful to let us call the target contract without knowing the source code and ABI.