🇧🇷
Ethereum Developer Pack - PT
  • Ethereum Developer Pack - PT
  • Módulo 1
    • Introdução a Smart Contracts
      • Fundamentos de Blockchain
        • Histórico
        • Bitcoin
        • O que é Blockchain
        • Conceitos-chave em Blockchain
        • Como funciona a Blockchain
        • Tipos de Blockchain
        • Modelos de Consenso
      • A nova Internet
        • Web 3
        • Elementos Fundamentais
        • Impacto do Ethereum em Diversos Setores
      • Wallets
        • Componentes de uma wallet
        • Tipos de Wallet
        • Códigos Mnemônicos
      • Ethereum 101
        • Smart Contracts
        • Contas
          • Tipos de contas
          • Conteúdo das contas
        • Transações
          • Componentes
          • Ciclo de vida
        • Gás
        • Solidity
        • EVM
          • A máquina de estados
          • Opcodes
          • Como funciona a EVM
          • Clientes de execução
          • DApps
      • Blockchain Explorer
        • Funções de um blockchain explorer
        • Beneficios de utilizar um blockchain explorer
      • Remix
        • Características do Remix
        • Workspaces ou espaços de trabalho
        • Carregar e compilar um contrato
        • Implantar na máquina virtual do Remix (Remix VM)
        • Interagindo com funções
        • Deployar em uma rede pública
      • Crie seu primeiro Smart Contract
  • Módulo 2
    • Fundamentos de Solidity
      • Hello World
      • Tipos de Dados
      • Funções
      • Variáveis
        • Exercício 1
      • Operadores
        • Ejercicio 2
      • Constructor
        • Exercício 3
      • Convenções de nomenclatura
      • Tipos de armazenamento para variáveis
      • Estruturas de Controle
        • Exercício 4
      • Modificadores
      • Eventos
        • Exercício 5
      • Tipos de Referencia
        • Arrays
          • Exercício 6
        • Mappings
          • Exercício 7
        • Structs
          • Exercício 8
      • Address Payable
      • Como os contratos e funções recebem Ether.
      • Transferências de Ether
      • Conceitos Avançados
        • Codificação de ABI
        • Hashing
        • This
        • Herança
        • Abstract
        • Interface
        • Chamada entre contratos
        • EVM
        • ABI
        • Bytecode
        • Opcodes
  • Módulo 3
    • ERCs, Bibliotecas e Padrões de Desenvolvimento
      • Boas Práticas de Desenvolvimento
      • Padrões de Desenvolvimento
      • EIP & ERC
      • ERC-20
      • ERC-721
      • Open Zeppelin
      • Crie um Token ERC-20
      • Almacenamiento Descentralizado: IPFS
      • Crea un Token ERC-721
      • DeFi
  • Módulo 4
    • Kit de ferramentas para desenvolvimento na Ethereum
      • Requisitos para o módulo 4
        • Terminal
        • Git e Github
        • Node.js e npm
        • Visual Studio Code para Solidity
      • Toolkit
        • JSON-RPC
        • Ethers.js
          • Exercício
        • Hardhat
          • Implantação de um contrato no Hardhat
          • Implantação de um contrato em uma rede pública
        • Scaffold-ETH
          • Características do Scaffold-ETHCaracterísticas
          • Como instalar o Scaffold-ETH
  • Módulo 5
    • Segurança, Testes e Auditorias
      • Testes
        • Importância de realizar testes
        • Métodos para testar contratos inteligentes
          • Testes automatizados
          • Testes manuais
        • Conceitos importantes em testes
        • Ferramentas para testes
        • Testes com Hardhat
        • Recursos adicionais
      • Segurança
        • Uma mentalidade diferente de design
        • Principais vulnerabilidades em contratos inteligentes
          • Reentrancy attack (ataque de reentrada)
          • Replay attack (ataque de repetición)
          • Price Oracle Manipulation (Manipulación de Oráculos de Precios)
          • Missing Access Control (Pérdida de Control de Acceso)
          • Reward Manipulation (Manipulación de Recompensas)
          • Failure to Initialize (Falla al Inicializar)
          • Front-running
          • Invariant Breaks (Quebra de Invariantes)
          • Mishandling of ETH (Má gestão de ETH)
          • Denial of Service (DoS - Negação de Serviço)
          • Integer overflow and underflow (Overflow e Underflow de inteiros)
          • Phishing y Typosquatting
        • Recursos adicionais
      • Auditoria de smart contracts
        • Processo de Auditoria
        • Ferramentas
        • Como se preparar para uma auditoria
        • O teste Rekt
        • Desafios
        • Recursos adicionais
  • Contribuye
    • Kipu Explorer
