WEB3DEV

Cover image for Otimização de Gás em Contratos Inteligentes Ethereum: 10 Melhores Práticas
Panegali
Panegali

Posted on

Otimização de Gás em Contratos Inteligentes Ethereum: 10 Melhores Práticas

As Taxas de gás na rede principal Ethereum têm sido uma preocupação persistente, especialmente em momentos de congestionamento de rede. Alta demanda por espaço de bloco significa altas taxas de transação para os usuários. Portanto, é crucial otimizar contratos inteligentes e reduzir o consumo de gás durante a fase de desenvolvimento do contrato. A otimização de gás pode levar a custos de transação mais baixos e maior eficiência, proporcionando, em última análise, uma experiência blockchain mais acessível e econômica para os usuários.

Este artigo oferece uma visão geral do mecanismo de gás da Máquina Virtual Ethereum (EVM), conceitos-chave associados à otimização de gás, bem como melhores práticas para otimização de gás ao desenvolver contratos inteligentes.

Uma Breve Introdução ao Mecanismo de Gás da EVM

Em redes compatíveis com a Máquina Virtual Ethereum (EVM), "gás" refere-se à unidade que mede o esforço computacional necessário para executar operações específicas.

O diagrama a seguir ilustra o layout da Máquina Virtual Ethereum. Nele, podemos ver que o consumo de gás é dividido em três partes: execução de operações, chamadas de mensagem externa e leitura e escrita na memória e armazenamento.

Fonte: Ethereum.org

Como cada transação requer recursos computacionais para ser executada, taxas são cobradas para evitar loops infinitos e ataques de negação de serviço (DoS). O pagamento necessário para concluir uma transação é chamado de "taxa de gás".

Após a implementação da EIP-1559 (a atualização London hardfork), a taxa de gás é calculada pela seguinte fórmula:

Taxa de gás = unidades de gás utilizadas * (taxa base + taxa de prioridade)
Enter fullscreen mode Exit fullscreen mode

A taxa base é queimada (removida permanentemente da oferta) e a taxa de prioridade serve como um incentivo para os validadores adicionarem transações à blockchain. Ao definir uma taxa de prioridade mais alta ao enviar uma transação, aumenta-se a probabilidade dela ser incluída no próximo bloco. Isso funciona como uma espécie de "gorjeta" do usuário para o validador.

Compreendendo a Otimização de Gás na EVM

Quando um contrato inteligente é compilado em Solidity, ele é convertido em uma série de "códigos de operação", também conhecidos como opcodes.

Qualquer fragmento específico de opcode (por exemplo, criar contratos, fazer chamadas de mensagem, acessar armazenamento de conta e executar operações na máquina virtual) possui um custo universalmente acordado em termos de gás, que é registrado no Ethereum yellow paper.

Após diversas Propostas de Melhoria da Ethereum (EIPs), alguns desses custos de gás foram alterados e podem ter se desviado do Ethereum yellow paper. Para obter as informações mais recentes sobre os custos associados a cada opcode, consulte aqui.

O Conceito Fundamental por Trás da Otimização de Gás

O conceito fundamental por trás da otimização de gás é priorizar ações eficientes em termos de custo e evitar aquelas dispendiosas em relação aos custos de gás nas blockchains da EVM.

Operações baratas na EVM incluem:

  • Leitura e escrita de variáveis de memória;
  • Leitura de constantes e variáveis imutáveis;
  • Leitura e escrita de variáveis locais;
  • Leitura de variáveis calldata, como arrays e structs calldata;
  • Chamadas internas de funções.

Operações caras incluem:

  • Leitura e escrita de variáveis de estado armazenadas no armazenamento do contrato;
  • Chamadas externas de funções;
  • Loops.

Melhores Práticas para Otimização de Gás na EVM

Com base no conceito fundamental mencionado acima, compilamos uma lista de melhores práticas para otimização de gás para referência da comunidade de desenvolvedores. Ao seguir essas práticas, os desenvolvedores podem reduzir o consumo de gás de seus contratos inteligentes, diminuir os custos de transação e criar aplicativos mais eficientes e amigáveis ao usuário.

