由 Zeppelin Solutions 和 Aragon 联合发布
我们最近读了一些关于 Solidity 中可以做的一些巧妙的把戏和攻击的文章。具体来讲,有 Jorge Izquierdo 写的关于库驱动型开发的文章 以及 Simon de la Rouviere 写的关于 ThrowProxy 的文章 。
这让我们开始思考如何利用这些想法将 Zeppelin 转变成一个可升级的部署在区块链上的代码库。目前,Zeepelin 是一个所有人可用的安全合约、社区审查合约的集合。这是创建安全标准以及行业最佳实践的工作。但是为了使用他们,开发者必须下载我们的代码并围绕他们的特定应用合约部署这些代码的并行副本。
这有如下几条缺点:
- 部署消耗了 Gas
- 代码重复部署在区块链上
- Bug 修复和更新需要在每个项目上独立部署(或者更糟糕,以太坊需要通过硬分叉来修复一个合约的问题)。
如果我们有一个在区块链上有个已部署版本的库,让所有使用 Zeppelin 的项目可以直接链接呢?这就是 Solidity 的库这篇文章的主要想法。问题是,一旦库代码被部署,它就是不可变的了。对于 Zappelin,修复安全漏洞并添加更多可重复使用的模块是用户在意的关键。因此,我们需要一些机制来升级合约代码。
相关的研究包括 Martin Swende 的通用代理以及 Arachnid 的可升级合约,但它们都各有缺点,并且不适用于库。我们的想法是:使用一个标准合约,但将其称作库(使用 delegatecall
而不是 call
)
结果:可以用!
建议的实现
代码:https://github.com/maraoz/solidity-proxy/blob/master/test/test.es6
作者的 GitHub 库:https://github.com/maraoz/solidity-proxy/
检查测试一下代码并给我们反馈吧!我们将在接下来的几周内评估如何使用这项新技术将 Zeppelin 转变成可部署、升级的库。在更技术的层面上,我们的解决方案流程如下所示:
面向用户的主合约不再直接与部署库的地址链接,它链接的是“调度器”。在编译和部署的时候,这种设计都表现良好,尽管调度器还没有实现库的任何方法,如果代码中有一个有效地址,那么这次部署将会成功。
当有一笔新交易到达时,主合约会认为该交易在给它链接的库发起一个 delegatecall
。但其实这个 delegatecall
将被发送给调度器。这就是事情变的有趣的地方。一旦调度器在它的回退函数中捕获到这个 delegatecall
,它就知道库代码的正确版本是哪个了,同时会再次使用 delegatecall
重定向该调用。一旦库返回,将一路返回到主合约。
限制
- 调度器需要知道该库调用返回的内存大小。现在我们通过建立函数签名以及它们的返回类型大小之间的映射解决该问题。为了简单起见,故意没有将这个部分画出来。
- 鉴于
delegatecall
在 EVM 上的工作方式,你只能在从一个合约到另一个与它有着相同存储空间的合约这种情况下使用它。由于库没有存储空间,因此我们将调度器也设计为没有存储空间。这就是为什么需要独立的 DispatcherStorage 保存所有它所需要的数据的原因。同样的,DispatcherStorage 的地址需要被硬编码在合约代码中。
请注意,对于用户合约而言,不需要特别的东西,只是这里不需要链接正确版本的库,只需要与调度器相连即可。
未来的工作
- 升级库时允许用户合约的数据迁移(数据结构可能会更改)。Golem 的 GTN 币的迁移机制可以作为灵感。
- 代理中存储布局的可用性检查。
- 紧急合约的任意代码执行。
原文链接: https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd