Pular para o conteúdo principal
Change page

Segurança de contratos inteligentes

Os contratos inteligentes são extremamente flexíveis e capazes de controlar grandes quantidades de valor e dados, enquanto executam uma lógica imutável baseada em código implantado na blockchain. Isso criou um ecossistema vibrante de aplicativos descentralizados e sem necessidade de confiança que oferecem muitas vantagens sobre os sistemas legados. Eles também representam oportunidades para invasores que buscam lucrar explorando vulnerabilidades em contratos inteligentes.

Blockchains públicas, como o Ethereum, complicam ainda mais a questão da segurança dos contratos inteligentes. O código de contrato implantado geralmente não pode ser alterado para corrigir falhas de segurança, enquanto os ativos roubados de contratos inteligentes são extremamente difíceis de rastrear e, em sua maioria, irrecuperáveis devido à imutabilidade.

Embora os números variem, estima-se que o valor total roubado ou perdido devido a defeitos de segurança em contratos inteligentes ultrapasse facilmente a marca de US$ 1 bilhão. Isso inclui incidentes de grande repercussão, como o hack da DAO (opens in a new tab) (3,6 milhões de ETH roubados, valendo mais de US$ 1 bilhão nos preços de hoje), o hack da carteira multisig da Parity (opens in a new tab) (US$ 30 milhões perdidos para hackers) e o problema da carteira congelada da Parity (opens in a new tab) (mais de US$ 300 milhões em ETH bloqueados para sempre).

Os problemas mencionados acima tornam imperativo que os desenvolvedores invistam esforço na criação de contratos inteligentes seguros, robustos e resilientes. A segurança de contratos inteligentes é um assunto sério, e que todo desenvolvedor fará bem em aprender. Este guia abordará considerações de segurança para desenvolvedores do Ethereum e explorará recursos para melhorar a segurança de contratos inteligentes.

Pré-requisitos

Certifique-se de estar familiarizado com os fundamentos do desenvolvimento de contratos inteligentes antes de abordar a segurança.

Diretrizes para a construção de contratos inteligentes seguros no Ethereum

1. Projete controles de acesso adequados

Em contratos inteligentes, funções marcadas como public ou external podem ser chamadas por quaisquer contas de propriedade externa (EOAs) ou contas de contrato. Especificar a visibilidade pública para funções é necessário se você quiser que outras pessoas interajam com seu contrato. No entanto, funções marcadas como private só podem ser chamadas por funções dentro do contrato inteligente, e não por contas externas. Dar a todos os participantes da rede acesso às funções do contrato pode causar problemas, especialmente se isso significar que qualquer pessoa pode realizar operações sensíveis (por exemplo, a cunhagem de novos tokens).

Para evitar o uso não autorizado de funções de contratos inteligentes, é necessário implementar controles de acesso seguros. Os mecanismos de controle de acesso restringem a capacidade de usar certas funções em um contrato inteligente a entidades aprovadas, como contas responsáveis pelo gerenciamento do contrato. O padrão Ownable e o controle baseado em funções (role-based control) são dois padrões úteis para implementar o controle de acesso em contratos inteligentes:

Padrão Ownable

No padrão Ownable, um endereço é definido como o “proprietário” (owner) do contrato durante o processo de criação do contrato. As funções protegidas recebem um modificador OnlyOwner, que garante que o contrato autentique a identidade do endereço chamador antes de executar a função. As chamadas para funções protegidas de outros endereços que não sejam o proprietário do contrato sempre revertem, impedindo o acesso indesejado.

Controle de acesso baseado em funções (Role-based access control)

Registrar um único endereço como Owner em um contrato inteligente introduz o risco de centralização e representa um ponto único de falha. Se as chaves da conta do proprietário forem comprometidas, os invasores poderão atacar o contrato de sua propriedade. É por isso que usar um padrão de controle de acesso baseado em funções com várias contas administrativas pode ser uma opção melhor.

No controle de acesso baseado em funções, o acesso a funções sensíveis é distribuído entre um conjunto de participantes confiáveis. Por exemplo, uma conta pode ser responsável pela cunhagem de tokens, enquanto outra conta realiza atualizações ou pausa o contrato. Descentralizar o controle de acesso dessa forma elimina pontos únicos de falha e reduz as premissas de confiança para os usuários.

Usando carteiras de multissinatura

Outra abordagem para implementar o controle de acesso seguro é usar uma conta de multissinatura para gerenciar um contrato. Ao contrário de uma EOA comum, as contas de multissinatura pertencem a várias entidades e exigem assinaturas de um número mínimo de contas — digamos, 3 de 5 — para executar transações.

O uso de uma multisig para controle de acesso introduz uma camada extra de segurança, já que as ações no contrato alvo exigem o consentimento de várias partes. Isso é particularmente útil se o uso do padrão Ownable for necessário, pois torna mais difícil para um invasor ou um infiltrado mal-intencionado manipular funções sensíveis do contrato para fins maliciosos.

2. Use as instruções require(), assert() e revert() para proteger as operações do contrato

Como mencionado, qualquer pessoa pode chamar funções públicas em seu contrato inteligente assim que ele for implantado na blockchain. Como você não pode saber com antecedência como as contas externas interagirão com um contrato, o ideal é implementar salvaguardas internas contra operações problemáticas antes da implantação. Você pode impor o comportamento correto em contratos inteligentes usando as instruções require(), assert() e revert() para acionar exceções e reverter as alterações de estado se a execução não satisfizer determinados requisitos.

require(): require são definidos no início das funções e garantem que condições predefinidas sejam atendidas antes que a função chamada seja executada. Uma instrução require pode ser usada para validar as entradas do usuário, verificar variáveis de estado ou autenticar a identidade da conta chamadora antes de prosseguir com uma função.

assert(): assert() é usado para detectar erros internos e verificar violações de “invariantes” em seu código. Um invariante é uma asserção lógica sobre o estado de um contrato que deve ser verdadeira para todas as execuções de função. Um exemplo de invariante é o fornecimento total máximo ou o saldo de um contrato de token. O uso de assert() garante que seu contrato nunca atinja um estado vulnerável e, se isso acontecer, todas as alterações nas variáveis de estado serão revertidas.

revert(): revert() pode ser usado em uma instrução if-else que aciona uma exceção se a condição exigida não for satisfeita. O contrato de exemplo abaixo usa revert() para proteger a execução de funções:

3. Teste os contratos inteligentes e verifique a exatidão do código

A imutabilidade do código em execução na Máquina Virtual Ethereum significa que os contratos inteligentes exigem um nível mais alto de avaliação de qualidade durante a fase de desenvolvimento. Testar seu contrato extensivamente e observá-lo em busca de resultados inesperados melhorará muito a segurança e protegerá seus usuários a longo prazo.

O método usual é escrever pequenos testes de unidade usando dados simulados (mock data) que o contrato deve receber dos usuários. O teste de unidade é bom para testar a funcionalidade de certas funções e garantir que um contrato inteligente funcione conforme o esperado.

Infelizmente, o teste de unidade é minimamente eficaz para melhorar a segurança do contrato inteligente quando usado isoladamente. Um teste de unidade pode provar que uma função é executada corretamente para dados simulados, mas os testes de unidade são tão eficazes quanto os testes que são escritos. Isso dificulta a detecção de casos extremos (edge cases) não previstos e vulnerabilidades que poderiam quebrar a segurança do seu contrato inteligente.

Uma abordagem melhor é combinar testes de unidade com testes baseados em propriedades realizados usando análise estática e dinâmica. A análise estática depende de representações de baixo nível, como grafos de fluxo de controle (opens in a new tab) e árvores de sintaxe abstrata (opens in a new tab) para analisar estados de programa alcançáveis e caminhos de execução. Enquanto isso, técnicas de análise dinâmica, como o fuzzing de contratos inteligentes (opens in a new tab), executam o código do contrato com valores de entrada aleatórios para detectar operações que violam as propriedades de segurança.

A verificação formal é outra técnica para verificar propriedades de segurança em contratos inteligentes. Ao contrário dos testes regulares, a verificação formal pode provar conclusivamente a ausência de erros em um contrato inteligente. Isso é alcançado criando uma especificação formal que captura as propriedades de segurança desejadas e provando que um modelo formal dos contratos adere a essa especificação.

4. Peça uma revisão independente do seu código

Depois de testar seu contrato, é bom pedir a outras pessoas que verifiquem o código-fonte em busca de problemas de segurança. Os testes não descobrirão todas as falhas em um contrato inteligente, mas obter uma revisão independente aumenta a possibilidade de detectar vulnerabilidades.

Auditorias

Comissionar uma auditoria de contrato inteligente é uma maneira de conduzir uma revisão de código independente. Os auditores desempenham um papel importante em garantir que os contratos inteligentes sejam seguros e livres de defeitos de qualidade e erros de design.