Powered by GitBook
On this page
  • 1. Padrão de Verificação-Efeitos-Interação (Checks-Effects-Interactions)
  • 2. Padrão de Retirada (Withdrawal Pattern)
  • 3. Padrão de Acesso Restrito (Access Restriction)
  • 4. Padrão de Parada de Emergência (Emergency Stop)
  • 5. Padrão de Fábrica de Contratos (Factory Pattern)
  • 6. Padrão de Máquina de Estados (State Machine)
  • 7. Patrón para obtener datos de un oráculo
  • 8. Padrão de Contratos Atualizáveis (Upgradeable Contracts)
  • 9. Padrão de Mapa Iterável
  • 10. Padrão de Lista de Endereços
  • 11. Padrão de Comparação de Strings
  1. Módulo 3
  2. ERCs, Bibliotecas e Padrões de Desenvolvimento

Padrões de Desenvolvimento

No desenvolvimento de contratos inteligentes com Solidity, a aplicação de padrões de design comprovados pode melhorar significativamente a segurança, eficiência e manutenção do código. Esses padrões ajudam a resolver problemas recorrentes no desenvolvimento de software e são especialmente importantes no Ethereum, onde erros podem ser custosos e a otimização de gas é essencial. A seguir, apresentamos alguns padrões de design importantes em Solidity:

1. Padrão de Verificação-Efeitos-Interação (Checks-Effects-Interactions)

Este padrão ajuda a prevenir ataques de reentrância ao garantir que chamadas a contratos externos sejam realizadas apenas no final de uma função. Sua estrutura segue três etapas:

  • Verificação: Primeiro, valide todas as condições e entradas da função.

  • Efeitos: Em seguida, atualize o estado do contrato.

  • Interação: Por último, realize interações com outros contratos.

Exemplo deste padrão:

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

contract SafeWithdrawal {
    mapping(address => uint) public balances;
    
    // Função para depositar Ether no contrato
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
    
    // Função para saque, seguindo o padrão de Verificação-Efeitos-Interação
    function withdraw(uint _amount) public {
        // Verificação: Confirma se o remetente tem saldo suficiente para sacar
        require(balances[msg.sender] >= _amount, "Saldo insuficiente");
    
        // Efeito: Atualiza o saldo antes da interação
        balances[msg.sender] -= _amount;
    
        // Interação: Transfere Ether para o remetente
        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "Falha ao enviar Ether");
    }
    
    // Função para consultar o saldo do contrato
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

2. Padrão de Retirada (Withdrawal Pattern)

Em vez de enviar Ether diretamente aos usuários (por exemplo, com send ou transfer), este padrão permite que os usuários retirem os fundos por conta própria. Isso reduz o risco de erros e ataques de reentrância.

contract WithdrawalContract {
    mapping(address => uint) public balances;

    function withdraw() public {
        uint amount = balances[msg.sender];
        require(amount > 0);

        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success);
    }
}

3. Padrão de Acesso Restrito (Access Restriction)

Este padrão é utilizado para restringir o acesso a determinadas funções do contrato a usuários específicos. É comumente implementado por meio de modificadores de função.

contract RestrictedAccess {
    address public owner = msg.sender;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function restrictedAction() public onlyOwner {
        // Lógica restringida
    }
}

Da mesma forma que, neste caso, o acesso é restrito para que apenas o proprietário (Owner) possa acessar a função restrita, é possível criar modificadores para outros perfis, como administradores ou pessoas autorizadas.

