In this article, we will focus on an important topic, the receive Ether function in Solidity. This function allows us to receive the currency and is the first of the two possible approaches for receiving currency. The other approach is the fallback function, and we’ll briefly introduce it here to become aware of the wider picture. We’ll also cover the fallback function in the following article in more detail.
It’s part of our long-standing tradition to make this (and other) articles a faithful companion, or a supplement to the official Solidity documentation.
Overview
When our contract receives a transaction without a matching function, a special function called receive()
is automatically executed.
Of course, we imply that our contract contains such a function. The contract can receive incoming Ether using this function and execute operations like putting the received amount in a balance or invoking other contract procedures.
We should differentiate the receive()
function, which is a callback function, from a fallback function (a point for the upcoming section).
Implementation
A contract may have only one receive Ether function. The function is declared as:
receive() external payable { ... }
Note how we omitted the keyword function
.
There are several peculiarities regarding the receive function: it cannot have any arguments, doesn’t return anything, and must have external visibility and payable
state mutability.
Besides that, the receive function can be virtual, it can override its base function and also have modifiers.
π‘ Note:
π A function (also called a base function) that allows an inheriting contract to override its behavior will be marked with a keyword virtual
. (source)
π A function that overrides that base function should be marked with the keyword override
. (source)
Receive Function Properties
The receive
function gets executed when a call with empty calldata
arguments is made on a contract.
For instance, the receive
function is executed on simple, plain Ether transfers, such as the ones made via .send()
or .transfer()
functions. However, if we didn’t introduce a receive function, but do have a payable fallback function (in more detail in the following article), the fallback function will be activated on a plain Ether transfer.
We can already notice that a fallback function is an alternative to having a receive function. A few paragraphs later, we’ll see if they’re almost equivalent and why. Finally, if we don’t have a receive function or a fallback function, our contract won’t be able to receive Ether via regular transactions and will throw an exception.
There’s a possible confusion between the terms fallback function and callback function, so let’s first get that out of the way:
- A fallback function is executed if (1) none of the other functions match the function identifier, or (2) no data (arguments) was provided with the function call. (source) A fallback function shouldn’t be confused with a callback function.
- A callback function is a function passed as an argument to a called function. After the called function is executed successfully, the callback function gets called. In practice, a callback function construct is commonly used for asynchronous behavior, where we want an activity to take place after the previous event completes. (source)
Operations consuming more than 2300 gas are:
- writing to storage,
- creating a contract,
- calling an external function that consumes a large amount of gas, and
- sending Ether.
They can only execute if the receive function is called by the low-level .call function.
β‘ Warnings β‘
Although available, payable fallback methods aren’t recommended for receiving Ether. It’s much better to have a separate receive function for receiving Ether and a fallback function to cover all other cases. This approach will make our debugging easier and enable healthy separation of functionality.
There are two special cases where our contract will be able to receive Ether without a receive function.
It will do so as a recipient of a coinbase transaction, also known as a miner block reward, and as a destination of a keyword selfdestruct
.
A contract is not able to react to Ether transfers of this kind and therefore cannot decline them. This is an inherent property stemming from the EVM design choice that Solidity cannot bypass.
The consequence of such implementation may be an unexpected value of address(this).balance
, possibly higher than the amount accounted for by the contract logic.
Receive Function Example
In the following example, we’ll see what a contract’s receive function looks like in its simplest form.
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract Sink {
Here we define an event that will be emitted when the receive function is executed.
event Received(address, uint);
This is the receive function declaration which usually contains more complex logic. In this example, it just emits the event (signal) letting us know that the receive function finished its execution.
receive() external payable { emit Received(msg.sender, msg.value); } }
Conclusion
In this article, we went through the details of receive
function in Solidity. We also mentioned its alternative, the fallback function. We learned that the receive function allows us to receive the currency and is the first and the preferred one of the two possible choices for receiving currency.
First, we briefly overview the purpose behind the receive function in Solidity.
Second, we discussed the function implementation, i.e., what syntax we should use.
Third, we took a closer look at the function properties, learned how it works, and got familiar with a few use specifics in the form of warnings.
Fourth, we made a simple example showing how a receive function looks in action.
What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):