use cosmwasm_std::{Decimal, Uint128};
use dao_voting::status::Status;
use dao_voting::threshold::{PercentageThreshold, Threshold};
use dao_voting::voting::Vote;
use rand::{prelude::SliceRandom, Rng};
pub enum ShouldExecute {
Yes,
No,
Meh,
}
pub struct TestSingleChoiceVote {
pub voter: String,
pub position: Vote,
pub weight: Uint128,
pub should_execute: ShouldExecute,
}
pub fn test_simple_votes<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Passed,
None,
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Rejected,
None,
)
}
pub fn test_simple_vote_no_overflow<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(u128::max_value()),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Passed,
None,
);
}
pub fn test_vote_no_overflow<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(u128::max_value()),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Passed,
None,
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "zeke".to_string(),
position: Vote::No,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(u128::max_value() - 1),
should_execute: ShouldExecute::Yes,
},
],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(99)),
},
Status::Passed,
None,
)
}
pub fn test_simple_early_rejection<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "zeke".to_string(),
position: Vote::No,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Rejected,
None,
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(99)),
},
Status::Open,
Some(Uint128::from(u128::max_value())),
);
}
pub fn test_vote_abstain_only<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(u64::max_value().into()),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Rejected,
None,
);
for i in 0..101 {
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(u64::max_value().into()),
should_execute: ShouldExecute::Yes,
}],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Percent(Decimal::percent(100)),
quorum: PercentageThreshold::Percent(Decimal::percent(i)),
},
Status::Rejected,
None,
);
}
}
pub fn test_tricky_rounding<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(1)),
},
Status::Passed,
Some(Uint128::new(100)),
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(1)),
},
Status::Passed,
Some(Uint128::new(1000)),
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(9999999),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(1)),
},
Status::Open,
Some(Uint128::new(1000000000)),
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
}],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(1)),
},
Status::Rejected,
None,
);
}
pub fn test_no_double_votes<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(2),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(2),
should_execute: ShouldExecute::No,
},
],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(100)),
},
Status::Open,
Some(Uint128::new(10)),
)
}
pub fn test_votes_favor_yes<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::No,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "ezek".to_string(),
position: Vote::Yes,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(50)),
},
Status::Passed,
None,
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(50)),
},
Status::Passed,
None,
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Abstain,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "ezek".to_string(),
position: Vote::No,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
],
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Percent(Decimal::percent(50)),
},
Status::Passed,
None,
);
}
pub fn test_votes_low_threshold<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Percent(Decimal::percent(10)),
quorum: PercentageThreshold::Majority {},
},
Status::Passed,
None,
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(5),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "ezek".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Percent(Decimal::percent(10)),
quorum: PercentageThreshold::Majority {},
},
Status::Passed,
None,
);
}
pub fn test_majority_vs_half<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Percent(Decimal::percent(50)),
quorum: PercentageThreshold::Majority {},
},
Status::Passed,
None,
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::Yes,
weight: Uint128::new(10),
should_execute: ShouldExecute::Yes,
},
],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Majority {},
},
Status::Rejected,
None,
);
}
pub fn test_pass_threshold_not_quorum<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(59),
should_execute: ShouldExecute::Yes,
}],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Percent(Decimal::percent(60)),
},
Status::Open,
Some(Uint128::new(100)),
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(59),
should_execute: ShouldExecute::Yes,
}],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Percent(Decimal::percent(60)),
},
Status::Rejected,
Some(Uint128::new(100)),
);
}
pub fn test_pass_exactly_quorum<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(60),
should_execute: ShouldExecute::Yes,
}],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Percent(Decimal::percent(60)),
},
Status::Passed,
Some(Uint128::new(100)),
);
do_votes(
vec![
TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::Yes,
weight: Uint128::new(59),
should_execute: ShouldExecute::Yes,
},
TestSingleChoiceVote {
voter: "keze".to_string(),
position: Vote::No,
weight: Uint128::new(1),
should_execute: ShouldExecute::Yes,
},
],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Percent(Decimal::percent(60)),
},
Status::Passed,
Some(Uint128::new(100)),
);
do_votes(
vec![TestSingleChoiceVote {
voter: "ekez".to_string(),
position: Vote::No,
weight: Uint128::new(60),
should_execute: ShouldExecute::Yes,
}],
Threshold::ThresholdQuorum {
threshold: PercentageThreshold::Majority {},
quorum: PercentageThreshold::Percent(Decimal::percent(60)),
},
Status::Rejected,
Some(Uint128::new(100)),
);
}
pub fn fuzz_voting<F>(do_votes: F)
where
F: Fn(Vec<TestSingleChoiceVote>, Threshold, Status, Option<Uint128>),
{
let mut rng = rand::thread_rng();
let dist = rand::distributions::Uniform::<u64>::new(1, 200);
for _ in 0..10 {
let yes: Vec<u64> = (0..50).map(|_| rng.sample(dist)).collect();
let no: Vec<u64> = (0..50).map(|_| rng.sample(dist)).collect();
let yes_sum: u64 = yes.iter().sum();
let no_sum: u64 = no.iter().sum();
let expected_status = match yes_sum.cmp(&no_sum) {
std::cmp::Ordering::Less => Status::Rejected,
std::cmp::Ordering::Equal => Status::Rejected,
std::cmp::Ordering::Greater => Status::Passed,
};
let yes = yes
.into_iter()
.enumerate()
.map(|(idx, weight)| TestSingleChoiceVote {
voter: format!("yes_{idx}"),
position: Vote::Yes,
weight: Uint128::new(weight as u128),
should_execute: ShouldExecute::Meh,
});
let no = no
.into_iter()
.enumerate()
.map(|(idx, weight)| TestSingleChoiceVote {
voter: format!("no_{idx}"),
position: Vote::No,
weight: Uint128::new(weight as u128),
should_execute: ShouldExecute::Meh,
});
let mut votes = yes.chain(no).collect::<Vec<_>>();
votes.shuffle(&mut rng);
do_votes(
votes,
Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Majority {},
},
expected_status,
None,
);
}
}