voting.gno
5.48 Kb ยท 208 lines
1package gnopendao
2
3import (
4 "chain/runtime"
5 "gno.land/p/nt/commondao"
6 "gno.land/p/nt/ufmt"
7)
8
9// ProposeApproveCollection - Create a proposal to approve a new collection
10func ProposeApproveCollection(_ realm, collectionAddr address, collectionName string, reason string) uint64 {
11 caller := runtime.PreviousRealm().Address()
12
13 if !IsDAOMember(caller) {
14 panic("only DAO members can create proposals")
15 }
16
17 propDef := NewCollectionProposal(PROPOSAL_TYPE_APPROVE_COLLECTION, collectionAddr, collectionName, reason)
18
19 proposal, err := marketplaceDAO.Propose(caller, propDef)
20 if err != nil {
21 panic("failed to create proposal: " + err.Error())
22 }
23
24 return proposal.ID()
25}
26
27// ProposeRemoveCollection - Create a proposal to remove a collection
28func ProposeRemoveCollection(_ realm, collectionAddr address, collectionName string, reason string) uint64 {
29 caller := runtime.PreviousRealm().Address()
30
31 if !IsDAOMember(caller) {
32 panic("only DAO members can create proposals")
33 }
34
35 propDef := NewCollectionProposal(PROPOSAL_TYPE_REMOVE_COLLECTION, collectionAddr, collectionName, reason)
36
37 proposal, err := marketplaceDAO.Propose(caller, propDef)
38 if err != nil {
39 panic("failed to create proposal: " + err.Error())
40 }
41
42 return proposal.ID()
43}
44
45// ProposeUpdateFees - Create a proposal to update marketplace fees
46func ProposeUpdateFees(_ realm, newFeeBasisPoints int64, reason string) uint64 {
47 caller := runtime.PreviousRealm().Address()
48
49 if !IsDAOMember(caller) {
50 panic("only DAO members can create proposals")
51 }
52
53 if newFeeBasisPoints < 0 || newFeeBasisPoints > 1000 {
54 panic("fees must be between 0 and 1000 basis points (0-10%)")
55 }
56
57 propDef := NewFeesProposal(newFeeBasisPoints, reason)
58
59 proposal, err := marketplaceDAO.Propose(caller, propDef)
60 if err != nil {
61 panic("failed to create proposal: " + err.Error())
62 }
63
64 return proposal.ID()
65}
66
67// Vote - Vote on a proposal (yes or no)
68func Vote(_ realm, proposalID uint64, choice string) string {
69 caller := runtime.PreviousRealm().Address()
70
71 if !IsDAOMember(caller) {
72 panic("only DAO members can vote")
73 }
74
75 if choice != "yes" && choice != "no" {
76 panic("choice must be 'yes' or 'no'")
77 }
78
79 proposal := marketplaceDAO.ActiveProposals().Get(proposalID)
80 if proposal == nil {
81 panic("proposal not found")
82 }
83
84 if proposal.HasVotingDeadlinePassed() {
85 panic("voting period has ended")
86 }
87
88 vote := commondao.Vote{
89 Address: caller,
90 Choice: commondao.VoteChoice(choice),
91 Reason: "",
92 Context: nil,
93 }
94
95 proposal.VotingRecord().AddVote(vote)
96
97 return "Vote recorded: " + choice
98}
99
100// TallyProposal - Tally votes and execute if approved
101func TallyProposal(_ realm, proposalID uint64) string {
102 caller := runtime.PreviousRealm().Address()
103
104 if caller != daoAdmin {
105 panic("only admin can tally proposals")
106 }
107
108 proposal := marketplaceDAO.ActiveProposals().Get(proposalID)
109 if proposal == nil {
110 panic("proposal not found")
111 }
112
113 // Try collection proposal
114 if collProp, ok := proposal.Definition().(*CollectionProposal); ok {
115 passes, err := collProp.Tally(proposal.VotingRecord().Readonly(), commondao.NewMemberSet(marketplaceDAO.Members()))
116 if err != nil {
117 panic("tally failed: " + err.Error())
118 }
119
120 if passes && !collProp.IsExecuted() {
121 if collProp.proposalType == PROPOSAL_TYPE_APPROVE_COLLECTION {
122 // Execute approval - add to whitelist or registry
123 approvedCollections.Set(collProp.collectionAddr.String(), true)
124 } else if collProp.proposalType == PROPOSAL_TYPE_REMOVE_COLLECTION {
125 // Execute removal - remove from whitelist
126 approvedCollections.Remove(collProp.collectionAddr.String())
127 }
128 collProp.Execute()
129 }
130
131 return ufmt.Sprintf("Proposal %d - Approved: %t, Executed: %t", proposalID, passes, collProp.IsExecuted())
132 }
133
134 // Try fees proposal
135 if feesProp, ok := proposal.Definition().(*FeesProposal); ok {
136 passes, err := feesProp.Tally(proposal.VotingRecord().Readonly(), commondao.NewMemberSet(marketplaceDAO.Members()))
137 if err != nil {
138 panic("tally failed: " + err.Error())
139 }
140
141 if passes && !feesProp.IsExecuted() {
142 // Execute fee change
143 marketplaceFee = feesProp.GetNewFee()
144 feesProp.Execute()
145 }
146
147 return ufmt.Sprintf("Proposal %d - Approved: %t, New Fee: %d bp, Executed: %t",
148 proposalID, passes, feesProp.GetNewFee(), feesProp.IsExecuted())
149 }
150
151 return "Unknown proposal type"
152}
153
154// GetProposalInfo - Get information about a proposal
155func GetProposalInfo(proposalID uint64) string {
156 proposal := marketplaceDAO.ActiveProposals().Get(proposalID)
157 if proposal == nil {
158 return "Proposal not found"
159 }
160
161 yesVotes := 0
162 noVotes := 0
163
164 proposal.VotingRecord().Iterate(0, proposal.VotingRecord().Size(), false, func(v commondao.Vote) bool {
165 if string(v.Choice) == "yes" {
166 yesVotes++
167 } else if string(v.Choice) == "no" {
168 noVotes++
169 }
170 return false
171 })
172
173 output := ufmt.Sprintf(`Proposal #%d
174Title: %s
175Body: %s
176Yes Votes: %d
177No Votes: %d
178Total Votes: %d
179Voting Ended: %t`,
180 proposalID,
181 proposal.Definition().Title(),
182 proposal.Definition().Body(),
183 yesVotes,
184 noVotes,
185 yesVotes+noVotes,
186 proposal.HasVotingDeadlinePassed(),
187 )
188
189 return output
190}
191
192// GetAllActiveProposals - Get all active proposals
193func GetAllActiveProposals() string {
194 proposals := marketplaceDAO.ActiveProposals()
195
196 if proposals.Size() == 0 {
197 return "No active proposals"
198 }
199
200 output := "=== Active Proposals ===\n\n"
201
202 // Fix: use *commondao.Proposal instead of commondao.Proposal
203 proposals.Iterate(0, proposals.Size(), false, func(p *commondao.Proposal) bool {
204 output += ufmt.Sprintf("ID: %d | %s\n", p.ID(), p.Definition().Title())
205 return false
206 })
207
208 return output
209}