PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd / gnofly / arena
Files
15 (gnomod.toml)
Exported functions
21
Module
gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/v0
gno
0.9

Files (15)

  • gnomod.tomltoml
  • arena.gnogno
  • claims.gnogno
  • doc.gnogno
  • earnings.gnogno
  • errors.gnogno
  • escrow.gnogno
  • json.gnogno
  • lobby.gnogno
  • render.gnogno
  • results.gnogno
  • roles.gnogno
  • rounds.gnogno
  • spec.gnogno
  • validate.gnogno
  • rounds.gnogno
    1package arena23import (4	"chain"5	"strings"67	"gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0"8

    Functions

    • ArenaJSON(id string) string

    • ArenasJSON() string

    • CancelRound(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}, id string)

    • Claim(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}) int64

    • Claimable(addr string) int64

    • ClaimsJSON(addr string) string

    • EarningsJSON() 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}, spec string, ticket string) string

    • Escrowed() int64

    • ExpireLobby(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}, id string)

    • Leave(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}, id string)

    • PlayerJSON(addr string) string

    • RefundDelay() int64

    • RefundRound(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}, id string)

    • Render(path string) string

    • ResultJSON(id string) string

    • ResultsJSON() string

    • SetRefundDelay(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}, seconds int64)

    • SettleRound(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}, id string, deaths string)

    • StartRound(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}, id string) int64

    • Version() int64

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    Rendered

    RenderedRawgnoweb ↗
    )
    9
    10// round is the started game of an arena. Its players are fixed at start, and
    11// each of them has one stake in it.
    12type round struct {
    13 startedAt int64 // block time, Unix seconds
    14 players []address // in the order they entered
    15}
    16
    17// A game ends in exactly one of three ways: SettleRound, CancelRound or
    18// RefundRound. Each of them deletes the arena before anything else, so a
    19// second one finds no arena and aborts.
    20
    21// StartRound starts the game of an arena with every player of its lobby.
    22// Referee only. The lobby must hold at least minSeats players; it holds at
    23// most maxSeats, Enter sees to that. No coin moves: the stakes that were
    24// owed to the lobby are now owed to the round. It returns how many play.
    25func StartRound(cur realm, id string) int64 {
    26 referee := assertReferee(0, cur)
    27 a := mustArena(id)
    28 if a.round != nil {
    29 panic(errRoundRunning)
    30 }
    31 if int64(len(a.lobby)) < a.minSeats {
    32 panic(errTooFewPlayers)
    33 }
    34 a.round = &round{startedAt: now(), players: a.lobby}
    35 a.lobby = nil
    36 a.tickets = nil
    37
    38 chain.Emit("RoundStarted",
    39 "arena", id,
    40 "players", joinAddrs(a.round.players),
    41 "startedAt", itoa(a.round.startedAt),
    42 "referee", referee.String(),
    43 )
    44 return int64(len(a.round.players))
    45}
    46
    47// SettleRound closes the game of an arena: it applies the rule of its mode
    48// to what happened, pays the players, and pays the fee to the treasury.
    49// Referee only.
    50//
    51// deaths is "victim:killer" entries separated by ";", in the order they
    52// happened, "victim:" when nobody shot the victim down, and the empty
    53// string when nobody died. A player dies at most as often as the arena has
    54// lives.
    55//
    56// The referee is trusted for these facts and for nothing else. The rule
    57// refuses a victim or a killer who is not a player of the round, and decides
    58// the amounts. The realm then checks them (see settle) before it pays.
    59func SettleRound(cur realm, id string, deaths string) {
    60 referee := assertReferee(0, cur)
    61 a := runningArena(id)
    62 list, err := payout.ParseDeaths(deaths)
    63 if err != nil {
    64 panic(err)
    65 }
    66 rule := payout.Lookup(a.mode)
    67 if rule == nil {
    68 panic(errInvalidMode) // cannot happen: parseSpec checked the mode
    69 }
    70 // The rule gets a copy of the players: the list the realm pays from is
    71 // not one a rule can touch.
    72 players := make([]address, len(a.round.players))
    73 copy(players, a.round.players)
    74 g := payout.Game{Stake: a.stake, Lives: a.lives, KillTarget: a.killTarget, FeeBps: feeBps}
    75 res, err := rule.Settle(g, players, list)
    76 if err != nil {
    77 panic(err)
    78 }
    79 settle(0, cur, id, a, res, referee)
    80}
    81
    82// settle closes the game of an arena with the result of its rule.
    83//
    84// It trusts nothing about the result. Before a counter or a coin moves,
    85// checkResult verifies that only players of the round are paid and that the
    86// payouts and the fee add up to the pot, to the ugnot.
    87func settle(_ int, rlm realm, id string, a *arena, res payout.Result, referee address) {
    88 r := a.round
    89 pot := a.stake * int64(len(r.players))
    90 checkResult(pot, r.players, res)
    91
    92 // The fee goes to the treasury of the game realm as it is now. If that
    93 // is not an address the realm can pay, the fee goes back to the players
    94 // instead: shared by the order of the round, the remainder to the first.
    95 fee := res.Fee
    96 payouts := res.Payouts
    97 treasury := roles.Treasury()
    98 if fee > 0 && (!validAddress(treasury) || treasury == rlm.Address()) {
    99 payouts = withFeeShared(r.players, payouts, fee)
    100 fee = 0
    101 }
    102
    103 // State first: the arena is gone and its pot is off the books before the
    104 // first coin leaves.
    105 arenas.Remove(id)
    106 release(pot)
    107 record(id, result{
    108 mode: a.mode,
    109 stake: a.stake,
    110 outcome: outcomeSettled,
    111 players: int64(len(r.players)),
    112 pot: pot,
    113 fee: fee,
    114 closedAt: now(),
    115 payouts: payouts,
    116 })
    117 creditRound(a.stake, r.players, payouts)
    118
    119 // The winnings stay in the realm until each player claims them: a
    120 // settlement sends no coin to a player, so it cannot fail on one.
    121 for _, p := range payouts {
    122 credit(p.Address, id, p.Amount)
    123 }
    124 if fee > 0 {
    125 pay(0, rlm, treasury, fee)
    126 }
    127 assertSolvent(0, rlm)
    128
    129 chain.Emit("RoundSettled",
    130 "arena", id,
    131 "players", itoa(int64(len(r.players))),
    132 "pot", itoa(pot),
    133 "fee", itoa(fee),
    134 "payouts", joinPayouts(payouts),
    135 "treasury", treasury.String(),
    136 "referee", referee.String(),
    137 )
    138}
    139
    140// withFeeShared returns payouts with fee added, shared between every player
    141// of the round, in their order: the remainder goes to the first.
    142func withFeeShared(players []address, payouts []payout.Payout, fee int64) []payout.Payout {
    143 each := fee / int64(len(players))
    144 extra := fee - each*int64(len(players))
    145 out := make([]payout.Payout, 0, len(players))
    146 for i, p := range players {
    147 amount := each
    148 if i == 0 {
    149 amount += extra
    150 }
    151 for _, q := range payouts {
    152 if q.Address == p {
    153 amount += q.Amount
    154 }
    155 }
    156 if amount > 0 {
    157 out = append(out, payout.Payout{Address: p, Amount: amount})
    158 }
    159 }
    160 return out
    161}
    162
    163// checkResult panics unless a result can be paid out of a pot: every payout
    164// goes to a player of the round, a player is paid at most once and never
    165// zero or less, the fee is not negative, and all of it adds up to the pot
    166// exactly.
    167//
    168// The built-in rules guarantee all of this. The check is here so that the
    169// realm does not depend on it: whatever a rule returns, the realm never
    170// sends more than the pot, and never to a stranger.
    171func checkResult(pot int64, players []address, res payout.Result) {
    172 // Every amount is bounded by the pot before it is added, so the sum
    173 // cannot overflow: at most 9 terms of at most 8e9.
    174 if res.Fee < 0 || res.Fee > pot {
    175 panic(errNotConserved)
    176 }
    177 sum := res.Fee
    178 paid := make([]bool, len(players))
    179 for _, p := range res.Payouts {
    180 seat := indexOfAddr(players, p.Address)
    181 if seat < 0 || paid[seat] || p.Amount <= 0 {
    182 panic(errBadSettlement)
    183 }
    184 if p.Amount > pot {
    185 panic(errNotConserved)
    186 }
    187 paid[seat] = true
    188 sum += p.Amount
    189 }
    190 if sum != pot {
    191 panic(errNotConserved)
    192 }
    193}
    194
    195// CancelRound closes the game of an arena without a result: every player
    196// gets their stake back in full. Referee only.
    197//
    198// The game server uses it for a game it cannot referee, for example one it
    199// finds running when it starts.
    200func CancelRound(cur realm, id string) {
    201 referee := assertReferee(0, cur)
    202 a := runningArena(id)
    203 refund(0, cur, id, a, outcomeCancelled, "RoundCancelled", referee)
    204}
    205
    206// RefundRound closes the game of an arena without a result, like
    207// CancelRound, and can be called by anyone, once
    208//
    209// startedAt + seconds + RefundDelay()
    210//
    211// has passed on the block clock. It is the players' way out when the
    212// referee never settles: no stake depends on a server staying up.
    213func RefundRound(cur realm, id string) {
    214 caller := callerOf(0, cur)
    215 a := runningArena(id)
    216 if now() < a.round.startedAt+a.seconds+refundDelay {
    217 panic(errRefundTooEarly)
    218 }
    219 refund(0, cur, id, a, outcomeRefunded, "RoundRefunded", caller)
    220}
    221
    222// refund closes the game of an arena and sends every player their stake
    223// back. No fee is taken.
    224func refund(_ int, rlm realm, id string, a *arena, outcome, event string, caller address) {
    225 r := a.round
    226 pot := a.stake * int64(len(r.players))
    227 payouts := make([]payout.Payout, len(r.players))
    228 for i, p := range r.players {
    229 payouts[i] = payout.Payout{Address: p, Amount: a.stake}
    230 }
    231
    232 // State first, as in settle.
    233 arenas.Remove(id)
    234 release(pot)
    235 record(id, result{
    236 mode: a.mode,
    237 stake: a.stake,
    238 outcome: outcome,
    239 players: int64(len(r.players)),
    240 pot: pot,
    241 closedAt: now(),
    242 payouts: payouts,
    243 })
    244
    245 for _, p := range r.players {
    246 pay(0, rlm, p, a.stake)
    247 }
    248 assertSolvent(0, rlm)
    249
    250 chain.Emit(event,
    251 "arena", id,
    252 "players", itoa(int64(len(r.players))),
    253 "pot", itoa(pot),
    254 "caller", caller.String(),
    255 )
    256}
    257
    258// runningArena returns an arena whose game is running. It panics for an
    259// unknown arena and for one whose game has not started.
    260func runningArena(id string) *arena {
    261 a := mustArena(id)
    262 if a.round == nil {
    263 panic(errNoRound)
    264 }
    265 return a
    266}
    267
    268// Refund delay bounds, in seconds.
    269const (
    270 minRefundDelay = 60
    271 maxRefundDelay = 86400
    272)
    273
    274// SetRefundDelay sets how long after a game's time limit anyone may refund
    275// it. Admin only.
    276func SetRefundDelay(cur realm, seconds int64) {
    277 assertAdmin(0, cur)
    278 if seconds < minRefundDelay || seconds > maxRefundDelay {
    279 panic(errInvalidRefundDelay)
    280 }
    281 refundDelay = seconds
    282 version++
    283 chain.Emit("RefundDelaySet", "seconds", itoa(seconds))
    284}
    285
    286// RefundDelay returns the refund delay, in seconds.
    287func RefundDelay() int64 {
    288 return refundDelay
    289}
    290
    291// joinAddrs writes addresses separated by commas, for an event.
    292func joinAddrs(list []address) string {
    293 var b strings.Builder
    294 for i, a := range list {
    295 if i > 0 {
    296 b.WriteByte(',')
    297 }
    298 b.WriteString(a.String())
    299 }
    300 return b.String()
    301}
    302
    303// joinPayouts writes payouts as "address:amount" separated by commas, for
    304// an event and for the stored results.
    305func joinPayouts(list []payout.Payout) string {
    306 var b strings.Builder
    307 for i, p := range list {
    308 if i > 0 {
    309 b.WriteByte(',')
    310 }
    311 b.WriteString(p.Address.String())
    312 b.WriteByte(':')
    313 b.WriteString(itoa(p.Amount))
    314 }
    315 return b.String()
    316}
    317

    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.