Search Apps Documentation Source Content File Folder Download Copy Actions Download

types.gno

5.65 Kb · 231 lines
  1package impl
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/nt/bptree/v0"
  7	"gno.land/p/nt/ufmt/v0"
  8	"gno.land/r/gov/dao"
  9	"gno.land/r/gov/dao/v3/memberstore"
 10)
 11
 12type Law struct {
 13	Supermajority float64
 14}
 15
 16func NewLaw(supermajority float64) Law {
 17	return Law{Supermajority: supermajority}
 18}
 19
 20func (l *Law) String() string {
 21	return ufmt.Sprintf("This law contains the following data:\n\n- Supermajority: %v%%", l.Supermajority)
 22}
 23
 24// ProposalsStatuses contains the status of all the proposals indexed by the proposal ID.
 25type ProposalsStatuses struct {
 26	*bptree.BPTree // map[int]*proposalStatus
 27}
 28
 29func NewProposalsStatuses() ProposalsStatuses {
 30	return ProposalsStatuses{bptree.NewBPTree32()}
 31}
 32
 33func (pss ProposalsStatuses) GetStatus(id dao.ProposalID) *proposalStatus {
 34	if pss.BPTree == nil {
 35		return nil
 36	}
 37
 38	pids := id.String()
 39	psv, ok := pss.Get(pids)
 40	if !ok {
 41		return nil
 42	}
 43
 44	ps, ok := psv.(*proposalStatus)
 45	if !ok {
 46		panic("ProposalsStatuses must contains only proposalStatus types")
 47	}
 48
 49	return ps
 50}
 51
 52type proposalStatus struct {
 53	YesVotes     memberstore.MembersByTier
 54	NoVotes      memberstore.MembersByTier
 55	AbstainVotes memberstore.MembersByTier
 56	AllVotes     memberstore.MembersByTier
 57
 58	Accepted bool
 59	Denied   bool
 60
 61	DeniedReason string
 62
 63	TiersAllowedToVote []string
 64}
 65
 66func getMembers(cur realm) memberstore.MembersByTier {
 67	return memberstore.Get(0, cur)
 68}
 69
 70func newEmptyVoteStore() memberstore.MembersByTier {
 71	mbt := memberstore.NewMembersByTier()
 72	mbt.SetTier(memberstore.T1)
 73	mbt.SetTier(memberstore.T2)
 74	mbt.SetTier(memberstore.T3)
 75	return mbt
 76}
 77
 78func newProposalStatus(allowedToVote []string) *proposalStatus {
 79	return &proposalStatus{
 80		YesVotes:           newEmptyVoteStore(),
 81		NoVotes:            newEmptyVoteStore(),
 82		AbstainVotes:       newEmptyVoteStore(),
 83		AllVotes:           newEmptyVoteStore(),
 84		TiersAllowedToVote: allowedToVote,
 85	}
 86}
 87
 88// totalPower computes the total voting power dynamically from current members
 89// rather than using a snapshot. See https://github.com/gnolang/gno/pull/5271#discussion_r2952523023
 90//
 91// Non-crossing: rlm is threaded into the crossing getMembers(cross(rlm))
 92// call. The proposalStatus methods that compose this (votePowerPercent,
 93// YesPercent etc., String) all take `_ int, rlm realm` for the same reason.
 94func (ps *proposalStatus) totalPower(_ int, rlm realm) float64 {
 95	members := getMembers(cross(rlm))
 96	var tp float64
 97	for _, tn := range ps.TiersAllowedToVote {
 98		power := memberstore.GetTierPower(tn, members)
 99		tp += power * float64(members.GetTierSize(tn))
100	}
101	return tp
102}
103
104func (ps *proposalStatus) votePowerPercent(_ int, rlm realm, votes memberstore.MembersByTier) float64 {
105	members := getMembers(cross(rlm))
106	var vp float64
107	memberstore.IterateTiers(func(tn string, tier memberstore.Tier) bool {
108		power := memberstore.GetTierPower(tn, members)
109		ts := votes.GetTierSize(tn)
110		vp = vp + (power * float64(ts))
111		return false
112	})
113	tp := ps.totalPower(0, rlm)
114	if tp == 0 {
115		return 0
116	}
117	return (vp / tp) * 100
118}
119
120func (ps *proposalStatus) YesPercent(_ int, rlm realm) float64 {
121	return ps.votePowerPercent(0, rlm, ps.YesVotes)
122}
123
124func (ps *proposalStatus) NoPercent(_ int, rlm realm) float64 {
125	return ps.votePowerPercent(0, rlm, ps.NoVotes)
126}
127
128func (ps *proposalStatus) AbstainPercent(_ int, rlm realm) float64 {
129	return ps.votePowerPercent(0, rlm, ps.AbstainVotes)
130}
131
132func (ps *proposalStatus) IsAllowed(tier string) bool {
133	for _, ta := range ps.TiersAllowedToVote {
134		if ta == tier {
135			return true
136		}
137	}
138
139	return false
140}
141
142// XXX: can be optimized by passing down total power to Yes/No/Abstain percent fn to avoid re-computing
143func (ps *proposalStatus) String(_ int, rlm realm) string {
144	var sb strings.Builder
145	sb.WriteString("### Stats\n")
146
147	if ps.Accepted {
148		sb.WriteString("- **PROPOSAL HAS BEEN ACCEPTED**\n")
149	} else if ps.Denied {
150		sb.WriteString("- **PROPOSAL HAS BEEN DENIED**\n")
151		if ps.DeniedReason != "" {
152			sb.WriteString("REASON: ")
153			sb.WriteString(ps.DeniedReason)
154			sb.WriteString("\n")
155		}
156	} else {
157		sb.WriteString("- **Proposal is open for votes**\n")
158	}
159
160	sb.WriteString("- Tiers eligible to vote: ")
161	sb.WriteString(strings.Join(ps.TiersAllowedToVote, ", "))
162	sb.WriteString("\n")
163
164	sb.WriteString(ufmt.Sprintf("- YES PERCENT: %v%%\n", ps.YesPercent(0, rlm)))
165	sb.WriteString(ufmt.Sprintf("- NO PERCENT: %v%%\n", ps.NoPercent(0, rlm)))
166	sb.WriteString(ufmt.Sprintf("- ABSTAIN PERCENT: %v%%\n", ps.AbstainPercent(0, rlm)))
167
168	return sb.String()
169}
170
171func StringifyVotes(_ int, rlm realm, ps *proposalStatus) string {
172	var sb strings.Builder
173
174	writeVotes(0, rlm, &sb, ps.YesVotes, "YES")
175	writeVotes(0, rlm, &sb, ps.NoVotes, "NO")
176	writeVotes(0, rlm, &sb, ps.AbstainVotes, "ABSTAIN")
177
178	if sb.String() == "" {
179		return "No one voted yet."
180	}
181
182	return sb.String()
183}
184
185func writeVotes(_ int, rlm realm, sb *strings.Builder, t memberstore.MembersByTier, title string) {
186	if t.Size() == 0 {
187		return
188	}
189	members := getMembers(cross(rlm))
190	t.Iterate("", "", func(tn string, value interface{}) bool {
191		_, ok := memberstore.GetTier(tn)
192		if !ok {
193			panic("tier not found")
194		}
195
196		power := memberstore.GetTierPower(tn, members)
197
198		sb.WriteString(ufmt.Sprintf("%v from %v (VPPM %v):\n\n", title, tn, power))
199		ms, _ := value.(*bptree.BPTree)
200		ms.Iterate("", "", func(addr string, _ interface{}) bool {
201			sb.WriteString("- " + tryResolveAddr(address(addr)) + "\n")
202			return false
203		})
204
205		sb.WriteString("\n")
206
207		return false
208	})
209}
210
211func StringifyProposal(p *dao.Proposal) string {
212	out := ufmt.Sprintf(`
213### Title: %s
214
215### Proposed by: %s
216
217%s
218`, p.Title(), p.Author(), p.Description())
219
220	if p.ExecutorString() != "" {
221		out += ufmt.Sprintf(`
222This proposal contains the following metadata:
223
224%s
225
226Executor created in: %s
227`, p.ExecutorString(), p.ExecutorCreationRealm())
228	}
229
230	return out
231}