WEB3DEV

Cover image for Contrato Inteligente de Loteria na Blockchain Cronos
Fatima Lima
Fatima Lima

Posted on

Contrato Inteligente de Loteria na Blockchain Cronos

Image description

Introdução

A Cronos é uma blockchain compatível com EVM que usa Proof-Of-Authority (POA ou prova de autoridade) para oferecer uma finalidade mais rápida e barata aos seus usuários. Como uma blockchain de código aberto de Camada 1, a Cronos tem como objetivo combinar sua infraestrutura compatível com EVM com outras blockchains criadas sobre o SDK (kit de desenvolvimento de software) da Cronos para permitir transferências rápidas e baratas de ativos.

Neste tutorial, aprenderemos como criar um contrato inteligente de loteria usando o Solidity e como implementar e verificar contratos inteligentes na blockchain Cronos usando o Hardhat.

Configurando um projeto Hardhat

Antes de escrever nosso contrato inteligente, precisamos configurar nosso ambiente de desenvolvimento. Para criar um projeto Hardhat:

  1. Crie uma pasta chamada cronosHardhat (ou o nome que você quiser).
  2. Abra a nova pasta no terminal e execute o comando: \ npm install --save-dev hardhat \ Esse comando executa o download da última versão do Hardhat em seu diretório.
  3. 1. Para criar um projeto de amostra Hardhat, execute npx hardhat.
  4. 1. Isso abrirá uma interface UI em seu terminal.

Image description

Selecione ‘Create a JavaScript project’ e pressione enter para todas as opções. Isso criará um projeto de amostra do Hardhat em seu terminal e o Hardhat baixará automaticamente todas as dependências adicionais para esse projeto de amostra usando ‘hardhat-toolbox’.

Dando uma olhada na estrutura do projeto do Hardhat

Agora você deve ter um projeto básico do Hardhat configurado em seu diretório. Vamos dar uma breve olhada em algumas pastas e arquivos dentro do layout do projeto:

  • Contracts (Contratos): essa pasta contém todos os seus contratos inteligentes.
  • Scripts: essa pasta contém arquivos JavaScript que são usados para implantar contratos inteligentes já compilados na blockchain. Normalmente, você terá de escrever pelo menos um script de implantação básico para implantar seus contratos inteligentes.
  • Test (Teste): o ideal é que você teste seus contratos inteligentes antes de implantá-los na blockchain. A pasta Test é onde você escreve esses testes. O Hardhat usa Mocha e Chai, duas bibliotecas JavaScript muito poderosas para permitir testes em sua estrutura.
  • hardhat.config: como o nome sugere, esse arquivo contém todos os parâmetros de configuração para implantar seus contratos inteligentes. O Hardhat suporta a otimização de gas, definindo várias redes, entre muitos outros recursos que podem ser ajustados por meio do arquivo hardhat.config.

Escrevendo um contrato inteligente no Hardhat

Dentro da pasta contracts, crie um novo arquivo chamado Lottery.sol.
Dentro desse arquivo, cole o seguinte código:

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

contract Lottery {

    address public owner;

    address payable[] public players;

    bool public lotteryInProgress;

    uint public lotteryId;

    mapping (uint => address payable) public lotteryHistory;

    constructor() {

        owner = msg.sender;

        lotteryId = 1;

    }

    function startLottery() public onlyowner{

        lotteryInProgress=true;

    }

    function alreadyEntered() private view returns(bool){

        for(uint i=0; i< players.length; i++){

            if(players[i]==msg.sender)

            return true;

        }

        return false;

    } 

    function enterLottery() public payable {

        require(lotteryInProgress,"No lottery event is currently in progress");

        require(msg.value > 0.5 ether, "Please deposit at least 0.5 Ether");

        require(msg.sender!=owner, "As owner, you can't enter the lottery");

        require(alreadyEntered()==false, "You have already entered in the Lottery");

        require(players.length<=10, "This lottery has maximum participation. Please wait for the next event");

        players.push(payable(msg.sender));

    }

    function getRandomNumber() private view returns (uint) {

        return uint(keccak256(abi.encodePacked( block.timestamp, block.difficulty, block.gaslimit)));

    }

    function pickWinner() public onlyowner {

        require(lotteryInProgress,"No lottery event is currently in progress");

        require(players.length>0, "can't pick winners without participants");

        uint index = getRandomNumber()%players.length;

        players[index].transfer(address(this).balance - 0.1 ether);

        lotteryHistory[lotteryId] = players[index];

        lotteryId++;       

        lotteryInProgress=false;

        players = new address payable[](0);

    }

    modifier onlyowner() {

      require(msg.sender == owner, "Only owner can call this function");

      _;

    }

    function getWinnerByLottery(uint lottery) public view returns (address payable) {

        return lotteryHistory[lottery];

    }

    function getBalance() public view returns (uint) {

        return address(this).balance/(10**18);

    }

    function numOfPlayers() public view returns (uint) {

        return players.length;

    }

    function getPlayers() public view returns (address payable[] memory) {

        return players;

    }

}
Enter fullscreen mode Exit fullscreen mode

