SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
20.11.2024

Lição: 082: Guardas de Reentrância

Nesta aula, iremos explorar o conceito de reentrância em contratos inteligentes e como nos defender contra isso utilizando guardas de reentrância. A reentrância é um vetor de ataque comum no Ethereum que pode permitir que usuários maliciosos explorem a forma como seus contratos gerenciam chamadas de função. Esta aula fornece uma visão geral da reentrância, suas implicações e exemplos práticos para ajudá-lo a proteger seus contratos inteligentes.

O que é Reentrância?

A reentrância ocorre quando uma função pode ser chamada novamente antes que sua execução anterior esteja completa. Isso pode levar a consequências indesejadas, especialmente em funções que modificam estados ou gerenciam transferências de ether. O exemplo mais famoso de um ataque de reentrância é o hack do DAO em 2016, onde os atacantes conseguiram retirar fundos repetidamente antes que o contrato tivesse a chance de atualizar seu estado, resultando em perdas significativas.

Compreendendo as Guardas de Reentrância

Para mitigar ataques de reentrância, você pode usar um padrão conhecido como guarda de reentrância. Uma guarda de reentrância impede que uma função seja invocada enquanto ainda está em execução. Isso é tipicamente alcançado usando uma variável booleana que rastreia se uma chamada de função está em andamento.

Implementação de uma Guarda de Reentrância

Aqui está como você pode implementar uma simples guarda de reentrância em um contrato inteligente Solidity:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExemploReentrancia {
    bool private bloqueado;

    constructor() {
        bloqueado = false;
    }

    modifier naoReentrante() {
        require(!bloqueado, "GuardaDeReentrancia: chamada reentrante");
        bloqueado = true;
        _;
        bloqueado = false;
    }

    mapping(address => uint) public saldos;

    function depositar() public payable {
        require(msg.value > 0, "Você deve enviar algum Ether");
        saldos[msg.sender] += msg.value;
    }

    function retirar(uint _quantidade) public naoReentrante {
        require(saldos[msg.sender] >= _quantidade, "Saldo insuficiente");

        // Atualiza o estado antes de enviar Ether para evitar reentrância
        saldos[msg.sender] -= _quantidade;

        // Envia Ether
        (bool sucesso, ) = msg.sender.call{value: _quantidade}("");
        require(sucesso, "Transferência falhou");
    }
}

Explicação do Código

  1. Bloqueio Booleano: Definimos uma variável booleana privada bloqueado para rastrear se a função está sendo executada.

  2. Modifier: Criamos um modifier naoReentrante que verifica o estado de bloqueado. Se o estado for true, a chamada é revertida com uma mensagem de erro. Se for false, o bloqueado é definido como true no início da execução e redefinido para false antes de finalizar.

  3. Função de Depósito: Os usuários podem depositar ether, o que simplesmente adiciona o valor enviado ao seu saldo.

  4. Função de Retirada: Antes de realizar qualquer transferência de ether, devemos garantir que o usuário tenha saldo suficiente e atualizar seu saldo em conformidade. A operação de envio real acontece após a atualização do saldo, reduzindo significativamente o risco de reentrância.

Melhores Práticas para Usar Guardas de Reentrância

  • Ordem de Execução: Sempre atualize o estado do contrato (por exemplo, saldos) antes de fazer chamadas externas (por exemplo, transferindo ether).
  • Chamadas Externas Mínimas: Minimize o número de chamadas de função externas em seu contrato para reduzir a superfície de ataque.
  • Use Bibliotecas Estabelecidas: Sempre que possível, utilize bibliotecas bem auditadas, como a ReentrancyGuard do OpenZeppelin, para implementar essas proteções em vez de codificar suas próprias.

Usando a Guarda de Reentrância do OpenZeppelin

O OpenZeppelin fornece uma implementação robusta de uma guarda de reentrância que você pode facilmente integrar em seus contratos. Veja como você pode fazer isso:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ExemploReentranciaOpenZeppelin é ReentrancyGuard {
    mapping(address => uint) public saldos;

    function depositar() public payable {
        require(msg.value > 0, "Você deve enviar algum Ether");
        saldos[msg.sender] += msg.value;
    }

    function retirar(uint _quantidade) public naoReentrante {
        require(saldos[msg.sender] >= _quantidade, "Saldo insuficiente");

        saldos[msg.sender] -= _quantidade;

        (bool sucesso, ) = msg.sender.call{value: _quantidade}("");
        require(sucesso, "Transferência falhou");
    }
}

Conclusão

Os ataques de reentrância representam um risco significativo para contratos inteligentes no Ethereum. Ao usar guardas de reentrância, você pode mitigar efetivamente esses riscos e garantir que seu contrato opere de maneira segura. Sempre lembre-se de seguir as melhores práticas, atualizar o estado do contrato antes de fazer chamadas externas e aproveitar bibliotecas estabelecidas para aumentar a segurança de seus contratos inteligentes.

Video

Did you like this article? Rate it from 1 to 5:

Thank you for voting!