1use super::*;
21
22use alloc::{collections::btree_map::BTreeMap, vec::Vec};
23use assert_matches::assert_matches;
24use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelist_account};
25use frame_support::{
26 dispatch::RawOrigin,
27 traits::{
28 fungible,
29 tokens::{Fortitude::Polite, Preservation::Expendable},
30 Currency, Get,
31 },
32};
33use sp_runtime::traits::Bounded;
34
35use crate::Pallet as ConvictionVoting;
36
37const SEED: u32 = 0;
38
39fn fill_voting<T: Config<I>, I: 'static>(
42) -> (ClassOf<T, I>, BTreeMap<ClassOf<T, I>, Vec<IndexOf<T, I>>>) {
43 let mut r = BTreeMap::<ClassOf<T, I>, Vec<IndexOf<T, I>>>::new();
44 for class in T::Polls::classes().into_iter() {
45 for _ in 0..T::MaxVotes::get() {
46 match T::Polls::create_ongoing(class.clone()) {
47 Ok(i) => r.entry(class.clone()).or_default().push(i),
48 Err(()) => break,
49 }
50 }
51 }
52 let c = r.iter().max_by_key(|(_, v)| v.len()).unwrap().0.clone();
53 (c, r)
54}
55
56fn funded_account<T: Config<I>, I: 'static>(name: &'static str, index: u32) -> T::AccountId {
57 let caller: T::AccountId = account(name, index, SEED);
58 T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
59 caller
60}
61
62fn account_vote<T: Config<I>, I: 'static>(b: BalanceOf<T, I>) -> AccountVote<BalanceOf<T, I>> {
63 let v = Vote { aye: true, conviction: Conviction::Locked1x };
64
65 AccountVote::Standard { vote: v, balance: b }
66}
67
68benchmarks_instance_pallet! {
69 where_clause { where T::MaxVotes: core::fmt::Debug }
70
71 vote_new {
72 let caller = funded_account::<T, I>("caller", 0);
73 whitelist_account!(caller);
74 let account_vote = account_vote::<T, I>(100u32.into());
75
76 T::VotingHooks::on_vote_worst_case(&caller);
77
78 let (class, all_polls) = fill_voting::<T, I>();
79 let polls = &all_polls[&class];
80 let r = polls.len() - 1;
81 for i in polls.iter().skip(1) {
83 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote)?;
84 }
85 let votes = match VotingFor::<T, I>::get(&caller, &class) {
86 Voting::Casting(Casting { votes, .. }) => votes,
87 _ => return Err("Votes are not direct".into()),
88 };
89 assert_eq!(votes.len(), r as usize, "Votes were not recorded.");
90
91 let index = polls[0];
92 }: vote(RawOrigin::Signed(caller.clone()), index, account_vote)
93 verify {
94 assert_matches!(
95 VotingFor::<T, I>::get(&caller, &class),
96 Voting::Casting(Casting { votes, .. }) if votes.len() == (r + 1) as usize
97 );
98 }
99
100 vote_existing {
101 let caller = funded_account::<T, I>("caller", 0);
102 whitelist_account!(caller);
103 let old_account_vote = account_vote::<T, I>(100u32.into());
104
105 T::VotingHooks::on_vote_worst_case(&caller);
106
107 let (class, all_polls) = fill_voting::<T, I>();
108 let polls = &all_polls[&class];
109 let r = polls.len();
110 for i in polls.iter() {
112 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?;
113 }
114 let votes = match VotingFor::<T, I>::get(&caller, &class) {
115 Voting::Casting(Casting { votes, .. }) => votes,
116 _ => return Err("Votes are not direct".into()),
117 };
118 assert_eq!(votes.len(), r, "Votes were not recorded.");
119
120 let new_account_vote = account_vote::<T, I>(200u32.into());
121 let index = polls[0];
122 }: vote(RawOrigin::Signed(caller.clone()), index, new_account_vote)
123 verify {
124 assert_matches!(
125 VotingFor::<T, I>::get(&caller, &class),
126 Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
127 );
128 }
129
130 remove_vote {
131 let caller = funded_account::<T, I>("caller", 0);
132 whitelist_account!(caller);
133 let old_account_vote = account_vote::<T, I>(100u32.into());
134
135 T::VotingHooks::on_vote_worst_case(&caller);
136
137 let (class, all_polls) = fill_voting::<T, I>();
138 let polls = &all_polls[&class];
139 let r = polls.len();
140 for i in polls.iter() {
142 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?;
143 }
144 let votes = match VotingFor::<T, I>::get(&caller, &class) {
145 Voting::Casting(Casting { votes, .. }) => votes,
146 _ => return Err("Votes are not direct".into()),
147 };
148 assert_eq!(votes.len(), r, "Votes were not recorded.");
149
150 let index = polls[0];
151 }: _(RawOrigin::Signed(caller.clone()), Some(class.clone()), index)
152 verify {
153 assert_matches!(
154 VotingFor::<T, I>::get(&caller, &class),
155 Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize
156 );
157 }
158
159 remove_other_vote {
160 let caller = funded_account::<T, I>("caller", 0);
161 let voter = funded_account::<T, I>("caller", 0);
162 let voter_lookup = T::Lookup::unlookup(voter.clone());
163 whitelist_account!(caller);
164 let old_account_vote = account_vote::<T, I>(100u32.into());
165
166 T::VotingHooks::on_vote_worst_case(&caller);
167
168 let (class, all_polls) = fill_voting::<T, I>();
169 let polls = &all_polls[&class];
170 let r = polls.len();
171 for i in polls.iter() {
173 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote)?;
174 }
175 let votes = match VotingFor::<T, I>::get(&caller, &class) {
176 Voting::Casting(Casting { votes, .. }) => votes,
177 _ => return Err("Votes are not direct".into()),
178 };
179 assert_eq!(votes.len(), r, "Votes were not recorded.");
180
181 let index = polls[0];
182 assert!(T::Polls::end_ongoing(index, false).is_ok());
183 }: _(RawOrigin::Signed(caller.clone()), voter_lookup, class.clone(), index)
184 verify {
185 assert_matches!(
186 VotingFor::<T, I>::get(&voter, &class),
187 Voting::Casting(Casting { votes, .. }) if votes.len() == (r - 1) as usize
188 );
189 }
190
191 delegate {
192 let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1);
193
194 let all_polls = fill_voting::<T, I>().1;
195 let class = T::Polls::max_ongoing().0;
196 let polls = &all_polls[&class];
197 let voter = funded_account::<T, I>("voter", 0);
198 let voter_lookup = T::Lookup::unlookup(voter.clone());
199 let caller = funded_account::<T, I>("caller", 0);
200 whitelist_account!(caller);
201
202 let delegated_balance: BalanceOf<T, I> = 1000u32.into();
203 let delegate_vote = account_vote::<T, I>(delegated_balance);
204
205 for i in polls.iter().take(r as usize) {
207 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?;
208 }
209 assert_matches!(
210 VotingFor::<T, I>::get(&voter, &class),
211 Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
212 );
213
214 }: _(RawOrigin::Signed(caller.clone()), class.clone(), voter_lookup, Conviction::Locked1x, delegated_balance)
215 verify {
216 assert_matches!(VotingFor::<T, I>::get(&caller, &class), Voting::Delegating(_));
217 }
218
219 undelegate {
220 let r in 0 .. T::MaxVotes::get().min(T::Polls::max_ongoing().1);
221
222 let all_polls = fill_voting::<T, I>().1;
223 let class = T::Polls::max_ongoing().0;
224 let polls = &all_polls[&class];
225 let voter = funded_account::<T, I>("voter", 0);
226 let voter_lookup = T::Lookup::unlookup(voter.clone());
227 let caller = funded_account::<T, I>("caller", 0);
228 whitelist_account!(caller);
229
230 let delegated_balance: BalanceOf<T, I> = 1000u32.into();
231 let delegate_vote = account_vote::<T, I>(delegated_balance);
232
233 ConvictionVoting::<T, I>::delegate(
234 RawOrigin::Signed(caller.clone()).into(),
235 class.clone(),
236 voter_lookup,
237 Conviction::Locked1x,
238 delegated_balance,
239 )?;
240
241 for i in polls.iter().take(r as usize) {
243 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?;
244 }
245 assert_matches!(
246 VotingFor::<T, I>::get(&voter, &class),
247 Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize
248 );
249 assert_matches!(VotingFor::<T, I>::get(&caller, &class), Voting::Delegating(_));
250 }: _(RawOrigin::Signed(caller.clone()), class.clone())
251 verify {
252 assert_matches!(VotingFor::<T, I>::get(&caller, &class), Voting::Casting(_));
253 }
254
255 unlock {
256 let caller = funded_account::<T, I>("caller", 0);
257 let caller_lookup = T::Lookup::unlookup(caller.clone());
258 whitelist_account!(caller);
259 let normal_account_vote = account_vote::<T, I>(T::Currency::free_balance(&caller) - 100u32.into());
260 let big_account_vote = account_vote::<T, I>(T::Currency::free_balance(&caller));
261
262 let (class, all_polls) = fill_voting::<T, I>();
264 assert!(all_polls.len() > 0);
265 for (class, polls) in all_polls.iter() {
266 assert!(polls.len() > 0);
267 for i in polls.iter() {
268 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote)?;
269 }
270 }
271
272 let orig_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
273 let polls = &all_polls[&class];
274
275 ConvictionVoting::<T, I>::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?;
278 let now_usable = <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite);
279 assert_eq!(orig_usable - now_usable, 100u32.into());
280
281 ConvictionVoting::<T, I>::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?;
283
284 }: _(RawOrigin::Signed(caller.clone()), class, caller_lookup)
286 verify {
287 assert_eq!(orig_usable, <T::Currency as fungible::Inspect<T::AccountId>>::reducible_balance(&caller, Expendable, Polite));
288 }
289
290 impl_benchmark_test_suite!(
291 ConvictionVoting,
292 crate::tests::new_test_ext(),
293 crate::tests::Test
294 );
295}