use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::error::ContractError;
use cosmwasm_std::{CosmosMsg, Decimal, Empty, HumanAddr};
use cw0::{Duration, Expiration};
use cw3::{ThresholdResponse, Vote};
use cw4::MemberChangedHookMsg;
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct InstantiateMsg {
pub group_addr: HumanAddr,
pub threshold: Threshold,
pub max_voting_period: Duration,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Threshold {
AbsoluteCount { weight: u64 },
AbsolutePercentage { percentage: Decimal },
ThresholdQuorum { threshold: Decimal, quorum: Decimal },
}
impl Threshold {
pub fn validate(&self, total_weight: u64) -> Result<(), ContractError> {
match self {
Threshold::AbsoluteCount {
weight: weight_needed,
} => {
if *weight_needed == 0 {
Err(ContractError::ZeroThreshold {})
} else if *weight_needed > total_weight {
Err(ContractError::UnreachableThreshold {})
} else {
Ok(())
}
}
Threshold::AbsolutePercentage {
percentage: percentage_needed,
} => valid_percentage(percentage_needed),
Threshold::ThresholdQuorum {
threshold,
quorum: quroum,
} => {
valid_percentage(threshold)?;
valid_percentage(quroum)
}
}
}
pub fn to_response(&self, total_weight: u64) -> ThresholdResponse {
match self.clone() {
Threshold::AbsoluteCount { weight } => ThresholdResponse::AbsoluteCount {
weight,
total_weight,
},
Threshold::AbsolutePercentage { percentage } => ThresholdResponse::AbsolutePercentage {
percentage,
total_weight,
},
Threshold::ThresholdQuorum { threshold, quorum } => {
ThresholdResponse::ThresholdQuorum {
threshold,
quorum,
total_weight,
}
}
}
}
}
fn valid_percentage(percent: &Decimal) -> Result<(), ContractError> {
if percent.is_zero() {
Err(ContractError::ZeroThreshold {})
} else if *percent > Decimal::one() {
Err(ContractError::UnreachableThreshold {})
} else {
Ok(())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Propose {
title: String,
description: String,
msgs: Vec<CosmosMsg<Empty>>,
latest: Option<Expiration>,
},
Vote {
proposal_id: u64,
vote: Vote,
},
Execute {
proposal_id: u64,
},
Close {
proposal_id: u64,
},
MemberChangedHook(MemberChangedHookMsg),
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Threshold {},
Proposal { proposal_id: u64 },
ListProposals {
start_after: Option<u64>,
limit: Option<u32>,
},
ReverseProposals {
start_before: Option<u64>,
limit: Option<u32>,
},
Vote { proposal_id: u64, voter: HumanAddr },
ListVotes {
proposal_id: u64,
start_after: Option<HumanAddr>,
limit: Option<u32>,
},
Voter { address: HumanAddr },
ListVoters {
start_after: Option<HumanAddr>,
limit: Option<u32>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_percentage() {
let err = valid_percentage(&Decimal::zero()).unwrap_err();
assert_eq!(err.to_string(), ContractError::ZeroThreshold {}.to_string());
valid_percentage(&Decimal::one()).unwrap();
let err = valid_percentage(&Decimal::percent(101)).unwrap_err();
assert_eq!(
err.to_string(),
ContractError::UnreachableThreshold {}.to_string()
);
let err = valid_percentage(&Decimal::permille(1001)).unwrap_err();
assert_eq!(
err.to_string(),
ContractError::UnreachableThreshold {}.to_string()
);
valid_percentage(&Decimal::permille(1)).unwrap();
valid_percentage(&Decimal::percent(17)).unwrap();
valid_percentage(&Decimal::percent(99)).unwrap();
}
#[test]
fn validate_threshold() {
let err = Threshold::AbsoluteCount { weight: 0 }
.validate(5)
.unwrap_err();
assert_eq!(err.to_string(), ContractError::ZeroThreshold {}.to_string());
let err = Threshold::AbsoluteCount { weight: 6 }
.validate(5)
.unwrap_err();
assert_eq!(
err.to_string(),
ContractError::UnreachableThreshold {}.to_string()
);
Threshold::AbsoluteCount { weight: 1 }.validate(5).unwrap();
Threshold::AbsoluteCount { weight: 5 }.validate(5).unwrap();
let err = Threshold::AbsolutePercentage {
percentage: Decimal::zero(),
}
.validate(5)
.unwrap_err();
assert_eq!(err.to_string(), ContractError::ZeroThreshold {}.to_string());
Threshold::AbsolutePercentage {
percentage: Decimal::percent(51),
}
.validate(5)
.unwrap();
Threshold::ThresholdQuorum {
threshold: Decimal::percent(51),
quorum: Decimal::percent(40),
}
.validate(5)
.unwrap();
let err = Threshold::ThresholdQuorum {
threshold: Decimal::percent(101),
quorum: Decimal::percent(40),
}
.validate(5)
.unwrap_err();
assert_eq!(
err.to_string(),
ContractError::UnreachableThreshold {}.to_string()
);
let err = Threshold::ThresholdQuorum {
threshold: Decimal::percent(51),
quorum: Decimal::percent(0),
}
.validate(5)
.unwrap_err();
assert_eq!(err.to_string(), ContractError::ZeroThreshold {}.to_string());
}
#[test]
fn threshold_response() {
let total_weight: u64 = 100;
let res = Threshold::AbsoluteCount { weight: 42 }.to_response(total_weight);
assert_eq!(
res,
ThresholdResponse::AbsoluteCount {
weight: 42,
total_weight
}
);
let res = Threshold::AbsolutePercentage {
percentage: Decimal::percent(51),
}
.to_response(total_weight);
assert_eq!(
res,
ThresholdResponse::AbsolutePercentage {
percentage: Decimal::percent(51),
total_weight
}
);
let res = Threshold::ThresholdQuorum {
threshold: Decimal::percent(66),
quorum: Decimal::percent(50),
}
.to_response(total_weight);
assert_eq!(
res,
ThresholdResponse::ThresholdQuorum {
threshold: Decimal::percent(66),
quorum: Decimal::percent(50),
total_weight
}
);
}
}