Это третий пост из серии «Мой первый смарт-контракт», целью которой является обучение в течение семи недель некоторым концепциям solidity, пока мы не создадим токен на основе ERC-20 с некоторыми модульными тестами.
В этом посте мы создадим договор пари, в котором вы сможете выбрать номер, поставить сумму Эфира, разыграть выигрышный номер и выплатить победителям.
Инструменты
Мы продолжим использовать Remix IDE для создания наших контрактов.
Создание нового файла
Мы создадим новый файл в папке contracts
под названием 02-bet.sol
.
Внутри файла 02-bet.sol
мы объявим лицензии нашего контракта, версию solidity и дадим контракту имя.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Bet {
}
Организация кода
Не существует правильного способа структурировать код наших контрактов, но для простоты понимания мы будем использовать следующий шаблон:
Структуры: где мы определяем некоторые более сложные типы данных (в этом посте мы разберем, что это значит);
Свойства: Здесь мы определяем наши переменные;
Модификаторы: Здесь мы определяем наши модификаторы;
Constructor: где мы определяем наш конструктор;
Общественные функции: Здесь мы определяем наши общественные функции;
Частные функции: Здесь мы определяем наши частные функции;
Структуры
struct
используются для представления структуры данных.
Например, чтобы иметь возможность делать ставки, каждому игроку нужно сообщить сумму ставки и выбранное число, для этого мы создадим struct
под названием Player
, которая будет хранить amountBet
(сумма ставки) и numberSelected
(выбранное число), и оба будут иметь тип uint256
.
//Structs
struct Player{
uint256 amountBet;
uint256 numberSelected;
}
Если вы хотите узнать немного больше о структурах, нажмите здесь
Переменные
Чтобы сделать нашу систему ставок, нам нужно будет создать несколько публичных переменных для хранения наших данных.
// Properties
address public owner;
address[] public players;
address[] public winners;
uint256 public totalBet;
uint256 public minimunBet;
mapping(address => Player) addressToPlayer;
Модификаторы
Давайте создадим модификатор isOwner
, который будет использоваться для функций, которые может выполнять только владелец контракта.
modifier isOwner() {
require(msg.sender == owner , "Sender is not owner!");
_;
}
Конструктор
Давайте теперь создадим наш конструктор, который определит, что owner
будет получать msg.sender
и мы сделаем проверку, что minimunBetValue
должно быть ненулевым, если minimunBetValue
равно нулю, мы вернем сообщение об ошибке.
constructor (uint256 minimunBetValue) {
owner = msg.sender;
if(minimunBetValue != 0) {
minimunBet = minimunBetValue;
}else {
revert("Invalid value");
}
}
Общественные функции
Выполнение ставки
Создадим публичную функцию bet
, в которую передадим параметр numberSelected
, который будет иметь тип uint256
.
Поскольку эта функция будет взаимодействовать с Эфирами, нам необходимо обеспечить отправку этих Эфиров в контракт.
Чтобы Solidity поняла, что она будет работать с эфирами, нам нужно добавить модификатор payable
, любая функция в Solidity с модификатором Payable гарантирует, что функция может отправлять и получать эфиры.
// Public Functions
function bet(uint256 numberSelected) public payable {
}
Если вы хотите узнать немного больше о модификаторе оплаты, нажмите здесь
Внутри функции bet
мы проверим, больше или равна ли сумма ставки minimunBet
, если больше, то мы создадим две переменные, одна из которых называется valueBet
типа uint256
, которая будет получать msg. value
и другой под названием playerBet
типа address
, который получит 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;
}
Теперь добавим информацию о наших участниках в список addressToPlayer
.
Мы создадим переменную newPlayer
, которая будет иметь тип Player
, и поскольку наши переменные не будут ничего хранить внутри себя, мы просто будем использовать их как временное место для хранения данных, пока мы не сохраним эти данные внутри переменной, нам нужно сообщить ей, что это память
. Внутри переменной newPlayer
мы определим, что numberSelected
получит numberSelected
и amountBet
получит valueBet
в конце мы добавим наш newPlayer
внутри отображения addressToPlayer
с «ключом» 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;
}
Если вы хотите узнать немного больше о
памяти
, нажмите здесь.
В конце функции мы суммируем сумму ставки с totalBet
и добавляем playerBet
в наш массив 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);
}
Генерация победителя
Создадим публичную функцию generateWinner
, только владелец контракта сможет использовать эту функцию, поэтому нам нужно вызвать наш модификатор isOwner
, внутри функции generateWinner
вызовем функцию generateWinnerNumber
.
function generateWinner() public isOwner{
generateWinnerNumber();
}
Частные функции
Платежеспособный победитель
Создадим частную функцию rewardWinner
, в которую передадим параметр numberPrizeGenerated
, который будет иметь тип uint256
.
// Private Functions
function rewardWinner(uint256 numberPrizeGenerated) private{
}
Внутри функции rewardWinner
мы создадим переменную count
, которая будет иметь тип uint256
и изначально получит значение 0. И создадим повторяющуюся структуру для проверки того, равно ли число, на которое сделана ставка, числу, которое было разыграно, для этого создадим переменную playerAddress
типа address
, которая получит players[i]
, таким образом, мы получим возможность проверить, равно ли число всех сделавших ставки числу, которое было разыграно, если равно, то мы добавим адрес сделавшего ставку в массив winners
и увеличим 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++;
}
}
}
Если вы хотите узнать немного больше о повторяющихся структурах в solidity, нажмите здесь
Теперь давайте произведем выплату победителям, для этого мы сделаем проверку, чтобы узнать, отличается ли count
от нуля, если отличается, мы создадим переменную winnerEtherAmount
, которая получит totalBet
, деленную на count
, и создадим повторяющуюся структуру для выплаты всем победителям, поэтому мы создадим переменную payTo
, которая будет иметь тип address
и payable
и получит winners[j]
. Мы проверим, является ли адрес победителя действительным адресом, если да, то мы выполним перевод на этот адрес.
// 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);
}
}
}
}
Если вы хотите понять, что означает
address(0)
, нажмите здесь
Нарисуйте число
Давайте создадим частную функцию generateWinnerNumber
, которая будет отвечать за розыгрыш случайного числа.
Необходимо помнить, что твердость не способна создавать случайные числа. На самом деле, ни один язык программирования сам по себе не способен создать абсолютно случайные числа.
Но мы можем создавать псевдослучайные числа, которые представляют собой наборы значений или элементов, которые являются статистически случайными, но получены из известной начальной точки и обычно повторяются несколько раз.
Поэтому давайте создадим переменную типа uint256
под названием numberPrize
, которая получит block.number
— номер текущего блока + block.timestamp
— число в секундах даты и времени, когда блок был закрыт, и разделим все это на 10, возьмем остаток от деления и сложим его с + 1 .
И вызовем функцию rewardWinner
, передав в качестве параметра numberPrize
.
function generateWinnerNumber() private {
uint256 numberPrize = (block.number + block.timestamp) % 10 + 1;
rewardWinner(uint256(numberPrize));
}
Как выглядит наш код
// 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();
}
}
Руки на
Теперь давайте скомпилируем и развернем наш контракт 02-bet.sol
.
-
В левом боковом меню нажмите на «Solidity compiler».
-
Нажмите на кнопку «Compile 02-bet.sol».
-
В левом боковом меню нажмите на «Развернуть & запустить транзакции».
-
Введите минимальную сумму ставки и нажмите кнопку «Развернуть».
-
Нажмите на стрелку, чтобы увидеть функции нашего договора.
-
Замените «Вэй» на «Эфир».
-
Введите сумму Эфиров, меньшую, чем минимальное значение ставки, введите число для ставки и нажмите «bet».
-
Поскольку мы ставим меньше минимальной ставки, в консоли появляется ошибка.
-
Теперь поставьте сумму, превышающую минимальную ставку, введите число для ставки и нажмите «bet».
-
Меняйте кошельки.
-
Заключить пари с другим портфелем.
-
Перед проведением розыгрыша выигрышного номера мы посмотрим на баланс наших кошельков.
-
С портфелем того, кто развернул контракт, нажмите на кнопку «generateWinner».
-
После генерации выигрышного номера мы проверим баланс наших кошельков.
Заключение
Это был третий пост из серии постов «Мой первый смарт-контракт».
Если вы выполнили все вышеперечисленные действия, то теперь у вас есть смарт-контракт для ставок, в котором вы можете установить минимальное количество Эфиров для ставки, выбрать номер для ставки, поставить сумму Эфиров и сгенерировать выигрышный номер.
Если вам понравился этот материал и он вам чем-то помог, оставьте лайк, чтобы помочь этому материалу охватить больше людей.
Ссылка на репозиторий
https://github.com/viniblack/meu-primeiro-smart-contract
Давайте обменяемся идеями?
Не стесняйтесь звонить мне, чтобы обменяться идеями, ниже приведены мои контакты.
https://www.linkedin.com/in/viniblack/