WEB3DEV

Cover image for Técnicas de Otimização de Gás de Contratos Inteligentes
Panegali
Panegali

Posted on

Técnicas de Otimização de Gás de Contratos Inteligentes

Toda vez que uma transação é enviada para a blockchain, taxas de gás devem ser pagas. A quantidade de gás está relacionada com a quantidade de computação que a transação requer, ou seja, a quantidade de computação que a EVM terá que realizar para executar a transação (caso a transação não envolva a EVM, uma simples transferência de Ether por exemplo, a quantidade de gás é fixa).

Você pode projetar e implementar seus contratos inteligentes para serem eficientes em termos de gás. Existem dois “ tipos ” de gás sobre os quais falarei neste blog:

  • Gás de Transação: A quantidade de gás que seus usuários terão que pagar toda vez que interagirem com seu contrato inteligente. A ideia aqui é implementar funções eficientes de gás que consumam o mínimo de gás possível.
  • Gás de implantação: a quantidade de gás que você terá que pagar toda vez que implantar seus contratos inteligentes. A implantação do contrato inteligente é algo que geralmente acontece apenas uma vez, mas ainda assim economizar gás pode ser interessante para você.

Às vezes, técnicas para reduzir um tipo de gás podem fazer com que o outro tipo de gás aumente, essa é uma compensação com a qual você terá que lidar…

1

Esta é uma lista de coisas que você deve ter em mente ao trabalhar em seus contratos inteligentes para economizar gás.

  • Minimize dados on-chain (eventos, IPFS, contratos sem estado, provas de merkle)
  • Minimize as operações on-chain (strings, valor de armazenamento de retorno, looping, armazenamento local, lotes)
  • Locais de memória (calldata, pilha, memória, armazenamento)
  • Ordenação de variáveis
  • Tipos de dados preferidos
  • Bibliotecas (incorporadas, implantadas)
  • Proxy mínimo
  • Construtor
  • Tamanho do contrato (mensagens, modificadores, funções)
  • Otimizador do compilador Solidity

Minimize os dados on-chain

Economizar dados na memória de armazenamento é caro, se você conseguir reduzir ao mínimo a quantidade de informações que precisa armazenar na blockchain, estará economizando muito gás de transação.

  • Eventos: você pode considerar o uso de eventos para “armazenar” dados na blockchain. Um evento é uma informação que realmente será armazenada na blockchain, só que não fará parte do armazenamento do seu contrato, na verdade, não será possível que contratos inteligentes leiam ou usem eventos de forma alguma. Os eventos estão disponíveis apenas para aplicativos off-chain que leem a blockchain. É por isso que os eventos não devem ser usados ​​se o seu contrato inteligente exigir essas informações, mas se você só precisar que os dados sejam persistidos na blockchain apenas para fins de leitura.
  • IPFS: Caso você precise salvar arquivos (documentos, vídeos, …) de forma descentralizada, considere o IPFS (um armazenamento de arquivos distribuído e barato). Cada arquivo armazenado no IPFS terá um ID exclusivo que você pode armazenar na blockchain para referência, mas o arquivo real será armazenado no IPFS.
  • Contratos sem estado: Se você só precisa usar a blockchain como um banco de dados descentralizado para armazenar alguns dados “simples”, como pares de chave/valor ou similares, você pode usar o que é chamado de contrato sem estado. A ideia é implantar um contrato com funções que definem alguns parâmetros de entrada, mas não armazena nenhum dado no armazenamento. Os usuários invocarão os métodos que passam os parâmetros de entrada como parte dos dados da transação. As transações serão armazenadas para sempre na blockchain, o que significa que você sempre poderá ler de um aplicativo off-chain o conteúdo dos dados das transações (que contém os parâmetros de entrada). A desvantagem aqui é que você precisará implementar um back-end robusto capaz de rastrear e extrair esses valores da blockchain. Os eventos são mais fáceis de rastrear, filtrar e extrair, mas são mais caros.
  • Provas de Merkle: Se você precisar usar a blockchain para verificar se alguma informação é válida ou não, você pode usar provas de merkle. Uma prova merkle usa um único pedaço de dados para provar a validade de uma quantidade muito maior de dados. A ideia é que você só precisará armazenar a raiz da árvore de Merkle na blockchain (Hash12345678) para poder validar várias transações (Tx1 …. Tx8). Se por exemplo alguém quiser provar a validade “Tx4”, ela precisará fornecer Tx4, Hash3, Hash12 e Hash5678, então seu contrato poderá recalcular a raiz de Merkle (Hash12345678) e verificar se ela corresponde à armazenada na blockchain. Você não precisará armazenar os hashes de todas as transações.

2

Minimize as operações on-chain

Apenas adicione aos seus contratos inteligentes a funcionalidade que, por segurança, legal ou qualquer outro motivo muito bom, precisa ser executada na blockchain. Mantenha todas as tarefas restantes off-chain, em um back-end dedicado ou até mesmo no seu front-end, dessa forma você economizará gás de transação.

  • Strings: strings são apenas bytes para a ethereum. Mesmo que os dois tipos de dados existam, a EVM processará strings como bytes, o que requer alguma sobrecarga, o que significa que, se você puder usar bytes em vez de strings, faça isso. Se você ainda precisar usar strings, tente manter as operações de string (concatenação etc.) fora de seus contratos inteligentes.
  • Retornar valores de armazenamento: se você precisar retornar valores de armazenamento após executar alguma funcionalidade. Retorne-o como está, sem transformá-lo, deixe o aplicativo off-chain recuperando os dados fazer o trabalho (extrair certos valores de uma matriz, etc…).
  • Looping: evite fazer loops por longas matrizes, não só vai custar muito gás, mas pode até tornar seu contrato impossível de ser executado se os custos do gás aumentarem muito (além do limite de gás do bloco). Em vez disso, use mapeamentos que são tabelas de hash que permitem acessar qualquer valor em uma única operação usando sua chave, em vez de percorrer uma matriz até encontrar a chave que está procurando.
  • Armazenamento local: variáveis ​​de armazenamento local são variáveis ​​locais de método que apontam para uma variável de estado real (armazenada no armazenamento). Em vez de copiar/colar matrizes de armazenamento na memória para manipulá-las e, em seguida, copiá-las de volta para o armazenamento, basta usar variáveis ​​de armazenamento local e trabalhar diretamente no armazenamento.
  • Batching: em vez de fazer com que seus usuários invoquem a mesma função várias vezes com valores diferentes (enviando várias transações para a blockchain), dê a eles a possibilidade de passar matrizes de tamanho dinâmico para que possam executar a mesma funcionalidade em uma única transação. Isso permitirá que eles economizem alguns custos indiretos.

Locais de memória

A Ethereum tem 4 locais de memória, do mais barato ao mais caro: calldata, pilha, memória e armazenamento. Se usado corretamente, você economizará muito gás de transação.

  • Calldata: disponível apenas para parâmetros de entrada que são tipos de dados de referência (matrizes, string, …) de funções externas. Os argumentos calldata são somente leitura, mas se você tiver alguns tipos de referência que precisa passar para o seu método, sempre considere a localização da memória calldata, pois é a mais barata.
  • Pilha: disponível apenas para tipos de valor definidos em um método.
  • Memória: É a memória RAM volátil que será removida no momento em que a EVM terminar. Você pode usá-la para armazenar tipos de dados de referência e é mais barato que o armazenamento. Ao passar argumentos para outras funções ou declarar variáveis ​​temporariamente em sua função, sempre use memória, a menos que você precise usar armazenamento estritamente.
  • Armazenamento: o local de memória **mais caro. **Os dados de armazenamento são mantidos na blockchain e, conforme declarado no primeiro elemento desta lista, você deve sempre minimizar os dados on-chain.

Ordenação de variáveis

Os slots de armazenamento do Solidity têm 32 bytes, mas nem todos os tipos de dados ocupam essa quantidade de espaço: bool, int8 … int128, bytes1 … bytes31 e os endereços ocupam menos de 32 bytes.

O compilador do solidity tentará empacotar variáveis ​​em um único slot, mas essas variáveis ​​precisam ser definidas próximas umas das outras.

Por exemplo, se você definir 2 int128 próximos um do outro, ambos serão empacotados no mesmo slot de armazenamento, pois ocupam 16 bytes cada. No entanto, se você definir um int128, seguido por um unit256, então outro int128, você estará usando 3 slots de armazenamento, pois o unit256 entre os 2 int128 precisa de um slot de armazenamento completo.

3

4

Você poderá economizar espaço de armazenamento e gás de transação fazendo isso.

Tipos de dados preferidos

Se você for definir variáveis ​​que ocuparão um slot de armazenamento completo, é melhor usar variáveis ​​que realmente ocupem o slot de armazenamento completo.

Vamos explicar com um exemplo.

