Deciphering Solidity: Does adding a PAYABLE keyword actually save GAS?

An interesting explanation behind the mysterious gas consumption in Payable & Non-Payable functions in Solidity

Deciphering Solidity: Does adding a PAYABLE keyword actually save GAS?

Hey, fam ๐Ÿ‘‹๐Ÿป

Welcome to another article in the Decipher Series, where we take one specific Smart Contract/Web3 topic and get to the very bottom of it.

If you have been developing smart contracts using Solidity lately, chances are you might have come across the payable keyword.

This blog is specifically about the same where we decipher all its interesting and weird secrets. ๐Ÿ˜ƒ

Quick Intro: Basics of the Payable Keyword

Out of all the wonderful things that a smart contract can do, storing your money(ETH) is one of them. Now in order to take receive ETH in a smart contract, Solidity language has got a specific keyword called payable.

A payable keyword, in very simpler terms, is a modifier in the solidity language that can be attached to any function. Once attached, this keyword allows the function to receive ether. In other words, while triggering a function with a payable keyword, you can send ether (msg.value) along with that transaction.

While this is all good, I came across an interesting caveat around the payable keyword while scrolling through Twitter a couple of months ago. It grabbed all my attention and I figured that an interesting (but really wired) scenario takes place whenever a payable modifier is attached to any function ๐Ÿ‘€.

Letโ€™s take a quick look at this interestingly wired scenario:

A setter function with NO Payable Keyword

In the image attached above, we have a very simple setter function that sets the uint256 variable state to 100. If you trigger this function, you will find that the transaction gas cost is somewhere around 43300.

Alright, now letโ€™s look at a 2nd condition.

A setter function WITH Payable Keyword

In the 2nd case, we have the exact same function that executes an exactly similar transaction of setting a state variable. However, the only difference here is an additional payable modifier attached to the function.

Quite interestingly if you look at the transaction gas cost for calling this function, it's around 43276 which is lower than the function with no payable keyword mentioned above.

Yes, you got that right.

Adding a simple payable keyword just reduced the amount of gas consumption in the function. ๐Ÿ˜ƒ

The AHA Moment ๐Ÿ’ก๐Ÿ’ก

Alright, now it's time to understand โ€” Why exactly does the payable modifier lower the Gas consumption?

A very simple answer to that question is:

Adding a payable keyword lowers the number of opcodes being executed, thus lowering the amount of gas consumption.

Thatโ€™s weird, isnโ€™t it? How does adding an extra modifier to a function lower down the number of opcodes instead of increasing it???

Well, hereโ€™s some technical (and logical) explanation for it. ๐Ÿ˜ƒ

  1. As we already know, for a function to be capable of receiving ether, a payable modifier must be attached to it. While a function without any payable modifier shall never be able to receive any ether.
  2. It must be noted that this is a strict rule in Solidity and therefore if you try to pass Ether while calling a non-payable function, it will simply revert.
  3. Therefore in the case of Non-Payable functions, there are additional opcodes that are executed while calling a non-payable function which ensures that the function shall only be executed if the ether (msg.value) that is sent along with the transaction is exactly equal to ZERO.
  4. However, the same is not true for Payable function. Payable functions allow users to pass in both non-zero or zero ether values while calling the function.
  5. This basically means that even if zero ether (msg.value == 0) is sent while calling a payable function, the transaction is not reverted. Hence, there is no need to explicitly check the msg.value in the case of Payable functions.

In a Nutshell ๐Ÿฅœ

GAS Difference between PAYABLE and NON-PAYABLE Functions in Solidity

In the case of Non-Payable Functions:

a. Additional checks are included to ensure that no ether value is passed while calling the function.

b. These checks increase the number of opcodes being executed.

c. The increased number of opcodes ultimately results in higher gas usage.

In the case of Payable functions:

a. No additional checks are required since the function can accept both zero or non-zero values of ether.

b. No additional checks means no additional opcodes being executed.

c. Lower opcodes in execution means lower consumption of gas.

My TWO CENTS ๐Ÿช™๐Ÿช™

Does all of the above-mentioned details, mean we should use PAYABLE Functions to save gas?

Well, that could be a discussion.

Gas optimization is undoubtedly something that every smart contract wizard dreams of in their contracts.

However, an imperative question is:

While saving gas is important, itโ€™s not really a good idea to compromise on the intended behavior of the function, minimizing the use of necessary state changes, or using inadequate tactics just to save a few extra amounts of gas.

In other words, if a function has nothing to do with receiving ether, then it should not really have any payable keyword attached to it, even if that saves you some gas.
Dropping โ€™s 2 Cents as well ๐Ÿ˜‡

Therefore, I firmly believe that adding an unnecessary payable keyword in a function just to save gas is probably a bad decision. The above-mentioned scenario of reduction in gas while adding the payable keyword is just a wired solidity language design that is not at all effective.