Zurück zur Startseite

NamedBeacon Proxy: Solidity-Proxy-Optimierung

NamedBeacon und NamedBeaconProxy bieten eine effiziente Alternative zum Standard-BeaconProxy. Zentrale Speicherung von Implementierungen per bytes32-ID minimiert Proxy-Größe und Code-Duplikation. Geeignet für die Produktion mit Solidity 0.8.33.

Effizientes NamedBeacon und Proxy auf Solidity ohne zusätzlichen Code
Advertisement 728x90

NamedBeacon und Proxy: Optimierte Implementierung für upgradefähige Solidity-Verträge

Entwickler stoßen oft auf ineffiziente Strukturen beim Standard-BeaconProxy: Ein separates Contract nur zur Speicherung einer Implementationsadresse, doppelte Referenzen in immutable und Storage sowie redundante Admin-Logik in jedem Proxy. Die vorgeschlagene Kombination aus NamedBeacon und NamedBeaconProxy beseitigt diese Probleme und minimiert Gas-Kosten sowie Bytecode-Größe.

NamedBeacon zentralisiert die Verwaltung von Implementationsadressen mithilfe von stringähnlichen bytes32-Identifikatoren. Der Proxy speichert lediglich immutable Referenzen auf den Beacon und die ReferenceId und holt die aktuelle Implementierung dynamisch zur Laufzeit ab.

NamedBeacon: Zentrale Implementations-Registry

Dieser Contract implementiert ein mapping(bytes32 => address), um Adressen zu speichern. Zwei Schlüsselfunktionen:

Google AdInline article slot
  • registerImplementation(bytes32 _referenceId, address _implementation) — Registrierung, nur vom Owner möglich.
  • getImplementation(bytes32 _referenceId) — Ruft die Adresse anhand der ID ab, gibt address(0) zurück, falls nicht gefunden.

Die Administration ist zentralisiert: Owner-Prüfungen werden in registerImplementation durchgeführt. Implementationsvalidierung verhindert Null-Adressen ohne Code. Für Kompatibilität mit OpenZeppelin ist eine implementation()-Funktion enthalten, die address(this) zurückgibt.

Der Speicherverbrauch ist ideal für immutable Szenarien nach dem Deployment. Das Fehlen von SlotStorage vereinfacht den Code, begrenzt aber die Beacon-Upgrades.

Vollständiger NamedBeacon-Code:

Google AdInline article slot
// 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 Dies muss durch Ihre benutzerdefinierte Auth-Logik überschrieben werden
    address public owner;

    /// @notice Hauptspeicher für Implementierungen
    mapping(bytes32 => address) internal implementations;

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

    // ctor
    constructor (address _owner) {
        /// @dev Dies muss durch Ihre benutzerdefinierte Auth-Logik überschrieben werden
        require (_owner != address(0));
        owner = _owner;
    }

    /// @notice Gibt die Adresse einer Implementierung zurück, die unter _referenceId registriert ist. Falls keine Referenz gefunden wird, wird address(0) zurückgegeben.
    /// @param _referenceId Eindeutige Referenz-ID des referenzierten Contracts
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation) {
        return implementations[_referenceId];
    }

    /// @notice Registriert die Implementierung _implementation unter der angegebenen _referenceId.
    /// @param _referenceId Eindeutige Referenz-ID des referenzierten Contracts
    /// @param _implementation Adresse der Implementierung
    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 Spezialfunktion zur Nachbildung der Beacon-Funktionalität aus OZ
    /// @dev Existiert nur zur Kompatibilität mit OZ BeaconProxy
    /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation();
    /// @dev 1. Diese Funktion muss eine gültige Adresse zurückgeben
    /// @dev 2. UND sie muss ein Contract sein: if (beaconImplementation.code.length == 0) { revert }
    /// @dev Ähnliches Problem tritt in ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data),
    /// @dev Zeile `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);`
    /// @dev Angesichts dessen
    function implementation() external view returns (address _current) {
        return address(this);
    }
}

NamedBeaconProxy: Minimalistischer upgradefähiger Proxy

Erweitert Proxy und nutzt Address aus OpenZeppelin. Immutable-Felder: beacon und implementationReferenceId — keine Speicherplätze werden genutzt.

Im Konstruktor:

  • Validierung des Beacons (code.length > 0).
  • Abruf der Implementierung über Beacon via referenceId, Prüfung von code.length.
  • Optionaler delegatecall zum Initialisieren mit _data.

Überschriebene _implementation() gibt INamedBeacon(beacon).getImplementation(implementationReferenceId) zurück.

Google AdInline article slot

Unterstützt receive() für Zahlbarkeit, wirft NonPayable-Fehler, wenn msg.value > 0 ohne _data.

Vollständiger NamedBeaconProxy-Code:

// 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 {
        // beacon setzen
        if (_beacon.code.length == 0) {
            revert InvalidBeacon(_beacon);
        }
        beacon = _beacon;

        // Implementations-Referenz-ID setzen
        address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId);
        if (implementationFromBeacon.code.length == 0) {
            revert InvalidImplementation(implementationFromBeacon);
        }
        implementationReferenceId = _implementationReferenceId;

        // Initialisierung, falls Daten bereitgestellt werden
        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();
        }
    }
}

Bereitstellung und Nutzung – Workflow

  • NamedBeacon mit einem Owner bereitstellen.
  • Implementierungsverträge bereitstellen.
  • Registrieren: registerImplementation(keccak256('v1'), implementationAddress).
  • NamedBeaconProxy(beacon, referenceId, initData) bereitstellen.

Validierungsprüfungen im Proxy können deaktiviert werden, um Gas bei Aufrufen zu sparen. Die Owner-Logik kann in einem abgeleiteten NamedBeacon-Contract angepasst werden.

Hauptvorteile

  • Zentrale Speicherung von Implementationsadressen über bytes32-IDs reduziert Code-Duplikation.
  • Immutable Referenzen im Proxy eliminieren Speicherplatznutzung.
  • Minimaler Proxy-Größe: Keine Admin-Logik, nur delegatecall an den Beacon.
  • Vollständige OpenZeppelin-Kompatibilität: Unterstützt implementation() für upgradeBeaconToAndCall.
  • Für Produktion geeignet: Solidity 0.8.33, Hardhat 3.1.6, mit umfassender Testabdeckung.

— Editorial Team

Advertisement 728x90

Weiterlesen