在本文中,通过 7 个任务,如何来编写可升级合约,测试以及自动、活动实施升级。 在 7 个任务中,分别介绍了可升级合约可能遇到的各种情况: 在新实现合约中添加函数、添加状态变量、修改状态变量可见性(修改函数)。
有几篇关于可升级智能合约或代理模式的好文章。但找不到一个分步骤的指南来构建、部署代理合约并与之交互。
在本教程中,列出了7个细节任务,你可以按照这些任务将代理合约部署到本地测试网和公共测试网Ropsten。每个任务都有几个子任务。我们将在这里使用[OpenZeppelin代理合约](https://docs.openzeppelin.com/contracts/4.x/api/proxy)和[OpenZeppelin Upgrades plugin for Hardhat](https://docs.openzeppelin.com/upgrades)(或Truffle)。
特别感谢OpenZeppelin生态系统中的两个相关指南: [OpenZeppelin Upgrades教程: 使用Hardhat和 Gnosis Safe 进行合约升级](https://learnblockchain.cn/article/3103) 和 [通过多签和 Defender 升级合约](https://docs.openzeppelin.com/defender/guide-upgrades).
## 可升级的智能合约如何工作?

这个插图解释了可升级智能合约的工作原理。具体来说,这是[透明代理模式](https://learnblockchain.cn/article/4257),另一个是[UUPS代理模式](https://learnblockchain.cn/article/4257)(通用可升级代理标准)。
可升级智能合约实际上有3个合约:
– **代理合约:**用户与之交互的智能合约。它保持数据/状态,这意味着数据被存储在这个代理合约账户的背景下,它是一个[EIP1967标准](https://eips.ethereum.org/EIPS/eip-1967)代理合约。
– **实现合约:**智能合约提供功能和逻辑。请注意,数据也是在这个合约中定义的。这是我们编写的智能合约。
– **ProxyAdmin合约:**该合约连接了Proxy和Implementation。
ProxyAmdin在[OpenZeppelin 文档](https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#what-is-a-proxy-admin)中解释:
> 什么是ProxyAmdin?
>
> ProxyAdmin是一个合约,它作为所有代理合约的所有者(Owner)。每个网络通过部署一个ProxyAmdin就可以。当你启动项目时,ProxyAdmin由部署者地址拥有,但你可以通过调用transferOwnership来转移它的所有权。
当把ProxyAmin的所有权转移到一个多重签名的账户时,升级Proxy合约(将代理链接到新的实现)的权力也会转移到多重签名身上。
## 如何部署代理?如何升级代理?
当我们第一次使用OpenZeppelin升级插件为Hardhat部署可升级的合约时,我们部署了三个合约:
1. 部署 `实现合约`
2. 部署 `ProxyAdmin合约`
3. 部署 `代理合约`
在ProxyAdmin合约中,实现和代理被关联起来。
当用户调用代理合约时,调用被委托给实现合约执行([委托调用](https://docs.soliditylang.org/en/v0.8.12/introduction-to-smart-contracts.html?highlight=delegatecall#delegatecall-callcode-and-libraries))。
当升级合约时,我们所做的是:
1. 部署一个新的 `实现合约`
2. 在 `ProxyAdmin合约 `中升级,将所有对代理的调用重定向到新的实现合约。
Hardhat/Truffle的OpenZeppelin Upgrades插件可以帮助我们完成升级工作。
如果你想知道如何修改合约使其可升级,你可以参考OpenZeppelin的文档, 见[链接](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable)。
让我们开始编写和部署一个可升级的智能合约,你可以在文末找到本文网站的代码。
## 任务1: 编写可升级的智能合约
### 任务1.1:启动Hardhat项目
我们将使用Hardhat、Hardhat Network本地测试网和OpenZeppelin Upgrades插件。
第1步:安装hardhat并启动一个项目
“`bash
mkdir solproxy && cd solproxy
yarn init -y
yarn add harthat
yarn hardhat
// choose option: sample typescript
“`
第2步:添加插件`@openzeppelin/hardhat-upgrades`
“`
yarn add @openzeppelin/hardhat-upgrades
“`
编辑`hardhat.config.ts`以使用Upgrades插件:
“`
// hardhat.config.ts
import ‘@openzeppelin/hardhat-upgrades’;
“`
我们将使用hardhat升级插件的三个功能([API参考链接](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades))。
“`
deployProxy()
upgradeProxy()
prepareUpgrade()
“`
### 1.2: 编写一个可升级的智能合约
我们使用[OpenZeppelin学习指南](https://docs.openzeppelin.com/learn/developing-smart-contracts#setting-up-a-solidity-project)中的`Box.sol`合约。我们将建立这个合约的几个版本。
– Box.sol
– BoxV2.sol
– BoxV3.sol
– BoxV4.sol
普通合约和可升级合约的最大区别是,可升级合约没有`constructor()`,[文档链接](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable)。
“`solidity
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Box {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}
“`
解释一下:
– 用户可以调用 `store()`把一个值存到这个合约中,并在以后调用`retrieve()`获取它。
### 任务 1.3: `Box.sol`的单元测试脚本
让我们为`Box.sol`编写单元测试脚本。下面的Hardhat单元测试脚本改编自(
[OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580))。我们在其中做了一些修改。
编辑`test/1.Box.test.ts`。
“`typescript
// test/1.Box.test.ts
import { expect } from “chai”;
import { ethers } from “hardhat”
import { Contract, BigNumber } from “ethers”
describe(“Box”, function () {
let box:Contract;
beforeEach(async function () {
const Box = await ethers.getContractFactory(“Box”)
box = await Box.deploy()
await box.deployed()
})
it(“should retrieve value previously stored”, async function () {
await box.store(42)
expect(await box.retrieve()).to.equal(BigNumber.from(’42’))
await box.store(100)
expect(await box.retrieve()).to.equal(BigNumber.from(‘100’))
})
})
// NOTE: should also add test for event: event ValueChanged(uint256 newValue)
“`
运行测试:
“`
yarn hardhat test test/1.Box.test.ts
“`
结果:
“`
Box
should retrieve value previously stored
1 passing (505ms)
Done in 3.34s.
“`
## 任务 2: 部署可升级的智能合约
### 任务2.1:用 `OpenZeppelin Upgrades plugin for Hardhat `编写部署脚本
当我们写脚本来部署智能合约时,我们使用:
“`
const Greeter = await ethers.getContractFactory(“Greeter”);
const greeter = await Greeter.deploy(“Hello, Hardhat!”);
“`
为了部署一个可升级的合约,将调用`deployProxy()`,文档可以在[链接](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#deploy-proxy)找到。
“`
const Box = await ethers.getContractFactory(“Box”)
const box = await upgrades.deployProxy(Box,[42], { initializer: ‘store’ })
“`
在第二行,我们使用OpenZeppelin升级插件,通过调用`store()`作为初始化器,以初始值`42`部署`Box`。
编辑`scripts/1.deploy_box.ts`:
“`typescript
// scripts/1.deploy_box.ts
import { ethers } from “hardhat”
import { upgrades } from “hardhat”
async function main() {
const Box = await ethers.getContractFactory(“Box”)
console.log(“Deploying Box…”)
const box = await upgrades.deployProxy(Box,[42], { initializer: ‘store’ })
console.log(box.address,” box(proxy) address”)
console.log(await upgrades.erc1967.getImplementationAddress(box.address),” getImplementationAddress”)
console.log(await upgrades.erc1967.getAdminAddress(box.address),” getAdminAddress”)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
“`
解释一下:
– 我们使用`upgrades.deployProxy()`部署一个可升级的合约`Box.sol`。
– 三个合约将被部署: 实现合约、ProxyAdmin、Proxy。我们记录他们的地址。
### 任务2.2:将合约部署到本地测试网
让我们在本地testnet中运行部署脚本。
第1步:在另一个终端运行一个独立的hardhat 测试网络。
“`
yarn hardhat node
“`
第2步:运行部署脚本
“`
yarn hardhat run scripts/1.deploy_box.ts –network localhost
“`
结果:
“`
Deploying Box…
0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 box(proxy) address
0x5FbDB2315678afecb367f032d93F642f64180aa3 getImplementationAddress
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 getAdminAddress
Done in 3.83s.
“`
用户可以通过box(proxy)地址与Box合约交互:`0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0`.
注意:如果你多次运行这个部署,你可以发现ProxyAdmin保持不变:`0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512`。
### 任务2.3:测试`Box.sol`通过代理模式是否正常工作
你可能会发现,用户通过box(proxy)合约与实现合约进行交互。
为了确保它们正确工作,让我们添加单元测试进行测试。在单元测试中,我们使用`upgrades.deployProxy()`部署合约,并通过box(proxy)合约再次进行交互验证结果:
编辑`test/2.BoxProxy.test.ts`。
“`typescript
// test/2.BoxProxy.test.ts
import { expect } from “chai”
import { ethers, upgrades } from “hardhat”
import { Contract, BigNumber } from “ethers”
describe(“Box (proxy)”, function () {
let box:Contract
beforeEach(async function () {
const Box = await ethers.getContractFactory(“Box”)
//initilize with 42
box = await upgrades.deployProxy(Box, [42], {initializer: ‘store’})
})
it(“should retrieve value previously stored”, async function () {
// console.log(box.address,” box(proxy)”)
// console.log(await upgrades.erc1967.getImplementationAddress(box.address),” getImplementationAddress”)
// console.log(await upgrades.erc1967.getAdminAddress(box.address), ” getAdminAddress”)
expect(await box.retrieve()).to.equal(BigNumber.from(’42’))
await box.store(100)
expect(await box.retrieve()).to.equal(BigNumber.from(‘100′))
})
})
“`
运行测试:
“`
yarn hardhat test test/2.BoxProxy.test.ts
“`
结果:
“`
Box (proxy)
should retrieve value previously stored
1 passing (579ms)
Done in 3.12s.`
“`
我们的box.sol现在工作正常了。
后来,我们发现需要一个`increment()`函数。与其重新部署这个合约,不如将数据迁移到新的合约,并要求所有用户访问新的合约地址。我们可以很容易地升级合约。
## 任务3:将智能合约升级到BoxV2
### 任务3.1:编写新的实现
我们通过继承`Box.sol`来写一个新版本的Box`BoxV2.sol`。
编辑`contracts/BoxV2.sol`:
“`solidity
// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import “./Box.sol”;
contract BoxV2 is Box{
// Increments the stored value by 1
function increment() public {
store(retrieve()+1);
}
}
“`
### 任务3.2:正常部署的测试脚本
我们编写单元测试脚本来测试本地部署的BoxV2:
编辑`test/3.BoxV2.test.ts`:
“`typescript
// test/3.BoxV2.test.ts
import { expect } from “chai”
import { ethers } from “hardhat”
import { Contract, BigNumber } from “ethers”
describe(“Box V2”, function () {
let boxV2:Contract
beforeEach(async function () {
const BoxV2 = await ethers.getContractFactory(“BoxV2”)
boxV2 = await BoxV2.deploy()
await boxV2.deployed()
});
it(“should retrievevalue previously stored”, async function () {
await boxV2.store(42)
expect(await boxV2.retrieve()).to.equal(BigNumber.from(’42’))
await boxV2.store(100)
expect(await boxV2.retrieve()).to.equal(BigNumber.from(‘100’))
});
it(‘should increment value correctly’, async function () {
await boxV2.store(42)
await boxV2.increment()
expect(await boxV2.retrieve()).to.equal(BigNumber.from(’43’))
})
})
“`
运行测试:
“`bash
yarn hardhat test test/3.BoxV2.test.ts
“`
结果:
“`
Box V2
should retrievevalue previously stored
should increment value correctly
2 passing (579ms)
Done in 3.38s.
“`
### 任务3.3:为升级部署准备测试脚本
我们在部署代理服务模式下,为BoxV2编写单元测试脚本:
– 首先,我们部署Box.sol
– 然后我们将其升级到BoxV2.sol
– 测试BoxV2是否正常工作
编辑`test/4.BoxProxyV2.test.ts`:
“`typescript
// test/4.BoxProxyV2.test.ts
import { expect } from “chai”
import { ethers, upgrades } from “hardhat”
import { Contract, BigNumber } from “ethers”
describe(“Box (proxy) V2”, function () {
let box:Contract
let boxV2:Contract
beforeEach(async function () {
const Box = await ethers.getContractFactory(“Box”)
const BoxV2 = await ethers.getContractFactory(“BoxV2″)
//initilize with 42
box = await upgrades.deployProxy(Box, [42], {initializer: ‘store’})
// console.log(box.address,” box/proxy”)
// console.log(await upgrades.erc1967.getImplementationAddress(box.address),” getImplementationAddress”)
// console.log(await upgrades.erc1967.getAdminAddress(box.address), ” getAdminAddress”)
boxV2 = await upgrades.upgradeProxy(box.address, BoxV2)
// console.log(boxV2.address,” box/proxy after upgrade”)
// console.log(await upgrades.erc1967.getImplementationAddress(boxV2.address),” getImplementationAddress after upgrade”)
// console.log(await upgrades.erc1967.getAdminAddress(boxV2.address),” getAdminAddress after upgrade”)
})
it(“should retrieve value previously stored and increment correctly”, async function () {
expect(await boxV2.retrieve()).to.equal(BigNumber.from(’42’))
await boxV2.increment()
//result = 42 + 1 = 43
expect(await boxV2.retrieve()).to.equal(BigNumber.from(’43’))
await boxV2.store(100)
expect(await boxV2.retrieve()).to.equal(BigNumber.from(‘100’))
})
})
“`
运行测试:
“`
yarn hardhat test test/4.BoxProxyV2.test.ts
“`
结果:
“`
Box (proxy) V2
should retrieve value previously stored and increment correctly
1 passing (617ms)
Done in 3.44s.
“`
### 任务3.4:编写升级脚本
在子任务2.2中,我们将Box(proxy)部署到`0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0`。
在这个子任务中,我们将把它升级到BoxV2(部署一个新的合约,并在ProxyAdmin中把代理链接到新的实现合约)。
编辑 scripts/2.upgradeV2.ts
“`typescript
// sc…
- 原文:https://dev.to/yakult/tutorial-write-upgradeable-smart-contract-proxy-contract-with-openzeppelin-1916
- 译文出自:区块链开发网翻译计划
- 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
有几篇关于可升级智能合约或代理模式的好文章。但找不到一个分步骤的指南来构建、部署代理合约并与之交互。
在本教程中,列出了7个细节任务,你可以按照这些任务将代理合约部署到本地测试网和公共测试网Ropsten。每个任务都有几个子任务。我们将在这里使用OpenZeppelin代理合约和OpenZeppelin Upgrades plugin for Hardhat(或Truffle)。
特别感谢OpenZeppelin生态系统中的两个相关指南: OpenZeppelin Upgrades教程: 使用Hardhat和 Gnosis Safe 进行合约升级 和 通过多签和 Defender 升级合约.
可升级的智能合约如何工作?
这个插图解释了可升级智能合约的工作原理。具体来说,这是透明代理模式,另一个是UUPS代理模式(通用可升级代理标准)。
可升级智能合约实际上有3个合约:
- 代理合约:用户与之交互的智能合约。它保持数据/状态,这意味着数据被存储在这个代理合约账户的背景下,它是一个EIP1967标准代理合约。
- 实现合约:智能合约提供功能和逻辑。请注意,数据也是在这个合约中定义的。这是我们编写的智能合约。
- ProxyAdmin合约:该合约连接了Proxy和Implementation。
ProxyAmdin在OpenZeppelin 文档中解释:
什么是ProxyAmdin?
ProxyAdmin是一个合约,它作为所有代理合约的所有者(Owner)。每个网络通过部署一个ProxyAmdin就可以。当你启动项目时,ProxyAdmin由部署者地址拥有,但你可以通过调用transferOwnership来转移它的所有权。
当把ProxyAmin的所有权转移到一个多重签名的账户时,升级Proxy合约(将代理链接到新的实现)的权力也会转移到多重签名身上。
如何部署代理?如何升级代理?
当我们第一次使用OpenZeppelin升级插件为Hardhat部署可升级的合约时,我们部署了三个合约:
- 部署
实现合约
- 部署
ProxyAdmin合约
- 部署
代理合约
在ProxyAdmin合约中,实现和代理被关联起来。
当用户调用代理合约时,调用被委托给实现合约执行(委托调用)。
当升级合约时,我们所做的是:
- 部署一个新的
实现合约
- 在
ProxyAdmin合约
中升级,将所有对代理的调用重定向到新的实现合约。
Hardhat/Truffle的OpenZeppelin Upgrades插件可以帮助我们完成升级工作。
如果你想知道如何修改合约使其可升级,你可以参考OpenZeppelin的文档, 见链接。
让我们开始编写和部署一个可升级的智能合约,你可以在文末找到本文网站的代码。
任务1: 编写可升级的智能合约
任务1.1:启动Hardhat项目
我们将使用Hardhat、Hardhat Network本地测试网和OpenZeppelin Upgrades插件。
第1步:安装hardhat并启动一个项目
mkdir solproxy && cd solproxy
yarn init -y
yarn add harthat
yarn hardhat
// choose option: sample typescript
第2步:添加插件@openzeppelin/hardhat-upgrades
yarn add @openzeppelin/hardhat-upgrades
编辑hardhat.config.ts
以使用Upgrades插件:
// hardhat.config.ts
import '@openzeppelin/hardhat-upgrades';
我们将使用hardhat升级插件的三个功能(API参考链接)。
deployProxy()
upgradeProxy()
prepareUpgrade()
1.2: 编写一个可升级的智能合约
我们使用OpenZeppelin学习指南中的Box.sol
合约。我们将建立这个合约的几个版本。
- Box.sol
- BoxV2.sol
- BoxV3.sol
- BoxV4.sol
普通合约和可升级合约的最大区别是,可升级合约没有constructor()
,文档链接。
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Box {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}
解释一下:
- 用户可以调用
store()
把一个值存到这个合约中,并在以后调用retrieve()
获取它。
任务 1.3: Box.sol
的单元测试脚本
让我们为Box.sol
编写单元测试脚本。下面的Hardhat单元测试脚本改编自( OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat)。我们在其中做了一些修改。
编辑test/1.Box.test.ts
。
// test/1.Box.test.ts
import { expect } from "chai";
import { ethers } from "hardhat"
import { Contract, BigNumber } from "ethers"
describe("Box", function () {
let box:Contract;
beforeEach(async function () {
const Box = await ethers.getContractFactory("Box")
box = await Box.deploy()
await box.deployed()
})
it("should retrieve value previously stored", async function () {
await box.store(42)
expect(await box.retrieve()).to.equal(BigNumber.from('42'))
await box.store(100)
expect(await box.retrieve()).to.equal(BigNumber.from('100'))
})
})
// NOTE: should also add test for event: event ValueChanged(uint256 newValue)
运行测试:
yarn hardhat test test/1.Box.test.ts
结果:
Box
should retrieve value previously stored
1 passing (505ms)
Done in 3.34s.
任务 2: 部署可升级的智能合约
任务2.1:用 OpenZeppelin Upgrades plugin for Hardhat
编写部署脚本
当我们写脚本来部署智能合约时,我们使用:
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, Hardhat!");
为了部署一个可升级的合约,将调用deployProxy()
,文档可以在链接找到。
const Box = await ethers.getContractFactory("Box")
const box = await upgrades.deployProxy(Box,[42], { initializer: 'store' })
在第二行,我们使用OpenZeppelin升级插件,通过调用store()
作为初始化器,以初始值42
部署Box
。
编辑scripts/1.deploy_box.ts
:
// scripts/1.deploy_box.ts
import { ethers } from "hardhat"
import { upgrades } from "hardhat"
async function main() {
const Box = await ethers.getContractFactory("Box")
console.log("Deploying Box...")
const box = await upgrades.deployProxy(Box,[42], { initializer: 'store' })
console.log(box.address," box(proxy) address")
console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
console.log(await upgrades.erc1967.getAdminAddress(box.address)," getAdminAddress")
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
解释一下:
- 我们使用
upgrades.deployProxy()
部署一个可升级的合约Box.sol
。 - 三个合约将被部署: 实现合约、ProxyAdmin、Proxy。我们记录他们的地址。
任务2.2:将合约部署到本地测试网
让我们在本地testnet中运行部署脚本。
第1步:在另一个终端运行一个独立的hardhat 测试网络。
yarn hardhat node
第2步:运行部署脚本
yarn hardhat run scripts/1.deploy_box.ts --network localhost
结果:
Deploying Box...
0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 box(proxy) address
0x5FbDB2315678afecb367f032d93F642f64180aa3 getImplementationAddress
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 getAdminAddress
Done in 3.83s.
用户可以通过box(proxy)地址与Box合约交互:0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
.
注意:如果你多次运行这个部署,你可以发现ProxyAdmin保持不变:0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
。
任务2.3:测试Box.sol
通过代理模式是否正常工作
你可能会发现,用户通过box(proxy)合约与实现合约进行交互。
为了确保它们正确工作,让我们添加单元测试进行测试。在单元测试中,我们使用upgrades.deployProxy()
部署合约,并通过box(proxy)合约再次进行交互验证结果:
编辑test/2.BoxProxy.test.ts
。
// test/2.BoxProxy.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"
describe("Box (proxy)", function () {
let box:Contract
beforeEach(async function () {
const Box = await ethers.getContractFactory("Box")
//initilize with 42
box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
})
it("should retrieve value previously stored", async function () {
// console.log(box.address," box(proxy)")
// console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
// console.log(await upgrades.erc1967.getAdminAddress(box.address), " getAdminAddress")
expect(await box.retrieve()).to.equal(BigNumber.from('42'))
await box.store(100)
expect(await box.retrieve()).to.equal(BigNumber.from('100'))
})
})
运行测试:
yarn hardhat test test/2.BoxProxy.test.ts
结果:
Box (proxy)
should retrieve value previously stored
1 passing (579ms)
Done in 3.12s.`
我们的box.sol现在工作正常了。
后来,我们发现需要一个increment()
函数。与其重新部署这个合约,不如将数据迁移到新的合约,并要求所有用户访问新的合约地址。我们可以很容易地升级合约。
任务3:将智能合约升级到BoxV2
任务3.1:编写新的实现
我们通过继承Box.sol
来写一个新版本的BoxBoxV2.sol
。
编辑contracts/BoxV2.sol
:
// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Box.sol";
contract BoxV2 is Box{
// Increments the stored value by 1
function increment() public {
store(retrieve()+1);
}
}
任务3.2:正常部署的测试脚本
我们编写单元测试脚本来测试本地部署的BoxV2:
编辑test/3.BoxV2.test.ts
:
// test/3.BoxV2.test.ts
import { expect } from "chai"
import { ethers } from "hardhat"
import { Contract, BigNumber } from "ethers"
describe("Box V2", function () {
let boxV2:Contract
beforeEach(async function () {
const BoxV2 = await ethers.getContractFactory("BoxV2")
boxV2 = await BoxV2.deploy()
await boxV2.deployed()
});
it("should retrievevalue previously stored", async function () {
await boxV2.store(42)
expect(await boxV2.retrieve()).to.equal(BigNumber.from('42'))
await boxV2.store(100)
expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
});
it('should increment value correctly', async function () {
await boxV2.store(42)
await boxV2.increment()
expect(await boxV2.retrieve()).to.equal(BigNumber.from('43'))
})
})
运行测试:
yarn hardhat test test/3.BoxV2.test.ts
结果:
Box V2
should retrievevalue previously stored
should increment value correctly
2 passing (579ms)
Done in 3.38s.
任务3.3:为升级部署准备测试脚本
我们在部署代理服务模式下,为BoxV2编写单元测试脚本:
- 首先,我们部署Box.sol
- 然后我们将其升级到BoxV2.sol
- 测试BoxV2是否正常工作
编辑test/4.BoxProxyV2.test.ts
:
// test/4.BoxProxyV2.test.ts
import { expect } from "chai"
import { ethers, upgrades } from "hardhat"
import { Contract, BigNumber } from "ethers"
describe("Box (proxy) V2", function () {
let box:Contract
let boxV2:Contract
beforeEach(async function () {
const Box = await ethers.getContractFactory("Box")
const BoxV2 = await ethers.getContractFactory("BoxV2")
//initilize with 42
box = await upgrades.deployProxy(Box, [42], {initializer: 'store'})
// console.log(box.address," box/proxy")
// console.log(await upgrades.erc1967.getImplementationAddress(box.address)," getImplementationAddress")
// console.log(await upgrades.erc1967.getAdminAddress(box.address), " getAdminAddress")
boxV2 = await upgrades.upgradeProxy(box.address, BoxV2)
// console.log(boxV2.address," box/proxy after upgrade")
// console.log(await upgrades.erc1967.getImplementationAddress(boxV2.address)," getImplementationAddress after upgrade")
// console.log(await upgrades.erc1967.getAdminAddress(boxV2.address)," getAdminAddress after upgrade")
})
it("should retrieve value previously stored and increment correctly", async function () {
expect(await boxV2.retrieve()).to.equal(BigNumber.from('42'))
await boxV2.increment()
//result = 42 + 1 = 43
expect(await boxV2.retrieve()).to.equal(BigNumber.from('43'))
await boxV2.store(100)
expect(await boxV2.retrieve()).to.equal(BigNumber.from('100'))
})
})
运行测试:
yarn hardhat test test/4.BoxProxyV2.test.ts
结果:
Box (proxy) V2
should retrieve value previously stored and increment correctly
1 passing (617ms)
Done in 3.44s.
任务3.4:编写升级脚本
在子任务2.2中,我们将Box(proxy)部署到0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0
。
在这个子任务中,我们将把它升级到BoxV2(部署一个新的合约,并在ProxyAdmin中把代理链接到新的实现合约)。
编辑 scripts/2.upgradeV2.ts
// sc...
剩余50%的内容订阅专栏后可查看
- 发表于 2022-06-24 22:12
- 阅读 ( 1714 )
- 学分 ( 2 )
- 分类:Solidity
- 专栏:全面掌握Solidity智能合约开发