4. Padrão de Parada de Emergência (Emergency Stop)

Este padrão, também conhecido como "Circuit Breaker", permite pausar a execução de certas funções críticas em caso de emergência ou quando se detecta um comportamento anômalo. Este padrão é particularmente útil para prevenir danos maiores, como a perda de fundos ou a exploração de vulnerabilidades, até que o problema possa ser investigado e resolvido.

Para implementar esse padrão, normalmente se utiliza uma variável de estado que indica se o contrato está no modo "pausado" ou não. As funções que podem ser vulneráveis a ataques ou falhas são modificadas para que sua execução dependa do estado dessa variável. Além disso, implementam-se funções para alterar o estado dessa variável, permitindo ativar ou desativar o "modo de emergência". Essas funções de controle devem ser restritas a endereços autorizados para evitar o mau uso.

A seguir, um exemplo simples de como implementá-lo:

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

contract EmergencyStopExample {
    address private owner;
    bool private paused;

    modifier onlyOwner() {
        require(msg.sender == owner, "No eres el propietario");
        _;
    }

    modifier whenPaused() {
        require(paused, "El contrato no está pausado");
        _;
    }

    event Paused();
    event Unpaused();
		event FundsWithdrawn(address owner, uint amount);
		
    constructor() {
        owner = msg.sender;
        paused = false;
    }

    function pause() public onlyOwner {
        paused = true;
        emit Paused();
    }

    function unpause() public onlyOwner {
        paused = false;
        emit Unpaused();
    }

    function emergencyWithdraw() public onlyOwner whenPaused {
        // Supondo que essa função saca todo o saldo do contratopro endereço do proprietário.
        uint balance = address(this).balance;
        (bool sent, ) = owner.call{value: balance}("");
        require(sent, "Fallo al enviar Ether");
        emit FundsWithdrawn(owner, balance);
    }

    // Outras funções também precisam checar o estado do contrato utilizando o modificador `whenNotPaused`
}

Considerações:

  • Autorização: É crucial restringir quem pode ativar ou desativar o modo de emergência, geralmente o proprietário do contrato ou um conjunto de endereços confiáveis.

  • Transparência: A capacidade de colocar o contrato em modo de emergência deve ser comunicada claramente aos usuários, explicando em quais circunstâncias isso será utilizado.

  • Recuperação: Deve haver um plano claro sobre como os problemas que levaram à ativação do modo de emergência serão resolvidos e como a normalidade será retomada.

5. Padrão de Fábrica de Contratos (Factory Pattern)

Este padrão permite a criação de novos contratos a partir de outro contrato. Ele é especialmente útil quando é necessário criar várias instâncias de um contrato com configurações semelhantes ou quando se deseja centralizar a lógica de criação de contratos para facilitar a manutenção e a atualização. A "fábrica" atua como um criador centralizado que pode gerar instâncias de contratos sob demanda. A seguir, é mostrado um exemplo simples de como implementar o padrão Factory Contract. Neste exemplo, o contrato ChildContract será o contrato que a fábrica produz, e o FactoryContract atuará como a fábrica que cria instâncias do ChildContract.

Contrato Filho Primeiro, definimos o contrato que queremos produzir. Este contrato pode ser qualquer coisa, mas, para fins deste exemplo, será um contrato simples que armazena um número.

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

// Contrato filho que nossa fábrica produzirá
contract ChildContract {
    uint public number;

    // Construtor que inicializa o contrato com um número específico
    constructor(uint _number) {
        number = _number;
    }
}

Contrato Fábrica

O contrato fábrica será responsável por criar novas instâncias do contrato filho. Ele manterá um registro de todas as instâncias criadas para poder interagir com elas posteriormente, caso necessário.

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

// Referência ao contrato filho
import "./ChildContract.sol";

