In Solidity, function calls are the main way contracts communicate with each other. While high-level calls (like directly calling a function on a contract) are generally safer, sometimes low-level calls are necessary to have more control or interact with unknown contract interfaces. This article will provide a detailed, beginner-friendly look at one such low-level function: call.
What is call in Solidity?
The call function in Solidity is a low-level function that allows you to interact with other contracts and addresses. Unlike high-level function calls, call provides no type safety, lacks automatic revert handling, and requires more care in usage. Despite these challenges, call is powerful because it can be used even if you don’t have the ABI of the other contract. It’s commonly used to:
Send Ether to a contract or address.
Call functions that may or may not exist on the target contract.
Call a contract’s fallback or receive function directly.
When Should You Use call?
You should use call mainly in these scenarios:
Sending Ether:
callis recommended for transferring Ether to another contract or account, as it enables specifying a custom gas amount.Fallback/Receive Function Calls: When calling a function that might not exist or is unknown, using
callallows you to handle it gracefully if the function is missing.
Reasons to Avoid call for Regular Function Calls
For calling specific functions, high-level calls (direct function calls or using interfaces) are generally better. Using call is not recommended unless you need it, for the following reasons:
No Revert Propagation:
calldoes not automatically bubble up reverts from the called contract, which can lead to silent failures.No Type Safety: Solidity skips type checks with
call, making it easier to call functions with incorrect parameters.No Existence Check:
callwon’t verify that a function exists before executing, which can trigger a fallback function if one is present.
The Syntax of call
Here's the basic syntax of call in Solidity:
(bool success, bytes memory data) = address.call{value: msg.value, gas: 5000}(abi.encodeWithSignature("functionName(arguments)", args));
success: Boolean value that indicates if the call was successful.
data: Contains any returned data from the called contract.
address.call: Specifies the address being called.
abi.encodeWithSignature: Encodes the function signature and arguments.
Example of call Usage
Below is an example that demonstrates the usage of call between two contracts, Caller and Receiver. Receiver has a function foo that emits an event, and a fallback function to handle calls that don’t match any existing functions.
Receiver contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Receiver {
event Received(address caller, uint256 amount, string message);
fallback() external payable {
emit Received(msg.sender, msg.value, "Fallback was called");
}
function foo(string memory _message, uint256 _x) public payable returns (uint256) {
emit Received(msg.sender, msg.value, _message);
return _x + 1;
}
}
Caller contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Caller {
event Response(bool success, bytes data);
function testCallFoo(address payable _addr) public payable {
(bool success, bytes memory data) = _addr.call{
value: msg.value,
gas: 5000
}(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));
emit Response(success, data);
}
function testCallDoesNotExist(address payable _addr) public payable {
(bool success, bytes memory data) = _addr.call{value: msg.value}(
abi.encodeWithSignature("doesNotExist()")
);
emit Response(success, data);
}
}
Explanation
testCallFoo: Calls thefoofunction onReceiverusingcall, passing"call foo"and123as arguments.testCallDoesNotExist: Attempts to call a non-existent function. SinceReceiverdoesn’t have adoesNotExistfunction, thefallbackfunction is triggered instead, which emits a fallback event.
The following image shows the logs generated when testCallFoo is called on Receiver:

The next image shows the logs produced when testCallDoesNotExist is called on Receiver, triggering its fallback function:

Practical Tips and Caveats
- Error Handling in
callUnlike high-level calls,calldoes not automatically revert if it fails. You must manually check thesuccessboolean. Ifsuccessis false, handle the error appropriately, either by reverting or logging an error.
(bool success, bytes memory data) = address.call(...);
if (!success) {
revert("Call failed");
}
Beware of Reentrancy Attacks
Reentrancy is a vulnerability where an external contract repeatedly calls back into the calling contract before the initial execution is complete. Mitigate this risk by using the checks-effects-interactions pattern, where external calls are placed last.Empty Addresses
Unlike high-level calls,calldoes not check if an address has deployed code. If you call an address with no code, it may succeed without doing anything. To avoid this, add a check before calling:
require(_addr.code.length > 0, "Target address is not a contract");
Advanced Usage: Using call for Fund Transfers
Another common use of call is sending Ether. In the example below, ContractTwo sends Ether to ContractOne, which increments the sender's balance.
ContractOne and ContractTwo Example
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;
contract ContractOne {
mapping(address => uint) public addressBalances;
receive() external payable {
addressBalances[msg.sender] += msg.value;
}
}
contract ContractTwo {
function depositOnContractOne(address _contractOne) public payable {
(bool success, ) = _contractOne.call{value: 10, gas: 100000}("");
require(success, "Transfer failed");
}
}
In this example:
ContractOnehas areceivefunction to accept Ether and record it inaddressBalances.ContractTwosends 10 wei toContractOneusing call.
Conclusion
The call function in Solidity is a powerful low-level tool that allows for flexible interaction between contracts. However, it should be used with caution due to the lack of type checking, revert handling, and existence checks. When used correctly and with proper error handling, call can be invaluable for tasks like Ether transfers and dynamic function calls.
To minimize risks, always:
Check the success flag.
Validate contract addresses before calling.
Follow me for more insights on Solidity, EVM, and blockchain development!👨💻
Top comments (2)
Excellent work!!!
Thanks a lot!!!