WEB3DEV

Cover image for Meu primeiro smart contract: Criando um contrato de aposta
ViniBlack
ViniBlack

Posted on • Atualizado em

Meu primeiro smart contract: Criando um contrato de aposta

Esse é o terceiro post da série Meu primeiro smart contract, que tem a intenção de ensinar ao longo de sete semanas alguns conceitos do solidity até construirmos um token baseado no ERC-20 com alguns testes unitários.

Nesse post vamos criar um contrato de aposta, onde você vai conseguir escolher um número, apostar uma quantidade de Ether, sortear um número premiado e pagar os vencedores.

Ferramentas

Vamos continuar usando Remix IDE para criação dos nossos contratos.

Criando um novo arquivo

Vamos criar um novo arquivo dentro da pasta contracts chamado 02-bet.sol.

Criando o novo contrato 02-bet.sol

Dentro do arquivo 02-bet.sol, vamos declarar as licenças do nosso contrato, a versão do solidity e dar um nome para o contrato.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Bet {

}
Enter fullscreen mode Exit fullscreen mode

Organização do código

Não existe uma forma certa de estruturar o código dos nossos contratos, mas para facilitar o entendimento vamos utilizar o seguinte padrão:

Estrutura do código

Structs: Onde definimos alguns tipos de dados mais complexo (nesse post vamos entender o que isso significa);
Properties: Onde definimos nossas variáveis;
Modifiers: Onde definimos nossos modificadores;
Constructor: Onde definimos nosso construtor;
Public Functions: Onde definimos nossas funções públicas;
Private Functions: Onde definimos nossas funções privadas;

Structs

Os struct são usados ​​para representar uma estrutura de dados.
Por exemplo, para conseguirmos realizar as nossas apostas todo player precisa informar o valor apostado e o número selecionado, para isso vamos criar um struct chamado Player que vai armazenar amountBet (valor apostado) e numberSelected (número selecionado) e os dois vão ser do tipo uint256.

//Structs
struct Player{
   uint256 amountBet;
   uint256 numberSelected;
}
Enter fullscreen mode Exit fullscreen mode

Caso queira entender um pouco mais sobre Structs clique aqui

Variáveis

Para fazermos o nosso sistema de apostas, vamos precisar criar algumas variáveis públicas para armazenar nossos dados.

  • owner: Que vai ser do tipo address;
  • players: Que vai ser um array do tipo address;
  • winners: Que vai ser um array do tipo address;
  • totalBet: Que vai ser do tipo uint256;
  • minimunBet: Que vai ser do tipo uint256;
  • addressToPlayer: Que vai ser um mapping que irá receber address como "chave" e Player como "valor";
// Properties
address public owner;
address[] public players;
address[] public winners;
uint256 public totalBet;
uint256 public minimunBet;

mapping(address => Player) addressToPlayer;
Enter fullscreen mode Exit fullscreen mode

Modifiers

Vamos criar o modifier isOwner para utilizarmos nas funções que só o dono do contrato poderá executar.

modifier isOwner() {
    require(msg.sender == owner , "Sender is not owner!");
    _;
}
Enter fullscreen mode Exit fullscreen mode

Construtor

Vamos agora criar o nosso construtor, que vai definir que o owner vai receber o msg.sender e vamos fazer uma verificação que o minimunBetValue tem que ser diferente de zero, caso minimunBetValue for igual a zero vamos retornar uma mensagem de erro.

constructor (uint256 minimunBetValue) {
    owner = msg.sender;
    if(minimunBetValue != 0) {
        minimunBet = minimunBetValue;
    }else {
        revert("Invalid value");
    }
}
Enter fullscreen mode Exit fullscreen mode

Funções públicas

Realizando uma aposta

Vamos criar uma função pública chamada bet, onde vamos conseguir passar o parâmetro numberSelected que vai ser do tipo uint256.
Como essa função irá interagir com Ethers precisamos garantir que esses Ethers sejam enviados para o contrato.
Para que o solidity entenda que ela vai mexer com Ethers precisamos adicionar o modificador payable, qualquer função no Solidity com o modificador Payable garante que a função possa enviar e receber Ethers.

// Public Functions
function bet(uint256 numberSelected) public payable {

}
Enter fullscreen mode Exit fullscreen mode

Caso queria entender um pouco mais sobre o modificador payable clique aqui

Dentro da função bet vamos verificar se o valor apostado é maior ou igual ao minimunBet, se for maior vamos criar duas variaveis, uma chamada valueBet do tipo uint256 que irá receber o msg.value e outra chamada playerBet do tipo address que irá receber msg.sender.

