dao.gno

5.98 Kb · 257 lines
  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}