以太坊最大的优势就是,每一笔用来转账、部署合约或者和合约交互的交易(事务)都被存在一个叫做区块链的公共账本上。一旦交易发生,就再也无法隐藏或者改变。这带来一个巨大的好处,就是在以太坊中的每一个节点都可以去验证任意一笔交易的合法性和当前状态。这使得以太坊成为一个非常健壮的去中心化系统。
但是随之而来的是,它还有一个最大的缺点,就是智能合约一旦部署之后,就再也无法改变源码。开发中心化应用(比如facebook或者Airbnb)的开发者,都已经习惯了,为了修复bug或者引入新的特性而频繁更新产品。但这种方式却不适用以太坊。
还记得当面Parity多签名钱包被黑导致150000以太币被偷的恶劣事件吗?在整个攻击中,就因为钱包中的一个bug导致很多巨额钱包的资金被清空。而唯一的解决方案就是尝试以比黑客更快的速度,利用相同的漏洞攻击剩余的钱包,来把以太币重新分配给它们合法的所有者。
要是有一种方法可以在智能合约部署之后,还能对它们进行升级,那该多好…
引入代理模式
尽管想升级已经部署的智能合约中的代码是不可能的,但是可以通过设计一个代理合约结构,这个结构可以让你可以通过新部署一个合约的方式,来实现升级主要的处理逻辑的目的。
代理结构模式就像下面这张图一样:所有消息通过一个代理合约来间接调用最新部署的逻辑合约。如果想要升级的话,只需要部署一个新的合约,然后在代理合约中更新引用新的合约地址就可以了。
作为实现zeppelin_os的一部分,zeppelin正致力于实现集中代理模式。目前已经探索出来的有下面三个:
- 继承存储模式
Inherited Storage
- 永久存储模式
Eternal Storage
- 非结构化存储模式
Unstructured Storage
所有三种模式都依赖低阶的delegatecall。尽管solidity提供了一个delegatecall方法,但它只能返回true或者false来显示调用是否成功,而不是允许你操作返回的数据。
在我们深入了解之前,理解两个关键的概念很重要:
- 当调用一个合约中并不支持的的方法时,就会调用合约中的fallback方法。你可以自己写一个fallback函数来处理这种场景。代理合约就是用自定义的fallback方法将调用重定向到其他合约实现。
- 每当合约A授权对另一个合约B的调用时,它就会在合约A的上下文中执行合约B的代码。这就意味着
msg.value
和msg.sender
的值会被保留。并且对存储的修改将会作用在合约A的存储上。
zeppelin的代理合约,为了可以返回调用逻辑合约后的结果,实现了自己的delegatecall方法,所有模式都是这样。如果你想要使用zeppelin的代理合约代码,你就要理解代码的每一个细节。让我们先来看看它是如何发挥作用的,以及理解为了达到目的它所使用的assembly操作码。
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
let size := returndatasize
returndatacopy(ptr, 0, size)
switch result
case