WEB3DEV

Cover image for Jogo de Loteria usando Solidity e ChainLink VRF (Chainlink VRF Parte II)
Panegali
Panegali

Posted on

Jogo de Loteria usando Solidity e ChainLink VRF (Chainlink VRF Parte II)

Você comprou um bilhete para uma loteria, onde o vencedor leva para casa $1.000.000. Quando a loteria é realizada, você descobre que ganhou e, os organizadores não querem pagar a você.

Além disso, imagine um sistema de loteria com 500 participantes, onde o vencedor pode ser previsto ou adulterado. Onde não há confiança entre os organizadores e os participantes. Você já se sentirá enganado quando não souber como o vencedor foi selecionado.

É aí que entra a blockchain, para remover o elemento de ter que confiar em um sistema antes de participar. O elemento de fraude ou falsificação.

Neste artigo, vou mostrar como garantir ainda mais a integridade em um sistema de loteria usando contratos inteligentes (Solidity e Chainlink VRF para obter um número aleatório).

Se você não souber como configurar um canal de assinatura no Chainlink VRF, tenho um artigo que fornece etapas detalhadas e pictóricas para criar uma assinatura.

Link -> https://shorturl.at/hwEG6.


Vamos mergulhar na implementação do sistema Chainlink VRF. Portanto, estaremos implementando nosso contrato inteligente com a Chainlink VRF na rede Goerli.

Acesse https://docs.chain.link/vrf/v2/subscription/supported-networks para saber mais sobre as redes suportadas pela Chainlink VRF.

Vamos precisar dos valores contidos na imagem acima quando começarmos a escrever nosso contrato inteligente.

Além disso, será necessário a rede de testes Goerli será necessária e pode ser obtida em https://goerlifaucet.com/

Vamos criar um arquivo lottery.sol e adicionar as variáveis básicas, eventos, e funções base.

//Uma variável de estado para os participantes
uint playerId;

//Uma variável de estado para a idade média de um participante    
uint age = 18;

//Uma variável de estado para o status atual do nosso jogo de loteria
bool lotteryStatus;

//Uma variável de estado para saber se o jogo terminou 
bool lotteryEnded;

//Uma variável de estado para o número máximo de participantes
uint constant MAX_PATICIPANTS = 100;

//Preço do bilhete de loteria
uint constant TICKET_PRICE = 1 ether;

//endereço do administrador
address lotteryAdmin;

//participantes da loteria
Participant[] participants;

struct Participant {
  uint timeAdded;
  uint lotteryCard;
  address playerAddress;
  }

//Evento para quando um participante é adicionado
event ParticipantAdded(address participant);

//Evento para o jogo inicia
event LotteryStarted(bool status, uint timeStart);

//Evento quando a solicitação ao VRF é feita
event RequestSent(uint256 requestId, uint32 numWords);

//Evento quando a solicitação ao VRF é atendida
event RequestFulfilled(uint256 requestId, uint256[] randomWords);

//Status da solicitação
struct RequestStatus {
        bool fulfilled; // se a solicitação foi atendida com sucesso 
        bool exists; // se existe um requestId
        uint256[] randomWords;
    }

//VRF COORDINATOR baseado na rede que você irá implantar no
VRFCoordinatorV2Interface immutable COORDINATOR;

//id do seu canal de assinatura VRF
uint64 immutable s_subscriptionId;

//O valor do hash da chave da via de gás, que é o preço máximo do gás que você está disposto a pagar por uma solicitação em wei
bytes32 immutable s_keyHash;

//o limite de quanto gás usar para a solicitação de retorno de chamada para a função fillRandomWords() do seu contrato
uint32 constant CALLBACK_GAS_LIMIT = 2500000;

//Quantas vezes os valores aleatórios da sua solicitação serão confirmados pela
uint16 constant REQUEST_CONFIRMATIONS = 3;

//Quantos valores aleatórios você deseja do Chainlink. Two(2) é a
uint32 constant NUM_WORDS = 2;

// o último ID de solicitação que seu contrato recebeu do chainlink;
uint256 public lastRequestId;


// Construtor com todos os parâmetros necessários para
constructor() VRFConsumerBaseV2(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D){
        COORDINATOR = VRFCoordinatorV2Interface(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);
        s_keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;
        lotteryAdmin = msg.sender;
        s_subscriptionId = //Você adicionará seu ID de assinatura;
}
Enter fullscreen mode Exit fullscreen mode

Apenas estabelecemos os blocos de construção do nosso Sistema de Loteria. Vamos adicionar funcionalidade para adicionar participantes, iniciar e encerrar a Loteria.

// Iniciar loteria
function startLottery() external {
    require(msg.sender == lotteryAdmin, "Unauthorized");
    require(!lotteryStatus, "Lottery already active");
    require(!lotteryEnded, "Lottery has ended");
    require(participants.length > 10, "Particpants not enough to start");
    lotteryStatus = true;
}

// Encerrar loteria
 function endLottery() external {
    require(msg.sender == lotteryAdmin, "Unauthorized");
    require(lotteryStatus, "Lottery Game hasn't started");
    require(!lotteryEnded, "Lottery already ended");
    lotteryEnded = true;
    playerId = 0;
   }

Enter fullscreen mode Exit fullscreen mode

Apenas adicionamos funcionalidade para permitir que apenas o administrador do jogo de loteria inicie e encerre a loteria. Também há uma verificação de que os participantes devem ter pelo menos 20 anos antes de o jogo começar.

Vamos adicionar a função que permite aos jogadores participarem na loteria.

