What are Payable Functions in Solidity?

The keyword payable allows someone to send ether to a contract and run code to account for this deposit.

This code could potentially log an event, modify storage to record the deposit, or it could even revert the transaction if it chooses to do so.

When a developer explicitly marks a smart contract with the payable type, they are saying “I expect ether to be sent to this function”. To understand why this is important, imagine how bad it would be if someone sent ether to a contract and the developer did not write code to handle that event. In that case, it would be highly possible that the ether could be locked forever or never withdrawn by its intended recipient.

This article will cover:

  1. How to send ether to a smart contract
  2. A payable function example
  3. A revertible payable function Example
  4. Writing payable function logic

How to Send Ether to a Smart Contract

Sending ether is a native function of the Ethereum Virtual Machine (EVM). This is different from any other transfer in the EVM which requires the developer to write custom logic inside a smart contract to handle the transfer (i.e. for NFTs or ERC20s).

When someone sends ether to a smart contract, they do so through a value field on the transaction itself. Let’s take a look at what a transaction looks like in JSON:

    "to": "0x5baf84167cad405ce7b2e8458af73975f9489291",
    "value": "0xb1a2bc2ec50000", // 1 ether 
    "data": "0xd0e30db0" // deposit() 
    // ... other properties

This transaction sends 1 ether to the address 0x5baf84167cad405ce7b2e8458af73975f9489291. If this address is a smart contract, it will attempt to parse the calldata (data) to figure out which smart contract function this user is attempting to call (in this case it is deposit()).

Depending on whether the function is payable or non-payable, one of two things will happen:

  1. If the function is a payable function, then it will run the logic.
  2. If the function is not payable, the transaction will revert and funds will be returned minus the gas cost for the transaction.

What is an example of a Solidity payable function?

Here is an example of a basic payable function in Solidity with the deposit function:

function deposit() payable external {
    // no need to write anything here!

Notice, in this case, we didn’t write any code in the deposit function body. Writing a payable function alone is enough to receive ether and you may not need to write any logic.

For example, if this was a payable smart contract that was controlled by a charity accepting cryptocurrency donations, perhaps users would just call deposit and the charity would eventually be able to withdraw these contributions to an address of their choosing. In that case, it may be better to write a receive function:

receive() external payable {
    // this built-in function doesn't require any calldata,
    // it will get called if the data field is empty and 
    // the value field is not empty.
    // this allows the smart contract to receive ether just like a 
    // regular user account controlled by a private key would.

What is an example of a Solidity payable function that can revert?

A payable smart contract function can revert. Here is an example of a revertible payable function that uses two require statements for msg.value and balances[msg.sender].

mapping(address => uint) balances;

function deposit() payable external {
    // deposit sizes are restricted to 1 ether
    require(msg.value == 1 ether);
    // an address cannot deposit twice
    require(balances[msg.sender] == 0);
    balances[msg.sender] += msg.value;

If either of the require statements are not true, the transaction would revert and the sender would receive their funds back.

Why might we write logic in a payable function?

If we had a smart contract where we needed to keep track of who deposited which ether, we might keep track of that in storage:

mapping(address => uint) balances;

function deposit() payable external {
    // record the value sent 
    // to the address that sent it
    balances[msg.sender] += msg.value;

The msg.value here corresponds with the value field encoded in the transaction we looked at in the “how to send ether” section. As a Solidity developer, we can tap into message value to record deposits and map it to some internal balance for the address making this transaction.

Why is it called msg.value?

In the EVM, interactions with smart contracts are referred to as message calls. This is true whether a user is calling a smart contract directly or if a smart contract is calling another smart contract (internal transaction).

Solidity Payable Functions

In summary, a payable function is a function that can receive ether. It provides the developer with the opportunity to respond to an ether deposit for record-keeping or any additional necessary logic.