# List of EVM features potentially worth removing _Special thanks to Micah Zoltu for suggesting some of these_ ## Preamble: why the merge is our last good opportunity to remove things, and why we should use it We know far more about smart contract and blockchain protocol design in 2020 than we did in 2013-15. As a result, there are many features in Ethereum that were introduced during those earlier years, that if we were building Ethereum from scratch in 2021 we would not introduce again. However, removing features from a running chain with a running ecosystem is harder than not adding them to a new system. Some of these "vestigial features" are harmless. Others can be safely and slowly removed or improved over time. Still others are too deeply integrated into too many applications to contemplate ever changing (eg. the 256-bit EVM might be an example). And on the other hand, there are features that either have been removed or improved already, or will be soon (improvements to the state tree, and replacement of RLP with SSZ, come to mind). But there are also some cases in the middle: features that cause a medium amount of ongoing harm to the ecosystem due to their complexity, and which _can_ be removed, but there is a _small but nonzero amount of risk_ in doing so. If we remove these features, then a small number of applications may break. But if we do not, they will continue being a drag on the ecosystem until the end of time. As is often the case in situations with "short term pain, long term gain", it is easy to underestimate the magnitude of the long term gains. Particularly in our case, the fact that "the code is already written" to handle complexity makes it feel like there is no cost in keeping it. But in reality, there are two significant costs: 1. Cost to anyone making a new implementation of the protocol 2. Risk of "interaction bugs" when changing some feature B that _interacts_ with a complex feature A that does not need to exist One illustrative example is state tree redesign: the more it is the case that the Ethereum state follows some simple invariants, the easier it is to replace the two-layer hexary Patricia tree with more efficient structures. However, the fact that eg. the `SELFDESTRUCT` opcode can remove an unboundedly large number of storage slots in a single transaction makes state tree reform more difficult. Another example is the 2300 gas stipend (see below) making gas repricings more complicated. **The "merge" (the event when the eth1 PoW chain is discarded and its state is imported into the eth2 PoS beacon chain) may be the last chance we will have to rip off some of these painful bandaids, and this post makes the case for doing so.** The merge is a natural point for making a final round of breaking changes for a few reasons: 1. Clients that are built post-merge will likely not even try to process the PoW chain, instead exclusively validating the PoS beacon chain. Hence, if protocol complexity is removed at or before the merge, clients can most easily benefit this as they do not have to implement those features at all. (Technically, even clients built pre-merge could be designed to only process the chain starting from 1-2 hardforks in the past, but it seems likely that not bothering to process the distant past will be an easier sell when it's "a separate chain") 2. There are already large changes happening, and there's a community consensus around the idea that it's "a major upgrade to ethereum". Particularly, there's a community consensus around the idea that until sharding and the merge are completed, _rapid evolution is expected_, whereas post-merge there is an expectation of much more stability. 3. Backwards-incompatible changes (eg. the BLOCKHASH opcode no longer being a good source of randomness) are by necessity happening already. This post will go through a few examples of features that could be considered for removal. ## List of features ### The 2300 gas stipend * **What is it?**: When a contract calls another contract, the recipient automatically gets 2300 gas during which it can perform a very limited amount of execution (enough to do a bit of computation and issue a log, but not enough to fill a storage slot) * **Why was it introduced?**: It was originally introduced to allow smart contract wallets to issue a log when they receive funds. Since then it has also been used to implement "guards" that prevent a contract from receiving ETH. * **Why is it bad?** * It's a fixed quantity of gas, and so any claims about what kind of computation it supports do not survive gas repricings. * It does not satisfy its intended purpose well, for two reasons. First, many users still use EOAs and EOAs have no logging. Second, `SELFDESTRUCT` bypasses the stipend mechanic. In the longer term, EOAs are expected to be de-emphasized in favor of contracts via [account abstraction](https://github.com/ethereum/EIPs/pull/2938), and later on this post advocates removing `SELFDESTRUCT` but until/unless both of those things are done, it's an insufficient half-solution. * **How could we remove it?**: there are two natural possibilities here - the 2300 number could either be replaced with 0 (no child execution) or with infinity (child execution gets all the gas from the parent). * **What might removing it break?** * If we remove child execution, then this would add an awkward two-clause mechanic into call where 0 gas is interpreted as 0 and any other number is interpreted as "send all the gas". It would also break anti-receiving guards and logging. * If we give the child all the gas from the execution, then sending ETH with CALL would become a "trusted" operation, and malicious contracts may be able to interfere with some applications. However, Solidity documentation already [discourages `transfer` in favor of "withdrawal" patterns](https://docs.soliditylang.org/en/v0.5.3/common-patterns.html#withdrawal-pattern) which do not incur any risk. * **How could we address those concerns?** * Make all ETH transfers, including due to calls and `SELFDESTRUCT`s (while that opcode remains) issue a log, so the wallet does not need to * Add a rule that a call where 0 gas is provided is a "`STATICCALL` that can issue logs". This replicates existing functionality in terms of what can be actually done inside a stipend-gas-only execution context. ### Gas visibility * **What is it?** The `GAS` opcode allows the contract to see how much gas is left in the current execution context. `CALL` allows the caller to specify a fixed amount of gas for the child context to be able to execute. * **Why was it introduced?**: The main reason against `CALL` simply forwarding _all_ of the gas of the parent context to the child context was to allow "untrusted calls": calls where the sender does not trust the recipient. One simple example is a financial mechanism sending ETH to participants. Another toy example is a M-of-N oracle which calls out to some number of contracts, gets responses from each one, and outputs the median of the results. * **Why is it bad?** * There are actually very few use cases for untrusted calls that could not be easily done without untrusted calls. For transfers, Solidity documentation already [discourages `transfer` in favor of "withdrawal" patterns](https://docs.soliditylang.org/en/v0.5.3/common-patterns.html#withdrawal-pattern). The toy M-of-N oracle example above could easily be implemented by making a separate transaction per oracle. * It makes it hard to do gas repricings, as it enables fixed-gas calls that could break if gas prices change * **How could we remove it?** * Make `CALL`s automatically forward the parent's _entire_ gas to the child. The `GAS` opcode would simply return the transaction's initial gas amount. * **What might removing it break?** * The main category of "legitimate usecase for untrusted calls" that we know about is calls sponsored by a third party. A third party would publish a transaction containing a call that you want them to make, and after the call is made they would automatically collect payment from you (you would publish a signature that entitles them to do this). This is helpful for smart contract wallets where the user does not have any ETH, privacy preservation for mixers, and other use cases. A limited-gas call is needed to ensure that the final payment clause actually gets called and does not get reverted due to an out-of-gas issue. * **How could we address those concerns?** * Miners could directly serve as these intermediaries, and they would simply throw away a transaction if it turns out not to pay them. See [Phil Daian's work](https://ethresear.ch/t/flashbots-frontrunning-the-mev-crisis/8251) on creating an ecosystem of third-party bots that miners could automatically to generate "safe" transaction batches. * Add an explicit in-protocol "third-party-payer" transaction type. See for example [EIP 2711](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2711.md). Note also that if we want to go even further, we could also adjust the [63/64 rule](https://github.com/ethereum/EIPs/issues/114) so that if a child call fails, the parent call fails completely (so there is not even the 1/64 left). This could break more use cases ("do one simple small thing if the child call fails"), but it would ensure that future gas cost changes can only cause one type of change in behavior (the transaction failing instead of succeeding). ### `SELFDESTRUCT` See [this whole document](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/selfdestruct). ### Gas refunds * **What is it?**: `SELFDESTRUCT`-ing a contract, or setting a storage slot to zero, refund 15000-25000 gas. This refund is applied at the end of a transaction and reduces the fee the sender needs to pay. * **Why was it introduced?**: To motivate application developers to write applications that practice "good state hygiene", clearing storage slots and contracts that are no longer needed. * **Why is it bad?** * In practice, almost no one actually practices good state hygiene. The incentives are not high enough, and it is not worth the added code complexity and even security risks of attempting to do so. * Refunds give rise to [GasToken](https://gastoken.io/). 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 and inefficiently clogging blockchain gas usage * It increases block size variance. The theoretical maximum amount of actual gas consumed in a block is nearly twice the on-paper gas limit. This is [not fatal](https://notes.ethereum.org/@vbuterin/eip_1559_spikes), but is still undesirable, especially given that refunds can be used to maintain 2x usage spikes for far longer than EIP 1559 can. * **How could we remove it?**: just remove the refunds feature from the protocol entirely. * **What might removing it break?** * We can be fairly confident that no applications would be outright unusable, because refunds are applied only after execution is finished, so eliminating refunds would not change how much gas is available to any execution context. * GasToken would be rendered useless * Applications would lose their ability to cut costs during unusually-high-gas periods. However, the most prominent users of this today are defi arbitrage bots, and gas price competitions between arbitrage bots are a zero-sum activity so it's not clear that removing this weapon for them to fight with would actually have any global downsides * **How could we address those concerns?** * The Gastoken already warn about the possibility of future protocol changes rendering GasToken useless on [their website](https://gastoken.io/) so it would hardly be a surprise for users * We can publicly signal this change ahead of time ### Other candidates (speculative) I am much less confident about the value of focusing on these than the other items above, but it's still worth maintaining a list. * **RIPEMD160 precompile**: non-standard hash function used by very few projects (only things that interface with Bitcoin). Can be replaced with an EVM-code implementation, and projects that _really_ need efficient verification can just use ZK-SNARKs * **Dynamic jumps**: jumps where the destination is a variable makes code analysis and code manipulation much more difficult (eg. can't easily search-and-replace some opcode sequences with other opcode sequences, or prepend some code). Removing dynamic jumps and allowing only static jumps with relative offsets as well as perhaps some dedicated pointer scheme (where the pointers are NOT exposed as integers) for subroutines could fix this. However, this would be a deep change and may break many custom contracts, so the benefit/cost ratio seems less favorable than the other items in this list * **MODEXP precompile**: clearly the wrong "base primitive" for big-integer math, and featuring a fairly complex gas cost computation scheme. The better alternative would be to either (i) replace it with ADD, MUL and MOD for bigints as precompiles and replace MODEXP with a code implementation that calls into those precompiles, or (ii) expand EVM384 into many more sizes (256, 384, 512, 768, 1024 ... 8192)