WEB3DEV

Cover image for TODOS os Métodos EXTREMOS de Otimização de Gás do Solidity (x30)
Panegali
Panegali

Posted on

TODOS os Métodos EXTREMOS de Otimização de Gás do Solidity (x30)

Neste artigo, mostrarei TODAS as técnicas conhecidas para otimizar o gás em suas chamadas e implantação de contratos inteligentes.

  • Veremos qual método é útil e qual é inútil. (ou evento prejudicial)
  • Veremos quanto gás você pode economizar usando cada método.
  • Classificarei todos os métodos usando uma lista de níveis. (A = excelente, D = inútil/prejudicial)
  • Vou tentar ser o mais exaustivo possível.

Então vamos lá!

1

Otimização de implantação x Otimização de chamada.

A princípio, você precisa saber que existem 2 formas de otimizar o gás em um dAPP.

1. Otimização dos custos de implantação

  • Você pode otimizar o custo para implantar um contrato inteligente.
  • O custo = 21.000 gás (para criar a transação) + 32.000 gás (para criar um contrato) + EXECUÇÃO DO CONSTRUTOR + BYTES IMPLANTADOS * 78.125
  • Portanto, para implantar um contrato inteligente, você gastará pelo menos 53.000 gás.
  • Para minimizar os custos de gás, você precisa minimizar o tamanho de um contrato inteligente e o custo do construtor.

2. Otimize cada chamada de função.

  • Você também pode otimizar o custo de chamar cada função em seu contrato inteligente.
  • O custo é igual a 21000 gás (para criar a transação) + o custo de execução da função.
  • Assim, o objetivo será reduzir o custo de execução da função.

Observe que esses 2 meios de otimização não são os mesmos!

Muitas vezes, você pode implantar um código Solidity maior (portanto, com maior custo de gás de implantação), mas mais otimizado para cada chamada de função. (Assim, com um menor custo de gás de chamada)

Se você precisar, escolha qual tipo de otimização precisará usar.

Eu recomendo fortemente que você otimize cada chamada de função, pois a implantação é feita apenas uma vez e a experiência do usuário será aprimorada. (a menos que o tamanho do seu contrato inteligente exceda o limite de 24 KB)

Classificação dos métodos de otimização de gás

Classificarei todos os métodos de otimização de gás usando uma lista de níveis. (Porque há uma diferença entre economizar 2 gás em uma transação de 21.000 gás e economizar 10.000 gás.)

Nível A: Se você não conhece essas regras, não é um VERDADEIRO desenvolvedor Solidity.

Nível B: Essencial, deve ser conhecido por todos os desenvolvedores.

Nível C: pode ser útil em algum momento, deve ser conhecido pelos desenvolvedores também.

Nível D: Inútil/nocivo, não os use (a menos que você saiba o que está fazendo). Você poderá perder, ou o seu tempo ou a segurança do contrato inteligente.

2

Nível A: indispensável

No nível A, surpreendentemente, NÃO há nenhuma dica e truque “fácil” para economizar gás, mas ao invés disso…

A1: Saber quanto custa cada código de operação (opcode) EVM.

Aqui estão os 6 dos códigos de operação de gás mais caros (entre os mais usados nos contratos inteligentes):

  • CREATE/CREATE2: Implanta um contrato inteligente. (32000 gás)
  • SSTORE: Armazena um valor no armazenamento. (20000 gás se o slot não foi acessado, 2900 caso contrário.)
  • EXTCODESIZE: Obtém o tamanho em bytes de um código de contrato inteligente. (2600 se não foi acessado antes, 100 gás caso contrário)
  • BALANCE: (address(this).balance), o mesmo que EXTCODESIZE.
  • SLOAD: Acessa um valor no armazenamento. (2100 gás se não foi acessado antes, 100 gás caso contrário)
  • LOG4: Cria um evento com 4 tópicos. (1875 gás)

(Esta é de fato uma estimativa. Algumas delas podem variar dependendo da situação.)

O melhor site que descreve quanto custa cada código de operação é https://www.evm.codes/, e está muito bem explicado.

Se você souber disso, entenderá melhor Solidity e terá uma intuição “melhor” de quanto custa cada linha de código.

A2: Use o modificador “view”

Isto é bastante óbvio, mas ainda vejo alguns “devs” não marcando as funções necessárias como view…

function getBalance() external view {
   return balance;
}
Enter fullscreen mode Exit fullscreen mode

Você pode facilmente reduzir seus custos de gás para 0 ao chamar o contrato inteligente se não gravar no armazenamento. (além disso, você não precisa esperar de 5 a 20 segundos pela resposta)

A3: Compreender a linguagem Solidity

De forma mais geral, você precisa entender como Solidity funciona, isso permitirá que você não dependa de tutoriais online para economizar gás!

