前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Openzeppelin的三种代理模式

Openzeppelin的三种代理模式

作者头像
Tiny熊
发布2022-11-07 12:48:58
1.8K0
发布2022-11-07 12:48:58
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:gasshadow[1]

可以对照https://learnblockchain.cn/article/4899 一起看,算是那一篇的细化版。

Openzeppelin[2]:

https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy

有三种代理模式:

  1. 透明代理: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/transparent
  2. UUPS: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/utils
  3. 信标代理:https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/beacon

UUPS

图如下

用户和代理合约交互,代理合约不直接实现upgradeToupgradeToAndCall,由逻辑合约实现。

代理合约

代码语言:javascript
复制
// 这里需要用逻辑合约地址来构建代理合约。所以需要先部署逻辑合约。
constructor(address _logic, bytes memory _data) payable {
    _upgradeToAndCall(_logic, _data, false);// 直接设置代理合约的implementation槽位
}
function _implementation() internal view virtual override returns (address impl) {
    return ERC1967Upgrade._getImplementation();// 返回implementation槽位的值
}

逻辑合约

代码语言:javascript
复制
modifier onlyProxy() {
    // 要求不能直接调用,通过delegatecall 那么address(this)就是代理合约
    require(address(this) != __self, "Function must be called through delegatecall");
    // 要求代理合约实现的_getImplementation() 指向我这个逻辑合约
    require(_getImplementation() == __self, "Function must be called through active proxy");
    _;
}

// 这个是要求直接调用,不通过delegatecall 调用
modifier notDelegated() {
    require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
    _;
}
// 注意这里的notDelegated,这个函数是在升级时父类ERC1967Upgrade里调用的方法。
// 父类要求子类实现这个方法并返回_IMPLEMENTATION_SLOT用以证明是UUPS
function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
    return _IMPLEMENTATION_SLOT;
}

升级合约

合约升级上,openzeppelin 的 mocks 里,有一个实现,可以校验升级的合约是否可回滚。代码简单,就是先升级到新合约,然后设置回滚标记,然后回滚(升级)到旧合约,对比旧合约地址,然后再升级到新合约。

代码语言:javascript
复制
// 可以参考:mocks/UUPS/UUPSLegacy.sol
function _upgradeToAndCallSecureLegacyV1(
    address newImplementation,
    bytes memory data,
    bool forceCall
) internal {
    address oldImplementation = _getImplementation();

    // Initial upgrade and setup call
    __setImplementation(newImplementation);
    if (data.length > 0 || forceCall) {
        Address.functionDelegateCall(newImplementation, data);
    }

    // Perform rollback test if not already in progress
    StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
    if (!rollbackTesting.value) {
        // Trigger rollback using upgradeTo from the new implementation
        rollbackTesting.value = true;
        Address.functionDelegateCall(
            newImplementation,
            abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
        );
        rollbackTesting.value = false;
        // Check rollback was effective
        require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
        // Finally reset to the new implementation and log the upgrade
        _upgradeTo(newImplementation);
    }
}

Transparent

透明代理,有三方参与:代理合约、逻辑合约和管理合约。图如下:

代理合约

代码语言:javascript
复制
// 代理合约的构造,需要逻辑合约和管理合约地址,所以需要先部署逻辑合约和管理合约。
constructor(
    address _logic,
    address admin_,
    bytes memory _data
) payable ERC1967Proxy(_logic, _data) {
    _changeAdmin(admin_);
}

// 只能让管理合约调用,如果不是管理合约,就直接转到逻辑合约执行(_fallback)。
modifier ifAdmin() {
    if (msg.sender == _getAdmin()) {
        _;
    } else {
        _fallback();
    }
}

// 每次fallback调用,都要检查是不是admin
function _beforeFallback() internal virtual override {
    require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
    super._beforeFallback();
}

逻辑合约

就算逻辑合约里有upgradeTo等方法,也不会影响调用。因为普通用户就直接在代理合约判断ifAdmin的时候就转到了逻辑合约里,而管理合约的调用,就会放行到代理合约直接执行。

管理合约

仅仅是回调参数传过来的 proxy 的同名函数。整个管理合约是否可以调用 proxy 的函数,是在 proxy 代理合约里判断的。所以管理合约很轻量。

合约升级

用代理合约地址,直接调用管理合约的upgrade或者upgradeAndCall方法即可。

Beacon

以上两种代理,都存在一种缺陷,就是如果我要升级一批具有相同逻辑合约的代理合约,那么需要在每个代理合约都执行一遍升级(因为每个代理合约独立存储了_implementation)。信标合约,就是将所有的具有相同逻辑合约的代理合约的_implementation 只存一份在信标合约中,所有的代理合约通过和信标合约接口调用,获取_implementation,这样,在升级的时候,就可以只升级信标合约,就能搞定所有的代理合约的升级。如图:

代理合约

代码语言:javascript
复制
// 构造函数需要信标合约的地址,所以信标合约要先部署。将信标合约的地址传给代理合约进行构造。
constructor(address beacon, bytes memory data) payable {
    _upgradeBeaconToAndCall(beacon, data, false);
}
// 从信标合约获取实现。
function _implementation() internal view virtual override returns (address) {
    return IBeacon(_getBeacon()).implementation();
}

信标合约

代码语言:javascript
复制
// 构造函数需要逻辑合约的实现,所以先要部署逻辑合约,再部署信标合约。
constructor(address implementation_) {
    _setImplementation(implementation_);
}
// 升级直接升级信标合约的implementation即可。
function upgradeTo(address newImplementation) public virtual onlyOwner {
    _setImplementation(newImplementation);
    emit Upgraded(newImplementation);
}

合约升级

直接调用信标合约的upgradeTo 方法即可(当然只能是onlyOwner

参考资料

[1]

gasshadow: https://learnblockchain.cn/people/11678

[2]

Openzeppelin: https://learnblockchain.cn/article/727

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • UUPS
    • 代理合约
      • 逻辑合约
        • 升级合约
        • Transparent
          • 代理合约
            • 逻辑合约
              • 管理合约
                • 合约升级
                • Beacon
                  • 代理合约
                    • 信标合约
                      • 合约升级
                        • 参考资料
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档