Considerations for Upgradable Contract Solutions Based on Openzeppelin
Precautions
Constructor
Please do not use the constructor when writing an upgradeable contract . We contructor()
know that the logic and data of the upgradeable contract are separated at runtime, and the contract data is stored in . The data has not been initialized, so it is invalid. Including assigning initial values at declaration time is equivalent to setting those values in the constructor .代理合约
逻辑合约
逻辑合约
contructor()
逻辑合约
代理合约
全局变量
contructor()
Parent class contract initialization
If MyContract inherits from the contract BaseContract, then the modifier (decorator) of the initialization function initialize() of the BaseContract contract must use onlyInitializing, for example:
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;
}
......
}
Declare state variables
- When declaring a state variable, it cannot be assigned an initial value
it's wrong to do it like this
contract MyContract is Initializable {
int storageValue = 666; //初始值无效
function initialize() public initializer {
}
......
}
Because this practice is equivalent to setting these values in the constructor contructor()
, it does not apply to the upgradeable contract.
- Defining constant state variables is still possible
like this is ok
contract MyContract is Initializable {
// constant 常量关键字
int public constant storageValue = 666; //有效
function initialize() public initializer {
}
......
}
Create a new contract instance in the contract code
- Do not create a new contract instance in the contract, the created contract is not upgradeable
Like this, even though MyContract is upgradable, the ERC20 instance is not upgradable
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyContract is Initializable {
ERC20 public token;
function initialize() public initializer {
token = new ERC20("Test", "TST"); // 这个合约是不可升级的
}
......
}
- If you want to reference other upgradeable contracts in MyContract, it is a good solution to inject the deployed upgradeable contract instance into MyContract
like this
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
contract MyContract is Initializable {
IERC20Upgradeable public token;
function initialize(IERC20Upgradeable _token) public initializer {
token = _token;
}
......
}
Declare state variables when upgrading a contract
When writing a new version of a contract, whether due to a new feature or a bug fix, there is an additional restriction to respect: the order of declaration of the contract's state variables cannot be changed, nor can their type be changed.
For example, the current version (V1) contract state variable layout
contract MyContractV1 {
uint256 private x;
string private y;
}
So when writing a new version of the contract, please avoid the following mistakes:
- change the type of variable
This is wrong
contract MyContractV2 {
string private x;
string private y;
}
- change the order in which they are declared
This is wrong
contract MyContractV2 {
string private y;
uint256 private x;
}
- Introduce a new variable before an existing variable
This is wrong
contract MyContractV2 {
bytes private a;
uint256 private x;
string private y;
}
that's right
contract MyContractV2 {
uint256 private x;
string private y;
bytes private a;
}
- delete existing variable
This is wrong
contract MyContractV2 {
string private y;
}
Reference document:
Openzeppelin writing upgradeable contracts: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable