Search Apps Documentation Source Content File Folder Download Copy Actions Download

proposal.gno

8.99 Kb ยท 403 lines
  1package gnopendao9
  2
  3import (
  4	"time"
  5	"errors"
  6	"chain/banker"
  7	"chain/runtime"
  8	"chain"
  9	"strconv"
 10
 11	"gno.land/p/nt/commondao"
 12	"gno.land/p/nt/ufmt"
 13)
 14
 15const (
 16	PROPOSAL_TYPE_APPROVE_COLLECTION = "approve_collection"
 17	PROPOSAL_TYPE_REMOVE_COLLECTION  = "remove_collection"
 18	PROPOSAL_TYPE_UPDATE_FEES        = "update_fees"
 19	PROPOSAL_TYPE_WITHDRAW_TREASURY  = "withdraw_treasury"
 20	PROPOSAL_TYPE_FORCE_CANCEL_LISTING = "force_cancel_listing"
 21
 22	VOTING_PERIOD = 10 * time.Minute // 7 * 24 * time.Hour // 10 minutes for testing
 23	QUORUM        = 0               // 0% of members must vote
 24)
 25
 26// CollectionProposal - Proposal to approve or remove a collection
 27type CollectionProposal struct {
 28	proposalType    string
 29	collectionAddr  address
 30	collectionName  string
 31	reason          string
 32	approved        bool
 33	executed        bool
 34}
 35
 36func newCollectionProposal(propType string, addr address, name string, reason string) *CollectionProposal {
 37	return &CollectionProposal{
 38		proposalType:   propType,
 39		collectionAddr: addr,
 40		collectionName: name,
 41		reason:         reason,
 42		approved:       false,
 43		executed:       false,
 44	}
 45}
 46
 47func (p *CollectionProposal) Title() string {
 48	if p.proposalType == PROPOSAL_TYPE_APPROVE_COLLECTION {
 49		return "Approve Collection: " + p.collectionName
 50	}
 51	return "Remove Collection: " + p.collectionName
 52}
 53
 54func (p *CollectionProposal) Body() string {
 55	return ufmt.Sprintf(
 56		"Type: %s\nCollection: %s\nAddress: %s\nReason: %s\n\nVote YES to approve, NO to reject",
 57		p.proposalType,
 58		p.collectionName,
 59		p.collectionAddr.String(),
 60		p.reason,
 61	)
 62}
 63
 64func (p *CollectionProposal) VotingPeriod() time.Duration {
 65	return VOTING_PERIOD
 66}
 67
 68func (p *CollectionProposal) Tally(
 69	votes commondao.ReadonlyVotingRecord,
 70	members commondao.MemberSet,
 71) (bool, error) {
 72	yesVotes := 0
 73	noVotes := 0
 74	totalVotes := 0
 75
 76	votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
 77		if string(v.Choice) == "yes" {
 78			yesVotes++
 79		} else if string(v.Choice) == "no" {
 80			noVotes++
 81		}
 82		totalVotes++
 83		return false
 84	})
 85
 86	// Check quorum
 87	totalMembers := members.Size()
 88	quorumRequired := (totalMembers * QUORUM) / 100
 89
 90	if totalVotes < quorumRequired {
 91		p.approved = false
 92		return false, nil
 93	}
 94
 95	// Simple majority
 96	p.approved = yesVotes > noVotes
 97	return p.approved, nil
 98}
 99
100func (p *CollectionProposal) IsApproved() bool {
101	return p.approved
102}
103
104func (p *CollectionProposal) IsExecuted() bool {
105	return p.executed
106}
107
108func (p *CollectionProposal) Execute(realm) error {
109    // Do the actual action
110    if p.proposalType == PROPOSAL_TYPE_APPROVE_COLLECTION {
111        approvedCollections.Set(p.collectionAddr.String(), true)
112    } else if p.proposalType == PROPOSAL_TYPE_REMOVE_COLLECTION {
113        approvedCollections.Remove(p.collectionAddr.String())
114    }
115    
116    p.executed = true
117    return nil
118}
119
120func (p *CollectionProposal) GetCollectionAddr() address {
121	return p.collectionAddr
122}
123
124// FeesProposal - Proposal to update marketplace fees
125type FeesProposal struct {
126	newFee   int64
127	reason   string
128	approved bool
129	executed bool
130}
131
132func newFeesProposal(newFee int64, reason string) *FeesProposal {
133	return &FeesProposal{
134		newFee:   newFee,
135		reason:   reason,
136		approved: false,
137		executed: false,
138	}
139}
140
141func (p *FeesProposal) Title() string {
142	return ufmt.Sprintf("Update Marketplace Fee to %d basis points", p.newFee)
143}
144
145func (p *FeesProposal) Body() string {
146	currentFeePercent := float64(marketplaceFee) / 100.0
147	newFeePercent := float64(p.newFee) / 100.0
148
149	return ufmt.Sprintf(
150		"Current Fee: %.2f%%\nProposed Fee: %.2f%%\nReason: %s\n\nVote YES to approve, NO to reject",
151		currentFeePercent,
152		newFeePercent,
153		p.reason,
154	)
155}
156
157func (p *FeesProposal) VotingPeriod() time.Duration {
158	return VOTING_PERIOD
159}
160
161func (p *FeesProposal) Tally(
162	votes commondao.ReadonlyVotingRecord,
163	members commondao.MemberSet,
164) (bool, error) {
165	yesVotes := 0
166	noVotes := 0
167	totalVotes := 0
168
169	votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
170		if string(v.Choice) == "yes" {
171			yesVotes++
172		} else if string(v.Choice) == "no" {
173			noVotes++
174		}
175		totalVotes++
176		return false
177	})
178
179	// Check quorum
180	totalMembers := members.Size()
181	quorumRequired := (totalMembers * QUORUM) / 100
182
183	if totalVotes < quorumRequired {
184		p.approved = false
185		return false, nil
186	}
187
188	// Simple majority
189	p.approved = yesVotes > noVotes
190	return p.approved, nil
191}
192
193func (p *FeesProposal) IsApproved() bool {
194	return p.approved
195}
196
197func (p *FeesProposal) IsExecuted() bool {
198	return p.executed
199}
200
201func (p *FeesProposal) Execute(realm) error {
202    // Do the actual action
203    marketplaceFee = p.newFee
204
205    p.executed = true
206    return nil
207}
208
209func (p *FeesProposal) GetNewFee() int64 {
210	return p.newFee
211}
212
213// TreasuryProposal - Proposal to withdraw funds from treasury
214type TreasuryProposal struct {
215	amount      int64
216	recipient   address
217	reason      string
218	approved    bool
219	executed    bool
220}
221
222func NewTreasuryProposal(amount int64, recipient address, reason string) *TreasuryProposal {
223	return &TreasuryProposal{
224		amount:    amount,
225		recipient: recipient,
226		reason:    reason,
227		approved:  false,
228		executed:  false,
229	}
230}
231
232func (p *TreasuryProposal) Title() string {
233	return ufmt.Sprintf("Withdraw %d ugnot from Treasury", p.amount)
234}
235
236func (p *TreasuryProposal) Body() string {
237	currentBalance := GetBalance()
238
239	return ufmt.Sprintf(
240		"Withdraw: %s\nRecipient: %s\nCurrent Treasury: %s\nReason: %s\n\nVote YES to approve, NO to reject",
241		formatPrice(p.amount),
242		p.recipient.String(),
243		formatPrice(currentBalance),
244		p.reason,
245	)
246}
247
248func (p *TreasuryProposal) VotingPeriod() time.Duration {
249	return VOTING_PERIOD
250}
251
252func (p *TreasuryProposal) Tally(
253	votes commondao.ReadonlyVotingRecord,
254	members commondao.MemberSet,
255) (bool, error) {
256	yesVotes := 0
257	noVotes := 0
258	totalVotes := 0
259
260	votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
261		if string(v.Choice) == "yes" || string(v.Choice) == "YES" {
262			yesVotes++
263		} else if string(v.Choice) == "no" || string(v.Choice) == "NO" {
264			noVotes++
265		}
266		totalVotes++
267		return false
268	})
269
270	// Check quorum
271	totalMembers := members.Size()
272	quorumRequired := (totalMembers * QUORUM) / 100
273
274	if totalVotes < quorumRequired {
275		p.approved = false
276		return false, nil
277	}
278
279	// Simple majority
280	p.approved = yesVotes > noVotes
281	return p.approved, nil
282}
283
284func (p *TreasuryProposal) Execute(realm) error {
285	// Verify sufficient balance
286	currentBalance := GetBalance()
287	if currentBalance < p.amount {
288		return errors.New("insufficient treasury balance")
289	}
290
291	// Send funds
292	bnkr := banker.NewBanker(banker.BankerTypeRealmSend)
293	realmAddr := runtime.CurrentRealm().Address()
294
295	coins := chain.Coins{chain.Coin{"ugnot", p.amount}}
296	bnkr.SendCoins(realmAddr, p.recipient, coins)
297
298	p.executed = true
299	return nil
300}
301
302func (p *TreasuryProposal) GetAmount() int64 {
303	return p.amount
304}
305
306func (p *TreasuryProposal) GetRecipient() address {
307	return p.recipient
308}
309
310// ForceCancelListingProposal - Proposal to force cancel a problematic listing
311type ForceCancelListingProposal struct {
312	listingId   int
313	reason      string
314	approved    bool
315	executed    bool
316}
317
318func NewForceCancelListingProposal(listingId int, reason string) *ForceCancelListingProposal {
319	return &ForceCancelListingProposal{
320		listingId: listingId,
321		reason:    reason,
322		approved:  false,
323		executed:  false,
324	}
325}
326
327func (p *ForceCancelListingProposal) Title() string {
328	return ufmt.Sprintf("Force Cancel Listing #%d", p.listingId)
329}
330
331func (p *ForceCancelListingProposal) Body() string {
332	listing := getListing(p.listingId)
333	if listing == nil {
334		return ufmt.Sprintf("Listing #%d\nStatus: NOT FOUND\nReason: %s\n\nVote YES to approve, NO to reject", p.listingId, p.reason)
335	}
336
337	return ufmt.Sprintf(
338		"Listing ID: %d\nToken ID: %s\nSeller: %s\nPrice: %s\nReason for cancellation: %s\n\nVote YES to force cancel, NO to reject",
339		p.listingId,
340		listing.TokenId.String(),
341		listing.Seller.String(),
342		formatPrice(listing.Price),
343		p.reason,
344	)
345}
346
347func (p *ForceCancelListingProposal) VotingPeriod() time.Duration {
348	return VOTING_PERIOD
349}
350
351func (p *ForceCancelListingProposal) Tally(
352	votes commondao.ReadonlyVotingRecord,
353	members commondao.MemberSet,
354) (bool, error) {
355	yesVotes := 0
356	noVotes := 0
357	totalVotes := 0
358
359	votes.Iterate(0, votes.Size(), false, func(v commondao.Vote) bool {
360		if string(v.Choice) == "yes" || string(v.Choice) == "YES" {
361			yesVotes++
362		} else if string(v.Choice) == "no" || string(v.Choice) == "NO" {
363			noVotes++
364		}
365		totalVotes++
366		return false
367	})
368
369	// Check quorum
370	totalMembers := members.Size()
371	quorumRequired := (totalMembers * QUORUM) / 100
372
373	if totalVotes < quorumRequired {
374		p.approved = false
375		return false, nil
376	}
377
378	// Simple majority
379	p.approved = yesVotes > noVotes
380	return p.approved, nil
381}
382
383func (p *ForceCancelListingProposal) Execute(realm) error {
384	listing := getListing(p.listingId)
385	if listing == nil {
386		return errors.New("listing not found")
387	}
388
389	if !listing.Active {
390		return errors.New("listing already inactive")
391	}
392
393	// Force cancel the listing
394	listing.Active = false
395	listings.Set(strconv.Itoa(p.listingId), listing)
396
397	p.executed = true
398	return nil
399}
400
401func (p *ForceCancelListingProposal) GetListingId() int {
402	return p.listingId
403}