paint-brush
How the Ethereum Blockchain Incentivizes Users to Clear Storage by@zartaj
164 reads

How the Ethereum Blockchain Incentivizes Users to Clear Storage

by Md Zartaj AfserApril 4th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

EVM has the functionality of rewarding users with a gas refund for clearing the on-chain storage occupied by the contract data. There have been some changes after the London fork. To understand it better we will look at the scenario before London Upgrade and then we will see the changes proposed in the London upgrade.
featured image - How the Ethereum Blockchain Incentivizes Users to Clear Storage
Md Zartaj Afser HackerNoon profile picture

Did you know that EVM has the functionality of rewarding users with a gas refund for clearing the on-chain storage occupied by contract data? Let’s learn more about this. There have been some changes after the London fork, but to understand it better we will look at the scenario before London Upgrade and then we will see the changes proposed in the London upgrade.


But before starting with refunds we should get a bit of understanding about storage gas consumption; this will make gas calculations easy to understand.


I have copied some gas amounts directly from the Ethereum Yellow paper, let’s see what are they:


1. Gcoldsload 2,100 Cost of a cold storage access.

2. Gsset 20,000 Paid for an SSTORE operation when the storage value is set to non-zero from zero.

3. Gsreset 2,900 Paid for an SSTORE operation when the storage value’s zeroness remains unchanged or is set to zero.

4. Rsclear 15,000 Refund given (added into refund counter) when the storage value is set to zero from non-zero.


Let’s quickly understand this:

1. Gcoldsload — you have to pay every time you access any storage variable for the first time in a function, the second or consecutive times it costs 100 gas.

2. Gsset — Pay every time you set any variable from zero value to non-zero (false to true in case of bools). In simple words, you are changing the default value and the nodes now have to keep track of that slot.

3. Gsreset — Pay every time you set a non-zero value to a non-zero or zero value.

4. Rsclear — Whenever you set any value to its default value you get the refund.


Remember these points before we start

  • Whenever we set any value from non-zero to non-zero or non-zero to zero, we collectively say the gas consumed to be 5,000, adding Gcoldsload and Gsreset.

  • Every transaction will consume all the related gas while executed and the refund is calculated at the very end of the transaction.

  • There is an initial gas attached to every transaction which is 21,000. This gas is consumed by the validator to determine whether the transaction is valid or not. 21,000 is added to every transaction and it is not in our control, so every transaction consumes 21,000 + whatever gas the function data needs.

  • The gas calculations below will implicitly include 21,000 gas.


The refunds work whenever you set something to its default value. For uint, it is zero; for a boolean, it’s false, etc. For ease of understanding, I’ll be taking examples of uint.

Before the London Fork

The EVM refunds gas to the user in two cases. First, if you are calling the selfdestruct function, 24,000 gas is refunded from the total consumed gas. This was simple and there’s nothing to talk about as the gas refund functionality for selfdestruct was removed in EIP-3529 and recently the selfdestruct method is also deprecated.


Second, if you set any variable to its default value, you get a refund of 15,000. Why does it happen? Setting the value to default value means you are clearing the storage because the nodes don’t need to track that particular slot of storage you just cleared.


This is a screenshot of the Ethereum Yellow paper stating the refund amount.

But there is a proper way of calculating the gas to refund.


Here is the reference from the Ethereum Yellow paper.

Mechanism: The refund is given at the end of any transaction and capped at a maximum number. That maximum number is half of the total used gas.


Let’s understand it with the example of uint. What happens when we set any non-zero uint value to zero?


Suppose you are setting a single uint variable to zero in a transaction so the total applicable refund will be 15,000, but the total gas consumed in this transaction is 24,000, half of which is 12,000, now 12,000 becomes the maximum amount of gas that can be refunded which means the transaction will now cost you24,000 — MAXIMUM_GAS_REFUNDABLE = 24000–12000 = 12000 gas.


Although we were expecting to get 15,000 gas back, the limit got capped at 12,000, hence reducing the amount to 12,000. What if our transaction is consuming 40,000 gas? In this case, half of 40,000 is 20,000 which is greater than 15,000 so we will get a refund of 15,000 gas.


This was the case when you are setting only one uint variable to zero, what will happen if we are setting multiple uint variables to zero? For example:


