Pular para o conteúdo principal

Reduzir contratos para combater o limite de tamanho do contrato

solidezsmart contractsarmazenamentotruffle
Intermediário
Markus Waas
soliditydeveloper.com(opens in a new tab)
26 de junho de 2020
6 minutos de leitura minute read

Por que há um limite?

Em 22 de novembro de 2016(opens in a new tab) o fork Spurius Dragon introduziu a EIP-170(opens in a new tab) que adicionou um limite de tamanho do contrato inteligente de 24.576 kb. Para você como desenvolvedor de Solidity isso significa que quando você adiciona mais e mais funcionalidade ao seu contrato, em algum momento você alcançará o limite e quando implantado verá o erro:

Aviso: O código do contrato excede 24576 bytes (um limite introduzido no Dragão Purioso). This contract may not be deployable on Mainnet. Considere habilitar o otimizador (com um valor baixo de "execução"!), desligar as strings de reverter ou usar bibliotecas.

Este limite foi introduzido para impedir ataques de negação de serviço (DOS). Qualquer apelo a um contrato é relativamente barato. No entanto, o impacto de uma chamada de contrato para os nós da Ethereum aumenta de forma desproporcionada, dependendo do tamanho do código do contrato chamado (lendo o código do disco, pré-processando o código, adicionando dados à prova de Merkle). Sempre que você tiver uma situação em que o agressor requer poucos recursos para causar muito trabalho para os outros, você tem o potencial para ataques DOS.

Originalmente, tratava-se de um problema menor, porque um limite de tamanho natural do contrato é o limite de gas por bloco. Obviamente, um contrato precisa ser implementado dentro de uma transação que tenha todo o bytecode do contrato. Se você incluir apenas essa transação em um bloco, você pode usar todo esse gas, mas não é infinito. Desde a London Upgrade, o limite de gas de bloco tem sido capaz de variar entre 15M e 30M de unidades, de acordo com a demanda da rede.

Começando a luta

Infelizmente, não há maneira fácil de obter o tamanho do bytecode dos seus contratos. Uma ótima ferramenta para ajudá-lo é o plugin truffle-contract-size(opens in a new tab) se você estiver usando o Truffle.

  1. npm install truffle-contract-size
  2. Adicione o plugin ao truffle-config.js: plugins: ["truffle-contract-size"]
  3. Execute truffle rodando contract-size

Isso irá ajudá-lo a descobrir como suas mudanças estão afetando o tamanho total do contrato.

A seguir, analisaremos alguns métodos ordenados pelo seu potencial impacto. Pense nisso em termos de perda de peso. A melhor estratégia para alguém atingir o seu peso alvo (no nosso caso 24kb) é concentrar-se primeiro nos grandes métodos de impacto. Na maioria dos casos, só de ajustar a sua dieta já ajudará, mas às vezes é necessário de um pouco mais. Então você pode adicionar algum exercício (impacto médio) ou até suplementos (impacto pequeno).

Grande impacto

Separe os seus contratos

Esta deve ser sempre sua primeira abordagem. Como você pode separar o contrato em vários contratos menores? Geralmente isso te força a criar uma boa arquitetura para seus contratos. Os contratos menores são sempre preferidos por uma perspectiva de legibilidade de código. Para dividir contratos, pergunte a si mesmo:

  • Quais as funções que devem estar juntas? Cada conjunto de funções pode ser o melhor em seu próprio contrato.
  • Que funções não requerem leitura do estado do contrato ou apenas um subconjunto específico do estado?
  • Você pode dividir o armazenamento e a funcionalidade?

Bibliotecas

Uma maneira simples de mover o código de funcionalidade para longe do armazenamento é usando uma biblioteca(opens in a new tab). Não declarar as funções da biblioteca como internas, como essas, serão adicionadas ao contrato(opens in a new tab) diretamente durante a compilação. Mas se usarmos funções públicas, elas estarão então de fato, num contrato separado de biblioteca. Considere o uso de(opens in a new tab) para fazer o uso de bibliotecas mais convenientes.

Proxies

Uma estratégia mais avançada seria um sistema de procuração. As bibliotecas usam DELEGATECALL na parte traseira, que simplesmente executa a função de outro contrato com o estado do contrato de chamada. Confira esta postagem no blog(opens in a new tab) para saber mais sobre sistemas de proxy. Eles lhe dão mais funcionalidade, por exemplo, permitem a atualização, mas também adicionam muita complexidade. Eu não adicionaria aquelas apenas para reduzir os tamanhos dos contratos, a menos que fosse a sua única opção por qualquer motivo.