Dito isso, você deve evitar tratar as auditorias como uma solução mágica. As auditorias de contratos inteligentes não detectarão todos os bugs e são projetadas principalmente para fornecer uma rodada adicional de revisões, o que pode ajudar a detectar problemas não percebidos pelos desenvolvedores durante o desenvolvimento e os testes iniciais. Você também deve seguir as melhores práticas para trabalhar com auditores, como documentar o código adequadamente e adicionar comentários em linha, para maximizar o benefício de uma auditoria de contrato inteligente.

Programas de recompensas por bugs (Bug bounties)

Configurar um programa de recompensas por bugs (bug bounty) é outra abordagem para implementar revisões de código externas. Um bug bounty é uma recompensa financeira dada a indivíduos (geralmente hackers whitehat) que descobrem vulnerabilidades em um aplicativo.

Quando usados adequadamente, os programas de recompensas por bugs dão aos membros da comunidade hacker um incentivo para inspecionar seu código em busca de falhas críticas. Um exemplo da vida real é o “bug do dinheiro infinito” que teria permitido a um invasor criar uma quantidade ilimitada de ether na Optimism (opens in a new tab), um protocolo de camada 2 (l2) rodando no Ethereum. Felizmente, um hacker whitehat descobriu a falha (opens in a new tab) e notificou a equipe, ganhando um grande pagamento no processo (opens in a new tab).

Uma estratégia útil é definir o pagamento de um programa de recompensas por bugs proporcionalmente à quantidade de fundos em jogo. Descrita como a “recompensa por bug escalável (opens in a new tab)”, essa abordagem fornece incentivos financeiros para que os indivíduos divulguem vulnerabilidades de forma responsável em vez de explorá-las.

5. Siga as melhores práticas durante o desenvolvimento de contratos inteligentes

A existência de auditorias e programas de recompensas por bugs não isenta sua responsabilidade de escrever código de alta qualidade. Uma boa segurança de contratos inteligentes começa seguindo processos adequados de design e desenvolvimento:

  • Armazene todo o código em um sistema de controle de versão, como o git

  • Faça todas as modificações de código por meio de pull requests

  • Certifique-se de que os pull requests tenham pelo menos um revisor independente — se você estiver trabalhando sozinho em um projeto, considere encontrar outros desenvolvedores e trocar revisões de código

  • Use um ambiente de desenvolvimento para testar, compilar e implantar contratos inteligentes

  • Execute seu código por meio de ferramentas básicas de análise de código, como Cyfrin Aderyn (opens in a new tab), Mythril e Slither. O ideal é que você faça isso antes que cada pull request seja mesclado e compare as diferenças na saída

  • Certifique-se de que seu código seja compilado sem erros e que o compilador Solidity não emita avisos

  • Documente adequadamente seu código (usando NatSpec (opens in a new tab)) e descreva detalhes sobre a arquitetura do contrato em uma linguagem fácil de entender. Isso tornará mais fácil para outras pessoas auditarem e revisarem seu código.

6. Implemente planos robustos de recuperação de desastres

Projetar controles de acesso seguros, implementar modificadores de função e outras sugestões podem melhorar a segurança dos contratos inteligentes, mas não podem descartar a possibilidade de explorações maliciosas. A construção de contratos inteligentes seguros exige “preparação para falhas” e um plano de contingência para responder de forma eficaz aos ataques. Um plano de recuperação de desastres adequado incorporará alguns ou todos os seguintes componentes:

Atualizações de contrato

Embora os contratos inteligentes do Ethereum sejam imutáveis por padrão, é possível atingir algum grau de mutabilidade usando padrões de atualização. A atualização de contratos é necessária nos casos em que uma falha crítica torna seu contrato antigo inutilizável e a implantação de uma nova lógica é a opção mais viável.

Os mecanismos de atualização de contratos funcionam de maneira diferente, mas o “padrão proxy” é uma das abordagens mais populares para atualizar contratos inteligentes. Os padrões proxy (opens in a new tab) dividem o estado e a lógica de um aplicativo entre dois contratos. O primeiro contrato (chamado de 'contrato proxy') armazena variáveis de estado (por exemplo, saldos de usuários), enquanto o segundo contrato (chamado de 'contrato lógico') contém o código para executar as funções do contrato.

As contas interagem com o contrato proxy, que despacha todas as chamadas de função para o contrato lógico usando a chamada de baixo nível delegatecall() (opens in a new tab). Ao contrário de uma chamada de mensagem regular, delegatecall() garante que o código em execução no endereço do contrato lógico seja executado no contexto do contrato chamador. Isso significa que o contrato lógico sempre gravará no armazenamento do proxy (em vez de em seu próprio armazenamento) e os valores originais de msg.sender e msg.value serão preservados.

A delegação de chamadas para o contrato lógico exige o armazenamento de seu endereço no armazenamento do contrato proxy. Portanto, atualizar a lógica do contrato é apenas uma questão de implantar outro contrato lógico e armazenar o novo endereço no contrato proxy. Como as chamadas subsequentes ao contrato proxy são roteadas automaticamente para o novo contrato lógico, você teria “atualizado” o contrato sem realmente modificar o código.

Mais sobre a atualização de contratos.

Paradas de emergência

Como mencionado, auditorias e testes extensivos não podem descobrir todos os bugs em um contrato inteligente. Se uma vulnerabilidade aparecer em seu código após a implantação, corrigi-la será impossível, pois você não pode alterar o código em execução no endereço do contrato. Além disso, os mecanismos de atualização (por exemplo, padrões proxy) podem levar tempo para serem implementados (geralmente exigem a aprovação de diferentes partes), o que apenas dá aos invasores mais tempo para causar mais danos.

A opção nuclear é implementar uma função de “parada de emergência” que bloqueia chamadas para funções vulneráveis em um contrato. As paradas de emergência normalmente compreendem os seguintes componentes:

  1. Uma variável booleana global indicando se o contrato inteligente está em um estado parado ou não. Essa variável é definida como false ao configurar o contrato, mas será revertida para true assim que o contrato for interrompido.

  2. Funções que referenciam a variável booleana em sua execução. Essas funções são acessíveis quando o contrato inteligente não está parado e se tornam inacessíveis quando o recurso de parada de emergência é acionado.

  3. Uma entidade que tem acesso à função de parada de emergência, que define a variável booleana como true. Para evitar ações maliciosas, as chamadas para essa função podem ser restritas a um endereço confiável (por exemplo, o proprietário do contrato).

Assim que o contrato ativar a parada de emergência, certas funções não poderão ser chamadas. Isso é alcançado envolvendo funções selecionadas em um modificador que faz referência à variável global. Abaixo está um exemplo (opens in a new tab) descrevendo uma implementação desse padrão em contratos:

Este exemplo mostra os recursos básicos das paradas de emergência:

  • isStopped é um booleano que é avaliado como false no início e true quando o contrato entra no modo de emergência.

  • Os modificadores de função onlyWhenStopped e stoppedInEmergency verificam a variável isStopped. stoppedInEmergency é usado para controlar funções que devem estar inacessíveis quando o contrato estiver vulnerável (por exemplo, deposit()). As chamadas para essas funções simplesmente reverterão.

onlyWhenStopped é usado para funções que devem ser chamáveis durante uma emergência (por exemplo, emergencyWithdraw()). Essas funções podem ajudar a resolver a situação, daí sua exclusão da lista de “funções restritas”.

O uso de uma funcionalidade de parada de emergência fornece uma medida paliativa eficaz para lidar com vulnerabilidades graves em seu contrato inteligente. No entanto, isso aumenta a necessidade de os usuários confiarem nos desenvolvedores para não ativá-la por motivos egoístas. Para esse fim, descentralizar o controle da parada de emergência, sujeitando-o a um mecanismo de votação onchain, timelock ou aprovação de uma carteira multisig, são soluções possíveis.

Monitoramento de eventos

Os eventos (opens in a new tab) permitem que você rastreie chamadas para funções de contratos inteligentes e monitore alterações nas variáveis de estado. O ideal é programar seu contrato inteligente para emitir um evento sempre que alguma parte realizar uma ação crítica para a segurança (por exemplo, sacar fundos).

Registrar eventos e monitorá-los offchain fornece insights sobre as operações do contrato e ajuda na descoberta mais rápida de ações maliciosas. Isso significa que sua equipe pode responder mais rapidamente a hacks e tomar medidas para mitigar o impacto nos usuários, como pausar funções ou realizar uma atualização.

Você também pode optar por uma ferramenta de monitoramento pronta para uso que encaminha alertas automaticamente sempre que alguém interage com seus contratos. Essas ferramentas permitirão que você crie alertas personalizados com base em diferentes gatilhos, como volume de transações, frequência de chamadas de função ou as funções específicas envolvidas. Por exemplo, você pode programar um alerta que chega quando o valor sacado em uma única transação ultrapassa um limite específico.

7. Projete sistemas de governança seguros

Você pode querer descentralizar seu aplicativo transferindo o controle dos principais contratos inteligentes para os membros da comunidade. Nesse caso, o sistema de contratos inteligentes incluirá um módulo de governança — um mecanismo que permite aos membros da comunidade aprovar ações administrativas por meio de um sistema de governança onchain. Por exemplo, uma proposta para atualizar um contrato proxy para uma nova implementação pode ser votada pelos detentores de tokens.