1: Minimize o Uso de Armazenamento

Em Solidity, o armazenamento é um recurso finito e é muito mais caro em termos de uso de gás em comparação com a memória. Cada vez que um contrato inteligente lê ou escreve no armazenamento, ele incorre em custos significativos de gás.

Conforme definido no Ethereum yellow paper, operações de armazenamento são mais de 100 vezes mais caras do que operações de memória. OPcodes como mload e mstore custam apenas 3 unidades de gás, enquanto operações de armazenamento como sload e sstore custam pelo menos 100 unidades, mesmo na situação mais otimista.

Algumas maneiras de limitar o uso de armazenamento incluem:

  • Armazenar dados não permanentes na memória;
  • Reduzir modificações no armazenamento salvando resultados intermediários na memória e atribuir resultados às variáveis de armazenamento apenas após a conclusão de todos os cálculos.

2: Compactação de Variáveis

O número de espaços de armazenamento utilizados e a forma como os desenvolvedores representam os dados no contrato inteligente afetam significativamente o uso de gás.

O compilador Solidity empacotará variáveis de armazenamento contínuas durante o processo de compilação, usando um slot de 32 bytes como a unidade base para armazenamento de variáveis. O empacotamento de variáveis significa organizar variáveis de modo que várias delas possam caber em um único slot.

À esquerda, há uma implementação inadequada que consumirá 3 slots de armazenamento. À direita, há uma implementação muito mais eficiente.

Fazendo esse pequeno ajuste, os desenvolvedores podem economizar 20.000 unidades de gás (custa 20.000 gás para armazenar um slot de armazenamento que não foi usado antes), já que agora será necessário apenas dois slots para o armazenamento.

Como cada slot de armazenamento tem um custo de gás, a compactação de variáveis ajuda a otimizar o uso de gás ao reduzir o número de slots necessários.

3: Economize em Tipos de Dados

Uma variável pode ser representada por vários tipos de dados, mas diferentes tipos de dados têm diferentes custos de operação. Escolher o tipo mais apropriado ajudará a otimizar o uso de gás.

Por exemplo, em Solidity, inteiros podem ser divididos em diferentes tamanhos: unit8, unit16, unit32 e assim por diante. Como a EVM executa operações em pedaços de 256 bits, usar uint8 significa que a EVM primeiro precisa convertê-lo para uint256. Essa conversão custa gás adicional.

Podemos usar o código deste exemplo para comparar o custo de gás para uint8 e uint256. A função UseUint() custa 120.382 unidades de gás, enquanto UseUInt8() custa 166.111 unidades de gás.

Isoladamente, usar uint256 é mais barato do que uint8 aqui. No entanto, também recomendamos anteriormente a compactação de variáveis. Se os desenvolvedores puderem agrupar quatro variáveis uint8 em um slot, o custo total de iterar por elas será mais barato do que quatro variáveis uint256. O contrato inteligente pode então ler/gravar um slot de armazenamento uma vez e colocar quatro variáveis uint8 na memória/armazenamento em uma única operação.

4: Use Variáveis de Tamanho Fixo em Vez de Dinâmicas

Recomenda-se usar o tipo de dados bytes32 em vez de bytes ou strings se os dados puderem caber em 32 bytes. Em geral, qualquer variável de tamanho fixo é menos cara do que uma de tamanho variável. Se o comprimento de bytes puder ser limitado, use a menor quantidade possível de bytes1 a bytes32.

5: Mapping vs Array

Em Solidity, listas de dados podem ser representadas usando dois tipos de dados: arrays e mappings. No entanto, a sintaxe e a estrutura deles são bastante diferentes.

Mappings são mais eficientes e geralmente menos dispendiosos, enquanto Arrays são iteráveis e empacotáveis. Portanto, é aconselhável utilizar mappings para gerenciar listas de dados, exceto quando a iteração é necessária ou é possível compactar tipos de dados.

6: Use Calldata em vez de Memory

Variáveis declaradas como parâmetros de função são armazenadas em calldata ou memory. Uma diferença chave entre memory e calldata é que memory pode ser modificada pela função, enquanto calldata é imutável.

