Continuamos nossa série de artigos educativos e hoje veremos algumas dicas específicas para a otimização de gas & auditoria durante o desenvolvimento de contratos inteligentes no Solidity!
Imagem original | (2)
Hoje também damos início a uma série exclusiva de três partes, na qual discutiremos várias facetas do trabalho de um auditor (ou desenvolvedor, se estivermos falando de auditoria interna), desde a otimização de gas até a proteção contra ataques e limitações de EVM. Garantimos que será divertido!
A propósito, há alguns espaços vagos agora, portanto, se o seu projeto precisar de uma auditoria, sinta-se à vontade para nos escrever e visitar nossa página de comentários públicos aqui. Entre em contato conosco: [email protected]!
Parte II: link
Parte III: link
Por que de fato precisamos otimizar o gas?
O gas refere-se às taxas necessárias para executar operações na rede Ethereum. Ao otimizar o uso de gas, você pode reduzir significativamente os custos relacionados à implantação e à interação com o contrato inteligente. Isso é particularmente importante para os usuários que estão pagando taxas de transação e pode impulsionar a adoção e a satisfação do usuário.
Noções básicas de otimização de contratos Solidity, explicadas para programadores comuns.
Portanto, a otimização do uso de gas é essencial para uma auditoria de segurança completa. Ela ajuda a identificar possíveis vulnerabilidades e garante o uso eficiente dos recursos computacionais. Ao analisar o consumo de gas, os auditores podem verificar se o contrato está funcionando de forma ideal e descobrir possíveis falhas ou ineficiências de segurança.
Por Que Precisamos de Fato Auditar o Código?
Além dos aspectos técnicos, as auditorias também oferecem uma oportunidade de revisão da linguagem natural. A base de código é frequentemente revisada por experts que podem identificar e corrigir quaisquer ambiguidades ou inconsistências no código, tornando-o mais legível e compreensível.
O lançamento de um projeto sem proteção e auditoria de contratos inteligentes é um risco significativo. Isso pode expor o projeto a possíveis vulnerabilidades, levando a perdas financeiras, danos à reputação e consequências legais. Os investidores e usuários estão se tornando cada vez mais cautelosos e conscientes dos riscos envolvidos em sistemas descentralizados e tendem a favorecer projetos que tenham sido auditados por empresas de boa reputação.
Em resumo, a proteção e a auditoria de contratos inteligentes antes do lançamento de um projeto são essenciais para garantir a segurança, a confiabilidade e a credibilidade do aplicativo descentralizado. Isso reduz as possíveis vulnerabilidades, aumenta a confiança entre usuários e investidores, alinha os incentivos econômicos e ajuda a atender aos requisitos regulamentares.
Podemos dizer com segurança que essas dicas podem ser lidas publicamente em poucos lugares e nosso blog é um desses lugares. A seguir, apresentamos nossas observações - apenas fatos concretos para auditores, dicas e os melhores truques de vida compartilhados por nossos melhores auditores.
Tudo o que você vê abaixo é baseado em nossa experiência pessoal. E hoje, queridos leitores, isso será disponibilizado para vocês.
Concluímos nossa própria pesquisa há alguns meses; leia-a se ainda não o fez:
Você também encontrará uma lista de ferramentas e pesquisas para estudo próprio e recomendamos fortemente que você as leia separadamente para melhor compreensão! Vamos começar!
I — Dicas sobre Otimização de Gas
Como nosso time vem trabalhando desde 2016, acumulamos um grande número de observações, que ofereceremos aqui, juntamente com várias recomendações de segurança. As técnicas listadas abaixo podem ajudá-lo a aumentar consideravelmente a segurança da integração do seu projeto:
-
Modificadores: Cada inclusão de
_
em um modificador insere o corpo da função no bytecode. Se uma função tiver vários modificadores, isso poderá aumentar significativamente o tamanho do contrato e o custo da implantação. Se precisar economizar, você pode combinar modificadores ou colocar as verificações em uma função separada; - Em versões mais antigas do Solidity, ler o comprimento do armazenamento do array (array.length) na condição de loop significa ler do armazenamento a cada iteração;
- Às vezes, ao auditar o código, você pode notar cópias desnecessárias (calldata->storage; calldata->memory; memory <-> storage) ao atribuir ou passar argumentos para uma função. Por exemplo, você deve sempre marcar argumentos reference-type de funções internas como calldata, e não, memory; algumas vezes você pode até usar referências storage (de armazenamento) em chamadas internal (internas);
- Você pode alterar a ordem das variáveis storage ou dos campos em uma estrutura em algum lugar para usar o storage package (empacotamento de armazenamento). E isso será útil. Entretanto, às vezes, ler e gravar com storage package nem sempre é mais econômico do que sem ele. Você pode economizar muito gas ao gravar um array de storage de estruturas com campos compactados;
- No Solidity, alguns tipos de dados têm um custo de gas maior do que outros. E isso é o que geralmente é exigido de um desenvolvedor de contratos inteligentes, e é por isso que você deve entender a utilização de gas dos tipos de dados disponíveis;
- É mais econômico usar erros personalizados do que revert("error text"). Há mais informações neste artigo: soliditylang.org/blog/2021/04/21/custom-errors.
Recursos Úteis
GASTAP: Um analisador de gas para contratos inteligentes
Gostaríamos de expressar nossa sincera admiração aos autores desses recursos! Dê uma olhada neles:
- Solidity Gas Optimization
- Gas Optimization Tips
- Short Circuiting
- Solidity Gas Optimization: Stop Using Bools for True/False Values
Também concluímos nossa própria pesquisa há alguns meses; leia-a, caso ainda não o tenha feito:
II — Dicas de Auditoria em Solidity
Ninguém pode negar que a base de qualquer integração segura é uma abordagem única para a criação de códigos. Como resultado, este artigo se concentrará apenas nas áreas que podem ser muito valiosas para manter seu código seguro e protegido. Concluímos nossa própria pesquisa há alguns meses; leia-a se ainda não o fez:
A integração de seu projeto será substancialmente mais segura se você implementar as recomendações abaixo:
-
Funções: As chamadas internas preservam o contexto (msg.sender, msg.value, etc). Por exemplo, uma chamada interna para
transfer(...)
dentro de um token transfere tokens do endereço do chamador, não do saldo do próprio contrato de token; - Funções: external > public
- Variáveis: A visibilidade das variáveis privadas e internas não oculta os dados. Os valores das variáveis podem ser lidos off-chain;
- Variáveis: A visibilidade Public (pública) para variáveis cria getters e o código dos getters ocupa espaço no contrato. Ao otimizar o gas, você pode remover a visibilidade Public de algumas variáveis (não utilizadas ou já lidas em outras funções) e, assim, reduzir o consumo de gas durante a execução do contrato;
- Constantes: As constantes mágicas são perigosas, portanto, crie constantes completas com um nome normal. Imutável também é aceitável;
- Constantes As assinaturas de funções devem ser verificadas com o 4bytes;
- Ether: payable (pagável)— pode ser redundante (a função pode receber ether, mas não o processa);
- Ether: fallback vs receive; receive é suficiente para receber dinheiro. Geralmente inclui uma verificação de que a transmissão vem de um dos endereços (por exemplo, WETH);
- Ether: É impossível limitar o consumo de ether (selfdestruct, mining), portanto, você não pode confiar no valor exato do saldo. Isso também se aplica aos tokens;
- Ether: Três opções de envio de Ether: send (enviar), call (chamar), transfer (transferir):
a) Especificidades de uso da address.transfer() — dispara em caso de falha, encaminha 2.300 de estipêndio de gas (não ajustável), é seguro contra reentrância, deve ser usado na maioria dos casos, pois é a maneira mais segura de enviar ether;
b) Especificidades de uso da address.send() — retorna falso em caso de falha, deve ser usado em casos raros, quando você quiser tratar a falha no contrato;
c) Especificidades de uso da address.call.value().gas()() — retorna falso em caso de falha, encaminha todo o gas disponível (ajustável), não é seguro contra reentrância, deve ser usado quando for necessário controlar a quantidade de gas a ser encaminhada ao enviar ether ou para chamar uma função de outro contrato;
- Não é o recomendável trabalhar diretamente com gas: ao trabalhar com a variável tx.gasprice, é necessário lembrar que seu valor é definido pelo usuário no momento da execução da transação;
- Não é o recomendável trabalhar diretamente com gas: liberação de uma quantidade específica de gas por meio de .gas(X) / {gas: X}
- .transfer VS .send VS .call — call é um bom padrão, mas traz seu próprio conjunto de problemas ("reentrância" etc.);
- É fácil fazer colisões, por exemplo, com arrays: abi.encodePacked([1,2],[3]) == abi.encodePacked([1],[2,3])) portanto, verifique-os cuidadosamente. Tecnicamente, algo semelhante pode ser feito com abi.encode, por exemplo, via structures.
Recursos Úteis
Gostaríamos de expressar nossa sincera gratidão aos autores de todos os materiais de apoio. Consulte-os:
- In Depth Understanding of Denial-of-Service Vulnerabilities
- Smart Contract Attack Vectors
- Smart Contract Best Practices
- Gas Optimization In Ethereum
Também concluímos nossa própria pesquisa há alguns meses; leia-a, caso ainda não o tenha feito:
III — Erros Aritméticos
Como nosso time vem trabalhando desde 2016, acumulamos várias observações, que compartilharemos a seguir, juntamente com várias recomendações de segurança.
Seguir as dicas abaixo pode aumentar significativamente a segurança da integração de seu projeto:
Overflow/Underflow
- Use SafeMath e similares para Solidity <0.8 (e não use para versões mais recentes), tenha em mente que a.add(b).mul(c) == (a+b)*c ;
- Verifique cuidadosamente todas as seções não marcadas para Solidity >=0.8 ;
- É melhor fazer todos os cálculos no tipo uint256 porque, por exemplo, no caso de
uint256(a) = uint16(b) * uint32(c) / uint16(d)
a parte da direita pode transbordar, porque o valor intermediário pode não caber em uint32 (para cada operação, é considerado o máximo de tipos de operandos, ou seja, o resultado da multiplicação de uint16 e uint32 será uint32); - Conversão do Tipo — sempre verifique se o número foi convertido normalmente. Recomendação: use bibliotecas como SafeCast;
MulDiv — Perda de Precisão nos Cálculos
- Quase sempre é a multiplicação primeiro, depois a divisão;
- Ao calcular frações, não se esqueça de que você pode obter acidentalmente zero. ex.
balanceOf(user) / totalSupply() == 0
. Você precisa multiplicar por um multiplicador adequado (geralmente 1e18). - É melhor colocar o multiplicador adicional em uma constante como
uint__constant private HUNDRED_PERCENT=1e18;
; - Há ocasiões em que você precisa calcular a soma de frações (ou seja, o denominador comum de todas as frações). É correto somar primeiro e depois dividir;
- Ao invés de
a/b > c/d
é geralmente melhor usara*d > c*b
.
Recursos úteis
Gostaríamos de expressar nossa sincera gratidão aos autores de todos os materiais de apoio! Consulte-os:
- Hack Solidity: Overflow and Underflow
- Arithmetic Overflow and Underflow
- Understanding uint Overflows and Underflows — Solidity (Ethereum)
- Preventing Arithmetic Overflow and Underflow in Solidity: Best Practices and Solutions
Também concluímos nossa própria pesquisa há alguns meses; leia-a se ainda não o fez:
Para concluir, gostaríamos de dizer que esperamos que este artigo tenha sido informativo e útil para você! Obrigado pela leitura! 🙂
O mais importante que gostaríamos de dizer e mostrar é que um instrumento novo não significa que seja bom e um instrumento antigo não significa que seja ruim. Tudo se resume a saber como usá-lo e estar disposto a procurar algo novo. Esperamos que você tenha se interessado por isso!
>Nos últimos meses, temos desenvolvido ativamente nossos próprios detectores Slither (confira nossa ferramenta Slitherin) para ajudar com o processo de revisão de código e auditoria. Entre em contato caso tenha descoberto um problema/bug/vulnerabilidade por meio de nossos detectors Slither personalizados. Você pode entrar em contato conosco abrindo um PR/Issue ou diretamente, o que for mais conveniente para você!
Quais instrumentos devemos analisar? Sobre o que você teria interesse em ler? Deixe seus comentários, teremos prazer em respondê-los e as melhores respostas e perguntas poderão ser incluídas no próximo artigo!
A propósito, há alguns espaços vagos agora, portanto, se o seu projeto precisar de uma auditoria, sinta-se à vontade para nos escrever e visite nossa página de comentários públicos aqui.
Fique bem!
Esse artigo foi escrito por Officer's Notes e traduzido por Fátima Lima. O original pode ser lido aqui.
Top comments (0)