How to determine the address of a smart contract before deployment: using CREATE2 for a crypto exchange

The topic of the blockchain does not cease to be a source of not only all sorts of hype, but also very valuable ideas from a technological point of view. Therefore, she did not bypass the inhabitants of the sunny city. People are looking closely, studying, trying to shift their expertise in the traditional infobase to blockchain systems. So far, pointwise: one of the developments of Rostelecom-Solar is able to check the security of software based on the blockchain. And along the way, some thoughts arise on solving applied problems of the blockchain community. One of these life hacks - how to determine the address of a smart contract before deployment using CREATE2 - today I want to share with you under the cut.

How to determine the address of a smart contract before deployment: using CREATE2 for a crypto exchange
The CREATE2 opcode was added in the Constantinople hard fork on February 28 this year. As stated in the EIP, this opcode was introduced primarily for state channels. However, we used it to solve a different problem.

There are users with balances on the exchange. We must provide each user with an Ethereum address, to which anyone can send tokens, thereby replenishing their account. Let's call these addresses "wallets". When tokens arrive in wallets, we must send them to a single wallet (hotwallet).

In the following sections, I analyze options for solving this problem without CREATE2 and explain why we abandoned them. If you are only interested in the final result, you can find it in the Final Solution section.

Ethereum addresses

The simplest solution is to generate new ethereum addresses for new users. These addresses will be the wallets. To transfer tokens from the wallet to the hotwallet, you need to sign the transaction by calling the function transfer() with the private key of the wallet from the backend.

This approach has the following advantages:

  • it's simple
  • the cost of transferring tokens from wallet to hotwallet is equal to the cost of calling the function transfer()

However, we abandoned this approach because it has one significant drawback: you need to store private keys somewhere. And it's not only that they can be lost, but also that you need to carefully manage access to these keys. If at least one of them is compromised, then the tokens of a certain user will not reach the hot wallet.

How to determine the address of a smart contract before deployment: using CREATE2 for a crypto exchange

Create a separate smart contract for each user

Deploying a separate smart contract for each user allows you not to store private keys from wallets on the server. The exchange will call this smart contract to transfer the tokens to the hotwallet.

We also abandoned this solution, since the user cannot be shown his wallet address without deploying a smart contract (this is actually possible, but in a rather complicated way with other disadvantages that we will not discuss here). On the exchange, the user can create as many accounts as he needs, and everyone needs their own wallet. This means that we need to spend money on deploying a contract without even being sure that the user will use this account.

Opcode CREATE2

To fix the problem of the previous method, we decided to use the CREATE2 opcode. CREATE2 allows you to pre-determine the address where the smart contract will be deployed. The address is calculated using the following formula:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]


, where:

  • address β€” address of the smart contract that will call CREATE2
  • salt - random value
  • init_code - smart contract bytecode for deployment

This ensures that the address we provide to the user will actually contain the desired bytecode. Also, this smart contract can be deployed whenever we need. For example, when a user decides to use their wallet for the first time.
How to determine the address of a smart contract before deployment: using CREATE2 for a crypto exchange
Moreover, you can calculate the smart contract address each time instead of storing it, because:

  • address in the formula is constant as it is the address of our wallet factory
  • salt - user_id hash
  • init_code is permanent since we use the same wallet

More improvements

The previous solution still has one drawback: you need to pay for smart contract deployment. However, you can get rid of it. For this you can call the function transfer(), and then selfdestruct() in the wallet constructor. And then the gas for deploying the smart contract will be returned.

Contrary to popular belief, you can deploy a smart contract to the same address multiple times with the CREATE2 opcode. This is because CREATE2 checks that the nonce of the target address is zero (it is assigned the value "1" at the beginning of the constructor). At the same time, the function selfdestruct() resets the nonce address each time. Thus, if you call CREATE2 again with the same arguments, the nonce check will pass.

Please note that this solution is similar to the ethereum address solution, but without the need to store private keys. The cost of transferring money from a wallet to a hotwallet is approximately equal to the cost of calling a function transfer(), since we do not pay for the deployment of the smart contract.

Final decision

How to determine the address of a smart contract before deployment: using CREATE2 for a crypto exchange

Initially prepared:

  • function to get salt by user_id
  • smart contract that will call the CREATE2 opcode with the appropriate salt (i.e. wallet factory)
  • wallet bytecode corresponding to the contract with the following constructor:

constructor () {
    address hotWallet = 0x…;
    address token = 0x…;
    token.transfer (hotWallet, token.balanceOf (address (this)));
    selfdestruct (address (0));
}


For each new user, we show his/her wallet address by calculating

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]


When the user transfers tokens to the corresponding wallet address, our backend sees the Transfer event with the parameter _toequal to the wallet address. At this point, it is already possible to increase the user's balance on the exchange before deploying the wallet.

When enough tokens accumulate in the wallet address, we can transfer them all at once to the hotwallet. To do this, the backend calls the smart contract factory function, which performs the following actions:

function deployWallet (соль uint256) {
    bytes memory walletBytecode =…;
    // invoke CREATE2 with wallet bytecode and salt
}


Thus, the wallet smart contract constructor is called, which transfers all its tokens to the hotwallet address and then self-destructs.

The complete code can be found here. Please note that this is not our production code, since we decided to optimize the wallet bytecode and write it in opcodes.

Author Pavel Kondratenkov, Ethereum specialist

Source: habr.com

Add a comment