基于 Openzeppelin 的可升级合约解决方案的注意事项
注意事项
构造函数
在编写可升级合约时请不要使用构造函数contructor()
,我们知道可升级合约运行时逻辑与数据分离的,合约数据保存在代理合约
中,我们编写的合约是逻辑合约
,当合约部署时,逻辑合约
调用contructor()
初始化的数据是逻辑合约
的,代理合约
中的数据并没有被初始化,所以是无效的。
包括全局变量
声明时赋值初始值,因为这种做法相当于在构造函数contructor()
中设置这些值。
父类合约初始化
如果MyContract继承自合约 BaseContract, 那么BaseContract合约的初始化函数 initialize() 的modifier(修饰器) 必须使用 onlyInitializing,比如:
contract BaseContract is Initializable {
uint256 public y;
function initialize() public onlyInitializing {
y = 42;
}
}
// BaseContract 继承自 Initializable,这里无需重复显式继承 Initializable
contract MyContract is BaseContract {
int storageValue;
// modifier(修饰器) initializer 可以确保initialize只会被调用一次
function initialize(int initValue) public initializer {
BaseContract.initialize();
storageValue = initValue;
}
......
}
声明状态变量
- 声明状态变量时,不能对其赋值 初始值
像这样做是错误的
contract MyContract is Initializable {
int storageValue = 666; //初始值无效
function initialize() public initializer {
}
......
}
因为这种做法相当于在构造函数contructor()
中设置这些值,因此不适用于可升级的约定。
- 定义常量状态变量仍然可以
像这样是可以的
contract MyContract is Initializable {
// constant 常量关键字
int public constant storageValue = 666; //有效
function initialize() public initializer {
}
......
}
在合约代码中创建新合约实例
- 不要在合约中创建新的合约实例,创建出的合约是不可升级的
像这样,即使MyContract是可升级的,但 ERC20 实例不可升级
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyContract is Initializable {
ERC20 public token;
function initialize() public initializer {
token = new ERC20("Test", "TST"); // 这个合约是不可升级的
}
......
}
- 如果你希望在MyContract引用其他可升级合约,那么将已部署好的可升级合约实例注入到MyContract中,是很好的解决方案
像这样
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
contract MyContract is Initializable {
IERC20Upgradeable public token;
function initialize(IERC20Upgradeable _token) public initializer {
token = _token;
}
......
}
升级合约时声明状态变量
在编写合约的新版本时,无论是由于新功能还是 bug 修复,还有一个额外的限制需要遵守:不能更改合约状态变量的声明顺序,也不能更改其类型。
比如当前版本(V1)合约状态变量布局
contract MyContractV1 {
uint256 private x;
string private y;
}
那么在编写新版本合约时,请避免一下错误操作:
- 更改变量的类型
这是错误的
contract MyContractV2 {
string private x;
string private y;
}
- 更改声明它们的顺序
这是错误的
contract MyContractV2 {
string private y;
uint256 private x;
}
- 在现有变量之前引入一个新变量
这是错误的
contract MyContractV2 {
bytes private a;
uint256 private x;
string private y;
}
这是正确的
contract MyContractV2 {
uint256 private x;
string private y;
bytes private a;
}
- 删除现有变量
这是错误的
contract MyContractV2 {
string private y;
}
参考文档:
Openzeppelin编写可升级合约:https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
版权声明:本文为Lyon_Nee原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。