本文作者:区块链行业专业打杂 - 为理想而活 [1]
为什么要升级合约?
根据设计,智能合约是不可变的。另一方面,软件质量在很大程度上取决于升级和修补源代码以生成迭代版本的能力。尽管基于区块链的软件从技术的不变性中获益匪浅,但修复错误和潜在的产品改进仍然需要一定程度的可变性。OpenZeppelin Upgrades 通过为智能合约提供易于使用、简单、健壮和可选的升级机制来解决这一明显的矛盾,该机制可以由任何类型的治理控制,无论是多重签名钱包、简单地址还是复杂的 DAO。
首次部署
需要部署三个合约,分别是逻辑合约,代理管理合约,代理合约。逻辑合约就是我们自己的业务合约,需要满足 OpenZeppelin 可升级合约的条件。以下业务合约以逻辑合约为例进行说明。本文使用 remix 部署合约,如需快速部署请参考:【翻译】用 Hardhat 进行升级部署(Using with Hardhat) | 登链社区 [2]
第一步,逻辑合约
首先部署逻辑合约。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract Logic is Initializable, OwnableUpgradeable { function initialize() public initializer { __Ownable_init(); } mapping(string => uint256) private logic; event logicSetted(string indexed_key, uint256_value); function SetLogic(string memory_key, uint256_value) external { logic[_key] =_value; emit logicSetted(_key,_value); } function GetLogic(string memory_key) public view returns (uint256){ return logic[_key]; } function GetInitializeData() public pure returns(bytes memory){ return abi.encodeWithSignature("initialize()"); } }
选中逻辑合约并部署。
image.png
逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3
第二步,管理合约
部署管理合约,用于升级逻辑合约。
image.png
管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005
第三步,代理合约
代理合约,用于存储逻辑合约数据。
部署需要参数如下:
-
逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3
-
管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005
-
逻辑合约初始化二进制码:0x8129fc1c (逻辑合约里以提供该二进制码的方法,默认为无参)
image.png
代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d
如何实现升级
至此,可升级合约已部署完成,开始测试
测试逻辑合约方法
注意:此处应调用代理合约而不是直接调用业务合约
首先选中业务合约,使用 At Address 填入代理合约地址(0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d)后生成已代理的可升级合约。
image.png
此处应使用生成出的合约,而不是部署的合约 !
-
调用 SetLogic 方法,传入("test",1)用于查询。
-
调用 GetLogic 方法,传入("test")返回的为 1。
修改逻辑合约并部署
先将旧的逻辑合约 GetLogic 方法替换,替换后为要部署的新逻辑合约。
function GetLogic(string memory_key) public view returns (uint256){ return logic[_key] + 99; }
image.png
部署新的业务合约,结束后应有三个逻辑合约实例
image.png
新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c
替换旧的逻辑合约
此时调用部署好的管理合约进行升级,此合约提供了两个升级方法
-
upgrade,需要传入 proxy 地址,新的逻辑实现地址。
-
upgradeAndCall,需要传入 roxy 地址,新的逻辑实现地址,初始化调用数据。
由于数据是保存在代理合约中,这份数据已经初始化过了,不需要再初始化,所以调用 upgrade 方法即可,传入参数如下:
-
代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d
-
新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c
测试新逻辑合约
此时升级已完成,也是最后一步。
此时不需要修改任何地方,只需要使用已经传入代理合约地址的 At Address 方法。
image.png
生成后将有四个逻辑合约实例:1. 首次部署的逻辑合约,2. 代理后的逻辑合约,3. 部署的新逻辑合约,4. 以升级的逻辑合约
调用生成的新逻辑合约中的 GetLogic 方法,传入("test"),此时应返回 100,这证明您的合约以成功升级,因为旧的逻辑合约传入了(1),修改后的逻辑合约中 GetLogic 方法在返回值上加上了(99),若为 99 证明升级失败。
至此,可升级的代理合约就部署并测试完成。
参考资料
[1]
区块链行业专业打杂 - 为理想而活 : https://learnblockchain.cn/people/1877
[2]
【翻译】用 Hardhat 进行升级部署(Using with Hardhat) | 登链社区 : https://learnblockchain.cn/article/2908