Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

spec.gno

3.86 Kb · 153 lines
  1package arena
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"strings"
  7
  8	"gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0"
  9)
 10
 11// An arena is created off chain, by the game server, at a player's request.
 12// It is described by a spec, a string of fields separated by "|":
 13//
 14//	v1|mode|stake|minSeats|maxSeats|lives|seconds|killTarget|creator|created|nonce
 15//
 16//	v1|deathmatch|10000000|2|8|3|900|5|g1abc...|1760000000|k3x9
 17//
 18// and known by its id, the first 24 hex characters of the SHA-256 of the
 19// spec. The first player to pay for an arena hands the realm its spec; the
 20// realm checks it and stores the arena under the id it computes. Nobody can
 21// change the stake or the rules of an id: a different spec is a different
 22// id.
 23//
 24// created is the server's clock when the arena was proposed. An arena takes
 25// entries for an hour from then, and its lobby can be closed by anyone after
 26// that, see ExpireLobby.
 27
 28// Bounds of a spec, the same as the game server's.
 29const (
 30	specVersion = "v1"
 31	idLen       = 24
 32
 33	minStake      = 1000000    // ugnot, 1 GNOT
 34	maxStake      = 1000000000 // ugnot, 1000 GNOT
 35	minSeconds    = 300        // 5 minutes
 36	maxSeconds    = 1800       // 30 minutes
 37	entryWindow   = 3600       // seconds an arena takes entries after it was created
 38	clockSkew     = 300        // seconds a server clock may run ahead of the chain
 39	maxNonceLen   = 16
 40	maxSpecLength = 160
 41
 42	// feeBps is the house's share of every settled game, for the treasury.
 43	feeBps = 500
 44)
 45
 46// spec is a parsed arena spec.
 47type spec struct {
 48	mode       string
 49	stake      int64
 50	minSeats   int64
 51	maxSeats   int64
 52	lives      int64
 53	seconds    int64
 54	killTarget int64
 55	creator    address
 56	created    int64
 57	nonce      string
 58}
 59
 60// idOf returns the id of a spec string.
 61func idOf(s string) string {
 62	sum := sha256.Sum256([]byte(s))
 63	return hex.EncodeToString(sum[:])[:idLen]
 64}
 65
 66// parseSpec reads and checks a spec string. It panics on anything the realm
 67// would not play.
 68func parseSpec(s string) spec {
 69	if len(s) > maxSpecLength {
 70		panic(errInvalidSpec)
 71	}
 72	f := strings.Split(s, "|")
 73	if len(f) != 11 || f[0] != specVersion {
 74		panic(errInvalidSpec)
 75	}
 76	num := func(i int) int64 {
 77		n, ok := digits(f[i])
 78		if !ok {
 79			panic(errInvalidSpec)
 80		}
 81		return n
 82	}
 83	sp := spec{
 84		mode:       f[1],
 85		stake:      num(2),
 86		minSeats:   num(3),
 87		maxSeats:   num(4),
 88		lives:      num(5),
 89		seconds:    num(6),
 90		killTarget: num(7),
 91		creator:    address(f[8]),
 92		created:    num(9),
 93		nonce:      f[10],
 94	}
 95	if payout.Lookup(sp.mode) == nil {
 96		panic(errInvalidMode)
 97	}
 98	if sp.stake < minStake || sp.stake > maxStake {
 99		panic(errInvalidStake)
100	}
101	if sp.minSeats < payout.MinPlayers || sp.minSeats > sp.maxSeats || sp.maxSeats > payout.MaxPlayers {
102		panic(errInvalidSeats)
103	}
104	if sp.lives < payout.MinLives || sp.lives > payout.MaxLives {
105		panic(errInvalidLives)
106	}
107	if sp.seconds < minSeconds || sp.seconds > maxSeconds {
108		panic(errInvalidSeconds)
109	}
110	if sp.killTarget > payout.MaxKillTarget {
111		panic(errInvalidKillTarget)
112	}
113	// A battle royale is won by staying in the air: a kill target would end
114	// it before that.
115	if sp.mode == payout.ModeBattleRoyale && sp.killTarget != 0 {
116		panic(errRoyaleTarget)
117	}
118	if !validAddress(sp.creator) {
119		panic(errInvalidAddress)
120	}
121	if !validNonce(sp.nonce) {
122		panic(errInvalidSpec)
123	}
124	return sp
125}
126
127// validNonce reports whether s is 1 to 16 characters of [a-z0-9].
128func validNonce(s string) bool {
129	if len(s) == 0 || len(s) > maxNonceLen {
130		return false
131	}
132	for i := 0; i < len(s); i++ {
133		c := s[i]
134		if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') {
135			return false
136		}
137	}
138	return true
139}
140
141// validID reports whether s can be an arena id: 24 lowercase hex characters.
142func validID(s string) bool {
143	if len(s) != idLen {
144		return false
145	}
146	for i := 0; i < len(s); i++ {
147		c := s[i]
148		if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') {
149			return false
150		}
151	}
152	return true
153}