A governança descentralizada pode ser benéfica, especialmente porque alinha os interesses dos desenvolvedores e dos usuários finais. No entanto, os mecanismos de governança de contratos inteligentes podem introduzir novos riscos se implementados incorretamente. Um cenário plausível é se um invasor adquirir um enorme poder de voto (medido em número de tokens mantidos) fazendo um empréstimo relâmpago e aprovar uma proposta maliciosa.

Uma maneira de evitar problemas relacionados à governança onchain é usar um timelock (opens in a new tab). Um timelock impede que um contrato inteligente execute certas ações até que um período de tempo específico passe. Outras estratégias incluem atribuir um “peso de voto” a cada token com base em quanto tempo ele esteve bloqueado, ou medir o poder de voto de um endereço em um período histórico (por exemplo, 2 a 3 blocos no passado) em vez do bloco atual. Ambos os métodos reduzem a possibilidade de acumular rapidamente poder de voto para influenciar os votos onchain.

Mais sobre como projetar sistemas de governança seguros (opens in a new tab), diferentes mecanismos de votação em DAOs (opens in a new tab) e os vetores de ataque comuns a DAOs que alavancam DeFi (opens in a new tab) nos links compartilhados.

8. Reduza a complexidade do código ao mínimo

Os desenvolvedores de software tradicionais estão familiarizados com o princípio KISS (“keep it simple, stupid” - mantenha isso simples, estúpido), que desaconselha a introdução de complexidade desnecessária no design de software. Isso segue o pensamento de longa data de que “sistemas complexos falham de maneiras complexas” e são mais suscetíveis a erros custosos.

Manter as coisas simples é de particular importância ao escrever contratos inteligentes, dado que os contratos inteligentes estão potencialmente controlando grandes quantidades de valor. Uma dica para alcançar a simplicidade ao escrever contratos inteligentes é reutilizar bibliotecas existentes, como a OpenZeppelin Contracts (opens in a new tab), sempre que possível. Como essas bibliotecas foram extensivamente auditadas e testadas por desenvolvedores, usá-las reduz as chances de introduzir bugs ao escrever novas funcionalidades do zero.

Outro conselho comum é escrever funções pequenas e manter os contratos modulares, dividindo a lógica de negócios em vários contratos. Escrever um código mais simples não apenas reduz a superfície de ataque em um contrato inteligente, mas também torna mais fácil raciocinar sobre a exatidão do sistema geral e detectar possíveis erros de design precocemente.

9. Defenda-se contra vulnerabilidades comuns de contratos inteligentes

Reentrada

A EVM não permite concorrência, o que significa que dois contratos envolvidos em uma chamada de mensagem não podem ser executados simultaneamente. Uma chamada externa pausa a execução e a memória do contrato chamador até que a chamada retorne, momento em que a execução prossegue normalmente. Esse processo pode ser formalmente descrito como a transferência do fluxo de controle (opens in a new tab) para outro contrato.

Embora na maioria das vezes inofensiva, a transferência do fluxo de controle para contratos não confiáveis pode causar problemas, como a reentrada. Um ataque de reentrada ocorre quando um contrato malicioso chama de volta um contrato vulnerável antes que a invocação da função original seja concluída. Esse tipo de ataque é melhor explicado com um exemplo.

Considere um contrato inteligente simples ('Victim') que permite a qualquer pessoa depositar e sacar ether:

Este contrato expõe uma função withdraw() para permitir que os usuários saquem ETH depositado anteriormente no contrato. Ao processar um saque, o contrato realiza as seguintes operações:

  1. Verifica o saldo de ETH do usuário
  2. Envia fundos para o endereço chamador
  3. Redefine seu saldo para 0, impedindo saques adicionais do usuário

A função withdraw() no contrato Victim segue um padrão de “verificações-interações-efeitos” (checks-interactions-effects). Ele verifica se as condições necessárias para a execução são satisfeitas (ou seja, o usuário tem um saldo de ETH positivo) e realiza a interação enviando ETH para o endereço do chamador, antes de aplicar os efeitos da transação (ou seja, reduzindo o saldo do usuário).

Se withdraw() for chamado de uma conta de propriedade externa (EOA), a função será executada conforme o esperado: msg.sender.call.value() envia ETH para o chamador. No entanto, se msg.sender for uma conta de contrato inteligente que chama withdraw(), o envio de fundos usando msg.sender.call.value() também acionará a execução do código armazenado nesse endereço.

Imagine que este seja o código implantado no endereço do contrato:

