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}