call is a low-level member function of the address type in Solidity used to interact with other contracts.
Call
We introduced using call to send ETH in the guide Sending ETH, and in this lesson we will cover how to use it to call contracts.
call returns a tuple of (bool, data), which correspond to whether the call succeeded and the return value of the target function, respectively.
callis the official Solidity-recommended method for sendingETHby triggering thefallbackorreceivefunctions.- Using
callto call another contract is not recommended, because when you call a function from an untrusted contract, you cede control to it. The recommended method is still to declare a contract variable and call its functions, see Calling Other Contracts - When you do not have the source code or
ABIof a target contract, you cannot generate a contract variable for it; in this case, you can still call the target contract's functions usingcall.
Usage Rules for call
The usage rules for call are as follows:
targetContractAddress.call(encodedData);
Where encodedData is obtained using the structured encoding function abi.encodeWithSignature:
abi.encodeWithSignature("functionSignature", commaSeparatedParameters)
The functionSignature is formatted as "functionName(commaSeparatedParameterTypes)". For example, abi.encodeWithSignature("f(uint256,address)", _x, _addr).
Additionally, when using call to call a contract, you can specify the amount of ETH to send and the gas limit for the transaction:
targetContractAddress.call{value: amountToSend, gas: gasAmount}(encodedData);
This may seem a bit complex, so let's walk through an example of using call below.
Target Contract
First, we will write and deploy a simple target contract OtherContract. The code is largely the same as the one in Calling Other Contracts, with the addition of a fallback function.
contract OtherContract { uint256 private _x = 0; // State variable x // Event emitted when ETH is received, records amount and gas used event Log(uint amount, uint gas);fallback() external payable{} // Returns the contract's ETH balance function getBalance() view public returns(uint) { return address(this).balance; } // Function that modifies the state variable _x, and can receive ETH (payable) function setX(uint256 x) external payable{ _x = x; // If ETH is sent, emit the Log event if(msg.value > 0){ emit Log(msg.value, gasleft()); } } // Reads the value of x function getX() external view returns(uint x){ x = _x; }
}
This contract includes a state variable x, an event Log triggered when ETH is received, and three functions:
getBalance(): Returns the contract'sETHbalance.setX():external payablefunction that sets the value ofxand can receiveETHsent to the contract.getX(): Reads the value ofx.
Calling the Target Contract Using call
1. Response Event
We will write a Call contract to call the target contract's functions. First, we will define a Response event that outputs the success and data returned by call to make it easier to observe the return values.
// Define the Response event, outputs the success and data returned by call
event Response(bool success, bytes data);
2. Calling the setX Function
We will define a callSetX function to call the target contract's setX() function, send msg.value amount of ETH, and emit the Response event to output success and data:
function callSetX(address payable _addr, uint256 x) public payable { // Call setX(), and send ETH at the same time (bool success, bytes memory data) = _addr.call{value: msg.value}( abi.encodeWithSignature("setX(uint256)", x) );emit Response(success, data); // Emit event
}
Next, we will call callSetX to set the state variable _x to 5, using the OtherContract address and 5 as parameters. Since the target function setX() has no return value, the data output by the Response event will be 0x, which is empty.
3. Calling the getX Function
Below we will call the getX() function, which will return the value of the target contract's _x variable, with type uint256. We can use abi.decode to decode the data return value from call and retrieve the numeric value.
function callGetX(address _addr) external returns(uint256){ // Call getX() (bool success, bytes memory data) = _addr.call( abi.encodeWithSignature("getX()") );emit Response(success, data); // Emit event return abi.decode(data, (uint256));
}
From the output of the Response event, we can see that data is 0x0000000000000000000000000000000000000000000000000000000000000005. After decoding with abi.decode, the final return value is 5.
4. Calling a Non-Existent Function
If the function you pass to call does not exist in the target contract, the target contract's fallback function will be triggered.
function callNonExist(address _addr) external{ // Call getX() (bool success, bytes memory data) = _addr.call( abi.encodeWithSignature("foo(uint256)") );emit Response(success, data); // Emit event
}
In the example above, we used call to call the non-existent foo function. call will still execute successfully and return success, but it will actually call the target contract's fallback function.
As you can see, call allows us to call a target contract without knowing its source code or ABI, which is very useful. However, this is not a recommended method because it is insecure.
This is a discussion topic split from the original thread at https://juejin.cn/post/7368419638986915840


