Fallback and Receive Functions
Introduction
Fallback and receive functions are special functions in Solidity that handle unexpected or direct Ether transfers. This guide covers their usage, differences, and best practices.
Basic Usage
Fallback Function
solidity
contract FallbackExample {
event FallbackCalled(address sender, uint256 value, bytes data);
// Called when no other function matches or
// when Ether is sent with data
fallback() external payable {
emit FallbackCalled(msg.sender, msg.value, msg.data);
}
// Basic function to compare
function normalFunction() public pure returns (string memory) {
return "Normal function called";
}
}
Receive Function
solidity
contract ReceiveExample {
event Received(address sender, uint256 value);
// Called when Ether is sent with empty calldata
receive() external payable {
emit Received(msg.sender, msg.value);
}
// Check contract balance
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Advanced Usage
Function Selection
solidity
contract FunctionSelector {
event FallbackCalled(bytes data);
event ReceiveCalled(uint256 value);
// Called for empty calldata with Ether
receive() external payable {
emit ReceiveCalled(msg.value);
}
// Called for non-empty calldata or
// when receive doesn't exist
fallback() external payable {
emit FallbackCalled(msg.data);
}
// Function selection flow:
// 1. If msg.data is empty and receive exists -> receive()
// 2. If msg.data is not empty or receive doesn't exist -> fallback()
// 3. If neither exists -> transaction reverts
}
Proxy Pattern
solidity
contract ProxyContract {
address public implementation;
constructor(address _implementation) {
implementation = _implementation;
}
// Forwards all calls to implementation
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
receive() external payable {
// Handle direct Ether transfers
}
}
Best Practices
Function Usage
- Use receive for plain Ether transfers
- Use fallback for unknown functions
- Keep functions simple
- Handle errors gracefully
Security
- Validate inputs
- Check msg.value
- Limit functionality
- Prevent reentrancy
Gas Efficiency
- Minimize operations
- Avoid complex logic
- Consider gas limits
- Optimize storage
Common Patterns
Payment Handler
solidity
contract PaymentHandler {
event PaymentReceived(address from, uint256 amount);
event PaymentForwarded(address to, uint256 amount);
address payable public owner;
constructor() {
owner = payable(msg.sender);
}
receive() external payable {
emit PaymentReceived(msg.sender, msg.value);
// Forward payment to owner
(bool success,) = owner.call{value: msg.value}("");
require(success, "Forward failed");
emit PaymentForwarded(owner, msg.value);
}
fallback() external payable {
revert("Use receive function");
}
}
Smart Wallet
solidity
contract SmartWallet {
mapping(address => bool) public authorized;
constructor() {
authorized[msg.sender] = true;
}
receive() external payable {
// Accept Ether
}
fallback() external payable {
// Handle unknown calls
require(authorized[msg.sender], "Not authorized");
address target = address(bytes20(msg.data[0:20]));
bytes memory data = msg.data[20:];
(bool success,) = target.call{value: msg.value}(data);
require(success, "Call failed");
}
function authorize(address user) external {
require(authorized[msg.sender], "Not authorized");
authorized[user] = true;
}
}
Practice Exercise
Create contracts that:
- Handle Ether transfers
- Implement proxy pattern
- Forward payments
- Validate calls
- Manage permissions
Key Takeaways
- Understand function selection
- Implement security checks
- Handle Ether properly
- Follow best practices
- Consider gas costs
Remember: Fallback and receive functions are powerful but require careful implementation.