contract FactoryContract {
    // Array para armazenar os endereços dos contratos filhos criados
    ChildContract[] public children;

    event ChildCreated(uint number, address childAddress);

    // Função para criar um novo contrato filho
    function createChild(uint _number) public {
        ChildContract child = new ChildContract(_number);
        children.push(child);
        emit ChildCreated(_number, address(child));
    }

    // Função para obter o endereço de um contrato filho no array
    function getChild(uint _index) public view returns (ChildContract) {
        require(_index < children.length, "Índice fora dos limites");
        return children[_index];
    }
}

Neste exemplo, o FactoryContract tem uma função createChild que implanta uma nova instância do ChildContract com um número específico. Cada nova instância do ChildContract é armazenada no array children, e um evento ChildCreated é emitido, registrando o número atribuído e o endereço do novo contrato filho.

Este padrão é poderoso porque permite a criação de contratos de maneira programática, o que facilita a gestão de múltiplas instâncias de contratos e a centralização da lógica de criação de contratos. Além disso, ao manter um registro de todos os contratos criados, o contrato fábrica pode interagir com eles ou fornecer funcionalidades adicionais, como a gestão ou atualização dos contratos filhos.

6. Padrão de Máquina de Estados (State Machine)

Este padrão é uma forma eficaz de gerenciar o ciclo de vida de um contrato, modelando explicitamente os diferentes estados pelos quais um contrato pode passar e as transições permitidas entre esses estados. Este padrão é particularmente útil para contratos com lógicas de negócios complexas que precisam lidar com diferentes fases ou etapas de maneira clara e segura, como contratos de votação, leilões ou crowdfunding.

Implementação básica do padrão State Machine

Para implementar uma máquina de estados em um contrato inteligente, você pode seguir estes passos:

  1. Definir os estados: Use um enum para definir todos os possíveis estados do contrato.

  2. Armazenar o estado atual: Use uma variável para armazenar o estado atual do contrato.

  3. Restringir funções a estados específicos: Use modificadores para permitir que certas funções sejam executadas apenas em estados específicos.

  4. Alterar de estado: Implemente funções que alterem o estado do contrato de maneira controlada.

Veja a seguir um exemplo simplificado de um contrato de leilão que utiliza o padrão State Machine:

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

contract AuctionStateMachine {
    enum Stages {
        AcceptingBlindBids,
        RevealBids,
        Finished
    }

    // Variável para armazenar o estado atual do contrato
    Stages public stage = Stages.AcceptingBlindBids;

    // Variável para armazenar o tempo em que o contrato avança para o próximo estado
    uint public nextStageTime = block.timestamp + 1 days;

    // Modificador para controlar o acesso às funções com base no estado atual
    modifier atStage(Stages _stage) {
        require(stage == _stage, "Função não permitida no estado atual.");
        _;
    }

    // Modificador para avançar para o próximo estado baseado no tempo
    modifier transitionNext() {
        if (block.timestamp >= nextStageTime) {
            nextStage();
        }
        _;
    }

    // Função para avançar para o próximo estado
    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
        nextStageTime = block.timestamp + 1 days;
    }

    // Exemplo de função que pode ser chamada em um estado específico
    function acceptBlindBid() public atStage(Stages.AcceptingBlindBids) transitionNext {
        // Lógica para aceitar uma proposta cega
    }

    function revealBids() public atStage(Stages.RevealBids) transitionNext {
        // Lógica para revelar as propostas
    }

    // Lógica para finalizar o leilão, que pode ser implementada ao mudar para o estado `Finished`
}

Neste contrato de leilão, existem três estados definidos: AcceptingBlindBids (aceitando propostas cegas), RevealBids (revelando propostas) e Finished (finalizado). O contrato começa no estado AcceptingBlindBids e avança sequencialmente pelos estados com base no tempo (a cada dia, avança para o próximo estado). São usados modificadores para garantir que certas funções só possam ser executadas em seus respectivos estados, e há uma função nextStage que gerencia a transição entre estados.

Benefícios do padrão Máquina de Estados:

  • Clareza: Facilita a compreensão do fluxo lógico do contrato e suas diferentes fases.

  • Segurança: Ajuda a prevenir a execução não autorizada de funções e garante que o contrato execute apenas operações válidas em seu estado atual.

  • Flexibilidade: Permite gerenciar complexidades em contratos com múltiplas fases ou etapas de maneira estruturada.

