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
.
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 {
}
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:
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;
}
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 tipoaddress
; -
players
: Que vai ser um array do tipoaddress
; -
winners
: Que vai ser um array do tipoaddress
; -
totalBet
: Que vai ser do tipouint256
; -
minimunBet
: Que vai ser do tipouint256
; -
addressToPlayer
: Que vai ser ummapping
que irá receberaddress
como "chave" ePlayer
como "valor";
// Properties
address public owner;
address[] public players;
address[] public winners;
uint256 public totalBet;
uint256 public minimunBet;
mapping(address => Player) addressToPlayer;
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!");
_;
}
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");
}
}
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 {
}
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;
}
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;
}
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);
}
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();
}
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{
}
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++;
}
}
}
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);
}
}
}
}
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));
}
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();
}
}
Mão na massa
Agora vamos compilar e realizar o deploy do nosso contrato 02-bet.sol
.
No menu lateral esquerdo clique em "Deploy & run transactions".
Informe o valor mínimo da aposta e clique no botão "Deploy".
Informe uma quantidade de Ether menor que o valor mínimo da aposta, informe um número para apostar e clique em "bet".
Como fizemos uma aposta com o valor menor que a aposta mínima vai acontecer um erro no console.
Agora aposte um valor maior que o valor de aposta mínima, informe um número para apostar e clique em "bet".
Antes de realizarmos o sorteio do número premiado vamos olhar o saldo das nossas carteiras.
Com a carteira de quem fez o deploy do contrato clique no botão "generateWinner"
Depois de gerar o número premiado vamos olhar o saldo das nossas carteiras.
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.
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.
Latest comments (9)
Olá Vini preciso falar com vc
Ola Naiara.
me chama no linkedin ou no discord ViniBlack#6627
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.
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
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
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.
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!
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
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.
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.
Alguns comentários foram escondidos pelo autor do post - Descubra mais