干货 | Solidity 中的代理库

Ajian   |     |   1965 次阅读

Zeppelin SolutionsAragon 联合发布

我们最近读了一些关于 Solidity 中可以做的一些巧妙的把戏和攻击的文章。具体来讲,有 Jorge Izquierdo 写的关于库驱动型开发的文章 以及 Simon de la Rouviere 写的关于 ThrowProxy 的文章

这让我们开始思考如何利用这些想法将 Zeppelin 转变成一个可升级的部署在区块链上的代码库。目前,Zeepelin 是一个所有人可用的安全合约、社区审查合约的集合。这是创建安全标准以及行业最佳实践的工作。但是为了使用他们,开发者必须下载我们的代码并围绕他们的特定应用合约部署这些代码的并行副本。

这有如下几条缺点:

如果我们有一个在区块链上有个已部署版本的库,让所有使用 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 币的迁移机制可以作为灵感。
  • 代理中存储布局的可用性检查。
  • 紧急合约的任意代码执行。

我们将在接下来的几周讨论这些主题。

感谢 Christian Reitwiessner 对初稿的审查和评价。如果你对智能合约安全性感兴趣,欢迎加入我们的slack在Medium上关注我们,或者申请与我们一起工作!我们同时也从事智能合约安全发展以及审计工作


原文链接: https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd
作者: Manuel Araoz
翻译&校对: Aisling & 阿剑


你可能还会喜欢:

干货 | 以太坊可更新智能合约研究与开发综述
白皮书 | zeppelin_os
科普 | 小跑进入以太坊,Part-2

 
0 人喜欢