proposal.gno
6.69 Kb ยท 305 lines
1package gnopendao8
2
3import (
4 "time"
5 "errors"
6 "chain/banker"
7 "chain/runtime"
8 "chain"
9
10 "gno.land/p/nt/commondao"
11 "gno.land/p/nt/ufmt"
12)
13
14const (
15 PROPOSAL_TYPE_APPROVE_COLLECTION = "approve_collection"
16 PROPOSAL_TYPE_REMOVE_COLLECTION = "remove_collection"
17 PROPOSAL_TYPE_UPDATE_FEES = "update_fees"
18 PROPOSAL_TYPE_WITHDRAW_TREASURY = "withdraw_treasury"
19
20 VOTING_PERIOD = 5 * time.Minute // 7 * 24 * time.Hour // 7 days -> 5 minutes for testing
21 QUORUM = 0 // 0% of members must vote
22)
23
24// CollectionProposal - Proposal to approve or remove a collection
25type CollectionProposal struct {
26 proposalType string
27 collectionAddr address
28 collectionName string
29 reason string
30 approved bool
31 executed bool
32}
33
34func newCollectionProposal(propType string, addr address, name string, reason string) *CollectionProposal {
35 return &CollectionProposal{
36 proposalType: propType,
37 collectionAddr: addr,
38 collectionName: name,
39 reason: reason,
40 approved: false,
41 executed: false,
42 }
43}
44
45func (p *CollectionProposal) Title() string {
46 if p.proposalType == PROPOSAL_TYPE_APPROVE_COLLECTION {
47 return "Approve Collection: " + p.collectionName
48 }
49 return "Remove Collection: " + p.collectionName
50}
51
52func (p *CollectionProposal) Body() string {
53 return ufmt.Sprintf(
54 "Type: %s\nCollection: %s\nAddress: %s\nReason: %s\n\nVote YES to approve, NO to reject",
55 p.proposalType,
56 p.collectionName,
57 p.collectionAddr.String(),
58 p.reason,
59 )
60}
61
62func (p *CollectionProposal) VotingPeriod() time.Duration {
63 return VOTING_PERIOD
64}
65
66func (p *CollectionProposal) Tally(
67 votes commondao.ReadonlyVotingRecord,
68 members commondao.MemberSet,
69) (bool, error) {
70 yesVotes := 0
71 noVotes := 0
72 totalVotes := 0
73
74 votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
75 if string(v.Choice) == "yes" {
76 yesVotes++
77 } else if string(v.Choice) == "no" {
78 noVotes++
79 }
80 totalVotes++
81 return false
82 })
83
84 // Check quorum
85 totalMembers := members.Size()
86 quorumRequired := (totalMembers * QUORUM) / 100
87
88 if totalVotes < quorumRequired {
89 p.approved = false
90 return false, nil
91 }
92
93 // Simple majority
94 p.approved = yesVotes > noVotes
95 return p.approved, nil
96}
97
98func (p *CollectionProposal) IsApproved() bool {
99 return p.approved
100}
101
102func (p *CollectionProposal) IsExecuted() bool {
103 return p.executed
104}
105
106func (p *CollectionProposal) Execute(realm) error {
107 // Do the actual action
108 if p.proposalType == PROPOSAL_TYPE_APPROVE_COLLECTION {
109 approvedCollections.Set(p.collectionAddr.String(), true)
110 } else if p.proposalType == PROPOSAL_TYPE_REMOVE_COLLECTION {
111 approvedCollections.Remove(p.collectionAddr.String())
112 }
113
114 p.executed = true
115 return nil
116}
117
118func (p *CollectionProposal) GetCollectionAddr() address {
119 return p.collectionAddr
120}
121
122// FeesProposal - Proposal to update marketplace fees
123type FeesProposal struct {
124 newFee int64
125 reason string
126 approved bool
127 executed bool
128}
129
130func newFeesProposal(newFee int64, reason string) *FeesProposal {
131 return &FeesProposal{
132 newFee: newFee,
133 reason: reason,
134 approved: false,
135 executed: false,
136 }
137}
138
139func (p *FeesProposal) Title() string {
140 return ufmt.Sprintf("Update Marketplace Fee to %d basis points", p.newFee)
141}
142
143func (p *FeesProposal) Body() string {
144 currentFeePercent := float64(marketplaceFee) / 100.0
145 newFeePercent := float64(p.newFee) / 100.0
146
147 return ufmt.Sprintf(
148 "Current Fee: %.2f%%\nProposed Fee: %.2f%%\nReason: %s\n\nVote YES to approve, NO to reject",
149 currentFeePercent,
150 newFeePercent,
151 p.reason,
152 )
153}
154
155func (p *FeesProposal) VotingPeriod() time.Duration {
156 return VOTING_PERIOD
157}
158
159func (p *FeesProposal) Tally(
160 votes commondao.ReadonlyVotingRecord,
161 members commondao.MemberSet,
162) (bool, error) {
163 yesVotes := 0
164 noVotes := 0
165 totalVotes := 0
166
167 votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
168 if string(v.Choice) == "yes" {
169 yesVotes++
170 } else if string(v.Choice) == "no" {
171 noVotes++
172 }
173 totalVotes++
174 return false
175 })
176
177 // Check quorum
178 totalMembers := members.Size()
179 quorumRequired := (totalMembers * QUORUM) / 100
180
181 if totalVotes < quorumRequired {
182 p.approved = false
183 return false, nil
184 }
185
186 // Simple majority
187 p.approved = yesVotes > noVotes
188 return p.approved, nil
189}
190
191func (p *FeesProposal) IsApproved() bool {
192 return p.approved
193}
194
195func (p *FeesProposal) IsExecuted() bool {
196 return p.executed
197}
198
199func (p *FeesProposal) Execute(realm) error {
200 // Do the actual action
201 marketplaceFee = p.newFee
202
203 p.executed = true
204 return nil
205}
206
207func (p *FeesProposal) GetNewFee() int64 {
208 return p.newFee
209}
210
211// TreasuryProposal - Proposal to withdraw funds from treasury
212type TreasuryProposal struct {
213 amount int64
214 recipient address
215 reason string
216 approved bool
217 executed bool
218}
219
220func NewTreasuryProposal(amount int64, recipient address, reason string) *TreasuryProposal {
221 return &TreasuryProposal{
222 amount: amount,
223 recipient: recipient,
224 reason: reason,
225 approved: false,
226 executed: false,
227 }
228}
229
230func (p *TreasuryProposal) Title() string {
231 return ufmt.Sprintf("Withdraw %d ugnot from Treasury", p.amount)
232}
233
234func (p *TreasuryProposal) Body() string {
235 currentBalance := GetBalance()
236
237 return ufmt.Sprintf(
238 "Withdraw: %s\nRecipient: %s\nCurrent Treasury: %s\nReason: %s\n\nVote YES to approve, NO to reject",
239 formatPrice(p.amount),
240 p.recipient.String(),
241 formatPrice(currentBalance),
242 p.reason,
243 )
244}
245
246func (p *TreasuryProposal) VotingPeriod() time.Duration {
247 return VOTING_PERIOD
248}
249
250func (p *TreasuryProposal) Tally(
251 votes commondao.ReadonlyVotingRecord,
252 members commondao.MemberSet,
253) (bool, error) {
254 yesVotes := 0
255 noVotes := 0
256 totalVotes := 0
257
258 votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
259 if string(v.Choice) == "yes" || string(v.Choice) == "YES" {
260 yesVotes++
261 } else if string(v.Choice) == "no" || string(v.Choice) == "NO" {
262 noVotes++
263 }
264 totalVotes++
265 return false
266 })
267
268 // Check quorum
269 totalMembers := members.Size()
270 quorumRequired := (totalMembers * QUORUM) / 100
271
272 if totalVotes < quorumRequired {
273 p.approved = false
274 return false, nil
275 }
276
277 // Simple majority
278 p.approved = yesVotes > noVotes
279 return p.approved, nil
280}
281
282func (p *TreasuryProposal) Execute(realm) error {
283 // Verify sufficient balance
284 currentBalance := GetBalance()
285 if currentBalance < p.amount {
286 return errors.New("insufficient treasury balance")
287 }
288
289 // Send funds
290 bnkr := banker.NewBanker(banker.BankerTypeRealmSend)
291 realmAddr := runtime.CurrentRealm().Address()
292
293 coins := chain.Coins{chain.Coin{"ugnot", p.amount}}
294 bnkr.SendCoins(realmAddr, p.recipient, coins)
295
296 p.executed = true
297 return nil
298}
299
300func (p *TreasuryProposal) GetAmount() int64 {
301 return p.amount
302}
303
304func (p *TreasuryProposal) GetRecipient() address {
305 return p.recipient
306}