Felizmente, em comparação com outros idiomas:

  • Rust
  • C++
  • Javascript

Solidity é MUITO fácil, há um número muito limitado de coisas para aprender.

Por exemplo, é possível aprender 100% da linguagem Solidity em cerca de 1 a 2 anos, mas mesmo em 100 anos você não dominará totalmente C++.

A4: Tornar-se bom em ciência da computação/algoritmos.

Você precisa saber como diferentes algoritmos funcionam, como eles podem ser otimizados e qual é a “complexidade”.

Por exemplo, digamos que você queira ordenar uma matriz. (que surge muitas vezes)

[4,5,108,3,7,1,94,15,99,34,0,24,5,4]
Enter fullscreen mode Exit fullscreen mode

Como você programa isso?

Existem dezenas de algoritmos de classificação diferentes: https://www.geeksforgeeks.org/sorting-algorithms/

Alguns deles são mais eficientes do que outros em alguns casos de uso…

Você precisa escolher o melhor dependendo da situação e não é tão fácil. Por exemplo, “quickSort” geralmente é a maneira mais rápida de classificar uma matriz com uma complexidade de O(nlog(n))

3

4. Nível B: Útil

Estas dicas e truques podem economizar uma boa quantidade de gás e devem ser implementados o máximo possível.

NOTA IMPORTANTE ANTES DE COMEÇAR:

  • Como a função not_optimized() está situada antes da função Optimized() no código de byte.
  • Chamar Optimized() custa mais do que not_optimized() com o mesmo código (22 gás a mais), então subtrairei 22 gás em cada chamada para a função Optimized().

B1: Operações em lote (Gás economizado: 21.000 gás * transações em lote)

O envio de uma transação sozinha na blockchain custa muito gás (21.000 gás para ser mais preciso); portanto, se você encontrar uma maneira de agrupar transações para seus usuários, poderá economizar uma boa quantidade de gás.

B2: Alterar as ordens do slot de armazenamento (20.000 gás economizados na implantação)

O armazenamento Ethereum é composto por slots de 32 bytes, o problema é que escrever é caro. (Até 20000 gás usando escrita “fria”)

Digamos que você tenha 3 variáveis:

uint128 a; //Slot 0 (16 bytes)
uint256 b; //Slot 1 (32 bytes)
uint128 c; //Slot 2 (16 bytes)
Enter fullscreen mode Exit fullscreen mode

Eles usam 3 slots de armazenamento diferentes

uint256 b; //Slot 0 (32 bytes)
uint128 a; //Slot 1 (16 bytes)
uint128 c; //Slot 1 (16 bytes)
Enter fullscreen mode Exit fullscreen mode

Mas se eu alinhar bem os 3 slots, consigo economizar 1 slot. (as variáveis ​​a e c estarão no mesmo slot)

Portanto, existe 1 slot a menos usado na implantação. (Portanto, 20.000 gás economizado)

Além disso, se você quiser acessar a variável c, tendo a variável b já está acessada, ele contará como um acesso morno (acessando um slot de armazenamento já acessado), portanto, custará 1.000 em vez de 2.900 de gás, o que é bastante significativo.

Se você não entender o armazenamento, a documentação pode ajudá-lo: https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html

B3: Use o otimizador (Economize gás: 10–50% na implantação e chamada)

Você pode usar o otimizador integrado da Solidity

Esta é uma maneira muito simples de economizar muito combustível sem aprender novos conceitos. Você precisa marcar a caixa de seleção "enable optimization".

4

Um valor próximo a 1 otimizará o custo do gás, mas um valor próximo ao máximo (2³²-1) otimizará as chamadas para a função.

Na maioria dos casos, você pode usar o valor padrão (200)

B4: use mapeamento em vez de matriz (5000 gás economizados por valor)

Mapeamentos geralmente são menos dispendiosos do que as matrizes, mas você não pode iterá-las.

5

função not_optimised(), utilização de gás: 48409 gás

função optimised(), utilização de gás: 43400 gás

B5: Use require ao invés de assert

Assert NÃO deve ser usado por outro meio que não seja para fins de teste. (porque quando a asserção falha, o gás NÃO é reembolsado ao contrário do require)

B6: use self destruct (autodestruição) (economize até 24.000 gás)

A função selfdestruct () destrói o contrato inteligente e devolve 24.000 gás.

Em uma transação mais complexa, como a implantação de outro contrato inteligente, você pode chamar selfdestruct () no contrato inteligente para economizar gás.

B7: Faça menos chamadas externas (valor economizado: variável)

Faça chamada para outro contrato apenas quando for obrigado a fazer.

B8: Procure código morto (economiza uma quantidade variável de gás na implantação)

Às vezes, os desenvolvedores esquecem de remover códigos inúteis como:

