SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
20.11.2024

Lição 080: Visão Geral da Segurança em Contratos Inteligentes

Contratos inteligentes são códigos revolucionários que operam em plataformas de blockchain como o Ethereum, permitindo a criação de aplicações descentralizadas e sem a necessidade de confiança. No entanto, sua natureza descentralizada também os torna alvos para ataques maliciosos. A segurança é fundamental ao desenvolver contratos inteligentes devido à natureza irreversível das transações em blockchain. Esta lição fornece uma visão geral da segurança em contratos inteligentes, abordando vulnerabilidades comuns e melhores práticas para mitigar riscos.

Vulnerabilidades Comuns em Contratos Inteligentes

1. Reentrância

A reentrância é uma vulnerabilidade que ocorre quando um contrato chama outro contrato e esse contrato faz uma nova chamada de volta ao primeiro contrato antes que a execução da primeira chamada esteja completa. Isso pode levar a comportamentos inesperados e esgotar fundos.

Exemplo de Ataque de Reentrância

// Contrato Vulnerável
pragma solidity ^0.8.0;

contract Vulneravel {
    mapping (address => uint) public saldos;

    function depositar() public payable {
        saldos[msg.sender] += msg.value;
    }

    function retirar(uint _quantidade) public {
        require(saldos[msg.sender] >= _quantidade);
        saldos[msg.sender] -= _quantidade;
        // Chamada a contrato externo (isto é vulnerável!)
        payable(msg.sender).transfer(_quantidade);
    }
}

Mitigação

Para prevenir ataques de reentrância, use o padrão Checks-Effects-Interactions:

// Contrato Mitigado
pragma solidity ^0.8.0;

contract Seguro {
    mapping (address => uint) public saldos;

    function depositar() public payable {
        saldos[msg.sender] += msg.value;
    }

    function retirar(uint _quantidade) public {
        require(saldos[msg.sender] >= _quantidade);
        saldos[msg.sender] -= _quantidade; // Efeito primeiro
        payable(msg.sender).transfer(_quantidade); // Interação por último
    }
}

2. Overflow e Underflow de Inteiros

Overflows e underflows de inteiros ocorrem quando um cálculo excede o limite máximo ou mínimo de um tipo inteiro.

Exemplo de Overflow de Inteiro

// Contrato Vulnerável
pragma solidity ^0.8.0;

contract Overflow {
    uint8 public contador;

    function incrementar() public {
        contador += 1; // Isso causará overflow se o contador for 255
    }
}

Mitigação

Utilize a biblioteca SafeMath embutida (os inversos estão incorporados ao Solidity 0.8.0 e posterior):

// Contrato Mitigado (0.8.0 e acima)
pragma solidity ^0.8.0;

contract ContadorSeguro {
    uint8 public contador;

    function incrementar() public {
        contador += 1; // Seguro em Solidity 0.8.0+
    }
}

3. Limite de Gas e Laços

Contratos inteligentes têm um limite de gas finito por transação, e laços extensivos podem potencialmente exceder esse limite, levando a falhas na execução.

Exemplo de Problema com Limite de Gas

// Contrato Vulnerável
pragma solidity ^0.8.0;

contract Laço {
    uint[] public numeros;

    function adicionarNumeros(uint _quantidade) public {
        for (uint i = 0; i < _quantidade; i++) {
            numeros.push(i);
        }
    }
}

Mitigação

Evite laços sem limites e garanta que as mudanças de estado possam ocorrer dentro das restrições de limite de gas:

// Contrato Mitigado
pragma solidity ^0.8.0;

contract LaçoSeguro {
    uint[] public numeros;

    function adicionarNumeros(uint _quantidade) public {
        require(_quantidade <= 100, "A quantidade é muito alta"); // Limitar a quantidade para mitigar problemas de gas
        for (uint i = 0; i < _quantidade; i++) {
            numeros.push(i);
        }
    }
}

4. Front-Running

O front-running ocorre quando um agente malicioso vê uma transação pendente e envia sua própria transação com uma taxa de gas mais alta para avançar na fila de transações.

Mitigação

Para mitigar o front-running, use técnicas como esquemas de compromisso-revelação ou carimbos de data e hora:

pragma solidity ^0.8.0;

contract Leilão {
    struct Lance {
        uint quantidade;
        address licitante;
    }

    Lance public maiorLance;
    mapping(address => uint) public retornosPendentes;

    function darLance() public payable {
        require(msg.value > maiorLance.quantidade, "Lance não é alto o suficiente");

        // Armazenar o retorno pendente do licitante anterior
        retornosPendentes[maiorLance.licitante] += maiorLance.quantidade;

        // Atualizar o maior lance
        maiorLance = Lance(msg.value, msg.sender);
    }
}

Melhores Práticas para Segurança em Contratos Inteligentes

  1. Realizar Auditorias de Segurança: Realize auditorias regularmente em seus contratos por empresas de segurança terceirizadas ou use ferramentas automatizadas para detectar vulnerabilidades.

  2. Implementar Mecanismos de Segurança: Desenhe contratos com funções pausáveis ou carteiras multisig para interromper operações em caso de problemas detectados.

  3. Usar Contratos Atualizáveis com Cuidado: Implemente padrões de proxy cuidadosamente para permitir futuras atualizações sem perder o estado.

  4. Teste Minuciosamente: Crie casos de teste abrangentes que cubram vários cenários, especialmente casos extremos, para identificar possíveis vulnerabilidades.

  5. Interação com a Comunidade: Engaje-se com a comunidade de desenvolvedores para compartilhar conhecimento e manter-se atualizado com as últimas tendências de segurança.

Conclusão

A segurança em contratos inteligentes é um aspecto crítico do desenvolvimento. Compreender vulnerabilidades comuns e implementar melhores práticas ajuda a construir aplicações mais seguras. Lembre-se sempre de que, uma vez implantados, os contratos inteligentes não podem ser alterados, tornando a segurança uma prioridade desde o início. Através da diligência em práticas de segurança, os desenvolvedores podem ajudar a proteger os usuários e manter a confiança em aplicações descentralizadas.

Video

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

Thank you for voting!