Este guia completo indicará estratégias para minimizar o uso de gás em seus contratos inteligentes Ethereum.
O domínio deste tópico é fundamental porque os altos custos de implantação podem afetar a adoção da blockchain e os custos de execução podem impedir os usuários de interagir com seu aplicativo descentralizado (DApp).
Consolidamos insights de várias fontes confiáveis, creditados no final, para garantir que você tenha as táticas mais eficazes à sua disposição.
Essas dicas serão classificadas nas categorias D e E, com D representando estratégias para economizar nos custos de implantação e E focando em métodos para minimizar os custos de execução em seus contratos inteligentes.
Ponto #1: Use a Otimização do Solidity (D || E)
Em ambientes de desenvolvimento de blockchain como Hardhat, Truffle ou Foundry, existe a capacidade de ativar o otimizador e especificar o número de suas execuções.
// Em hardhat.config.js
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 1000, <------------------
},
},
},
};
Como explica a documentação oficial do Solidity, existe uma compensação entre o número de execuções e os custos associados de implantação e execução. Menos execuções podem resultar em custos de implantação mais baixos em detrimento de custos de execução mais elevados. Por outro lado, um número maior de execuções pode aumentar os custos de implantação, mas reduzir consideravelmente o custo de execução.
O número de execuções (
--optimize-runs
) especifica aproximadamente com que frequência cada opcode do código implantado será executado durante a vida útil do contrato. Isso significa que é um parâmetro de compensação entre o tamanho do código (custo de implantação) e o custo de execução do código (custo após a implantação). Um parâmetro “runs” (execuções) de “1” produzirá código curto, mas caro. Em contrapartida, um parâmetro “runs” maior produzirá um código mais longo, porém com maior eficiência de gás. — Documentação do Solidity — O Otimizador
Ponto #2: Use Palavras-Chave Imutáveis e Constantes (D && E)
Quando um projeto usa variáveis para armazenar dados, é aconselhável usar a palavra-chave constant se o valor for identificável durante o desenvolvimento do código e não aplicado a alterações dentro do ciclo de vida do contrato, ou a palavra- chave immutable se o valor for conhecido durante a implantação do contrato (para ser definido no construtor). Desta forma, o conteúdo da variável é armazenado no bytecode do contrato e não no armazenamento.
O custo de implantação pode ser ligeiramente reduzido através deste método, mas o mais importante é que reduz significativamente os custos de execução. Isso ocorre porque o valor da variável é recuperado diretamente do bytecode e não do armazenamento, economizando assim o gás associado à operação SLOAD, uma vez que evita o custoso processo de leitura do armazenamento.
Ponto #3: Exclua Variáveis Não Utilizadas (E)
Nas blockchains EVM, um reembolso de gás é concedido quando o espaço de armazenamento é liberado. O ato de excluir uma variável proporciona um grande reembolso de gás, limitado a metade da despesa de gás de toda a transação.
Usar a palavra-chave delete para remover uma variável é equivalente a definir a variável de volta ao seu valor inicial específico para seu tipo de dados, como zero para números inteiros ou bytes32(0) para bytes32.
Em breve terei outro artigo relacionado especificamente ao Reembolso de Gás, valores atuais e históricos.
Ponto #4: Variáveis de Armazenamento de Pacotes (D)
Os dados na EVM são armazenados em slots de memória de 256 bits. Dados menores que 256 bits estão contidos em um único slot, enquanto dados maiores são distribuídos em vários slots. Como a atualização de cada slot de armazenamento consome gás, o empacotamento de variáveis pode otimizar o uso de gás, reduzindo o número de slots a serem atualizados dentro de um contrato.
Por exemplo, o compilador Solidity só pode armazenar quatro variáveis uint64 nos mesmos slots de memória de 256 bits se estiverem compactadas. Caso contrário, cada variável uint64 ocupará um slot uint256 individual, desperdiçando assim o restante dos bits por slot.
Ponto #5: Evite Armazenar Dados Desnecessários na Cadeia (E)
Para casos em que os dados não são cruciais ou não requerem acessibilidade por outros contratos inteligentes, é aceitável e muitas vezes benéfico emitir estes dados na forma de um evento. A emissão de dados em eventos permite que eles sejam documentados nos blocos históricos da blockchain sem torná-los acessíveis a outros contratos inteligentes.
Os eventos podem não ser confiáveis no futuro para acessar valores históricos, mas sim acessar os dados instantaneamente, uma vez que a Ethereum pode introduzir a poda da cadeia. A poda (pruning) é um processo que envolve a exclusão de dados mais antigos para otimizar a utilização do espaço em disco. Ao implementar a poda incremental, os clientes Ethereum podem gerenciar com eficiência suas necessidades de armazenamento, ao mesmo tempo em que mantêm os dados históricos necessários para as operações de rede.
Ponto #6: Use Assembly (D && E)
Quando você compila um contrato inteligente Solidity, ele é transformado em uma série de opcodes EVM (máquina virtual Ethereum).
Com Assembly, você escreve código muito próximo ao nível do opcode. Não é muito fácil escrever código em um nível tão baixo, mas a vantagem é que você pode otimizar manualmente o opcode e superar o bytecode do Solidity em alguns casos.
Ponto #7: Use Calldata em Vez de Memory (E)
Para parâmetros de função, use calldata em vez de memory, pois, quando memory é usado, as variáveis são copiadas para a memória, tornando o tempo de execução mais caro. Porém, se calldata for usado, o conteúdo da variável não será copiado e será apenas lido a partir do calldata.
Ponto #8: Use Mapping em Vez de Arrays (E)
O Solidity oferece dois tipos de dados distintos, arrays e mappings (mapeamentos), para representar listas de dados. No entanto, a sua sintaxe e estrutura divergem significativamente.
Embora os mapeamentos tendam a ser mais eficientes e econômicos na maioria dos cenários, os arrays apresentam iterabilidade e capacidade de empacotamento. Consequentemente, a recomendação é empregar mapeamentos para lidar com listas de dados, a menos que as situações exijam iteração ou viabilidade de empacotamento de tipos de dados.
Ponto #9: Variáveis de Armazenamento em Cache Antes de Usá-las (E)
É essencial armazenar em cache as variáveis de armazenamento na memória antes de usá-las (usá-las várias vezes ou iterar através delas), pois cada vez que uma variável de armazenamento é lida ou gravada, ela usa SLOAD/SSTORE respectivamente.
Porém, quando o valor é armazenado em cache, ele usa MLOAD/MSTORE, que é bem mais barato que o mencionado acima.
Ponto #10: Use Proxies Mínimos EIP-1167 (D)
Quando se espera que um contrato seja implantado muitas vezes, espera-se que o contrato seja implantado usando o padrão Proxies. Um dos padrões proxy mais baratos em termos de economia de implantação é a EIP-1167 Minimal Proxy, onde um contrato terá um bytecode mínimo que delega quaisquer calldata (dados de chamada) recebidos para o contrato de implementação codificado.
Nota: O uso de proxies da EIP-1167 será um pouco mais caro em termos de custo de tempo de execução, pois cada vez que uma chamada é feita, o custo de delegatecall é adicionado ao contrato de implementação.
Ponto #11: Use Erros Personalizados (D)
Erros personalizados foram introduzidos no Solidity 0.8.4, como um substituto para erros de string, que são mais caros.
Ponto #12: Limite a String de Erros a Menos de 32 Caracteres (D)
Se forem usados erros de string, e não erros personalizados, uma maneira de otimizar o uso dos erros de string é tornar a string de erro menor que 32 caracteres.
Ponto #13: Use Unchecked (E)
Após o Solidity 0.8.0, verificações integradas foram introduzidas no compilador para evitar overflow e underflow. Essas verificações custam gás, portanto, quando o desenvolvedor tem certeza de que a variável não atingirá o valor que a fará overflow ou underflow, é possível agrupar esse código em um bloco não verificado (unchecked).
Por exemplo, para loops que usam uint256 para incrementar, não é possível atingir o valor máximo de uint256. Dessa forma, você pode agrupar o iterador em um bloco não verificado.
Ponto #14: Use ++i em Vez de i++ (E)
++i _usa menos gás que _i++. No entanto, ambos retornam valores diferentes na execução.
Ponto #15: Use Vyper (E)
É importante notar que o uso do Vyper muitas vezes pode gerar código de execução altamente otimizado, às vezes até ultrapassando o nível de otimização do código escrito em Yul, a linguagem Assembly do Solidity. Vyper é uma linguagem de programação Python orientada a contratos voltada para a Máquina Virtual Ethereum (EVM).
https://twitter.com/0xz80/status/1674891842020622340?
Conselho:
- Use um registrador de gás para controlar o custo do gás do código que você escreve.
- Cuidado com o limite de tamanho de código definido no EIP-170.
- Nem toda otimização escrita online é verdadeira; o compilador às vezes otimiza para alguns casos extremos.
Conecte-se comigo no Github, Twitter e LinkedIn.
Este artigo foi escrito por OxAdnan e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Latest comments (0)