From 3d20ae0676dc8d72f7fed40d3feb7c7ac8c11cc0 Mon Sep 17 00:00:00 2001 From: ChZak Date: Wed, 27 Mar 2024 11:16:28 +0100 Subject: [PATCH 01/28] add realm and README --- .../r/demo/gnolotto_factory/README.md | 405 ++++++++++++++++++ .../gno.land/r/demo/gnolotto_factory/gno.mod | 7 + .../gnolotto_factory/gnolotto_factory.gno | 239 +++++++++++ 3 files changed, 651 insertions(+) create mode 100644 examples/gno.land/r/demo/gnolotto_factory/README.md create mode 100644 examples/gno.land/r/demo/gnolotto_factory/gno.mod create mode 100644 examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno diff --git a/examples/gno.land/r/demo/gnolotto_factory/README.md b/examples/gno.land/r/demo/gnolotto_factory/README.md new file mode 100644 index 00000000000..b960d6a81ee --- /dev/null +++ b/examples/gno.land/r/demo/gnolotto_factory/README.md @@ -0,0 +1,405 @@ +# Write a simple Lottery on Gno.land + +## Overview + +This guide will demonstrate how to write a simple lottery on GnoLand. We'll cover adding funds to the realm, buying tickets for participation, and finally, distributing the winnings once the winning numbers are drawn. Each step is designed to ensure a smooth, transparent process from the lottery's inception to the awarding the prizepool. + +## Lottery functionality + +- **Lottery Creation**: Admin can create a lottery specifying the draw time and the prize pool. The amount sent with the transaction must match the prize pool specified. +- **Buying Tickets**: Users can buy tickets by specifying the lottery they want to enter and their chosen numbers. Each ticket costs a fixed amount at 10ugnot, and users can only buy tickets before the draw time. +- **Drawing Winners**: Once the draw time has passed, the admin can draw the winning numbers. This process is handled by the `Draw` function, which selects pseudo-random numbers as winners. +- **Rendering Results**: The `Render` function generates a readable output for the homepage, showing available lotteries, their details, and results if available. + +## Package + +```go +package gnolotto + +import ( + "std" + "time" + "strings" + "strconv" +) + +type Ticket struct { + Numbers []int // Holds the selected numbers for the lottery ticket + Owner std.Address // Address of the ticket owner +} + +type Lottery struct { + Tickets []Ticket // All tickets in the lottery + WinningNumbers []int // Winning numbers after the draw + DrawTime time.Time // Time of the draw + PrizePool int64 // Total prize pool amount +} + +// Intializes a new lottery instance with a specified draw time and prize pool +func NewLottery(drawTime time.Time, prizePool int64) *Lottery { + return &Lottery{ + DrawTime: drawTime, + PrizePool: prizePool, + Tickets: make([]Ticket, 0), + } +} + +// Adds a new ticket to the lottery +func (l *Lottery) AddTicket(numbers []int, owner std.Address) { + l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) +} + +// Conducts the draw by generating 5 random numbers between 1 and 15 +func (l *Lottery) Draw() { + var blockHeight int64 = std.GetHeight() + l.WinningNumbers = nil + numbersMap := make(map[int]bool) + + // Add variability to the pseudo-random number generation + var variabilityFactor int64 = 1 + + for len(l.WinningNumbers) < 5 { + simpleSeed := (blockHeight + variabilityFactor*251) % 233280 + number := int(simpleSeed % 15) + 1 // Ensure number is between 1 and 15 + + if !numbersMap[number] { + l.WinningNumbers = append(l.WinningNumbers, number) + numbersMap[number] = true + } + + variabilityFactor += 13 // Adjusts for increased variability + } +} + +// Itterate over all tickets to identify and return the addresses of the winners +func(l *Lottery) CheckWinners() []std.Address { + var winningOwners []std.Address + + for _, ticket := range l.Tickets { + matchCount := 0 + + for _, tNum := range ticket.Numbers { + for _, wNum := range l.WinningNumbers { + if tNum == wNum { + matchCount++ + break + } + } + } + + if matchCount == len(l.WinningNumbers) { + winningOwners = append(winningOwners, ticket.Owner) + } + } + return winningOwners +} + +// Distributes the prize pool equally among the winning ticket owners +func (l *Lottery) PayWinners(winningOwners []std.Address) { + if len(winningOwners) == 0 { + return + } else { + // Calculate reward per winner + var reward int64 = l.PrizePool / int64(len(winningOwners)) + banker := std.GetBanker(std.BankerTypeRealmSend) + + for _, owner := range winningOwners { + send := std.Coins{{"ugnot", reward}} + banker.SendCoins(std.GetOrigPkgAddr(), owner, send) + } + + l.PrizePool = 0 // Reset the prize pool after distribution + } +} +``` + +A few remarks : + +- In the blockchain world, it's difficult to generate random numbers without using an oracle. Since Gno.land doesn't yet offer an oracle, the `Draw()` function generates random numbers based on the height of the block. This solution is not viable in real-life conditions, but is sufficient for this tutorial. +- In the `PayWinners()` function, we use the `std` package to manipulate the funds available in the realm. + +## Realm + +```go +package gnolotto_factory + +import ( + "bytes" + "time" + "strconv" + "strings" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/gnolotto" +) + +var lotteries *avl.Tree + +// Replace this address with your address +var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + +// Initializes the lottery AVL tree +func init() { + lotteries = avl.NewTree() +} + +// Creates a new lottery, only callable by admin. +func CreateLottery(drawTime int64, prizePool int64) (int, string) { + sentCoins := std.GetOrigSend() + amount := sentCoins.AmountOf("ugnot") + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", int64(amount)}} + + if prizePool != amount { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Prize pool must match the transaction value" + } + + if drawTime < time.Now().Unix() { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Invalid draw time" + } + + if std.GetOrigCaller() != admin { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Only the admin can create a lottery" + } + + lotteryID := lotteries.Size() + lottery := gnolotto.NewLottery(time.Unix(drawTime, 0), prizePool) + + lotteries.Set(ufmt.Sprintf("%d", lotteryID), lottery) + return lotteryID, "Lottery created successfully" +} + +// Buy ticket for a specific lottery. +func BuyTicket(lotteryID int, numbersStr string) (int, string) { + sentCoins := std.GetOrigSend() + amount := sentCoins.AmountOf("ugnot") + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", int64(amount)}} + + id := ufmt.Sprintf("%d", lotteryID) + lotteryRaw, exists := lotteries.Get(id) + + if !exists { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Lottery not found" + } + + // Convert string to slice of integers. + numbersSlice := strings.Split(numbersStr, ",") + numbers := make([]int, len(numbersSlice)) + + for i, numStr := range numbersSlice { + num, err := strconv.Atoi(numStr) + if err != nil { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + panic("Invalid number: " + err.Error()) + } + numbers[i] = num + } + + //Verify if the amount sent is equal to the ticket price. + if amount != 10 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Ticket price must be 10 UGNOT" + } + + // Verify if the numbers are unique. + uniqueNumbers := make(map[int]bool) + + for _, num := range numbers { + if uniqueNumbers[num] { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Numbers must be unique" + } + + uniqueNumbers[num] = true + } + + l, _ := lotteryRaw.(*gnolotto.Lottery) + + if time.Now().Unix() > l.DrawTime.Unix() { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "This lottery has already ended" + } + + if len(numbers) > 5 || len(numbers) < 5 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "You must select exactly 5 numbers" + } + + for _, num := range numbers { + if num > 15 || num < 1 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Invalid number, select number range from 1 to 15" + } + } + + caller := std.GetOrigCaller() + l.AddTicket(numbers, caller) + return 1, "Ticket purchased successfully" +} + +// Draws the winning numbers for a specific lottery, only callable by admin the draw time has passed. +func Draw(lotteryID int) (int, string) { + id := ufmt.Sprintf("%d", lotteryID) + + if std.GetOrigCaller() != admin { + return -1, "Only the admin can draw the winning numbers" + } + + lotteryRaw, exists := lotteries.Get(id) + + if !exists { + return -1, "Lottery not found" + } + + l, _ := lotteryRaw.(*gnolotto.Lottery) + + if time.Now().Unix() < l.DrawTime.Unix() { + return -1, "Draw time has not passed yet" + } + + l.Draw() + return 1, "Winning numbers drawn successfully" +} +``` + +A few remarks : + +- The `Draw()` function generates 5 winning numbers. A ticket purchase must be accompanied by a selection of 5 numbers in order to participate. +- In the `BuyTicket()` function, we take as arguments the winning numbers in string type, as it's not possible to pass a slice as an argument in `gnokey`. We therefore retrieve the winning numbers in string type, split them and convert them to slice to add them to our `Ticket` struct in our package +- When we make a function call using `gnokey` and add an amount in `-send`, this amount will be sent to the realm even if a condition does not allow the action in our code. This is why, in the `CreateLottery()` and `BuyTicket()` functions, we use the `std` package to refund the wallet that sent the funds in the event that a condition is not met. +- For this lottery, we have chosen to set the price of a ticket at 10ugnot. If the user buys a ticket and sends + or - 10ugnot, he will be refunded the amount sent. At the end of the lottery creation process, we check that the amount sent to the realm is equal to the amount defined in the prize pool. Sending the amount to the realm when the lottery is created allows us to distribute the winnings to the winner(s) automatically after the draw. + +## Render + +And finally, our Render() function, which displays our lottery. + +```go +func Render(path string) string { + if path == "" { + return renderHomepage() + } + + return "unknown page" +} + +func renderHomepage() string { + var b bytes.Buffer + b.WriteString("# Welcome to GnoLotto\n\n") + + if lotteries.Size() == 0 { + b.WriteString("### *No lotteries available currently!*\n") + return b.String() + } + + lotteries.Iterate("", "", func(key string, value interface{}) bool { + l := value.(*gnolotto.Lottery) + + b.WriteString( + ufmt.Sprintf( + "## Lottery ID: *%s*\n", + key, + ), + ) + + b.WriteString( + ufmt.Sprintf( + "Draw Time: *%s*\n", + l.DrawTime.Format("Mon Jan _2 15:04:05 2006"), + ), + ) + + b.WriteString( + ufmt.Sprintf( + "Prize Pool: *%d* UGNOT\n\n", + l.PrizePool, + ), + ) + + if time.Now().Unix() > l.DrawTime.Unix() { + // If the lottery has ended, display the winners. + var numbersStr string + for i, number := range l.WinningNumbers { + if i > 0 { + numbersStr += ", " + } + numbersStr += ufmt.Sprintf("%d", number) + } + + b.WriteString(ufmt.Sprintf("- Winning numbers [%s]\n\n", numbersStr)) + winners := l.CheckWinners() + l.PayWinners(winners) + + if len(winners) > 0 { + b.WriteString("Winners:\n\n") + for _, winner := range winners { + b.WriteString(ufmt.Sprintf("*%s*\n\n", winner.String())) + } + } else { + b.WriteString("*No winners for this lottery.*\n") + } + } else { + // If the lottery is still ongoing, display the participants. + if len(l.Tickets) > 0 { + b.WriteString("Participants:\n") + for _, ticket := range l.Tickets { + // Initialise string for displaying numbers + var numbersStr string + for i, number := range ticket.Numbers { + if i > 0 { + numbersStr += ", " + } + + numbersStr += ufmt.Sprintf("%d", number) + } + + b.WriteString(ufmt.Sprintf("- *%s* with numbers [%s]\n", ticket.Owner.String(), numbersStr)) + } + } else { + b.WriteString("*No participants yet.*\n") + } + } + b.WriteString("\n") + return false + }) + banker := std.GetBanker(std.BankerTypeReadonly) + contractAddress := std.GetOrigPkgAddr() + coins := banker.GetCoins(contractAddress) + + b.WriteString("## Contract Balance:\n") + b.WriteString(coins.String() + "\n\n") + + return b.String() +} +``` + +Congratulations, your lottery has been successfully created 🥳 ! Below you'll find the commands for using this lottery with `gnokey`. + +**Create a new Lottery (Admin) :** +``` +gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "CreateLottery" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "10000ugnot" -broadcast -chainid "dev" -args "1711487446" -args "10000" -remote "tcp://127.0.0.1:36657" test1 +``` +*The first argument corresponds to the date and time of the draw run, in unix format* +*The second is the prize pool amount, so don't forget to put the same amount in `-send`.* + +**Buy a ticket :** +``` +gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "BuyTicket" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "10ugnot" -broadcast -chainid "dev" -args "0" -args "1,2,3,4,5" -remote "tcp://127.0.0.1:36657" test1 +``` +*The first argument corresponds to the ID of Lottery* +*The second arguments corresponds to the lottery participation numbers* +*Don't forget to add 10ugnot to `-send`, which corresponds to the price of a ticket.* + +**Drawing (Admin) :** + +``` +gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "Draw" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -broadcast -chainid "dev" -args "0" -remote "tcp://127.0.0.1:36657" test1 +``` + +*The argument corresponds to the ID of the lottery for which you wish to perform the draw. (Don't forget that you can't make a draw until the date defined at creation has passed.)* diff --git a/examples/gno.land/r/demo/gnolotto_factory/gno.mod b/examples/gno.land/r/demo/gnolotto_factory/gno.mod new file mode 100644 index 00000000000..d0eaf029775 --- /dev/null +++ b/examples/gno.land/r/demo/gnolotto_factory/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/gnolotto_factory + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/gnolotto v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno new file mode 100644 index 00000000000..76b4dffbdef --- /dev/null +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -0,0 +1,239 @@ +package gnolotto_factory + +import ( + "bytes" + "std" + "strconv" + "strings" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/gnolotto" + "gno.land/p/demo/ufmt" +) + +var lotteries *avl.Tree + +// Replace this address with your address +var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + +// Initializes the lottery AVL tree +func init() { + lotteries = avl.NewTree() +} + +// Creates a new lottery, only callable by admin. +func CreateLottery(drawTime int64, prizePool int64) (int, string) { + sentCoins := std.GetOrigSend() + amount := sentCoins.AmountOf("ugnot") + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", int64(amount)}} + + if prizePool != amount { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Prize pool must match the transaction value" + } + + if drawTime < time.Now().Unix() { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Invalid draw time" + } + + if std.GetOrigCaller() != admin { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Only the admin can create a lottery" + } + + lotteryID := lotteries.Size() + lottery := gnolotto.NewLottery(time.Unix(drawTime, 0), prizePool) + + lotteries.Set(ufmt.Sprintf("%d", lotteryID), lottery) + return lotteryID, "Lottery created successfully" +} + +// Buy ticket for a specific lottery. +func BuyTicket(lotteryID int, numbersStr string) (int, string) { + sentCoins := std.GetOrigSend() + amount := sentCoins.AmountOf("ugnot") + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", int64(amount)}} + + id := ufmt.Sprintf("%d", lotteryID) + lotteryRaw, exists := lotteries.Get(id) + if !exists { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Lottery not found" + } + + // Convert string to slice of integers. + numbersSlice := strings.Split(numbersStr, ",") + numbers := make([]int, len(numbersSlice)) + for i, numStr := range numbersSlice { + num, err := strconv.Atoi(numStr) + if err != nil { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + panic("Invalid number: " + err.Error()) + } + numbers[i] = num + } + + //Verify if the amount sent is equal to the ticket price. + if amount != 10 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Ticket price must be 10 UGNOT" + } + + // Verify if the numbers are unique. + uniqueNumbers := make(map[int]bool) + for _, num := range numbers { + if uniqueNumbers[num] { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Numbers must be unique" + } + uniqueNumbers[num] = true + } + + l, _ := lotteryRaw.(*gnolotto.Lottery) + + if time.Now().Unix() > l.DrawTime.Unix() { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "This lottery has already ended" + } + + if len(numbers) > 5 || len(numbers) < 5 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "You must select exactly 5 numbers" + } + + for _, num := range numbers { + if num > 15 || num < 1 { + banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) + return -1, "Invalid number, select number range from 1 to 15" + } + } + + caller := std.GetOrigCaller() + l.AddTicket(numbers, caller) + + return 1, "Ticket purchased successfully" +} + +// Draws the winning numbers for a specific lottery, only callable by admin the draw time has passed. +func Draw(lotteryID int) (int, string) { + id := ufmt.Sprintf("%d", lotteryID) + if std.GetOrigCaller() != admin { + return -1, "Only the admin can draw the winning numbers" + } + lotteryRaw, exists := lotteries.Get(id) + if !exists { + return -1, "Lottery not found" + } + + l, _ := lotteryRaw.(*gnolotto.Lottery) + + if time.Now().Unix() < l.DrawTime.Unix() { + return -1, "Draw time has not passed yet" + } + + l.Draw() + return 1, "Winning numbers drawn successfully" +} + +func Render(path string) string { + if path == "" { + return renderHomepage() + } + + return "unknown page" +} + +func renderHomepage() string { + var b bytes.Buffer + + b.WriteString("# Welcome to GnoLotto\n\n") + + if lotteries.Size() == 0 { + b.WriteString("### *No lotteries available currently!*\n") + return b.String() + } + + lotteries.Iterate("", "", func(key string, value interface{}) bool { + l := value.(*gnolotto.Lottery) + + b.WriteString( + ufmt.Sprintf( + "## Lottery ID: *%s*\n", + key, + ), + ) + + b.WriteString( + ufmt.Sprintf( + "Draw Time: *%s*\n", + l.DrawTime.Format("Mon Jan _2 15:04:05 2006"), + ), + ) + + b.WriteString( + ufmt.Sprintf( + "Prize Pool: *%d* UGNOT\n\n", + l.PrizePool, + ), + ) + + if time.Now().Unix() > l.DrawTime.Unix() { + // If the lottery has ended, display the winners. + var numbersStr string + for i, number := range l.WinningNumbers { + if i > 0 { + numbersStr += ", " + } + numbersStr += ufmt.Sprintf("%d", number) + } + b.WriteString(ufmt.Sprintf("- Winning numbers [%s]\n\n", numbersStr)) + + winners := l.CheckWinners() + + l.PayWinners(winners) + + if len(winners) > 0 { + b.WriteString("Winners:\n\n") + for _, winner := range winners { + b.WriteString(ufmt.Sprintf("*%s*\n\n", winner.String())) + } + } else { + b.WriteString("*No winners for this lottery.*\n") + } + } else { + // If the lottery is still ongoing, display the participants. + if len(l.Tickets) > 0 { + b.WriteString("Participants:\n") + for _, ticket := range l.Tickets { + // Initialise string for displaying numbers + var numbersStr string + for i, number := range ticket.Numbers { + if i > 0 { + numbersStr += ", " + } + numbersStr += ufmt.Sprintf("%d", number) + } + b.WriteString(ufmt.Sprintf("- *%s* with numbers [%s]\n", ticket.Owner.String(), numbersStr)) + } + } else { + b.WriteString("*No participants yet.*\n") + } + } + + b.WriteString("\n") + return false + }) + + banker := std.GetBanker(std.BankerTypeReadonly) + contractAddress := std.GetOrigPkgAddr() + coins := banker.GetCoins(contractAddress) + + b.WriteString("## Contract Balance:\n") + b.WriteString(coins.String() + "\n\n") + + return b.String() +} From f0a477d89eaf3bd48f1416ec8f1f0f5f42dc5d7a Mon Sep 17 00:00:00 2001 From: ChZak Date: Wed, 27 Mar 2024 11:17:01 +0100 Subject: [PATCH 02/28] add package gnolotto --- examples/gno.land/p/demo/gnolotto/gno.mod | 1 + .../gno.land/p/demo/gnolotto/gnolotto.gno | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 examples/gno.land/p/demo/gnolotto/gno.mod create mode 100644 examples/gno.land/p/demo/gnolotto/gnolotto.gno diff --git a/examples/gno.land/p/demo/gnolotto/gno.mod b/examples/gno.land/p/demo/gnolotto/gno.mod new file mode 100644 index 00000000000..f21596b6079 --- /dev/null +++ b/examples/gno.land/p/demo/gnolotto/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnolotto diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno new file mode 100644 index 00000000000..2097fb10603 --- /dev/null +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -0,0 +1,100 @@ +package gnolotto + +import ( + "std" + "strconv" + "strings" + "time" +) + +type Ticket struct { + Numbers []int // Holds the selected numbers for the lottery ticket + Owner std.Address // Address of the ticket owner +} + +type Lottery struct { + Tickets []Ticket // All tickets in the lottery + WinningNumbers []int // Winning numbers after the draw + DrawTime time.Time // Time of the draw + PrizePool int64 // Total prize pool amount +} + +// Intializes a new lottery instance with a specified draw time and prize pool +func NewLottery(drawTime time.Time, prizePool int64) *Lottery { + return &Lottery{ + DrawTime: drawTime, + PrizePool: prizePool, + Tickets: make([]Ticket, 0), + } +} + +// Adds a new ticket to the lottery +func (l *Lottery) AddTicket(numbers []int, owner std.Address) { + l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) +} + +// Conducts the draw by generating 5 random numbers between 1 and 15 +func (l *Lottery) Draw() { + var blockHeight int64 = std.GetHeight() + + l.WinningNumbers = nil + numbersMap := make(map[int]bool) + + // Add variability to the pseudo-random number generation + var variabilityFactor int64 = 1 + + for len(l.WinningNumbers) < 5 { + simpleSeed := (blockHeight + variabilityFactor*251) % 233280 + number := int(simpleSeed%15) + 1 // Ensure number is between 1 and 15 + + if !numbersMap[number] { + l.WinningNumbers = append(l.WinningNumbers, number) + numbersMap[number] = true + } + + variabilityFactor += 13 // Adjusts for increased variability + } +} + +// Itterate over all tickets to identify and return the addresses of the winners +func (l *Lottery) CheckWinners() []std.Address { + var winningOwners []std.Address + + for _, ticket := range l.Tickets { + matchCount := 0 + + for _, tNum := range ticket.Numbers { + for _, wNum := range l.WinningNumbers { + if tNum == wNum { + matchCount++ + break + } + } + } + + if matchCount == len(l.WinningNumbers) { + winningOwners = append(winningOwners, ticket.Owner) + } + } + + return winningOwners +} + +// Distributes the prize pool equally among the winning ticket owners +func (l *Lottery) PayWinners(winningOwners []std.Address) { + if len(winningOwners) == 0 { + return + } else { + // Calculate reward per winner + var reward int64 = l.PrizePool / int64(len(winningOwners)) + + banker := std.GetBanker(std.BankerTypeRealmSend) + + for _, owner := range winningOwners { + send := std.Coins{{"ugnot", reward}} + banker.SendCoins(std.GetOrigPkgAddr(), owner, send) // Send reward to each winner + } + + l.PrizePool = 0 // Reset the prize pool after distribution + } +} From e56ea565fbb4e4e2086fafe9b904d817661b79c9 Mon Sep 17 00:00:00 2001 From: ChZak Date: Tue, 2 Apr 2024 10:04:35 +0200 Subject: [PATCH 03/28] replace comment random by pseudo-random --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 2097fb10603..1ae45d47642 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -33,7 +33,7 @@ func (l *Lottery) AddTicket(numbers []int, owner std.Address) { l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) } -// Conducts the draw by generating 5 random numbers between 1 and 15 +// Conducts the draw by generating 5 pseudo-random numbers between 1 and 15 func (l *Lottery) Draw() { var blockHeight int64 = std.GetHeight() From f9f4993981ee5f7a53f478cf6e370492c0c36597 Mon Sep 17 00:00:00 2001 From: ChZak Date: Tue, 2 Apr 2024 10:20:52 +0200 Subject: [PATCH 04/28] comment modification, replacing random by pesudo-random --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 1ae45d47642..b3376d080f8 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -33,7 +33,7 @@ func (l *Lottery) AddTicket(numbers []int, owner std.Address) { l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) } -// Conducts the draw by generating 5 pseudo-random numbers between 1 and 15 +// Conducts the draw by generating 5 pseudo-random numbers between 1 and 15 inclusive func (l *Lottery) Draw() { var blockHeight int64 = std.GetHeight() From 793f749976d1a051de7126bd49d7aba9c3ed3be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:30:42 +0200 Subject: [PATCH 05/28] Add constant for max lottery numbers --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 4 +++- .../gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index b3376d080f8..d42ed0d51a8 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -28,6 +28,8 @@ func NewLottery(drawTime time.Time, prizePool int64) *Lottery { } } +const MaxLottoNumbers = 5 + // Adds a new ticket to the lottery func (l *Lottery) AddTicket(numbers []int, owner std.Address) { l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) @@ -43,7 +45,7 @@ func (l *Lottery) Draw() { // Add variability to the pseudo-random number generation var variabilityFactor int64 = 1 - for len(l.WinningNumbers) < 5 { + for len(l.WinningNumbers) < MaxLottoNumbers { simpleSeed := (blockHeight + variabilityFactor*251) % 233280 number := int(simpleSeed%15) + 1 // Ensure number is between 1 and 15 diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 76b4dffbdef..1b8f281e46c 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -100,7 +100,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { return -1, "This lottery has already ended" } - if len(numbers) > 5 || len(numbers) < 5 { + if len(numbers) > gnolotto.MaxLottoNumbers || len(numbers) < gnolotto.MaxLottoNumbers { banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) return -1, "You must select exactly 5 numbers" } From 303dfd430d57ee0063a5205ef6477a77ce1a3715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:33:58 +0200 Subject: [PATCH 06/28] Correct typo for `iterate` --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index d42ed0d51a8..fb0cf77d7ba 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -58,7 +58,7 @@ func (l *Lottery) Draw() { } } -// Itterate over all tickets to identify and return the addresses of the winners +// Iterate over all tickets to identify and return the addresses of the winners func (l *Lottery) CheckWinners() []std.Address { var winningOwners []std.Address From f6b51f2512980e6d32a9f4528310df5297e6cda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:36:37 +0200 Subject: [PATCH 07/28] replace winners by winningOwners --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index fb0cf77d7ba..56bb04f35fa 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -58,7 +58,7 @@ func (l *Lottery) Draw() { } } -// Iterate over all tickets to identify and return the addresses of the winners +// Iterate over all tickets to identify and return the addresses of the winningOwners func (l *Lottery) CheckWinners() []std.Address { var winningOwners []std.Address From ca1398d4d302dc10a80e6879708c0c9eba2da6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:53:08 +0200 Subject: [PATCH 08/28] remove casting to int64 --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 1b8f281e46c..d732208e2fe 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -27,7 +27,7 @@ func CreateLottery(drawTime int64, prizePool int64) (int, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") banker := std.GetBanker(std.BankerTypeRealmSend) - send := std.Coins{{"ugnot", int64(amount)}} + send := std.Coins{{"ugnot", amount}} if prizePool != amount { banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) From 8780052aa276226d4edc4d93c88304462f87b6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:15:43 +0200 Subject: [PATCH 09/28] remove refund manually and add panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index d732208e2fe..44c8eff27f8 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -30,8 +30,7 @@ func CreateLottery(drawTime int64, prizePool int64) (int, string) { send := std.Coins{{"ugnot", amount}} if prizePool != amount { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Prize pool must match the transaction value" + panic("Prize pool must match the transaction value") } if drawTime < time.Now().Unix() { From 88e2171fc0fadc7e04bbb1a9b5df66d386725769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:58:15 +0200 Subject: [PATCH 10/28] remove refund manually and add panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 44c8eff27f8..8c8f982d0c5 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -60,8 +60,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { id := ufmt.Sprintf("%d", lotteryID) lotteryRaw, exists := lotteries.Get(id) if !exists { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Lottery not found" + panic("Lottery not found") } // Convert string to slice of integers. From 0832c337bccb8de94b4fc11b384025e4dbe39971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:59:59 +0200 Subject: [PATCH 11/28] remove refund manually and add panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 8c8f982d0c5..86b94ad22dc 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -85,8 +85,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { uniqueNumbers := make(map[int]bool) for _, num := range numbers { if uniqueNumbers[num] { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Numbers must be unique" + panic("Numbers must be unique") } uniqueNumbers[num] = true } From add8778642aa7c0721da785cc2d761c38de0a067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:00:57 +0200 Subject: [PATCH 12/28] remove refund manually and add panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 86b94ad22dc..cd40fda90ae 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -93,8 +93,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { l, _ := lotteryRaw.(*gnolotto.Lottery) if time.Now().Unix() > l.DrawTime.Unix() { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "This lottery has already ended" + panic("This lottery has already ended") } if len(numbers) > gnolotto.MaxLottoNumbers || len(numbers) < gnolotto.MaxLottoNumbers { From 769d870fa3a739cac47915e671d0e3a7e7c3f7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:04:41 +0200 Subject: [PATCH 13/28] remove refund manually and add panic and modify condition --- .../gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index cd40fda90ae..52cf9ebca95 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -96,9 +96,8 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { panic("This lottery has already ended") } - if len(numbers) > gnolotto.MaxLottoNumbers || len(numbers) < gnolotto.MaxLottoNumbers { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "You must select exactly 5 numbers" + if len(numbers) != gnolotto.MaxLottoNumbers { + panic("You must select exactly 5 numbers") } for _, num := range numbers { From 92caa5492ecc90f6e035453f56f89c8eed9b8dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:06:21 +0200 Subject: [PATCH 14/28] remove refund manually and add panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 52cf9ebca95..275e3f6cd18 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -102,8 +102,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { for _, num := range numbers { if num > 15 || num < 1 { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Invalid number, select number range from 1 to 15" + panic("Invalid number, select number range from 1 to 15") } } From a0069150572f2febd24ca5693cafe1a01090c1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:11:07 +0200 Subject: [PATCH 15/28] replace return by panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 275e3f6cd18..a607333b28d 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -116,7 +116,7 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { func Draw(lotteryID int) (int, string) { id := ufmt.Sprintf("%d", lotteryID) if std.GetOrigCaller() != admin { - return -1, "Only the admin can draw the winning numbers" + panic("Only the admin can draw the winning numbers") } lotteryRaw, exists := lotteries.Get(id) if !exists { From e54628a8c344f41c6535ce03e07e105fdf9e1184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:12:13 +0200 Subject: [PATCH 16/28] replace return by panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index a607333b28d..32164a2d7b6 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -120,7 +120,7 @@ func Draw(lotteryID int) (int, string) { } lotteryRaw, exists := lotteries.Get(id) if !exists { - return -1, "Lottery not found" + panic("Lottery not found") } l, _ := lotteryRaw.(*gnolotto.Lottery) From 3b683a7600123dde554a378952f139e41ac2c511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:13:13 +0200 Subject: [PATCH 17/28] replace return by panic --- examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 32164a2d7b6..a10d10683af 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -126,7 +126,7 @@ func Draw(lotteryID int) (int, string) { l, _ := lotteryRaw.(*gnolotto.Lottery) if time.Now().Unix() < l.DrawTime.Unix() { - return -1, "Draw time has not passed yet" + panic("Draw time has not passed yet") } l.Draw() From 444b5ef8ecfe44379daf2588011281238efb47d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:29:09 +0200 Subject: [PATCH 18/28] Modify ID lottery by seqid --- .../gno.land/r/demo/gnolotto_factory/gno.mod | 1 + .../gnolotto_factory/gnolotto_factory.gno | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gno.mod b/examples/gno.land/r/demo/gnolotto_factory/gno.mod index d0eaf029775..96e93b3b013 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gno.mod +++ b/examples/gno.land/r/demo/gnolotto_factory/gno.mod @@ -4,4 +4,5 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/demo/gnolotto v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index a10d10683af..53976cdc008 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -10,9 +10,11 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/gnolotto" "gno.land/p/demo/ufmt" + "gno.land/p/demo/seqid" ) var lotteries *avl.Tree +var lotteriesID seqid.ID // Replace this address with your address var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" @@ -23,41 +25,39 @@ func init() { } // Creates a new lottery, only callable by admin. -func CreateLottery(drawTime int64, prizePool int64) (int, string) { +func CreateLottery(drawTime int64, prizePool int64) (string, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - send := std.Coins{{"ugnot", amount}} if prizePool != amount { panic("Prize pool must match the transaction value") } if drawTime < time.Now().Unix() { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Invalid draw time" + panic("Invalid draw time") } if std.GetOrigCaller() != admin { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Only the admin can create a lottery" + panic("Only the admin can create a lottery") } - lotteryID := lotteries.Size() + lotteryID := lotteriesID.Next() lottery := gnolotto.NewLottery(time.Unix(drawTime, 0), prizePool) - lotteries.Set(ufmt.Sprintf("%d", lotteryID), lottery) - return lotteryID, "Lottery created successfully" + lotteries.Set(lotteryID.Binary(), lottery) + return lotteryID.String(), "Lottery created successfully" } // Buy ticket for a specific lottery. -func BuyTicket(lotteryID int, numbersStr string) (int, string) { +func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") banker := std.GetBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", int64(amount)}} - id := ufmt.Sprintf("%d", lotteryID) + lotteryID, _ := seqid.FromString(lotteryIDStr) + id := lotteryID.Binary() + lotteryRaw, exists := lotteries.Get(id) if !exists { panic("Lottery not found") @@ -113,8 +113,10 @@ func BuyTicket(lotteryID int, numbersStr string) (int, string) { } // Draws the winning numbers for a specific lottery, only callable by admin the draw time has passed. -func Draw(lotteryID int) (int, string) { - id := ufmt.Sprintf("%d", lotteryID) +func Draw(lotteryIDStr string) (int, string) { + lotteryID, _ := seqid.FromString(lotteryIDStr) + id := lotteryID.Binary() + if std.GetOrigCaller() != admin { panic("Only the admin can draw the winning numbers") } @@ -154,10 +156,13 @@ func renderHomepage() string { lotteries.Iterate("", "", func(key string, value interface{}) bool { l := value.(*gnolotto.Lottery) + lotteryID, _ := seqid.FromBinary(key) + readableID := lotteryID.String() + b.WriteString( ufmt.Sprintf( "## Lottery ID: *%s*\n", - key, + readableID, ), ) From b0bccca90a70351529a9453ac44ad0b143d4f4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:35:21 +0200 Subject: [PATCH 19/28] add const TICKET_PRICE --- .../gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 53976cdc008..ba91a5e4b43 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -19,6 +19,8 @@ var lotteriesID seqid.ID // Replace this address with your address var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" +const TICKET_PRICE = 10 + // Initializes the lottery AVL tree func init() { lotteries = avl.NewTree() @@ -76,9 +78,8 @@ func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { } //Verify if the amount sent is equal to the ticket price. - if amount != 10 { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Ticket price must be 10 UGNOT" + if amount != TICKET_PRICE { + panic("Ticket price must be 10 UGNOT") } // Verify if the numbers are unique. From bba81f54056ae2c8096c46022a3553fbafdda570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:09:52 +0200 Subject: [PATCH 20/28] separation of string to int slice conversion function --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 13 +++++++++++++ .../r/demo/gnolotto_factory/gnolotto_factory.gno | 12 +++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 56bb04f35fa..04abf125931 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -100,3 +100,16 @@ func (l *Lottery) PayWinners(winningOwners []std.Address) { l.PrizePool = 0 // Reset the prize pool after distribution } } + +func StringToIntSlice(numbersStr string) ([]int, error) { + numbersSlice := strings.Split(numbersStr, ",") + numbers := make([]int, len(numbersSlice)) + for i, numStr := range numbersSlice { + num, err := strconv.Atoi(strings.TrimSpace(numStr)) + if err != nil { + return nil, err + } + numbers[i] = num + } + return numbers, nil +} \ No newline at end of file diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index ba91a5e4b43..bfe4f591724 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -66,15 +66,9 @@ func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { } // Convert string to slice of integers. - numbersSlice := strings.Split(numbersStr, ",") - numbers := make([]int, len(numbersSlice)) - for i, numStr := range numbersSlice { - num, err := strconv.Atoi(numStr) - if err != nil { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - panic("Invalid number: " + err.Error()) - } - numbers[i] = num + numbers, err := gnolotto.StringToIntSlice(numbersStr) + if err != nil { + panic("Invalid number: " + err.Error()) // Gestion de l'erreur si la conversion échoue. } //Verify if the amount sent is equal to the ticket price. From 1cdf875e7dc1fa91b27baf5303ef7a5640870827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:35:06 +0200 Subject: [PATCH 21/28] formatting gnokey commands --- .../r/demo/gnolotto_factory/README.md | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/README.md b/examples/gno.land/r/demo/gnolotto_factory/README.md index b960d6a81ee..3f09f45a83c 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/README.md +++ b/examples/gno.land/r/demo/gnolotto_factory/README.md @@ -383,14 +383,36 @@ Congratulations, your lottery has been successfully created 🥳 ! Below you'll **Create a new Lottery (Admin) :** ``` -gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "CreateLottery" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "10000ugnot" -broadcast -chainid "dev" -args "1711487446" -args "10000" -remote "tcp://127.0.0.1:36657" test1 +gnokey maketx call \ +-pkgpath "gno.land/r/demo/gnolotto_factory" \ +-func "CreateLottery" \ +-gas-fee 1000000ugnot \ +-gas-wanted 2000000 \ +-send "10000ugnot" \ +-broadcast \ +-chainid "dev" \ +-args "1711487446" \ +-args "10000" \ +-remote "tcp://127.0.0.1:36657" \ +test1 ``` *The first argument corresponds to the date and time of the draw run, in unix format* *The second is the prize pool amount, so don't forget to put the same amount in `-send`.* **Buy a ticket :** ``` -gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "BuyTicket" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "10ugnot" -broadcast -chainid "dev" -args "0" -args "1,2,3,4,5" -remote "tcp://127.0.0.1:36657" test1 +gnokey maketx call \ +-pkgpath "gno.land/r/demo/gnolotto_factory" \ +-func "BuyTicket" \ +-gas-fee 1000000ugnot \ +-gas-wanted 2000000 \ +-send "10ugnot" \ +-broadcast \ +-chainid "dev" \ +-args "0" \ +-args "1,2,3,4,5" \ +-remote "tcp://127.0.0.1:36657" \ +test1 ``` *The first argument corresponds to the ID of Lottery* *The second arguments corresponds to the lottery participation numbers* @@ -399,7 +421,17 @@ gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "BuyTicket" **Drawing (Admin) :** ``` -gnokey maketx call -pkgpath "gno.land/r/demo/gnolotto_factory" -func "Draw" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -broadcast -chainid "dev" -args "0" -remote "tcp://127.0.0.1:36657" test1 +gnokey maketx call \ +-pkgpath "gno.land/r/demo/gnolotto_factory" \ +-func "Draw" \ +-gas-fee 1000000ugnot \ +-gas-wanted 2000000 \ +-send "" \ +-broadcast \ +-chainid "dev" \ +-args "0" \ +-remote "tcp://127.0.0.1:36657" \ +test1 ``` *The argument corresponds to the ID of the lottery for which you wish to perform the draw. (Don't forget that you can't make a draw until the date defined at creation has passed.)* From 4c649fd5e6cfd04a4ce4f4688d0a32ca3725e7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:31:09 +0200 Subject: [PATCH 22/28] refactoring README --- .../r/demo/gnolotto_factory/README.md | 208 ++++++++++-------- 1 file changed, 112 insertions(+), 96 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/README.md b/examples/gno.land/r/demo/gnolotto_factory/README.md index 3f09f45a83c..7ea900439d5 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/README.md +++ b/examples/gno.land/r/demo/gnolotto_factory/README.md @@ -2,14 +2,21 @@ ## Overview -This guide will demonstrate how to write a simple lottery on GnoLand. We'll cover adding funds to the realm, buying tickets for participation, and finally, distributing the winnings once the winning numbers are drawn. Each step is designed to ensure a smooth, transparent process from the lottery's inception to the awarding the prizepool. +This guide will demonstrate how to write a simple lottery on GnoLand. +We'll cover adding funds to the realm, buying tickets for participation, and finally, +distributing the winnings once the winning numbers are drawn. +Each step is designed to ensure a smooth, transparent process from the lottery's inception to the awarding the prizepool. ## Lottery functionality -- **Lottery Creation**: Admin can create a lottery specifying the draw time and the prize pool. The amount sent with the transaction must match the prize pool specified. -- **Buying Tickets**: Users can buy tickets by specifying the lottery they want to enter and their chosen numbers. Each ticket costs a fixed amount at 10ugnot, and users can only buy tickets before the draw time. -- **Drawing Winners**: Once the draw time has passed, the admin can draw the winning numbers. This process is handled by the `Draw` function, which selects pseudo-random numbers as winners. -- **Rendering Results**: The `Render` function generates a readable output for the homepage, showing available lotteries, their details, and results if available. +- **Lottery Creation**: Admin can create a lottery specifying the draw time and the prize pool. +The amount sent with the transaction must match the prize pool specified. +- **Buying Tickets**: Users can buy tickets by specifying the lottery they want to enter and their chosen numbers. +Each ticket costs a fixed amount at 10ugnot, and users can only buy tickets before the draw time. +- **Drawing Winners**: Once the draw time has passed, the admin can draw the winning numbers. +This process is handled by the `Draw` function, which selects pseudo-random numbers as winners. +- **Rendering Results**: The `Render` function generates a readable output for the homepage, +showing available lotteries, their details, and results if available. ## Package @@ -18,49 +25,52 @@ package gnolotto import ( "std" - "time" - "strings" "strconv" + "strings" + "time" ) type Ticket struct { - Numbers []int // Holds the selected numbers for the lottery ticket - Owner std.Address // Address of the ticket owner + Numbers []int // Holds the selected numbers for the lottery ticket + Owner std.Address // Address of the ticket owner } type Lottery struct { - Tickets []Ticket // All tickets in the lottery - WinningNumbers []int // Winning numbers after the draw - DrawTime time.Time // Time of the draw - PrizePool int64 // Total prize pool amount + Tickets []Ticket // All tickets in the lottery + WinningNumbers []int // Winning numbers after the draw + DrawTime time.Time // Time of the draw + PrizePool int64 // Total prize pool amount } // Intializes a new lottery instance with a specified draw time and prize pool func NewLottery(drawTime time.Time, prizePool int64) *Lottery { return &Lottery{ - DrawTime: drawTime, + DrawTime: drawTime, PrizePool: prizePool, - Tickets: make([]Ticket, 0), - } + Tickets: make([]Ticket, 0), + } } +const MaxLottoNumbers = 5 + // Adds a new ticket to the lottery func (l *Lottery) AddTicket(numbers []int, owner std.Address) { l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) } -// Conducts the draw by generating 5 random numbers between 1 and 15 +// Conducts the draw by generating 5 pseudo-random numbers between 1 and 15 inclusive func (l *Lottery) Draw() { var blockHeight int64 = std.GetHeight() + l.WinningNumbers = nil numbersMap := make(map[int]bool) // Add variability to the pseudo-random number generation var variabilityFactor int64 = 1 - for len(l.WinningNumbers) < 5 { + for len(l.WinningNumbers) < MaxLottoNumbers { simpleSeed := (blockHeight + variabilityFactor*251) % 233280 - number := int(simpleSeed % 15) + 1 // Ensure number is between 1 and 15 + number := int(simpleSeed%15) + 1 // Ensure number is between 1 and 15 if !numbersMap[number] { l.WinningNumbers = append(l.WinningNumbers, number) @@ -71,13 +81,13 @@ func (l *Lottery) Draw() { } } -// Itterate over all tickets to identify and return the addresses of the winners -func(l *Lottery) CheckWinners() []std.Address { +// Iterate over all tickets to identify and return the addresses of the winningOwners +func (l *Lottery) CheckWinners() []std.Address { var winningOwners []std.Address for _, ticket := range l.Tickets { matchCount := 0 - + for _, tNum := range ticket.Numbers { for _, wNum := range l.WinningNumbers { if tNum == wNum { @@ -91,6 +101,7 @@ func(l *Lottery) CheckWinners() []std.Address { winningOwners = append(winningOwners, ticket.Owner) } } + return winningOwners } @@ -101,21 +112,37 @@ func (l *Lottery) PayWinners(winningOwners []std.Address) { } else { // Calculate reward per winner var reward int64 = l.PrizePool / int64(len(winningOwners)) + banker := std.GetBanker(std.BankerTypeRealmSend) - + for _, owner := range winningOwners { send := std.Coins{{"ugnot", reward}} - banker.SendCoins(std.GetOrigPkgAddr(), owner, send) + banker.SendCoins(std.GetOrigPkgAddr(), owner, send) // Send reward to each winner } l.PrizePool = 0 // Reset the prize pool after distribution } } + +func StringToIntSlice(numbersStr string) ([]int, error) { + numbersSlice := strings.Split(numbersStr, ",") + numbers := make([]int, len(numbersSlice)) + for i, numStr := range numbersSlice { + num, err := strconv.Atoi(strings.TrimSpace(numStr)) + if err != nil { + return nil, err + } + numbers[i] = num + } + return numbers, nil +} ``` A few remarks : -- In the blockchain world, it's difficult to generate random numbers without using an oracle. Since Gno.land doesn't yet offer an oracle, the `Draw()` function generates random numbers based on the height of the block. This solution is not viable in real-life conditions, but is sufficient for this tutorial. +- In the blockchain world, it's difficult to generate random numbers without using an oracle. +Since Gno.land doesn't yet offer an oracle, the `Draw()` function generates random numbers based on the height of the block. +This solution is not viable in real-life conditions, but is sufficient for this tutorial. - In the `PayWinners()` function, we use the `std` package to manipulate the funds available in the realm. ## Realm @@ -125,143 +152,124 @@ package gnolotto_factory import ( "bytes" - "time" - "strconv" - "strings" "std" + "time" "gno.land/p/demo/avl" - "gno.land/p/demo/ufmt" "gno.land/p/demo/gnolotto" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/seqid" ) var lotteries *avl.Tree +var lotteriesID seqid.ID // Replace this address with your address var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" +const TICKET_PRICE = 10 + // Initializes the lottery AVL tree func init() { lotteries = avl.NewTree() } // Creates a new lottery, only callable by admin. -func CreateLottery(drawTime int64, prizePool int64) (int, string) { +func CreateLottery(drawTime int64, prizePool int64) (string, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - send := std.Coins{{"ugnot", int64(amount)}} if prizePool != amount { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Prize pool must match the transaction value" + panic("Prize pool must match the transaction value") } if drawTime < time.Now().Unix() { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Invalid draw time" + panic("Invalid draw time") } - + if std.GetOrigCaller() != admin { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Only the admin can create a lottery" + panic("Only the admin can create a lottery") } - lotteryID := lotteries.Size() + lotteryID := lotteriesID.Next() lottery := gnolotto.NewLottery(time.Unix(drawTime, 0), prizePool) - lotteries.Set(ufmt.Sprintf("%d", lotteryID), lottery) - return lotteryID, "Lottery created successfully" + lotteries.Set(lotteryID.Binary(), lottery) + return lotteryID.String(), "Lottery created successfully" } // Buy ticket for a specific lottery. -func BuyTicket(lotteryID int, numbersStr string) (int, string) { +func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - send := std.Coins{{"ugnot", int64(amount)}} - id := ufmt.Sprintf("%d", lotteryID) - lotteryRaw, exists := lotteries.Get(id) + lotteryID, _ := seqid.FromString(lotteryIDStr) + id := lotteryID.Binary() + lotteryRaw, exists := lotteries.Get(id) if !exists { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Lottery not found" + panic("Lottery not found") } // Convert string to slice of integers. - numbersSlice := strings.Split(numbersStr, ",") - numbers := make([]int, len(numbersSlice)) - - for i, numStr := range numbersSlice { - num, err := strconv.Atoi(numStr) - if err != nil { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - panic("Invalid number: " + err.Error()) - } - numbers[i] = num + numbers, err := gnolotto.StringToIntSlice(numbersStr) + if err != nil { + panic("Invalid number: " + err.Error()) // Gestion de l'erreur si la conversion échoue. } //Verify if the amount sent is equal to the ticket price. - if amount != 10 { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Ticket price must be 10 UGNOT" + if amount != TICKET_PRICE { + panic("Ticket price must be 10 UGNOT") } // Verify if the numbers are unique. uniqueNumbers := make(map[int]bool) - for _, num := range numbers { if uniqueNumbers[num] { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Numbers must be unique" + panic("Numbers must be unique") } - uniqueNumbers[num] = true } l, _ := lotteryRaw.(*gnolotto.Lottery) if time.Now().Unix() > l.DrawTime.Unix() { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "This lottery has already ended" + panic("This lottery has already ended") } - if len(numbers) > 5 || len(numbers) < 5 { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "You must select exactly 5 numbers" + if len(numbers) != gnolotto.MaxLottoNumbers { + panic("You must select exactly 5 numbers") } for _, num := range numbers { if num > 15 || num < 1 { - banker.SendCoins(std.GetOrigPkgAddr(), std.GetOrigCaller(), send) - return -1, "Invalid number, select number range from 1 to 15" + panic("Invalid number, select number range from 1 to 15") } } caller := std.GetOrigCaller() l.AddTicket(numbers, caller) + return 1, "Ticket purchased successfully" } // Draws the winning numbers for a specific lottery, only callable by admin the draw time has passed. -func Draw(lotteryID int) (int, string) { - id := ufmt.Sprintf("%d", lotteryID) - +func Draw(lotteryIDStr string) (int, string) { + lotteryID, _ := seqid.FromString(lotteryIDStr) + id := lotteryID.Binary() + if std.GetOrigCaller() != admin { - return -1, "Only the admin can draw the winning numbers" + panic("Only the admin can draw the winning numbers") } - lotteryRaw, exists := lotteries.Get(id) - if !exists { - return -1, "Lottery not found" + panic("Lottery not found") } l, _ := lotteryRaw.(*gnolotto.Lottery) if time.Now().Unix() < l.DrawTime.Unix() { - return -1, "Draw time has not passed yet" + panic("Draw time has not passed yet") } l.Draw() @@ -272,9 +280,12 @@ func Draw(lotteryID int) (int, string) { A few remarks : - The `Draw()` function generates 5 winning numbers. A ticket purchase must be accompanied by a selection of 5 numbers in order to participate. -- In the `BuyTicket()` function, we take as arguments the winning numbers in string type, as it's not possible to pass a slice as an argument in `gnokey`. We therefore retrieve the winning numbers in string type, split them and convert them to slice to add them to our `Ticket` struct in our package -- When we make a function call using `gnokey` and add an amount in `-send`, this amount will be sent to the realm even if a condition does not allow the action in our code. This is why, in the `CreateLottery()` and `BuyTicket()` functions, we use the `std` package to refund the wallet that sent the funds in the event that a condition is not met. -- For this lottery, we have chosen to set the price of a ticket at 10ugnot. If the user buys a ticket and sends + or - 10ugnot, he will be refunded the amount sent. At the end of the lottery creation process, we check that the amount sent to the realm is equal to the amount defined in the prize pool. Sending the amount to the realm when the lottery is created allows us to distribute the winnings to the winner(s) automatically after the draw. +- In the `BuyTicket()` function, we take as arguments the winning numbers in string type, as it's not possible to pass a slice as an argument in `gnokey`. We therefore retrieve the winning numbers in string type, +split them and convert them to slice to add them to our `Ticket` struct in our package +- For this lottery, we have chosen to set the price of a ticket at 10ugnot. +If the user buys a ticket and sends + or - 10ugnot, he will be refunded the amount sent. +At the end of the lottery creation process, we check that the amount sent to the realm is equal to the amount defined in the prize pool. +Sending the amount to the realm when the lottery is created allows us to distribute the winnings to the winner(s) automatically after the draw. ## Render @@ -283,7 +294,7 @@ And finally, our Render() function, which displays our lottery. ```go func Render(path string) string { if path == "" { - return renderHomepage() + return renderHomepage() } return "unknown page" @@ -291,6 +302,7 @@ func Render(path string) string { func renderHomepage() string { var b bytes.Buffer + b.WriteString("# Welcome to GnoLotto\n\n") if lotteries.Size() == 0 { @@ -300,28 +312,31 @@ func renderHomepage() string { lotteries.Iterate("", "", func(key string, value interface{}) bool { l := value.(*gnolotto.Lottery) - + + lotteryID, _ := seqid.FromBinary(key) + readableID := lotteryID.String() + b.WriteString( ufmt.Sprintf( "## Lottery ID: *%s*\n", - key, + readableID, ), ) - + b.WriteString( ufmt.Sprintf( "Draw Time: *%s*\n", l.DrawTime.Format("Mon Jan _2 15:04:05 2006"), ), ) - + b.WriteString( ufmt.Sprintf( "Prize Pool: *%d* UGNOT\n\n", l.PrizePool, ), ) - + if time.Now().Unix() > l.DrawTime.Unix() { // If the lottery has ended, display the winners. var numbersStr string @@ -330,12 +345,13 @@ func renderHomepage() string { numbersStr += ", " } numbersStr += ufmt.Sprintf("%d", number) - } - + } b.WriteString(ufmt.Sprintf("- Winning numbers [%s]\n\n", numbersStr)) + winners := l.CheckWinners() + l.PayWinners(winners) - + if len(winners) > 0 { b.WriteString("Winners:\n\n") for _, winner := range winners { @@ -355,23 +371,23 @@ func renderHomepage() string { if i > 0 { numbersStr += ", " } - numbersStr += ufmt.Sprintf("%d", number) } - b.WriteString(ufmt.Sprintf("- *%s* with numbers [%s]\n", ticket.Owner.String(), numbersStr)) } } else { b.WriteString("*No participants yet.*\n") } } + b.WriteString("\n") return false }) + banker := std.GetBanker(std.BankerTypeReadonly) contractAddress := std.GetOrigPkgAddr() coins := banker.GetCoins(contractAddress) - + b.WriteString("## Contract Balance:\n") b.WriteString(coins.String() + "\n\n") From 58e0675845a4c41d48fb29c1fae5adc89aeec4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:32:02 +0200 Subject: [PATCH 23/28] delete import not used in realm --- .../gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index bfe4f591724..5960c4b3caa 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -3,8 +3,6 @@ package gnolotto_factory import ( "bytes" "std" - "strconv" - "strings" "time" "gno.land/p/demo/avl" @@ -54,8 +52,6 @@ func CreateLottery(drawTime int64, prizePool int64) (string, string) { func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - send := std.Coins{{"ugnot", int64(amount)}} lotteryID, _ := seqid.FromString(lotteryIDStr) id := lotteryID.Binary() From 7fbf161d33a4f6f108645888713e8cb391c29cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:37:40 +0200 Subject: [PATCH 24/28] make tidy --- examples/gno.land/r/demo/gnolotto_factory/gno.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gno.mod b/examples/gno.land/r/demo/gnolotto_factory/gno.mod index 96e93b3b013..eee263106b6 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gno.mod +++ b/examples/gno.land/r/demo/gnolotto_factory/gno.mod @@ -1,8 +1,8 @@ module gno.land/r/demo/gnolotto_factory require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/gnolotto v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnolotto v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) From 3c2409b0d52eacbfc627229e1468b691152e7e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:31:50 +0200 Subject: [PATCH 25/28] modification CheckWinners() --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 04abf125931..55f82a9c046 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -63,18 +63,16 @@ func (l *Lottery) CheckWinners() []std.Address { var winningOwners []std.Address for _, ticket := range l.Tickets { - matchCount := 0 - - for _, tNum := range ticket.Numbers { - for _, wNum := range l.WinningNumbers { - if tNum == wNum { - matchCount++ - break - } + isWinner := true + + for i := 0; i < MaxLottoNumbers; i++ { + if ticket.Numbers[i] != l.WinningNumbers[i] { + isWinner = false + break } } - if matchCount == len(l.WinningNumbers) { + if isWinner { winningOwners = append(winningOwners, ticket.Owner) } } From a4a5f388aa40c6ca498b16935610f5376b635646 Mon Sep 17 00:00:00 2001 From: Kazai777 Date: Fri, 30 Aug 2024 08:10:18 +0200 Subject: [PATCH 26/28] add test file for realm and fix code --- .../gno.land/r/demo/gnolotto_factory/gno.mod | 2 + .../gnolotto_factory/gnolotto_factory.gno | 57 ++++++++------- .../gnolotto_factory_test.gno | 70 +++++++++++++++++++ 3 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory_test.gno diff --git a/examples/gno.land/r/demo/gnolotto_factory/gno.mod b/examples/gno.land/r/demo/gnolotto_factory/gno.mod index eee263106b6..119df58469d 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gno.mod +++ b/examples/gno.land/r/demo/gnolotto_factory/gno.mod @@ -4,5 +4,7 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/gnolotto v0.0.0-latest gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno index 5960c4b3caa..691702bfa72 100644 --- a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory.gno @@ -7,12 +7,14 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/gnolotto" - "gno.land/p/demo/ufmt" "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" ) -var lotteries *avl.Tree -var lotteriesID seqid.ID +var ( + lotteries *avl.Tree + lotteriesID seqid.ID +) // Replace this address with your address var admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" @@ -25,7 +27,7 @@ func init() { } // Creates a new lottery, only callable by admin. -func CreateLottery(drawTime int64, prizePool int64) (string, string) { +func CreateLottery(drawTime int64, prizePool int64) string { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") @@ -37,19 +39,21 @@ func CreateLottery(drawTime int64, prizePool int64) (string, string) { panic("Invalid draw time") } - if std.GetOrigCaller() != admin { + if std.PrevRealm().Addr() != admin { panic("Only the admin can create a lottery") } lotteryID := lotteriesID.Next() + lottery := gnolotto.NewLottery(time.Unix(drawTime, 0), prizePool) lotteries.Set(lotteryID.Binary(), lottery) - return lotteryID.String(), "Lottery created successfully" + + return ufmt.Sprintf("Lottery with ID: %s created successfully", lotteryID.String()) } // Buy ticket for a specific lottery. -func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { +func BuyTicket(lotteryIDStr string, numbersStr string) string { sentCoins := std.GetOrigSend() amount := sentCoins.AmountOf("ugnot") @@ -64,21 +68,13 @@ func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { // Convert string to slice of integers. numbers, err := gnolotto.StringToIntSlice(numbersStr) if err != nil { - panic("Invalid number: " + err.Error()) // Gestion de l'erreur si la conversion échoue. + panic("Invalid number: " + err.Error()) // Error handling if conversion fails. } - //Verify if the amount sent is equal to the ticket price. + str := ufmt.Sprintf("Ticket costs %d ugnot", TICKET_PRICE) + // Verify if the amount sent is equal to the ticket price. if amount != TICKET_PRICE { - panic("Ticket price must be 10 UGNOT") - } - - // Verify if the numbers are unique. - uniqueNumbers := make(map[int]bool) - for _, num := range numbers { - if uniqueNumbers[num] { - panic("Numbers must be unique") - } - uniqueNumbers[num] = true + panic(str) } l, _ := lotteryRaw.(*gnolotto.Lottery) @@ -97,21 +93,31 @@ func BuyTicket(lotteryIDStr string, numbersStr string) (int, string) { } } - caller := std.GetOrigCaller() - l.AddTicket(numbers, caller) + // Verify if the numbers are unique. + uniqueNumbers := make(map[int]bool) + for _, num := range numbers { + if uniqueNumbers[num] { + panic("Ticket numbers must be unique") + } + uniqueNumbers[num] = true + } + + l.AddTicket(numbers, std.PrevRealm().Addr()) - return 1, "Ticket purchased successfully" + return "Ticket purchased successfully" } // Draws the winning numbers for a specific lottery, only callable by admin the draw time has passed. -func Draw(lotteryIDStr string) (int, string) { +func Draw(lotteryIDStr string) string { lotteryID, _ := seqid.FromString(lotteryIDStr) id := lotteryID.Binary() - if std.GetOrigCaller() != admin { + if std.PrevRealm().Addr() != admin { panic("Only the admin can draw the winning numbers") } + lotteryRaw, exists := lotteries.Get(id) + if !exists { panic("Lottery not found") } @@ -123,7 +129,8 @@ func Draw(lotteryIDStr string) (int, string) { } l.Draw() - return 1, "Winning numbers drawn successfully" + + return "Winning numbers drawn successfully" } func Render(path string) string { diff --git a/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory_test.gno b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory_test.gno new file mode 100644 index 00000000000..9e55a8e47e8 --- /dev/null +++ b/examples/gno.land/r/demo/gnolotto_factory/gnolotto_factory_test.gno @@ -0,0 +1,70 @@ +package gnolotto_factory + +import ( + "std" + "strings" + "testing" + "time" + + "gno.land/p/demo/seqid" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +var nonAdmin = testutils.TestAddress("nonAdmin") + +func createLottery(t *testing.T, admin std.Address, drawTime int64, prizePool int64) seqid.ID { + std.TestSetOrigCaller(admin) + std.TestSetOrigSend(std.Coins{{Denom: "ugnot", Amount: prizePool}}, nil) + + result := CreateLottery(drawTime, prizePool) + + lotteryIDStr := strings.Split(result, " ")[3] + + lotteryID, err := seqid.FromString(lotteryIDStr) + if err != nil { + t.Fatalf("Failed to parse lottery ID: %s", lotteryIDStr) + } + + return lotteryID +} + +func TestCreateLottery(t *testing.T) { + drawTime := time.Now().Unix() + 60 + prizePool := int64(1000) + + lotteryID := createLottery(t, admin, drawTime, prizePool) + uassert.Equal(t, "0000001", lotteryID.String()) + + std.TestSetOrigCaller(nonAdmin) + uassert.PanicsWithMessage(t, "Only the admin can create a lottery", func() { + CreateLottery(drawTime, prizePool) + }) +} + +func TestBuyTicket(t *testing.T) { + drawTime := time.Now().Unix() + 60 + prizePool := int64(1000) + + lotteryID := createLottery(t, admin, drawTime, prizePool) + numbers := "1,2,3,4,5" + + std.TestSetOrigSend(std.Coins{{Denom: "ugnot", Amount: 10}}, nil) + + result := BuyTicket(lotteryID.String(), numbers) + uassert.Equal(t, "Ticket purchased successfully", result) + + std.TestSetOrigSend(std.Coins{{Denom: "ugnot", Amount: 5}}, nil) + uassert.PanicsWithMessage(t, "Ticket costs 10 ugnot", func() { + BuyTicket(lotteryID.String(), numbers) + }) +} + +func TestDraw(t *testing.T) { + drawTime := time.Now().Unix() + prizePool := int64(1000) + lotteryID := createLottery(t, admin, drawTime, prizePool) + + result := Draw(lotteryID.String()) + uassert.Equal(t, "Winning numbers drawn successfully", result) +} From 1fb9689f5cf7df940e1115e9e7d845e72b65309d Mon Sep 17 00:00:00 2001 From: Kazai777 Date: Fri, 30 Aug 2024 08:11:07 +0200 Subject: [PATCH 27/28] add testfile for package and fix code --- examples/gno.land/p/demo/gnolotto/gno.mod | 6 + .../gno.land/p/demo/gnolotto/gnolotto.gno | 56 +++++---- .../p/demo/gnolotto/gnolotto_test.gno | 113 ++++++++++++++++++ 3 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 examples/gno.land/p/demo/gnolotto/gnolotto_test.gno diff --git a/examples/gno.land/p/demo/gnolotto/gno.mod b/examples/gno.land/p/demo/gnolotto/gno.mod index f21596b6079..ca04a12422b 100644 --- a/examples/gno.land/p/demo/gnolotto/gno.mod +++ b/examples/gno.land/p/demo/gnolotto/gno.mod @@ -1 +1,7 @@ module gno.land/p/demo/gnolotto + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 55f82a9c046..309c4f7b9cb 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -5,6 +5,8 @@ import ( "strconv" "strings" "time" + + "gno.land/p/demo/ufmt" ) type Ticket struct { @@ -58,45 +60,37 @@ func (l *Lottery) Draw() { } } -// Iterate over all tickets to identify and return the addresses of the winningOwners func (l *Lottery) CheckWinners() []std.Address { - var winningOwners []std.Address + var winners []std.Address for _, ticket := range l.Tickets { - isWinner := true - - for i := 0; i < MaxLottoNumbers; i++ { - if ticket.Numbers[i] != l.WinningNumbers[i] { - isWinner = false - break - } - } - - if isWinner { - winningOwners = append(winningOwners, ticket.Owner) + if AreNumberMatching(ticket.Numbers, l.WinningNumbers) { + winners = append(winners, ticket.Owner) } } - return winningOwners + return winners } // Distributes the prize pool equally among the winning ticket owners -func (l *Lottery) PayWinners(winningOwners []std.Address) { - if len(winningOwners) == 0 { +func (l *Lottery) PayWinners(winners []std.Address) { + if len(winners) == 0 { return - } else { - // Calculate reward per winner - var reward int64 = l.PrizePool / int64(len(winningOwners)) + } - banker := std.GetBanker(std.BankerTypeRealmSend) + var reward int64 = l.PrizePool / int64(len(winners)) + if reward <= 0 || reward > l.PrizePool { + panic("Invalid reward calculation") + } - for _, owner := range winningOwners { - send := std.Coins{{"ugnot", reward}} - banker.SendCoins(std.GetOrigPkgAddr(), owner, send) // Send reward to each winner - } + banker := std.GetBanker(std.BankerTypeRealmSend) - l.PrizePool = 0 // Reset the prize pool after distribution + for _, owner := range winners { + send := std.Coins{{"ugnot", reward}} + banker.SendCoins(std.GetOrigPkgAddr(), owner, send) } + + l.PrizePool = 0 } func StringToIntSlice(numbersStr string) ([]int, error) { @@ -110,4 +104,14 @@ func StringToIntSlice(numbersStr string) ([]int, error) { numbers[i] = num } return numbers, nil -} \ No newline at end of file +} + +func AreNumberMatching(ticketNumbers, winningNumbers []int) bool { + for i := 0; i < MaxLottoNumbers; i++ { + if ticketNumbers[i] != winningNumbers[i] { + return false + } + } + + return true +} diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto_test.gno b/examples/gno.land/p/demo/gnolotto/gnolotto_test.gno new file mode 100644 index 00000000000..37784caaea8 --- /dev/null +++ b/examples/gno.land/p/demo/gnolotto/gnolotto_test.gno @@ -0,0 +1,113 @@ +package gnolotto + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" +) + +var ( + user1 = testutils.TestAddress("user1") + user2 = testutils.TestAddress("user2") +) + +func createMockLottery() *Lottery { + drawTime := time.Now().Add(24 * time.Hour) + prizePool := int64(1000) + return NewLottery(drawTime, prizePool) +} + +func TestNewLottery(t *testing.T) { + drawTime := time.Now().Add(24 * time.Hour) + prizePool := int64(1000) + lottery := NewLottery(drawTime, prizePool) + + uassert.True(t, lottery.DrawTime.Equal(drawTime), "Draw time should be set correctly") + uassert.Equal(t, lottery.PrizePool, prizePool, "Prize pool should be set correctly") + uassert.Equal(t, len(lottery.Tickets), 0, "Lottery should start with no tickets") +} + +func TestAddTicket(t *testing.T) { + lottery := createMockLottery() + numbers := []int{1, 2, 3, 4, 5} + lottery.AddTicket(numbers, user1) + + uassert.Equal(t, len(lottery.Tickets), 1, "Expected 1 ticket") + uassert.True(t, AreNumberMatching(lottery.Tickets[0].Numbers, numbers), "Ticket numbers should match") + uassert.Equal(t, lottery.Tickets[0].Owner, user1, "Ticket owner should match") +} + +func TestDraw(t *testing.T) { + lottery := createMockLottery() + lottery.Draw() + + uassert.Equal(t, len(lottery.WinningNumbers), MaxLottoNumbers, "Expected 5 winning numbers") + + uniqueNumbers := make(map[int]bool) + for _, number := range lottery.WinningNumbers { + uassert.True(t, number >= 1 && number <= 15, ufmt.Sprintf("Winning number out of range: %d", number)) + uassert.False(t, uniqueNumbers[number], ufmt.Sprintf("Duplicate winning number found: %d", number)) + uniqueNumbers[number] = true + } +} + +func TestCheckWinners(t *testing.T) { + lottery := createMockLottery() + + lottery.AddTicket([]int{1, 2, 3, 4, 5}, user1) + lottery.AddTicket([]int{6, 7, 8, 9, 10}, user2) + lottery.WinningNumbers = []int{1, 2, 3, 4, 5} + + winners := lottery.CheckWinners() + + uassert.Equal(t, len(winners), 1, "Expected 1 winner") + uassert.Equal(t, winners[0], user1, "Winner should be user1") +} + +func TestStringToIntSlice(t *testing.T) { + input := "1, 2, 3, 4, 5" + expected := []int{1, 2, 3, 4, 5} + result, err := StringToIntSlice(input) + + uassert.NoError(t, err, "Unexpected error") + uassert.Equal(t, len(result), len(expected), "Slice length should match") + + for i := range expected { + uassert.Equal(t, result[i], expected[i], ufmt.Sprintf("Expected %d at index %d, got %d", expected[i], i, result[i])) + } +} + +func TestAreNumberMatching(t *testing.T) { + ticketNumbers := []int{1, 2, 3, 4, 5} + winningNumbers := []int{1, 2, 3, 4, 5} + uassert.True(t, AreNumberMatching(ticketNumbers, winningNumbers), "Expected numbers to match") + + winningNumbers = []int{1, 2, 3, 4, 6} + uassert.False(t, AreNumberMatching(ticketNumbers, winningNumbers), "Expected numbers to not match") +} + +func TestPayWinners(t *testing.T) { + lottery := createMockLottery() + realmAddress := std.GetOrigPkgAddr() + std.TestSetOrigCaller(realmAddress) + + winners := []std.Address{user1, user2} + initialPrizePool := lottery.PrizePool + expectedReward := initialPrizePool / int64(len(winners)) + + lottery.PayWinners(winners) + + uassert.Equal(t, lottery.PrizePool, int64(0), "The prize pool should be reset to 0 after distribution") + + coinsUser1 := std.GetBanker(std.BankerTypeRealmSend).GetCoins(user1) + receivedRewardUser1 := coinsUser1.AmountOf("ugnot") + uassert.Equal(t, receivedRewardUser1, expectedReward, ufmt.Sprintf("Winner %s should have received %d but received %d", user1.String(), expectedReward, receivedRewardUser1)) + + coinsUser2 := std.GetBanker(std.BankerTypeRealmSend).GetCoins(user2) + receivedRewardUser2 := coinsUser2.AmountOf("ugnot") + uassert.Equal(t, receivedRewardUser2, expectedReward, ufmt.Sprintf("Winner %s should have received %d but received %d", user2.String(), expectedReward, receivedRewardUser2)) +} From 4805e229410f89e4e9af8a6d830ac9e222a11217 Mon Sep 17 00:00:00 2001 From: Kazai777 Date: Fri, 30 Aug 2024 08:30:09 +0200 Subject: [PATCH 28/28] fix commit --- examples/gno.land/p/demo/gnolotto/gnolotto.gno | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/gno.land/p/demo/gnolotto/gnolotto.gno b/examples/gno.land/p/demo/gnolotto/gnolotto.gno index 309c4f7b9cb..007159f8e72 100644 --- a/examples/gno.land/p/demo/gnolotto/gnolotto.gno +++ b/examples/gno.land/p/demo/gnolotto/gnolotto.gno @@ -5,8 +5,6 @@ import ( "strconv" "strings" "time" - - "gno.land/p/demo/ufmt" ) type Ticket struct { @@ -79,9 +77,6 @@ func (l *Lottery) PayWinners(winners []std.Address) { } var reward int64 = l.PrizePool / int64(len(winners)) - if reward <= 0 || reward > l.PrizePool { - panic("Invalid reward calculation") - } banker := std.GetBanker(std.BankerTypeRealmSend) @@ -103,6 +98,7 @@ func StringToIntSlice(numbersStr string) ([]int, error) { } numbers[i] = num } + return numbers, nil }