Este contrato foi projetado para fazer três coisas:

  1. Aceitar um depósito de outra conta (provavelmente a EOA do invasor)
  2. Depositar 1 ETH no contrato Victim
  3. Sacar o 1 ETH armazenado no contrato inteligente

Não há nada de errado aqui, exceto que Attacker tem outra função que chama withdraw() em Victim novamente se o gás restante da msg.sender.call.value de entrada for superior a 40.000. Isso dá a Attacker a capacidade de reentrar em Victim e sacar mais fundos antes que a primeira invocação de withdraw seja concluída. O ciclo se parece com isto:

O resumo é que, como o saldo do chamador não é definido como 0 até que a execução da função seja concluída, as invocações subsequentes serão bem-sucedidas e permitirão que o chamador saque seu saldo várias vezes. Esse tipo de ataque pode ser usado para drenar os fundos de um contrato inteligente, como o que aconteceu no hack da DAO em 2016 (opens in a new tab). Os ataques de reentrada ainda são um problema crítico para os contratos inteligentes hoje, como mostram as listagens públicas de explorações de reentrada (opens in a new tab).

Como evitar ataques de reentrada

Uma abordagem para lidar com a reentrada é seguir o padrão de verificações-efeitos-interações (checks-effects-interactions) (opens in a new tab). Esse padrão ordena a execução de funções de forma que o código que realiza as verificações necessárias antes de prosseguir com a execução venha primeiro, seguido pelo código que manipula o estado do contrato, com o código que interage com outros contratos ou EOAs chegando por último.

O padrão de verificações-efeitos-interações é usado em uma versão revisada do contrato Victim mostrada abaixo:

contract NoLongerAVictim {
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success);
    }
}

Este contrato realiza uma verificação no saldo do usuário, aplica os efeitos da função withdraw() (redefinindo o saldo do usuário para 0) e prossegue para realizar a interação (enviando ETH para o endereço do usuário). Isso garante que o contrato atualize seu armazenamento antes da chamada externa, eliminando a condição de reentrada que permitiu o primeiro ataque. O contrato Attacker ainda poderia chamar de volta para NoLongerAVictim, mas como balances[msg.sender] foi definido como 0, saques adicionais lançarão um erro.

Outra opção é usar um bloqueio de exclusão mútua (comumente descrito como um "mutex") que bloqueia uma parte do estado de um contrato até que a invocação de uma função seja concluída. Isso é implementado usando uma variável booleana que é definida como true antes da execução da função e reverte para false após a conclusão da invocação. Como visto no exemplo abaixo, o uso de um mutex protege uma função contra chamadas recursivas enquanto a invocação original ainda está sendo processada, interrompendo efetivamente a reentrada.

Você também pode usar um sistema de pagamentos pull (pull payments) (opens in a new tab) que exige que os usuários saquem fundos dos contratos inteligentes, em vez de um sistema de "pagamentos push" que envia fundos para as contas. Isso remove a possibilidade de acionar inadvertidamente o código em endereços desconhecidos (e também pode evitar certos ataques de negação de serviço).

Underflows e overflows de inteiros

Um overflow de inteiro ocorre quando os resultados de uma operação aritmética ficam fora do intervalo aceitável de valores, fazendo com que ele "zere" para o valor representável mais baixo. Por exemplo, um uint8 só pode armazenar valores de até 2^8-1=255. As operações aritméticas que resultam em valores superiores a 255 causarão overflow e redefinirão uint para 0, de forma semelhante a como o hodômetro de um carro é redefinido para 0 quando atinge a quilometragem máxima (999999).

Os underflows de inteiros acontecem por motivos semelhantes: os resultados de uma operação aritmética caem abaixo do intervalo aceitável. Digamos que você tentou decrementar 0 em um uint8, o resultado simplesmente voltaria para o valor máximo representável (255).

Tanto os overflows quanto os underflows de inteiros podem levar a alterações inesperadas nas variáveis de estado de um contrato e resultar em execução não planejada. Abaixo está um exemplo mostrando como um invasor pode explorar o overflow aritmético em um contrato inteligente para realizar uma operação inválida:

Como evitar underflows e overflows de inteiros

A partir da versão 0.8.0, o compilador Solidity rejeita o código que resulta em underflows e overflows de inteiros. No entanto, os contratos compilados com uma versão de compilador inferior devem realizar verificações em funções que envolvem operações aritméticas ou usar uma biblioteca (por exemplo, SafeMath (opens in a new tab)) que verifica a ocorrência de underflow/overflow.

Manipulação de oráculo

