proposal_test.gno
9.91 Kb ยท 391 lines
1package commondao_test
2
3import (
4 "errors"
5 "testing"
6 "time"
7
8 "gno.land/p/nt/uassert"
9 "gno.land/p/nt/urequire"
10
11 "gno.land/p/devx000/wip/nt/commondao"
12)
13
14func TestProposalNew(t *testing.T) {
15 cases := []struct {
16 name string
17 creator address
18 definition commondao.ProposalDefinition
19 err error
20 }{
21 {
22 name: "success",
23 creator: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
24 definition: testPropDef{votingPeriod: time.Minute * 10},
25 },
26 {
27 name: "invalid creator address",
28 creator: "invalid",
29 definition: testPropDef{},
30 err: commondao.ErrInvalidCreatorAddress,
31 },
32 {
33 name: "max custom vote choices exceeded",
34 creator: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
35 definition: testPropDef{
36 voteChoices: make([]commondao.VoteChoice, commondao.MaxCustomVoteChoices+1),
37 },
38 err: commondao.ErrMaxCustomVoteChoices,
39 },
40 }
41
42 for _, tc := range cases {
43 t.Run(tc.name, func(t *testing.T) {
44 id := uint64(1)
45
46 p, err := commondao.NewProposal(id, tc.creator, tc.definition)
47
48 if tc.err != nil {
49 urequire.ErrorIs(t, err, tc.err, "expected an error")
50 return
51 }
52
53 urequire.NoError(t, err, "unexpected error")
54 uassert.Equal(t, p.ID(), id)
55 uassert.NotEqual(t, p.Definition(), nil)
56 uassert.True(t, p.Status() == commondao.StatusActive)
57 uassert.Equal(t, p.Creator(), tc.creator)
58 uassert.False(t, p.CreatedAt().IsZero())
59 uassert.NotEqual(t, p.VotingRecord(), nil)
60 uassert.Empty(t, p.StatusReason())
61 uassert.True(t, p.VotingDeadline() == p.CreatedAt().Add(tc.definition.VotingPeriod()))
62 })
63 }
64}
65
66func TestProposalVoteChoices(t *testing.T) {
67 cases := []struct {
68 name string
69 definition commondao.ProposalDefinition
70 choices []commondao.VoteChoice
71 }{
72 {
73 name: "custom choices",
74 definition: testPropDef{voteChoices: []commondao.VoteChoice{"FOO", "BAR", "BAZ"}},
75 choices: []commondao.VoteChoice{
76 "BAR",
77 "BAZ",
78 "FOO",
79 },
80 },
81 {
82 name: "defaults because of empty custom choice list",
83 definition: testPropDef{voteChoices: []commondao.VoteChoice{}},
84 choices: []commondao.VoteChoice{
85 commondao.ChoiceAbstain,
86 commondao.ChoiceNo,
87 commondao.ChoiceYes,
88 },
89 },
90 {
91 name: "defaults because of single custom choice list",
92 definition: testPropDef{voteChoices: []commondao.VoteChoice{"FOO"}},
93 choices: []commondao.VoteChoice{
94 commondao.ChoiceAbstain,
95 commondao.ChoiceNo,
96 commondao.ChoiceYes,
97 },
98 },
99 }
100
101 for _, tc := range cases {
102 t.Run(tc.name, func(t *testing.T) {
103 p, _ := commondao.NewProposal(1, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", testPropDef{
104 voteChoices: tc.choices,
105 })
106
107 choices := p.VoteChoices()
108
109 urequire.Equal(t, len(choices), len(tc.choices), "expect vote choice count to match")
110 for i, c := range choices {
111 urequire.True(t, tc.choices[i] == c, "expect vote choice to match")
112 }
113 })
114 }
115}
116
117func TestIsQuorumReached(t *testing.T) {
118 cases := []struct {
119 name string
120 quorum float64
121 members []address
122 votes []commondao.Vote
123 fail bool
124 }{
125 {
126 name: "one third",
127 quorum: commondao.QuorumOneThird,
128 members: []address{
129 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
130 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
131 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
132 },
133 votes: []commondao.Vote{
134 {
135 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
136 Choice: commondao.ChoiceYes,
137 },
138 },
139 },
140 {
141 name: "one third no quorum",
142 quorum: commondao.QuorumOneThird,
143 members: []address{
144 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
145 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
146 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
147 },
148 fail: true,
149 },
150 {
151 name: "half",
152 quorum: commondao.QuorumHalf,
153 members: []address{
154 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
155 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
156 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
157 "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
158 },
159 votes: []commondao.Vote{
160 {
161 Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
162 Choice: commondao.ChoiceYes,
163 },
164 {
165 Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
166 Choice: commondao.ChoiceNo,
167 },
168 },
169 },
170 {
171 name: "half no quorum",
172 quorum: commondao.QuorumHalf,
173 members: []address{
174 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
175 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
176 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
177 "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
178 },
179 votes: []commondao.Vote{
180 {
181 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
182 Choice: commondao.ChoiceYes,
183 },
184 },
185 fail: true,
186 },
187 {
188 name: "two thirds",
189 quorum: commondao.QuorumTwoThirds,
190 members: []address{
191 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
192 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
193 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
194 },
195 votes: []commondao.Vote{
196 {
197 Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
198 Choice: commondao.ChoiceYes,
199 },
200 {
201 Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
202 Choice: commondao.ChoiceNo,
203 },
204 },
205 },
206 {
207 name: "two thirds no quorum",
208 quorum: commondao.QuorumTwoThirds,
209 members: []address{
210 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
211 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
212 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
213 },
214 votes: []commondao.Vote{
215 {
216 Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
217 Choice: commondao.ChoiceNo,
218 },
219 },
220 fail: true,
221 },
222 {
223 name: "three fourths",
224 quorum: commondao.QuorumThreeFourths,
225 members: []address{
226 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
227 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
228 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
229 "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
230 },
231 votes: []commondao.Vote{
232 {
233 Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
234 Choice: commondao.ChoiceYes,
235 },
236 {
237 Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
238 Choice: commondao.ChoiceNo,
239 },
240 {
241 Address: "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
242 Choice: commondao.ChoiceNo,
243 },
244 },
245 },
246 {
247 name: "three fourths no quorum",
248 quorum: commondao.QuorumThreeFourths,
249 members: []address{
250 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
251 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
252 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
253 "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
254 },
255 votes: []commondao.Vote{
256 {
257 Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
258 Choice: commondao.ChoiceYes,
259 },
260 },
261 fail: true,
262 },
263 {
264 name: "full",
265 quorum: commondao.QuorumFull,
266 members: []address{
267 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
268 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
269 },
270 votes: []commondao.Vote{
271 {
272 Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
273 Choice: commondao.ChoiceNo,
274 },
275 {
276 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
277 Choice: commondao.ChoiceNo,
278 },
279 },
280 },
281 {
282 name: "full no quorum",
283 quorum: commondao.QuorumFull,
284 members: []address{
285 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
286 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
287 },
288 votes: []commondao.Vote{
289 {
290 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
291 Choice: commondao.ChoiceNo,
292 },
293 },
294 fail: true,
295 },
296 {
297 name: "no quorum with empty vote",
298 quorum: commondao.QuorumHalf,
299 members: []address{
300 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
301 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
302 },
303 votes: []commondao.Vote{
304 {
305 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
306 Choice: commondao.ChoiceNone,
307 },
308 },
309 fail: true,
310 },
311 {
312 name: "no quorum with abstention",
313 quorum: commondao.QuorumHalf,
314 members: []address{
315 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
316 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
317 },
318 votes: []commondao.Vote{
319 {
320 Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
321 Choice: commondao.ChoiceAbstain,
322 },
323 },
324 fail: true,
325 },
326 {
327 name: "invalid quorum percentage",
328 quorum: -1,
329 fail: true,
330 },
331 }
332
333 for _, tc := range cases {
334 t.Run(tc.name, func(t *testing.T) {
335 members := commondao.NewMemberStorage()
336 for _, m := range tc.members {
337 members.Add(m)
338 }
339
340 var record commondao.VotingRecord
341 for _, v := range tc.votes {
342 record.AddVote(v)
343 }
344
345 success := commondao.IsQuorumReached(tc.quorum, record.Readonly(), commondao.NewMemberSet(members))
346
347 if tc.fail {
348 uassert.False(t, success, "expect quorum to fail")
349 } else {
350 uassert.True(t, success, "expect quorum to succeed")
351 }
352 })
353 }
354}
355
356func TestMustValidate(t *testing.T) {
357 uassert.NotPanics(t, func() {
358 commondao.MustValidate(testPropDef{})
359 }, "expect validation to succeed")
360
361 uassert.PanicsWithMessage(t, "validable proposal definition is nil", func() {
362 commondao.MustValidate(nil)
363 }, "expect validation to panic with nil definition")
364
365 uassert.PanicsWithMessage(t, "boom!", func() {
366 commondao.MustValidate(testPropDef{validationErr: errors.New("boom!")})
367 }, "expect validation to panic")
368}
369
370type testPropDef struct {
371 votingPeriod time.Duration
372 tallyResult bool
373 validationErr, tallyErr error
374 voteChoices []commondao.VoteChoice
375}
376
377func (testPropDef) Title() string { return "" }
378func (testPropDef) Body() string { return "" }
379func (d testPropDef) VotingPeriod() time.Duration { return d.votingPeriod }
380func (d testPropDef) Validate() error { return d.validationErr }
381
382func (d testPropDef) Tally(commondao.ReadonlyVotingRecord, commondao.MemberSet) (bool, error) {
383 return d.tallyResult, d.tallyErr
384}
385
386func (d testPropDef) CustomVoteChoices() []commondao.VoteChoice {
387 if len(d.voteChoices) > 0 {
388 return d.voteChoices
389 }
390 return []commondao.VoteChoice{commondao.ChoiceYes, commondao.ChoiceNo, commondao.ChoiceAbstain}
391}