1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
use cosmwasm_std::{Decimal, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ThresholdError {
#[error("Required threshold cannot be zero")]
ZeroThreshold {},
#[error("Not possible to reach required (passing) threshold")]
UnreachableThreshold {},
}
/// A percentage of voting power that must vote yes for a proposal to
/// pass. An example of why this is needed:
///
/// If a user specifies a 60% passing threshold, and there are 10
/// voters they likely expect that proposal to pass when there are 6
/// yes votes. This implies that the condition for passing should be
/// `yes_votes >= total_votes * threshold`.
///
/// With this in mind, how should a user specify that they would like
/// proposals to pass if the majority of voters choose yes? Selecting
/// a 50% passing threshold with those rules doesn't properly cover
/// that case as 5 voters voting yes out of 10 would pass the
/// proposal. Selecting 50.0001% or or some variation of that also
/// does not work as a very small yes vote which technically makes the
/// majority yes may not reach that threshold.
///
/// To handle these cases we provide both a majority and percent
/// option for all percentages. If majority is selected passing will
/// be determined by `yes > total_votes * 0.5`. If percent is selected
/// passing is determined by `yes >= total_votes * percent`.
///
/// In both of these cases a proposal with only abstain votes must
/// fail. This requires a special case passing logic.
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PercentageThreshold {
/// The majority of voters must vote yes for the proposal to pass.
Majority {},
/// A percentage of voting power >= percent must vote yes for the
/// proposal to pass.
Percent(Decimal),
}
/// The ways a proposal may reach its passing / failing threshold.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Threshold {
/// Declares a percentage of the total weight that must cast Yes
/// votes in order for a proposal to pass. See
/// `ThresholdResponse::AbsolutePercentage` in the cw3 spec for
/// details.
AbsolutePercentage { percentage: PercentageThreshold },
/// Declares a `quorum` of the total votes that must participate
/// in the election in order for the vote to be considered at all.
/// See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for
/// details.
ThresholdQuorum {
threshold: PercentageThreshold,
quorum: PercentageThreshold,
},
/// An absolute number of votes needed for something to cross the
/// threshold. Useful for multisig style voting.
AbsoluteCount { threshold: Uint128 },
}
/// Asserts that the 0.0 < percent <= 1.0
fn validate_percentage(percent: &PercentageThreshold) -> Result<(), ThresholdError> {
if let PercentageThreshold::Percent(percent) = percent {
if percent.is_zero() {
Err(ThresholdError::ZeroThreshold {})
} else if *percent > Decimal::one() {
Err(ThresholdError::UnreachableThreshold {})
} else {
Ok(())
}
} else {
Ok(())
}
}
/// Asserts that a quorum <= 1. Quorums may be zero.
fn validate_quorum(quorum: &PercentageThreshold) -> Result<(), ThresholdError> {
if let PercentageThreshold::Percent(quorum) = quorum {
if *quorum > Decimal::one() {
Err(ThresholdError::UnreachableThreshold {})
} else {
Ok(())
}
} else {
Ok(())
}
}
impl Threshold {
/// returns error if this is an unreachable value,
/// given a total weight of all members in the group
pub fn validate(&self) -> Result<(), ThresholdError> {
match self {
Threshold::AbsolutePercentage {
percentage: percentage_needed,
} => validate_percentage(percentage_needed),
Threshold::ThresholdQuorum { threshold, quorum } => {
validate_percentage(threshold)?;
validate_quorum(quorum)
}
Threshold::AbsoluteCount { threshold } => {
if threshold.is_zero() {
Err(ThresholdError::ZeroThreshold {})
} else {
Ok(())
}
}
}
}
}