contract {
    uint256 count = 1;
    uint256 count2 = 2;
    uint256 count3 = 3;
    uint256 count4 = 3;
    uint256 count5 = 3;
    uint256 count6 = 5;
    uint256 count7 = 6;
    uint256 count8 = 6;
    
   function setTOzero() external {
        count = 0;
        count2 = 0;
        count3 = 0;
        count4 = 0;
        count5 = 0;
        count6 = 0;
        count7 = 0;
        count8 = 0;
    }
}
//Transaction cost will be 21000+ execution cost
//execution cost = 8 * (Gcoldsload +Gsreset) = 8 * 5000 = 40000
// Transaction cost = 21000 + 40000 = 61000


Whatever your transaction cost is the EXECUTION_COST+ 21,000. Now the refund is calculated based on how many variables you are setting to default. In this case, we are setting 8 uint variables to zero so the refundable amount counts to be 15,000 * 8 = 120,000.


Wait, if this is the refund amount then how much gas is our transaction consuming? When you calculate the gas to be consumed you will get the amount of approx 61,000. Now, what will happen if 120,000 gas is refunded on a transaction of 61,000? The miner will end up paying for the transaction, this is why the capping mechanism was introduced.


According to the formula, the maximum gas to be refunded will be 61000/2 = 30500. So the gas consumed will be 61000–30500 = 30500 instead of 61000 – 1,20,000. Now I think this is pretty clear to you, but this method is no more valid after the London upgrade.


If you want to test this then you can switch to the Berlin version of VM in Remix IDE and watch everything working.

Now let’s see what has changed in the London upgrade.

Post London Upgrade

These numbers have changed after the introduction of EIP-3529 in the London upgrade. The refund gas amount is now reduced to 4,800 and the maximum amount that can be refunded now is one-fifth of the gas consumed. Let’s understand with the same approach as before.


In the first case, if we are setting only one variable to zero, the transaction is consuming 26,000. when we notice the maximum gas rule in EIP-3529, it says TOTAL_GAS_CONSUMED divided by 5, which is in our case 26,000/5 equals 5,200, which means we were eligible for a maximum refund of 5,200 in this transaction but as mentioned in the EIP-3529 the refund amount is 4,800. This time 4,800 is refunded to us and the transaction costs 2,1400 gas (some additional gas).


Let’s take the second case where we were setting 8 variables to zero. The gas consumed is still 61,000. Talking about the refund, we expect a total refund of 4,800 * 8 =38,400, meaning the consumption we expect is 61000–38400 = 22600.


But when you execute this transaction, you will notice that the gas consumed is 49,000. Now you understand that the gas to be refunded is capped to a maximum of 61,000/5 = 12,200 which means the transaction will consume a total of 61,000–12,200 ~ 49,000.


You might be thinking what caused the gas refunds to get lower in the London upgrade. Look at the reasons I have copied from the official EIP website:


Gas refunds for _SSTORE_ and _SELFDESTRUCT_ were originally introduced to motivate application developers to write applications that practice “good state hygiene”, clearing storage slots and contracts that are no longer needed. However, the benefits of this technique have proven to be far lower than anticipated, and gas refunds have had multiple unexpected harmful consequences:


  • Refunds give rise to GasToken. GasToken has benefits in moving gas space from low-fee periods to high-fee periods, but it also has downsides to the network, particularly in exacerbating state size (as state slots are effectively used as a “battery” to save up gas) and inefficiently clogging blockchain gas usage
  • Refunds increase block size variance. The theoretical maximum amount of actual gas consumed in a block is nearly twice the on-paper gas limit (as refunds add gas space for subsequent transactions in a block, though refunds are capped at 50% of a transaction’s gas used). This is not fatal but is still undesirable, especially given that refunds can be used to maintain 2x usage spikes for far longer than EIP-1559 can.


The concept of gas refund must be clear to you now. The more you play with this on remix, the more you will understand the calculations. Why not try executing some real-world functions as the ones I wrote above are just examples? You can try to see the gas consumption in real contracts like the transfer function ERC20 when the balance is about to get zero. Let me know what you find by these experiments.


Have any questions about this topic? Ask me anytime, on Twitter, LinkedIn, or telegram.


Also published here.