EIP 3298: Removal of refunds

I wrote up the phase out alternative, now EIP-3300

Additionally, gastokens are fairly sustainable. They are created with the purpose of deletion. Looking at CHI , the total supply today (1.6mm) is less thant it was on October 8th, 2020 (1.9mm). It peaked around December, at 3mm.

One other problem that I see with gastokens is that they are inherently inefficient. You need 20000 gas to make a gastoken but you only get 10000 gas from using it (the ratio for GST2 is similar), and so there’s an extra 10000 gas that gets spent that provides no actual value to the network. Because the gas limit is bounded primarily by worst-case DoS attack limits, and not by average usage, we suffer the penalty of this wasted gas being part of the gas limit that everyone can use without getting any benefits out of it.

When a user transfers their entire balance of an ERC-20, they transfer balanceOf(...), that storage slot gets cleared, and they get a refund. However, if there were no refund, they would be incentivized to not clear the storage slot, and only transfer balanceOf(...) - 1, to save gas in case they ever wanted to re-acquire some of this ERC-20. The same logic can be applied to approvals, deposits/withdrawals into DeFi protocols, deployed contracts, and so on.

If we want to mitigate this, one idea is to just reduce the SSTORE gas cost in the nonzero → zero case down to some very minimal value (eg. 100); note that the cold-storage-load cost introduced in EIP 2929 would still be applied on top of this. This would increase the worst-case write count to be about the same as the worst-case read count, but it would fix some of the mispricing issues.

This is insufficient because the difference is dwarfed by SSTORE_SET_GAS. Once you’ve paid for that you should never give it up.

Here is a more comprehensive proposal. This does cut back some of the “simplicity” benefits of removing refunds, but OTOH it does allow us to retain most of the benefits of refunds while achieving the goals of (i) breaking gastoken, and (ii) removing block size variance.


Replace the “refund” counter with two counters: (i) a new_storage_slots_filled counter that increments every time a storage slot goes from zero to nonzero, and (ii) a storage_slots_cleared counter that increments every time a storage slot goes from nonzero to zero. At the end of a transaction, refund 15000 * max(storage_slots_cleared - new_storage_slots_filled, 0) gas. So every 15000 gas refunded must be matched by 15000 gas storage-increase gas paid, and so the maximum amount of gas spent on execution would not be able to exceed the gas limit.

I should hope that’s not the explicit goal, but you don’t achieve it here; GST1 would still work.

You would still have block size variance because you have refunds.

Based on the text I think you got the subtraction backward but it’s problematic either way.


Meaningful refunds seem necessary to incentivize good smart contract architectures where approvals and balances are zeroed when possible. These refunds currently introduce up to 2x block elasticity, which is good. From the Motivation it sounds like there is concern that 4x could be a DoS vector, but since Ethereum is so far under capacity, so-long as the opcodes are correctly priced I don’t think DoS is an issue. With more than 2x elasticity we can have more stable gas prices, reliable confirmations, and handle the irregularities in demand.

I agree that as a gas storage mechanism, current refunds are inefficient and waste storage, but if we had another refund mechanism that was slightly more efficient and didn’t waste storage, classic gas tokens would be out-competed. We would lose the constant state growth property, but the elasticity would come much cheaper in both storage and computation.

This can be done with three opcodes and a persistent account gas refund counter, which could implement a better gas token:

  • SELFGAS which pushed the current account’s refund counter onto the stack
  • USEGAS which reduces the contract’s refund count by up to the amount popped from the stack, adding the amount to the refund counter
  • STOREGAS which increases the current account’s refund counter and consumes additional gas by the amount popped from the stack

This would preserve the gas market and the stability it provides, while phasing out usage of inefficient and wasteful alternatives.

I wrote up the efficient gas storage approach here.

I should hope that’s not the explicit goal, but you don’t achieve it here; GST1 would still work.

Now that I think about it, you are right; the refunds would be still able to cancel out the portion of gas usage that is new-storage-fills. The refund rule would have to be more restrictive (refund only if 0 = original = new != current) to solve that issue.

You would still have block size variance because you have refunds.

But this is not true, because the key invariant that remains is that gas spent on execution would not go above the gaslimit. Every 15000 gas refund would be matched by 15000 gas spent on filling a new storage slot.

Based on the text I think you got the subtraction backward but it’s problematic either way.

I don’t think it’s backward! You refund only if you cleared more storage slots than you fill.

Another thing worth considering is that clearing storage is going to be less useful in the future, if we are implementing either weak statelessness or state expiry. In fact, truly clearing storage would not even be possible; you would need to leave a stub to show that the value is zero, as opposed to the slot being not-yet-edited and the value needing to be dug up from the past. So in the longer term, having less good incentives to clear storage is not even such a useful thing.