function participate(uint _age) external payable {

  //Cada jogador paga um preço de bilhete de 0,01ETH
  require(msg.value == TICKET_PRICE, "Ticket Price is 0.01ETH");

  //Verifica se cada jogador é maior de 18 anos
  require(age >= _age, "You are not above 18Years");

  //Verifica para confirmar que a loteria foi iniciada
  require(lotteryStatus == false, "Lottery has started already");

  //Verifica se o número máximo de jogadores não foi atingido
  require(participants.length < 100, "Max Participants reached");

  playerId++;

  //Cria uma estrutura de detalhes do jogador e a adiciona à matriz de participantes
  Participant memory _participant;
  _participant.timeAdded = block.timestamp;
  _participant.lotteryCard = playerId;
  _participant.playerAddress = msg.sender;

  participants.push(_participant);

  //emite um evento informando que o jogador foi adicionado
  emit ParticipantAdded(msg.sender);
  }
Enter fullscreen mode Exit fullscreen mode

Agora, como decidimos o processo para escolher um vencedor? Normalmente, poderíamos ter usado algum meio de aleatoriedade (usando block.timestamp, uma frase-semente e alguns valores, hasheado e depois realizar algumas operações aritméticas para selecionar um vencedor. Esse processo não é seguro e pode ser adulterado).

Aqui é onde entra o oráculo da Chainlink; a Chainlink fornece uma funcionalidade chamada Verifiable Random Function (Função aleatória verificável ou VRF).

A Chainlink VRF (Verifiable Random Function) é um gerador de números aleatórios (RNG) comprovadamente justo e verificável que permite que contratos inteligentes acessem valores aleatórios sem comprometer a segurança ou usabilidade.

A própria Chainlink fornece um código de contrato inteligente para solicitar e atender valores aleatórios a serem usados pelo seu contrato inteligente.

//Solicitação de valor aleatório
function requestRandomWords() external returns (uint256 requestId) {
       requestId = COORDINATOR.requestRandomWords(
           s_keyHash,
           s_subscriptionId,
           REQUEST_CONFIRMATIONS,
           CALLBACK_GAS_LIMIT,
           NUM_WORDS
       );
        s_requests[requestId] = RequestStatus({
           randomWords: new uint256[](0),
           exists: true,
           fulfilled: false
       });

       lastRequestId = requestId;
       emit RequestSent(requestId, NUM_WORDS);
       return requestId;
   }

   // Atender à solicitação de aleatoriedade do elo da corrente
   function fulfillRandomWords(
       uint256 _requestId,
       uint256[] memory _randomWords
   ) internal override {
       require(s_requests[_requestId].exists, "request not found");
       s_requests[_requestId].fulfilled = true;
       s_requests[_requestId].randomWords = _randomWords;
       emit RequestFulfilled(_requestId, _randomWords);
   }
Enter fullscreen mode Exit fullscreen mode

Cada linha de código no trecho acima trabalha com variáveis que expliquei quando estabelecemos a base para o nosso contrato. A função requestRandomWords recebe os parâmetros especificados e envia a solicitação para o contrato do coordenador VRF.

Quando a função requestRandomWords é chamada, um valor de ID de solicitação é retornado da ChainLink VRF. Este é um exemplo de saída quando usei o Remix para testar o jogo de loteria.

Nos logs de saída, o ID da solicitação é 65372568078059010733545628340548094994590515889458705464888828779780316747169.

A função fulfilledRandomWords, que é interna, recebe os valores aleatórios e os armazena em seu contrato. Temos uma função que obtém os valores aleatórios conectados a um ID de solicitação.


function getRequestStatus(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
    require(s_requests[_requestId].exists, "solicitação não encontrada");
    RequestStatus memory request = s_requests[_requestId];
    return (request.fulfilled, request.randomWords);
}

Enter fullscreen mode Exit fullscreen mode

A função getRequestStatus simplesmente mostra as palavras aleatórias solicitadas depois que foram atendidas. A saída decodificada consiste no estado de nossa solicitação, o ID e, em seguida, o mais importante, os valores gerados aleatoriamente pela Chainlink VRF.

Este valor será usado para gerar nosso vencedor aleatório para o jogo de loteria.

Agora, vamos escrever uma função que escolhe um vencedor para a loteria.


function pickWinner() external payable returns (Participant memory winner) {
    require(!lotteryStatus, "Lottery Game not active");
    require(msg.sender == lotteryAdmin, "Unauthorized Access");

    RequestStatus memory _request = s_requests[lastRequestId];
    uint randomWinner = _request.randomWords[0] % participants.length;

    winner = participants[randomWinner];
}

Enter fullscreen mode Exit fullscreen mode

Esta é uma função muito simples que utiliza o poder da Chainlink VRF; nosso vencedor não pode ser determinado por qualquer violação de segurança, pois há uma verificação rigorosa pela Chainlink VRF nos bastidores para confirmar a singularidade de nossos valores aleatórios.

Quando o administrador chama a função pickWinner, obtemos uma saída decodificada, como o trecho abaixo.

A partir disso, podemos ver que o vencedor é um usuário com o bilhete de loteria nº 2.

Conseguimos criar um contrato que simula um jogo de loteria da vida real, onde os jogadores são adicionados e um vencedor aleatório é selecionado usando a Chainlink VRF. Dessa forma, eliminamos a necessidade de qualquer verificação para evitar violações externas ao nosso jogo de loteria.

Por favor, aplauda, comente e siga para mais artigos como este sobre conceitos de blockchain, escrita de contratos inteligentes e Web3.

Se você achar isso esclarecedor e útil, pode me comprar um café em https://www.buymeacoffee.com/etimpaul22a.


Artigo escrito por Joe. Traduzido por Marcelo Panegali.

Top comments (0)