Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}