本文是对openzeppelin文档中Creating Upgradeable Contracts From Solidity的实践
接上篇使用 openzeppelin 开发第一个可升级智能合约 -Part2
要建立两个合约Factory.sol
和Product.sol
,目标是通过Factory.sol
去创建一个可以升级的Product.sol
合约.
在contracts
目录下创建Factory.sol
合约
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
|
// contracts/Factory.sol
pragma solidity ^0.5.0;
import "@openzeppelin/upgrades/contracts/Initializable.sol";
import "@openzeppelin/upgrades/contracts/application/App.sol";
contract Factory is Initializable {
App private app;
event InstanceCreated(address);
function initialize(App _app) public initializer {
// 和之前不一样的是这里把另一个合约作为对象
app = _app;
}
function createInstance(bytes memory _data) public {
// first-smart-contract是当前项目的包名称
string memory packageName = "first-smart-contract";
string memory contractName = "Product";
address admin = msg.sender;
address product = address(
app.create(packageName, contractName, admin, _data)
);
emit InstanceCreated(product);
}
}
|
其中 first-smart-contract
是之前使用npm init
初始化时输入的包名,这个值也保存在.openzeppelin/project.json
配置文件中,可以自己查看
在contracts
目录下创建Product.sol
合约,合约代码使用之前Box.sol
的代码稍微做一下更改
修改下初始化函数,可以指定value的值
依然保留store和retrieve函数
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
|
// contracts/Product.sol
pragma solidity ^0.5.0;
import "@openzeppelin/upgrades/contracts/Initializable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
contract Product is Initializable, Ownable {
uint256 public value;
event ValueChanged(uint256 newValue);
// 修改下初始化函数,可以指定value的值
function initialize(uint256 _value) public initializer {
value = _value;
}
// 依然保留store和retrieve函数
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {
return value;
}
}
|
依然是先启动本地测试链,--db
指定之前保存链数据的目录
1
|
npx ganache-cli --deterministic --db ~/data
|
再新开一个命令行窗口,进入项目目录,输入:
返回:
1
2
3
|
$ npx oz add Product
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
✓ Added contract Product
|
此步骤是将Product.sol编译
,会在build/contracts
目录下生成编译后的json文件
再将Procuct
添加到.openzeppelin/project.json
配置文件中contract
字段
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
|
{
"manifestVersion": "2.2",
"contracts": {
"Box": "Box",
"Product": "Product" <--- 就是这个位置
},
"dependencies": {},
"name": "first-smart-contract",
"version": "1.0.0",
"compiler": {
"compilerSettings": {
"optimizer": {
"enabled": false,
"runs": "200"
}
},
"typechain": {
"enabled": false
},
"manager": "openzeppelin",
"solcVersion": "0.5.17",
"artifactsDir": "build/contracts",
"contractsDir": "contracts"
},
"telemetryOptIn": false
}
|
执行oz push
返回(Box是上一篇中用到的合约,可以忽略不看):
1
2
3
4
5
|
$ oz push
Nothing to compile, all contracts are up to date.
? Pick a network development
✓ Contract Box deployed
✓ Contract Product deployed
|
此步骤是将.openzeppelin/project.json
配置文件中contract
字段的合约进行部署,会在.openzeppelin/dev-1587092426749.json
中生成以合约名
为字段的数据,dev-1587092426749.json
中数字部分是时间戳,所以每个人不一样
.openzeppelin/dev-1587092426749.json
大致内容:
1
2
3
4
5
6
7
8
9
10
11
|
{
"contracts": {
"Box": {...省略...},
"Product": {...省略...}
},
"solidityLibs": {},
"proxies": {},
"manifestVersion": "2.2",
"version": "1.0.0"
}
|
其中Product
字段内容大致如下,省略部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"address": "0xA94B7f0465E98609391C623d0560C5720a3f2D33",
"constructorCode": "60806040526109f6806100136000396000f3fe",
"bodyBytecodeHash": "9711d7e2adef036560304526efa3b5605a73565d8600a96c07f635293e00532f",
"localBytecodeHash": "e85d71e1251c7f3ba93e4d0ec72fd80027628cc30ab70cc3a2d4bcad56c9370b",
"deployedBytecodeHash": "e85d71e1251c7f3ba93e4d0ec72fd80027628cc30ab70cc3a2d4bcad56c9370b",
"types": {...省略...},
"storage": [...省略...],
"warnings": {
"hasConstructor": false,
"hasSelfDestruct": false,
"hasDelegateCall": false,
"hasInitialValuesInDeclarations": false,
"uninitializedBaseContracts": []
}
}
}
|
执行oz publish
返回:
1
2
3
4
5
6
|
$ oz publish
? Pick a network development
✓ Project structure deployed
✓ Registering Box at 0x0E696947A06550DEf604e82C26fd9E493e576337 in directory
✓ Registering Product at 0xA94B7f0465E98609391C623d0560C5720a3f2D33 in directory
✓ Published to dev-1587092426749!
|
此步骤会在.openzeppelin/project.json
配置文件中增加几个字段app
,package
,provider
3个字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"contracts": {
"Box": {...省略...},
"Product": {...省略...} ,
},
"solidityLibs": {},
"proxies": {},
"manifestVersion": "2.2",
"version": "1.0.0",
"app": {
"address": "0x6eD79Aa1c71FD7BdBC515EfdA3Bd4e26394435cC"
},
"package": {
"address": "0xb09bCc172050fBd4562da8b229Cf3E45Dc3045A6"
},
"provider": {
"address": "0xFC628dd79137395F3C9744e33b1c5DE554D94882"
}
}
|
其中app
字段:
1
2
3
|
"app": {
"address": "0x6eD79Aa1c71FD7BdBC515EfdA3Bd4e26394435cC"
}
|
现在可以像之前那样去部署Factory
合约:
返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$ oz deploy
Nothing to compile, all contracts are up to date.
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Factory
✓ Added contract Factory
✓ Contract Factory deployed
All implementations have been deployed
# 选择Y,调用初始化函数,函数选择initialize(_app: address)
? Call a function to initialize the instance after creating it? Yes
? Select which function initialize(_app: address)
# 复制前面app字段的地址进来
? _app: address: 0x6eD79Aa1c71FD7BdBC515EfdA3Bd4e26394435cC
✓ Setting everything up to create contract instances
✓ first-smart-contract Factory instance created at 0x86072CbFF48dA3C1F01824a6761A03F105BCC697
To upgrade this instance run 'oz upgrade'
0x86072CbFF48dA3C1F01824a6761A03F105BCC697
|
Factory
合约地址为0x86072CbFF48dA3C1F01824a6761A03F105BCC697
,这个需要用到.
用命令行调用Factory
合约的createInstance
函数去创建一个新的Product
合约
createInstance
接收一个bytes
类型的_data
数据,这个值需要通过计算获得
1
2
3
4
|
$ node
> const { encodeCall } = require('@openzeppelin/upgrades');
> encodeCall('initialize', ['uint256'], [42]);
'0xfe4b84df000000000000000000000000000000000000000000000000000000000000002a'
|
在使用命令行发送交易进行调用
返回(选择createInstance(_data: bytes)函数,复制上面计算的_data进去):
1
2
3
4
5
6
7
8
|
$ oz send-tx
? Pick a network development
? Pick an instance Factory at 0x86072CbFF48dA3C1F01824a6761A03F105BCC697
? Select which function createInstance(_data: bytes)
? _data: bytes: 0xfe4b84df000000000000000000000000000000000000000000000000000000000000002a
✓ Transaction successful. Transaction hash: 0x2449a21c33249836420c12209eaba2dec72eb1b81ba693ef7d04aa5b7083da56
Events emitted:
- InstanceCreated(0x067805E69e62E8bE56e8D13f4EBf53372D3dD02e)
|
TIP
用javascript的方式去调用Factory
合约的createInstance
函数,没有调试通过,还有问题待解决
待解决