# Pragmatic destruction of `SELFDESTRUCT` This post describes some reasons why the `SELFDESTRUCT` opcode brings more harm than good to the Ethereum ecosystem, and so should be neutered or removed in some way. To deal with the existing contracts that use `SELFDESTRUCT`, I propose some ways to eliminate the harmful aspects of `SELFDESTRUCT` with minimal disruption. ## A history: `SELFDESTRUCT` is not necessary `SELFDESTRUCT` (originally called `SUICIDE`) was introduced very early on in Ethereum's history; in fact, it was present even in this [pre-announcement "spec" of the Ethereum protocol](https://web.archive.org/web/20131219030753/http://vitalik.ca/ethereum.html) from December 2013. At the time, there was little rigorous thought being done about long-term state size management. However, there was (in my [Vitalik's] head) a general impression that to prevent state from filling up with vestigial garbage without limit, we need it to be possible for any object that can be created to be destroyed. Externally-owned accounts (EOAs), the thinking goes, would automatically be destroyed when their balance drops to zero, and contracts could have a self-destruct clause in the code to delete themselves when they are no longer needed. A gas refund would encourage them to do this. In January 2014, Andrew Miller pointed out something that in retrospect was face-palmingly obvious: in the Dec 2013 spec, EOAs were vulnerable to replay attacks. If I had 100 coins and I sent you 10, you could simply republish this transaction on-chain ten times to clear my entire balance. This was quickly fixed with nonces. However, the addition of nonces removed all hope of EOAs being deleted: the nonce could not be reset to zero. In 2015, [some schemes were proposed](https://github.com/ethereum/EIPs/issues/169) to get around this and potentially allow accounts that send away all of their ETH to be safely deleted. However, by then it was clear that almost no contract developers were actually using the self-destruct feature: figuring out when to self-destruct is too hard and the rewards are too little. By 2019-21, it has become clear that we need some other form of state management, whether rent or "expiring" long-untouched parts of the tree (aka "partial statelessness"). But if we _have_ such a scheme, and it works, then we no longer need to care about giving contracts the ability to delete themselves voluntarily. ## `SELFDESTRUCT` is the only opcode that breaks important invariants In addition to not being very useful, the `SELFDESTRUCT` opcode is not harmless. It breaks some important invariants that would be nice to have, that we cannot have because of this one single opcode. #### `SELFDESTRUCT` is the only opcode which causes an unbounded number of state objects to be altered in a single block All other opcodes work on individual values in accounts, or individual keys in storage trees, and so there is a bound on how many fixed-size objects can be changed (usually, one object per opcode call). SELFDESTRUCT, however, removes an entire storage tree. In the present tree construction, this is bearable. However, it requires caches to be designed in a particular way with additional complexity to deal with the possibility of a SELFDESTRUCT deleting many storage slots and then a contract being created at the same address and reading those same storage slots in a subsequent transaction. Furthermore, it makes it difficult to move to a different state storage format in the future. Two examples that SELFDESTRUCT prevents are: * Any kind of "single-layer" scheme (where we actually have a single tree, or a single [hashmap](https://ethresear.ch/t/hashmap-based-polynomial-commitments-for-state/7186), instead of a tree-per-contract-inside-a-tree) * A scheme where storage slots can be stored "near" some address _other_ than the contract in the state tree (this could be useful for witness size optimization, eg. for an ERC20 transfer or Uniswap trade) Note that this is not purely theoretical; discussions about radical changes to state storage (binary trees, Verkle trees...) are happening right now, and if state storage _can_ approximate a single key/value store with a low bound on how many keys can be changed in a block this would significantly expand the options we have to choose from. #### `SELFDESTRUCT` is the only opcode which can cause the code of a contract to change There is value in having the invariant that once a piece of code exists at a particular address, that code is guaranteed to be at that address forever. It makes it easier to build applications that depend on calling contracts that have already been deployed. Account abstraction greatly relies on this invariant if we want abstracted accounts to be able to call libraries. But other applications as well have their security properties significantly complicated by the existence of the possibility of code changing: the Parity multisig wallet was bought down in 2017 by the [library contract code being accidentally deleted](https://www.parity.io/a-postmortem-on-the-parity-multi-sig-library-self-destruct/). The only opcode that breaks the code immutability invariant (and indeed, was responsible for the demise of the Parity multisig) is... `SELFDESTRUCT`. #### `SELFDESTRUCT` is the only opcode which can change other accounts' balances without their consent `SELFDESTRUCT` has a built-in "send", and this send does not execute contract code and so bypasses anti-receiving guards and logging. This risks breaking smart contract wallets, breaks other [potentially useful tricks](https://ethresear.ch/t/poorme-unreasonably-cheap-on-chain-erc-transfers/8620), and generally is yet another edge case that contract developers and auditors need to think about. ## Current uses of `SELFDESTRUCT` There are two significant types of applications today that use `SELFDESTRUCT`: 1. **GasToken**: burn gas when the gasprice is low by creating contracts, recover that gas when the gasprice is high by calling `SELFDESTRUCT` and claiming the refund (~60% of the creation cost for near-zero-size contracts). 2. **Deliberately using SELFDESTRUCT to dynamically change the code**: this could be used as an "upgrade" pattern for a dapp or DAO and other similar use cases. (1) can be safely broken. The [GasToken devs themselves warn](https://gastoken.io) that "it is extremely likely that the developers of GasToken will advocate for changes to the network that render GasToken unusable, irredeemable, non-fungible, and/or worthless". The only thing that will happen from selfdestruct refunds being removed is that some operations will become up to 2x more expensive. In the long term, (2) is unneeded; there are other widely available patterns to allow dynamic code changing. The easiest to implement is the `DELEGATECALL` forwarder, where the contract immediately performs a `DELEGATECALL`-to-self using a code address taken from a storage slot; changing the storage slot will upgrade the code. In the short term, however, a few applications that use (2) exist. ## Proposal 1: full removal of `SELFDESTRUCT` We choose some block `FLAG_BLOCK` (eg. one natural option is the same block as the merge), at which `SELFDESTRUCT` would stop working entirely. If, on or after this block, the EVM execution encounters the opcode `0xff`, it simply exits with an exception, in much the same way that it would if the EVM execution had encountered a byte that is not any valid opcode. To give users added warning, one gimmick we can add is an asymptotically increasing gas price: if `block.number + 10**6 >= FLAG_BLOCK`, the gas cost of `SELFDESTRUCT` is increased to `10**10 // (FLAG_BLOCK - block.number)`. ## Proposal 2: neutering of `SELFDESTRUCT` We can also change the behavior of the opcode to preserve functionality but eliminate the fact of trees getting destroyed, and add feature by which contracts can flag themselves as being un-self-destructible and thus guaranteed to not change code. Proposed temporary new `SELFDESTRUCT` behavior: * When a contract calls `SELFDESTRUCT`, it does not get deleted. Instead, its code gets zeroed out and its nonce increased by `2**40`. A refund is not provided. * ETH in the contract is moved to the destination via a call (with either 0 gas or all the gas in the parent). * Contract creation is possible if the target address has empty code. * `SSTORE` and `SLOAD` calls in a contract with address `A` use the storage tree of account `A_offset = (A + A.nonce // 2**40) % 2**160` Note that from an EIP-2929 perspective, `A_offset` would need to be "accessed" and an additional 2600 gas would need to be charged if that account is not yet in the already-accessed set. Another alternative is to adjust the hash function which converts storage keys into tree keys, using `sha3(storage_key + contract_nonce // 2**40)` instead of just `sha3(storage_key)`. Note that a somewhat similar rework would need to be done anyway to facilitate contract-level [expanding-key-space statelessness](https://ethresear.ch/t/alternative-bounded-state-friendly-address-scheme/8602). Contracts could specify `0xA8` as the first byte in their code; the EVM would recognize this as a no-op, but use it to turn on a flag that fully disables the functionality of `SELFDESTRUCT` (note: this is equivalent to the [SET_INDESTRUCTIBLE proposal](https://github.com/ethereum/EIPs/pull/2937)). The two solutions can also be combined: immediate neutering plus longer-term full removal. Alternatively, the opcode could never be _fully_ removed; instead, it could eventually be renamed `CLEAR` and have only one piece of functionality, equivalent to a call to the target sending an amount of ETH equal to the contract's current balance.