Receive and Fallback Functions in Solidity for Ether Handling
Receive Function
A contract can declare at most one receive function.
receive() external payable {
// Logic executed upon receiving Ether
}
Key Characteristics:
- Declared without the
functionkeyword. - Must be marked
external payable. - Cannot have arguments or return values.
- Is called on plain Ether transfers (e.g., via
.send()or.transfer()). - It can use modifiers and perform arbitrary logic within gas limits.
- If a contract needs to accept Ether and lacks a
payablefallback function, it must implement areceivefunction.
Example Implementation:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ReceiverExample {
event DepositReceived(address indexed from, uint256 amount);
receive() external payable {
emit DepositReceived(msg.sender, msg.value);
}
}
Fallback Function
A contract can declare at most one fallback function.
// Minimal declaration
fallback() external [payable] {
// Logic when no other function matches
}
// Declaration with data handling
fallback(bytes calldata inputData) external [payable] returns (bytes memory outputData) {
// Logic with input/output data
}
Key Characteristics:
- Declared without the
functionkeyword. - Must be marked
external;payableis optional. - Executed when a call to the contract does not match any funciton signature, or if no data is supplied (if
receiveis absent). - It can serve as a backup for receiving Ether if made
payable. - Can use modifiers and perform complex operations.
- The version with parameters allows inspection and response to arbitrary call data.
Example Contracts Demonstrating Behavior:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ContractA {
uint256 public stateVar;
// Non-payable fallback; contract rejects Ether transfers.
fallback() external {
stateVar = 100;
}
}
contract ContractB {
uint256 public valueX;
uint256 public valueY;
// Payable fallback for calls with unmatched signatures.
fallback() external payable {
valueX = 10;
valueY = msg.value;
}
// Receive function for plain Ether transfers.
receive() external payable {
valueX = 20;
valueY = msg.value;
}
}
contract CallerExample {
function interactWithContractA(ContractA targetContract) public returns (bool) {
// Call a non-existent function, triggering fallback.
(bool callSuccess, ) = address(targetContract).call(
abi.encodeWithSignature("fakeFunction()")
);
require(callSuccess); // stateVar becomes 100.
// Attempting to send Ether will fail.
return payable(address(targetContract)).send(1 ether); // Returns false
}
function interactWithContractB(ContractB targetContract) public returns (bool) {
// Call non-existent function without value.
(bool success, ) = address(targetContract).call(
abi.encodeWithSignature("fakeFunction()")
);
require(success); // valueX = 10, valueY = 0.
// Call non-existent function with Ether value.
(success, ) = address(targetContract).call{value: 1 ether}(
abi.encodeWithSignature("fakeFunction()")
);
require(success); // valueX = 10, valueY = 1 ether.
// Plain Ether transfer triggers receive().
(success, ) = address(targetContract).call{value: 2 ether}("");
require(success); // valueX = 20, valueY = 2 ether.
return true;
}
}
Execution Priority: For an incoming call with Ether:
- If the call data is empty, the
receive()function is executed if it exists. - If
receive()does not exist, but apayable fallback()exists,fallback()is executed. - If neither exists, the call reverts.
For an incoming call without Ether but with unmatched call data, the
fallback()function is executed if it exists.