Os oráculos buscam informações offchain e as enviam onchain para que os contratos inteligentes as usem. Com os oráculos, você pode projetar contratos inteligentes que interoperam com sistemas offchain, como mercados de capitais, expandindo muito sua aplicação.

Mas se o oráculo for corrompido e enviar informações incorretas onchain, os contratos inteligentes serão executados com base em entradas errôneas, o que pode causar problemas. Essa é a base do “problema do oráculo”, que diz respeito à tarefa de garantir que as informações de um oráculo de blockchain sejam precisas, atualizadas e oportunas.

Uma preocupação de segurança relacionada é o uso de um oráculo onchain, como uma exchange descentralizada, para obter o preço à vista (spot price) de um ativo. As plataformas de empréstimo no setor de finanças descentralizadas (DeFi) costumam fazer isso para determinar o valor do colateral de um usuário para determinar quanto ele pode tomar emprestado.

Os preços das DEXs costumam ser precisos, em grande parte devido aos arbitradores que restauram a paridade nos mercados. No entanto, eles estão abertos à manipulação, principalmente se o oráculo onchain calcular os preços dos ativos com base em padrões históricos de negociação (como costuma ser o caso).

Por exemplo, um invasor poderia inflar artificialmente o preço à vista de um ativo fazendo um empréstimo relâmpago logo antes de interagir com seu contrato de empréstimo. Consultar a DEX sobre o preço do ativo retornaria um valor mais alto do que o normal (devido à grande “ordem de compra” do invasor distorcendo a demanda pelo ativo), permitindo que ele tomasse emprestado mais do que deveria. Esses "ataques de empréstimo relâmpago" têm sido usados para explorar a dependência de oráculos de preços entre aplicativos DeFi, custando aos protocolos milhões em fundos perdidos.

Como evitar a manipulação de oráculos

O requisito mínimo para evitar a manipulação de oráculos (opens in a new tab) é usar uma rede de oráculos descentralizada que consulta informações de várias fontes para evitar pontos únicos de falha. Na maioria dos casos, os oráculos descentralizados têm incentivos criptoeconômicos integrados para encorajar os nós do oráculo a relatar informações corretas, tornando-os mais seguros do que os oráculos centralizados.

Se você planeja consultar um oráculo onchain para obter preços de ativos, considere usar um que implemente um mecanismo de preço médio ponderado pelo tempo (TWAP). Um oráculo TWAP (opens in a new tab) consulta o preço de um ativo em dois momentos diferentes (que você pode modificar) e calcula o preço à vista com base na média obtida. A escolha de períodos de tempo mais longos protege seu protocolo contra a manipulação de preços, já que grandes ordens executadas recentemente não podem impactar os preços dos ativos.

Recursos de segurança de contratos inteligentes para desenvolvedores

Ferramentas para analisar contratos inteligentes e verificar a exatidão do código

  • Ferramentas e bibliotecas de teste - Coleção de ferramentas e bibliotecas padrão da indústria para realizar testes unitários, análise estática e análise dinâmica em contratos inteligentes.

  • Ferramentas de verificação formal - Ferramentas para verificar a exatidão funcional em contratos inteligentes e checar invariantes.

  • Serviços de auditoria de contratos inteligentes - Lista de organizações que fornecem serviços de auditoria de contratos inteligentes para projetos de desenvolvimento no Ethereum.

  • Plataformas de bug bounty - Plataformas para coordenar recompensas por bugs e recompensar a divulgação responsável de vulnerabilidades críticas em contratos inteligentes.

  • Fork Checker (opens in a new tab) - Uma ferramenta online gratuita para verificar todas as informações disponíveis sobre um contrato bifurcado.

  • ABI Encoder (opens in a new tab) - Um serviço online gratuito para codificar as funções do seu contrato em Solidity e os argumentos do construtor.

  • Aderyn (opens in a new tab) - Analisador Estático de Solidity, que percorre as Árvores de Sintaxe Abstrata (AST) para identificar suspeitas de vulnerabilidades e imprimir os problemas em um formato markdown fácil de consumir.

Ferramentas para monitorar contratos inteligentes

Ferramentas para administração segura de contratos inteligentes

  • Safe (opens in a new tab) - Carteira de contrato inteligente rodando no Ethereum que exige um número mínimo de pessoas para aprovar uma transação antes que ela possa ocorrer (M-de-N).

  • OpenZeppelin Contracts (opens in a new tab) - Bibliotecas de contratos para implementar recursos administrativos, incluindo propriedade de contrato, atualizações, controles de acesso, governança, capacidade de pausa e muito mais.