require(a == 0)
if (a == 0) {
 return true;
} else {
 return false
}
Enter fullscreen mode Exit fullscreen mode

Este é um exemplo de despejo, pois mostra que alguns desenvolvedores podem esquecer de remover partes inúteis.

C7: utilize variáveis ​​imutáveis ​​(economiza 15.000 gás na implantação)

utilize immutable para variáveis ​​de armazenamento sempre que possível.

6

contrato not_optimised, uso de gás: 116800 gás

contrato optimised, uso de gás: 101013 gás

C5: armazenamento de dados em eventos (até 20.000 gás economizados na chamada de função)

Se você não precisa acessar os dados na cadeia em Solidity, você pode armazená-los usando eventos.

pragma solidity ^0.8.0;
contract Test {
address store;
   event Store(uint256 indexed key,address indexed data);
   function optimised() external {
      emit Store(1,0xaaC5322e456d45E7b6c452038836C5631C2AeBc0);
   }
function not_optimised() external {
      store = 0xaaC5322e456d45E7b6c452038836C5631C2AeBc0;
   }
}
Enter fullscreen mode Exit fullscreen mode

função not_optimised(), utilização de gás: 43353 gás

função optimised(), utilização de gás: 22747 gás

5. Nível C: Por que não, mas não mudará sua vida

Estas dicas podem economizar uma boa quantidade de gás e não custar nada.

C1: Use o tipo de tamanho estático em Solidity (cerca de 50 gás economizados)

Tipos de tamanho estático (como bool, uint256, bytes5) são mais baratos que tipos de tamanho dinâmico (como string ou bytes)

7

função not_optimised(), uso de gás: 21255 gás

função optimised(), uso de gás: 21227 gás

C2: Acesso frio vs acesso quente (70 gás economizado)

Não acesse 2 vezes a mesma variável de armazenamento.

8

função not_optimised(), uso de gás: 23412 gás

função optimised(), uso de gás: 23347 gás

Não economiza muito, graças a EVM que já otimiza o acesso frio.

C3: usando calldata em vez de memória (450 gás economizado por chamada)

9

função not_optimised(), uso de gás: 22442 gás

função optimised(), uso de gás: 21994 gás

C5: Use eventos indexados (62 gás economizados na chamada de função por tópico)

Você pode marcar cada evento como indexado conforme abaixo:

O evento indexado permite que os eventos sejam pesquisados ​​mais facilmente.

contract Test {
   event Testa(address a,address b);
   event Testa2(address indexed a,address indexed b);

   function not_optimised() external {
       emit Testa(0x..., 0x...);
   }
   function optimised() external {
       emit Testa2(0... ,0...);
   }
}
Enter fullscreen mode Exit fullscreen mode

Aqui a função optimised() usa 135 menos gás do que not_optimised().

C6: Alterar a ordem das chamadas (20–2000 gás economizado por chamada)

Como disse na nota importante:

Podemos chamar todos os contratos de um contrato inteligente, você fornece em msg.data a assinatura da função. (os primeiros 4 bytes da função hash keccak256).

Primeiramente, a EVM fará uma “troca” dessa assinatura, para ver qual função executar.

switch(msg.data[0:4]) { // comparar a assinatura
   case 0x01234567: go to functionA;
   case 0x11111111: go to functionB; // custa mais 22 gás..
   case 0x4913aaaa: go to functionC; // custa mais 22+22 gás...
   ...
}
Enter fullscreen mode Exit fullscreen mode

Comparando com o cálculo da assinatura, use um pouco de gás (22 gás). Você precisa colocar a função mais chamada no primeiro lugar de switch(), garantindo que a assinatura esteja no primeiro lugar (alterando seu nome)

Você pode aprender mais sobre este par em meus artigos EVM reversíveis: https://trustchain.medium.com/reversing-and-debugging-evm-smart-contracts-392fdadef32d

C7: use i++ ao invés de i = i+1 (62 gás economizado em cada chamada)

Isso soa como uma piada, mas parece funcionar.

10

função not_optimised(), uso de gás: 21401 gás

função optimised(), uso de gás: 21339 gás

C8: Use uint256 em vez de uintXX no armazenamento (55 gás economizado por chamada)

Para gravar/acessar dados no armazenamento, a EVM usa slots de 32 bytes (= 256 bits), usando tipos menores que uint256. A EVM precisa executar operações adicionais para garantir a conversão.

Portanto, é melhor usar uint256 em vez de uint8 para armazenar dados.

11

função not_optimised(), uso de gás: 43353 gás

função optimised(), uso de gás: 43298 gás

C9: Crie um erro personalizado (24 gás economizado por chamada)

Você pode criar erros personalizados em Solidity e reverter estes erros, como abaixo.

12