Vamos repassar o código rapidamente:

  • O constructor faz duas coisas. Primeiro, ele define o endereço de nossa carteira como o "owner" (proprietário) do endereço. Como o constructor é chamado apenas uma vez no ciclo de vida de um contrato, esse valor não pode ser alterado. Em segundo lugar, ele define o ID da loteria como 1, que será incrementado sempre que encerrarmos um concurso de loteria.
  • A função startLottery() simplesmente inicia uma variável booleana. Essa variável é basicamente um sinalizador que nos informa se um evento de loteria está em andamento no momento ou não.
  • A função enterLottery() permite a participação de novos jogadores no concurso se a contribuição deles para o pool de loteria for maior que 0,5 Ether. Ele também utiliza a função alreadyEntered() para verificar se alguém está tentando participar novamente. Se todas as outras condições também forem atendidas, o participante será adicionado à lista de jogadores com direito a ganhar na loteria.
  • A função getRandomNumber() é o nosso carro-chefe. Uma loteria deve decidir o vencedor de forma aleatória. No entanto, o Solidity não tem uma função nativa de randomizador. Além disso, a Chianlink VRF (função de aleatoriedade verificável), um popular serviço descentralizado de geração de números aleatórios, ainda não suporta a Cronos. Portanto, nossa melhor opção é combinar algumas propriedades variáveis da blockchain para gerar um número pseudoaleatório. Usamos as seguintes variáveis globais como parte de nosso algoritmo de hash:
    1. block.timestamp
    2. block.difficulty
    3. block.gaslimit
  • Todas essas variáveis globais variam em valor de acordo com as condições existentes na blockchain enquanto estamos implantando/chamando nosso contrato. Isso torna nosso número bastante aleatório, mas não totalmente.
  • O hash gerado será pseudoaleatório, mas funcionará na maioria dos casos.
  • A pickWinner() é uma função que só pode ser chamada pelo proprietário do contrato. Ela usa a getRandomNumber() para selecionar um índice aleatório. O endereço nesse índice recebe a transferência de todo o Ether na loteria naquele momento, menos uma taxa de 0,1 Ether que mantemos como taxa de serviço.
  • As últimas funções são algumas funções de visualização simples que usamos para manter o controle dos parâmetros atuais do nosso contrato.

Depois de colar o código, salve o arquivo e vá para o terminal. Dentro dele, execute o comando:

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

Isso compilará todos os contratos inteligentes em sua pasta contracts e gerará ABIs para eles na pasta artifacts. Agora temos um contrato inteligente pronto para ser implantado.

Definindo as variáveis env (de ambiente)

Para implantar nossos contratos inteligentes, precisamos basicamente de duas coisas:

  • Uma chave privada para uma carteira baseada em EVM que tenha um número suficiente de tokens TCRO, a criptomoeda nativa da rede de teste Cronos. Se você não tiver tokens TCRO, poderá obter alguns na faucet (torneira) oficial.
  • Também precisamos de um URL de RPC (chamada de procedimento remoto) para nos conectarmos a uma blockchain. Você pode usar um URL de RPC público; OU pode se inscrever na Chainstack e usar um URL exclusivo que lhe proporcionará um serviço mais rápido e confiável.
  • Se você não sabe como configurar um endpoint (ponto de extremidade) com a Chainstack, pode dar uma olhada neste tutorial no nosso blog.

Image description

Tutorial sobre como configurar um nó de cadeia pública com a Chainstack

  • Além disso, verificaremos nosso contrato inteligente depois de implantá-lo na rede de teste da Cronos. Podemos fazer isso diretamente da linha de comando usando o plugin hardhat-cronoscan. Para usar o plugin, precisamos obter uma chave API do Cronoscan. Faça login no Cronoscan e pegue uma chave API.

Embora seja possível colocar suas chaves diretamente no arquivo hardhat.config, não recomendamos fazer isso, pois você pode acabar enviando esses dados para repositórios on-line, o que pode fazer com que suas chaves privadas fiquem comprometidas. Usaremos o pacote dotenv para exportar nossas chaves com segurança.

Para instalar o pacote dotenv em seu diretório, abra seu terminal e execute:

  1. npm i dotenv: Para instalar o pacote dotenv em seu diretório.
  2. touch .env: Para criar um novo arquivo .env em seu diretório. Vá até o arquivo dotenv recém-criado e insira suas chaves privadas. Este é o aspecto que seu arquivo dotenv deve ter agora:
RPC_URL=https://nd-XXXXXXXXX

PRIVATE_KEY=0x2fqXXXXXXXXXXXXXXXXXXXXXu7

API_KEY=XW10AXXXXXXXXXXXXXXXXXG5
Enter fullscreen mode Exit fullscreen mode

Salve o arquivo dotenv e execute este comando em seu terminal:

source .env
Enter fullscreen mode Exit fullscreen mode

Esse comando carregará seus dados confidenciais em sua linha de comando. Você pode executar echo $RPC_URL em seu terminal para verificar se os dados podem ser acessados pelo Hardhat.

Configurando o hardhat.config

Conforme explicado anteriormente, esse arquivo contém toda a configuração do projeto hardhat. Vá para o hardhat.config em seu diretório raiz e cole este código após excluir seu conteúdo:

require("@nomicfoundation/hardhat-toolbox");

require('dotenv').**config**();

require("@nomiclabs/hardhat-etherscan");

require("@cronos-labs/hardhat-cronoscan");

module.exports = {

  solidity: "0.8.17",

  defaultNetwork: "Cronos_testnet",

  networks: {

    **Cronos**_testnet: {

        url: `${process.env.RPC_URL}`,

        accounts: [process.env.PRIVATE_KEY]

    },

},

    etherscan: {

        apiKey: {

         cronosTestnet: `${process.env.API_KEY}`,

  },

},

};
Enter fullscreen mode Exit fullscreen mode

Algumas coisas acontecem aqui:

  • hardhat-toolbox é uma atualização recente do Hardhat que reúne uma variedade de pacotes e plug-ins do Hardhat comumente usados, em uma única importação.
  • O plugin hardhat-cronoscan é usado em conjunto com o plugin hardhat-etherscan a fim de verificar os contratos implantados na rede principal e na rede de teste da Cronos.
  • Podemos definir várias conexões de blockchain dentro do objeto networks. Também podemos definir uma rede padrão para a qual o Hardhat voltará caso não especifiquemos uma determinada rede na linha de comando. Isso ajuda no caso de termos várias redes definidas no arquivo de configuração, mas na maioria das vezes usamos apenas uma rede específica.

  • Definimos a chave da API do Cronoscan dentro do objeto etherscan.

O hardhat.config suporta muitos outros parâmetros de configuração, mas isso é tudo de que precisamos por enquanto.

No entanto, você deve ter notado que ainda não instalamos o plug-in hardhat-cronoscan. Para instalar o plug-in, vá para o terminal em seu diretório raiz e execute:

npm **i** --save-dev **@nomiclabs**/hardhat-etherscan@^3.1.0 @cronos-labs/hardhat-cronoscan
Enter fullscreen mode Exit fullscreen mode

Agora salve seu arquivo config e execute o comando npx hardhat compile em seu terminal mais uma vez. Isso garantirá que todos os seus contratos sejam compilados de acordo com as configurações mais recentes do nosso projeto Hardhat.

Testando no Hardhat

  1. Os contratos inteligentes são destinados a lidar com dinheiro. Portanto, é altamente recomendável testar seus contratos inteligentes antes de implantá-los em uma rede de blockchain ativa.
  2. Para testar, vamos nos conectar à rede Hardhat, que é basicamente uma blockchain fictícia criada em seu sistema pelo Hardhat e testaremos nosso contrato inteligente usando as bibliotecas Mocha e Chai por meio de um script de teste automático.
  3. Para começar, abra um novo terminal no mesmo diretório e execute o comando: \ **npx hardhat node \ Isso nos dará uma blockchain simulada em nossa linha de comando com uma lista de contas preenchidas com um monte de ETH falsos. Que conveniente!
  4. Precisamos instalar um último plug-in que nos permita aproveitar todo o poder da biblioteca Chai. Abra seu terminal original e execute:
npm install --save-dev <strong>@nomicfoundation</strong>/hardhat-chai-matchers
Enter fullscreen mode Exit fullscreen mode
  1. Isso adicionará alguns recursos específicos da Ethereum à biblioteca Chai. Veremos isso em mais detalhes a seguir. Por fim, abra o hardhat-config.js e adicione esta linha à parte superior do arquivo
**require**("@nomicfoundation/hardhat-chai-matchers");
Enter fullscreen mode Exit fullscreen mode
  1. Agora estamos prontos para testar nosso código Solidity.
  2. Abra a pasta de teste e exclua todos os arquivos contidos nela. Crie um novo arquivo com o nome de Lottery.js e adicione o seguinte código nele:
**const** { expect } = require("chai");

**const** hre = require("hardhat");

**describe**("Lottery", **function** () {

    **let** owner, player1, player2, lottery;

    **before**(**async** () => {

        [owner, player1, player2] = **await** ethers.**getSigners**();

        provider = **await** ethers.**getDefaultProvider**();

        **const** **Lottery** = **await** hre.ethers.**getContractFactory**("Lottery");

        console.**log**("Deploying Contract for testing.......")

        lottery = **await** **Lottery**.**deploy**();

        **await** lottery.**deployed**();

    });

    **it**("Should set owner correctly", **async** **function** () {

        **const** ownerAddress = **await** lottery.**owner**();

        console.**log**("owner from contract is : ", ownerAddress);

        **expect**(**await** ownerAddress).to.**equal**(owner.address);

    });

    **it**("Non-owner shouldn't be able to start lottery", **async** **function** () {

        **await** **expect**(lottery.**connect**(player1).**startLottery**()).to.be.**revertedWith**("Only owner can call this function");

    });

    **it**("Should start lottery event", **async** **function** () {

        **let** lotteryInProgress = **await** lottery.**lotteryInProgress**();

        console.**log**("Current value of lotteryInProgress is : ", lotteryInProgress);

        **await** lottery.**startLottery**();

        console.**log**("New value of lotteryInProgress is : ", **await** lottery.**lotteryInProgress**());

        **expect**(**await** lottery.**lotteryInProgress**()).to.**equal**(true);

    });

    **it**("Player 1 should be 1st participant", **async** **function** () {

        **await** lottery.**connect**(player1).**enterLottery**({ value: ethers.utils.**parseEther**("2.0") });

        **let** participant1 = **await** lottery.**players**(0);

        player1Balance = **await** ethers.provider.**getBalance**(player1.address);

        console.**log**("Remaining ETH with player1: ", player1Balance / (10 ** 18));

        **expect**(participant1).to.**equal**(player1.address);

    });

    **it**("Player 2 should be 2nd participant", **async** **function** () {

        **await** lottery.**connect**(player2).**enterLottery**({ value: ethers.utils.**parseEther**("3.0") });

        **let** participant2 = **await** lottery.**players**(1);

        player2Balance = **await** ethers.provider.**getBalance**(player2.address);

        console.**log**("Remaining ETH with player2: ", player2Balance / (10 ** 18));

        **expect**(participant2).to.**equal**(player2.address);

    });

    **it**("pickWinner() function should not be called by non-owner", **async** **function** () {

        **await** **expect**(lottery.**connect**(player1).**pickWinner**()).to.be.**revertedWith**("Only owner can call this function");

    });

    **it**("pickWinner() function should work correctly", **async** **function** () {

        lottery.**pickWinner**();

        **LotteryId** = lottery.**lotteryId**();

        **let** **Winner** = **await** lottery.**lotteryHistory**(**LotteryId**);

        **let** **WinnerBalance** = **await** ethers.provider.**getBalance**(**Winner**);

        console.**log**("Winner is: ", **Winner**);

        console.**log**("Winner address is:", **WinnerBalance** / (10 ** 18));

        **expect**(**await** **Winner**).to.**satisfy**(**function** (anyWinner) {

            **if** ((anyWinner == player1.address) || (anyWinner == player2.address)) {

                **return** true;

            } **else** {

                **return** false;

            }

        });

    });

});
Enter fullscreen mode Exit fullscreen mode

Isso é muita coisa. Vamos examinar cuidadosamente esse código e o que ele faz:

  • A função describe() chega até nós por meio da biblioteca mocha. A função describe é basicamente usada para agrupar vários testes. Normalmente, ela contém vários testes it(), que também é o que ocorre aqui.
  • A função before() também é fornecida pela biblioteca mocha. Essa função é executada uma vez antes do primeiro teste em qualquer script mocha. Usando essa função, implantamos nosso contrato inteligente na blockchain e usamos o objeto lottery da função no restante do código.
  • Funções it() são testes unitários únicos com uma descrição para expressar seu uso individual. Temos vários testes it() em nosso código.
  • No final de cada teste it(), normalmente, "afirmamos" algo. Uma asserção é basicamente onde você verifica se os resultados reais correspondem ou não aos resultados esperados. A biblioteca Chai nos oferece muitos tipos diferentes de asserções. Você pode ler mais sobre elas em seus documentos oficiais.
  • Você deve ter observado que nosso contrato inteligente usa um modificador chamado onlyOwner(). A função de um modificador é reverter uma função com um erro específico sempre que uma condição de exigência falhar. O plugin chai-matchers nos permite testar essas reversões específicas, o que não seria possível apenas com a biblioteca Chai padrão.
  • Nossa última função it() usa a asserção .to.satisfy para chamar uma função matcher específica que garante que o ganhador da loteria seja escolhido somente entre os dois participantes.
  • Testar um contrato inteligente é uma tarefa minuciosa que deve ser conduzida com cuidado. Sinta-se à vontade para ajustar este código para adicionar ou remover testes conforme achar necessário. A ideia por trás desta seção era fazer com que você começasse a testar no Hardhat.

