Volver al inicio

NamedBeacon Proxy: optimización de proxy en Solidity

NamedBeacon y NamedBeaconProxy ofrecen una alternativa eficiente a BeaconProxy estándar. Almacenamiento centralizado de implementaciones por bytes32 ID minimiza el tamaño del proxy y la duplicación de código. Adecuado para producción con Solidity 0.8.33.

NamedBeacon y Proxy eficientes en Solidity sin código extra
Advertisement 728x90

NamedBeacon y Proxy: Implementación Optimizada para Contratos Solidity Actualizables

Los desarrolladores a menudo enfrentan ineficiencias con el BeaconProxy estándar: un contrato separado solo para almacenar una dirección de implementación, referencias duplicadas en immutable y storage, y lógica de administrador redundante en cada proxy. El par propuesto de NamedBeacon y NamedBeaconProxy elimina estos problemas, minimizando los costos de gas y el tamaño del bytecode.

NamedBeacon centraliza la gestión de direcciones de implementación usando identificadores bytes32 similares a cadenas. El proxy almacena únicamente referencias inmutables al beacon y al referenceId, obteniendo dinámicamente la implementación actual en tiempo de ejecución.

NamedBeacon: Registro Centralizado de Implementaciones

Este contrato implementa un mapeo (bytes32 => address) para almacenar direcciones. Dos funciones clave:

Google AdInline article slot
  • registerImplementation(bytes32 _referenceId, address _implementation) — registro, disponible solo para el propietario.
  • getImplementation(bytes32 _referenceId) — recupera la dirección por ID, devuelve address(0) si no se encuentra.

La administración es centralizada: las verificaciones de propietario se aplican en registerImplementation. La validación de implementación evita direcciones cero sin código. Para compatibilidad con OpenZeppelin, se incluye una función implementation() que devuelve address(this).

El uso de almacenamiento es óptimo en escenarios inmutables tras el despliegue. La ausencia de SlotStorage simplifica el código, pero limita las actualizaciones del beacon.

Código completo 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 Esto debe sobrescribirse con tu lógica personalizada de autenticación
    address public owner;

    /// @notice Almacenamiento principal de implementaciones
    mapping(bytes32 implId => address implAddress) internal implementations;

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

    // ctor
    constructor (address _owner) {
        /// @dev Esto debe sobrescribirse con tu lógica personalizada de autenticación
        require (_owner != address(0));
        owner = _owner;
    }

    /// @notice Devuelve la dirección de una implementación registrada bajo _referenceId. Si no se encuentra, devuelve address(0).
    /// @param _referenceId ID único del contrato referenciado
    function getImplementation(bytes32 _referenceId) external view returns (address _implementation) {
        return implementations[_referenceId];
    }

    /// @notice Registra la implementación _implementation bajo el _referenceId dado.
    /// @param _referenceId ID único del contrato referenciado
    /// @param _implementation Dirección de la implementación
    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 Función especial para simular funcionalidad de beacon de OZ
    /// @dev Existe solo para compatibilidad con OZ BeaconProxy
    /// @dev BeaconProxy.ctor() => ERC1967Utils.upgradeBeaconToAndCall(beacon, data) => _setBeacon(address newBeacon) => address beaconImplementation = IBeacon(newBeacon).implementation();
    /// @dev 1. esto debe devolver una dirección válida
    /// @dev 2. Y debe ser un contrato: if (beaconImplementation.code.length == 0) { revert }
    /// @dev Problema similar en ERC1967Utils.upgradeBeaconToAndCall(address newBeacon, bytes memory data),
    /// @dev en la línea `Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);`
    /// @dev Dado esto,
    function implementation() external view returns (address _current) {
        return address(this);
    }
}

NamedBeaconProxy: Proxy Minimalista Actualizable

Extiende Proxy y utiliza Address de OpenZeppelin. Campos inmutables: beacon y implementationReferenceId — no se usan ranuras de almacenamiento.

En el constructor:

  • Valida el beacon (code.length > 0).
  • Obtiene la implementación desde el beacon mediante referenceId, verifica code.length.
  • Llamada opcional delegatecall para inicializar con _data.

Sobrescribe _implementation() para devolver INamedBeacon(beacon).getImplementation(implementationReferenceId).

Google AdInline article slot

Soporta receive() para pagos, lanza error NonPayable si msg.value > 0 sin _data.

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

        // establecer ID de referencia de implementación
        address implementationFromBeacon = INamedBeacon(_beacon).getImplementation(_implementationReferenceId);
        if (implementationFromBeacon.code.length == 0) {
            revert InvalidImplementation(implementationFromBeacon);
        }
        implementationReferenceId = _implementationReferenceId;

        // inicializar si se proporciona datos
        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();
        }
    }
}

Flujo de Despliegue y Uso

  • Despliega NamedBeacon con un propietario.
  • Despliega contratos de implementación.
  • Registra: registerImplementation(keccak256('v1'), implementationAddress).
  • Despliega NamedBeaconProxy(beacon, referenceId, initData).

Las comprobaciones de validación en el proxy pueden desactivarse para ahorrar gas en llamadas. La lógica de propietario puede personalizarse en un contrato derivado de NamedBeacon.

Beneficios Clave

  • Almacenamiento centralizado de direcciones de implementación mediante IDs bytes32 reduce la duplicación de código.
  • Referencias inmutables en el proxy eliminan el uso de ranuras de almacenamiento.
  • Tamaño mínimo del proxy: sin lógica de administrador, solo delegatecall al beacon.
  • Compatibilidad total con OpenZeppelin: soporta implementation() para upgradeBeaconToAndCall.
  • Diseñado para producción: Solidity 0.8.33, Hardhat 3.1.6, con cobertura de pruebas completa.

— Editorial Team

Advertisement 728x90

Leer después