memberstore.gno
5.89 Kb · 226 lines
1package memberstore
2
3import (
4 "strings"
5
6 "gno.land/p/demo/svg"
7 "gno.land/p/moul/md"
8 "gno.land/p/nt/bptree/v0"
9 "gno.land/p/nt/mux/v0"
10 "gno.land/p/nt/ufmt/v0"
11 "gno.land/r/gov/dao"
12)
13
14var (
15 members MembersByTier
16 tiers TiersByName // private to prevent external modification
17 router *mux.Router
18)
19
20const (
21 T1 = "T1"
22 T2 = "T2"
23 T3 = "T3"
24)
25
26func init() {
27 members = NewMembersByTier()
28
29 tiers = TiersByName{bptree.NewBPTree32()}
30 tiers.Set(T1, Tier{
31 InvitationPoints: 3,
32 MinSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
33 return 70
34 },
35 MaxSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
36 return 0
37 },
38 BasePower: 3,
39 PowerHandler: func(membersByTier MembersByTier, tiersByName TiersByName) float64 {
40 return 3
41 },
42 })
43
44 tiers.Set(T2, Tier{
45 InvitationPoints: 2,
46 MaxSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
47 return membersByTier.GetTierSize(T1) * 2
48 },
49 MinSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
50 return membersByTier.GetTierSize(T1) / 4
51 },
52 BasePower: 2,
53 PowerHandler: func(membersByTier MembersByTier, tiersByName TiersByName) float64 {
54 t1ms := float64(membersByTier.GetTierSize(T1))
55 t1, _ := tiersByName.GetTier(T1)
56 t2ms := float64(membersByTier.GetTierSize(T2))
57 t2, _ := tiersByName.GetTier(T2)
58
59 t1p := t1.BasePower * t1ms
60 t2p := t2.BasePower * t2ms
61
62 // capped to 2/3 of tier 1
63 t1ptreshold := t1p * (2.0 / 3.0)
64 if t2p > t1ptreshold {
65 return t1ptreshold / t2ms
66 }
67
68 return t2.BasePower
69 },
70 })
71
72 tiers.Set(T3, Tier{
73 InvitationPoints: 1,
74 MaxSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
75 return 0
76 },
77 MinSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
78 return 0
79 },
80 BasePower: 1,
81 PowerHandler: func(membersByTier MembersByTier, tiersByName TiersByName) float64 {
82 t1ms := float64(membersByTier.GetTierSize(T1))
83 t1, _ := tiersByName.GetTier(T1)
84 t3ms := float64(membersByTier.GetTierSize(T3))
85 t3, _ := tiersByName.GetTier(T3)
86
87 t1p := t1.BasePower * t1ms
88 t3p := t3.BasePower * t3ms
89
90 // capped to 1/3 of tier 1
91 t1ptreshold := t1p * (1.0 / 3.0)
92 if t3p > t1ptreshold {
93 return t1ptreshold / t3ms
94 }
95
96 return t3.BasePower
97 },
98 })
99
100 initRouter()
101}
102
103// initRouter initializes the router for the memberstore.
104func initRouter() {
105 router = mux.NewRouter()
106 router.HandleFunc("", renderHome)
107 router.HandleFunc("members", renderMembers)
108 router.NotFoundHandler = renderNotFound
109}
110
111// renderHome displays the tiers data (Number of members and powers) and tiers charts.
112func renderHome(res *mux.ResponseWriter, req *mux.Request) {
113 var sb strings.Builder
114 sb.WriteString(md.Link("> Go to Members list <", "/r/gov/dao/v3/memberstore:members") + "\n")
115
116 members.Iterate("", "", func(tn string, ti interface{}) bool {
117 tree, ok := ti.(*bptree.BPTree)
118 if !ok {
119 return false
120 }
121
122 tier, ok := tiers.GetTier(tn)
123 if !ok {
124 return false
125 }
126
127 tp := (tier.PowerHandler(members, tiers) * float64(members.GetTierSize(tn)))
128
129 sb.WriteString(ufmt.Sprintf("- %v Tier %v contains %v members with power: %v\n", tierColoredChip(tn), tn, tree.Size(), tp))
130
131 return false
132 })
133
134 sb.WriteString("\n" + RenderCharts(members))
135 res.Write(sb.String())
136}
137
138// renderMembers displays the members list.
139func renderMembers(res *mux.ResponseWriter, req *mux.Request) {
140 path := strings.Replace(req.RawPath, "members", "", 1) // We have to clean the path
141 res.Write(RenderMembers(path, members))
142}
143
144func renderNotFound(res *mux.ResponseWriter, req *mux.Request) {
145 res.Write("# 404\n\nThat page was not found. Would you like to [**go home**?](/r/gov/dao/v3/memberstore)")
146}
147
148func tierColor(tn string) string {
149 switch tn {
150 case T1:
151 return "#329175"
152 case T2:
153 return "#21577A"
154 case T3:
155 return "#F3D3BC"
156 default:
157 return "#FFF"
158 }
159}
160
161// tierColoredChip returns a colored chip svg for the given tier name.
162func tierColoredChip(tn string) string {
163 canvas := svg.NewCanvas(16, 16)
164 canvas.Append(svg.NewRectangle(0, 0, 16, 16, tierColor(tn)))
165 return canvas.Render(tn + " colored chip")
166}
167
168func Render(path string) string {
169 var sb strings.Builder
170 sb.WriteString(md.H1("Memberstore Govdao v3"))
171 sb.WriteString(router.Render(path))
172 return sb.String()
173}
174
175// Get gets the Members store.
176//
177// rlm is the cur of an in-scope crossing frame, threaded by the caller.
178// The IsCurrent() check rejects stale or stashed realm values — a
179// malicious realm cannot replay an old cur to claim allowed-DAO
180// identity. After the check, rlm.PkgPath() is the authentic immediate
181// caller's realm.
182func Get(_ int, rlm realm) MembersByTier {
183 if !rlm.IsCurrent() {
184 panic("memberstore.Get: rlm is not the caller's live cur (stale capture or sibling frame)")
185 }
186 currealm := rlm.PkgPath()
187 if !dao.InAllowedDAOs(currealm) {
188 panic("this Realm is not allowed to get the Members data: " + currealm)
189 }
190
191 return members
192}
193
194// GetTier returns a tier by name. This is a read-only accessor.
195func GetTier(name string) (Tier, bool) {
196 return tiers.GetTier(name)
197}
198
199// IterateTiers iterates over all tiers in order. This is a read-only accessor.
200// The callback receives the tier name and tier data.
201// Return true from the callback to stop iteration.
202func IterateTiers(fn func(name string, tier Tier) bool) {
203 tiers.Iterate("", "", func(name string, value interface{}) bool {
204 tier, ok := value.(Tier)
205 if !ok {
206 return false
207 }
208 return fn(name, tier)
209 })
210}
211
212// setTiers replaces the tiers configuration.
213// This is internal and should only be called via governance proposal execution.
214func setTiers(newTiers TiersByName) {
215 tiers = newTiers
216}
217
218// GetTierPower calculates the effective voting power for a tier given the current members.
219// This is a safe accessor that uses the internal tiers configuration.
220func GetTierPower(tierName string, members MembersByTier) float64 {
221 tier, ok := tiers.GetTier(tierName)
222 if !ok {
223 return 0
224 }
225 return tier.PowerHandler(members, tiers)
226}