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

memberstore.gno

5.89 Kb · 226 lines
  1package memberstore
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/moul/md/v0"
  7	"gno.land/p/nt/bptree/v0"
  8	"gno.land/p/nt/mux/v0"
  9	"gno.land/p/nt/svg/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/memberstore/v0: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/memberstore/v0)")
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 v0"))
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}