返回首页

NamedBeacon Proxy:Solidity 代理优化

NamedBeacon 和 NamedBeaconProxy 为标准 BeaconProxy 提供高效替代。通过 bytes32 ID 集中存储实现,最大限度减少代理大小和代码重复。适用于使用 Solidity 0.8.33 的生产环境。

Solidity 上无需额外代码的高效 NamedBeacon 和 Proxy
Advertisement 728x90

NamedBeacon与Proxy:可升级Solidity合约的优化实现

该合约实现了一个 mapping(bytes32 => address) 来存储地址。两个核心函数:

  • registerImplementation(bytes32 _referenceId, address _implementation) —— 注册功能,仅限所有者调用。
  • getImplementation(bytes32 _referenceId) —— 根据ID获取地址,未找到时返回 address(0)。

管理权集中:registerImplementation 中强制执行所有者检查。实现验证可防止零地址或无代码的地址。为兼容 OpenZeppelin,还包含一个 implementation() 函数,返回 address(this)。

部署后,存储使用在不可变场景下达到最优。由于没有 SlotStorage,代码更简洁,但限制了信标升级能力。

Google AdInline article slot

完整 NamedBeacon 代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

interface INamedBeacon {
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation);
    function registerImplementation(bytes32 _referenceId, address _implementation) external;
}

contract NamedBeacon {
    /// @dev 这必须由你的自定义认证逻辑覆盖
    address public owner;

    /// @notice 主要的实现地址存储
    mapping(bytes32 implId => address implAddress) internal implementations;

    error InvalidImplementation(address implementation);
    error UnauthorizedAccess();

    // 构造函数
    constructor (address _owner) {
        /// @dev 这必须由你的自定义认证逻辑覆盖
        require (_owner != address(0));
        owner = _owner;
    }

    /// @notice 返回指定 _referenceId 注册的实现地址。若未找到,返回 address(0)。
    /// @param _referenceId 被引用合约的唯一标识符
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation) {
        return implementations[_referenceId];
    }

    /// @notice 在给定 _referenceId 下注册实现 _implementation。
    /// @param _referenceId 被引用合约的唯一标识符
    /// @param _implementation 实现地址
    function registerImplementation(bytes32 _referenceId, address _implementation) external {
        require (msg.sender == owner, UnauthorizedAccess());
        if (_implementation != address(0) && _implementation.code.length == 0) {
            revert InvalidImplementation(_implementation);
        }
        implementations[_referenceId] = _implementation;
    }

    /// @notice 特殊函数,用于模拟 OZ 的信标功能
    /// @dev 仅为了与 OZ BeaconProxy 兼容
    /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation();
    /// @dev 1. 此函数应返回有效地址
    /// @dev 2. 且应为合约:if (beaconImplementation.code.length == 0) { revert }
    /// @dev 类似问题也出现在 ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data) 中,
    /// @dev 第一行 `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);`
    /// @dev 基于此,
    function implementation() external view returns (address _current) {
        return address(this);
    }
}

NamedBeaconProxy:极简可升级代理

继承自 Proxy 并使用 OpenZeppelin 的 Address。不可变字段:beacon 和 implementationReferenceId —— 不占用任何存储槽位。

构造函数中:

Google AdInline article slot
  • 验证信标(code.length > 0)。
  • 通过 referenceId 从信标获取实现,并检查 code.length。
  • 可选地通过 delegatecall 初始化,传入 _data。

重写 _implementation() 返回 INamedBeacon(beacon).getImplementation(implementationReferenceId)

支持 receive() 接收付款,若 msg.value > 0 且无 _data,则抛出 NonPayable 错误。

完整 NamedBeaconProxy 代码:

Google AdInline article slot
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol";
import { INamedBeacon } from "./NamedBeacon.sol";

contract NamedBeaconProxy is Proxy {
    address private immutable beacon;
    bytes32 private immutable implementationReferenceId;

    error NonPayable();
    error InvalidBeacon(address beacon);
    error InvalidImplementation(address implementation);
    error ImplementationNotFound(bytes32 implementationReferenceId);

    constructor(address _beacon, bytes32 _implementationReferenceId, bytes memory _data) payable {
        // 设置信标
        if (_beacon.code.length == 0) {
            revert InvalidBeacon(_beacon);
        }
        beacon = _beacon;

        // 设置实现引用 ID
        address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId);
        if (implementationFromBeacon.code.length == 0) {
            revert InvalidImplementation(implementationFromBeacon);
        }
        implementationReferenceId = _implementationReferenceId;

        // 若提供数据则初始化
        if (_data.length > 0) {
            Address.functionDelegateCall(implementationFromBeacon, _data);
        } else {
            _checkNonPayable();
        }
    }

    function _implementation() internal view virtual override returns (address) {
        return INamedBeacon(beacon).getImplementation(implementationReferenceId);
    }

    receive() external payable {}

    function _checkNonPayable() private {
        if (msg.value > 0) {
            revert NonPayable();
        }
    }
}

部署与使用流程

  • 使用所有者部署 NamedBeacon。
  • 部署实现合约。
  • 注册:registerImplementation(keccak256('v1'), implementationAddress)
  • 部署 NamedBeaconProxy(beacon, referenceId, initData)。

代理中的验证检查可被禁用以节省调用 gas。所有者逻辑可在派生的 NamedBeacon 合约中自定义。

核心优势

  • 通过 bytes32 ID 集中管理实现地址,减少代码重复。
  • 代理中使用不可变引用,完全避免存储槽位占用。
  • 极小代理体积:无管理员逻辑,仅通过 delegatecall 调用信标。
  • 完全兼容 OpenZeppelin:支持 implementation() 用于 upgradeBeaconToAndCall。
  • 生产就绪:基于 Solidity 0.8.33、Hardhat 3.1.6,具备全面测试覆盖。

— Editorial Team

Advertisement 728x90

继续阅读