手把手部署以太坊可升级智能合约

如何部署以太 坊可升级智能合约

## 为什么要升级合约? 根据设计,智能合约是不可变的。另一方面,软件质量在很大程度上取决于升级和修补源代码以生成迭代版本的能力。尽管基于区块链的软件从技术的不变性中获益匪浅,但修复错误和潜在的产品改进仍然需要一定程度的可变性。OpenZeppelin Upgrades 通过为智能合约提供易于使用、简单、健壮和可选的升级机制来解决这一明显的矛盾,该机制可以由任何类型的治理控制,无论是多重签名钱包、简单地址还是复杂的 DAO。 --- ## 首次部署 需要部署三个合约,分别是逻辑合约,代理管理合约,代理合约。逻辑合约就是我们自己的业务合约,需要满足OpenZeppelin可升级合约的条件。以下业务合约以逻辑合约为例进行说明。 本文使用remix部署合约,如需快速部署请参考:[【翻译】用 Hardhat 进行升级部署(Using with Hardhat) | 登链社区](https://learnblockchain.cn/article/2908) ### 第一步,逻辑合约 首先部署逻辑合约。 ``` // 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](https://img.learnblockchain.cn/attachments/2021/08/O6QwtYE8612c9b6f625b5.png) ![image.png](https://img.learnblockchain.cn/attachments/2021/08/m56wfHTt612c9bb7e4175.png) 逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3 --- ### 第二步,管理合约 部署管理合约,用于升级逻辑合约。 ![image.png](https://img.learnblockchain.cn/attachments/2021/08/SXg0Itrs612c9c3ecaf66.png) ![image.png](https://img.learnblockchain.cn/attachments/2021/08/11qeRjTt612c9c64ad60a.png) 管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005 --- ### 第三步,代理合约 代理合约,用于存储逻辑合约数据。![image.png](https://img.learnblockchain.cn/attachments/2021/08/BgGKcnHo612c9cdce2c19.png) 部署需要参数如下: 1. 逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3 2. 管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005 3. 逻辑合约初始化二进制码:0x8129fc1c(逻辑合约里以提供该二进制码的方法,默认为无参) ![image.png](https://img.learnblockchain.cn/attachments/2021/08/0YN4j1lU612c9e42881e3.png) 代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d --- ## 如何实现升级 至此,可升级合约已部署完成,开始测试 ### 测试逻辑合约方法 > 注意:此处应调用代理合约而不是直接调用业务合约 首先选中业务合约,使用At Address填入代理合约地址(0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d)后生成已代理的可升级合约。 ![image.png](https://img.learnblockchain.cn/attachments/2021/08/O6QwtYE8612c9b6f625b5.png) ![image.png](https://img.learnblockchain.cn/attachments/2021/08/ydRiy1hI612ca042650fb.png) 此处应使用生成出的合约,而不是部署的合约! ![image.png](https://img.learnblockchain.cn/attachments/2021/08/ktWUMmSs612ca130a60ed.png) 1. 调用SetLogic方法,传入("test",1)用于查询。 2. 调用GetLogic方法,传入("test")返回的为1。 --- ### 修改逻辑合约并部署 先将旧的逻辑合约GetLogic方法替换,替换后为要部署的新逻辑合约。 ``` function GetLogic(string memory _key) public view returns (uint256){ return logic[_key] + 99; } ``` ![image.png](https://img.learnblockchain.cn/attachments/2021/08/ysW5rm5W612ca32621934.png) 部署新的业务合约,结束后应有三个逻辑合约实例 ![image.png](https://img.learnblockchain.cn/attachments/2021/08/TUSqwuVo612ca46ae6931.png) 新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c --- ### 替换旧的逻辑合约 此时调用部署好的管理合约进行升级,此合约提供了两个升级方法 1. upgrade,需要传入proxy地址,新的逻辑实现地址。 2. upgradeAndCall,需要传入roxy地址,新的逻辑实现地址,初始化调用数据。 由于数据是保存在代理合约中,这份数据已经初始化过了,不需要再初始化,所以调用upgrade方法即可,传入参数如下: 1. 代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d 2. 新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c ![image.png](https://img.learnblockchain.cn/attachments/2021/08/UxPO3my8612ca4ffc1b16.png) --- ### 测试新逻辑合约 此时升级已完成,也是最后一步。 此时不需要修改任何地方,只需要使用已经传入代理合约地址的At Address方法。 ![image.png](https://img.learnblockchain.cn/attachments/2021/08/Q7cS8D2Q612ca60d4b375.png) > 生成后将有四个逻辑合约实例: > 1.首次部署的逻辑合约,2.代理后的逻辑合约,3.部署的新逻辑合约,4.以升级的逻辑合约 > ![image.png](https://img.learnblockchain.cn/attachments/2021/08/9U2xxveO612ca6d7be5a8.png) 调用生成的新逻辑合约中的GetLogic方法,传入("test"),此时应返回100,这证明您的合约以成功升级,因为旧的逻辑合约传入了(1),修改后的逻辑合约中GetLogic方法在返回值上加上了(99),若为99证明升级失败。 ![image.png](https://img.learnblockchain.cn/attachments/2021/08/nVdMrxUF612ca82233498.png) --- 至此,可升级的代理合约就部署并测试完成。

