Smart-contract development

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 these

Two 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.