use cosmwasm_std::{Decimal, StdError};
use tg3::{Status, Vote};
use crate::multitest::suite::{get_proposal_id, SuiteBuilder};
use crate::state::{RulesBuilder, Votes};
use crate::ContractError;
#[test]
fn proposal_creator_votes_automatically() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_rules(rules)
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes, Votes::yes(1));
}
#[test]
fn simple_vote() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_rules(rules)
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("bob", proposal_id, Vote::Yes).unwrap();
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
}
#[test]
fn proposal_creator_cannot_vote() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_rules(rules)
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
let err = suite.vote("alice", proposal_id, Vote::Yes).unwrap_err();
assert_eq!(ContractError::AlreadyVoted {}, err.downcast().unwrap());
}
#[test]
fn cannot_double_vote() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_rules(rules)
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("bob", proposal_id, Vote::Yes).unwrap();
let err = suite.vote("bob", proposal_id, Vote::Yes).unwrap_err();
assert_eq!(ContractError::AlreadyVoted {}, err.downcast().unwrap());
}
#[test]
fn non_voters_cannot_vote() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_rules(rules)
.build();
let proposal_creation_height = suite.app.block_info().height;
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
let err = suite.vote("some guy", proposal_id, Vote::Yes).unwrap_err();
assert_eq!(
ContractError::Std(StdError::GenericErr {
msg: format!(
"Unauthorized: wasn't member of a group at block height: {}",
proposal_creation_height
)
}),
err.downcast().unwrap()
);
}
#[test]
fn members_with_no_voting_power_cannot_vote() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 0)
.with_rules(rules)
.build();
let proposal_creation_height = suite.app.block_info().height;
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
let err = suite.vote("carol", proposal_id, Vote::Yes).unwrap_err();
assert_eq!(
ContractError::Std(StdError::GenericErr {
msg: format!(
"Unauthorized: member didn't have voting power at block height: {}",
proposal_creation_height
)
}),
err.downcast().unwrap()
);
}
#[test]
fn veto_counts_as_no() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(50))
.with_quorum(Decimal::percent(10))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_rules(rules.clone())
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("bob", proposal_id, Vote::Veto).unwrap();
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
assert_eq!(prop.status, Status::Open);
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
assert_eq!(prop.status, Status::Rejected);
}
#[test]
fn proposal_rejected_when_quorum_not_reached() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.with_quorum(Decimal::percent(40))
.with_allow_early(false)
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_member("dave", 4)
.with_rules(rules.clone())
.build();
let response = suite.propose("bob", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("alice", proposal_id, Vote::No).unwrap();
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
assert_eq!(prop.status, Status::Rejected);
}
#[test]
fn abstaining_can_help_reach_quorum() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.with_quorum(Decimal::percent(40))
.with_allow_early(false)
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_member("dave", 4)
.with_rules(rules.clone())
.build();
let response = suite.propose("bob", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("alice", proposal_id, Vote::No).unwrap();
suite.vote("carol", proposal_id, Vote::Abstain).unwrap();
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 6);
assert_eq!(prop.status, Status::Passed);
}
#[test]
fn abstaining_does_not_count_as_yes() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.with_quorum(Decimal::percent(40))
.with_allow_early(false)
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_member("dave", 4)
.with_rules(rules.clone())
.build();
let response = suite.propose("bob", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("carol", proposal_id, Vote::No).unwrap();
suite.vote("dave", proposal_id, Vote::Abstain).unwrap();
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 9);
assert_eq!(prop.status, Status::Rejected);
}
#[test]
fn proposal_can_be_rejected_after_voting_period() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(50))
.with_quorum(Decimal::percent(10))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_member("carol", 3)
.with_rules(rules.clone())
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.vote("bob", proposal_id, Vote::No).unwrap();
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
assert_eq!(prop.status, Status::Open);
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 3);
assert_eq!(prop.status, Status::Rejected);
}
#[test]
fn passing_a_proposal_after_voting_period_works() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(50))
.with_quorum(Decimal::percent(20))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 4)
.with_member("bob", 6)
.with_rules(rules.clone())
.build();
let response = suite.propose("alice", "proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 4);
assert_eq!(prop.status, Status::Open);
suite.app.advance_seconds(rules.voting_period_secs());
let prop = suite.query_proposal(proposal_id).unwrap();
assert_eq!(prop.votes.total(), 4);
assert_eq!(prop.status, Status::Passed);
}
#[test]
fn expired_proposals_cannot_be_voted_on() {
let rules = RulesBuilder::new()
.with_threshold(Decimal::percent(51))
.with_quorum(Decimal::percent(35))
.build();
let mut suite = SuiteBuilder::new()
.with_member("alice", 1)
.with_member("bob", 2)
.with_rules(rules.clone())
.build();
let response = suite.propose("alice", "cool proposal", "proposal").unwrap();
let proposal_id: u64 = get_proposal_id(&response).unwrap();
suite.app.advance_seconds(rules.voting_period_secs());
let err = suite.vote("bob", proposal_id, Vote::Yes).unwrap_err();
assert_eq!(ContractError::NotOpen {}, err.downcast().unwrap());
}