use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Decimal, StdError};
use thiserror::Error;
#[cw_serde]
pub enum Threshold {
AbsoluteCount { weight: u64 },
AbsolutePercentage { percentage: Decimal },
ThresholdQuorum { threshold: Decimal, quorum: Decimal },
}
impl Threshold {
pub fn validate(&self, total_weight: u64) -> Result<(), ThresholdError> {
match self {
Threshold::AbsoluteCount {
weight: weight_needed,
} => {
if *weight_needed == 0 {
Err(ThresholdError::ZeroWeight {})
} else if *weight_needed > total_weight {
Err(ThresholdError::UnreachableWeight {})
} else {
Ok(())
}
}
Threshold::AbsolutePercentage {
percentage: percentage_needed,
} => valid_threshold(percentage_needed),
Threshold::ThresholdQuorum {
threshold,
quorum: quroum,
} => {
valid_threshold(threshold)?;
valid_quorum(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_threshold(percent: &Decimal) -> Result<(), ThresholdError> {
if *percent > Decimal::percent(100) || *percent < Decimal::percent(50) {
Err(ThresholdError::InvalidThreshold {})
} else {
Ok(())
}
}
fn valid_quorum(percent: &Decimal) -> Result<(), ThresholdError> {
if percent.is_zero() {
Err(ThresholdError::ZeroQuorumThreshold {})
} else if *percent > Decimal::one() {
Err(ThresholdError::UnreachableQuorumThreshold {})
} else {
Ok(())
}
}
#[cw_serde]
pub enum ThresholdResponse {
AbsoluteCount { weight: u64, total_weight: u64 },
AbsolutePercentage {
percentage: Decimal,
total_weight: u64,
},
ThresholdQuorum {
threshold: Decimal,
quorum: Decimal,
total_weight: u64,
},
}
#[derive(Error, Debug)]
pub enum ThresholdError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Invalid voting threshold percentage, must be in the 0.5-1.0 range")]
InvalidThreshold {},
#[error("Required quorum threshold cannot be zero")]
ZeroQuorumThreshold {},
#[error("Not possible to reach required quorum threshold")]
UnreachableQuorumThreshold {},
#[error("Required weight cannot be zero")]
ZeroWeight {},
#[error("Not possible to reach required (passing) weight")]
UnreachableWeight {},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_quorum_percentage() {
let err = valid_quorum(&Decimal::zero()).unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::ZeroQuorumThreshold {}.to_string()
);
valid_quorum(&Decimal::one()).unwrap();
let err = valid_quorum(&Decimal::percent(101)).unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::UnreachableQuorumThreshold {}.to_string()
);
let err = valid_quorum(&Decimal::permille(1001)).unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::UnreachableQuorumThreshold {}.to_string()
);
}
#[test]
fn validate_threshold_percentage() {
valid_threshold(&Decimal::percent(51)).unwrap();
valid_threshold(&Decimal::percent(67)).unwrap();
valid_threshold(&Decimal::percent(99)).unwrap();
let err = valid_threshold(&Decimal::percent(101)).unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::InvalidThreshold {}.to_string()
);
}
#[test]
fn validate_threshold() {
let err = Threshold::AbsoluteCount { weight: 0 }
.validate(5)
.unwrap_err();
assert_eq!(err.to_string(), ThresholdError::ZeroWeight {}.to_string());
let err = Threshold::AbsoluteCount { weight: 6 }
.validate(5)
.unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::UnreachableWeight {}.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(),
ThresholdError::InvalidThreshold {}.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(),
ThresholdError::InvalidThreshold {}.to_string()
);
let err = Threshold::ThresholdQuorum {
threshold: Decimal::percent(51),
quorum: Decimal::percent(0),
}
.validate(5)
.unwrap_err();
assert_eq!(
err.to_string(),
ThresholdError::ZeroQuorumThreshold {}.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
}
);
}
}