7. Patrón para obtener datos de un oráculo

O uso de oráculos em contratos inteligentes do Ethereum permite acessar dados externos à blockchain, o que é essencial para muitas aplicações descentralizadas (DApps) que precisam interagir com o mundo real. No entanto, o Ethereum e outras blockchains semelhantes não podem acessar diretamente dados externos devido à sua natureza isolada e determinista. É aqui que entram os oráculos, atuando como intermediários que trazem informações do exterior para a blockchain.

Um oráculo é um serviço (geralmente operado por um terceiro) que envia dados do mundo real para a blockchain. Esses dados podem ser qualquer coisa, desde preços de ativos e resultados esportivos até a temperatura de uma cidade. Os oráculos desempenham um papel crucial no funcionamento de muitos tipos de DApps, como as financeiras (DeFi), as de seguros e as de jogos de azar, entre outras.

Implementação Básica A implementação de um oráculo em Solidity geralmente segue esses passos:

  1. Solicitação de Dados: O contrato inteligente envia uma solicitação de dados para o oráculo. Essa solicitação pode ser o resultado de uma ação do usuário ou de outra função do contrato.

  2. Obtenção de Dados: O oráculo recebe a solicitação, obtém os dados necessários do mundo real e os envia de volta para a blockchain.

  3. Processamento de Dados: O contrato inteligente recebe os dados e executa a lógica correspondente com essas informações.

Exemplo com Chainlink Chainlink é um dos serviços de oráculo mais populares e amplamente utilizados no ecossistema Ethereum. A seguir, é mostrado um exemplo básico de como um contrato inteligente pode interagir com o Chainlink para obter o preço do ETH em USD.

Primeiro, certifique-se de ter importado as dependências do Chainlink no seu arquivo Solidity, o que geralmente é feito por meio de importações de NPM ou diretamente com URLs.

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

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract PriceConsumerV3 {

    AggregatorV3Interface internal priceFeed;

    /**
     * Rede: Ethereum Mainnet
     * Agregador: ETH/USD
     * Endereço: 0x... (Endereço do contrato do Chainlink para ETH/USD)
     */
    constructor() {
        priceFeed = AggregatorV3Interface(0x...); // Endereço do oráculo Chainlink para ETH/USD
    }

    /**
     * Retorna o preço mais recente de ETH/USD
     */
    function getLatestPrice() public view returns (int) {
        (
            /* uint80 roundID */,
            int price,
            /* uint startedAt */,
            /* uint timeStamp */,
            /* uint80 answeredInRound */
        ) = priceFeed.latestRoundData();
        return price; // O preço é retornado com 8 casas decimais
    }
}

Neste exemplo, o contrato PriceConsumerV3 utiliza a interface AggregatorV3Interface do Chainlink para interagir com um contrato oráculo do Chainlink que fornece o preço atual do ETH em USD. A função getLatestPrice consulta o último dado de preço disponível e o retorna.

Ao trabalhar com oráculos, é crucial considerar a confiabilidade e a segurança do provedor de dados. Dependendo de um único oráculo, pode-se introduzir um ponto de falha centralizado. Para mitigar isso, algumas aplicações utilizam múltiplos oráculos ou serviços de oráculos descentralizados como o Chainlink, que agregam dados de várias fontes para fornecer uma medida mais confiável e resistente à manipulação.

8. Padrão de Contratos Atualizáveis (Upgradeable Contracts)

Este padrão, comumente conhecido como o padrão 'Proxy' ou 'Contratos Atualizáveis', é uma técnica avançada que permite aos desenvolvedores alterar o código de um contrato inteligente após ele ter sido implantado na blockchain. Dado que o código de um contrato inteligente é imutável após a implantação, esse padrão oferece uma forma flexível de atualizar a lógica do contrato sem perder o estado ou os dados armazenados, nem mudar o endereço do contrato.

Como Funciona o Padrão Proxy

