Skip to content

Commit d91d837

Browse files
committed
Move Threshold and coexisting implementations into packages/utils
1 parent 5e4561f commit d91d837

File tree

10 files changed

+373
-360
lines changed

10 files changed

+373
-360
lines changed

contracts/cw3-fixed-multisig/src/contract.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ use cosmwasm_std::{
99

1010
use cw2::set_contract_version;
1111
use cw3::{
12-
ProposalListResponse, ProposalResponse, Status, ThresholdResponse, Vote, VoteInfo,
13-
VoteListResponse, VoteResponse, VoterDetail, VoterListResponse, VoterResponse,
12+
ProposalListResponse, ProposalResponse, Status, Vote, VoteInfo, VoteListResponse, VoteResponse,
13+
VoterDetail, VoterListResponse, VoterResponse,
1414
};
1515
use cw_storage_plus::Bound;
16-
use utils::Expiration;
16+
use utils::{Expiration, ThresholdResponse};
1717

1818
use crate::error::ContractError;
1919
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

contracts/cw3-flex-multisig/src/contract.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ use cosmwasm_std::{
99

1010
use cw2::set_contract_version;
1111
use cw3::{
12-
ProposalListResponse, ProposalResponse, Status, ThresholdResponse, Vote, VoteInfo,
13-
VoteListResponse, VoteResponse, VoterDetail, VoterListResponse, VoterResponse,
12+
ProposalListResponse, ProposalResponse, Status, Vote, VoteInfo, VoteListResponse, VoteResponse,
13+
VoterDetail, VoterListResponse, VoterResponse,
1414
};
1515
use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff};
1616
use cw_storage_plus::Bound;
17-
use utils::{maybe_addr, Expiration};
17+
use utils::{maybe_addr, Expiration, ThresholdResponse};
1818

1919
use crate::error::ContractError;
2020
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
@@ -431,10 +431,9 @@ mod tests {
431431
use cw4::{Cw4ExecuteMsg, Member};
432432
use cw4_group::helpers::Cw4GroupContract;
433433
use cw_multi_test::{next_block, App, AppBuilder, Contract, ContractWrapper, Executor};
434-
use utils::Duration;
434+
use utils::{Duration, Threshold};
435435

436436
use super::*;
437-
use crate::msg::Threshold;
438437

439438
const OWNER: &str = "admin0001";
440439
const VOTER1: &str = "voter0001";
@@ -625,7 +624,10 @@ mod tests {
625624
None,
626625
)
627626
.unwrap_err();
628-
assert_eq!(ContractError::InvalidThreshold {}, err.downcast().unwrap());
627+
assert_eq!(
628+
ContractError::Threshold(utils::ThresholdError::InvalidThreshold {}),
629+
err.downcast().unwrap()
630+
);
629631

630632
// Total weight less than required weight not allowed
631633
let instantiate_msg = InstantiateMsg {
@@ -643,7 +645,10 @@ mod tests {
643645
None,
644646
)
645647
.unwrap_err();
646-
assert_eq!(ContractError::UnreachableWeight {}, err.downcast().unwrap());
648+
assert_eq!(
649+
ContractError::Threshold(utils::ThresholdError::UnreachableWeight {}),
650+
err.downcast().unwrap()
651+
);
647652

648653
// All valid
649654
let instantiate_msg = InstantiateMsg {

contracts/cw3-flex-multisig/src/error.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
use cosmwasm_std::StdError;
2+
use utils::ThresholdError;
3+
24
use thiserror::Error;
35

46
#[derive(Error, Debug, PartialEq)]
57
pub enum ContractError {
68
#[error("{0}")]
79
Std(#[from] StdError),
810

9-
#[error("Invalid voting threshold percentage, must be in the 0.5-1.0 range")]
10-
InvalidThreshold {},
11-
12-
#[error("Required quorum threshold cannot be zero")]
13-
ZeroQuorumThreshold {},
14-
15-
#[error("Not possible to reach required quorum threshold")]
16-
UnreachableQuorumThreshold {},
17-
18-
#[error("Required weight cannot be zero")]
19-
ZeroWeight {},
20-
21-
#[error("Not possible to reach required (passing) weight")]
22-
UnreachableWeight {},
11+
#[error("{0}")]
12+
Threshold(#[from] ThresholdError),
2313

2414
#[error("Group contract invalid address '{addr}'")]
2515
InvalidGroup { addr: String },
Lines changed: 3 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use schemars::JsonSchema;
22
use serde::{Deserialize, Serialize};
33

4-
use crate::error::ContractError;
5-
use cosmwasm_std::{CosmosMsg, Decimal, Empty};
6-
use cw3::{ThresholdResponse, Vote};
4+
use cosmwasm_std::{CosmosMsg, Empty};
5+
use cw3::Vote;
76
use cw4::MemberChangedHookMsg;
8-
use utils::{Duration, Expiration};
7+
use utils::{Duration, Expiration, Threshold};
98

109
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
1110
pub struct InstantiateMsg {
@@ -15,102 +14,6 @@ pub struct InstantiateMsg {
1514
pub max_voting_period: Duration,
1615
}
1716

18-
/// This defines the different ways tallies can happen.
19-
///
20-
/// The total_weight used for calculating success as well as the weights of each
21-
/// individual voter used in tallying should be snapshotted at the beginning of
22-
/// the block at which the proposal starts (this is likely the responsibility of a
23-
/// correct cw4 implementation).
24-
/// See also `ThresholdResponse` in the cw3 spec.
25-
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
26-
#[serde(rename_all = "snake_case")]
27-
pub enum Threshold {
28-
/// Declares that a fixed weight of Yes votes is needed to pass.
29-
/// See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.
30-
AbsoluteCount { weight: u64 },
31-
32-
/// Declares a percentage of the total weight that must cast Yes votes in order for
33-
/// a proposal to pass.
34-
/// See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.
35-
AbsolutePercentage { percentage: Decimal },
36-
37-
/// Declares a `quorum` of the total votes that must participate in the election in order
38-
/// for the vote to be considered at all.
39-
/// See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.
40-
ThresholdQuorum { threshold: Decimal, quorum: Decimal },
41-
}
42-
43-
impl Threshold {
44-
/// returns error if this is an unreachable value,
45-
/// given a total weight of all members in the group
46-
pub fn validate(&self, total_weight: u64) -> Result<(), ContractError> {
47-
match self {
48-
Threshold::AbsoluteCount {
49-
weight: weight_needed,
50-
} => {
51-
if *weight_needed == 0 {
52-
Err(ContractError::ZeroWeight {})
53-
} else if *weight_needed > total_weight {
54-
Err(ContractError::UnreachableWeight {})
55-
} else {
56-
Ok(())
57-
}
58-
}
59-
Threshold::AbsolutePercentage {
60-
percentage: percentage_needed,
61-
} => valid_threshold(percentage_needed),
62-
Threshold::ThresholdQuorum {
63-
threshold,
64-
quorum: quroum,
65-
} => {
66-
valid_threshold(threshold)?;
67-
valid_quorum(quroum)
68-
}
69-
}
70-
}
71-
72-
/// Creates a response from the saved data, just missing the total_weight info
73-
pub fn to_response(&self, total_weight: u64) -> ThresholdResponse {
74-
match self.clone() {
75-
Threshold::AbsoluteCount { weight } => ThresholdResponse::AbsoluteCount {
76-
weight,
77-
total_weight,
78-
},
79-
Threshold::AbsolutePercentage { percentage } => ThresholdResponse::AbsolutePercentage {
80-
percentage,
81-
total_weight,
82-
},
83-
Threshold::ThresholdQuorum { threshold, quorum } => {
84-
ThresholdResponse::ThresholdQuorum {
85-
threshold,
86-
quorum,
87-
total_weight,
88-
}
89-
}
90-
}
91-
}
92-
}
93-
94-
/// Asserts that the 0.5 < percent <= 1.0
95-
fn valid_threshold(percent: &Decimal) -> Result<(), ContractError> {
96-
if *percent > Decimal::percent(100) || *percent < Decimal::percent(50) {
97-
Err(ContractError::InvalidThreshold {})
98-
} else {
99-
Ok(())
100-
}
101-
}
102-
103-
/// Asserts that the 0.5 < percent <= 1.0
104-
fn valid_quorum(percent: &Decimal) -> Result<(), ContractError> {
105-
if percent.is_zero() {
106-
Err(ContractError::ZeroQuorumThreshold {})
107-
} else if *percent > Decimal::one() {
108-
Err(ContractError::UnreachableQuorumThreshold {})
109-
} else {
110-
Ok(())
111-
}
112-
}
113-
11417
// TODO: add some T variants? Maybe good enough as fixed Empty for now
11518
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
11619
#[serde(rename_all = "snake_case")]
@@ -170,153 +73,3 @@ pub enum QueryMsg {
17073
limit: Option<u32>,
17174
},
17275
}
173-
174-
#[cfg(test)]
175-
mod tests {
176-
use super::*;
177-
178-
#[test]
179-
fn validate_quorum_percentage() {
180-
// TODO: test the error messages
181-
182-
// 0 is never a valid percentage
183-
let err = valid_quorum(&Decimal::zero()).unwrap_err();
184-
assert_eq!(
185-
err.to_string(),
186-
ContractError::ZeroQuorumThreshold {}.to_string()
187-
);
188-
189-
// 100% is
190-
valid_quorum(&Decimal::one()).unwrap();
191-
192-
// 101% is not
193-
let err = valid_quorum(&Decimal::percent(101)).unwrap_err();
194-
assert_eq!(
195-
err.to_string(),
196-
ContractError::UnreachableQuorumThreshold {}.to_string()
197-
);
198-
// not 100.1%
199-
let err = valid_quorum(&Decimal::permille(1001)).unwrap_err();
200-
assert_eq!(
201-
err.to_string(),
202-
ContractError::UnreachableQuorumThreshold {}.to_string()
203-
);
204-
}
205-
206-
#[test]
207-
fn validate_threshold_percentage() {
208-
// other values in between 0.5 and 1 are valid
209-
valid_threshold(&Decimal::percent(51)).unwrap();
210-
valid_threshold(&Decimal::percent(67)).unwrap();
211-
valid_threshold(&Decimal::percent(99)).unwrap();
212-
let err = valid_threshold(&Decimal::percent(101)).unwrap_err();
213-
assert_eq!(
214-
err.to_string(),
215-
ContractError::InvalidThreshold {}.to_string()
216-
);
217-
}
218-
219-
#[test]
220-
fn validate_threshold() {
221-
// absolute count ensures 0 < required <= total_weight
222-
let err = Threshold::AbsoluteCount { weight: 0 }
223-
.validate(5)
224-
.unwrap_err();
225-
// TODO: remove to_string() when PartialEq implemented
226-
assert_eq!(err.to_string(), ContractError::ZeroWeight {}.to_string());
227-
let err = Threshold::AbsoluteCount { weight: 6 }
228-
.validate(5)
229-
.unwrap_err();
230-
assert_eq!(
231-
err.to_string(),
232-
ContractError::UnreachableWeight {}.to_string()
233-
);
234-
235-
Threshold::AbsoluteCount { weight: 1 }.validate(5).unwrap();
236-
Threshold::AbsoluteCount { weight: 5 }.validate(5).unwrap();
237-
238-
// AbsolutePercentage just enforces valid_percentage (tested above)
239-
let err = Threshold::AbsolutePercentage {
240-
percentage: Decimal::zero(),
241-
}
242-
.validate(5)
243-
.unwrap_err();
244-
assert_eq!(
245-
err.to_string(),
246-
ContractError::InvalidThreshold {}.to_string()
247-
);
248-
Threshold::AbsolutePercentage {
249-
percentage: Decimal::percent(51),
250-
}
251-
.validate(5)
252-
.unwrap();
253-
254-
// Quorum enforces both valid just enforces valid_percentage (tested above)
255-
Threshold::ThresholdQuorum {
256-
threshold: Decimal::percent(51),
257-
quorum: Decimal::percent(40),
258-
}
259-
.validate(5)
260-
.unwrap();
261-
let err = Threshold::ThresholdQuorum {
262-
threshold: Decimal::percent(101),
263-
quorum: Decimal::percent(40),
264-
}
265-
.validate(5)
266-
.unwrap_err();
267-
assert_eq!(
268-
err.to_string(),
269-
ContractError::InvalidThreshold {}.to_string()
270-
);
271-
let err = Threshold::ThresholdQuorum {
272-
threshold: Decimal::percent(51),
273-
quorum: Decimal::percent(0),
274-
}
275-
.validate(5)
276-
.unwrap_err();
277-
assert_eq!(
278-
err.to_string(),
279-
ContractError::ZeroQuorumThreshold {}.to_string()
280-
);
281-
}
282-
283-
#[test]
284-
fn threshold_response() {
285-
let total_weight: u64 = 100;
286-
287-
let res = Threshold::AbsoluteCount { weight: 42 }.to_response(total_weight);
288-
assert_eq!(
289-
res,
290-
ThresholdResponse::AbsoluteCount {
291-
weight: 42,
292-
total_weight
293-
}
294-
);
295-
296-
let res = Threshold::AbsolutePercentage {
297-
percentage: Decimal::percent(51),
298-
}
299-
.to_response(total_weight);
300-
assert_eq!(
301-
res,
302-
ThresholdResponse::AbsolutePercentage {
303-
percentage: Decimal::percent(51),
304-
total_weight
305-
}
306-
);
307-
308-
let res = Threshold::ThresholdQuorum {
309-
threshold: Decimal::percent(66),
310-
quorum: Decimal::percent(50),
311-
}
312-
.to_response(total_weight);
313-
assert_eq!(
314-
res,
315-
ThresholdResponse::ThresholdQuorum {
316-
threshold: Decimal::percent(66),
317-
quorum: Decimal::percent(50),
318-
total_weight
319-
}
320-
);
321-
}
322-
}

contracts/cw3-flex-multisig/src/state.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storag
66
use cw3::{Status, Vote};
77
use cw4::Cw4Contract;
88
use cw_storage_plus::{Item, Map};
9-
use utils::{Duration, Expiration};
10-
11-
use crate::msg::Threshold;
9+
use utils::{Duration, Expiration, Threshold};
1210

1311
// we multiply by this when calculating needed_votes in order to round up properly
1412
// Note: `10u128.pow(9)` fails as "u128::pow` is not yet stable as a const fn"

packages/cw3/examples/schema.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use std::fs::create_dir_all;
44
use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
55

66
use cw3::{
7-
Cw3ExecuteMsg, Cw3QueryMsg, ProposalListResponse, ProposalResponse, ThresholdResponse,
8-
VoteListResponse, VoteResponse, VoterDetail, VoterListResponse, VoterResponse,
7+
Cw3ExecuteMsg, Cw3QueryMsg, ProposalListResponse, ProposalResponse, VoteListResponse,
8+
VoteResponse, VoterDetail, VoterListResponse, VoterResponse,
99
};
10+
use utils::ThresholdResponse;
1011

1112
fn main() {
1213
let mut out_dir = current_dir().unwrap();

0 commit comments

Comments
 (0)