Aqui, o princípio é usar calldata em vez de memory se o argumento da função for somente leitura. Isso evita cópias desnecessárias de calldata para memory na função.

Neste exemplo, que usa a palavra-chave memory, os valores do array são mantidos em calldata codificado e são copiados para memory durante a decodificação da ABI. O custo de execução para este bloco de código é de 3.694 unidades de gás.

No segundo exemplo, o valor é lido diretamente do calldata e não há operações intermediárias de memória. Esse ajuste resulta em um custo de execução de apenas 2.413 unidades de gás para este bloco de código, marcando uma melhoria de 35% na eficiência de gás.

7: Use Palavras-chave Constantes/Imutáveis Sempre que Possível

Variáveis Constantes/Imutáveis não são armazenadas no armazenamento do contrato. Essas variáveis são avaliadas durante o tempo de compilação e são armazenadas no bytecode do contrato. Portanto, são muito mais baratas de acessar em comparação com o armazenamento e é recomendável usar as palavras-chave Constant / Immutable sempre que possível.

8: Use o Modificador Unchecked Quando o Under/Overflow é Impossível

Quando os desenvolvedores têm certeza de que uma operação aritmética não resultará em overflow ou underflow, eles podem usar a palavra-chave unchecked introduzida no Solidity v0.8.0 para evitar verificações redundantes de underflow/overflow e economizar nos custos de gás.

No exemplo abaixo, a variável i nunca pode ter um overflow devido à restrição condicional i < length. Aqui, length é definido como uint256, o que significa que o valor máximo que i pode atingir é max(uint) - 1. Portanto, incrementar i dentro de um bloco não verificado é considerado seguro e mais eficiente em termos de gás.

Além disso, a biblioteca SafeMath não é mais necessária para versões do compilador 0.8.0 e superiores, pois a proteção contra overflow e underflow agora está incorporada ao próprio compilador.

9: Otimização de Modificadores

O código dos modificadores é colocado em uma função modificada e o código do modificador é copiado em todas as instâncias em que é usado. Isso aumentará o tamanho do bytecode e o uso de gás.

A seguir, uma maneira de otimizar o custo de gás do modificador.

Antes:

Depois

Neste exemplo, um refatoramento é feito na função interna _checkOwner(), permitindo que a função interna seja reutilizada em modificadores. Este método economiza tamanho de bytecode e custo de gás.

10: Curto-Circuito

No caso dos operadores || e &&, a avaliação é curto-circuitada, o que significa que a segunda condição não é avaliada se a primeira condição já determinar o resultado da expressão lógica.

Para otimizar o uso de gás, organize as condições de forma que o cálculo menos dispendioso seja colocado primeiro. Isso pode potencialmente evitar a execução do cálculo mais caro.

Recomendações Gerais Extras

Remova Códigos Inúteis

Se um contrato possui funções ou variáveis que não são usadas, é aconselhável removê-las. Esta é a maneira mais direta de reduzir os custos de implantação do contrato e ajudar a manter o tamanho do contrato pequeno.

Aqui estão algumas recomendações práticas:

  • Utilize os algoritmos mais eficientes para realizar cálculos. Se os resultados de certos cálculos são usados diretamente no contrato, considere remover esses cálculos redundantes. Em essência, quaisquer cálculos não utilizados devem ser eliminados.
  • Na Ethereum, os desenvolvedores são recompensados com um reembolso de gás por liberar espaço de armazenamento. Se uma variável não for mais necessária, os desenvolvedores devem removê-la usando a palavra-chave 'delete' ou definindo-a como seu valor padrão.
  • Otimização de loops: evite operações de loop caras, combine loops se possível e mova cálculos repetidos para fora do loop.

Use Contratos Pré-Compilados