The EIP 3403 discussions-to link points here.

I do prefer 3403 to 3298 because it fixes 3298’s storage misincentive, though my preference is still 3322 because I support block elasticity.

Remove the SSTORE refund in all cases except for one specific case: if new value and original value both equal 0 but current value does not, refund 15000 gas.

The 15000 number should probably be higher. I think the Istanbul refund for this scenario is 19800. Since this is a “warm read” it should be cheap and also refund the bulk of the 20000 cost from SSTORE_SET.

Since the proposal removes all refunds except for this case, it doesn’t make sense to maintain a refund counter. Instead, the gas should be added to current gas counter. Should the gas used by a *CALL be negative, the surplus gas is returned to the gas counter, so that the cost negation works recursively. Then the refund counter can be removed entirely.

You are mistaken. eth_estimateGas already returns the correct gas limit.

Huh… if I have a case where this isn’t true–due to refunds–is this a bug I could thereby file and it would get fixed? I’d looked through other issues and I was under the impression that the eth_estimateGas issues were just “this is known to not work and we aren’t going to fix it” ;P.

So, I work on a layer 2 payments system that has to use storage to prevent replays (and the order is intrinsically arbitrary, so it can’t use a monotonic nonce). I use one storage slot per payment. Right now, I’ve got everything set up so that everyone would naturally “want to” delete expired replay prevention slots. (Yes: I also added a way to “forge” replay prevention slots, as it also forms a gas token ;P. I don’t care at all about this behavior.)

The result of this is that, over time, usage of the contract is going to result in O(number of users) storage, as it will be something like “the only storage slots in use are for the payments that haven’t yet been expired, and each user has some small number of payments in flight at any one moment”. Without refunds of any form, this is going to be O(number of payments), which is much much much bigger (something Ethereum obviously itself avoids with its account model).

FWIW, the “viability” of a gas token is strongly related to the “power” of the refund. Have you considered making it so that deleting a storage slot just makes the storage cost non-existent and gives you maybe a tiny 500-800 gas refund? This wouldn’t be viable for a gas token, as you’d need ~500 gas to even specify and find/calculate the storage slot that is storing the gas token you are freeing: the goal of this refund (and associated subsidy) is just to make it economically reasonable to be a bit altruistic and clean up old state.

Put differently, I think there’s something valuable in strategies that don’t necessarily “reward” people for messing with storage state in potentially-weird ways but at least doesn’t penalize people for cleaning up state: without refunds–and with deletions costing thousands of gas–I’d actually be punished pretty hard for bothering to clean up state, and I feel like I should want to clean up state even if it isn’t making me money, as long as it isn’t hurting me.

(edit: And like, I appreciate that maybe in the future state doesn’t matter as much, or it will naturally expire and I won’t need strategies for expiring it myself; I definitely think maintenance of state perpetually isn’t sustainable… but, given the speed of how changes happen, it could be years before we get there, and I want to think that systems like mine should have at least not be disincentivized from avoiding spamming state ;P.)

1 Like

3403 still ruins any incentive to clean up state outside of the same transaction. The misalignment of incentives should offset any perceived gains on the storage-bloat motivation, but with real storage that would need to be rented under any state rent scheme. The concerns described by @thabaptiser have only been addressed in the same-transaction case. Here are some specifically incentivized behaviors, which I listed in ACD.

  • Storage arrays should use 1-indexing such-that length-zero is stored as 1 so adding anther element does not incur SSTORE_SET. Additionally, popped elements should not be cleared for the same reason
  • UI-prompted approvals should always be infinite.
  • When selling all tokens you should leave 1 in your wallet, which will change the “Sell MAX” behavior on most DEX interfaces.

I’m also opposed to 3403’s motivation because I favor elasticity. 4x is the best part of 1559 and as a power user who transacts during congestion I don’t want to see larger spikes.

In ACD, @holiman mentioned that miners have been mining blocks that are only gas tokens. F2Pool clarified this was because this is their default candidate block and it is only mined if they have not yet sealed another.

In ACD it was mentioned that miners may increase the gas limit under 1559 to fight against the base fee. The possibility of 4x may discourage that behavior since they will struggle to capitalize on MEV if 4x exceeded their full capacity. Because of this and sync-time concerns for the node failure case, I don’t expect miners to target the gas limit above 4x their capacity.

Here is a copy of the prepared statement I read during today’s ACD. I didn’t read it very well (8 charisma), and Tim said on Twitter that some asked for the text.

