fallback and receive functions
fallback and receive are a contract's two catch-all entry points. receive handles the simplest case: someone sends the contract plain Ether with no data attached. fallback handles calls that match no named function — and it is the engine behind every proxy contract, since 'a call to a function I do not have' is exactly what a proxy wants to forward.
The dispatch rule is precise. If a call arrives with empty calldata, the EVM runs receive() when it is defined, otherwise it falls back to fallback(). If a call arrives with non-empty calldata whose leading 4-byte selector matches no function, fallback() runs. receive() must be declared external payable; fallback() is external and may optionally be payable. For a contract to accept a plain ETH transfer at all, one of these must exist and be payable — otherwise the transfer reverts.
Two practical points. Proxies put their delegatecall-forwarding logic in fallback(), so any unknown selector is routed to the implementation contract. And beware the 2,300-gas stipend: the legacy .transfer() and .send() methods forward only 2,300 gas to the recipient's receive/fallback — enough to emit an event but not to perform a storage write — so a recipient that does real work in receive/fallback will revert under them. That brittleness is a major reason the ecosystem moved to call{value: amount}("") paired with a reentrancy guard for sending Ether.
receive() external payable {} // plain ETH, empty calldata
fallback() external payable { // unknown selector
// a proxy delegatecalls to its implementation here
}
// .transfer()/.send() forward only 2300 gas to theseTwo catch-alls: bare Ether, and unmatched calls.
Do not rely on .transfer()/.send() to pay a contract: their 2,300-gas stipend bricks any recipient that does more than trivial work. Prefer call{value:...}("") with a reentrancy guard and a checked return value.