referrerpolicy=no-referrer-when-downgrade

pallet_conviction_voting/
lib.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//! # Voting Pallet
19//!
20//! - [`Config`]
21//! - [`Call`]
22//!
23//! ## Overview
24//!
25//! Pallet for managing actual voting in polls.
26
27#![recursion_limit = "256"]
28#![cfg_attr(not(feature = "std"), no_std)]
29
30extern crate alloc;
31
32use frame_support::{
33	dispatch::DispatchResult,
34	ensure,
35	traits::{
36		fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling,
37		ReservableCurrency, WithdrawReasons,
38	},
39};
40use sp_runtime::{
41	traits::{AtLeast32BitUnsigned, Saturating, StaticLookup, Zero},
42	ArithmeticError, DispatchError, Perbill,
43};
44
45mod conviction;
46mod traits;
47mod types;
48mod vote;
49pub mod weights;
50
51pub use self::{
52	conviction::Conviction,
53	pallet::*,
54	traits::{Status, VotingHooks},
55	types::{Delegations, Tally, UnvoteScope},
56	vote::{AccountVote, Casting, Delegating, Vote, Voting},
57	weights::WeightInfo,
58};
59use sp_runtime::traits::BlockNumberProvider;
60
61#[cfg(test)]
62mod tests;
63
64#[cfg(feature = "runtime-benchmarks")]
65pub mod benchmarking;
66
67const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot";
68
69pub type BlockNumberFor<T, I> =
70	<<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
71
72type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
73pub type BalanceOf<T, I = ()> =
74	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
75pub type VotingOf<T, I = ()> = Voting<
76	BalanceOf<T, I>,
77	<T as frame_system::Config>::AccountId,
78	BlockNumberFor<T, I>,
79	PollIndexOf<T, I>,
80	<T as Config<I>>::MaxVotes,
81>;
82#[allow(dead_code)]
83type DelegatingOf<T, I = ()> =
84	Delegating<BalanceOf<T, I>, <T as frame_system::Config>::AccountId, BlockNumberFor<T, I>>;
85pub type TallyOf<T, I = ()> = Tally<BalanceOf<T, I>, <T as Config<I>>::MaxTurnout>;
86pub type VotesOf<T, I = ()> = BalanceOf<T, I>;
87pub type PollIndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
88#[cfg(feature = "runtime-benchmarks")]
89pub type IndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
90pub type ClassOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Class;
91
92#[frame_support::pallet]
93pub mod pallet {
94	use super::*;
95	use frame_support::{
96		pallet_prelude::{
97			DispatchResultWithPostInfo, IsType, StorageDoubleMap, StorageMap, ValueQuery,
98		},
99		traits::ClassCountOf,
100		Twox64Concat,
101	};
102	use frame_system::pallet_prelude::{ensure_signed, OriginFor};
103	use sp_runtime::BoundedVec;
104
105	#[pallet::pallet]
106	pub struct Pallet<T, I = ()>(_);
107
108	#[pallet::config]
109	pub trait Config<I: 'static = ()>: frame_system::Config + Sized {
110		// System level stuff.
111		#[allow(deprecated)]
112		type RuntimeEvent: From<Event<Self, I>>
113			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
114		/// Weight information for extrinsics in this pallet.
115		type WeightInfo: WeightInfo;
116		/// Currency type with which voting happens.
117		type Currency: ReservableCurrency<Self::AccountId>
118			+ LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self, I>>
119			+ fungible::Inspect<Self::AccountId>;
120
121		/// The implementation of the logic which conducts polls.
122		type Polls: Polling<
123			TallyOf<Self, I>,
124			Votes = BalanceOf<Self, I>,
125			Moment = BlockNumberFor<Self, I>,
126		>;
127
128		/// The maximum amount of tokens which may be used for voting. May just be
129		/// `Currency::total_issuance`, but you might want to reduce this in order to account for
130		/// funds in the system which are unable to vote (e.g. parachain auction deposits).
131		type MaxTurnout: Get<BalanceOf<Self, I>>;
132
133		/// The maximum number of concurrent votes an account may have.
134		///
135		/// Also used to compute weight, an overly large value can lead to extrinsics with large
136		/// weight estimation: see `delegate` for instance.
137		#[pallet::constant]
138		type MaxVotes: Get<u32>;
139
140		/// The minimum period of vote locking.
141		///
142		/// It should be no shorter than enactment period to ensure that in the case of an approval,
143		/// those successful voters are locked into the consequences that their votes entail.
144		#[pallet::constant]
145		type VoteLockingPeriod: Get<BlockNumberFor<Self, I>>;
146		/// Provider for the block number. Normally this is the `frame_system` pallet.
147		type BlockNumberProvider: BlockNumberProvider;
148		/// Hooks are called when a new vote is registered or an existing vote is removed.
149		///
150		/// The trait does not expose weight information.
151		/// The weight of each hook is assumed to be benchmarked as part of the function that calls
152		/// it. Hooks should never recursively call into functions that called,
153		/// directly or indirectly, the function that called them.
154		/// This could lead to infinite recursion and stack overflow.
155		/// Note that this also means to not call into other generic functionality like batch or
156		/// similar. Also, anything that a hook did will be subject to the transactional semantics
157		/// of the calling function. This means that if the calling function fails, the hook will
158		/// be rolled back without further notice.
159		type VotingHooks: VotingHooks<Self::AccountId, PollIndexOf<Self, I>, BalanceOf<Self, I>>;
160	}
161
162	/// All voting for a particular voter in a particular voting class. We store the balance for the
163	/// number of votes that we have recorded.
164	#[pallet::storage]
165	pub type VotingFor<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
166		_,
167		Twox64Concat,
168		T::AccountId,
169		Twox64Concat,
170		ClassOf<T, I>,
171		VotingOf<T, I>,
172		ValueQuery,
173	>;
174
175	/// The voting classes which have a non-zero lock requirement and the lock amounts which they
176	/// require. The actual amount locked on behalf of this pallet should always be the maximum of
177	/// this list.
178	#[pallet::storage]
179	pub type ClassLocksFor<T: Config<I>, I: 'static = ()> = StorageMap<
180		_,
181		Twox64Concat,
182		T::AccountId,
183		BoundedVec<(ClassOf<T, I>, BalanceOf<T, I>), ClassCountOf<T::Polls, TallyOf<T, I>>>,
184		ValueQuery,
185	>;
186
187	#[pallet::event]
188	#[pallet::generate_deposit(pub(super) fn deposit_event)]
189	pub enum Event<T: Config<I>, I: 'static = ()> {
190		/// An account has delegated their vote to another account. \[who, target\]
191		Delegated(T::AccountId, T::AccountId, ClassOf<T, I>),
192		/// An \[account\] has cancelled a previous delegation operation.
193		Undelegated(T::AccountId, ClassOf<T, I>),
194		/// An account has voted
195		Voted {
196			who: T::AccountId,
197			vote: AccountVote<BalanceOf<T, I>>,
198			poll_index: PollIndexOf<T, I>,
199		},
200		/// A vote has been removed
201		VoteRemoved {
202			who: T::AccountId,
203			vote: AccountVote<BalanceOf<T, I>>,
204			poll_index: PollIndexOf<T, I>,
205		},
206		/// The lockup period of a conviction vote expired, and the funds have been unlocked.
207		VoteUnlocked { who: T::AccountId, class: ClassOf<T, I> },
208	}
209
210	#[pallet::error]
211	pub enum Error<T, I = ()> {
212		/// Poll is not ongoing.
213		NotOngoing,
214		/// The given account did not vote on the poll.
215		NotVoter,
216		/// The actor has no permission to conduct the action.
217		NoPermission,
218		/// The actor has no permission to conduct the action right now but will do in the future.
219		NoPermissionYet,
220		/// The account is already delegating.
221		AlreadyDelegating,
222		/// The account currently has votes attached to it and the operation cannot succeed until
223		/// these are removed through `remove_vote`.
224		AlreadyVoting,
225		/// Too high a balance was provided that the account cannot afford.
226		InsufficientFunds,
227		/// The account is not currently delegating.
228		NotDelegating,
229		/// Delegation to oneself makes no sense.
230		Nonsense,
231		/// Maximum number of votes reached.
232		MaxVotesReached,
233		/// The class must be supplied since it is not easily determinable from the state.
234		ClassNeeded,
235		/// The class ID supplied is invalid.
236		BadClass,
237	}
238
239	#[pallet::call]
240	impl<T: Config<I>, I: 'static> Pallet<T, I> {
241		/// Vote in a poll. If `vote.is_aye()`, the vote is to enact the proposal;
242		/// otherwise it is a vote to keep the status quo.
243		///
244		/// The dispatch origin of this call must be _Signed_.
245		///
246		/// - `poll_index`: The index of the poll to vote for.
247		/// - `vote`: The vote configuration.
248		///
249		/// Weight: `O(R)` where R is the number of polls the voter has voted on.
250		#[pallet::call_index(0)]
251		#[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))]
252		pub fn vote(
253			origin: OriginFor<T>,
254			#[pallet::compact] poll_index: PollIndexOf<T, I>,
255			vote: AccountVote<BalanceOf<T, I>>,
256		) -> DispatchResult {
257			let who = ensure_signed(origin)?;
258			Self::try_vote(&who, poll_index, vote)
259		}
260
261		/// Delegate the voting power (with some given conviction) of the sending account for a
262		/// particular class of polls.
263		///
264		/// The balance delegated is locked for as long as it's delegated, and thereafter for the
265		/// time appropriate for the conviction's lock period.
266		///
267		/// The dispatch origin of this call must be _Signed_, and the signing account must either:
268		///   - be delegating already; or
269		///   - have no voting activity (if there is, then it will need to be removed through
270		///     `remove_vote`).
271		///
272		/// - `to`: The account whose voting the `target` account's voting power will follow.
273		/// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls
274		///   to this function are required.
275		/// - `conviction`: The conviction that will be attached to the delegated votes. When the
276		///   account is undelegated, the funds will be locked for the corresponding period.
277		/// - `balance`: The amount of the account's balance to be used in delegating. This must not
278		///   be more than the account's current balance.
279		///
280		/// Emits `Delegated`.
281		///
282		/// Weight: `O(R)` where R is the number of polls the voter delegating to has
283		///   voted on. Weight is initially charged as if maximum votes, but is refunded later.
284		// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
285		// because a valid delegation cover decoding a direct voting with max votes.
286		#[pallet::call_index(1)]
287		#[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))]
288		pub fn delegate(
289			origin: OriginFor<T>,
290			class: ClassOf<T, I>,
291			to: AccountIdLookupOf<T>,
292			conviction: Conviction,
293			balance: BalanceOf<T, I>,
294		) -> DispatchResultWithPostInfo {
295			let who = ensure_signed(origin)?;
296			let to = T::Lookup::lookup(to)?;
297			let votes = Self::try_delegate(who, class, to, conviction, balance)?;
298
299			Ok(Some(T::WeightInfo::delegate(votes)).into())
300		}
301
302		/// Undelegate the voting power of the sending account for a particular class of polls.
303		///
304		/// Tokens may be unlocked following once an amount of time consistent with the lock period
305		/// of the conviction with which the delegation was issued has passed.
306		///
307		/// The dispatch origin of this call must be _Signed_ and the signing account must be
308		/// currently delegating.
309		///
310		/// - `class`: The class of polls to remove the delegation from.
311		///
312		/// Emits `Undelegated`.
313		///
314		/// Weight: `O(R)` where R is the number of polls the voter delegating to has
315		///   voted on. Weight is initially charged as if maximum votes, but is refunded later.
316		// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
317		// because a valid delegation cover decoding a direct voting with max votes.
318		#[pallet::call_index(2)]
319		#[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))]
320		pub fn undelegate(
321			origin: OriginFor<T>,
322			class: ClassOf<T, I>,
323		) -> DispatchResultWithPostInfo {
324			let who = ensure_signed(origin)?;
325			let votes = Self::try_undelegate(who, class)?;
326			Ok(Some(T::WeightInfo::undelegate(votes)).into())
327		}
328
329		/// Remove the lock caused by prior voting/delegating which has expired within a particular
330		/// class.
331		///
332		/// The dispatch origin of this call must be _Signed_.
333		///
334		/// - `class`: The class of polls to unlock.
335		/// - `target`: The account to remove the lock on.
336		///
337		/// Weight: `O(R)` with R number of vote of target.
338		#[pallet::call_index(3)]
339		#[pallet::weight(T::WeightInfo::unlock())]
340		pub fn unlock(
341			origin: OriginFor<T>,
342			class: ClassOf<T, I>,
343			target: AccountIdLookupOf<T>,
344		) -> DispatchResult {
345			ensure_signed(origin)?;
346			let target = T::Lookup::lookup(target)?;
347			Self::update_lock(&class, &target);
348			Self::deposit_event(Event::VoteUnlocked { who: target, class });
349			Ok(())
350		}
351
352		/// Remove a vote for a poll.
353		///
354		/// If:
355		/// - the poll was cancelled, or
356		/// - the poll is ongoing, or
357		/// - the poll has ended such that
358		///   - the vote of the account was in opposition to the result; or
359		///   - there was no conviction to the account's vote; or
360		///   - the account made a split vote
361		/// ...then the vote is removed cleanly and a following call to `unlock` may result in more
362		/// funds being available.
363		///
364		/// If, however, the poll has ended and:
365		/// - it finished corresponding to the vote of the account, and
366		/// - the account made a standard vote with conviction, and
367		/// - the lock period of the conviction is not over
368		/// ...then the lock will be aggregated into the overall account's lock, which may involve
369		/// *overlocking* (where the two locks are combined into a single lock that is the maximum
370		/// of both the amount locked and the time is it locked for).
371		///
372		/// The dispatch origin of this call must be _Signed_, and the signer must have a vote
373		/// registered for poll `index`.
374		///
375		/// - `index`: The index of poll of the vote to be removed.
376		/// - `class`: Optional parameter, if given it indicates the class of the poll. For polls
377		///   which have finished or are cancelled, this must be `Some`.
378		///
379		/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
380		///   Weight is calculated for the maximum number of vote.
381		#[pallet::call_index(4)]
382		#[pallet::weight(T::WeightInfo::remove_vote())]
383		pub fn remove_vote(
384			origin: OriginFor<T>,
385			class: Option<ClassOf<T, I>>,
386			index: PollIndexOf<T, I>,
387		) -> DispatchResult {
388			let who = ensure_signed(origin)?;
389			Self::try_remove_vote(&who, index, class, UnvoteScope::Any)
390		}
391
392		/// Remove a vote for a poll.
393		///
394		/// If the `target` is equal to the signer, then this function is exactly equivalent to
395		/// `remove_vote`. If not equal to the signer, then the vote must have expired,
396		/// either because the poll was cancelled, because the voter lost the poll or
397		/// because the conviction period is over.
398		///
399		/// The dispatch origin of this call must be _Signed_.
400		///
401		/// - `target`: The account of the vote to be removed; this account must have voted for poll
402		///   `index`.
403		/// - `index`: The index of poll of the vote to be removed.
404		/// - `class`: The class of the poll.
405		///
406		/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
407		///   Weight is calculated for the maximum number of vote.
408		#[pallet::call_index(5)]
409		#[pallet::weight(T::WeightInfo::remove_other_vote())]
410		pub fn remove_other_vote(
411			origin: OriginFor<T>,
412			target: AccountIdLookupOf<T>,
413			class: ClassOf<T, I>,
414			index: PollIndexOf<T, I>,
415		) -> DispatchResult {
416			let who = ensure_signed(origin)?;
417			let target = T::Lookup::lookup(target)?;
418			let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired };
419			Self::try_remove_vote(&target, index, Some(class), scope)?;
420			Ok(())
421		}
422	}
423}
424
425impl<T: Config<I>, I: 'static> Pallet<T, I> {
426	/// Actually enact a vote, if legit.
427	fn try_vote(
428		who: &T::AccountId,
429		poll_index: PollIndexOf<T, I>,
430		vote: AccountVote<BalanceOf<T, I>>,
431	) -> DispatchResult {
432		ensure!(
433			vote.balance() <= T::Currency::total_balance(who),
434			Error::<T, I>::InsufficientFunds
435		);
436		// Call on_vote hook
437		T::VotingHooks::on_before_vote(who, poll_index, vote)?;
438
439		T::Polls::try_access_poll(poll_index, |poll_status| {
440			let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T, I>::NotOngoing)?;
441			VotingFor::<T, I>::try_mutate(who, &class, |voting| {
442				if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting {
443					match votes.binary_search_by_key(&poll_index, |i| i.0) {
444						Ok(i) => {
445							// Shouldn't be possible to fail, but we handle it gracefully.
446							tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?;
447							if let Some(approve) = votes[i].1.as_standard() {
448								tally.reduce(approve, *delegations);
449							}
450							votes[i].1 = vote;
451						},
452						Err(i) => {
453							votes
454								.try_insert(i, (poll_index, vote))
455								.map_err(|_| Error::<T, I>::MaxVotesReached)?;
456						},
457					}
458					// Shouldn't be possible to fail, but we handle it gracefully.
459					tally.add(vote).ok_or(ArithmeticError::Overflow)?;
460					if let Some(approve) = vote.as_standard() {
461						tally.increase(approve, *delegations);
462					}
463				} else {
464					return Err(Error::<T, I>::AlreadyDelegating.into());
465				}
466				// Extend the lock to `balance` (rather than setting it) since we don't know what
467				// other votes are in place.
468				Self::extend_lock(who, &class, vote.balance());
469				Self::deposit_event(Event::Voted { who: who.clone(), vote, poll_index });
470				Ok(())
471			})
472		})
473	}
474
475	/// Remove the account's vote for the given poll if possible. This is possible when:
476	/// - The poll has not finished.
477	/// - The poll has finished and the voter lost their direction.
478	/// - The poll has finished and the voter's lock period is up.
479	///
480	/// This will generally be combined with a call to `unlock`.
481	fn try_remove_vote(
482		who: &T::AccountId,
483		poll_index: PollIndexOf<T, I>,
484		class_hint: Option<ClassOf<T, I>>,
485		scope: UnvoteScope,
486	) -> DispatchResult {
487		let class = class_hint
488			.or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1))
489			.ok_or(Error::<T, I>::ClassNeeded)?;
490		VotingFor::<T, I>::try_mutate(who, class, |voting| {
491			if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting {
492				let i = votes
493					.binary_search_by_key(&poll_index, |i| i.0)
494					.map_err(|_| Error::<T, I>::NotVoter)?;
495				let v = votes.remove(i);
496
497				T::Polls::try_access_poll(poll_index, |poll_status| match poll_status {
498					PollStatus::Ongoing(tally, _) => {
499						ensure!(matches!(scope, UnvoteScope::Any), Error::<T, I>::NoPermission);
500						// Shouldn't be possible to fail, but we handle it gracefully.
501						tally.remove(v.1).ok_or(ArithmeticError::Underflow)?;
502						if let Some(approve) = v.1.as_standard() {
503							tally.reduce(approve, *delegations);
504						}
505						Self::deposit_event(Event::VoteRemoved {
506							who: who.clone(),
507							vote: v.1,
508							poll_index,
509						});
510						T::VotingHooks::on_remove_vote(who, poll_index, Status::Ongoing);
511						Ok(())
512					},
513					PollStatus::Completed(end, approved) => {
514						if let Some((lock_periods, balance)) =
515							v.1.locked_if(vote::LockedIf::Status(approved))
516						{
517							let unlock_at = end.saturating_add(
518								T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()),
519							);
520							let now = T::BlockNumberProvider::current_block_number();
521							if now < unlock_at {
522								ensure!(
523									matches!(scope, UnvoteScope::Any),
524									Error::<T, I>::NoPermissionYet
525								);
526								prior.accumulate(unlock_at, balance)
527							}
528						} else if v.1.as_standard().is_some_and(|vote| vote != approved) {
529							// Unsuccessful vote, use special hook to lock the funds too in case of
530							// conviction.
531							if let Some(to_lock) =
532								T::VotingHooks::lock_balance_on_unsuccessful_vote(who, poll_index)
533							{
534								if let AccountVote::Standard { vote, .. } = v.1 {
535									let unlock_at = end.saturating_add(
536										T::VoteLockingPeriod::get()
537											.saturating_mul(vote.conviction.lock_periods().into()),
538									);
539									let now = T::BlockNumberProvider::current_block_number();
540									if now < unlock_at {
541										ensure!(
542											matches!(scope, UnvoteScope::Any),
543											Error::<T, I>::NoPermissionYet
544										);
545										prior.accumulate(unlock_at, to_lock)
546									}
547								}
548							}
549						}
550						// Call on_remove_vote hook
551						T::VotingHooks::on_remove_vote(who, poll_index, Status::Completed);
552						Ok(())
553					},
554					PollStatus::None => {
555						// Poll was cancelled.
556						T::VotingHooks::on_remove_vote(who, poll_index, Status::None);
557						Ok(())
558					},
559				})
560			} else {
561				Ok(())
562			}
563		})
564	}
565
566	/// Return the number of votes for `who`.
567	fn increase_upstream_delegation(
568		who: &T::AccountId,
569		class: &ClassOf<T, I>,
570		amount: Delegations<BalanceOf<T, I>>,
571	) -> u32 {
572		VotingFor::<T, I>::mutate(who, class, |voting| match voting {
573			Voting::Delegating(Delegating { delegations, .. }) => {
574				// We don't support second level delegating, so we don't need to do anything more.
575				*delegations = delegations.saturating_add(amount);
576				1
577			},
578			Voting::Casting(Casting { votes, delegations, .. }) => {
579				*delegations = delegations.saturating_add(amount);
580				for &(poll_index, account_vote) in votes.iter() {
581					if let AccountVote::Standard { vote, .. } = account_vote {
582						T::Polls::access_poll(poll_index, |poll_status| {
583							if let PollStatus::Ongoing(tally, _) = poll_status {
584								tally.increase(vote.aye, amount);
585							}
586						});
587					}
588				}
589				votes.len() as u32
590			},
591		})
592	}
593
594	/// Return the number of votes for `who`.
595	fn reduce_upstream_delegation(
596		who: &T::AccountId,
597		class: &ClassOf<T, I>,
598		amount: Delegations<BalanceOf<T, I>>,
599	) -> u32 {
600		VotingFor::<T, I>::mutate(who, class, |voting| match voting {
601			Voting::Delegating(Delegating { delegations, .. }) => {
602				// We don't support second level delegating, so we don't need to do anything more.
603				*delegations = delegations.saturating_sub(amount);
604				1
605			},
606			Voting::Casting(Casting { votes, delegations, .. }) => {
607				*delegations = delegations.saturating_sub(amount);
608				for &(poll_index, account_vote) in votes.iter() {
609					if let AccountVote::Standard { vote, .. } = account_vote {
610						T::Polls::access_poll(poll_index, |poll_status| {
611							if let PollStatus::Ongoing(tally, _) = poll_status {
612								tally.reduce(vote.aye, amount);
613							}
614						});
615					}
616				}
617				votes.len() as u32
618			},
619		})
620	}
621
622	/// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`.
623	///
624	/// Return the upstream number of votes.
625	fn try_delegate(
626		who: T::AccountId,
627		class: ClassOf<T, I>,
628		target: T::AccountId,
629		conviction: Conviction,
630		balance: BalanceOf<T, I>,
631	) -> Result<u32, DispatchError> {
632		ensure!(who != target, Error::<T, I>::Nonsense);
633		T::Polls::classes().binary_search(&class).map_err(|_| Error::<T, I>::BadClass)?;
634		ensure!(balance <= T::Currency::total_balance(&who), Error::<T, I>::InsufficientFunds);
635		let votes =
636			VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
637				let old = core::mem::replace(
638					voting,
639					Voting::Delegating(Delegating {
640						balance,
641						target: target.clone(),
642						conviction,
643						delegations: Default::default(),
644						prior: Default::default(),
645					}),
646				);
647				match old {
648					Voting::Delegating(Delegating { .. }) =>
649						return Err(Error::<T, I>::AlreadyDelegating.into()),
650					Voting::Casting(Casting { votes, delegations, prior }) => {
651						// here we just ensure that we're currently idling with no votes recorded.
652						ensure!(votes.is_empty(), Error::<T, I>::AlreadyVoting);
653						voting.set_common(delegations, prior);
654					},
655				}
656
657				let votes =
658					Self::increase_upstream_delegation(&target, &class, conviction.votes(balance));
659				// Extend the lock to `balance` (rather than setting it) since we don't know what
660				// other votes are in place.
661				Self::extend_lock(&who, &class, balance);
662				Ok(votes)
663			})?;
664		Self::deposit_event(Event::<T, I>::Delegated(who, target, class));
665		Ok(votes)
666	}
667
668	/// Attempt to end the current delegation.
669	///
670	/// Return the number of votes of upstream.
671	fn try_undelegate(who: T::AccountId, class: ClassOf<T, I>) -> Result<u32, DispatchError> {
672		let votes =
673			VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
674				match core::mem::replace(voting, Voting::default()) {
675					Voting::Delegating(Delegating {
676						balance,
677						target,
678						conviction,
679						delegations,
680						mut prior,
681					}) => {
682						// remove any delegation votes to our current target.
683						let votes = Self::reduce_upstream_delegation(
684							&target,
685							&class,
686							conviction.votes(balance),
687						);
688						let now = T::BlockNumberProvider::current_block_number();
689						let lock_periods = conviction.lock_periods().into();
690						prior.accumulate(
691							now.saturating_add(
692								T::VoteLockingPeriod::get().saturating_mul(lock_periods),
693							),
694							balance,
695						);
696						voting.set_common(delegations, prior);
697
698						Ok(votes)
699					},
700					Voting::Casting(_) => Err(Error::<T, I>::NotDelegating.into()),
701				}
702			})?;
703		Self::deposit_event(Event::<T, I>::Undelegated(who, class));
704		Ok(votes)
705	}
706
707	fn extend_lock(who: &T::AccountId, class: &ClassOf<T, I>, amount: BalanceOf<T, I>) {
708		ClassLocksFor::<T, I>::mutate(who, |locks| {
709			match locks.iter().position(|x| &x.0 == class) {
710				Some(i) => locks[i].1 = locks[i].1.max(amount),
711				None => {
712					let ok = locks.try_push((class.clone(), amount)).is_ok();
713					debug_assert!(
714						ok,
715						"Vec bounded by number of classes; \
716						all items in Vec associated with a unique class; \
717						qed"
718					);
719				},
720			}
721		});
722		T::Currency::extend_lock(
723			CONVICTION_VOTING_ID,
724			who,
725			amount,
726			WithdrawReasons::except(WithdrawReasons::RESERVE),
727		);
728	}
729
730	/// Rejig the lock on an account. It will never get more stringent (since that would indicate
731	/// a security hole) but may be reduced from what they are currently.
732	fn update_lock(class: &ClassOf<T, I>, who: &T::AccountId) {
733		let class_lock_needed = VotingFor::<T, I>::mutate(who, class, |voting| {
734			voting.rejig(T::BlockNumberProvider::current_block_number());
735			voting.locked_balance()
736		});
737		let lock_needed = ClassLocksFor::<T, I>::mutate(who, |locks| {
738			locks.retain(|x| &x.0 != class);
739			if !class_lock_needed.is_zero() {
740				let ok = locks.try_push((class.clone(), class_lock_needed)).is_ok();
741				debug_assert!(
742					ok,
743					"Vec bounded by number of classes; \
744					all items in Vec associated with a unique class; \
745					qed"
746				);
747			}
748			locks.iter().map(|x| x.1).max().unwrap_or(Zero::zero())
749		});
750		if lock_needed.is_zero() {
751			T::Currency::remove_lock(CONVICTION_VOTING_ID, who);
752		} else {
753			T::Currency::set_lock(
754				CONVICTION_VOTING_ID,
755				who,
756				lock_needed,
757				WithdrawReasons::except(WithdrawReasons::RESERVE),
758			);
759		}
760	}
761}