In Solidity, we can use the keyword payable
to specify that an address or a function can receive Ether. This article shows you how it works and how we can use it.
Ethereum Accounts and Transactions
Each Ethereum account, either an external account (human) or a contract account, has a balance that shows how much Ether it has. We can send a transaction to transfer Ether from one account to another, but not all addresses can receive Ether.
If we want to send Ether to an address, the address needs to be payable. If the target is a smart contract, it needs to have at least one of the following functions:
receive()
functionfallback()
function
which are declared as payable
. If none of these functions exists in the contract, the transfer will fail.
It means that if we want to create a smart contract that receives Ether, we will need to implement at least one of these functions. In a contract, we can have more than one payable
function, but this article will focus on the receive()
and fallback()
functions.
Receive Ether function
When we transfer Ether to a contract (i.e. plain Ether transfer), the receive()
function is executed as long as such function is defined in the contract.
The receive()
function is a special function to receive Ether in Solidity, and it has the following characteristics:
- It is declared without the
function
keyword. - It cannot have arguments.
- It cannot return data.
- It has
external
visibility and is markedpayable
.
You can find a very simple example of the receive()
function in the Solidity documentation as shown below:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; // This contract keeps all Ether sent to it with no way // to get it back. contract Sink { event Received(address, uint); receive() external payable { emit Received(msg.sender, msg.value); } }
In line 8, we can see the receive()
function. In this example, it simply logs the sender and the amount in the event.
receive() external payable { emit Received(msg.sender, msg.value); }
The following screenshot shows how this function works on Remix IDE. The logs show the amount when sending 100 wei to the contract.
The transaction will fail if the call is not a plain Ether transfer (i.e. the calldata is not empty). For example, the following screenshot shows an error message when specifying data, saying, "'Fallback' function is not defined"
.
Fallback Function
The fallback function runs when the signature of the called function does not match any of the existing functions in the contract. As the name suggests, the EVM cannot call any functions, so it falls back on this function. It has the following characteristics:
- It is declared without the
function
keyword. - It can receive data.
- It can return data.
- It has
external
visibility.
It needs to be marked payable
to receive Ether.
The Solidity documentation recommends always defining the receive()
function as well as the fallback()
function.
? A payable fallback function is also executed for plain Ether transfers, if no receive Ether function is present. It is recommended to always define a receive Ether function as well, if you define a payable fallback function to distinguish Ether transfers from interface confusions.
Letβs add a simple fallback function to the example we used in the previous section. In line 12, a new event called βCalledFallback
β is defined, and a fallback function is defined in line 13 – line 15, simply logging the event.
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; // This contract keeps all Ether sent to it with no way // to get it back. contract Sink { event Received(address, uint); receive() external payable { emit Received(msg.sender, msg.value); } event CalledFallback(address, uint); fallback() external payable { emit CalledFallback(msg.sender, msg.value); } }
When pressing the βTransact
β button without data, we see that the receive()
function is called.
But when pressing the button with data (β0x10
β as an example), we can see that fallback()
function is called.
Sending Ether From a Contract to Another Contract
We can send Ether from a contract to another contract. I have taken the following example from Solidity documentation, and have slightly modified it for demonstration purposes.
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.2 <0.9.0; contract Test { event CalledFallback(address); // This function is called for all messages sent to // this contract (there is no other function). // Sending Ether to this contract will cause an exception, // because the fallback function does not have the `payable` // modifier. fallback() external { emit CalledFallback(msg.sender); } } contract TestPayable { event CalledFallback(address, uint); event CalledReceive(address, uint); // This function is called for all messages sent to // this contract, except plain Ether transfers // (there is no other function except the receive function). // Any call with non-empty calldata to this contract will execute // the fallback function (even if Ether is sent along with the call). fallback() external payable { emit CalledFallback(msg.sender, msg.value); } // This function is called for plain Ether transfers, i.e. // for every call with empty calldata. receive() external payable { emit CalledReceive(msg.sender, msg.value); } function getBalance() external view returns (uint256){ return address(this).balance; } } contract Caller { function callTest(Test test) public returns (bool) { (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); require(success); // CalledFallback will be logged. // address(test) will not allow to call ``send`` directly, since ``test`` has no payable // fallback function. // It has to be converted to the ``address payable`` type to even allow calling ``send`` on it. address payable testPayable = payable(address(test)); // If someone sends Ether to that contract, // the transfer will fail, i.e. this returns false here. return testPayable.send(2 ether); } function callTestPayable(TestPayable test) public returns (bool) { (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); require(success); // CalledFallback will be logged. The balance will not change. (success,) = address(test).call{value: 1 ether}(abi.encodeWithSignature("nonExistingFunction()")); require(success); // CalledFallback will be logged. The balance will increase by 1 Ether. // If someone sends Ether to that contract, the receive function in TestPayable will be called. // Since that function writes to storage, it takes more gas than is available with a // simple ``send`` or ``transfer``. Because of that, we have to use a low-level call. (success,) = address(test).call{value: 2 ether}(""); require(success); // CalledReceive will be logged. The balance will increase by 2 Ether. return true; } // Add a receive function to fund the contract receive() external payable {} }
There are three contracts, Test
, TestPayable
and Caller
.
- The contract
Test
has a fallback function, which is not markedpayable
, so it cannot receive Ether. An event is defined in line 5, and thefallback()
function logs the event (line 12). - The contract
TestPayable
has two functions,fallback()
andreceive()
, both of which are payable, so they can receive Ether. As we saw in the previous section, if no data is sent with Ether, thereceive()
function is called; otherwise, thefallback()
function is called in this example. In lines 17 and 18, two events are defined and used in lines 25 and 31, respectively. In addition, another functiongetBalance()
is defined (line 34) to verify the Ether balance in the contract. - The contract
Caller
has two functions. The first functioncallTest()
takes the address of theTest
contract as an argument and calls a function nonExistingFunction()
. AsnonExistingFunction()
does not exist in the contractTest
, thefallback()
function will be called.
Then, it tries to send Ether to the contract. The contract Test
does not have a receive()
function or payable fallback()
function, so the contract address needs to be converted to address payable
(line 49) in order to execute send. Finally, it sends 2 Ether (line 53), but it will fail (i.e. the function will return False
), which is expected because the receiving fallback()
function is not payable.
The second function callTestPayable()
takes the address of the TestPayable
contract as an argument and again calls a function nonExistingFunction()
. Like the previous example, the fallback()
function will be called because nonExistingFunction()
does not exist in the contract TestPayable
. As no Ether was sent, the balance of the contract TestPayable
will not change. Then, it sends 1 Ether to the same function. As the fallback()
function is marked as payable
, the call will be successful, and the balance will increase by 1 Ether. Lastly, it sends 2 Ether to the contract, which will call the receive()
function and increase the balance by 2 Ether.
The Caller
contract also has a function receive()
because the contract needs to have some Ether to send to Test
and TestPayable
contracts. Before running the callTestPayable
, make sure to fund at least 3 Ether to the Caller contract; otherwise, the calls will fail.
Summary
Each Ethereum account has a balance that shows how much Ether it has, and we can transfer Ether from one account to another, but when sending Ether to a contract, the receiving functions need to be marked payable.
A contract can receive Ether by specifically implementing at least one of the following functions as payable:
receive()
functionfallback()
function
If you define a payable
fallback function, it is recommended to define a receive Ether function.
Learn Solidity Course
Solidity is the programming language of the future.
It gives you the rare and sought-after superpower to program against the “Internet Computer”, i.e., against decentralized Blockchains such as Ethereum, Binance Smart Chain, Ethereum Classic, Tron, and Avalanche – to mention just a few Blockchain infrastructures that support Solidity.
In particular, Solidity allows you to create smart contracts, i.e., pieces of code that automatically execute on specific conditions in a completely decentralized environment. For example, smart contracts empower you to create your own decentralized autonomous organizations (DAOs) that run on Blockchains without being subject to centralized control.
NFTs, DeFi, DAOs, and Blockchain-based games are all based on smart contracts.
This course is a simple, low-friction introduction to creating your first smart contract using the Remix IDE on the Ethereum testnet – without fluff, significant upfront costs to purchase ETH, or unnecessary complexity.
Programmer Humor
Q: What is the object-oriented way to become wealthy?
π°
A: Inheritance.