What is “payable” in Solidity?

5/5 - (1 vote)

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() function
  • fallback() 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 marked payable.

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.

Sending 100 wei to the Sink contract
Sending 100 wei to the Sink 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"

Error sending 100 wei when a fallback function does not exist
Error sending 100 wei when a fallback function does not exist

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.

Calling the receive function
Calling the receive function 

But when pressing the button with data (β€œ0x10” as an example), we can see that fallback() function is called.

Calling the fallback function
Calling the fallback function 

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 marked payable, so it cannot receive Ether. An event is defined in line 5, and the fallback() function logs the event (line 12).
  • The contract TestPayable has two functions, fallback() and receive(), both of which are payable, so they can receive Ether. As we saw in the previous section, if no data is sent with Ether, the receive() function is called; otherwise, the fallback() 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 function getBalance() is defined (line 34) to verify the Ether balance in the contract.
  • The contract Caller has two functions. The first function callTest() takes the address of the Test contract as an argument and calls a function nonExistingFunction(). As nonExistingFunction() does not exist in the contract Test, the fallback() 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() function
  • fallback() 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.