Search Apps Documentation Source Content File Folder Download Copy Actions Download

types.gno

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