O padrão se baseia em dois componentes principais: o contrato Proxy e o contrato de Implementação (ou lógica).

  • Contrato Proxy: É o contrato com o qual os usuários interagem diretamente. Ele mantém o estado do contrato e delega chamadas a um contrato de implementação que contém a lógica do negócio. O endereço do contrato proxy permanece constante, mesmo durante as atualizações.

  • Contrato de Implementação: Contém a lógica do negócio e o código que pode ser atualizado. Quando a lógica do contrato é atualizada, um novo contrato de implementação é implantado, e o proxy é atualizado para apontar para o novo endereço.

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

// Contrato de Implementação
contract LogicContractV1 {
    uint public counter;

    function increment() public {
        counter += 1;
    }
}

// Contrato Proxy
contract Proxy {
    address public implementation;

    constructor(address _logic) {
        implementation = _logic;
    }

    function upgrade(address _newImplementation) external {
        implementation = _newImplementation;
    }

    fallback() external payable {
        address _impl = implementation;
        require(_impl != address(0));
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(ptr, 0, size)
            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }
}

Neste exemplo, o Proxy delega todas as chamadas para o LogicContractV1. Se quisermos atualizar a lógica, implantaríamos um novo contrato de implementação (por exemplo, LogicContractV2) e, em seguida, chamaríamos o método upgrade no Proxy para mudar o endereço da implementação.

Considerações de Segurança:

  1. Armazenamento consistente: É crucial garantir que a estrutura de armazenamento permaneça consistente entre as versões dos contratos de implementação para evitar problemas de corrupção de dados.

  2. Transparência das atualizações: As atualizações devem ser feitas com cautela para manter a confiança dos usuários, idealmente por meio de um processo de governança claro ou um período de aviso antes de realizar mudanças significativas.

  3. Controle de acesso: O contrato Proxy deve ter controles de acesso robustos para garantir que apenas entidades autorizadas possam atualizar o contrato de implementação.

9. Padrão de Mapa Iterável

Implementar um mapa iterável em Solidity permite combinar as vantagens dos mapas (acesso rápido aos dados por chave) com as dos arrays (capacidade de iterar sobre os elementos). Como o Solidity não oferece nativamente uma estrutura de dados que seja ao mesmo tempo um mapa e permita a iteração sobre seus elementos, é necessário projetar uma estrutura que atenda a ambos os requisitos.

A seguir, veremos uma implementação detalhada do padrão de mapa iterável, o qual é composto por um mapa para armazenar os dados e um array para manter a ordem das chaves e permitir a iteração.

Implementação

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

contract MapaIterável {
    // Estrutura para armazenar os dados junto com um flag para marcar se o elemento está no array
    struct DadosDoMapa {
        uint valor;
        bool existe;
    }

    // Mapa para armazenar os dados associados a uma chave
    mapping(address => DadosDoMapa) public mapaDeDados;

    // Array para manter a ordem das chaves
    address[] public chaves;

    // Função para adicionar ou atualizar um valor
    function set(address chave, uint valor) public {
       // Se for a primeira vez que a chave é adicionada, ela também será adicionada ao array
        if (!mapaDeDados[chave].existe) {
            chaves.push(chave);
            mapaDeDados[chave] = DadosDoMapa(valor, true);
        } else {
            // Se já existia, apenas atualizamos o valor
            mapaDeDados[chave].valor = valor;
        }
    }

    // Função para obter um valor
    function get(address chave) public view returns (uint) {
        require(mapaDeDados[chave].existe, "A chave não existe");
        return mapaDeDados[chave].valor;
    }

    // Função para remover um elemento
    function remove(address chave) public {
        require(mapaDeDados[chave].existe, "A chave não existe");

        // Remover a chave do mapa
        delete mapaDeDados[chave];

        // Encontrar o índice da chave no array e removê-la
        for (uint i = 0; i < chaves.length; i++) {
            if (chaves[i] == chave) {
                // Mover o último elemento para o lugar do elemento removido e depois excluir o último elemento
                chaves[i] = chaves[chaves.length - 1];
                chaves.pop();
                break;
            }
        }
    }

    // Função para obter o tamanho do mapa
    function tamanho() public view returns (uint) {
        return chaves.length;
    }

    // Função para verificar se uma chave existe no mapa
    function existe(address chave) public view returns (bool) {
        return mapaDeDados[chave].existe;
    }

    // Função para obter uma chave em um índice específico
    function obterChaveNoIndice(uint indice) public view returns (address) {
        require(indice < chaves.length, "Índice fora do intervalo");
        return chaves[indice];
    }
}

