在 ETH Cape Town 黑客马拉松期间,我们使用功能强大的,新的以太坊操作码 —— CREATE2,制作了一个合约钱包的快速概念证明。较之 CREATE 操作码,CTEATE2 操作码可以在使用以太坊合约来生成新合约时更好地控制新合约的地址,当你想要反事实地(counter-factually)部署并使用一个自定义权限的合约时,这是非常有用的。

原文标题:《Wisps: The Magical World of Create2》
原文作者:RicMoo
翻译 & 校对 : Aisling & 阿剑

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?鬼火 —— Herrmann Hendrich(1884)

Wisp 合约的目标是演示一种生成稳定合约钱包地址的技术,该地址可与其他用户共享,也可以实现代币和以太币转账功能,同时还可以在不保留任何链上合约钱包代码的情况下控制链上资产(例如 ENS 域名)。

Wisp 地址的优点包括:首先,所有发给这种 Wisp 地址的交易都只需要确定的 21, 000 gas,但是更重要的是,这意味着可以访问代币、资金以及其他链上资产的合约钱包不能被攻击,因为它的代码实际上不在链上。

如果合约钱包中出现错误,可于再次使用前在客户端进行更新,此期间所有加密资产都是安全的。

使用静态引导程序_initcode 可以_获取 CREATE2 runtime 合约字节码从而生成可复用的合约地址。

initcode 是什么?

在深入了解这种技术之前,先得理解以太坊合约的部署过程。

合约的部署过程使用了一段被叫做 initcode 的代码,它只是一个普通的以太坊程序,正常执行将返回部署合约实际所用的合约字节码。虽然有点抽象,但是它可以支撑功能非常强大的部署系统。

你可以想象一下,JavaScript 中 「Hello Word」 的 initcode 可能就是这样:
Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?
这个程序运行时将返回一个 「Hello World」 程序。这样做可以实现额外的部署时配置,例如:

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?
需要注意的非常重要的一点是: 被部署的不是 initcode,而是 initcode 的运行结果。

CREATE vs CREATE2

尽管大多数以太坊开发者都使用了 CREATE 操作码(来部署链上合约),但是他们可能并没有意识到这件事。Solidity 所生成的字节码实际上是 initcode,它使用 CREATE 来执行构造函数中的对应操作,随后返回合约除构造函数外的其他部分。实际部署上链的代码不包含构造函数代码。

为确定部署上链的合约地址,CREATE使用的标准参数包括:

1.发送账户(它本身可能也是一个合约)
2. 发送账户的当前交易序号(也可以是一个合约当前的 nonce)

因此,任意两个不同的发送者将生成不同的合约地址,而来自同一个账户的任意两笔不同交易也将生成不同的合约地址。

为确定部署上链的合约地址,新的CREATE2使用:

1.发送账户(同样,它本身可能也是一个合约)
2.合约 initcode(执行产生合约字节码)
3. 由开发人员设定的自定义数(salt)

因此,CREATE2 与 CREATE 一样,不同的发送者将生成不同的合约地址。而 initcode 相同 但是自定义数不同时将生成不同的合约地址;而 initcode 不同时(通常意味着不同合约),也将生成不同的合约地址。

CREATE2 的另一个值得注意的(有用的)是,由于其对计算合约地址的参数多了一点控制,如果一个合约自毁了,那么新合约未来可以再次部署到这个地址上。但是,如果已经有非自毁合约部署到这个地址上了,那么 CREATE2 不能在这个地址上再次部署一个合约。

综合一下

由于控制发送者以及自定义数很容易,因此实现 Wisp 合约目标唯一要做的事情就是绕过 CREATE2 的第二个参数;最终允许两个不同的合约在不同的时期被部署在同一个地址上

但是,initcode 只是一个用于确定所部署合约的程序。这一特点可以用各种有趣的方法加以利用。

每个 Wisp 合约的入口都是一个启用和管理所有 CREATE2 调用的跳板合约(SpringboardContract),因此跳板合约将一直是发送者。至于 salt,因为使用 msg.sender 的哈希值作为自定义数,因此同一个账户的任意两个调用将始终指向同一个 Wisp 合约。

剩下的是一个普通的(静态)引导程序 initcode。initcode 要在新合约中运行,但是此时新合约还未创建;这意味着 msg.sender
实际上是跳板合约。因此,跳板合约会将所需的合约字节码保存在自己的存储空间中,并提供一个名为 getPendingBytecode()
的公共方法,引导程序的(伪)代码可以简单地表示成如下形式:

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?

也就是说,大体上,我们针对源码删除了一些小细节,同时又添加了一些额外的内容,总之,该技术运行良好,感兴趣的开发者已经可以使用了!

Wisp 合约生存周期

这是一个用于帮助说明 WIsp 合约生存周期的简单图表及摘要:

  1. 一笔交易的目的地址是跳板合约(请注意,合约也可以调用跳板合约,在这种情况下,该合约地址将拥有 Wisp 合约)
  2. CREATE2 被用于初始化 Wisp 合约
  3. 在初始化期间,Wisp 合约回调跳板合约以获取所需的 runtime 字节码,该字节码随后由 initcode 返回。
  4. Wisp 合约的 execute() 方法被调用时,运行所有所需的操作
  5. Wisp 合约的 die() 方法被调用时,销毁该 Wisp 合约,因此未来可在该地址重新创建合约。

注意:所有 ETH 都将返还给 Wisp 合约所有者,因为 ETH 在计划会自毁的合约中是不安全的。

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?

跳板合约代码(Solidity)

一个简单的例子:这是被部署到 Ropsten 上的,在黑客马拉松期间使用的代码。就像大多黑客马拉松的代码那样,它有点简陋,因此不要将其用于实际应用。

该版本同时支持外部所有账户(EOA)和 ENS 域名。如果调用 ENS 域名版本,则 ENS 域名所有者控制 Wisp 合约,该版本允许通过修改 ENS 解析的地址来转移 Wisp 所有者(以及它控制的所有资产)。

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?

在黑客马拉松期间使用的引导程序非常简单,并且是手写代码,因此可以使用部署脚本中的几行 JavaScript 代码轻松组装。但是由于该代码稳健性不够好,需要检查返回状态。

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?

Wisp 示例

GitHub repo 中有几个 Wisp 合约的例子,但是基本上所有的操作都可以放在 execute() 函数中。

出于黑客马拉松的目的,任何转发给 Wisp 的余额在 CREATE2 部分都作为捐赠提供,因此使用 this.balance 而不是 msg.value。正如上面的插图展示的那样,可以转发给至 execute() 函数 。下面是一些可在 Wisp 合约中完成的工作的例子:

Wisps 合约:还在说 Create?一起看看 Create2 有何不一样?

结论

CREATE2 操作码非常棒并且功能多样。目前我们仍在进行相关探索,我们尝试着在我们的多签名合约钱包中使用它创建资产商店。

我们的目标是创造一个可靠并且灵活的资产商店,其可以存储大量的 CryptoKitties,ENS 域名以及各种代币,同时可以轻松实现资产在多签名实例之间的转移。由于 Wisp 合约可以针对其控制的资产执行任意操作,因此它甚至可以使用资产被访问之时还不存在的功能。

可以说它基本上就是一个花哨的委托调用(Delegate Call)。

来源链接:blog.ricmoo.com