allowlist.gno
2.51 Kb · 89 lines
1package mint
2
3import (
4 "strings"
5
6 "gno.land/r/sys/users"
7 validators "gno.land/r/sys/validators/v2"
8
9 "gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/safe/v1"
10 "gno.land/r/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/core"
11)
12
13// Allowlist sets (design §4.6). A phase's set is a comma-separated
14// union of terms; an address is eligible if any term admits it:
15//
16// users registered in r/sys/users (live check)
17// validators in the active validator set (live check)
18// holders:<cid> holds a token of collection <cid>
19// merkle:<root> in a sha256 merkle tree; the minter passes the proof
20//
21// A term may carry "|"-separated provenance notes for humans, e.g.
22// "merkle:<root>|src:<git-sha>|data:<sha256>"; notes never affect logic.
23const (
24 MaxSetLen = 512
25 MaxSetTerms = 4
26)
27
28type setTerm struct {
29 name, arg string
30}
31
32// parseSet validates a set and returns its terms; it panics on any error so
33// AddPhase rejects a malformed set before it is ever frozen.
34func parseSet(set string) []setTerm {
35 safe.AssertText("set", set, MaxSetLen)
36 parts := strings.Split(set, ",")
37 if len(parts) > MaxSetTerms {
38 panic("set: at most 4 terms")
39 }
40 terms := make([]setTerm, 0, len(parts))
41 for _, part := range parts {
42 body, _, _ := strings.Cut(part, "|")
43 name, arg, _ := strings.Cut(body, ":")
44 switch name {
45 case "users", "validators":
46 if arg != "" {
47 panic("set: " + name + " takes no argument")
48 }
49 case "holders":
50 core.Info(arg) // panics for an unknown collection
51 case "merkle":
52 arg = safe.AssertHex("set merkle root", arg, 32)
53 default:
54 panic("set: unknown term " + name)
55 }
56 terms = append(terms, setTerm{name: name, arg: arg})
57 }
58 return terms
59}
60
61// eligible reports whether addr may mint in a phase with this set, and why
62// not. Live sets are evaluated at mint time, so a deleted user or a removed
63// validator loses eligibility immediately.
64func eligible(cid, set string, addr address, proof string) (bool, string) {
65 if set == "" {
66 return true, ""
67 }
68 for _, t := range parseSet(set) {
69 switch t.name {
70 case "users":
71 if users.ResolveAddress(addr) != nil {
72 return true, ""
73 }
74 case "validators":
75 if validators.IsValidator(addr) {
76 return true, ""
77 }
78 case "holders":
79 if n, err := core.BalanceOf(t.arg, addr); err == nil && n > 0 {
80 return true, ""
81 }
82 case "merkle":
83 if proof != "" && safe.MerkleVerify(t.arg, safe.AllowlistLeaf(cid, addr), proof) {
84 return true, ""
85 }
86 }
87 }
88 return false, "address is not in this phase's allowlist (" + set + ")"
89}