Padrões de Proxy para Atualização de Contratos Inteligentes

Padrão Proxy Transparente

O modelo transparente separa claramente as responsabilidades: o proxy gerencia o acesso e as atualizações, enquanto o contrato de implementação contém a lógica de negócio.

contract ProxyTransparente {
    address private enderecoLogica;
    address private enderecoGestor;
    
    fallback() external payable {
        require(msg.sender != enderecoGestor, "Gestor nao pode chamar");
        
        (bool sucesso, ) = enderecoLogica.delegatecall{value: msg.value}(msg.data);
        if (!sucesso) {
            assembly {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }
    }
    
    function migrar(address novaLogica) external {
        if (msg.sender != enderecoGestor) {
            // Redireciona para fallback se nao for gestor
            (bool s, ) = enderecoLogica.delegatecall(msg.data);
            require(s);
        }
        enderecoLogica = novaLogica;
    }
}

Padrão UUPS (Universal Upgradeable Proxy Standard)

Aqui o proxy é minimalista, contendo apenas o endereço da implementação. A lógica de upgrade vive no próprio contrato de implementação, que através do delegatecall modifica o estado do proxy.

// Proxy UUPS - Estrutura simplificada
contract ProxyUUPS {
    address public implementacao;
    
    fallback() external payable {
        address alvo = implementacao;
        require(alvo != address(0), "Implementacao invalida");
        
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let resultado := delegatecall(gas(), alvo, ptr, calldatasize(), 0, 0)
            let tamanho := returndatasize()
            returndatacopy(ptr, 0, tamanho)
            
            switch resultado
            case 0 { revert(ptr, tamanho) }
            default { return(ptr, tamanho) }
        }
    }
}

// Mixin para contratos UUPS
abstract contract ModuloUpgradeUUPS {
    address internal contaAdmin;
    address internal slotImplementacao;
    
    function _atualizar(address proximaVersao) internal {
        require(msg.sender == contaAdmin, "Sem permissao");
        // Modifica o slot do proxy (delegatecall context)
        slotImplementacao = proximaVersao;
    }
}

Padrão Diamante

Esta arquitetura avançada permite que um único proxy delegue para múltiplos contratos de implementação (facetas) com base nos seletores de função, superando limites de tamanho e permitindo modularidade extrema.

Comparação de Características

  • Proxy Transparente: Maior simplicidade conceitual, mas custo de gas elevado em cada chamada devido à verificação de permissões.
  • UUPS: Gas otimizado para operações, porém exige cuidado para não perder a capacidade de upgrade.
  • Diamante: Escalabilidade máxima e flexibilidade, ao custo de complexidade significativa.

Soluções da OpenZeppelin

A biblioteca fornece implementações prontas:

  • TransparentUpgradeableProxy - Proxy transparente com segurança aprimorada
  • UUPSUpgradeable - Mixin para contratos seguindo o padrão UUPS

Detalhe Crucial: Inicialização

Em contratos upgradeáveis, evite constructors. Utilize funções initialize com modificadores initializer. O constructor executa apenas no contexto do contrato de implmeentação, enquanto o estado efetivo pertence ao proxy. Inicializar via constructor consome gas desnecessariamente e não afeta o estado usado pelos usuários.

Tags: solidity proxy-pattern uups transparent-proxy diamond-pattern

Publicado em 9-27 04:27