// Public Functions
function bet(uint256 numberSelected) public payable {
    require(msg.value >= minimunBet * 10**18, "The bet amount is less than the minimum allowed");
    uint256 valueBet = msg.value;
    address playerBet = msg.sender;
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos adicionar as informações dos nossos apostadores na nossa lista addressToPlayer.
Vamos criar uma variável chamada newPlayer, que vai ser do tipo Player e como nossas variáveis não vão gravar nada dentro delas, só vamos usar elas como local temporário para armazenar os dados até salvarmos esses dados dentro de uma variável precisamos informar que ela é um memory. Dentro da variável newPlayer, vamos definir que numberSelected vai receber numberSelected e amountBet vai receber valueBet ao final disso vamos adicionar o nosso newPlayer dentro do mapping addressToPlayer com a "chave" playerBet.

// Public Functions
function bet(uint256 numberSelected) public payable {
    require(msg.value >= minimunBet * 10**18, "The bet amount is less than the minimum allowed");
    uint256 valueBet = msg.value;
    address playerBet = msg.sender;

    Player memory newPlayer = Player({
        numberSelected : numberSelected,
        amountBet: valueBet
    });
    addressToPlayer[playerBet] = newPlayer;
}
Enter fullscreen mode Exit fullscreen mode

Caso queira entender um pouco mais sobre memory clique aqui.

No final da função vamos somar o valor apostado com o totalBet e adicionar playerBet no nosso array players.

// Public Functions
function bet(uint256 numberSelected) public payable {
    require(msg.value >= minimunBet * 10**18, "The bet amount is less than the minimum allowed");
    uint256 valueBet = msg.value;
    address playerBet = msg.sender;

    Player memory newPlayer = Player({
        numberSelected : numberSelected,
        amountBet: valueBet
    });
    addressToPlayer[playerBet] = newPlayer;

    totalBet += valueBet;
    players.push(playerBet);
}
Enter fullscreen mode Exit fullscreen mode

Gerando vencedor

Vamos criar uma função pública chamada generateWinner, apenas o dono do contrato vai conseguir utilizar essa função, por isso precisamos chamar nosso modificador isOwner, dentro da funçãogenerateWinner vamos chamar a função generateWinnerNumber.

 function generateWinner() public isOwner{
    generateWinnerNumber();
}
Enter fullscreen mode Exit fullscreen mode

Funções privadas

Pagando vencedor

Vamos criar uma função privada chamada rewardWinner onde vamos conseguir passar o parametro numberPrizeGenerated que vai ser do tipo uint256.

// Private Functions
function rewardWinner(uint256 numberPrizeGenerated)  private{

}
Enter fullscreen mode Exit fullscreen mode

Dentro da função rewardWinner, vamos criar uma variável chamada count que vai ser do tipo uint256 que vai receber 0 inicialmente. E vamos criar uma estrutura de repetição para verificar se algum número apostado é igual ao número sorteado, para isso criaremos uma variável chamada playerAddress do tipo address que vai receber players[i] assim vamos conseguir verificar se o número de todos os apostadores é igual o número sorteado, se for igual vamos adicionar o endereço do apostador no array winners e aumentar o count.

// Private Functions
function rewardWinner(uint256 numberPrizeGenerated)  private{
    uint256 count = 0;

    for(uint256 i = 0; i < players.length; i++){
        address playerAddress = players[i]; 

        if(addressToPlayer[playerAddress].numberSelected == numberPrizeGenerated){
            winners.push(playerAddress);
            count++;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Caso queira entender um pouco mais sobre estruturas de repetição no solidity clique aqui

Agora vamos realizar o pagamento para os vencedores, para isso vamos fazer uma validação para ver se o count é diferente de zero, se for diferente vamos criar uma variável chamada winnerEtherAmount que vai receber totalBet dividido pelo count e vamos criar uma estrutura de repetição para fazer o pagamento para todos os vencedores, então vamos criar uma variável chamada payTo que vai ser do tipo address e payable e vai receber winners[j]. Vamos fazer uma verificação para ver se o endereço do vencedor é um endereço válido, se for vamos realizar a transferência para esse endereço.

// Private Functions
function rewardWinner(uint256 numberPrizeGenerated)  private{
    uint256 count = 0;

    for(uint256 i = 0; i < players.length; i++){
        address playerAddress = players[i]; 

        if(addressToPlayer[playerAddress].numberSelected == numberPrizeGenerated){
            winners.push(playerAddress);
            count++;
        }
    }

    if(count != 0){
        uint256 winnerEtherAmount = totalBet / count;
        for(uint256 j = 0; j < count; j++) {
            address payable payTo = payable(winners[j]);
            if(payTo != address(0)) {
                payTo.transfer(winnerEtherAmount);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Caso queira entender o que significa address(0) clique aqui

Sorteado um número

Vamos criar uma função privada chamada generateWinnerNumber que vai ser responsável por sortear um número aleatório.
Precisamos ter em mente que solidity não é capaz de criar números aleatórios. Na verdade, nenhuma linguagem de programação por si só é capaz de criar números completamente aleatórios.
Mas conseguimos criar números pseudo-aleatórios que são conjuntos de valores ou elementos que são estatisticamente aleatórios, mas é derivado de um ponto de partida conhecido e normalmente é repetido várias vezes.
Então vamos criar uma variável do tipo uint256 chamada numberPrize que vai receber block.number que é o número do bloco atual + block.timestamp que é o número em segundos da data e hora que o bloco foi fechado, e vamos dividir tudo isso por 10 e pegar o resto dessa divisão e somar com + 1 .
E vamos chamar a função rewardWinner passando numberPrize como parâmetro.

function generateWinnerNumber() private {
    uint256 numberPrize = (block.number + block.timestamp) % 10 + 1;
    rewardWinner(uint256(numberPrize));
}
Enter fullscreen mode Exit fullscreen mode

Como ficou nosso código

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Bet {

    //Structs
    struct Player{
        uint256 amountBet;
        uint256 numberSelected;
    }

    // Properties
    address public owner;
    address[] public players;
    uint256 public totalBet;
    uint256 public minimunBet;
    address[] public winners;

    mapping(address => Player) addressToPlayer;
    mapping(address => uint256) private addressToBalance;


    // Modifiers
    modifier isOwner() {
        require(msg.sender == owner , "Sender is not owner!");
        _;
    }

    // Constructor
    constructor (uint256 minimunBetValue) {
        owner = msg.sender;
        if(minimunBetValue != 0) {
            minimunBet = minimunBetValue;
        }else {
            revert("Invalid value");
        }
    }

    // Public Functions
    function bet(uint256 numberSelected) public payable {
        require(msg.value >= minimunBet * 10**18, "The bet amount is less than the minimum allowed");
        uint256 valueBet = msg.value;
        address playerBet = msg.sender;

        Player memory newPlayer = Player({
            numberSelected : numberSelected,
            amountBet: valueBet
        });
        addressToPlayer[playerBet] = newPlayer;

        totalBet += valueBet;
        players.push(playerBet);
    }

    // Private Functions
    function rewardWinner(uint256 numberPrizeGenerated) private{
        uint256 count = 0;

        for(uint256 i = 0; i < players.length; i++){
            address playerAddress = players[i]; 

            if(addressToPlayer[playerAddress].numberSelected == numberPrizeGenerated){
                winners.push(playerAddress);
                count++;
            }
        }

        if(count != 0){
             uint256  winnerEtherAmount = totalBet/count;
            for(uint256 j = 0; j < count; j++) {
                address payable payTo = payable(winners[j]); // verificar se precisa dos dois payable
                if(payTo != address(0)) {
                    payTo.transfer(winnerEtherAmount);
                }
            }
        }
    }

    function generateWinnerNumber() private {
        uint256 numberPrize = (block.number + block.timestamp) % 10 + 1;
        rewardWinner(uint256(numberPrize));
    }

    function generateWinner() public isOwner{
        generateWinnerNumber();
    }
}
Enter fullscreen mode Exit fullscreen mode

Mão na massa

Agora vamos compilar e realizar o deploy do nosso contrato 02-bet.sol.

  1. No menu lateral esquerdo clique em "Solidity compiler".
    Abrindo aba para compilar o contrato

  2. Clique no botão "Compile 02-bet.sol".
    Compilando o contrato 02-bet.sol

  3. No menu lateral esquerdo clique em "Deploy & run transactions".
    Abrindo aba para fazer o deploy

  4. Informe o valor mínimo da aposta e clique no botão "Deploy".
    Fazendo o deploy do contrato 02-bet.sol

  5. Clique na seta para vermos as funções do nosso contrato.
    Abrindo contrato que fizemos deploy

  6. Mude de "Wei" para "Ether".
    Mudando de Wei para Ether

  7. Informe uma quantidade de Ether menor que o valor mínimo da aposta, informe um número para apostar e clique em "bet".
    Realizando uma aposta menor que o valor minimo

  8. Como fizemos uma aposta com o valor menor que a aposta mínima vai acontecer um erro no console.
    Erro por conta que o valor apostado é menor que o valor da aposta minima

  9. Agora aposte um valor maior que o valor de aposta mínima, informe um número para apostar e clique em "bet".
    Realizando uma aposta

  10. Troque de carteira.
    Trocando de carteira

  11. Realize uma aposta com outra carteira.
    Fazendo outra aposta com uma carteira diferente

  12. Antes de realizarmos o sorteio do número premiado vamos olhar o saldo das nossas carteiras.
    Vendo saldo das contas antes do sorteio do número premiado

  13. Com a carteira de quem fez o deploy do contrato clique no botão "generateWinner"
    Gerando o número premiado

  14. Depois de gerar o número premiado vamos olhar o saldo das nossas carteiras.
    Vendo saldo das contas depois do sorteio do número premiado

Conclusão

Esse foi o terceiro post da série de posts Meu primeiro smart contract.
Se você realizou todas as etapas acima, agora você tem um smart contract de aposta, onde você consegue definir o valor mínimo de Ethers para apostar, escolher um número para apostar, apostar uma quantidade de Ether e gerar o número premiado.

Se você gostou do conteúdo e te ajudou de alguma forma, deixe um like para ajudar o conteúdo a chegar para mais pessoas.

deixa um like


Link do repositório

https://github.com/viniblack/meu-primeiro-smart-contract

Vamos trocar uma ideia ?

Fique a vontade para me chamar para trocarmos uma ideia, aqui embaixo está meu contato.

https://www.linkedin.com/in/viniblack/

Top comments (9)

Collapse
 
alexcustodio profile image
Alex Custódio

Esses valores em Ether das contas ali na penúltima imagem não são dinheiro real né? Então você saberia me dizer o que exatamente eles seriam? Eu fiz um programa em java que fazia transação entre duas contas na blockchain ethereum privada do Ganache, que também tinha endereços com valores de 100 Ether mas nunca entendi direito de onde vinha esse valor e o que realmente representava.

Collapse
 
viniblack profile image
ViniBlack

Quando estamos usando o remix IDE, Ganache ou qualquer outra tecnologia que nos ajuda a criar smart contracts estamos usando uma rede de test (testnet)
As redes de testes nos permitem realizar testes de desenvolvimento de blockchain antes de nos lançarmos na rede principal. Seu funcionamento é semelhante à rede principal, com a exceção de que o dinheiro nessas redes de testes não tem valor real.

Assim podemos criar nossos contratos e implementar (deploy) a rede de teste e ver se tem algum erro

Collapse
 
alexcustodio profile image
Alex Custódio

Nessa rede de testes também há um custo computacional né? Mano tua explicação foi bastante esclarecedora. No caso, ethereum mainnet seria a rede principal? minha cabeça explodiu KAKSKSKSK vlw dms

Thread Thread
 
viniblack profile image
ViniBlack

Até onde eu sei não existe custo computacional nas redes de testes, mas posso estar errado, teria que dar uma pesquisada antes de afirmar isso para você kkkkk

Isso a mainnet é a rede principal, onde de fato acontece o processo de mineração e as negociações de compra e venda das criptos.

Collapse
 
alexcustodio profile image
Alex Custódio

Esse artigo me ajudou demais no que eu tava precisando, valeu mano! Vou entrar em contato com você pelo twitter, pois acho que a gente pode ter uma conversa bacana sobre tecnologia e eu tenho algumas perguntas.

Primeiramente, qual a diferença de Wei e Ether e por que você informou que é preciso mudar? Daria pra fazer com Wei também ou nesse caso específico foi preferido o uso de Ether?

Ainda também não entendi bem o funcionamento do Mapping, mas isso vou pegando aos poucos. De qualquer forma muito obrigado pelos três artigos e principalmente esse que tem uma função muito criativa.

Valeu!

Collapse
 
viniblack profile image
ViniBlack

Fico feliz por te conseguido ajudar 😃
Bora marcar uma conversa para trocarmos conhecimento.

Wei e Ether são unidades de medida da moeda, pense que são como o centavo (Wei) e o real (Ether).
Para conseguirmos usar o Wei precisamos mudar uma coisa no código

 require(msg.value >= minimunBet, "The bet amount is less than the minimum allowed");
Enter fullscreen mode Exit fullscreen mode

tirar a multiplicação do minimunBet * 10**18 aqui estamos multiplicando minimunBet vezes 10 e elevando a 18 potência.

Dê uma olhada nessa explicação da web3dev sobre mapping
solidity.web3dev.com.br/apostila/m...

Valeu pelo feedback, comecei a escrever agora então a cada post estou estou aprendendo como explicar as coisas.

Collapse
 
alexcustodio profile image
Info Comentário ocultado pelo autor do artigo - thread somente acessível via permalink
Alex Custódio

Opa, vamos marcar de conversar sim! Te chamei no twitter bastante informalmente pra a gente trocar uma ideia. Agora eu tô na escola mas umas 17:00 tô em casa.

Collapse
 
naiaraaraujodelima profile image
Naiara Araújo de Lima

Olá Vini preciso falar com vc

Collapse
 
viniblack profile image
ViniBlack

Ola Naiara.

me chama no linkedin ou no discord ViniBlack#6627

Alguns comentários foram escondidos pelo autor do post - Descubra mais