WEB3DEV

Cover image for Proxy - Imutabilidade de variável
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Proxy - Imutabilidade de variável

26 de setembro de 2023

A tecnologia blockchain é fundada em algumas ideias: segurança, abertura, igualdade e imutabilidade. Vamos nos concentrar nesta última palavra. Quando criamos uma transação, e ela for aceita dentro do bloco, as informações dentro nunca serão alteradas. Essa transação pode ser qualquer coisa: criação de um novo contrato inteligente, interação com outro contrato ou envio de Ether para alguém, etc. - não faz diferença. Todas as informações salvas na blockchain estarão lá para sempre. E graças a isto, todo usuário da blockchain pode dormir tranquilo, sem medo de que alguém mude as regras do jogo e eles não percebam.

Mas essa é uma faca de dois gumes. Se ninguém é capaz de alterar o conteúdo da blockchain, você também não. Mesmo que você queira mudar alguma coisa dentro do seu contrato inteligente que você implantou e, depois disso, notou um bug. Desculpe, no seu contrato sempre existirá o bug, a menos que você crie um novo contrato inteligente sem esse bug e o implante. Mas o contrato inteligente com erro ainda permanecerá na blockchain. Mas isso não deve ser um problema, não é? A resposta é: sim e não. Quando este contrato não é relevante, por exemplo, você o criou quando aprendeu Solidity, não é um problema. Mas quando é importante, porque esse contrato é parte de um grande projeto e, por esse bug, alguém poderia destruir o projeto inteiro, é um problema enorme e nós gostaríamos de resolvê-lo rapidamente. Mas como, quando não podemos?

Solução número 1 - Migração

A única solução é criar um novo contrato sem bug e implantá-lo. Depois disso, informe a todos, que todos devem usar esse novo contrato. Fácil, não é mesmo? Não exatamente, vamos olhar para o impacto potencial desse movimento. Imagine que o contrato era muito popular e tinha muitos usuários, que codificaram rigidamente o endereço dele dentro de seu próprio contrato. Quando implantarmos um novo contrato para substituir o antigo, todos eles terão que alterar o endereço dentro de seus contratos. Como alteramos endereços codificados rigidamente? Claro, através da escrita de um novo contrato e implantação do mesmo. Você vê? É como uma avalanche. No início, é apenas uma alteração de um contrato, mas tem potencial para causar um grande impacto de insegurança. Por que insegurança? Porque, por exemplo, pode ter alguém que não percebeu a mudança de endereço.

Solução número 2 - Proxy

Então, qual é a outra solução? Deixe-me apresentar a você, um proxy. Da maneira mais fácil, o proxy é um contrato inteligente que possui um endereço de um segundo contrato inteligente (chame-o de Logic). O usuário, quando quiser utilizar alguma função do Logic, envia a transação com informações sobre o nome da função e o valor de envio, e assim sucessivamente, para o endereço do Proxy. Teoricamente, o Proxy não tem uma função com o mesmo nome que está na Logic, então, as transações do usuário vão para o fallback do Proxy. Existe uma delegatecall (chamada delegada) implementada para o Logic. Como o Proxy usa delegatecall, todas as mudanças de estado serão salvas no armazenamento do Proxy, não dentro do Logic.

# A ideia do proxy.

Quando alguém encontrar um bug dentro do Logic, ou alguém desejar implementar uma nova função, não há uma maneira fácil de mudar isso. Como isso pode ser feito? Apenas substituindo o endereço do Logic dentro do Proxy. Graças a isso, o usuário continuará usando o mesmo endereço (o endereço do Proxy), mas poderá usar uma nova versão do Logic.

A ideia de Proxy é elegante e bonita, mas existem alguns problemas.

Problemas com o Proxy

O uso não apropriado de delegatecall leva a uma brecha de segurança, por isso é muito importante ter o mesmo conjunto de variáveis dentro do Proxy e do Logic. A assinatura tem apenas 4 bytes, então é plausível que algumas funções possam ter a mesma assinatura. O problema é chamado de Colisão de Armazenamento (Storage Collision). Para evitá-lo, usamos uma das três formas principais de organização do armazenamento do Proxy: armazenamento herdado (Inherited Storage), armazenamento eterno (Eternal Storage) e armazenamento desestruturado (Unstructured Storage). Escreverei sobre todos eles nos meus próximos posts.

A próxima dificuldade com o Proxy é a não possibilidade de usar a constructor (função construtora) para inicializar o valor inicial. Por que? Porque a constructor define o valor no armazenamento do contrato que tem essa constructor, não dentro do Proxy, de onde lemos o valor. Portanto, mesmo que nós definíssemos o valor pela constructor, o proxy não o notaria. Para evitar isso, poderíamos usar a initializer (função inicializadora). É uma função que pode ser chamada apenas uma vez e contém toda a informação que normalmente escrevemos dentro da constructor. Além disso, escrever dentro do Logic algo como uint256 public InitValue = 42, não inicializará o valor, porque a definição do valor assim é alterada pelo compilador para definir o valor pela constructor. Portanto, poderíamos colocar todo esse tipo de operação na initializer.

Como mencionei, o Proxy usa o método fallback, onde é implementada a delegatecall, que usa a função da Lógica. Sabemos que a EVM (Ethereum Virtual Machine, ou Máquina Virtual Ethereum) usa assinaturas para escolher a função chamada. E se o Proxy e o Logic tem uma função com o mesmo nome? Como a EVM saberá qual delas ela deve chamar? Esse problema é chamado de conflito de seletores de Proxy.

Neste momento, existem 3 dos padrões de Proxy mais populares: o padrão de proxy transparente (The Transparent Proxy Pattern), o padrão de proxy atualizável universal (​​Universal Upgradeable Proxy Standard) e o padrão de diamante (Diamond Pattern)​. Vou mostrá-los em meus próximos posts. Todos eles são criados para evitar o choque de seletores de Proxy.

Esse artigo foi escrito por Eszymi e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)