referrerpolicy=no-referrer-when-downgrade

pallet_conviction_voting/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! ConvictionVoting pallet benchmarking.
19
20use 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
39/// Fill all classes as much as possible up to `MaxVotes` and return the Class with the most votes
40/// ongoing.
41fn 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		// We need to create existing votes
82		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		// We need to create existing votes
111		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		// We need to create existing votes
141		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		// We need to create existing votes
172		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		// We need to create existing delegations
206		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		// We need to create delegations
242		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		// Fill everything up to the max by filling all classes with votes and voting on them all.
263		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		// Vote big on the class with the most ongoing votes of them to bump the lock and make it
276		// hard to recompute when removed.
277		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		// Remove the vote
282		ConvictionVoting::<T, I>::remove_vote(RawOrigin::Signed(caller.clone()).into(), Some(class.clone()), polls[0])?;
283
284		// We can now unlock on `class` from 200 to 100...
285	}: _(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}