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