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