El patrón UUPS (Universal Upgradeable Proxy Standard) es una estrategia empleada en Ethereum para dotar de capacidad de actualización a los contratos inteligentes. Este enfoque se basa en la segregación de las responsabilidades entre un contrato proxy y uno o varios contratos de lógica.
Una ventaja distintiva del patrón UUPS radica en que las funciones de actualización residen dentro del contrato de lógica. Esto mitiga el riesgo de colisiones potenciales entre los selectores de función de la operación de actualización y las funcionse definidas en el contrato proxy.
Conceptos Clave del Patrón UUPS
- Contrato Proxy: Su función principal es recibir y redirigir todas las invocaciones de transacciones hacia el contrtao de lógica correspondiente.
- Contrato de Lógica: Alberga la implementación del código de negocio. Puede ser reemplazado por nuevas versiones de contratos de lógica para efetcuar las actualizaciones.
- Función de Actualización: Cada contrato de lógica incluye una función específica diseñada para gestionar el proceso de actualización.
Ejemplo Práctico
Contrato Proxy
pragma solidity ^0.8.0;
contract UUPSProxy {
address public implementation; // Dirección del contrato de lógica
address public admin; // Dirección del administrador
string public dataStorage; // Almacenamiento de datos, modificable vía lógica
constructor(address _initialImplementation) {
admin = msg.sender;
implementation = _initialImplementation;
}
// Función fallback para delegar llamadas al contrato de lógica
receive() external payable {
_callImplementation();
}
fallback() external payable {
_callImplementation();
}
function _callImplementation() private {
(bool success, bytes memory result) = implementation.delegatecall(msg.data);
require(success, "Delegatecall failed");
}
}
Contratos de Lógica
pragma solidity ^0.8.0;
// Versión inicial del contrato de lógica UUPS
contract LogicV1 {
address public implementation; // Debe coincidir con el proxy
address public admin; // Debe coincidir con el proxy
string public dataStorage; // Debe coincidir con el proxy
// Función para modificar el estado y ser llamada vía proxy
// Selector: 0x12345678 (ejemplo)
function updateData(string memory _newData) public {
dataStorage = _newData;
}
// Función de actualización. Cambia la dirección del contrato de lógica.
// Requiere autorización del administrador. Selector: 0x87654321 (ejemplo)
function upgradeTo(address newImplementation) external {
require(msg.sender == admin, "Only admin can upgrade");
implementation = newImplementation;
}
}
// Nueva versión del contrato de lógica UUPS
contract LogicV2 {
address public implementation; // Debe coincidir con el proxy
address public admin; // Debe coincidir con el proxy
string public dataStorage; // Debe coincidir con el proxy
// Función para modificar el estado y ser llamada vía proxy
// Selector: 0x12345678 (ejemplo, el mismo que V1)
function updateData(string memory _newData) public {
dataStorage = string(abi.encodePacked("V2 Updated: ", _newData));
}
// Función de actualización. Cambia la dirección del contrato de lógica.
// Requiere autorización del administrador. Selector: 0x87654321 (ejemplo, el mismo que V1)
function upgradeTo(address newImplementation) external {
require(msg.sender == admin, "Only admin can upgrade");
implementation = newImplementation;
}
}