首页 > 区块链 > 区块链 > 常见的几种合约升级模式有哪些?
智能合约部署后默认是不可变的,一旦你创建了它们,就没有办法改变它们。每个智能合约都有一个唯一的地址,用户将交易发送到智能合约的地址,来执行存储在该合约中的代码。但是,在实际应用中,开发者通常因为业务升级或安全升级的需求需要通过技术的手段来更新合约逻辑,因此合约升级的开发模式应运而生。常见的几种合约升级模式如下。
1.1 继承存储模式
继承存储模式通过为代理合约和逻辑合约所需的状态变量提供严格的存储顺序来解决状态碰撞问题,代理合约委托逻辑合约进行调用。因此,只有代理合约的存储在使用。继承了公共存储合约的代理合约可以访问其父合约的所有状态变量,每个状态变量根据其索引占据适当的内存位置。
初始化流程如下:
(1)部署 Registry 合约
(2)部署合约的初始版本 (v1),确保它继承了 “可升级” 合约
(3)将初始版本的地址注册到 Registry
(4)请求 Registry 合约创建 UpgradeabilityProxy 实例
(5)调用 UpgradeabilityProxy 来升级到合约的初始版本
升级流程如下:
(1)部署从初始版本继承的新版本的合约 (v2),以确保它保留代理的存储结构和合约初始版本中的存储结构
(2)将合约的新版本注册到 Registry
(3)调用 UpgradeabilityProxy 实例以升级到新的注册版本
虽然继承存储模式解决了可升级合约的存储碰撞问题,但这种方法也有自己的缺点。由于所有先前声明的状态变量都需要被复制到新部署的版本中,因此升级变得昂贵。其中一些可能没有被使用,最终会不必要地占用内存。由于公共存储模式,逻辑合约变得与代理合约紧密耦合。因此,不可能将这些逻辑合约用于不继承公共存储合约的任何其他代理。
1.2 非结构化存储模式
在这种模式中,代理合约往往是具有一些基本函数的最小化合约,XM外汇xm-sign.com大部分的业务逻辑都在逻辑合约里面完成,新的逻辑合约保持了最新的实现合约中存在的状态变量的顺序。代理合约一般需要所有者和实现变量来维持所有权和版本控制,代理合约将这些属性指定为常量,它们被设置在合约的字节码内。OpenZepplin 在其可升级的合约服务中使用了非结构化的存储模式。
初始化流程如下:
(1)部署 OwnedUpgradeabilityProxy 实例
(2)部署合约的初始版本 (v1)
(3)调用 OwnedUpgradeabilityProxy 实例来升级到初始版本的地址
(4)如果逻辑合约依赖它的构造函数来设置一些初始状态,那么在它与代理链接后,就必须重新进行设置,因为代理的存储不知道这些值。OwnedUpgradeabilityProxy 有一个函数 upgradeToAndCall,专门用来调用逻辑合约上的一些函数,在代理升级到它之后重新进行设置
升级流程如下:
(1)部署新版本的合约 (v2),确保它继承以前版本中使用的状态变量结构
(2)调用 OwnedUpgradeabilityProxy 实例来升级到新合约版本的地址
1.3 永久存储模式
这种模式的目标是尽量减少存储复制的要求。在这种模式中,一个单独的合约被维护为永久存储合约。因此,所有的逻辑版本都利用这个永久存储合约来满足其存储需求。因此,存储复制的要求被从升级模式中移除,这种方法大大降低了升级成本。
初始化流程如下:
(1)部署一个 EternalStorageProxy 实例
(2)部署一个初始版本的合约(v1)
(3)调用 EternalStorageProxy 实例来升级到初始版本的地址
(4)如果逻辑合约依赖其构造函数来设置一些初始状态,那就必须在其链接到代理后重新进行,因为代理的存储不知道这些值。EternalStorageProxy 有一个函数 upgradeToAndCall,专门用来调用逻辑合约上的一些函数,在代理升级到它之后重新进行设置
升级流程如下:
(1)部署一个新版本的合约(v2),确保它拥有永久存储结构
(2)调用 EternalStorageProxy 实例来升级到新版本
1.4 Diamond 模式
Solidity 合约的最大限制为 24KB。因此,每个合约的大小永远不能超过 24KB。Diamond 模式使开发人员能够编写没有大小限制的智能合约。此模式还支持升级,而无需重新部署现有功能,详细 Diamond 标准可见 EIP 2535。
Diamond 是一个合约,它将调用委托给称为面的实现合约。Diamond 合约实现了一个 diamondCut 函数,它提供了添加 / 替换 / 删除的能力。除了 diamondCut 函数之外,还实现了一组 loupe 函数。这些函数显示了关于所实现的 Diamond 的信息。Diamond 合约维护了一个 selectorToFacet 的映射,这基本上是一个函数签名到实现合约地址的映射。当用户调用一个函数时,Diamond 代理的回退函数使用函数签名来寻找这个映射中的实现地址。在找到函数签名的地址后,回退函数只是将调用委托给实现合约,并将响应返回给用户。
Diamond 代理依靠的是一种存储模式。该代理被一些被称为面的实现合约所共享。理想情况下,每个切面都有自己的存储。然而,根据实施要求,面可以与其他面共享存储。DiamondStorage 和 AppStorage 是一些流行的存储模式,分别用于隔离和共享存储。
Diamond 模式为创建模块化的智能合约应用程序提供了一种简洁的方式,没有任何最大尺寸限制。因此,它似乎是那些希望在未来扩大规模的大型智能合约应用程序的完美选择。
1.5 create2
create 操作码用于在以太坊区块链上部署合约。合约地址是通过散列部署者的地址和该地址的 nonce 来生成的。Nonce 是一个标量值,等于从部署者的地址发送的交易数量。同样,在合约账户的情况下,nonce 是该账户创建合约的数量。nonce 有助于保持交易的顺序(来自一个地址的低 nonce 交易会先被开采),并防止重入攻击。nonce 是一个递增的数字,防止 create 操作码产生重复的地址。只要在当前和下一个 nonce 之间没有新的交易发生,就有可能通过简单散列下一个 nonce 和地址来知道下一个合约的地址。
create2 操作码被添加到以太坊虚拟机中,作为君士坦丁堡硬分叉的一部分,这个操作码也被用来部署智能合约。create2 使用一些用户控制的输入来推导出智能合约的地址。换句话说,这个操作码提供了一种方法,在将智能合约部署到区块链之前计算其地址。这个操作码不是对部署者的地址和 nonce 进行散列,而是对部署者的地址、salt(由部署者提供的 32 字节的字符串)和合约的字节码的哈希进行散列。由于这个操作码的所有参数都由用户控制,create2 提供了一种预先确定合约地址的方法。这个方法在推导出优化 gas 的合约地址和实现像状态通道这样的扩展解决方案时非常有用。在可升级性方面,create2 提供了创建可变合约(metamorphic contract)的能力,这些合约可以用新的字节码重新部署到同一地址。
EVM 包含一个 selfdestruct 操作码,智能合约可以通过这个操作码来删除。为了在原始地址上部署一个新的字节码,该地址必须是自由的,因为智能合约是不可改变的。有几种方法可以将新的字节码部署到原始地址上,一个低效的方法是找到 salt 的参数,与新的字节码相结合,生成原始地址,寻找正确的 salt 参数的计算可以在链下完成。然而,这并不是一个很好的方法。另一个选择是部署一个可变合约。如上所述,可变合约可以使用不同的字节码重新部署到原始地址。
建立一个良好的升级模式,需要一个可变合约工厂。这个可变合约工厂的目的是通过改变其实现而不改变其地址来促进升级。在部署合约时,对应的部署函数使用 create2 预先计算可变合约的地址,实现合约是使用传统的 create 操作码部署的。这个操作码使用地址和 nonce 来生成地址,并将合约部署到该地址。要求实现地址不能为零。否则,实现合约没有被正确部署,函数必须返回。部署完实现合约后,工厂状态被更新来存储当前的实现合约。
值得注意的是,实现合约必须是自毁的,这也会使可变合约自毁。在重新部署可变合约之前,要确保可变合约使用 selfdestruct 操作码进行自我销毁。由于 create2 操作码的存在,可变合约的地址总是提前知道的。此外,由于它能够改变其实现,可变合约每次都可以用不同的实现重新部署。
使用 create2 是有优势的,但它也有自己的风险。最重要的风险是,每次合约被重新部署时,其存储都会被抹去。另外,带有 selfdestruct 操作码的实现合约可能不是一个可靠的资金存储方式。因此,在采用这种智能合约升级模式之前,开发者必须谨慎行事。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69995368/viewspace-2915506/ ,如需转载,请注明出处,否则将追究法律责任。