First, the proposal removes any incentive to clear state, except for the same-transaction case. When smart contract engineers build around such incentives we will see more state bloat. Approving less than infinite will be phased out in the UX. Selling all of your tokens on most interfaces will leave 1 unit behind. Storage arrays will be cleared by setting the size field to 1 (which will mean 0) and leaving all entries dirty as it would be foolish to clear them. And these are only a few of the design implications.

The proposal also sacrifices current elasticity, which smooths gas price spikes during peak congestion. 1559 does not provide sufficient elasticity because peak congestion lasts hours not minutes. By sacrificing refund elasticity concurrently with 1559 we should expect a net increase in volatility that would counteract the anticipated improvements in signature-time gas price estimation. While the motivations for the proposal cite brief 4x sprints as possibly dangerous, I believe them to be the top feature of 1559. Grocers don’t raise their prices during peak hours; they hire part-time workers, so their customers don’t complain and flee to other stores. The long-term costs of potential 4x blocks are amortized during periods of lower congestion. But all nodes should be able to verify the consecutive 4x blocks smoothly, else they wouldn’t be able to sync the blockchain in any reasonable timeframe.

As proof that the network can handle 4x today I present Binance Smart Chain, which sets a higher gas limit of 30 million every 3 seconds, approximately 11 times the current Ethereum capacity. My devnode, which runs on an older low-end processor, gets 70 mgas per second in Berlin, so I would still be able to sync Binance Smart Chain even if all of their blocks were 4x.

In the previous meeting we agreed to table 3403 if 4x wasn’t a security concern. There have been some better ideas floated in the discord, such as separate markets for computation gas and storage growth. A less-rushed solution might do more good than harm, and we could free up London engineering bandwidth for more-important work.

I’m in support of this EIP. It cleans up EVM mechanics in a way that feels worth the downsides that I’ve seen thus far.

Addressing some specific points:

I am empathetic to what appears to be a significant financial downside that you may face with this change, but this type of reason isn’t compelling to me. You should probably start looking now at how you can reduce your financial risk from this change.

Refunds have been shown to not be a viable mechanism for mitigating state growth in a meaningful way. Thus, the argument that this will make state growth/bloat worse is not compelling here. See this other post on “state expiry” for how we intend to meaningfully address state growth: Resurrection-conflict-minimized state bounding, take 2 - #17 by vbuterin - Execution Layer Research - Ethereum Research . The “state expiry” plans will be effective with or without refunds or incentives to clear state.

citation needed; refuted several times with specific examples.

We have had refunds since the beginning of the chain. State growth is a problem and continues to be a problem… Removing refunds willmake the problem worse. My intuition is that the amount worse is going to be insignificant when compared to the overall rate of growth. Without firm economic bounds, state growth/bloat will continue to be a problem Refunds do not provide economic bounds for the overall state size. State expiry fully solves this. With state expiry, state clearing incentives no longer matter with respect to limiting total state size. Thus, the added benefit of having some incentive to clear state feel insignificant compared to the complexity cleanup that removal of refunds provides.

Please let me know if something is unclear here.

1 Like

I’m in support of this EIP. It cleans up EVM mechanics in a way that feels worth the downsides that I’ve seen thus far.

FWIW, I don’t have any skin in the game here (so to the extent to which anyone is looking at “these arguments are motivated”: I’m immune).

My goal is only to make it so that my contracts are as gas efficient as possible; to such end, all of my test cases report how much gas they used, so I can attempt to optimize whatever I can. I try try try to not fret tens of gas, but I consider the tradeoffs carefully for hundreds of gas, I consider thousands of gas to be worthy of going to great lengths to avoid, and when I see tens of thousands of gas I will spend a full week figuring out if I can somehow remove it.

The current specification–the status quo–thereby has a kind of beauty to it: developers like me are incentivized to minimize storage. If you think I am simply not doing that, you are being needlessly hyperbolic. Meanwhile, users are also likewise incentivized: they are encouraged to delete their accounts and clean up their state, rather than leave it to rot.

The idea that–as @wjmelements points out–users are going to get no refund (or even discount) for setting a storage slot to 0 and yet are dinged 15,000 gas for setting it from 0 to not 0 feels really horrible. It is the kind of ridiculous special case–one that absolutely will change the way I develop contracts, as that 15,000 gas “transition through 0” penalty will light up my test cases as a very large and annoyingly-avoidable (by using “1 is the new 0”) cost–that should make one pause and realize “we have incorrectly modeled this”.

If one wants to make new rules, those rules need to at least be consistent! Here is thereby another suggestion (which actively leans into the idea of what you want): if the cost for changing a storage slot away from 0 is going to somehow cost money in a way that isn’t later consistent with changes back and forth through 0, it sounds like what you actually want to charge for is something like “storage allocation”. As such, you should make it only 5000 (not 20000) to re-write to a storage slot that has ever been written to before.