Serviços de auditoria de contratos inteligentes

  • ConsenSys Diligence (opens in a new tab) - Serviço de auditoria de contratos inteligentes que ajuda projetos em todo o ecossistema da blockchain a garantir que seus protocolos estejam prontos para o lançamento e construídos para proteger os usuários.

  • CertiK (opens in a new tab) - Empresa de segurança em blockchain pioneira no uso de tecnologia de ponta de verificação formal em contratos inteligentes e redes blockchain.

  • Trail of Bits (opens in a new tab) - Empresa de segurança cibernética que combina pesquisa de segurança com uma mentalidade de invasor para reduzir riscos e fortalecer o código.

  • PeckShield (opens in a new tab) - Empresa de segurança em blockchain que oferece produtos e serviços para a segurança, privacidade e usabilidade de todo o ecossistema da blockchain.

  • QuantStamp (opens in a new tab) - Serviço de auditoria que facilita a adoção em massa da tecnologia blockchain por meio de serviços de segurança e avaliação de riscos.

  • OpenZeppelin (opens in a new tab) - Empresa de segurança de contratos inteligentes que fornece auditorias de segurança para sistemas distribuídos.

  • Runtime Verification (opens in a new tab) - Empresa de segurança especializada em modelagem e verificação formal de contratos inteligentes.

  • Hacken (opens in a new tab) - Auditor de segurança cibernética da Web3 que traz uma abordagem de 360 graus para a segurança da blockchain.

  • Nethermind (opens in a new tab) - Serviços de auditoria em Solidity e Cairo, garantindo a integridade dos contratos inteligentes e a segurança dos usuários no Ethereum e na Starknet.

  • HashEx (opens in a new tab) - A HashEx foca na auditoria de blockchain e contratos inteligentes para garantir a segurança das criptomoedas, fornecendo serviços como desenvolvimento de contratos inteligentes, testes de penetração e consultoria em blockchain.

  • Code4rena (opens in a new tab) - Plataforma de auditoria competitiva que incentiva especialistas em segurança de contratos inteligentes a encontrar vulnerabilidades e ajudar a tornar a Web3 mais segura.

  • CodeHawks (opens in a new tab) - Plataforma de auditorias competitivas que hospeda competições de auditoria de contratos inteligentes para pesquisadores de segurança.

  • Cyfrin (opens in a new tab) - Potência em segurança da Web3, incubando a segurança cripto por meio de produtos e serviços de auditoria de contratos inteligentes.

  • ImmuneBytes (opens in a new tab) - Empresa de segurança da Web3 que oferece auditorias de segurança para sistemas blockchain por meio de uma equipe de auditores experientes e as melhores ferramentas da categoria.

  • Oxorio (opens in a new tab) - Auditorias de contratos inteligentes e serviços de segurança em blockchain com experiência em EVM, Solidity, ZK e tecnologia cross-chain para empresas cripto e projetos de finanças descentralizadas (DeFi).

  • Inference (opens in a new tab) - Empresa de auditoria de segurança, especializada em auditoria de contratos inteligentes para blockchains baseadas na EVM. Graças aos seus auditores especialistas, eles identificam possíveis problemas e sugerem soluções acionáveis para corrigi-los antes da implantação.

Plataformas de bug bounty

  • Immunefi (opens in a new tab) - Plataforma de bug bounty para contratos inteligentes e projetos DeFi, onde pesquisadores de segurança revisam códigos, divulgam vulnerabilidades, são pagos e tornam o mundo cripto mais seguro.

  • HackerOne (opens in a new tab) - Plataforma de coordenação de vulnerabilidades e bug bounty que conecta empresas a testadores de penetração e pesquisadores de segurança cibernética.

  • HackenProof (opens in a new tab) - Plataforma especializada em bug bounty para projetos cripto (DeFi, Contratos Inteligentes, Carteiras, CEX e mais), onde profissionais de segurança fornecem serviços de triagem e pesquisadores são pagos por relatórios de bugs relevantes e verificados.

  • Sherlock (opens in a new tab) - Subscritor na Web3 para segurança de contratos inteligentes, com pagamentos para auditores gerenciados via contratos inteligentes para garantir que bugs relevantes sejam pagos de forma justa.

  • CodeHawks (opens in a new tab) - Plataforma competitiva de bug bounty onde auditores participam de concursos e desafios de segurança e (em breve) de suas próprias auditorias privadas.

Publicações de vulnerabilidades e exploits conhecidos em contratos inteligentes

Desafios para aprender sobre segurança de contratos inteligentes

Melhores práticas para proteger contratos inteligentes

Tutoriais sobre segurança de contratos inteligentes