接上篇使用openzeppelin开发第一个可升级智能合约-Part1
在上一篇,我们跟着文档走,结果合约升级确报了3个错,文档更新并不及时:
1
2
3
4
5
6
7
|
# 1.合约中有构造函数,应当修改为initializer函数
- Contract Box or an ancestor has a constructor. Change it to an initializer function. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#initializers.
# 2. 应该是附带问题
- New variable 'address _owner' was inserted in contract Ownable in @openzeppelin/contracts/ownership/Ownable.sol:1. You should only add new variables at the end of your contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
# 3.导入的合约来自@openzeppelin/contracts,使用@openzeppelin/contracts-ethereum-package替换
- Contract Box imports ownership/Ownable.sol, GSN/Context.sol from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See https://docs.openzeppelin.com/cli/2.6/dependencies#linking-the-contracts-ethereum-package.
|
WTF??? 一步步对着文档来的,你和我搞这个?
先解决第3个:
1
2
3
4
5
6
|
# 移除旧包,做测试的话这个包先不要移除
npm remove @openzeppelin/contracts
# 安装新包
# @openzeppelin/upgrades也要装,不然会提示你@openzeppelin/contracts-ethereum-package依赖这个包
npm install --save-dev @openzeppelin/upgrades
npm install --save-dev @openzeppelin/contracts-ethereum-package
|
第1个问题:
https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable
您可以在OpenZeppelin升级中使用您的Solidity合同,无需对其进行任何修改(构造函数除外)。由于基于代理的可升级性系统的要求,因此在可升级合同中不能使用构造函数。要了解此限制的原因,请访问代理。
这意味着,在将合同与OpenZeppelin升级配合使用时,您需要将其构造函数更改为常规函数(通常名为)initialize
,在其中运行所有设置逻辑:
由于这种模式在编写可升级合同时非常普遍,因此OpenZeppelin升级提供了一个Initializable
基本合同,该合同具有一个initializer
修饰符来处理此问题:
使用initializer
去替代构造函数,并引入OpenZeppelin提供的Initializable来限制它只能运行一次(像构造函数一样工作)
重新编写代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// contracts/Box.sol
pragma solidity ^0.5.0;
import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合约拥有者权限
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
contract Box is Initializable, Ownable {
uint256 private value;
event ValueChanged(uint256 newValue);
// 使用初始化函数代替构造函数
function initialize() public initializer {
// 要初始化Ownable合约
Ownable.initialize(msg.sender);
}
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {
return value;
}
}
|
部署:
Call a function to initialize the instance after creating it? 选择的是 Y,调用initialize函数,相当于执行了构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ oz deploy
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Box
✓ Contract Box deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? Yes
? Select which function * initialize()
✓ Setting everything up to create contract instances
✓ Instance created at 0x0290FB167208Af455bB137780163b7B7a9a10C16
To upgrade this instance run 'oz upgrade'
0x0290FB167208Af455bB137780163b7B7a9a10C16
|
再次使用node src/index.js
去调用,不过要修改一下地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');
async function main() {
// 连接RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({provider: web3}).web3;
// 改为新的地址
const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
const box = loader.fromArtifact('Box', address);
// 调用 retrieve 函数,使用call的方式
let value = await box.methods.retrieve().call();
console.log("Box value Before is", value);
// 获取账户列表
const accounts = await web3.eth.getAccounts();
// 使用第一个账户accounts[0]来发送交易,调用store函数,将值设为20,指定gas为50000,gasPrice为1e6
await box.methods.store(20)
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次调用 retrieve 函数,查看值是否变化
value = await box.methods.retrieve().call();
console.log("Box value After is", value);
}
main();
|
返回(合约刚部署,value
没有值,所以等于0,后面等于20):
1
2
3
|
$ node src/index.js
Box value Before is 0
Box value After is 20
|
升级合约
此时value
=20,假设升级内容是增加一个函数increment
,没调用一次value
+1
得到新的合约代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// contracts/Box.sol
pragma solidity ^0.5.0;
import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合约拥有者权限
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
contract Box is Initializable, Ownable {
uint256 private value;
event ValueChanged(uint256 newValue);
// 使用初始化函数代替构造函数
function initialize() public initializer {
// 要初始化Ownable合约
Ownable.initialize(msg.sender);
}
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {
return value;
}
// 新增一个函数,每次使value值+1
function increment() public onlyOwner {
value = value + 1;
emit ValueChanged(value);
}
}
|
使用oz upgrade
再次升级
当Call a function to initialize the instance after creating it? 再次选择 Y 时会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? Yes
? Select which function * initialize()
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
✓ Contract Box deployed
All implementations have been deployed
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 and calling 'initialize' with no arguments
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16
Proxy first-smart-contract/Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16 failed to upgrade with error: Returned error: VM Exception while processing transaction: revert
|
选择N,升级成功
1
2
3
4
5
6
7
8
9
|
$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? No
Nothing to compile, all contracts are up to date.
All implementations are up to date
✓ Instance upgraded at 0x0290FB167208Af455bB137780163b7B7a9a10C16. Transaction receipt: 0x2b84fba55b975b97c511798eb772156aa14e7f5002cbc25d345b895d4d3dd159
✓ Instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 upgraded
|
看到地址没有变化,现在要验证:
value
是否等于20?,如果等于表示合约状态(数据)还在
- 新增的
increment
是否存在?
修改一下 src/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');
async function main() {
// 连接RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({provider: web3}).web3;
// 改为新的地址
const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
const box = loader.fromArtifact('Box', address);
// 调用 retrieve 函数,使用call的方式
let value = await box.methods.retrieve().call();
console.log("Box value Before is", value);
// 获取账户列表
const accounts = await web3.eth.getAccounts();
// 使用第一个账户accounts[0]来发送交易,调用store函数,将值设为30,指定gas为50000,gasPrice为1e6
await box.methods.store(30)
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次调用 retrieve 函数,查看值是否变化
value = await box.methods.retrieve().call();
console.log("Box value After is", value);
// 调用 increment 函数
await box.methods.increment()
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次查看value值,此时 value应该等于31
value = await box.methods.retrieve().call();
console.log("Box value Increment is", value);
}
main();
|
返回(结果符合预期):
1
2
3
4
|
$ node src/index.js
Box value Before is 20
Box value After is 30
Box value Increment is 31
|