Gráfico Atualizado do Padrão Proxy
Motivação
Como sempre, ao trabalhar em um novo espaço, como o desenvolvimento de infraestrutura e aplicativos de blockchain, sempre aprendo algo novo e fico surpreso com a falta de informações on-line sobre os padrões atuais para alguém que precisa aprender rapidamente.
Portanto, para usar meus próprios aprendizados e experiências e contribuir com o módulo Web3 Brownie Phyton, decidi escrever alguns códigos, juntamente com algumas explicações e comparações, para que qualquer pessoa possa começar a implantar proxies de padrões variados mais rapidamente.
O objetivo é ser um guia para que alguém aprenda rapidamente os conceitos e métodos relevantes para implementá-los. Não se trata de uma base de código otimizada ou pronta para produção.
Código do Github
Escrevi uma biblioteca simples usando três padrões que serão discutidos a seguir. Mantive a simplicidade e mostrei exemplos de contratos implementados. Fique à vontade para deixar comentários e/ou discussões!
https://github.com/Harsh-Gill/brownie-proxy-factory-mix/tree/main
Conceitos importantes a serem conhecidos
- Delegate Call
A função Delegate Call é definida em (https://solidity-by-example.org/delegatecall/). É uma função muito poderosa que nos permite ter proxies. Essencialmente, podemos ter um contrato A que usa o código do contrato B para ser executado em seu próprio contexto.
Isso significa que agora podemos ter contratos atualizáveis, pois podemos executar a lógica do contrato A a partir do contrato B ou C ou D... e assim por diante!
- Função Initialize (SEM CONTRATOS DE CONSTRUTOR)
Ao executar um padrão de proxy, os construtores não são muito úteis, já que as informações dos construtores não são oficialmente armazenadas on-chain. Isso significa que um contrato A que precise de um construtor do contrato B não conseguirá obter essas informações. Uma solução simples para isso é definir uma função "semelhante a um construtor" e chamá-la de initialize() . Simplesmente, essa função é definida como um "inicializador", fazendo o mesmo trabalho que um construtor faria, mas na verdade sendo uma função.
- Layout de Armazenamento
O layout de armazenamento é sobre como os contratos inteligentes do Solidity armazenam suas variáveis. Uma grande complexidade e perigo do padrão DelegateCall é que, se o contrato A executar o código do contrato B, ele precisará ter a mesma estrutura de armazenamento de variáveis ou uma estrutura semelhante para executar a função corretamente e não misturar as variáveis. Essa não é uma tarefa fácil e requer um planejamento cuidadoso. Se o contrato A e o contrato B armazenarem variáveis diferentes no mesmo local, isso poderá criar conflito e causar consequências indesejadas.
Entendendo os Contratos Clonáveis
Os contratos clonáveis são fundamentalmente muito mais simples de entender e criam uma base melhor para entender os Proxies. Portanto, vamos tratar disso primeiro.
Um contrato Factory é um padrão em que implantamos um contrato de lógica principal, denominado Implementation (Implementação), e sobre o qual podemos implantar inúmeros "Clones" desse Implementation com propriedades exclusivas, como um nome exclusivo ou qualquer outro parâmetro definível.
Um exemplo gráfico é apresentado a seguir :
Assim, uma implementação inicial é implantada, sobre a qual um Factory pode implantar vários contratos shell que assumem a lógica da implementação.
Isso pode reduzir significativamente os custos de implantação de contratos, pois o contrato lógico (que é a parte mais cara) é implantado apenas UMA VEZ e os clones baratos que contêm apenas informações como o nome podem ser implantados inúmeras vezes.
Esse é um padrão comum no mundo atual da Blockchain. Por exemplo, o Uniswap Pools usa exatamente esse padrão Factory para ter inúmeros pools que são todos exclusivos, mas que dependem de uma única lógica. Para isso, eles também contam com um padrão Proxy Factory.
Um Contrato Factory não precisa ser atualizável, embora possa ser definido como tal, o que veremos mais adiante na seção sobre Proxies Beacon.
Entendendo os Contratos Atualizáveis
Pensar em contratos inteligentes atualizáveis pode parecer surpreendente, uma vez que a blockchain é frequentemente apontada como um sistema imutável (permanente). Isso ainda é verdade, mas, com alguma lógica nova, podemos fazer com que os contratos implantados se tornem atualizáveis sem deixar de ser imutáveis.
O código do contrato inteligente por si só NÃO é atualizável. No entanto, o uso de um padrão proxy nos permite alcançar a capacidade de atualização.
Esse padrão proxy é uma solução simples e genial. Essencialmente, implantamos dois contratos em vez de um.
Implantamos um contrato chamado Proxy e outro chamado Implementation. O Proxy é com o qual os usuários interagem e o Implementation é onde o Proxy obtém sua lógica. Uma visualização gráfica seria assim:
Gráfico de Padrão Proxy
Portanto, para que um usuário acesse as funções A ou B, ele terá que chamá-las por meio do Proxy. É nesse ponto que a incrível capacidade de atualização pode ser adicionada. Se quiséssemos adicionar uma nova função C, poderíamos criar um novo contrato e simplesmente alterar o ponteiro do nosso Proxy para o novo Implementation! Isso é mostrado abaixo:
Gráfico de Atualização do Padrão Proxy
Assim, na verdade, não atualizamos nenhum código, mas atualizamos um ponteiro para obter o código de um novo local atualizado!
Esse padrão pode ser implementado de várias maneiras e, principalmente, exploraremos os seguintes padrões Proxy:
- Proxy Atualizável Transparente
Esse é um dos padrões mais simples. Geralmente, ele usa 3 contratos. Esses contratos são o Admin (opcional para incluir), o Proxy e a Implementation.
O contrato Admin (administrador) é usado para tratar todas as atualizações do Proxy para um novo Implementation. Ele usa o contrato definido pelo OpenZeppelin : https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/ProxyAdmin.sol
Neste ponto, o Proxy e o Implementation estão claros.
Prós
Esse talvez seja o design de proxy mais simples e limpo, o que, por si só, já é uma grande vantagem. Além disso, ele é relativamente mais seguro devido à presença do Contrato Admin (no entanto, isso nem sempre é verdade, pois o próprio admin pode ser um vetor de ataque). A inclusão adicional do contrato Admin foi para garantir a segurança e evitar que qualquer conta que não seja admin encontre uma vulnerabilidade e possa atualizar o contrato.
Contras
O contrato Proxy será sempre DelegateCall se o endereço de origem não for o contrato do administrador, o que, em essência, evita que qualquer contrato de endereço que não seja do administrador chame as funções de nível admin. No entanto, isso chega a um custo de consumo maior de gas, já que TODA CHAMADA será verificada primeiro para ver se é da Admin ou não, o que pode acumular com o tempo, já que essa ação exige a leitura do armazenamento e, portanto, aumenta o gas.
- Proxy UUPS
O padrão Proxy UUPS foi uma solução criada para reduzir os custos de gas e também transferir a capacidade de atualização para a própria lógica de implementação. O Proxy UUPS não usa o padrão de primeiro verificar se cada usuário é admin ou não. O que é uma grande vantagem, no atual ecossistema blockchain, em que as taxas de gas são muito caras. Além disso, o recurso agregado é que podemos atualizar o contrato e, quando estivermos satisfeitos, podemos realmente tornar o próprio contrato NÃO ATUALIZÁVEL em algum ponto escolhido.
Prós
O padrão UUPS pode, então, ser visto como uma alternativa potencialmente mais barata e mais poderosa. Além disso, podemos escolher que as atualizações subsequentes se tornem de fato não atualizáveis. O que pode ser desejável para confiança 0 ou qualquer outro motivo específico.
Contras
No entanto, isso traz mais complexidade e perigos, pois é preciso ter cuidado ao gerenciar o padrão, já que a capacidade de atualização pode ser danificada (permanentemente). Além disso, ao usar o UUPS, precisaremos alterar nosso próprio Contrato Lógico para herdar dele. Isso significa que precisaremos de algum nível de reescrita para integrar um contrato e torná-lo compatível com o UUPS.
- Proxy Beacon
O Padrão Proxy Beacon é mais complicado e requer um conhecimento mais profundo do Solidity e das infraestruturas de DApp.
Essencialmente, ele consiste em um contrato Factory, Clones, Proxy Beacon e Implementation.
O Factory é capaz de implantar clones leves que dependem do Proxy Beacon para obter a lógica.
Prós
Esse padrão é excelente e muito superior ao implantar contratos que dependem de uma lógica semelhante, mas que têm alguns parâmetros diferentes. Ele pode reduzir significativamente o custo, já que implantamos apenas um Implementation e um proxy beacon, após o que podemos implantar constantemente proxies de baixo custo de gas que podem obter a lógica da implementação.
Contras
Esse padrão requer uma sólida compreensão de vários componentes, heranças de propriedade e também é relativamente rígido, o que significa que é difícil alterá-lo depois de implantado e que todos os clones devem se basear em um padrão semelhante. No entanto, também é possível fazer com que todos os contratos possam ser atualizados (incluindo Factory e Beacon). No entanto, novamente, isso tem o custo da complexidade e dos possíveis problemas/hacks.
Código Python (Web3 Brownie)
Escrevi uma biblioteca que implementa esses padrões.
Basicamente, há um contrato chamado SimpleContract
Ele tem as seguintes funções
- initialize
- getValue
Em seguida, esse contrato será atualizado para SimpleContractV2
- initialize
- getValue
- setValue (new upgraded function)
O código do Github é baseado no Brownie.
Os contratos e os scripts têm três subpastas principais que documentam a implantação do padrão e o uso de cada um deles.
Para executá-los em um Padrão Proxy Transparente:
brownie run scripts/transparent_upgradeable_proxy/deploy.py
Para executar o Padrão UUPS:
brownie run scripts/uups_proxy/deploy.py
Para executar o proxy Beacon Factory:
brownie run scripts/beacon_proxy/deploy.py
Comparando Custo de Gas e Função (Muito breve!)
Essa é uma comparação muito superficial entre os diferentes padrões. Na realidade, o aplicativo em si pode influenciar muito esses números e há vários outros fatores a serem considerados - portanto, considere essas comparações com cautela (se houver grande interesse, posso fazer uma análise detalhada desses padrões e de seus custos de gas). No entanto, segue abaixo um resumo simples:
Custo da Chamada de Contrato Simples
Implante um contrato simples => ~ 94635 Gas
- setValue => 22542 gas
Padrão Proxy Transparente
Custos de Gas adicionais :
Contrato Admin => ~ 441875 Gas
Contrato Proxy => ~ 628 329 Gas
Chamada Overhead Extra :
- setValue => (~ + 6500)
Padrão Proxy UUPS
Custos de Gas adicionais :
Contrato Proxy => ~ 270 827 Gas
Contrato Implementation => 603 785 Gas
Chamada Overhead Extra :
- setValue => (~ + 5500)
Padrão Proxy Factory Beacon
Custos de Gas adicionais :
Custo Factory => 714 558 Gas
Custo Beacon => 287 611 Gas
Chamada Overhead Extra:
- setValue => + 1708 do beacon atualizável
Esse artigo foi escrito por Harsh Winder e traduzido por Fátima Lima. O original pode ser lido aqui.
Latest comments (0)