função not_optimised(), uso de gás: 21476 gás

função optimised(), uso de gás: 21474 gás

C10: Trocar 2 variáveis ​​usando uma tupla (a, b) = (b, a) (economiza 5 gás em cada chamada)

13

Eu o coloquei no nível C porque a notação é muito mais legível.

função not_optimised(), uso de gás: 21241 gás

função optimised(), uso de gás: 21236 gás

6. Nível D: Inútil/prejudicial

As dicas de otimização de gás são inúteis, não perca seu tempo para economizar uma pequena quantidade de gás que será ofuscada de qualquer maneira pela transação de 21000 gás.

D1: usando unchecked (não verificado) (economia de gás: 150/operação)

Desde o Solidity 0.8.0, operações como + * / — são verificadas sempre que houver um overflow/underflow.

Mas custa um pouco de gás para verificar se há um overflow/underflow

A menos que você esteja fazendo muitas operações em uma única chamada de função (como em loops), você NÃO deve usar unchecked.

14

função not_optimised(), uso de gás: 21419 gás

função optimised(), uso de gás: 21253 gás

D2: alterando e otimizando o byte-code você mesmo

Não faça isso a menos que você tenha um bom motivo para fazê-lo. Algumas das funções podem acabar com um comportamento indefinido e problemas de segurança.

Em vez disso, use o otimizador ou implemente uma política de teste muito rígida.

D3: Excluir mensagens de string de erros (cerca de 78.125 gás economizado no custo de implantação por caracteres)

Não faça isso, o código pode ser mais difícil de depurar para você e para os usuários.

Por exemplo, alguém pode substituir:

require(account != address(0), "ERC20: cunhar para o endereco zero");
Enter fullscreen mode Exit fullscreen mode

por:

require(account != address(0));
Enter fullscreen mode Exit fullscreen mode

D4: usando assembly de baixo nível (economia de gás: variável)

Se não sabe o que está fazendo, NÃO utilize o Solidity em modo Assembly. Você acabará tendo muito mais chances de ter uma falha de segurança em seu contrato por uma quantidade marginal de gás economizado.

D5: use funções externas em vez de públicas

Surpreendentemente, não há diferença entre funções externas e públicas

15

função not_optimised(), uso de gás: 21379 gás

função optimised(), uso de gás: 21401 gás (-22 = 21379)

D6: Use deslocamento à esquerda em vez de multiplicação >> (150 gás economizado)

Deslocar um dado binário n vezes para a esquerda resulta na multiplicação dos dados por 2^n. Aqui está um exemplo:

00000001 = 1 dec
00000010 = 2 dec
00000100 = 4 dec
00001000 = 8 dec
Enter fullscreen mode Exit fullscreen mode

16

função not_optimised(), uso de gás: 21615 gás

função optimised(), uso de gás: 21436 gás

Os ganhos não são ruins, mas o operador >> não verifica se há um overflow.

D7: Excluir hash de metadados (3.000–5.000 gás na implantação)

Em cada código de byte de contrato inteligente, um “hash” é anexado no final.

Este é o hash de todos os metadados do contrato inteligente. (Incluindo abi, comentários, código…)

Como o hash de metadados tem 32 bytes de comprimento, você pode economizar muito gás.

Mas isso é muito difícil de fazer, porque você tem que faze-lo à mão.

Portanto, pode ser perigoso se for mal executado.

D8: Adicionar payable (pagável) a cada função (24 gás economizado por chamada)

Adicionando payable a uma função, remova a verificação em msg.value. Portanto, algum gás é economizado.

pragma solidity ^0.8.0;
contract Test {
address store;
   function optimised() external payable {
   }
function not_optimised() external {
   }
}
Enter fullscreen mode Exit fullscreen mode

função not_optimised(), uso de gás: 21186 gás

função optimised(), uso de gás: 21184 gás

Não é prejudicial, mas também não é muito útil.

D9: fazendo o trabalho importante fora da cadeia.

Tenha cuidado com o trabalho que você está fazendo fora da cadeia (como em JS)

Por exemplo, você não pode fazer verificação de segurança fora da cadeia no front-end do site, isso é muito perigoso, pois qualquer pessoa pode alterar o código JS no navegador e enviar entradas incorretas para o contrato inteligente.

SEMPRE FAÇA A VERIFICAÇÃO DE SEGURANÇA NA CADEIA.

Conclusão

Eu cobri 90% das técnicas de otimização de gás em Solidity, se você já sabe, como elas funcionam e como você pode economizar uma quantidade não desprezível de gás, parabéns!

Só não caia na armadilha de gastar muito tempo para economizar um monte de gás, você também precisa aplicar a otimização do tempo à sua vida!


Artigo escrito por TrustChain e traduzido por Marcelo Panegali

Latest comments (0)