Características e considerações

  • Flexibilidade: Este padrão permite tanto a recuperação de valores por chave quanto a iteração sobre todas as entradas.

  • Gestão de estado: A consistência entre o mapa e o array é mantida, garantindo que as operações de adicionar, atualizar e remover se reflitam em ambos.

  • Eficiência de Gas: A remoção e adição de elementos, especialmente em um conjunto grande, podem consumir uma quantidade significativa de gas devido às operações sobre o array. A eficiência deve ser considerada ao projetar o contrato, especialmente para operações que modificam o array.

Este padrão é especialmente útil em situações onde é necessário tanto um acesso rápido aos dados por meio de chaves quanto a capacidade de iterar sobre todas as entradas armazenadas.

10. Padrão de Lista de Endereços

Este padrão é utilizado para manter uma lista curada de endereços pelo proprietário. Isso é necessário, por exemplo, quando se precisa de uma lista de endereços em uma whitelist (lista de permissões) que estejam autorizados a executar determinadas funções em um contrato. Neste padrão, apenas o proprietário do contrato pode adicionar e remover endereços da lista.

Vamos ver um exemplo.

contract AddressList is Ownable {
		 mapping(address => bool) internal map;
		 function add(address _address) public onlyOwner {
		        map[_address] = true;
		    }

		 function remove(address _address) public onlyOwner {
		        map[_address] = false;
		    }

		 function isExists(address _address) public view returns (bool) {
		        return map[_address];
    }
}

11. Padrão de Comparação de Strings

Em Solidity, as strings são um tipo especial de dado que representa sequências de caracteres. No entanto, Solidity não oferece uma maneira direta de comparar strings devido à sua natureza de baixo nível e ao foco na eficiência de gas. Comparar strings envolve comparar o conteúdo dos dados, em vez das localizações de memória, o que exige uma lógica específica dada a forma como os strings são armazenados na EVM (Ethereum Virtual Machine).

Uma maneira comum de comparar strings em Solidity é convertê-las primeiro em bytes e depois comparar esses bytes usando o algoritmo de hash keccak256. Se duas strings forem idênticas, seus hashes keccak256 também serão idênticos. Este método é eficiente e eficaz para a comparação de strings em contratos inteligentes.

A seguir, mostramos como implementar a comparação de strings usando keccak256:

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

contract StringComparison {
    // Função para comparar duas strings
    function compareStrings(string memory string1, string memory string2) public pure returns (bool) {
        return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2));
    }
}
  • abi.encodePacked(...): Esta função recebe qualquer número de argumentos de qualquer tipo e os codifica em um único conjunto contínuo de bytes. É usada aqui para converter as strings em bytes.

  • keccak256(...): Calcula o hash KECCAK-256 dos dados. Neste contexto, é usada para obter o hash dos bytes resultantes de cada string.

  • ==: Compara os hashes das duas strings. Se as strings forem iguais, seus hashes também serão, resultando em true. Caso contrário, o resultado será false.

Considerações

  • Eficiência de Gas: Usar keccak256 para comparar strings geralmente é eficiente em termos de gas, especialmente comparado com métodos que implicariam iterar sobre cada caractere das strings.

  • Colisões: Teoricamente, as funções hash podem ter colisões (entradas diferentes gerando o mesmo hash), embora a probabilidade disso ocorrer seja extremamente baixa com keccak256.

  • Limitações: Este método compara o conteúdo completo das strings. Se você precisar realizar comparações mais complexas (como verificações de prefixo ou padrões), será necessário uma lógica adicional."

PreviousBoas Práticas de DesenvolvimentoNextEIP & ERC

Last updated 5 months ago