Retour à l'accueil

NamedBeacon Proxy : optimisation de proxy Solidity

NamedBeacon et NamedBeaconProxy offrent une alternative efficace au BeaconProxy standard. Stockage centralisé des implémentations par ID bytes32 minimise la taille du proxy et la duplication de code. Convient à la production avec Solidity 0.8.33.

NamedBeacon et Proxy efficaces sur Solidity sans code supplémentaire
Advertisement 728x90

NamedBeacon et Proxy : Implémentation optimisée pour les contrats Solidity upgradables

Les développeurs rencontrent souvent des inefficacités avec le BeaconProxy standard : un contrat distinct uniquement pour stocker une adresse d'implémentation, des références dupliquées dans les variables immuables et en mémoire, ainsi qu'une logique d'administration redondante dans chaque proxy. La paire proposée, NamedBeacon et NamedBeaconProxy, élimine ces problèmes, réduisant au minimum les coûts en gaz et la taille du bytecode.

NamedBeacon centralise la gestion des adresses d'implémentation à l'aide d'identifiants de type bytes32. Le proxy ne conserve que des références immuables vers le beacon et le referenceId, récupérant dynamiquement l'implémentation actuelle à l'exécution.

NamedBeacon : Registre centralisé des implémentations

Ce contrat implémente un mapping(bytes32 => address) pour stocker les adresses. Deux fonctions clés :

Google AdInline article slot
  • registerImplementation(bytes32 _referenceId, address _implementation) — inscription, disponible uniquement pour le propriétaire.
  • getImplementation(bytes32 _referenceId) — récupère l'adresse par ID, renvoie address(0) si non trouvée.

L'administration est centralisée : les vérifications de propriétaire sont obligatoires dans registerImplementation. La validation des implémentations empêche les adresses nulles sans code. Pour assurer la compatibilité avec OpenZeppelin, une fonction implementation() est incluse, qui retourne address(this).

L'utilisation de stockage est optimale dans les scénarios immuables après déploiement. L'absence de SlotStorage simplifie le code mais limite les mises à jour du beacon.

Code complet de 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 Cela doit être remplacé par votre logique d'authentification personnalisée
    address public owner;

    /// @notice Stockage principal des implémentations
    mapping(bytes32 implId => address implAddress) internal implementations;

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

    // constructeur
    constructor (address _owner) {
        /// @dev Cela doit être remplacé par votre logique d'authentification personnalisée
        require (_owner != address(0));
        owner = _owner;
    }

    /// @notice Retourne l'adresse d'une implémentation enregistrée sous _referenceId. Si aucune référence n'est trouvée, retourne address(0).
    /// @param _referenceId ID unique du contrat référencé
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation) {
        return implementations[_referenceId];
    }

    /// @notice Enregistre l'implémentation _implementation sous le _referenceId donné.
    /// @param _referenceId ID unique du contrat référencé
    /// @param _implementation Adresse de l'implémentation
    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 Fonction spéciale pour simuler la fonctionnalité de beacon d’OZ
    /// @dev Existe uniquement pour la compatibilité avec OZ BeaconProxy
    /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation();
    /// @dev 1. cela doit retourner une adresse valide
    /// @dev 2. ET elle doit être un contrat : if (beaconImplementation.code.length == 0) { revert }
    /// @dev Problème similaire dans ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data),
    /// @dev ligne `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);`
    /// @dev Étant donné cela,
    function implementation() external view returns (address _current) {
        return address(this);
    }
}

NamedBeaconProxy : Proxy minimaliste upgradable

Hérite de Proxy et utilise Address d’OpenZeppelin. Champs immuables : beacon et implementationReferenceId — aucun emplacement de stockage utilisé.

Dans le constructeur :

  • Vérifie le beacon (code.length > 0).
  • Récupère l'implémentation depuis le beacon via le referenceId, vérifie code.length.
  • Appel optionnel delegatecall pour initialiser avec _data.

La fonction surchargée _implementation() retourne INamedBeacon(beacon).getImplementation(implementationReferenceId).

Google AdInline article slot

Prend en charge receive() pour les appels payables, lance une erreur NonPayable si msg.value > 0 sans _data.

Code complet de 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 {
        // définir le beacon
        if (_beacon.code.length == 0) {
            revert InvalidBeacon(_beacon);
        }
        beacon = _beacon;

        // définir l'ID de référence de l'implémentation
        address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId);
        if (implementationFromBeacon.code.length == 0) {
            revert InvalidImplementation(implementationFromBeacon);
        }
        implementationReferenceId = _implementationReferenceId;

        // initialiser si des données sont fournies
        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();
        }
    }
}

Flux de déploiement et d'utilisation

  • Déployer NamedBeacon avec un propriétaire.
  • Déployer les contrats d'implémentation.
  • Enregistrer : registerImplementation(keccak256('v1'), implementationAddress).
  • Déployer NamedBeaconProxy(beacon, referenceId, initData).

Les vérifications de validation dans le proxy peuvent être désactivées pour économiser du gaz lors des appels. La logique de propriétaire peut être personnalisée dans un contrat dérivé de NamedBeacon.

Principaux avantages

  • Stockage centralisé des adresses d'implémentation via des IDs bytes32 réduit la duplication de code.
  • Références immuables dans le proxy éliminent l'utilisation de slots de stockage.
  • Taille minimale du proxy : pas de logique d'admin, juste un delegatecall vers le beacon.
  • Compatibilité complète avec OpenZeppelin : prend en charge implementation() pour upgradeBeaconToAndCall.
  • Conçu pour la production : Solidity 0.8.33, Hardhat 3.1.6, avec couverture de tests complète.

— Editorial Team

Advertisement 728x90

Lire ensuite