Skip to content

Instantly share code, notes, and snippets.

@rjl493456442
Last active January 31, 2024 07:01
Show Gist options
  • Save rjl493456442/b8dbe56491c74c532cb2f0bab4b5292d to your computer and use it in GitHub Desktop.
Save rjl493456442/b8dbe56491c74c532cb2f0bab4b5292d to your computer and use it in GitHub Desktop.

Clarification on contract redeployment

Abstract

This writeup lists issues related to contract redeployment and vague specifications, making a proposal to precisely define the expected behavior in such instances.

Background

Contract deployment

According to EIP684, If a contract creation is attempted, due to either a creation transaction or the CREATE/CREATE2 opcode, and the destination address already has either nonzero nonce, or nonempty code, then the creation throws immediately. Specifically the prerequisites for a successful deployment includes:

  • account.nonce == 0
  • account.CodeHash == 0000000000000000000000000000000000000000000000000000000000000000 || account.CodeHash == c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470

It means the balance and storage presence is not checked upon the contract deployment and the latter brings some confusion.

Pre-funded account (balance != 0)

It’s a pretty common scenario supported by Ethereum protocol that the destination address is pre-funded and contract deployment happens later. The non-zero balance is regarded as valid, therefore, the contract deployment will proceed normally. The behaviour in this case is clearly defined and not the main toic of this writeup.

Non-empty storage account(storageRoot != empty)

What if the destination address has non-empty storage? One might wonder if it even possible for this situation to occur. The answer is Yes: we do have a few on mainnet (see appendix).

This kind of account can be specified as follow:

  • account.nonce == 0
  • account.CodeHash == 0000000000000000000000000000000000000000000000000000000000000000 || account.CodeHash == c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
  • account.StorageRoot != 0000000000000000000000000000000000000000000000000000000000000000 && account.StorageRoot != 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421

Since storage slots can only be created while the contract itself is running (within constructor or deployed code), and a requirement is that there is no deployed code, it follows that these slots can only have been set within the constructor, and that the constructor returned empty code while executing.

After EIP-158, the nonce of a new contract became set to 1, therefore it also follows that any such contract was depoyed before EIP-158 was deployed.

Contract redeployment

Redeployment indicates that the address has been deployed with a contract, but still meets the conditions of deployment. When such an address is deployed successfully with contract code again, this situation is called redeployment.

Prerequisites for redeployment

  • The account was deployed before EIP158, with zero nonce and empty code
  • The destination address of new deployment is same with the old one

The first condition is totally possible as described in background section. The main focus will be the second condition.

The destination address calculation

There are two formulas in Ethereum protocol for contract address calculation:

  • Keccak256(rlp{sender, nonce})[12:]
  • Keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]

The target account discussed in the writeup was created before EIP158 by using the first formula. Next we will prove that the same address cannot be calculated unless hash collision happens.

It’s impossible to calculate the same address with formula1

In order to produce a same address with formula1, the same sender and nonce pair must be held.

  • If the deployer is an EOA, the same deployment nonce is not available
  • If the deployer is a contract(via create opcode), the deployment nonce might be available if the contract can be self-destroyed, but the same deployment address is not available after destruction as the contract itself must be created by EOA or the contract created by EOA recursively.

It’s impossible to calculate the same address with formula2

According to EIP1014, the addresses created with this formula2 cannot collide with addresses created using the formula1, as 0xff can only be a starting byte for RLP for data many petabytes long.

Hash collision

According to the analysis from Peter Davies:

Gary's analysis seems correct to, but has missed a subtle point, which makes this complicated. The KECCAK collision required is only 160 bits, not the full 256 bits. 160 bit collisions are acheivable and cost around ~$10 billion

The address hash collision is theoretically possible. Therefore we can conclude it’s impossible to calculate the same destination address, unless hash collision happens with non-trivial computation cost.

Contract redeployment specification

After the analysis above, we know it’s possible to encounter contract re-deployment if hash-collision. To eliminate confusion and make all clients behave consistently in case of redeployment. we need precisely define the specification.

Here is what I propose as the expected behaviors:

  1. Contract deployment to an account with non-zero nonce: the create-operation fails. (No change)
  2. Contract deployment to an account with non-empty code: the create-operation fails. (No change)
  3. Contract deployment to an account with non-empty balance: the balance is inherited (No change)
  4. Contract deployment to an account with non-empty storage: the storage is retained (Change: previously the storage was deleted)

Rationale

The reason for choosing to retain the leftover storages:

  • There are some extra cost/complexities to delete the leftover storage, especially when verkle is enabled
  • Account reset notion (if storage is totally discarded) is a very non-intuitive notion, and should be removed.

An acceptable alternative, would be to reject contract deployement if storage is non-empty.

Appendix

The list of re-deployable accounts with non-empty storage on ethereum mainnet:

  • f468bcbc4a0bfdb06336e773382c5202e674db71
  • d8253352f6044cfe55bcc0748c3fa37b7df81f98
  • 5983c6ac846dcf85fbbc4303f43eb91c379f79ae
  • de425ad4b8d2d9e0e12f65cbcd6d55f447b44083
  • 50b1497068bae652df3562eb8ea7677ff84477fa
  • 8398ff6c618e9515468c1c4b198d53666cbe8462
  • 6f156dbf8ed30e53f7c9df73144e69f65cbb7e94
  • 2c081ed1949d7dd9447f9d96e509befe576d4461
  • db7c577b93baeb56dab50af4d6f86f99a06b96a2
  • 14725085d004f1b10ee07234a4ab28c5ad2a7b9e
  • ae3703584494ade958ad27ec2d289b7a67c19e90
  • 7d6ae067de8d44ae1a08750e7d626d61a623c44a
  • 4d149eb99bdeefc1f858f8fd22289c6beae99f2c
  • 361d7a60b43587c7f6bba4f9fd9642747f65210a
  • b619f45637c39ca49a41ac64c11637a0a194455e
  • 5071cb62aa170b7f66b26cae8004d90e6078bb1e
  • add92e0650457c5db0c4c08cbf7ca580175d33d2
  • 3311c08066580cb906a7287b6786e504c2ebd09f
  • 02820e4bee488c40f7455fdca53125565148708f
  • e62dc49c92fa799033644d2a9afd7e3babe5a80a
  • 5cc182fabfb81a056b6080d4200bc5150673d06f
  • f4a835ec1364809003de3925685f24cd360bdffe
  • fc4465f84b29a1f8794dc753f41bef1f4b025ed2
  • 40490c9c468622d5c89646d6f3097f8eaf80c411
  • a21b22389bfc1cd6bc7ba19a4fc96adc3d0fe074
  • 59ec0410867828e3b8c23dd8a29d9796ef523b17
  • 19272418753b90d9a3e3efc8430b1612c55fcb3a
  • fee7707fa4b8c0a923a0e40399db3e7ce26069c6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment