NamedBeacon与Proxy:可升级Solidity合约的优化实现
该合约实现了一个 mapping(bytes32 => address) 来存储地址。两个核心函数:
registerImplementation(bytes32 _referenceId, address _implementation)—— 注册功能,仅限所有者调用。getImplementation(bytes32 _referenceId)—— 根据ID获取地址,未找到时返回 address(0)。
管理权集中:registerImplementation 中强制执行所有者检查。实现验证可防止零地址或无代码的地址。为兼容 OpenZeppelin,还包含一个 implementation() 函数,返回 address(this)。
部署后,存储使用在不可变场景下达到最优。由于没有 SlotStorage,代码更简洁,但限制了信标升级能力。
完整 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 —— 不占用任何存储槽位。
构造函数中:
- 验证信标(code.length > 0)。
- 通过 referenceId 从信标获取实现,并检查 code.length。
- 可选地通过 delegatecall 初始化,传入 _data。
重写 _implementation() 返回 INamedBeacon(beacon).getImplementation(implementationReferenceId)。
支持 receive() 接收付款,若 msg.value > 0 且无 _data,则抛出 NonPayable 错误。
完整 NamedBeaconProxy 代码:
// 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
暂无评论。