基于 Openzeppelin 的可升级合约解决方案的注意事项

基于 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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值