Contratos pré-compilados fornecem funções de biblioteca complexas, como criptografia e algoritmos de hash. Eles requerem menos gás porque o código não é executado na EVM. Em vez disso, ele é executado localmente no nó do cliente. Usar contratos pré-compilados pode economizar gás, reduzindo a quantidade de trabalho computacional necessária para executar um contrato inteligente. Alguns exemplos de contratos pré-compilados incluem o Algoritmo de Assinatura Digital de Curva Elíptica (ECDSA) e o algoritmo de hash SHA2-256. Ao utilizar esses contratos pré-compilados em contratos inteligentes, os desenvolvedores podem reduzir os custos de gás e melhorar a eficiência de sua aplicação.

Aqui está uma lista abrangente de contratos pré-compilados suportados pela rede Ethereum.

Use o Código in-Line Assembly

O In-Line Assembly permite que os desenvolvedores escrevam código eficiente de baixo nível que pode ser executado diretamente pela EVM sem a necessidade de códigos de operação caros do Solidity. O In-Line Assembly também permite um controle mais preciso sobre o uso de memória e armazenamento, o que pode reduzir ainda mais os custos de gás. Além disso, o In-Line Assembly pode ser usado para realizar operações complexas que seriam difíceis de alcançar apenas com o Solidity, proporcionando mais flexibilidade para otimizar o uso de gás.

O seguinte é um exemplo de código para economizar gás com o In-Line Assembly:

Aqui observamos que o segundo caso, que utiliza técnicas do In-Line Assembly, demonstra uma eficiência de gás muito melhor em comparação com o caso de uso padrão.

No entanto, o uso do In-Line Assembly também pode ser arriscado e propenso a erros, portanto, deve ser usado com cuidado e apenas por desenvolvedores experientes.

Use Soluções de Camada 2

Use soluções de Camada 2 para reduzir a quantidade de dados que precisa ser armazenada e computada na rede principal Ethereum.

Soluções de Camada 2, como rollups, sidechains e canais de estado, permitem a transferência do processamento de transações da cadeia principal Ethereum, resultando em transações mais rápidas e mais baratas. Ao agrupar um grande número de transações juntas, essas soluções reduzem o número de transações na cadeia, o que, por sua vez, reduz as taxas de gás. O uso de soluções de Camada 2 também pode melhorar a escalabilidade da Ethereum, permitindo que mais usuários e aplicativos participem da rede sem sobrecarregá-la.

Use Ferramentas e Bibliotecas de Otimização

Existem várias ferramentas de otimização disponíveis, como o otimizador solc, o otimizador de compilação do Truffle e o compilador Solidity do Remix.

Essas ferramentas podem ajudar a minimizar o tamanho do bytecode, remover código inativo e reduzir o número de operações necessárias para executar um contrato inteligente. Em combinação com outras bibliotecas de otimização de gás, como o "solmate", os desenvolvedores podem reduzir efetivamente os custos de gás e melhorar a eficiência de seus contratos inteligentes.

Conclusão

Otimizar o uso de gás é uma prática essencial para desenvolvedores minimizarem os custos de transação e melhorarem a eficiência dos contratos inteligentes em redes compatíveis com EVM. Ao priorizar ações eficientes em termos de custos, minimizar o uso de armazenamento, utilizar o In-Line Assembly e seguir outras melhores práticas discutidas neste artigo, os desenvolvedores podem reduzir efetivamente o consumo de gás de seus contratos.

No entanto, deve-se notar que durante o processo de otimização, os desenvolvedores devem ter cuidado para evitar a introdução de vulnerabilidades de segurança. O esforço para simplificar o código e reduzir o consumo de gás nunca deve comprometer a segurança inerente de um contrato inteligente.

Como uma empresa experiente em segurança de blockchain, a CertiK possui significativa expertise nessa área. Nossos serviços de auditoria de contratos inteligentes incluem recomendações de otimização de gás e garantem tanto desempenho quanto segurança robusta. Se os desenvolvedores estiverem buscando orientação especializada para encontrar o equilíbrio certo entre eficiência e segurança, eles deveriam considerar nos envolver para uma auditoria abrangente e profissional. Nossa equipe está comprometida em auxiliar os desenvolvedores a alcançar contratos inteligentes seguros, eficientes e de alto desempenho em todas as redes blockchain, incluindo EVM e outras.


Artigo escrito por Certik. Traduzido por Marcelo Panegali.

Latest comments (0)