为什么要升级合约?

根据设计,智能合约是不可变的。另一方面,软件质量在很大程度上取决于升级和修补源代码以生成迭代版本的能力。尽管基于区块链的软件从技术的不变性中获益匪浅,但修复错误和潜在的产品改进仍然需要一定程度的可变性。OpenZeppelin Upgrades 通过为智能合约提供易于使用、简单、健壮和可选的升级机制来解决这一明显的矛盾,该机制可以由任何类型的治理控制,无论是多重签名钱包、简单地址还是复杂的 DAO。

首次部署

需要部署三个合约,分别是逻辑合约,代理管理合约,代理合约。逻辑合约就是我们自己的业务合约,需要满足OpenZeppelin可升级合约的条件。以下业务合约以逻辑合约为例进行说明。 本文使用remix部署合约,如需快速部署请参考:【翻译】用 Hardhat 进行升级部署(Using with Hardhat) | 登链社区

第一步,逻辑合约

首先部署逻辑合约。

// 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()");
    }
}

选中逻辑合约并部署。

逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3

第二步,管理合约

部署管理合约,用于升级逻辑合约。

管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005

第三步,代理合约

代理合约,用于存储逻辑合约数据。

部署需要参数如下:

  1. 逻辑合约地址:0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3
  2. 管理合约地址:0xd2a5bC10698FD955D1Fe6cb468a17809A08fd005
  3. 逻辑合约初始化二进制码:0x8129fc1c(逻辑合约里以提供该二进制码的方法,默认为无参)

代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d

如何实现升级

至此,可升级合约已部署完成,开始测试

测试逻辑合约方法

注意:此处应调用代理合约而不是直接调用业务合约

首先选中业务合约,使用At Address填入代理合约地址(0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d)后生成已代理的可升级合约。

此处应使用生成出的合约,而不是部署的合约!

  1. 调用SetLogic方法,传入("test",1)用于查询。
  2. 调用GetLogic方法,传入("test")返回的为1。

修改逻辑合约并部署

先将旧的逻辑合约GetLogic方法替换,替换后为要部署的新逻辑合约。

function GetLogic(string memory _key) public view returns (uint256){
        return logic[_key] + 99;
    }

部署新的业务合约,结束后应有三个逻辑合约实例

新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c

替换旧的逻辑合约

此时调用部署好的管理合约进行升级,此合约提供了两个升级方法

  1. upgrade,需要传入proxy地址,新的逻辑实现地址。
  2. upgradeAndCall,需要传入roxy地址,新的逻辑实现地址,初始化调用数据。

由于数据是保存在代理合约中,这份数据已经初始化过了,不需要再初始化,所以调用upgrade方法即可,传入参数如下:

  1. 代理合约地址:0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d
  2. 新的逻辑合约地址:0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c

测试新逻辑合约

此时升级已完成,也是最后一步。

此时不需要修改任何地方,只需要使用已经传入代理合约地址的At Address方法。

生成后将有四个逻辑合约实例: 1.首次部署的逻辑合约,2.代理后的逻辑合约,3.部署的新逻辑合约,4.以升级的逻辑合约

调用生成的新逻辑合约中的GetLogic方法,传入("test"),此时应返回100,这证明您的合约以成功升级,因为旧的逻辑合约传入了(1),修改后的逻辑合约中GetLogic方法在返回值上加上了(99),若为99证明升级失败。

至此,可升级的代理合约就部署并测试完成。

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

  • 发表于 2021-08-30 17:45
  • 阅读 ( 1411 )
  • 学分 ( 61 )
  • 分类:Solidity

评论