Create a different smart contract at the old address

Hi all.

Is it possible to destroy a smart contract and place another smart contract at its address??” – I was asked this question at a Solidity developer interview.

The exact answer requires analysis of the question and determination of the requirements for the task. The requirement is “to change the contract without changing the address”. There are approaches for this with an updatable smart contract… But it turned out not to be. This is a question about the knowledge of Solidity opcodes.

Now I’ll tell you how createdestroyplace_other_contract at the same smart contract address (hereinafter referred to as SMC) without using the Transparent Proxy and UUPS pattern.

Some PR and marketing

My name is Ilya Druzhinin, I am a development engineer. I have a R&D team of web3 developers. We research and develop non-trivial web3 projects (oracles, zkp, insurance, bridges, etc.) and not only for Ethereum.

We also provide personal and team training in the Web3 technology stack for programs of various durations. So write https://t.me/didexbot.

Terms

op_code (op_codes) – instructions for the Ethereum virtual machine. Instructions perform a set of sequential actions on data.

nonce – serial number, in our case transactions

Theory

Op_codes are used to place QMS in EVM CREATE And CREATE2. The CREATE op_code is most commonly used because it has no restrictions on who can call it.

CREATE

At the Solidity level, to create a QMS using the CREATE op_code, the keyword is used newwithout the salt specifier.

contract D {
    uint public x;
    constructor(uint a) payable {
        x = a;
    }
}

contract C {
    D d = new D(4);
    
    function createD(uint arg) public {
        D newD = new D(arg);
    }
}

In this case, the address of the created QMS is calculated based on information about who made the call and its nonce.

new_address = hash(sender, nonce)

Each account has its own nonce: for regular accounts, it increases with each transaction, and for QMS – every time a new QMS is created. Nonces cannot be reused, cannot be requested from code, and they always have a transitive sequence.

It is rather difficult to predict what the address of the QMS will be, since the nonce of the sender of the transaction is not known in advance, especially if the creator of the QMS is another QMS. Of course there are many options address + noncebut we cannot say with certainty which one will be the address of the QMS.

CREATE2

The CREATE2 op_code was entered with EIP-1014 (branch of Constantinople). The main idea behind the introduction of this op_code is the deterministic creation of the QMS address without reference to actions in the future. This op_code was created to improve the performance of the blockchain, through state channels.

Using CREATE2, an address is generated in a predictable way, for this you need:

  • constant 0xFF which prevents collisions with CREATE

  • sender’s address

  • salt – arbitrary data provided by the sender

  • bytecode of hosted QMS

new_address = hash(0xFF, sender, salt, bytecode)

CREATE2 guarantees that if the sender ever deploys bytecode using CREATE2 and the provided salt, then the QMS will be deployed at new_address. Since the bytecode is included in the calculation of the address, it is easy to check that it is this bytecode that creates this address and not some other one. The next example will explain it better.

Note: CREATE2 can only be called from QMS. It is not possible to use CREATE2 to deploy QMS from a user’s address.

CREATE2 can be used in a wide range of use cases. Let’s assume that a web3 service invites users to create charitable fundraising companies. Each charitable campaign will have its own treasury, due date and recipient(s) who will receive the payment for the purpose. Deploying a QMS to users can become quite costly for a web3 service, so deploying a fundraising QMS at the time of creation would be prohibitively expensive and poorly scalable. However, with CREATE2, the Fundraising QMS can be used without deploying the QMS itself. The platform may define a threshold point when the QMS can be deployed. This eliminates the need to create a QMS at the initial stage, while retaining the ability to replenish the QMS account and trust in the underlying logic.

There are some peculiarities regarding creation with CREATE2. QMS can be re-created at the same address after destruction. However, it is possible that a newly created QMS will have a different expanded bytecode, even if the creation bytecode was the same (this is a requirement because the address would otherwise have changed). This is because the constructor can request external state that may have changed between two creations and include it in the expanded bytecode before it is saved.

Can we place different QMS at the same address?

Yes, we can, and here’s an example.

Example

The full source code for the example is here: https://github.com/druzhtech/eth_dev/blob/main/contracts/contracts/Create2.sol

First, a repetition of the theory. Since we already know that QMS addresses are created (op_code CREATE) using the sender’s address and its nonce, we need to come up with a mechanism for resetting the nonce.

How to reset the nonce? Destroy the QMS that creates the QMS and create the same QMS in its place.

Algorithm

  1. We are creating a QMS Factory with the help of CREATE2

    1. Subsequently, we will be able to place the Factory code again at this address.

  2. Create with Factory SMK_A

    1. factory address nonce becomes 1

  3. Destroy SMK_A

  4. Destroying the Factory

    1. nonce factory addresses become 0.

  5. Create the same Factory again with CREATE2

  6. Create with Factory Smk_B

    1. the address of SMK_B will be the same as that of SMK_A, but the code is different.

Code

Basically QMS create function to create QMS Factory using CREATE2.

At the Solidity language level, we can use the keyword new and specify the name of the QMS created by the construction {salt:salt}. This is how the EVM will create the QMS using the CREATE2 op_code

function deploy() external {
  
  address addr = address(new ContractFactory{salt: salt}()); // create2
  
  emit ContractDeployed(addr);
}

In QMS Factory (ContractFactory) create three functions

function deployContract1() external {
  address addr = address(new ContractV1()); // create
  emit ContractDeployed(addr);
}

function deployContract2() external {
  address addr = address(new ContractV2()); // create
  emit ContractDeployed(addr);
}

function destroy() external {
  selfdestruct(payable(creator));
}

QMS ContractV1

contract ContractV1 {
  address creator;
  
  constructor() {
    creator = msg.sender;
  }

  function withdraw() external {
    (bool res, ) = creator.call{value: address(this).balance}("");
    require(res, "failed");
  }
  
  function destroy() external {
    selfdestruct(payable(creator));
  }
}

CM ContractV2 is based on ContractV1, but we change the withdraw function

function withdraw() external {
  (bool res,) = address(0xaB854be0A4d499B6FD8D0bB5F796Ab5b33cE825b).call{value: address(this).balance}("");
  require(res, "failed");
}

Now, when the user checks the QMS at the address with which he previously worked, he sees the signature of the withdraw function that has not been changed, but its behavior has already been changed.

After what has been said

Team selfdestruct since version 0.8.19 is marked as obsolete and not recommended for use. Let me remind you that she has several special behaviors:

  • replenishment of any address with ethers, even if the address does not want to accept ethers

  • total annihilation of ether: selfdestruct(address(this))

Sources

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *