以太坊智能合约升级

以太坊上的智能合约是在以太坊虚拟机 (EVM) 中运行的自动执行程序。

这些程序在设计上是不可变的,一旦部署了合约,就会阻止对业务逻辑的任何更新。

虽然不可变性对于智能合约的去信任、去中心化和安全性是必要的,但在某些情况下它可能是一个缺点。

例如,不可变代码可能使开发人员无法修复易受攻击的合约。

然而,对改进智能合约的更多研究导致引入了几种升级模式。

这些升级模式使开发人员能够通过将业务逻辑放在不同的合约中来升级智能合约(同时保持不变性)。

什么是智能合约升级?

智能合约升级涉及更改智能合约的业务逻辑,同时保留合约的状态。

重要的是要澄清可升级性和可变性是不一样的,尤其是在智能合约的背景下。

你仍然无法更改部署到以太坊网络地址的程序。但是您可以更改用户与智能合约交互时执行的代码。

这可以通过以下方法完成:

  1. 创建智能合约的多个版本并将状态(即数据)从旧合约迁移到合约的新实例。

  2. 创建单独的合约来存储业务逻辑和状态。

  3. 使用代理模式将函数调用从不可变代理合约委托给可修改的逻辑合约。

  4. 创建一个不可变的主合约,该合约与灵活的卫星合约交互并依赖于执行特定功能。

  5. 使用菱形模式将代理合约的函数调用委托给逻辑合约。

升级机制#1:合约迁移

合同迁移基于版本控制——创建和管理同一软件的独特状态的想法。

合约迁移涉及部署现有智能合约的新实例并将存储和余额转移到新合约。

新部署的合约将有一个空存储,允许您从旧合约中恢复数据并将其写入新的实现。

之后,您将需要更新与旧合约交互的所有合约以反映新地址。

合约迁移的最后一步是说服用户改用新合约。新的合约版本将保留用户余额和地址,从而保持不变性。

如果是基于代币的合约,您还需要联系交易所丢弃旧合约并使用新合约。

合约迁移是一种在不破坏用户交互的情况下升级智能合约的相对简单且安全的措施。

但是,手动将用户存储和余额迁移到新合约非常耗时,并且会产生高昂的 gas 成本。

升级机制#2:数据分离

升级智能合约的另一种方法是将业务逻辑和数据存储分离到单独的合约中。

这意味着用户与逻辑合约交互,而数据存储在存储合约中。

逻辑合约包含用户与应用程序交互时执行的代码。它还保存存储合约的地址并与之交互以获取和设置数据。

同时,存储合约持有与智能合约相关的状态,例如用户余额和地址。请注意,存储合约归逻辑合约所有,并在部署时配置了后者的地址。这可以防止未经授权的合约调用存储合约或更新其数据。

默认情况下,存储合约是不可变的——但您可以将其指向的逻辑合约替换为新的实现。这将更改在 EVM 中运行的代码,同时保持存储和余额不变。

使用这种升级方式需要更新存储合约中的逻辑合约地址。出于前面解释的原因,您还必须使用存储合约的地址配置新的逻辑合约。

与合同迁移相比,数据分离模式可以说更容易实现。但是,您必须管理多个合约并实施复杂的授权方案以保护智能合约免受恶意升级。

升级机制#3:代理模式

代理模式还使用数据分离将业务逻辑和数据保存在单独的合约中。

但是,在代理模式中,存储合约(称为代理)在代码执行期间调用逻辑合约。这是数据分离方法的逆向,逻辑合约调用存储合约。

这就是代理模式中发生的事情:

用户与存储数据但不保存业务逻辑的代理合约进行交互。

代理合约存储逻辑合约的地址,并使用delegatecall函数将所有函数调用委托给逻辑合约(持有业务逻辑)。

调用转发到逻辑合约后,从逻辑合约中取出返回的数据并返回给用户。

使用代理模式需要了解委托调用函数。

基本上,delegatecall 是一个操作码,它允许一个合约调用另一个合约,而实际的代码执行发生在调用合约的上下文中。

在代理模式中使用委托调用的一个含义是代理合约读取和写入其存储并执行存储在逻辑合约中的逻辑,就像调用内部函数一样。

来自 Solidity 文档:

存在一个消息调用的特殊变体,名为 delegatecall,它与消息调用相同,除了目标地址的代码在调用合约和 msg.sender 的上下文(即地址)中执行之外msg.value 不会改变它们的值。这意味着合约可以在运行时从不同的地址动态加载代码。存储、当前地址和余额仍然是调用合约,只是代码取自被调用地址。

代理合约知道在用户调用函数时调用委托调用,因为它内置了一个回退函数。在 Solidity 编程中,当函数调用与合约中指定的函数不匹配时,会执行回退函数。

使代理模式工作需要编写一个自定义回退函数,该函数指定代理合约应如何处理它不支持的函数调用。在这种情况下,代理的回退功能被编程为启动委托调用并将用户的请求重新路由到当前的逻辑合约实现。

代理合约默认是不可变的,但可以创建具有更新业务逻辑的新逻辑合约。然后执行升级就是更改代理合约中引用的逻辑合约的地址。

通过将代理合约指向一个新的逻辑合约,用户调用代理合约函数时执行的代码会发生变化。这允许我们在不要求用户与新合约交互的情况下升级合约的逻辑。

代理模式是升级智能合约的一种流行方法,因为它们消除了与合约迁移相关的困难。但是,代理模式使用起来更加复杂,如果使用不当,可能会引入关键缺陷,例如函数选择器冲突。

升级机制#4:策略模式

这种技术受到策略模式的影响,该模式鼓励创建与其他程序接口以实现特定功能的软件程序。

将策略模式应用于以太坊开发将意味着构建一个调用其他合约功能的智能合约。

在这种情况下,主合约包含核心业务逻辑,但与其他智能合约(“卫星合约”)接口以执行某些功能。这个主合约还存储了每个卫星合约的地址,并且可以在卫星合约的不同实现之间切换。

您可以构建一个新的卫星合约并使用新地址配置主合约。这允许您更改智能合约的策略(即实施新逻辑)。

尽管与前面讨论的代理模式相似,但策略模式不同,因为用户与之交互的主合约持有业务逻辑。使用此模式可以让您有机会在不影响核心基础架构的情况下对智能合约进行有限的更改。

主要缺点是这种模式主要用于推出小升级。此外,如果主合约被破坏(例如,通过黑客攻击),您不能使用此升级方法

升级机制#5:钻石纹

菱形图案可以被认为是对代理图案的改进。菱形模式与代理模式不同,因为菱形代理合约可以将函数调用委托给多个逻辑合约。

菱形图案中的逻辑契约称为构面。要使菱形模式起作用,您需要在代理合约中创建一个映射,将函数选择器映射到不同的构面地址。

当用户进行函数调用时,代理合约会检查映射以找到负责执行该函数的方面。然后它调用delegatecall(使用回退函数)并将调用重定向到适当的逻辑合约。

钻石升级模式与传统代理升级模式相比有一些优势:

它允许您在不更改所有代码的情况下升级合同的一小部分。使用代理模式进行升级需要创建一个全新的逻辑合约,即使是小的升级也是如此。

所有智能合约(包括代理模式中使用的逻辑合约)都有 24KB 的大小限制,这可能是一个限制——尤其是对于需要更多功能的复杂合约。菱形模式通过在多个逻辑合约之间拆分功能可以轻松解决此问题。

代理模式采用包罗万象的方法来进行访问控制。有权访问升级功能的实体可以更改整个合同。但是菱形模式支持模块化权限方法,您可以在其中限制实体升级智能合约中的某些功能。

参考资料

https://ethereum.org/zh/developers/docs/smart-contracts/upgrading/