Um tipo comum de vulnerabilidade em smart contracts: Reentrância e como evitá‑la
Os smart contracts são programas autoexecutáveis que rodam em blockchains como o Ethereum. Embora ofereçam transparência e automação, eles também trazem novos desafios de segurança. Entre as vulnerabilidades mais estudadas e exploradas pelos atacantes está a reentrância, um problema que já causou perdas de milhões de dólares, como no caso do DAO hack em 2016.
Este artigo aprofunda o conceito de reentrância, explica como ela funciona, apresenta exemplos práticos e, sobretudo, oferece um guia passo‑a‑passo para prevenir essa vulnerabilidade em seus contratos.
1. O que é reentrância?
Reentrância ocorre quando um contrato chama uma função externa que, por sua vez, chama de volta (re‑entra) a função original antes que o estado interno tenha sido completamente atualizado. Essa chamada recursiva permite que o invasor manipule o contrato, geralmente retirando fundos de forma indevida.
Em termos simples, imagine que o contrato A tem uma função withdraw() que envia Ether para o usuário e depois atualiza o saldo. Se o endereço do usuário for um contrato malicioso que tem um fallback function, ele pode chamar withdraw() novamente antes que o saldo seja reduzido, drenando o contrato.
2. Como a reentrância foi explorada na prática
O caso mais famoso foi o ataque ao DAO em 2016, que resultou em um roubo de cerca de 3,6 milhões de ETH. O atacante utilizou um contrato que chamava repetidamente a função de retirada enquanto o saldo ainda não havia sido atualizado, esgotando os fundos disponíveis.
Outro exemplo recente ocorreu em um contrato DeFi que permitia empréstimos flash. O invasor criou um contrato que, ao receber o empréstimo, chamava a função de retirada antes que o contrato registrasse o pagamento, gerando lucro ilícito.
3. Identificando pontos vulneráveis
Os desenvolvedores devem ficar atentos a três padrões que favorecem a reentrância:
- Chamadas externas antes da atualização de estado: sempre atualize variáveis críticas antes de enviar Ether ou chamar outro contrato.
- Uso de
call()sem verificação adequada:call()é a forma mais baixa de interação e pode executar código arbitrário no contrato de destino. - Falta de restrição de reentrância: não usar mecanismos como mutex ou reentrancy guard deixa o contrato vulnerável.
4. Boas práticas para prevenir a reentrância
A seguir, um checklist de segurança que você pode aplicar imediatamente:

4.1. Atualize o estado antes da chamada externa
Reordene o código para que todas as variáveis de controle sejam modificadas antes de enviar fundos. Exemplo:
// Código vulnerável
function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);
msg.sender.call{value: _amount}(); // chamada externa
balances[msg.sender] -= _amount; // atualização depois
}
// Código seguro
function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount; // atualização primeiro
(bool success, ) = msg.sender.call{value: _amount}();
require(success, "Transfer failed");
}
4.2. Use o padrão Checks‑Effects‑Interactions
Esse padrão recomenda: Check (verificar condições), Effect (alterar estado) e Interaction (interagir com outros contratos). Seguir essa ordem reduz drasticamente a chance de reentrância.
4.3. Utilize ReentrancyGuard da OpenZeppelin
A biblioteca OpenZeppelin oferece um contrato ReentrancyGuard que implementa um mutex simples. Basta herdar dele e marcar as funções críticas com o modificador nonReentrant:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MyVault is ReentrancyGuard {
mapping(address => uint) private balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint _amount) external nonReentrant {
require(balances[msg.sender] >= _amount, "Insufficient balance");
balances[msg.sender] -= _amount;
(bool success, ) = msg.sender.call{value: _amount}();
require(success, "Transfer failed");
}
}
4.4. Evite call() quando transfer() ou send() forem suficientes
Embora transfer() e send() imponham um limite de gás (2300), eles ainda podem ser descontinuados em futuras atualizações da EVM. Portanto, a abordagem recomendada hoje é usar call() com verificações explícitas de sucesso, combinada com ReentrancyGuard.
4.5. Limite o uso de fallback/receive functions
Contratos que não precisam receber Ether devem evitar implementar fallback ou receive functions que executam código complexo. Mantenha-as vazias ou apenas emitam eventos.
5. Ferramentas de auditoria e teste
Antes de lançar um contrato, use ferramentas automáticas para detectar padrões de reentrância:
- Slither – analisador estático de código Solidity.
- Echidna – fuzzer de propriedades para contratos.
- Manticore – framework de análise simbólica.
Além disso, serviços de auditoria profissional, como ConsenSys Diligence, podem validar a segurança do seu código antes do deployment.

6. Estudos de caso e lições aprendidas
Vamos analisar dois contratos reais que sofreram com reentrância e como corrigiram o problema.
6.1. DAO – O ataque histórico
O DAO não possuía nenhum mecanismo de proteção contra reentrância. A solução adotada pela comunidade foi um hard fork para reverter o estado e devolver os fundos aos investidores. Para saber mais sobre hard forks e seu impacto, confira o artigo Hard Fork: O que é, como funciona e seu impacto nas criptomoedas.
6.2. Um pool de liquidez DeFi vulnerável
Um contrato de pool permitia que usuários retirassem tokens sem atualizar o saldo interno. Após a exploração, a equipe implementou ReentrancyGuard e migrou para a versão 0.8.x do Solidity, que inclui verificações de overflow e recursos de segurança aprimorados.
7. Checklist rápido antes do deployment
- Aplicar o padrão Checks‑Effects‑Interactions.
- Usar
ReentrancyGuardou implementar mutex manual. - Preferir
call()com verificação de sucesso e limite de gás adequado. - Executar análise estática com Slither e fuzzing com Echidna.
- Realizar auditoria externa ou revisão de pares.
- Documentar e comentar claramente todas as funções críticas.
Seguindo essas etapas, você reduz drasticamente o risco de ser vítima de um ataque de reentrância.
Conclusão
A reentrância continua sendo uma das vulnerabilidades mais perigosas em smart contracts, mas, felizmente, ela é totalmente evitável com boas práticas de desenvolvimento e auditoria rigorosa. Ao adotar o padrão Checks‑Effects‑Interactions, usar guardas de reentrância como as fornecidas pela OpenZeppelin e validar seu código com ferramentas de análise, você protege seu projeto e seus usuários contra perdas significativas.
Fique atento às atualizações da linguagem Solidity e às novas recomendações da comunidade. A segurança nunca é estática – ela evolui junto com a tecnologia.