Médio impacto

Remover funções

Este deveria ser óbvio. Funções aumentam um pouco o tamanho de um contrato.

  • Externo: Frequentemente adicionamos muitas funções de exibição por motivos de conveniência. Está perfeitamente tudo bem até que você atinja o limite de tamanho. Então talvez queiram realmente pensar na eliminação de todos que não os absolutamente essenciais.
  • Interno: Você também pode remover funções internas/privadas e simplesmente inserir o código, desde que a função seja chamada apenas uma vez.

Evitar variáveis adicionais

Uma mudança simples assim:

1function get(uint id) returns (address,address) {
2 MyStruct memory myStruct = myStructs[id];
3 return (myStruct.addr1, myStruct.addr2);
4}
Copiar
1function get(uint id) returns (address,address) {
2 return (myStructs[id].addr1, myStructs[id].addr2);
3}
Copiar

faz diferença de 0.28kb. Você pode encontrar muitas situações semelhantes nos seus contratos e isso pode realmente somar quantias significativas.

Encurtar mensagem de erro

Mensagens de reversão longa e, em particular, muitas mensagens de reversão diferentes podem bloquear o contrato. Em vez disso, use códigos de erro curtos e decodifique-os no contrato. Uma mensagem longa poderia ser muito mais curta:

1require(msg.sender == owner, "Only the owner of this contract can call this function");
2
Copiar
1require(msg.sender == owner, "OW1");
Copiar

Use erros personalizados ao invés de mensagens de erro

Erros personalizados foram introduzidos no Solidity 0.8.4(opens in a new tab). Eles são uma ótima maneira de reduzir o tamanho de seus contratos, porque são codificados por ABI como seletores (assim como as funções são).

1error Unauthorized();
2
3if (msg.sender != owner) {
4 revert Unauthorized();
5}
Copiar

Considere um valor de baixa execução no otimizador

Você também pode alterar as configurações do otimizador. O valor padrão de 200 significa que está tentando otimizar o bytecode como se uma função fosse chamada 200 vezes. Se você alterá-lo para 1, basicamente diga ao otimizador para otimizar em caso de executar cada função apenas uma vez. Uma função otimizada para rodar apenas uma vez significa que ela é otimizada para a própria implantação. Esteja ciente de que isso aumenta o custo do gás por executar as funções, então você pode querer não otimizá-la.

Pequeno impacto

Evite passar instruções para funções

Se você estiver usando o ABIEncoderV2(opens in a new tab), ele pode ajudar a não passar de structs para uma função. Em vez de passar o parâmetro como uma estrutura...

1function get(uint id) returns (address,address) {
2 return _get(myStruct);
3}
4
5function _get(MyStruct memory myStruct) private view returns(address,address) {
6 return (myStruct.addr1, myStruct.addr2);
7}
Copiar
1function get(uint id) returns(address,address) {
2 return _get(myStructs[id].addr1, myStructs[id].addr2);
3}
4
5function _get(address addr1, address addr2) private view returns(address,address) {
6 return (addr1, addr2);
7}
Copiar

... passe os parâmetros necessários diretamente. Neste exemplo, salvamos outro 0.1kb.

Declarar a visibilidade correta para funções e variáveis

  • Funções ou variáveis que são chamadas apenas do lado de fora? Declará-las como externas em vez de públicas.
  • Funções ou variáveis apenas chamadas dentro do contrato? Declará-las como private ou internal em vez de public.

Remover modificadores

Os modificadores, especialmente quando usados intencionalmente, podem ter um impacto significativo no tamanho do contrato. Considere removê-los e, em vez disso, usar funções.

1modifier checkStuff() {}
2
3function doSomething() checkStuff {}
Copiar
1function checkStuff() private {}
2
3function doSomething() { checkStuff(); }
Copiar

Essas dicas devem ajudá-lo a reduzir significativamente o tamanho do contrato. Mais uma vez, nunca é demais salientar que se foca sempre na divisão dos contratos, se possível para o maior impacto.

Última edição: @MCreimer(opens in a new tab), 19 de janeiro de 2024

Este tutorial foi útil?