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(())
                }
            }
        }
    }
}