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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

parse.gno

3.79 Kb · 130 lines
  1package payout
  2
  3// Both parsers read their input in a single pass, byte by byte, and bound it
  4// before they read it. On chain every interpreted operation is paid for, and
  5// an oversized string must cost its sender nothing but a refusal.
  6//
  7// They only accept an address in its canonical form, see validAddress.
  8
  9const (
 10	// addressLen is the length of a gno address: "g1" and 38 characters.
 11	addressLen = 40
 12	// maxPlayersLen bounds a players string: MaxPlayers addresses, each
 13	// followed by a separator.
 14	maxPlayersLen = MaxPlayers * (addressLen + 1)
 15	// maxDeathsLen bounds a deaths string: MaxDeaths entries
 16	// of two addresses, each followed by a separator.
 17	maxDeathsLen = MaxDeaths * 2 * (addressLen + 1)
 18)
 19
 20// ParsePlayers parses a players string: comma-separated addresses, for
 21// example
 22//
 23//	g1aaa...,g1bbb...,g1ccc...
 24//
 25// It returns 1 to MaxPlayers addresses in the order of the string, each one
 26// valid and listed once. Whitespace, empty entries and a trailing separator
 27// are refused. Whether there are enough players for a round is decided by
 28// Rule.Settle, and by the realm that starts the round.
 29func ParsePlayers(s string) ([]address, error) {
 30	if s == "" {
 31		return nil, ErrNoPlayers
 32	}
 33	if len(s) > maxPlayersLen {
 34		return nil, ErrTooManyPlayers
 35	}
 36	var players []address
 37	i := 0
 38	for {
 39		if len(players) == MaxPlayers {
 40			return nil, ErrTooManyPlayers
 41		}
 42		end := indexFrom(s, i, ',')
 43		addr := address(s[i:end])
 44		if !validAddress(addr) {
 45			return nil, ErrPlayersFormat
 46		}
 47		if indexOf(players, addr) >= 0 {
 48			return nil, ErrDuplicatePlayer
 49		}
 50		players = append(players, addr)
 51		if end == len(s) {
 52			return players, nil
 53		}
 54		i = end + 1
 55		if i == len(s) {
 56			return nil, ErrPlayersFormat // a trailing ","
 57		}
 58	}
 59}
 60
 61// ParseDeaths parses a deaths string: "victim:killer" entries
 62// separated by ";", in the order they happened, "victim:" when nobody shot
 63// the victim down, for example
 64//
 65//	g1bbb...:g1aaa...;g1ccc...:
 66//
 67// The empty string means nobody died and gives an empty list. At
 68// most MaxDeaths entries are accepted, and every address must be valid.
 69// Whitespace, empty entries and a trailing separator are refused.
 70//
 71// It only checks the syntax. Whether the victims and the killers are players
 72// of the round, and how often they died, is decided by Rule.Settle.
 73func ParseDeaths(s string) ([]Death, error) {
 74	if s == "" {
 75		return nil, nil
 76	}
 77	if len(s) > maxDeathsLen {
 78		return nil, ErrTooManyDeaths
 79	}
 80	var out []Death
 81	i := 0
 82	for {
 83		if len(out) == MaxDeaths {
 84			return nil, ErrTooManyDeaths
 85		}
 86		colon := indexFrom(s, i, ':')
 87		if colon == len(s) {
 88			return nil, ErrDeathsFormat
 89		}
 90		end := indexFrom(s, colon+1, ';')
 91
 92		victim := address(s[i:colon])
 93		if !validAddress(victim) {
 94			return nil, ErrDeathsFormat
 95		}
 96		killer := address(s[colon+1 : end])
 97		if killer != "" && !validAddress(killer) {
 98			return nil, ErrDeathsFormat
 99		}
100		out = append(out, Death{Victim: victim, Killer: killer})
101		if end == len(s) {
102			return out, nil
103		}
104		i = end + 1
105		if i == len(s) {
106			return nil, ErrDeathsFormat // a trailing ";"
107		}
108	}
109}
110
111// validAddress reports whether addr is a valid address in its canonical,
112// lowercase form.
113//
114// Bech32 can also be written in all capitals, and address.IsValid accepts
115// that. To a realm the capitals would be a second name for the same account,
116// one that matches no player of a round. Bech32 does not allow mixing cases,
117// so looking at the first character is enough. The length is checked first:
118// it is free, and it keeps an overlong string away from the decoder.
119func validAddress(addr address) bool {
120	return len(addr) == addressLen && addr[0] == 'g' && addr.IsValid()
121}
122
123// indexFrom returns the index of the first c in s at or after i, or len(s)
124// when there is none.
125func indexFrom(s string, i int, c byte) int {
126	for i < len(s) && s[i] != c {
127		i++
128	}
129	return i
130}