Solidity可升级代理模式: 透明代理与UUPS代理

Solidity有两种模式实现升级:透明代理与UUPS代理, 他们的实现由细微差别,本文一起来看一看。

在你进一步行动之前,我假设你已经有一些Solidity的经验,并且知道存储槽在合约中是如何工作的。

为什么要用代理?

如果你有关注区块链和智能合约,你一定遇到过这个词--不可变性(immutable)。嗯,智能合约是不可变的。是的,你不能在该地址调整合约的任何功能。你只能与它交互。就是这样,而且是为了它的更好的发展! 否则,如果说有一天某个控制者突然对合约中的规则做出了有利于他们的规定,那么它就不会那么 值得信赖了。这与传统系统有着鲜明的区别,在传统的系统中,每天都会被修复措施推动着。

代理可以做什么?

鼓声响起来......代理 - 横空出世!代理已经有很多人研究,让代理模式成为目前升级智能合约的事实(标准)。

但是,等等,你刚才不是说合约是不可变的,不能被改变的吗!?

当然了! 当然!而且这个不可更改的部分仍然是正确的。但是代理可以解决这个问题。 尽管区块链的不可变性有很多好处,但在多个版本中推送bug修复和补丁是不能忽视的,而且非常需要修补bug和安全漏洞。代理模式解决了这个问题。让我们来看看代理如何工作的。

代理如何工作

这里的基本想法是有一个代理合约。在继续前行之前,先说一下背景术语:

代理合约 - 一个作为代理的合约,将所有调用委托给它所代理的合约。在这里,它也会被称为存储层

实现合约 - 你想升级或修补的合约。这是代理合约被代理的合约。在这种情况下,它也是逻辑层

代理合约将执行或逻辑层的合约地址存储为一个状态变量。与普通合约不同的是,用户实际上并不直接向逻辑层发送调用--那是我们的原始合约。相反,所有的调用都要经过代理,这个代理将调用委托给这个逻辑层(它是在代理地址上存储的具体实现合约),并把从逻辑层收到的任何数据返回给调用者,或者对错误进行回退。

delegatecall
User ---------->  Proxy  -----------> Implementation
             (storage layer)          (logic layer)

这里需要注意的关键是,代理通过delegatecall函数调用逻辑合约。因此,实际上是由代理合约来存储状态变量,即它是存储层。这就像你只是从执行合约(逻辑层!)借用逻辑,并在代理的上下文中执行,并影响代理合约在存储中的状态变量。

举个例子,考虑一个简单的Box(实现)合约,以及BoxProxy(代理)合约。

contract Box {
    uint256 private _value;

    function store(uint256 value) public {
        _value = value;
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }
}

contract BoxProxy {

     function _delegate(address implementation) internal virtual {
         // delegating logic call to boxImpl...
     }

     function getImplementationAddress() public view returns (address) {
         // Returns the address of the implementation contract
     }

     fallback() external {
         _delegate(getImplementationAddress());
     }
}

尽管Box定义了一个uint256状态变量_value,但实际上是BoxProxy合约存储了与_value相关的值(在槽0中,根据存储布局规则 - 中文文档)。

委托代码通常被放在代理的fallback函数中。

因此升级机制可以理解为:授权改变代理合约存储变量的实现合约地址,以指向整个新部署的、升级的实现合约。这样,升级就完成了。代理现在将调用委托给这个新合约。虽然那个旧合约会永远存在。

upgrade call
Admin -----------> Proxy --x--> Implementation_v1
                     |
                      --------> Implementation_v2

很简单吧?但是有一些问题,比如代理和实现合约之间潜在的、由delegatecall引起存储碰撞,。

代理合约和实现合约之间的存储碰撞

我们不能简单地在代理合约中声明 address implementation,因为这会引起与实现合约的存储发生冲突,即与实现合约中的多个变量在存储槽中有重叠。

|Proxy                   |Implementation |
|------------------------|---------------|
|address implementation  |address var1   | <- 碰撞!
|                        |mapping var2   |
|                        |uint256 var3   |
|                        |...            |

对实现中的var1的任何写入,实际上都会写入Proxy中的implementation(存储层!)。

解决方案是选择一个伪随机槽,并将 `impleme...

剩余50%的内容订阅专栏后可查看

点赞 8
收藏 12
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO