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}