以太坊智能合约如何提前计算部署地址【CREATE2】

CREATE2是以太坊在2019年2月的康斯坦丁包硬分叉中引入 的一个新操作码。利用CREATE2操作码可以在部署智能合约前就 预先计算出合约的部署地址。在这个教程中,我们将学习使用CRAETE2 计算智能合约部署地址的基本方法,理解CREATE2操作码可以 在同一地址多次部署合约,并利用CREATE2提出了交易平台 开发中一个常见问题的解决方案 —— 为用户生成以太坊钱包地址。

正如EIP中说明的,CREATE2操作码主要用于状态通道,不过我们 可以用它来解决交易所或平台开发中经常遇到的另一个问题。

用自己熟悉的语言学习 以太坊DApp开发Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

1、问题:交易平台如何为用户创建钱包地址?

对于交易所的每个用户,都需要向其提供一个以太坊地址以便 接收用户充入的代币,我们将这些地址称为钱包。当用户充值 进入钱包后,我们需要将重入的代币发送到一个中心钱包(热钱包)。

下面我们分析一下在没有CREATE2操作码时解决上述问题的方案, 以及为什么这些方案被弃用。如果你只对最终结果感兴趣,可以 直接查看最后一节的内容。

2、方案一:生成并使用以太坊外部地址作为用户的平台钱包

最简单的解决方案是为新用户生成以太坊外部地址(EOA)作为用户 的平台钱包。要将代币从用户钱包归集到中心热钱包,我们需要在后台用钱包 的私钥签名代币合约的transfer()调用。

这个方案的优点是:

  • 简单
  • 将代币从用户钱包转到热钱包的价格与调用transfer()的价格一样

不过我们决定放弃这个方案,因为它有一个重大的缺点:你需要 在某处保存私钥,这不仅仅是私钥有可能丢失的问题,你还需要仔细管理 对这些私钥的访问。如果其中一个私钥被盗,那么这个用户的代币就 不会归集到中心热钱包了。

2、方案二:部署并使用合约地址作为用户的平台钱包地址

第二个方案是为每个用户创建一个单独的智能合约并使用智能合约的 部署地址作为用户的平台钱包,这避免了在服务器上保存用户钱包的私钥, 并且交易所或平台可以调用这个智能合约来将用户代币归集到中心热钱包。

不过我们也没有选择这个方案,因为在部署合约之前用户没有办法显示 其钱包地址(实际上是可能的,但是会非常复杂,并且有一些其他问题)。 在交易所或平台上,一个用户应该可以创建任意多的账号,这意味着我们 需要在合约部署上浪费资金,并且还不能确认用户是否使用这个有成本的账号。

3、方案二改进A:使用CREATE2操作码预计算合约部署地址

我们还是决定继续研究采用智能合约地址作为用户钱包账号。 为了解决前面提到的问题,我们决定使用以太坊的CREATE2操作码。 这样我们就可以先不用部署合约了。

CREATE2允许提前计算要部署的智能合约的地址,计算公式如下:

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

其中:

  • address:要调用CREATE2的智能合约地址
  • salt:随机值
  • init_code:要部署合约的字节码

因此,可以保证要提供给用户的地址中包含了期望的字节码。此外,这个 智能合约可以在需要的时候才部署。例如,当决定使用用户的钱包时。

更进一步,交易所或平台可以随时计算智能合约的地址而无需保存这个地址:

  • address:公式中的地址是个常量,这是我们的工厂钱包地址
  • salt:使用user_id的哈希
  • init_code:对同一个要部署的合约而言,这是个常量

4、方案二改进B:用selfdestruct()返还合约部署手续费

前面的解决方案还有一个问题:平台需要为智能合约的部署支付手续费, 不过我们可以想办法避免这一点。为此可以先调用transfer()方法, 然后调用selfdestruct()方法,那么部署合约的手续费就可以得到返还。

与通常的错误认识相反,事实上你可以 在同一个地址使用CREATE2操作码 多次部署智能合约 ,这是因为CREATE2会检查目标地址的nonce是否为0。 在这个示例中,selfdestruct()方法将复位地址的nonce值。因此,如果 你使用同样的参数再次调用CREATE2操作码,对nonce的检查就可以通过。

注意这个解决方案类似于使用以太坊地址的方案,但是不需要保存用户 钱包的私钥。将用户钱包的资金归集到中心热钱包的成本基本等同于调用 transfer()方法的成本,因为我们不再需要为智能合约的部署付费。

5、方案二优化版:按需部署的用户平台钱包,无需额外部署成本

首先准备以下实现代码:

  • 按user_id获取随机值salt的函数
  • 使用恰当的salt值调用CREATE2操作码的智能合约,例如wallet fabric
  • 具有如下构造函数的钱包合约的字节码:
1
2
3
4
5
6
constructor () {
address hotWallet = 0x …;
address token = 0x …;
token.transfer (hotWallet, token.balanceOf (address (this)));
selfdestruct (address (0));
}

对于每个新用户,我们通过计算展示其平台钱包地址:

1
keccak256 (0xff ++ fabric_addr ++ hash (user_id) ++ keccak256 (wallet_init_code)) [12:]

当用户将代币转入其在平台上的钱包地址时,我们的后台系统会监控到 Transfer事件,其 _to参数表示转账目标地址。这时在实际部署钱包合约 前,已经可以增加用户在交易所的余额了。

当用户钱包中累积了足够的代币,我们就可以将其一次转入平台热钱包。 为此,后台调用工厂合约的方法:

1
2
3
4
function deployWallet (uint256 salt) {
bytes memory walletBytecode = …;
// invoke CREATE2 with wallet bytecode and salt
}

因此钱包智能合约的构造函数被调用,这会将所有代币转入平台热钱包然后 自动销毁。

可以在这里下载完整的代码. 注意,这不是我们的生产代码,因为我们还要优化钱包合约的字节码。


原文链接:How to Define Smart Contract Address Before the Deploy: CREATE2 Use Case for Crypto Exchange

汇智网翻译整理,转载请标明出处