构建可升级的合约:透明代理 VS UUPS代理

  • Louis
  • 发布于 2024-07-30 15:56
  • 阅读 2444

智能合约从技术角度实现了"codeislaw",在智能合约的世界里,代码本身就是法律规则的体现。这一理念的核心是,智能合约是自执行的协议,由编写好的代码直接控制,无需中介或第三方干预。

code is law:

大家都说,智能合约从技术角度实现了"code is law",在智能合约的世界里,代码本身就是法律规则的体现。这一理念的核心是,智能合约是自执行的协议,由编写好的代码直接控制,无需中介或第三方干预。一旦智能合约被部署并启动,它就会按照代码中的逻辑自动运行,任何参与者都必须遵循其中的规则。

是的,智能合约是不可改变的,一旦一个合约被部署,意味着你不能在这个地址调整合约的任何功能,你只能与它进行交互。为什么要这么做?试想一下,如果说有一天某个控制者突然对合约中的规则做出了有利于他们的规定,那么这个合约就不值得信赖了。这与传统的系统有着鲜明的区别。

为什么需要代理,代理可以做什么?

一句话总结:代理让合约升级成为事实上的标准,但是上面我们明明已经说了合约是不可以改变的,这不是前后矛盾吗?

其实,合约不可改变这个点是仍然正确的,但是代理可以解决这个问题。

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

代理是如何工作的

在深入讨论之前,我们先说明一些背景术语:

  • 代理合约:一个作为代理的合约,将所有调用委托给它所代理的合约。在这里,它也会被称为存储层。
  • 实现合约:你想升级或修补的合约。这是代理合约被代理的合约。在这种情况下,它也是逻辑层

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

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

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

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

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

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相关的值。

委托相关的逻辑通常被放在代理合约的fallback函数中。

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

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

很简单吧?但是有一些问题,比如代理和实现合约之间潜在的、由delegatecall引起Collisions of Solidity Storage Layouts

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

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

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

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

解决方案是选择一个伪随机槽,并将 implementation的地址写入该槽中。这个...

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

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

1 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis