1use cosmwasm_std::{Decimal, Uint128};
2use dao_voting::status::Status;
3use dao_voting::threshold::{PercentageThreshold, Threshold};
4use dao_voting::voting::Vote;
5use rand::{prelude::SliceRandom, Rng};
6
7pub enum ShouldExecute {
10 Yes,
12 No,
14 Meh,
16}
17
18pub struct TestSingleChoiceVote {
19 pub voter: String,
21 pub position: Vote,
23 pub weight: Uint128,
25 pub should_execute: ShouldExecute,
27}
28
29pub fn test_simple_votes<F>(do_votes: F)
30where
31 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
32{
33 do_votes(
34 vec![TestSingleChoiceVote {
35 voter: "ekez".to_string(),
36 position: Vote::Yes,
37 weight: Uint128::new(10),
38 should_execute: ShouldExecute::Yes,
39 }],
40 Threshold::AbsolutePercentage {
41 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
42 },
43 Status::Passed,
44 None,
45 );
46
47 do_votes(
48 vec![TestSingleChoiceVote {
49 voter: "ekez".to_string(),
50 position: Vote::No,
51 weight: Uint128::new(10),
52 should_execute: ShouldExecute::Yes,
53 }],
54 Threshold::AbsolutePercentage {
55 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
56 },
57 Status::Rejected,
58 None,
59 )
60}
61
62pub fn test_simple_vote_no_overflow<F>(do_votes: F)
63where
64 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
65{
66 do_votes(
67 vec![TestSingleChoiceVote {
68 voter: "ekez".to_string(),
69 position: Vote::Yes,
70 weight: Uint128::new(u128::MAX),
71 should_execute: ShouldExecute::Yes,
72 }],
73 Threshold::AbsolutePercentage {
74 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
75 },
76 Status::Passed,
77 None,
78 );
79}
80
81pub fn test_vote_no_overflow<F>(do_votes: F)
82where
83 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
84{
85 do_votes(
86 vec![TestSingleChoiceVote {
87 voter: "ekez".to_string(),
88 position: Vote::Yes,
89 weight: Uint128::new(u128::MAX),
90 should_execute: ShouldExecute::Yes,
91 }],
92 Threshold::AbsolutePercentage {
93 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
94 },
95 Status::Passed,
96 None,
97 );
98
99 do_votes(
100 vec![
101 TestSingleChoiceVote {
102 voter: "zeke".to_string(),
103 position: Vote::No,
104 weight: Uint128::new(1),
105 should_execute: ShouldExecute::Yes,
106 },
107 TestSingleChoiceVote {
108 voter: "ekez".to_string(),
109 position: Vote::Yes,
110 weight: Uint128::new(u128::MAX - 1),
111 should_execute: ShouldExecute::Yes,
112 },
113 ],
114 Threshold::AbsolutePercentage {
115 percentage: PercentageThreshold::Percent(Decimal::percent(99)),
116 },
117 Status::Passed,
118 None,
119 )
120}
121
122pub fn test_simple_early_rejection<F>(do_votes: F)
123where
124 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
125{
126 do_votes(
127 vec![TestSingleChoiceVote {
128 voter: "zeke".to_string(),
129 position: Vote::No,
130 weight: Uint128::new(1),
131 should_execute: ShouldExecute::Yes,
132 }],
133 Threshold::AbsolutePercentage {
134 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
135 },
136 Status::Rejected,
137 None,
138 );
139
140 do_votes(
141 vec![TestSingleChoiceVote {
142 voter: "ekez".to_string(),
143 position: Vote::No,
144 weight: Uint128::new(1),
145 should_execute: ShouldExecute::Yes,
146 }],
147 Threshold::AbsolutePercentage {
148 percentage: PercentageThreshold::Percent(Decimal::percent(99)),
149 },
150 Status::Open,
151 Some(Uint128::from(u128::MAX)),
152 );
153}
154
155pub fn test_vote_abstain_only<F>(do_votes: F)
156where
157 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
158{
159 do_votes(
160 vec![TestSingleChoiceVote {
161 voter: "ekez".to_string(),
162 position: Vote::Abstain,
163 weight: Uint128::new(u64::MAX.into()),
164 should_execute: ShouldExecute::Yes,
165 }],
166 Threshold::AbsolutePercentage {
167 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
168 },
169 Status::Rejected,
170 None,
171 );
172
173 for i in 0..101 {
176 do_votes(
177 vec![TestSingleChoiceVote {
178 voter: "ekez".to_string(),
179 position: Vote::Abstain,
180 weight: Uint128::new(u64::MAX.into()),
181 should_execute: ShouldExecute::Yes,
182 }],
183 Threshold::ThresholdQuorum {
184 threshold: PercentageThreshold::Percent(Decimal::percent(100)),
185 quorum: PercentageThreshold::Percent(Decimal::percent(i)),
186 },
187 Status::Rejected,
188 None,
189 );
190 }
191}
192
193pub fn test_tricky_rounding<F>(do_votes: F)
194where
195 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
196{
197 do_votes(
202 vec![TestSingleChoiceVote {
203 voter: "ekez".to_string(),
204 position: Vote::Yes,
205 weight: Uint128::new(1),
206 should_execute: ShouldExecute::Yes,
207 }],
208 Threshold::AbsolutePercentage {
209 percentage: PercentageThreshold::Percent(Decimal::percent(1)),
210 },
211 Status::Passed,
212 Some(Uint128::new(100)),
213 );
214
215 do_votes(
216 vec![TestSingleChoiceVote {
217 voter: "ekez".to_string(),
218 position: Vote::Yes,
219 weight: Uint128::new(10),
220 should_execute: ShouldExecute::Yes,
221 }],
222 Threshold::AbsolutePercentage {
223 percentage: PercentageThreshold::Percent(Decimal::percent(1)),
224 },
225 Status::Passed,
226 Some(Uint128::new(1000)),
227 );
228
229 do_votes(
231 vec![TestSingleChoiceVote {
232 voter: "ekez".to_string(),
233 position: Vote::Yes,
234 weight: Uint128::new(9999999),
235 should_execute: ShouldExecute::Yes,
236 }],
237 Threshold::AbsolutePercentage {
238 percentage: PercentageThreshold::Percent(Decimal::percent(1)),
239 },
240 Status::Open,
241 Some(Uint128::new(1000000000)),
242 );
243
244 do_votes(
245 vec![TestSingleChoiceVote {
246 voter: "ekez".to_string(),
247 position: Vote::Abstain,
248 weight: Uint128::new(1),
249 should_execute: ShouldExecute::Yes,
250 }],
251 Threshold::AbsolutePercentage {
252 percentage: PercentageThreshold::Percent(Decimal::percent(1)),
253 },
254 Status::Rejected,
255 None,
256 );
257}
258
259pub fn test_no_double_votes<F>(do_votes: F)
260where
261 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
262{
263 do_votes(
264 vec![
265 TestSingleChoiceVote {
266 voter: "ekez".to_string(),
267 position: Vote::Abstain,
268 weight: Uint128::new(2),
269 should_execute: ShouldExecute::Yes,
270 },
271 TestSingleChoiceVote {
272 voter: "ekez".to_string(),
273 position: Vote::Yes,
274 weight: Uint128::new(2),
275 should_execute: ShouldExecute::No,
276 },
277 ],
278 Threshold::AbsolutePercentage {
279 percentage: PercentageThreshold::Percent(Decimal::percent(100)),
280 },
281 Status::Open,
289 Some(Uint128::new(10)),
290 )
291}
292
293pub fn test_votes_favor_yes<F>(do_votes: F)
294where
295 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
296{
297 do_votes(
298 vec![
299 TestSingleChoiceVote {
300 voter: "ekez".to_string(),
301 position: Vote::Abstain,
302 weight: Uint128::new(10),
303 should_execute: ShouldExecute::Yes,
304 },
305 TestSingleChoiceVote {
306 voter: "keze".to_string(),
307 position: Vote::No,
308 weight: Uint128::new(5),
309 should_execute: ShouldExecute::Yes,
310 },
311 TestSingleChoiceVote {
312 voter: "ezek".to_string(),
313 position: Vote::Yes,
314 weight: Uint128::new(5),
315 should_execute: ShouldExecute::Yes,
316 },
317 ],
318 Threshold::AbsolutePercentage {
319 percentage: PercentageThreshold::Percent(Decimal::percent(50)),
320 },
321 Status::Passed,
322 None,
323 );
324
325 do_votes(
326 vec![
327 TestSingleChoiceVote {
328 voter: "ekez".to_string(),
329 position: Vote::Abstain,
330 weight: Uint128::new(10),
331 should_execute: ShouldExecute::Yes,
332 },
333 TestSingleChoiceVote {
334 voter: "keze".to_string(),
335 position: Vote::Yes,
336 weight: Uint128::new(5),
337 should_execute: ShouldExecute::Yes,
338 },
339 ],
340 Threshold::AbsolutePercentage {
341 percentage: PercentageThreshold::Percent(Decimal::percent(50)),
342 },
343 Status::Passed,
344 None,
345 );
346
347 do_votes(
348 vec![
349 TestSingleChoiceVote {
350 voter: "ekez".to_string(),
351 position: Vote::Abstain,
352 weight: Uint128::new(10),
353 should_execute: ShouldExecute::Yes,
354 },
355 TestSingleChoiceVote {
356 voter: "keze".to_string(),
357 position: Vote::Yes,
358 weight: Uint128::new(5),
359 should_execute: ShouldExecute::Yes,
360 },
361 TestSingleChoiceVote {
363 voter: "ezek".to_string(),
364 position: Vote::No,
365 weight: Uint128::new(5),
366 should_execute: ShouldExecute::Yes,
367 },
368 ],
369 Threshold::AbsolutePercentage {
370 percentage: PercentageThreshold::Percent(Decimal::percent(50)),
371 },
372 Status::Passed,
373 None,
374 );
375}
376
377pub fn test_votes_low_threshold<F>(do_votes: F)
378where
379 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
380{
381 do_votes(
382 vec![
383 TestSingleChoiceVote {
384 voter: "ekez".to_string(),
385 position: Vote::No,
386 weight: Uint128::new(10),
387 should_execute: ShouldExecute::Yes,
388 },
389 TestSingleChoiceVote {
390 voter: "keze".to_string(),
391 position: Vote::Yes,
392 weight: Uint128::new(5),
393 should_execute: ShouldExecute::Yes,
394 },
395 ],
396 Threshold::ThresholdQuorum {
397 threshold: PercentageThreshold::Percent(Decimal::percent(10)),
398 quorum: PercentageThreshold::Majority {},
399 },
400 Status::Passed,
401 None,
402 );
403
404 do_votes(
405 vec![
406 TestSingleChoiceVote {
407 voter: "ekez".to_string(),
408 position: Vote::No,
409 weight: Uint128::new(10),
410 should_execute: ShouldExecute::Yes,
411 },
412 TestSingleChoiceVote {
413 voter: "keze".to_string(),
414 position: Vote::Yes,
415 weight: Uint128::new(5),
416 should_execute: ShouldExecute::Yes,
417 },
418 TestSingleChoiceVote {
420 voter: "ezek".to_string(),
421 position: Vote::No,
422 weight: Uint128::new(10),
423 should_execute: ShouldExecute::Yes,
424 },
425 ],
426 Threshold::ThresholdQuorum {
427 threshold: PercentageThreshold::Percent(Decimal::percent(10)),
428 quorum: PercentageThreshold::Majority {},
429 },
430 Status::Passed,
431 None,
432 );
433}
434
435pub fn test_majority_vs_half<F>(do_votes: F)
436where
437 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
438{
439 do_votes(
440 vec![
441 TestSingleChoiceVote {
442 voter: "ekez".to_string(),
443 position: Vote::No,
444 weight: Uint128::new(10),
445 should_execute: ShouldExecute::Yes,
446 },
447 TestSingleChoiceVote {
448 voter: "keze".to_string(),
449 position: Vote::Yes,
450 weight: Uint128::new(10),
451 should_execute: ShouldExecute::Yes,
452 },
453 ],
454 Threshold::ThresholdQuorum {
455 threshold: PercentageThreshold::Percent(Decimal::percent(50)),
456 quorum: PercentageThreshold::Majority {},
457 },
458 Status::Passed,
459 None,
460 );
461
462 do_votes(
463 vec![
464 TestSingleChoiceVote {
465 voter: "ekez".to_string(),
466 position: Vote::No,
467 weight: Uint128::new(10),
468 should_execute: ShouldExecute::Yes,
469 },
470 TestSingleChoiceVote {
472 voter: "keze".to_string(),
473 position: Vote::Yes,
474 weight: Uint128::new(10),
475 should_execute: ShouldExecute::Yes,
476 },
477 ],
478 Threshold::ThresholdQuorum {
479 threshold: PercentageThreshold::Majority {},
480 quorum: PercentageThreshold::Majority {},
481 },
482 Status::Rejected,
483 None,
484 );
485}
486
487pub fn test_pass_threshold_not_quorum<F>(do_votes: F)
488where
489 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
490{
491 do_votes(
492 vec![TestSingleChoiceVote {
493 voter: "ekez".to_string(),
494 position: Vote::Yes,
495 weight: Uint128::new(59),
496 should_execute: ShouldExecute::Yes,
497 }],
498 Threshold::ThresholdQuorum {
499 threshold: PercentageThreshold::Majority {},
500 quorum: PercentageThreshold::Percent(Decimal::percent(60)),
501 },
502 Status::Open,
503 Some(Uint128::new(100)),
504 );
505 do_votes(
506 vec![TestSingleChoiceVote {
507 voter: "ekez".to_string(),
508 position: Vote::No,
509 weight: Uint128::new(59),
510 should_execute: ShouldExecute::Yes,
511 }],
512 Threshold::ThresholdQuorum {
513 threshold: PercentageThreshold::Majority {},
514 quorum: PercentageThreshold::Percent(Decimal::percent(60)),
515 },
516 Status::Rejected,
519 Some(Uint128::new(100)),
520 );
521}
522
523pub fn test_pass_exactly_quorum<F>(do_votes: F)
524where
525 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
526{
527 do_votes(
528 vec![TestSingleChoiceVote {
529 voter: "ekez".to_string(),
530 position: Vote::Yes,
531 weight: Uint128::new(60),
532 should_execute: ShouldExecute::Yes,
533 }],
534 Threshold::ThresholdQuorum {
535 threshold: PercentageThreshold::Majority {},
536 quorum: PercentageThreshold::Percent(Decimal::percent(60)),
537 },
538 Status::Passed,
539 Some(Uint128::new(100)),
540 );
541 do_votes(
542 vec![
543 TestSingleChoiceVote {
544 voter: "ekez".to_string(),
545 position: Vote::Yes,
546 weight: Uint128::new(59),
547 should_execute: ShouldExecute::Yes,
548 },
549 TestSingleChoiceVote {
557 voter: "keze".to_string(),
558 position: Vote::No,
559 weight: Uint128::new(1),
560 should_execute: ShouldExecute::Yes,
561 },
562 ],
563 Threshold::ThresholdQuorum {
564 threshold: PercentageThreshold::Majority {},
565 quorum: PercentageThreshold::Percent(Decimal::percent(60)),
566 },
567 Status::Passed,
568 Some(Uint128::new(100)),
569 );
570 do_votes(
571 vec![TestSingleChoiceVote {
572 voter: "ekez".to_string(),
573 position: Vote::No,
574 weight: Uint128::new(60),
575 should_execute: ShouldExecute::Yes,
576 }],
577 Threshold::ThresholdQuorum {
578 threshold: PercentageThreshold::Majority {},
579 quorum: PercentageThreshold::Percent(Decimal::percent(60)),
580 },
581 Status::Rejected,
582 Some(Uint128::new(100)),
583 );
584}
585
586pub fn fuzz_voting<F>(do_votes: F)
587where
588 F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
589{
590 let mut rng = rand::thread_rng();
591 let dist = rand::distributions::Uniform::<u64>::new(1, 200);
592 for _ in 0..10 {
593 let yes: Vec<u64> = (0..50).map(|_| rng.sample(dist)).collect();
594 let no: Vec<u64> = (0..50).map(|_| rng.sample(dist)).collect();
595
596 let yes_sum: u64 = yes.iter().sum();
597 let no_sum: u64 = no.iter().sum();
598 let expected_status = match yes_sum.cmp(&no_sum) {
599 std::cmp::Ordering::Less => Status::Rejected,
600 std::cmp::Ordering::Equal => Status::Rejected,
602 std::cmp::Ordering::Greater => Status::Passed,
603 };
604
605 let yes = yes
606 .into_iter()
607 .enumerate()
608 .map(|(idx, weight)| TestSingleChoiceVote {
609 voter: format!("yes_{idx}"),
610 position: Vote::Yes,
611 weight: Uint128::new(weight as u128),
612 should_execute: ShouldExecute::Meh,
613 });
614 let no = no
615 .into_iter()
616 .enumerate()
617 .map(|(idx, weight)| TestSingleChoiceVote {
618 voter: format!("no_{idx}"),
619 position: Vote::No,
620 weight: Uint128::new(weight as u128),
621 should_execute: ShouldExecute::Meh,
622 });
623 let mut votes = yes.chain(no).collect::<Vec<_>>();
624 votes.shuffle(&mut rng);
625
626 do_votes(
627 votes,
628 Threshold::AbsolutePercentage {
629 percentage: PercentageThreshold::Majority {},
630 },
631 expected_status,
632 None,
633 );
634 }
635}