Zpět na domů

NamedBeacon Proxy: optimalizace Solidity proxy

NamedBeacon a NamedBeaconProxy nabízejí efektivní alternativu ke standardní BeaconProxy. Centralizované ukládání implementací podle bytes32 ID minimalizuje velikost proxy a duplikaci kódu. Vhodné pro production s Solidity 0.8.33.

Efektivní NamedBeacon a Proxy na Solidity bez zbytečného kódu
Advertisement 728x90

NamedBeacon a Proxy: optimalizovaná implementace pro aktualizovatelné proxy v Solidity

Vývojáři často čelí neefektivitě standardního BeaconProxy: samostatný kontrakt pro uložení jediné adresy implementace, duplikace odkazů v immutable a storage, nadbytečný správní kód v každém proxy. Pár kontraktů NamedBeacon a NamedBeaconProxy odstraňuje tyto problémy, minimalizuje náklady na gas a velikost bytecode.

NamedBeacon centralizuje správu adres implementací pod řetězcovými identifikátory bytes32. Proxy uchovává pouze immutable odkazy na beacon a referenceId a dynamicky získává aktuální implementaci.

NamedBeacon: centrální úložiště implementací

Tento kontrakt implementuje mapping(bytes32 => address) pro uchování adres. Dvě klíčové funkce:

Google AdInline article slot
  • registerImplementation(bytes32 _referenceId, address _implementation) — registrace, dostupná pouze ownerovi.
  • getImplementation(bytes32 _referenceId) — čtení adresy podle ID, vrátí address(0), pokud nenalezeno.

Správa je lokalizována: owner je ověřen v registerImplementation. Validace implementace vylučuje nulové adresy bez kódu. Pro kompatibilitu s OpenZeppelin byla přidána implementation(), která vrací address(this).

Uložení v storage je vhodné pro scénáře, které jsou po nasazení neměnné. Odstranění SlotStorage zjednodušuje kód, ale omezuje možnosti aktualizace beaconu.

Celý kód NamedBeacon:

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 Toto musí být přepsáno vaší vlastní logikou ověření
    address public owner;

    /// @notice Hlavní úložiště implementací
    mapping(bytes32 implId => address implAddress) internal implementations;

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

    // ctor
    constructor (address _owner) {
        /// @dev Toto musí být přepsáno vaší vlastní logikou ověření
        require (_owner != address(0));
        owner = _owner;
    }

    /// @notice Vrátí adresu implementace registrovanou pod _referenceId. Pokud není nalezena žádná referenční hodnota, vrátí address(0).
    /// @param _referenceId Jedinečný ID odkazu na referovaný kontrakt
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation) {
        return implementations[_referenceId];
    }

    /// @notice Zaregistruje implementaci _implementation pod daným _referenceId.
    /// @param _referenceId Jedinečný ID odkazu na referovaný kontrakt
    /// @param _implementation Adresa implementace
    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 Speciální funkce pro napodobení chování beaconu z OZ
    /// @dev Existuje pouze kvůli kompatibilitě s OZ BeaconProxy
    /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation();
    /// @dev 1. toto by mělo vracet platnou adresu
    /// @dev 2. A mělo by jít o kontrakt: if (beaconImplementation.code.length == 0) { revert }
    /// @dev Podobný problém je i v ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data),
    /// @dev na řádku `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);`
    function implementation() external view returns (address _current) {
        return address(this);
    }
}

NamedBeaconProxy: minimalistické proxy

Dědí od Proxy a používá Address z OpenZeppelin. Immutable pole: beacon a implementationReferenceId — bez slotů storage.

V konstruktoru:

  • Ověření beaconu (code.length > 0).
  • Čtení implementace z beaconu podle referenceId, ověření code.length.
  • Volitelný delegatecall pro inicializaci s _data.

Přepsaná _implementation() vrací INamedBeacon(beacon).getImplementation(implementationReferenceId).

Google AdInline article slot

Podpora receive() pro payable, chyba NonPayable při msg.value > 0 bez _data.

Celý kód 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 {
        // nastavení beaconu
        if (_beacon.code.length == 0) {
            revert InvalidBeacon(_beacon);
        }
        beacon = _beacon;

        // nastavení ID implementace
        address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId);
        if (implementationFromBeacon.code.length == 0) {
            revert InvalidImplementation(implementationFromBeacon);
        }
        implementationReferenceId = _implementationReferenceId;

        // inicializace, pokud je poskytnut data
        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();
        }
    }
}

Postup nasazení a použití

  • Nasazení NamedBeacon s ownerem.
  • Nasazení implementací.
  • Registrace: registerImplementation(keccak256('v1'), implementationAddress).
  • Nasazení NamedBeaconProxy(beacon, referenceId, initData).

Ověření v proxy lze deaktivovat pro úsporu gasu při voláních. Logika ownera je přizpůsobitelná v dědičném kontraktu NamedBeacon.

Co je důležité

  • Centralizované uchování adres implementací pod ID bytes32 snižuje duplikaci kódu.
  • Immutable odkazy v proxy vylučují použití slotů storage.
  • Minimální velikost proxy: žádná správní logika, pouze delegatecall k beaconu.
  • Kompatibilita s OZ: implementation() pro upgradeBeaconToAndCall.
  • Solidity 0.8.33, Hardhat 3.1.6 — produkčně připravené s testy.

— Editorial Team

Advertisement 728x90

Číst dál