# 地址生成规则

By [kool](https://paragraph.com/@kool) · 2022-06-09

---

部署智能合约使用CREATE2
===============

操作码使我们能够预测将`CREATE2`部署合约的地址，而无需这样做。这为提高[用户引导和可扩展性](https://blog.openzeppelin.com/getting-the-most-out-of-create2/)提供了许多可能性。

在本指南中，我们将预先计算将部署合约的地址并将 Ether 发送到该地址。然后，我们将在同一个地址部署一个可升级的合约，并使用它来检索之前发送到那里的资金。

本指南介绍了 OpenZeppelin 工具的高级用法，并且需要熟悉 Solidity、开发区块链和 OpenZeppelin CLI。

如需复习这些主题，请前往[部署智能合约并与之交互](https://docs.openzeppelin.com/learn/deploying-and-interacting)。

创建智能合约
------

部署智能合约的主要方式有两种：使用`CREATE`和`CREATE2`流。我们将简要概述它们的工作原理及其核心区别。

如果您已经熟悉背后的目标`CREATE2`，请随意[跳过](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2#create2-from-the-cli)。

### CREATE

智能合约既可以由其他合约（使用[Solidity 的](https://solidity.readthedocs.io/en/v0.5.15/control-structures.html#creating-contracts-via-new)`new`关键字）创建，也可以由普通账户（例如运行时`oz deploy`）创建。在这两种情况下，新合约的地址都以相同的方式计算：作为发送者自己的地址和随机数的函数。

    new_address = hash(sender, nonce)
    

每个账户都有一个关联的 nonce：对于普通账户，它在每笔交易中都会增加，而对于合约账户，它会在每次合约创建时增加。随机数不能重复使用，它们必须是顺序的。

这意味着可以预测_下一个_创建的合约将被部署到的地址，但前提_是_在此之前没有其他交易发生——这对于反事实系统来说是一个不受欢迎的属性。

### CREATE2

这个操作码背后的整个想法是使生成的地址_独立于未来的事件_。无论区块链上可能发生什么，始终可以在预先计算的地址上部署合约。

新地址具有以下功能：

*   `0xFF`, 一个防止与`CREATE`
    
*   发件人自己的地址
    
*   盐（发件人提供的任意值）
    
*   待部署合约的字节码
    

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

`CREATE2`保证如果`sender`部署`bytecode`using`CREATE2`和提供的`salt`，它将存储在`new_address`.

因为`bytecode`包含在这个计算中，所以其他代理可以_依赖_这样一个事实，即如果一个合约被部署到`new_address`，它将是他们知道的一个。这是反事实部署背后的关键概念。

CREATE2从 CLI使用
--------------

因为`CREATE2`是 EVM 操作码，它通常只能由智能合约使用，而不能由外部账户使用。然而，OpenZeppelin CLI 提供了一种`CREATE2`直接从终端运行类似部署的便捷方式。

我们将从使用合约[初始化一个新的 OpenZeppelin 项目](https://docs.openzeppelin.com/cli/2.8/getting-started#setting-up-your-project)`Vault`开始：

    // contracts/Vault.sol
    pragma solidity ^0.5.0;
    
    import "@openzeppelin/upgrades/contracts/Initializable.sol";
    
    contract Vault is Initializable {
        address payable owner;
    
        function initialize(address payable _owner) initializer public {
            owner = _owner;
        }
    
        function withdraw() public {
            require(owner == msg.sender);
            owner.transfer(address(this).balance);
        }
    }
    

我们将计算`Vault`将要部署的地址，并将以太币发送到那里。然后，我们将`Vault`使用`CREATE2`并调用该`withdraw`方法进行部署，检索在部署之前发送给它的资金。

虽然这个简单的例子听起来很傻，但能够与尚不存在的合约交互是一个非常强大的工具，并且是状态通道、入职解决方案和抢先预防计划背后的关键构建块。

计算部署地址
------

回想一下，只有智能合约可以使用`CREATE2`：为了让 OpenZeppelin CLI 能够提供等效的行为，我们需要先进行一些设置。

在底层，CLI 将使用合约工厂来[部署来自 Solidity 的可升级合约](https://docs.openzeppelin.com/upgrades/2.8/creating-upgradeable-from-solidity)。这意味着我们需要使用两个不经常使用的低级 CLI 命令：`oz add`和`oz push`：

    $ npx oz add
    ? Pick which contracts you want to add Vault
    ✓ Added contract Vault
    $ npx oz push
    ✓ Contract Vault deployed
    All contracts have been deployed
    

您可以在不了解幕后情况的情况下安全使用`CREATE2`，但如果您想深入了解血淋淋的细节，请从了解[OpenZeppelin 升级代理](https://docs.openzeppelin.com/upgrades/2.8/proxies)开始。

有了这个设置，我们可以通过调用任意命令_查询_CLI 以获取我们的合约将被部署的地址：`saltoz create2`

    $ npx oz create2 --query --salt 12345 --network development
    ✓ Deployed ProxyFactory at 0x4e08589Cd399474157f24f591B9fB100D1adD5d9
    
    Any contract created with salt 12345 will be deployed to the following address
    0x4e08589Cd399474157f24f591B9fB100D1adD5d9
    

整洁的！我们现在可以与计算出的地址进行交互，知道我们以后可以在那里部署合约。

与反事实合约交互
--------

在正常情况下，将资金发送到随机的以太坊地址是一个坏主意。然而，在这里，我们知道我们将能够`Vault`在计算出的地址进行部署并取回我们的资金。所以让我们开始吧！

发送 Ether 的最简单方法是使用`oz transfer`：

    $ $ npx oz transfer
    ? Pick a network development
    ? Choose the account to send transactions from (0) 0xA84577357099567A750f542C2C002B0aA680d477
    ? Enter the receiver account 0x98329e006610472e6B372C080833f6D79ED833cf
    ? Enter an amount to transfer 10 ether
    ✓ Funds sent. Transaction hash: 0x9cff31198a80cefb9541e5cf406433f985490a4d786b72bb7e07139ae293657d
    

因为该地址没有字节码，我们也没有它的私钥，所以除了检查资金是否确实存在之外，我们无能为力：

    $ npx oz balance
    ? Enter an address to query its balance 0x98329e006610472e6B372C080833f6D79ED833cf
    Balance: 10 ETH
    

让我们把他们找回来。

退出我们的Vault
----------

`CREATE2`dpeloyments 使用相同的`oz create2`命令执行，这次没有`--query`选项。

回想一下，它的所有者`Vault`有一个`initialize`方法：我们将使用我们控制的一个帐户来调用它。

    $ npx oz create2 Vault --salt 12345 --init --args 0xA84577357099567A750f542C2C002B0aA680d477 --network development
    ✓ Instance created at 0x98329e006610472e6B372C080833f6D79ED833cf
    

如果一切顺利，我们现在应该能够`withdraw`从我们的`Vault`：

    $ npx oz send-tx
    ? Pick a network development
    ? Pick an instance Vault at 0x272F769068bDB8740e44E6e0E852b97c8C4865b0
    ? Select which function withdraw()
    
    ✓ Transaction successful. Transaction hash: 0xb0a67ba8a198a0d86814519ed12de8fbeaaaab151ae3b70f67a608236627ec4b
    

成功！可以肯定的是，让我们验证一下`Vault`确实是空的：

    $ npx oz balance
    ? Enter an address to query its balance 0x98329e006610472e6B372C080833f6D79ED833cf
    Balance: 0 ETH
    

我们已经将资金发送到我们预先计算好的地址，知道我们以后可以在那里部署合约并取回它们。作为奖励，我们的`Vault`合约可以通过`oz upgrade`!

---

*Originally published on [kool](https://paragraph.com/@kool/LjsYGpQZusaCq19MZzyQ)*
