在编写可升级合约时请不要使用构造函数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"); // 这个合约是不可升级的
}
......
}
像这样
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