Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}