Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}