Quando estiver pronto, salve o arquivo. Em seu terminal principal, execute:

npx hardhat test --network localhost
Enter fullscreen mode Exit fullscreen mode

Esse comando executará os scripts de teste na rede Hardhat, desde que você tenha realmente a rede em execução no sistema. Fizemos isso quando executamos o comando npx hardhat node.

É assim que seu terminal deve estar agora:

Image description

Testando no Hardhat

Todos os nossos testes foram aprovados e agora estamos prontos para implantar nosso contrato inteligente em uma rede ativa.

Implantando nosso contrato inteligente

Agora temos nossas chaves privadas prontas para uso e ajustamos o arquivo de configuração de acordo com nossos requisitos. Também acabamos de testar nosso contrato inteligente, que agora está pronto para ser usado.

Isso já é uma coisa boa. No entanto, para implantar de fato um contrato inteligente por meio do Hardhat, precisamos criar um script de implantação.

Dentro da pasta scripts, vá para deploy.js e apague tudo que estiver dentro dela.

Agora, cole o código a seguir no mesmo arquivo:

**const** hre = require("hardhat");

**async** **function** **main**() {

  **const** **CronosLottery** = **await** hre.ethers.**getContractFactory**("Lottery");

  console.**log**("Deploying your contract, please Wait.....");

  **const** cronosLottery = **await** **CronosLottery**.**deploy**();

  **await** cronosLottery.**deployed**();

  console.**log**("CronosToken deployed to:", cronosLottery.address);

}

**main**()

  .**then**(() => process.**exit**(0))

  .**catch**((error) => {

    console.**error**(error);

    process.**exit**(1);

  });
Enter fullscreen mode Exit fullscreen mode

Este é um script de implantação simples. Acessamos a função de implantação por meio do ambiente de tempo de execução do Hardhat.

Para executar o script de implantação, abra o terminal e execute:

npx hardhat run --network Cronos_testnet scripts/deploy.js
Enter fullscreen mode Exit fullscreen mode

Registramos o endereço do contrato que acabamos de implantar em nosso console.

Vá ao explorador da rede de teste da Cronos e busque o endereço do seu contrato. Você perceberá que o código do contrato ainda não está visível. Isso ocorre porque ainda não verificamos nosso contrato. Vamos prosseguir com isso.

Verificando nosso contrato inteligente

Para verificar nosso contrato por meio da linha de comando do Hardhat, precisamos usar o comando verify do Hardhat. Passaremos o endereço do contrato implantado junto com o URL do RPC da blockchain.

Se você se lembra, já definimos uma rede de teste da Cronos em nosso arquivo de configuração.

Abra seu terminal e execute:

npx hardhat verify --network Cronos_testnet {Contract **Address**}
Enter fullscreen mode Exit fullscreen mode

Isso pode levar um tempo, mas o Hardhat retornará um link para o seu contrato verificado no terminal se a verificação for bem-sucedida. Abra o link e agora você poderá interagir com o contrato diretamente do próprio explorador da rede de teste da Cronos.

Conclusão

Neste artigo, aprendemos como configurar um projeto Hardhat e como implantar e verificar um contrato inteligente na rede de teste da Cronos.

Observe que você pode ajustar os parâmetros dentro do arquivo de configuração do Hardhat para implantar e verificar o mesmo contrato na rede principal da Cronos com algumas pequenas alterações. A chave da API do Cronoscan funciona tanto para a rede principal quanto para a rede de teste.

Se estiver interessado em explorar mais a Cronos, você pode implantar seu próprio nó da Cronos com a Chainstack e trabalhar com ele, ou pode simplesmente ir ao nosso blog para ver mais alguns tutoriais interessantes sobre web3.

Boa programação!

Esse artigo foi escrito por Priyank Gupta e traduzido por Fátima Lima. O original pode ser lido aqui.

Top comments (0)