升级智能合约
上次编辑: , Invalid DateTime
以太坊上的智能合约是在以太坊虚拟机 (EVM) 中运行的自执行程序。 这些程序在设计上不可变,这意味着一旦部署合约,就无法对业务逻辑进行任何更新。
尽管不可变性对于智能合约的信任性、去中心化和安全性是必要的,但在某些情况下,它可能具有一定的缺点。 例如,不可变的代码可能会使开发者无法修复存在漏洞的合约。
然而,对改进智能合约的研究不断加强,导致引入了几种升级模式。 这些升级模式使开发者能够通过将业务逻辑放置在不同的合约中来升级智能合约(同时保持不可变性)。
前提条件
你应当充分了解智能合约、智能合约结构以及以太坊虚拟机 (EVM)。 本指南还假定读者掌握了编写智能合约的知识。
什么是智能合约升级?
智能合约升级涉及更改智能合约的业务逻辑,同时保留合约的状态。 重要的是要澄清,可升级性和可变性并不是相同的概念,尤其是在智能合约的背景下。
你仍然无法更改在以太坊网络上部署的地址上的程序。 但是你可以更改与用户交互时执行的智能合约代码。
这可以通过以下方式完成:
创建智能合约的多个版本并从旧合约到新合约实例迁移状态(即数据)。
创建独立的合约以存储业务逻辑和状态。
使用代理模式将不可修改的代理合约中的函数调用委托给可修改的逻辑合约。
创建一个不可变的主合约,与灵活的卫星合约进行接口交互,并依赖这些卫星合约来执行特定的功能。
使用钻石模式将函数调用从代理合约委托给逻辑合约。
升级机制 #1:合约迁移
合约迁移是基于版本控制的概念,即创建和管理相同软件的独特状态。 合约 迁移涉及部署现有智能合约的新实例,并将存储和余额转移到新合约中。
新部署的合约将有空的存储空间,使你能够从旧合约中恢复数据并将其写入新的实现。 之后,您将需要更新与旧合约交互的所有合约,以反映新地址的变更。
合约迁移的最后一步是说服用户改为使用新合约。 新合约版本将保留用户的余额和地址,从而保持不可变性。 如果这是一个基于代币的合约,你还需要与交易所联系,废弃旧合约并使用新合约。
合约迁移是一种相对简单且安全的方法,用于升级智能合约而不影响用户交互。 然而,手动迁移用户存储和余额到新合约非常耗时,且可能产生高额的燃料费用。
更多关于合约迁移的信息。(opens in a new tab)
升级机制 #2:数据分离
升级智能合约的另一种方法是将业务逻辑和数据存储分离为不同的合约。 这意味着用户与逻辑合约进行交互,而数据存储在存储合约中。
逻辑合约包含当用户与应用程序进行交互时执行的代码。 它还保存了存储合约的地址,并与之进行交互以获取和设置数据。
同时,存储合约保存与智能合约相关的状态,例如用户的余额和地址。 请注意,存储合约由逻辑合约拥有,并在部署时配置为后者的地址。 这可以防止未经授权的合约调用存储合约或更新其数据。
默认情况下,存储合约是不可变的,但你可以用新的实现替换它所指向的逻辑合约。 这将修改在以太坊虚拟机中运行的代码,同时保持存储和余额不变。
使用这种升级方法需要在存储合约中更新逻辑合约的地址。 你还必须基于上文解释的原因,配置新的逻辑合约,并提供存储合约的地址。
与合约迁移相比,数据分 离模式在实现上按理说更容易。 然而,你将需要管理多个合约并实施复杂的授权方案,以保护智能合约免受恶意升级的影响。
升级机制 #3:代理模式
代理模式还使用数据分离,将业务逻辑和数据保存在单独的合约中。 不过,在代理模式中,存储合约(称为代理)会在代码执行过程中调用逻辑合约。 这与数据分离法相反,即逻辑合约调用存储合约。
这是代理模式中的原理:
用户与代理合约进行交互,代理合约存储数据,但不保存业务逻辑。
代理合约存储逻辑合约的地址,并使用
delegatecall
函数将所有函数调用委托给逻辑合约(逻辑合约保存业务逻辑)。调用转移到逻辑合约后,逻辑合约返回的数据将被检索并返回给用户。
使用代理模式需要了解 delegatecall 函数。 基本上,delegatecall
是一个操作 码,它允许一个合约调用另一个合约,而实际的代码执行在调用合约过程中进行。 在代理模式中使用 delegatecall
的意义是,代理合约会读写其存储,并执行存储在逻辑合约中的逻辑,就像调用内部函数一样。
摘自 Solidity 文档(opens in a new tab):
存在一种消息调用的特殊变体,名为 delegatecall,它与消息调用相同,但是目标地址的代码在调用合约的语境(即地址)下执行,并且
msg.sender
和msg.value
不会更改其值。__这意味着合约在运行时可以从不同的地址动态加载代码。 存储、当前地址和余额仍参考调用合约,只是代码取自被调用地址。
每当用户调用函数时,代理合约就会调用 delegatecall
,因为它内置了一个 fallback
函数。 在 Solidity 编程中,当函数调用与合约中指定的函数不匹配时,将执行回退函数(opens in a new tab)。
要使代理模式正常工作,需要编写一个自定义的回退函数,指定代理合约应如何处理它不支持的函数调用。 在这种情况下,代理的回退函数被编程为启动委托调用,并将用户的请求转发给当前的逻辑合约实现。
代理合约在默认情况下是不可变的,但可以创建更新了业务逻辑的新逻辑合约。 然后,只需更改代理合约中引用的逻辑合约的地址即可执行升级。
通过将代理合约转向新的逻辑合约,用户调用代理合约函数时执行的代码就会发生变化。 这样,我们就可以在不要求用户与新合约进行交互的情况下,升级合约的逻辑。
代理模式是一种流行的智能合约升级方法,因为它消除了与合约迁移相关的困难。 但是,代理模式的使用更为复杂,如果使用不当,可能会带来严重缺陷,例如函数选择器冲突(opens in a new tab)。
更多关于代理模式的信息(opens in a new tab)。
升级机制 #4:策略模式
这项技术受到策略模式(opens in a new tab)的影响,该模式鼓励创建与其他程序进行接口交互的软件程序,以实现特定功能。 将策略模式应用于以太坊开发意味着构建一个智能合约,该合约可以调用其他合约的函数。
在这种情况下,主合约包含核心业务逻辑,但与其他智能合约(“卫星合约”)进行接口交互,以执行某些功能。 该主合约还存储每个卫星合约的地址,并可在卫星合约的不同实现之间切换。
你可以构建一个新的卫星合约,并为主合约配置新地址。 这允许你更改智能合约的策略(即实现新的逻辑)。
虽然策略模式与前面讨论的代理模式类似,但其不同之处在于,用户交互的主合约中包含了业务逻辑。 使用这种模式可以让你有机会在不影响核心基础架构的情况下对智能合约进行有限的更改。
这种模式的主要缺点是它主要适用于推出小规模升级。 此外,如果主合约被泄露(如被黑客攻击),则无法使用此升级方法。