Nosso contrato inteligente requer apenas uma variável de estado, um inteiro não assinado que nunca será maior que 255. Seremos tentados a usar uint8 como o tipo de dados. O problema é que os códigos de operação (opcodes) ethereum são projetados para usar variáveis ​​de 256 bits (tamanho da pilha EVM), enquanto o uint8 leva apenas 8 bits, a EVM preencherá os bits restantes com “0” para poder manipulá-lo. Essa adição “0” realizada pela EVM custará gás, o que significa que, para economizar gás de transação, é melhor usar uint256 em vez de uint8.

Bibliotecas

Se você for reutilizar o código entre seus contratos inteligentes, é melhor empacotar todo esse código em uma biblioteca, implantá-lo e fazer com que seus contratos apontem para ele importando-o.

As bibliotecas podem ser de dois tipos.

  • Bibliotecas Embutidas: bibliotecas que contêm funcionalidade interna. Essas bibliotecas não são implantadas, mas incorporadas ao seu contrato, o que significa que você implantará o código delas junto com o código do contrato inteligente… Você não reutilizará nada nem economizará gás com esse tipo de biblioteca…
  • Bibliotecas implantadas: bibliotecas que contêm funcionalidade pública ou externa. Essas bibliotecas são implantadas uma vez, então todos os contratos inteligentes que os importam, estarão delegando chamadas para eles. Isso significa que o código da biblioteca é implantado apenas uma vez e usado por todos os contratos inteligentes. Você economizará Gás de implantação se usar esse tipo de biblioteca.

Proxies Mínimos (ERC 1167)

Se você precisar implantar vários contratos com exatamente a mesma funcionalidade, considere o uso de “proxies mínimos” (definidos no ERC 1167).

Um proxy mínimo é apenas um contrato que delegará todas as suas chamadas para um contrato de implementação pré-definido, nada mais. Já existe um código de byte bem definido que representa o código compilado do contrato de proxy mínimo, você simplesmente precisará inserir o endereço do contrato de implementação nele e estará pronto para implantar quantas cópias do seu proxy mínimo precisar.

Como esse código de byte é tão mínimo, o custo de implantação é o mais baixo possível, você economizará muito gás de implantação.

Há uma ressalva com proxies mínimos que você deve ter em mente: O endereço do contrato de implementação de proxy mínimo não pode ser alterado, o que significa que você não poderá migrar o código deles.

Construtor

O método construtor é executado apenas uma vez, durante a criação do contrato, mas se você conseguir simplificá-lo, estará economizando gás de implantação.

  • Constantes acima de imutáveis: variáveis ​​de estado constantes e imutáveis ​​não podem ser alteradas após a implantação do contrato. A diferença é que a variável constante deve ser definida em tempo de compilação, enquanto imutável pode ser definida dentro do construtor. Sempre tente usar constantes para tornar o construtor mais barato.

Tamanho do contrato

Os custos de implantação do contrato dependem de várias coisas, uma delas é o tamanho do contrato que você está implantando (em KB, lembre-se de que há um limite de 24 KB para contratos únicos).

Uma maneira simples de reduzir o gás de implantação, é implementar contratos tão pequenos quanto possível.

  • Registros/Mensagens: torne as mensagens de reversão e afirmação o mais curtas possível.
  • Modificadores: o código do modificador é embutido, o que significa que ele é adicionado no início e no final da função que ele modifica. Um truque para reduzir o tamanho do contrato ao usar modificadores é escrever uma função que implemente a lógica do modificador e fazer com que o modificador invoque essa função. Dessa forma, o código que implementa a funcionalidade do modificador não será replicado, apenas a invocação da função. Essa técnica só funciona se o mesmo modificador for usado várias vezes.

5

  • Funções: tente usar o mínimo de códigos de operação possível ao implementar sua funcionalidade. Isso nem sempre é possível ou mesmo tão eficiente em termos de gás porque alguns códigos de operação são mais caros que outros, você pode estar economizando gás de implantação, mas aumentando o gás de transação…

Otimizador do compilador Solidity

Não se esqueça de ativar o otimizador de gás do compilador solidity ao compilar seu código antes da implantação. Esses recursos informam ao compilador para otimizar o código de byte que será gerado e implantado na blockchain para reduzir a implantação e o gás de transação.

No geral, o otimizador tenta simplificar expressões complicadas, o que reduz o tamanho do código e o custo de execução. Ele também especializa ou funções_ inline_. Especialmente a função inlining é uma operação que pode ocasionar um código muito maior, mas geralmente é feita porque resulta em oportunidades para mais simplificações.


Artigo escrito por Alberto Molina e traduzido por Marcelo Panegali.

Top comments (0)