dao_voting/
multiple_choice.rs1use cosmwasm_schema::cw_serde;
2use cosmwasm_std::{CosmosMsg, Empty, StdError, StdResult, Uint128};
3
4use crate::threshold::{validate_quorum, PercentageThreshold, ThresholdError};
5
6pub const MAX_NUM_CHOICES: u32 = 20;
9const NONE_OPTION_DESCRIPTION: &str = "None of the above";
10
11#[cw_serde]
13pub enum VotingStrategy {
14 SingleChoice { quorum: PercentageThreshold },
15}
16
17impl VotingStrategy {
18 pub fn validate(&self) -> Result<(), ThresholdError> {
19 match self {
20 VotingStrategy::SingleChoice { quorum } => validate_quorum(quorum),
21 }
22 }
23
24 pub fn get_quorum(&self) -> PercentageThreshold {
25 match self {
26 VotingStrategy::SingleChoice { quorum } => *quorum,
27 }
28 }
29}
30
31#[cw_serde]
33#[derive(Copy)]
34pub struct MultipleChoiceVote {
35 pub option_id: u32,
37}
38
39impl std::fmt::Display for MultipleChoiceVote {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}", self.option_id)
42 }
43}
44
45#[cw_serde]
47pub struct MultipleChoiceVotes {
48 pub vote_weights: Vec<Uint128>,
51}
52
53impl MultipleChoiceVotes {
54 pub fn total(&self) -> Uint128 {
56 self.vote_weights.iter().sum()
57 }
58
59 pub fn add_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> {
61 self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize]
62 .checked_add(weight)
63 .map_err(StdError::overflow)?;
64 Ok(())
65 }
66
67 pub fn remove_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> {
69 self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize]
70 .checked_sub(weight)
71 .map_err(StdError::overflow)?;
72 Ok(())
73 }
74
75 pub fn zero(num_choices: usize) -> Self {
77 Self {
78 vote_weights: vec![Uint128::zero(); num_choices],
79 }
80 }
81}
82
83#[cw_serde]
86pub enum MultipleChoiceOptionType {
87 None,
90 Standard,
91}
92
93#[cw_serde]
95pub struct MultipleChoiceOptions {
96 pub options: Vec<MultipleChoiceOption>,
97}
98
99#[cw_serde]
101pub struct MultipleChoiceOption {
102 pub title: String,
103 pub description: String,
104 pub msgs: Vec<CosmosMsg<Empty>>,
105}
106
107#[cw_serde]
110pub struct CheckedMultipleChoiceOptions {
111 pub options: Vec<CheckedMultipleChoiceOption>,
112}
113
114#[cw_serde]
116pub struct CheckedMultipleChoiceOption {
117 pub index: u32,
120 pub option_type: MultipleChoiceOptionType,
121 pub title: String,
122 pub description: String,
123 pub msgs: Vec<CosmosMsg<Empty>>,
124 pub vote_count: Uint128,
125}
126
127impl MultipleChoiceOptions {
128 pub fn into_checked(self) -> StdResult<CheckedMultipleChoiceOptions> {
129 if self.options.len() < 2 || self.options.len() > MAX_NUM_CHOICES as usize {
130 return Err(StdError::GenericErr {
131 msg: "Wrong number of choices".to_string(),
132 });
133 }
134
135 let mut checked_options: Vec<CheckedMultipleChoiceOption> =
136 Vec::with_capacity(self.options.len() + 1);
137
138 self.options
140 .into_iter()
141 .enumerate()
142 .for_each(|(idx, choice)| {
143 let checked_option = CheckedMultipleChoiceOption {
144 index: idx as u32,
145 option_type: MultipleChoiceOptionType::Standard,
146 description: choice.description,
147 msgs: choice.msgs,
148 vote_count: Uint128::zero(),
149 title: choice.title,
150 };
151 checked_options.push(checked_option)
152 });
153
154 let none_option = CheckedMultipleChoiceOption {
156 index: (checked_options.capacity() - 1) as u32,
157 option_type: MultipleChoiceOptionType::None,
158 description: NONE_OPTION_DESCRIPTION.to_string(),
159 msgs: vec![],
160 vote_count: Uint128::zero(),
161 title: NONE_OPTION_DESCRIPTION.to_string(),
162 };
163
164 checked_options.push(none_option);
165
166 let options = CheckedMultipleChoiceOptions {
167 options: checked_options,
168 };
169 Ok(options)
170 }
171}
172
173#[cw_serde]
174pub struct MultipleChoiceAutoVote {
175 pub vote: MultipleChoiceVote,
177 pub rationale: Option<String>,
181}
182
183#[cfg(test)]
184mod test {
185 use std::vec;
186
187 use super::*;
188
189 #[test]
190 fn test_display_multiple_choice_vote() {
191 let vote = MultipleChoiceVote { option_id: 0 };
192 assert_eq!("0", vote.to_string())
193 }
194
195 #[test]
196 fn test_multiple_choice_votes() {
197 let mut votes = MultipleChoiceVotes {
198 vote_weights: vec![Uint128::new(10), Uint128::new(100)],
199 };
200 let total = votes.total();
201 assert_eq!(total, Uint128::new(110));
202
203 votes
204 .add_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(10))
205 .unwrap();
206 let total = votes.total();
207 assert_eq!(total, Uint128::new(120));
208
209 votes
210 .remove_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(20))
211 .unwrap();
212 votes
213 .remove_vote(MultipleChoiceVote { option_id: 1 }, Uint128::new(100))
214 .unwrap();
215
216 assert_eq!(votes, MultipleChoiceVotes::zero(2))
217 }
218
219 #[test]
220 fn test_into_checked() {
221 let options = vec![
222 super::MultipleChoiceOption {
223 description: "multiple choice option 1".to_string(),
224 msgs: vec![],
225 title: "title".to_string(),
226 },
227 super::MultipleChoiceOption {
228 description: "multiple choice option 2".to_string(),
229 msgs: vec![],
230 title: "title".to_string(),
231 },
232 ];
233
234 let mc_options = super::MultipleChoiceOptions { options };
235
236 let checked_mc_options = mc_options.into_checked().unwrap();
237 assert_eq!(checked_mc_options.options.len(), 3);
238 assert_eq!(
239 checked_mc_options.options[0].option_type,
240 super::MultipleChoiceOptionType::Standard
241 );
242 assert_eq!(
243 checked_mc_options.options[0].description,
244 "multiple choice option 1",
245 );
246 assert_eq!(
247 checked_mc_options.options[1].option_type,
248 super::MultipleChoiceOptionType::Standard
249 );
250 assert_eq!(
251 checked_mc_options.options[1].description,
252 "multiple choice option 2",
253 );
254 assert_eq!(
255 checked_mc_options.options[2].option_type,
256 super::MultipleChoiceOptionType::None
257 );
258 assert_eq!(
259 checked_mc_options.options[2].description,
260 super::NONE_OPTION_DESCRIPTION,
261 );
262 }
263
264 #[should_panic(expected = "Wrong number of choices")]
265 #[test]
266 fn test_into_checked_wrong_num_choices() {
267 let options = vec![super::MultipleChoiceOption {
268 description: "multiple choice option 1".to_string(),
269 msgs: vec![],
270 title: "title".to_string(),
271 }];
272
273 let mc_options = super::MultipleChoiceOptions { options };
274 mc_options.into_checked().unwrap();
275 }
276}