1// Package lottery is an idiomatic gno.land port of the classic Solidity2// Lottery contract. Players Enter the current round (one entry per address per3// round). Once a round has at least two entrants, anyone may DrawWinner: a4// winner is picked deterministically from the block height (ChainHeight() %5// len(entrants), over the deterministically-sorted entrant set), recorded in6// the winners history, and a fresh round is opened.7//8// There is no real randomness on-chain, so "random" selection is derived from9// the block height — the same source Solidity ports use via block.number.10package lottery1112import (13 "strconv"14 "strings"1516 "chain"17 "chain/runtime"18 "chain/runtime/unsafe"1920 "gno.land/p/nt/avl/v0"21)2223// round is the current (open) round number, 1-based.24var round int = 12526// entrants holds the current round's players: key = address string, value =27// the address. Iteration is deterministic (sorted by key), which makes winner28// selection reproducible.29var entrants avl.Tree3031// winners records past rounds: key = zero-padded round number (for ordered32// iteration), value = *winnerRecord.33var winners avl.Tree3435type winnerRecord struct {36 Round int37 Winner address38 NumEntrants int39 Height int6440}4142// winnerKey zero-pads the round number so avl iteration yields chronological43// order for up to 10^9 rounds.44func winnerKey(r int) string {45 s := strconv.Itoa(r)46 for len(s) < 9 {47 s = "0" + s48 }49 return s50}5152// pickIndex derives the winning entrant index from the block height. Panics if53// there are no entrants. Pure helper (deterministic, no state) — safe to test.54func pickIndex(height int64, n int) int {55 if n <= 0 {56 panic("lottery: no entrants to draw from")57 }58 i := height % int64(n)59 if i < 0 {60 i += int64(n)61 }62 return int(i)63}6465// Enter adds the caller to the current round's entrants. Each address may only66// enter a given round once; a duplicate entry panics.67func Enter(cur realm) {68 player := unsafe.PreviousRealm().Address()69 key := player.String()70 if entrants.Has(key) {71 panic("lottery: already entered this round")72 }73 entrants.Set(key, player)7475 chain.Emit("Enter",76 "round", strconv.Itoa(round),77 "player", key,78 "entrants", strconv.Itoa(entrants.Size()),79 )80}8182// DrawWinner closes the current round, provided it has at least two entrants,83// picks the winner deterministically from the block height, records it, and84// opens a new empty round.85func DrawWinner(cur realm) address {86 n := entrants.Size()87 if n < 2 {88 panic("lottery: need at least 2 entrants to draw")89 }9091 height := runtime.ChainHeight()92 idx := pickIndex(height, n)9394 var winner address95 i := 096 entrants.Iterate("", "", func(_ string, value interface{}) bool {97 if i == idx {98 winner = value.(address)99 return true // stop100 }101 i++102 return false103 })104105 rec := &winnerRecord{106 Round: round,107 Winner: winner,108 NumEntrants: n,109 Height: height,110 }111 winners.Set(winnerKey(round), rec)112113 chain.Emit("DrawWinner",114 "round", strconv.Itoa(round),115 "winner", winner.String(),116 "entrants", strconv.Itoa(n),117 "height", strconv.FormatInt(height, 10),118 )119120 // Open a fresh round.121 round++122 entrants = avl.Tree{}123124 return winner125}126127// CurrentRound returns the currently open round number.128func CurrentRound() int {129 return round130}131132// NumEntrants returns how many players have entered the current round.133func NumEntrants() int {134 return entrants.Size()135}136137// HasEntered reports whether addr is in the current round.138func HasEntered(addr address) bool {139 return entrants.Has(addr.String())140}141142// Render shows the current round, its entrants, and the winners history.143func Render(path string) string {144 var b strings.Builder145 b.WriteString("# Lottery\n\n")146 b.WriteString("Enter the open round, then anyone can draw once it has ")147 b.WriteString("at least two players. The winner is picked from the block ")148 b.WriteString("height and a new round opens automatically.\n\n")149150 b.WriteString("## Round " + strconv.Itoa(round) + " (open)\n\n")151 if entrants.Size() == 0 {152 b.WriteString("_No entrants yet. Call `Enter` to join._\n\n")153 } else {154 b.WriteString(strconv.Itoa(entrants.Size()) + " entrant(s):\n\n")155 entrants.Iterate("", "", func(key string, _ interface{}) bool {156 b.WriteString("- `" + key + "`\n")157 return false158 })159 b.WriteString("\n")160 if entrants.Size() < 2 {161 b.WriteString("_Need at least 2 entrants before a draw._\n\n")162 } else {163 b.WriteString("_Ready to draw: call `DrawWinner`._\n\n")164 }165 }166167 b.WriteString("## Past winners\n\n")168 if winners.Size() == 0 {169 b.WriteString("_No rounds drawn yet._\n")170 } else {171 b.WriteString("| Round | Winner | Entrants | Block Height |\n")172 b.WriteString("|---|---|---|---|\n")173 winners.Iterate("", "", func(_ string, value interface{}) bool {174 r := value.(*winnerRecord)175 b.WriteString("| " + strconv.Itoa(r.Round) +176 " | `" + r.Winner.String() + "`" +177 " | " + strconv.Itoa(r.NumEntrants) +178 " | " + strconv.FormatInt(r.Height, 10) + " |\n")179 return false180 })181 }182183 return b.String()184}185CurrentRound() int
DrawWinner(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) string
Enter(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
HasEntered(addr string) bool
NumEntrants() int
Render(path string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.