1package govdao
2
3import (
4 "std"
5 "strconv"
6
7 "gno.land/p/demo/ufmt"
8 pproposal "gno.land/p/gov/proposal"
9)
10
11var (
12 proposals = make([]*proposal, 0)
13 members = make([]std.Address, 0) // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs
14)
15
16const (
17 msgMissingExecutor = "missing proposal executor"
18 msgPropExecuted = "prop already executed"
19 msgPropExpired = "prop is expired"
20 msgPropInactive = "prop is not active anymore"
21 msgPropActive = "prop is still active"
22 msgPropNotAccepted = "prop is not accepted"
23
24 msgCallerNotAMember = "caller is not member of govdao"
25 msgProposalNotFound = "proposal not found"
26)
27
28func init() {
29 // Prepare the initial members
30 set := []std.Address{
31 /* GNO CORE */
32 "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj", // Jae
33 "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", // Manfred
34 "g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2", // Milos
35 "g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7", // Nemanja
36 "g17p2kkyy9lp2z3ecw4psssk357vxp20afnyl00d", // Petar
37 "g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd", // Marc
38 "g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl", // Antonio
39 "g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x", // Guilhem
40 "g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j", // Ray
41 "g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq", // Maxwell
42 "g1acn3xssksatydd0fcuslvgmjyw0fzkjdhusddg", // Dylan
43 "g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864", // Morgan
44 "g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr", // Sergio
45 "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5", // Leon
46
47 /* GNO DEVX */
48 "g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu", // Ilker
49 "g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun", // Jerónimo
50 "g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd", // Denis
51 "g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7", // Danny
52 "g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5", // Michelle
53 "g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m", // Alan
54 "g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh", // Salvo
55 "g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq", // Alexis
56
57 /* ONBLOC */
58 "g17m8hlvm3k0agngz8vw29etpcjd2yvcel6pvt3k", // Andrew
59 "g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay", // Dongwon
60 "g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76", // Blake
61 "g17n4y745s08awwq4e0a38lagsgtntna0749tnxe", // Jinwoo
62 "g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9", // ByeongJun
63
64 /* TERITORI */
65 "g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a", // Norman
66
67 /* BERTY */
68 "g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm", // Rémi
69
70 /* FLIPPANDO / ZENTASKTIC */
71 "g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3", // Dragos
72 }
73
74 // Save the members
75 members = append(members, set...)
76}
77
78type proposal struct {
79 author std.Address
80 comment string
81 executor pproposal.Executor
82 voter Voter
83 executed bool
84 voters []std.Address // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs.
85}
86
87func (p proposal) Status() Status {
88 if p.executor.IsExpired() {
89 return Expired
90 }
91
92 if p.executor.IsDone() {
93 return Succeeded
94 }
95
96 if !p.voter.IsFinished(members) {
97 return Active
98 }
99
100 if p.voter.IsAccepted(members) {
101 return Accepted
102 }
103
104 return NotAccepted
105}
106
107// Propose is designed to be called by another contract or with
108// `maketx run`, not by a `maketx call`.
109func Propose(comment string, executor pproposal.Executor) int {
110 // XXX: require payment?
111 if executor == nil {
112 panic(msgMissingExecutor)
113 }
114 caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!
115 AssertIsMember(caller)
116
117 prop := &proposal{
118 comment: comment,
119 executor: executor,
120 author: caller,
121 voter: NewPercentageVoter(66), // at least 2/3 must say yes
122 }
123
124 proposals = append(proposals, prop)
125
126 return len(proposals) - 1
127}
128
129func VoteOnProposal(idx int, option string) {
130 assertProposalExists(idx)
131 caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!
132 AssertIsMember(caller)
133
134 prop := getProposal(idx)
135
136 if prop.executed {
137 panic(msgPropExecuted)
138 }
139
140 if prop.executor.IsExpired() {
141 panic(msgPropExpired)
142 }
143
144 if prop.voter.IsFinished(members) {
145 panic(msgPropInactive)
146 }
147
148 prop.voter.Vote(members, caller, option)
149}
150
151func ExecuteProposal(idx int) {
152 assertProposalExists(idx)
153 prop := getProposal(idx)
154
155 if prop.executed {
156 panic(msgPropExecuted)
157 }
158
159 if prop.executor.IsExpired() {
160 panic(msgPropExpired)
161 }
162
163 if !prop.voter.IsFinished(members) {
164 panic(msgPropActive)
165 }
166
167 if !prop.voter.IsAccepted(members) {
168 panic(msgPropNotAccepted)
169 }
170
171 prop.executor.Execute()
172 prop.voters = members
173 prop.executed = true
174}
175
176func IsMember(addr std.Address) bool {
177 if len(members) == 0 { // special case for initial execution
178 return true
179 }
180
181 for _, v := range members {
182 if v == addr {
183 return true
184 }
185 }
186
187 return false
188}
189
190func AssertIsMember(addr std.Address) {
191 if !IsMember(addr) {
192 panic(msgCallerNotAMember)
193 }
194}
195
196func Render(path string) string {
197 if path == "" {
198 if len(proposals) == 0 {
199 return "No proposals found :(" // corner case
200 }
201
202 output := ""
203 for idx, prop := range proposals {
204 output += ufmt.Sprintf("- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author)
205 }
206
207 return output
208 }
209
210 // else display the proposal
211 idx, err := strconv.Atoi(path)
212 if err != nil {
213 return "404"
214 }
215
216 if !proposalExists(idx) {
217 return "404"
218 }
219 prop := getProposal(idx)
220
221 vs := members
222 if prop.executed {
223 vs = prop.voters
224 }
225
226 output := ""
227 output += ufmt.Sprintf("# Prop #%d", idx)
228 output += "\n\n"
229 output += prop.comment
230 output += "\n\n"
231 output += ufmt.Sprintf("Status: %s", string(prop.Status()))
232 output += "\n\n"
233 output += ufmt.Sprintf("Voting status: %s", prop.voter.Status(vs))
234 output += "\n\n"
235 output += ufmt.Sprintf("Author: %s", string(prop.author))
236 output += "\n\n"
237
238 return output
239}
240
241func getProposal(idx int) *proposal {
242 if idx > len(proposals)-1 {
243 panic(msgProposalNotFound)
244 }
245
246 return proposals[idx]
247}
248
249func proposalExists(idx int) bool {
250 return idx >= 0 && idx <= len(proposals)
251}
252
253func assertProposalExists(idx int) {
254 if !proposalExists(idx) {
255 panic("invalid proposal id")
256 }
257}
dao.gno
5.98 Kb · 257 lines