Obviously, this means that nodes essentially no longer get to reallocate anything, as they will need to remember this storage slot for all time… but that’s their–and this idea of not giving people refunds–fault, and cannot just be avoided by not doing this: doing what you want without this change still does this, because “1 is the new 0”: only an idiot contract developer will ever clear a storage slot to 0 anyway in your model (due to having to pay 15,000 every time it is reset from 0), and so that storage is effectively burned.

What this alternation to your mechanism does, though, is it makes the development process a bit less “disgusting”: it internalizes to the platform this really horrible behavior you want to incentivize of avoiding clearing state–even if you semantically needed to clear state!!!–so that I don’t have to go through my contract and remove every single place where I not only purposefully, but even accidentally, set anything to 0 (which is such a sufficiently obviously “bad for the developer” incentive that I don’t understand how this proposal is getting as far as it seems to have gotten).

3 Likes

Just as @saurik described here, as a gas conscious developer, I will start avoiding setting to 0 in all contracts if this goes live. If/when I get involved in SushiSwap AMM V2 I will make sure it’s lean on gas usage, because this is a competitive advantage. In this era of very high gas prices, protocols will compete on gas efficiency. 1 will be the new 0 for me.

The constant changing of gas costs is a real PITA from a dev point of few, because many contracts can’t be changed after being deployed. With this EIP you’re helping protocols built by lazy devs and penalizing those who have taken great care to preserve gas and clean up state.

I’m all for improvements, and if gas tokens are a real issue, finding a way to get rid of them… but this proposal seems half baked.

P.S. I hold 0 gas tokens, I just like optimized code.

2 Likes

The following is the prepared statement I read in today’s ACD regarding security.

Previously we agreed that, with present gas limits, brief spurts of 4x throughput would not be an issue. But the concern was raised that miners will increase the gas limit in London to minimize the base fee, perhaps to an unsafe level. The initial proposal to fix this was to hardcode the block gas limit. That proposal was defeated in part because miners have responsibly managed the gas limit. If that is changing with 1559, a hard limit would be better DoS protection than this proposal. But if we expect miners to continue to be responsible, they would have to consider their capacity to handle brief 4x spurts in their gas limit voting, and this would limit the extent to which they could maximize the gas limit, thereby securing the base fee.

If we believe miners will push an infinite gas limit, it won’t matter if the elasticity is 2x or 4x. If this is a major security concern we should revisit the hard cap, though I would recommend a much higher limit than the original proposal.

On the other hand, if we assume miners will want to prevent each other from submitting DoS blocks, the BASE_FEE would be more secure with the possibility of 4x than just 2x.

I also read the following excerpts from this forum, citing the author.

I concluded:

Therefore, the storage bloat motivation for 3403 should be replaced with an acknowledgement that the proposal incentivizes protocols, interfaces, and users to bloat storage.

@shemnon mentioned an alternative proposal, to count gas used before refund against the total gas limit. This would eliminate what @holiman called the refund stipend, which would reduce elasticity. It may incentivize miners to reprioritize transactions according to total gas used instead of just gas price, but it would fix all currently-known issues, and seems slightly simpler than 3403. The only downside to this approach seems to be the reduced elasticity during congestion.

It seems the real issue is that the block gas limit includes long-term storage costs and rebates. If we could somehow isolate those costs from the block gas limit such that the user still pays for them but throughput gas remains constant, it would likely be the best solution.

1 Like

One alternative is to make updating storage a fixed cost and no refund for selfdestruct. This will obviously deal with the inefficiency of gas tokens for the system overall. Not ideal for coders like me who spend time optimizing their contracts, but at least if doesn’t lead to weird contracts trying to never go back to 0 (I’d probably end up writing a uint255 library, with one bit always set to 1).

As part of this it would be nice to introduce storage costing on transaction basis, so if a storage slot has the same value at the start of the tx and at the end you pay some minimal fee for updating a memory variable and an SLOAD fee.

From a developers perspective it would be nice if docs somewhere could be updated (in plain English and not math notation) to have the current gas math and approved EIP changes and when they’ll go live. It’s hard enough to optimize for gas, but even harder to keep up to date with the ever changing rules. (If this already exists, please point me in that direction…)

I see an issue with the following flow within single transaction:

  1. Write non-zero to zero storage slot
  2. Write zero to the same storage slot
  3. Revert both actions

Gas cost of the following solution would be approximately 25k without any gas refund (revert erases gas refunds). But actual job of doing nothing should not cost that high.

I propose instead of erasing gas refund on revert keep it equal to cost of all the reverted SSTORE operations.