Привет,
Время от времени мою светлую голову озаряют "элегантные решения сложнейших задач", которые почему-то никем не были решены до меня (*сарказм*), и сейчас я поделюсь с вами очередной такой киллер идеей на триллион копеек. Я назвал её "NamedBeacon and Proxy".
Собственно, речь о прокси (proxy) и беконах (beacon - "маяк") для обновляемых смартов на Solidity. Все началось с неудовлетворенности реализацией BeaconProxy от OpenZeppelin:
используемый бекон хранит лишь 1 адрес имплементации (имеет только 1 функцию
implementation() external view returns (address), следовательно, чтобы хранить аж целый 1 слот адреса надо деплоить отдельный смарт;реализация BeaconProxy к тому же хранит адрес бекона два раза - в immutable private переменной и еще в ERC1967Utils;
так же в прокси хранится много кода для администрирования (бекон, имплементация, админ) - каждый раз деплоя прокси, вы деплоите кучу всего лишнего.
Мое гениальное решение состоит из двух смартов, работающих в паре:
NamedBeacon:имеет 2 основные функции для регистрации и чтения адресов имплементаций по айди строке
registerImplementation(bytes32 referenceId, address implementation) externalgetImplementation(bytes32 referenceId) external view returns (address implementation)
администрирование ведется через этот смарт - логика администрирования в одном месте
минималистичен (время от времени я добавляю/удаляю в него проверки правильности ввода, чтения списка зарегистрированных зависимостей, но в целом это не необходимо - в основном достаточно просто сохранить референс с айди на адрес)
недостаток - текущая версия подразумевает, что контракт 1 раз задеплоится и больше не будет изменяться (ну, типа, зачем?) (я имею в виду, что храню данные в storage, а не по SlotStorage паттерну)
NamedBeaconProxyхранит ссылку на бекон, а так же реф айди, по которому будет читать адрес имплементации в immutable - задается в конструкторе, не влияет на storage
часть проверок скопирована из ERC1967Utils и BeaconProxy, но так-то их можно и скипнуть (например, можно убрать проверки на прочитанный из бекона адрес - следовательно, сохранить еще немного газа на вызовах этого прокси).
Как это работает:
деплоится бекон
деплоятся имплементации
имплементации регистрируются в беконе
под имплементацию деплоится прокси с указанием бекона и айди для чтения имплементации
всё.
Код можно посмотреть на гитхаб (понравится - smash the star button! pzhl).
Hardhat 3.1.6, Solidity 0.8.33, имплементация, скрипты, пару тестов, всё как в проде (ну почти).
(чёт как‑то немного получилось, поэтому ниже сокращенный код. код с комментами, ивентами и прочим - в гитхабе)
// 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 This must be overwritten by your custom auth logic address public owner; /// @notice Main storage of implementations mapping(bytes32 imlpId => address imlpAddress) internal implementations; error InvalidImplementation(address implementation); error UnauthorizedAccess(); // ctor constructor (address _owner) { /// @dev This must be overwritten by your custom auth logic require (_owner != address(0)); owner = _owner; } /// @notice Returns an address of an implementation, registered under _referenceId. If no reference is found, returns address(0). /// @param _referenceId Unique ref id of the referenced contract function getImplementation(bytes32 _referenceId) external view returns (address _implementation) { return implementations[_referenceId]; } /// @notice Registers implementation _implementation under given _referenceId. /// @param _referenceId Unique ref id of the referenced contract /// @param _implementation Address of 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 Special function to mimic beacon functionality from OZ /// @dev Exists only to be compatible with OZ BeaconProxy /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation(); /// @dev 1. this should return a valid address /// @dev 2. AND it should be a contract: if (beaconImplementation.code.length == 0) { revert } /// @dev Similar issue is in ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data), /// @dev on line `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);` /// @dev Given this, function implementation() external view returns (address _current) { return address(this); } }
// 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 { // set beacon if (_beacon.code.length == 0) { revert InvalidBeacon(_beacon); } beacon = _beacon; // set implementation ref id address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId); if (implementationFromBeacon.code.length == 0) { revert InvalidImplementation(implementationFromBeacon); } implementationReferenceId = _implementationReferenceId; // initialize if data is provided 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(); } } }
---
С 2009го пишу код, лидю тимы, доходил до пресейл техлида и руководителя отдела разработки.
Рассматриваю новые предложения (писать код, и/или пинать молодые горячие головы, и/или строить архитектуры, и/или говорить с заказчиком).
C#, JavaScript, Solidity, English C1; учу Rust. Посмотреть на фоточку меня можно на GH
