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

            
30
extern crate alloc;
31

            
32
use frame_support::{
33
	dispatch::DispatchResult,
34
	ensure,
35
	traits::{
36
		fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling,
37
		ReservableCurrency, WithdrawReasons,
38
	},
39
};
40
use frame_system::pallet_prelude::BlockNumberFor;
41
use sp_runtime::{
42
	traits::{AtLeast32BitUnsigned, Saturating, StaticLookup, Zero},
43
	ArithmeticError, DispatchError, Perbill,
44
};
45

            
46
mod conviction;
47
mod types;
48
mod vote;
49
pub mod weights;
50

            
51
pub use self::{
52
	conviction::Conviction,
53
	pallet::*,
54
	types::{Delegations, Tally, UnvoteScope},
55
	vote::{AccountVote, Casting, Delegating, Vote, Voting},
56
	weights::WeightInfo,
57
};
58

            
59
#[cfg(test)]
60
mod tests;
61

            
62
#[cfg(feature = "runtime-benchmarks")]
63
pub mod benchmarking;
64

            
65
const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot";
66

            
67
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
68
type BalanceOf<T, I = ()> =
69
	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
70
type VotingOf<T, I = ()> = Voting<
71
	BalanceOf<T, I>,
72
	<T as frame_system::Config>::AccountId,
73
	BlockNumberFor<T>,
74
	PollIndexOf<T, I>,
75
	<T as Config<I>>::MaxVotes,
76
>;
77
#[allow(dead_code)]
78
type DelegatingOf<T, I = ()> =
79
	Delegating<BalanceOf<T, I>, <T as frame_system::Config>::AccountId, BlockNumberFor<T>>;
80
pub type TallyOf<T, I = ()> = Tally<BalanceOf<T, I>, <T as Config<I>>::MaxTurnout>;
81
pub type VotesOf<T, I = ()> = BalanceOf<T, I>;
82
type PollIndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
83
#[cfg(feature = "runtime-benchmarks")]
84
type IndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
85
type ClassOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Class;
86

            
87
1835
#[frame_support::pallet]
88
pub mod pallet {
89
	use super::*;
90
	use frame_support::{
91
		pallet_prelude::{
92
			DispatchResultWithPostInfo, IsType, StorageDoubleMap, StorageMap, ValueQuery,
93
		},
94
		traits::ClassCountOf,
95
		Twox64Concat,
96
	};
97
	use frame_system::pallet_prelude::*;
98
	use sp_runtime::BoundedVec;
99

            
100
554790
	#[pallet::pallet]
101
	pub struct Pallet<T, I = ()>(_);
102

            
103
	#[pallet::config]
104
	pub trait Config<I: 'static = ()>: frame_system::Config + Sized {
105
		// System level stuff.
106
		type RuntimeEvent: From<Event<Self, I>>
107
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
108
		/// Weight information for extrinsics in this pallet.
109
		type WeightInfo: WeightInfo;
110
		/// Currency type with which voting happens.
111
		type Currency: ReservableCurrency<Self::AccountId>
112
			+ LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>
113
			+ fungible::Inspect<Self::AccountId>;
114

            
115
		/// The implementation of the logic which conducts polls.
116
		type Polls: Polling<
117
			TallyOf<Self, I>,
118
			Votes = BalanceOf<Self, I>,
119
			Moment = BlockNumberFor<Self>,
120
		>;
121

            
122
		/// The maximum amount of tokens which may be used for voting. May just be
123
		/// `Currency::total_issuance`, but you might want to reduce this in order to account for
124
		/// funds in the system which are unable to vote (e.g. parachain auction deposits).
125
		type MaxTurnout: Get<BalanceOf<Self, I>>;
126

            
127
		/// The maximum number of concurrent votes an account may have.
128
		///
129
		/// Also used to compute weight, an overly large value can lead to extrinsics with large
130
		/// weight estimation: see `delegate` for instance.
131
		#[pallet::constant]
132
		type MaxVotes: Get<u32>;
133

            
134
		/// The minimum period of vote locking.
135
		///
136
		/// It should be no shorter than enactment period to ensure that in the case of an approval,
137
		/// those successful voters are locked into the consequences that their votes entail.
138
		#[pallet::constant]
139
		type VoteLockingPeriod: Get<BlockNumberFor<Self>>;
140
	}
141

            
142
	/// All voting for a particular voter in a particular voting class. We store the balance for the
143
	/// number of votes that we have recorded.
144
825
	#[pallet::storage]
145
	pub type VotingFor<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
146
		_,
147
		Twox64Concat,
148
		T::AccountId,
149
		Twox64Concat,
150
		ClassOf<T, I>,
151
		VotingOf<T, I>,
152
		ValueQuery,
153
	>;
154

            
155
	/// The voting classes which have a non-zero lock requirement and the lock amounts which they
156
	/// require. The actual amount locked on behalf of this pallet should always be the maximum of
157
	/// this list.
158
465
	#[pallet::storage]
159
	pub type ClassLocksFor<T: Config<I>, I: 'static = ()> = StorageMap<
160
		_,
161
		Twox64Concat,
162
		T::AccountId,
163
		BoundedVec<(ClassOf<T, I>, BalanceOf<T, I>), ClassCountOf<T::Polls, TallyOf<T, I>>>,
164
		ValueQuery,
165
	>;
166

            
167
	#[pallet::event]
168
102
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
169
	pub enum Event<T: Config<I>, I: 'static = ()> {
170
		/// An account has delegated their vote to another account. \[who, target\]
171
		Delegated(T::AccountId, T::AccountId),
172
		/// An \[account\] has cancelled a previous delegation operation.
173
		Undelegated(T::AccountId),
174
	}
175

            
176
1944
	#[pallet::error]
177
	pub enum Error<T, I = ()> {
178
		/// Poll is not ongoing.
179
		NotOngoing,
180
		/// The given account did not vote on the poll.
181
		NotVoter,
182
		/// The actor has no permission to conduct the action.
183
		NoPermission,
184
		/// The actor has no permission to conduct the action right now but will do in the future.
185
		NoPermissionYet,
186
		/// The account is already delegating.
187
		AlreadyDelegating,
188
		/// The account currently has votes attached to it and the operation cannot succeed until
189
		/// these are removed through `remove_vote`.
190
		AlreadyVoting,
191
		/// Too high a balance was provided that the account cannot afford.
192
		InsufficientFunds,
193
		/// The account is not currently delegating.
194
		NotDelegating,
195
		/// Delegation to oneself makes no sense.
196
		Nonsense,
197
		/// Maximum number of votes reached.
198
		MaxVotesReached,
199
		/// The class must be supplied since it is not easily determinable from the state.
200
		ClassNeeded,
201
		/// The class ID supplied is invalid.
202
		BadClass,
203
	}
204

            
205
11028
	#[pallet::call]
206
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
207
		/// Vote in a poll. If `vote.is_aye()`, the vote is to enact the proposal;
208
		/// otherwise it is a vote to keep the status quo.
209
		///
210
		/// The dispatch origin of this call must be _Signed_.
211
		///
212
		/// - `poll_index`: The index of the poll to vote for.
213
		/// - `vote`: The vote configuration.
214
		///
215
		/// Weight: `O(R)` where R is the number of polls the voter has voted on.
216
		#[pallet::call_index(0)]
217
		#[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))]
218
		pub fn vote(
219
			origin: OriginFor<T>,
220
			#[pallet::compact] poll_index: PollIndexOf<T, I>,
221
			vote: AccountVote<BalanceOf<T, I>>,
222
501
		) -> DispatchResult {
223
501
			let who = ensure_signed(origin)?;
224
501
			Self::try_vote(&who, poll_index, vote)
225
		}
226

            
227
		/// Delegate the voting power (with some given conviction) of the sending account for a
228
		/// particular class of polls.
229
		///
230
		/// The balance delegated is locked for as long as it's delegated, and thereafter for the
231
		/// time appropriate for the conviction's lock period.
232
		///
233
		/// The dispatch origin of this call must be _Signed_, and the signing account must either:
234
		///   - be delegating already; or
235
		///   - have no voting activity (if there is, then it will need to be removed through
236
		///     `remove_vote`).
237
		///
238
		/// - `to`: The account whose voting the `target` account's voting power will follow.
239
		/// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls
240
		///   to this function are required.
241
		/// - `conviction`: The conviction that will be attached to the delegated votes. When the
242
		///   account is undelegated, the funds will be locked for the corresponding period.
243
		/// - `balance`: The amount of the account's balance to be used in delegating. This must not
244
		///   be more than the account's current balance.
245
		///
246
		/// Emits `Delegated`.
247
		///
248
		/// Weight: `O(R)` where R is the number of polls the voter delegating to has
249
		///   voted on. Weight is initially charged as if maximum votes, but is refunded later.
250
		// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
251
		// because a valid delegation cover decoding a direct voting with max votes.
252
		#[pallet::call_index(1)]
253
		#[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))]
254
		pub fn delegate(
255
			origin: OriginFor<T>,
256
			class: ClassOf<T, I>,
257
			to: AccountIdLookupOf<T>,
258
			conviction: Conviction,
259
			balance: BalanceOf<T, I>,
260
516
		) -> DispatchResultWithPostInfo {
261
516
			let who = ensure_signed(origin)?;
262
516
			let to = T::Lookup::lookup(to)?;
263
321
			let votes = Self::try_delegate(who, class, to, conviction, balance)?;
264

            
265
102
			Ok(Some(T::WeightInfo::delegate(votes)).into())
266
		}
267

            
268
		/// Undelegate the voting power of the sending account for a particular class of polls.
269
		///
270
		/// Tokens may be unlocked following once an amount of time consistent with the lock period
271
		/// of the conviction with which the delegation was issued has passed.
272
		///
273
		/// The dispatch origin of this call must be _Signed_ and the signing account must be
274
		/// currently delegating.
275
		///
276
		/// - `class`: The class of polls to remove the delegation from.
277
		///
278
		/// Emits `Undelegated`.
279
		///
280
		/// Weight: `O(R)` where R is the number of polls the voter delegating to has
281
		///   voted on. Weight is initially charged as if maximum votes, but is refunded later.
282
		// NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure
283
		// because a valid delegation cover decoding a direct voting with max votes.
284
		#[pallet::call_index(2)]
285
		#[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))]
286
		pub fn undelegate(
287
			origin: OriginFor<T>,
288
			class: ClassOf<T, I>,
289
84
		) -> DispatchResultWithPostInfo {
290
84
			let who = ensure_signed(origin)?;
291
84
			let votes = Self::try_undelegate(who, class)?;
292
			Ok(Some(T::WeightInfo::undelegate(votes)).into())
293
		}
294

            
295
		/// Remove the lock caused by prior voting/delegating which has expired within a particular
296
		/// class.
297
		///
298
		/// The dispatch origin of this call must be _Signed_.
299
		///
300
		/// - `class`: The class of polls to unlock.
301
		/// - `target`: The account to remove the lock on.
302
		///
303
		/// Weight: `O(R)` with R number of vote of target.
304
		#[pallet::call_index(3)]
305
		#[pallet::weight(T::WeightInfo::unlock())]
306
		pub fn unlock(
307
			origin: OriginFor<T>,
308
			class: ClassOf<T, I>,
309
			target: AccountIdLookupOf<T>,
310
375
		) -> DispatchResult {
311
375
			ensure_signed(origin)?;
312
375
			let target = T::Lookup::lookup(target)?;
313
315
			Self::update_lock(&class, &target);
314
315
			Ok(())
315
		}
316

            
317
		/// Remove a vote for a poll.
318
		///
319
		/// If:
320
		/// - the poll was cancelled, or
321
		/// - the poll is ongoing, or
322
		/// - the poll has ended such that
323
		///   - the vote of the account was in opposition to the result; or
324
		///   - there was no conviction to the account's vote; or
325
		///   - the account made a split vote
326
		/// ...then the vote is removed cleanly and a following call to `unlock` may result in more
327
		/// funds being available.
328
		///
329
		/// If, however, the poll has ended and:
330
		/// - it finished corresponding to the vote of the account, and
331
		/// - the account made a standard vote with conviction, and
332
		/// - the lock period of the conviction is not over
333
		/// ...then the lock will be aggregated into the overall account's lock, which may involve
334
		/// *overlocking* (where the two locks are combined into a single lock that is the maximum
335
		/// of both the amount locked and the time is it locked for).
336
		///
337
		/// The dispatch origin of this call must be _Signed_, and the signer must have a vote
338
		/// registered for poll `index`.
339
		///
340
		/// - `index`: The index of poll of the vote to be removed.
341
		/// - `class`: Optional parameter, if given it indicates the class of the poll. For polls
342
		///   which have finished or are cancelled, this must be `Some`.
343
		///
344
		/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
345
		///   Weight is calculated for the maximum number of vote.
346
		#[pallet::call_index(4)]
347
		#[pallet::weight(T::WeightInfo::remove_vote())]
348
		pub fn remove_vote(
349
			origin: OriginFor<T>,
350
			class: Option<ClassOf<T, I>>,
351
			index: PollIndexOf<T, I>,
352
159
		) -> DispatchResult {
353
159
			let who = ensure_signed(origin)?;
354
159
			Self::try_remove_vote(&who, index, class, UnvoteScope::Any)
355
		}
356

            
357
		/// Remove a vote for a poll.
358
		///
359
		/// If the `target` is equal to the signer, then this function is exactly equivalent to
360
		/// `remove_vote`. If not equal to the signer, then the vote must have expired,
361
		/// either because the poll was cancelled, because the voter lost the poll or
362
		/// because the conviction period is over.
363
		///
364
		/// The dispatch origin of this call must be _Signed_.
365
		///
366
		/// - `target`: The account of the vote to be removed; this account must have voted for poll
367
		///   `index`.
368
		/// - `index`: The index of poll of the vote to be removed.
369
		/// - `class`: The class of the poll.
370
		///
371
		/// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on.
372
		///   Weight is calculated for the maximum number of vote.
373
		#[pallet::call_index(5)]
374
		#[pallet::weight(T::WeightInfo::remove_other_vote())]
375
		pub fn remove_other_vote(
376
			origin: OriginFor<T>,
377
			target: AccountIdLookupOf<T>,
378
			class: ClassOf<T, I>,
379
			index: PollIndexOf<T, I>,
380
180
		) -> DispatchResult {
381
180
			let who = ensure_signed(origin)?;
382
180
			let target = T::Lookup::lookup(target)?;
383
57
			let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired };
384
57
			Self::try_remove_vote(&target, index, Some(class), scope)?;
385
			Ok(())
386
		}
387
	}
388
}
389

            
390
impl<T: Config<I>, I: 'static> Pallet<T, I> {
391
	/// Actually enact a vote, if legit.
392
501
	fn try_vote(
393
501
		who: &T::AccountId,
394
501
		poll_index: PollIndexOf<T, I>,
395
501
		vote: AccountVote<BalanceOf<T, I>>,
396
501
	) -> DispatchResult {
397
501
		ensure!(
398
501
			vote.balance() <= T::Currency::total_balance(who),
399
186
			Error::<T, I>::InsufficientFunds
400
		);
401
420
		T::Polls::try_access_poll(poll_index, |poll_status| {
402
315
			let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::<T, I>::NotOngoing)?;
403
64
			VotingFor::<T, I>::try_mutate(who, &class, |voting| {
404
48
				if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting {
405
48
					match votes.binary_search_by_key(&poll_index, |i| i.0) {
406
24
						Ok(i) => {
407
24
							// Shouldn't be possible to fail, but we handle it gracefully.
408
24
							tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?;
409
24
							if let Some(approve) = votes[i].1.as_standard() {
410
24
								tally.reduce(approve, *delegations);
411
24
							}
412
24
							votes[i].1 = vote;
413
						},
414
24
						Err(i) => {
415
24
							votes
416
24
								.try_insert(i, (poll_index, vote))
417
24
								.map_err(|_| Error::<T, I>::MaxVotesReached)?;
418
						},
419
					}
420
					// Shouldn't be possible to fail, but we handle it gracefully.
421
48
					tally.add(vote).ok_or(ArithmeticError::Overflow)?;
422
48
					if let Some(approve) = vote.as_standard() {
423
48
						tally.increase(approve, *delegations);
424
48
					}
425
				} else {
426
					return Err(Error::<T, I>::AlreadyDelegating.into())
427
				}
428
				// Extend the lock to `balance` (rather than setting it) since we don't know what
429
				// other votes are in place.
430
48
				Self::extend_lock(who, &class, vote.balance());
431
48
				Ok(())
432
64
			})
433
420
		})
434
501
	}
435

            
436
	/// Remove the account's vote for the given poll if possible. This is possible when:
437
	/// - The poll has not finished.
438
	/// - The poll has finished and the voter lost their direction.
439
	/// - The poll has finished and the voter's lock period is up.
440
	///
441
	/// This will generally be combined with a call to `unlock`.
442
216
	fn try_remove_vote(
443
216
		who: &T::AccountId,
444
216
		poll_index: PollIndexOf<T, I>,
445
216
		class_hint: Option<ClassOf<T, I>>,
446
216
		scope: UnvoteScope,
447
216
	) -> DispatchResult {
448
216
		let class = class_hint
449
216
			.or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1))
450
216
			.ok_or(Error::<T, I>::ClassNeeded)?;
451
148
		VotingFor::<T, I>::try_mutate(who, class, |voting| {
452
111
			if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting {
453
111
				let i = votes
454
111
					.binary_search_by_key(&poll_index, |i| i.0)
455
111
					.map_err(|_| Error::<T, I>::NotVoter)?;
456
				let v = votes.remove(i);
457

            
458
				T::Polls::try_access_poll(poll_index, |poll_status| match poll_status {
459
					PollStatus::Ongoing(tally, _) => {
460
						ensure!(matches!(scope, UnvoteScope::Any), Error::<T, I>::NoPermission);
461
						// Shouldn't be possible to fail, but we handle it gracefully.
462
						tally.remove(v.1).ok_or(ArithmeticError::Underflow)?;
463
						if let Some(approve) = v.1.as_standard() {
464
							tally.reduce(approve, *delegations);
465
						}
466
						Ok(())
467
					},
468
					PollStatus::Completed(end, approved) => {
469
						if let Some((lock_periods, balance)) = v.1.locked_if(approved) {
470
							let unlock_at = end.saturating_add(
471
								T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()),
472
							);
473
							let now = frame_system::Pallet::<T>::block_number();
474
							if now < unlock_at {
475
								ensure!(
476
									matches!(scope, UnvoteScope::Any),
477
									Error::<T, I>::NoPermissionYet
478
								);
479
								prior.accumulate(unlock_at, balance)
480
							}
481
						}
482
						Ok(())
483
					},
484
					PollStatus::None => Ok(()), // Poll was cancelled.
485
				})
486
			} else {
487
				Ok(())
488
			}
489
148
		})
490
216
	}
491

            
492
	/// Return the number of votes for `who`.
493
102
	fn increase_upstream_delegation(
494
102
		who: &T::AccountId,
495
102
		class: &ClassOf<T, I>,
496
102
		amount: Delegations<BalanceOf<T, I>>,
497
102
	) -> u32 {
498
136
		VotingFor::<T, I>::mutate(who, class, |voting| match voting {
499
			Voting::Delegating(Delegating { delegations, .. }) => {
500
				// We don't support second level delegating, so we don't need to do anything more.
501
				*delegations = delegations.saturating_add(amount);
502
				1
503
			},
504
102
			Voting::Casting(Casting { votes, delegations, .. }) => {
505
102
				*delegations = delegations.saturating_add(amount);
506
102
				for &(poll_index, account_vote) in votes.iter() {
507
					if let AccountVote::Standard { vote, .. } = account_vote {
508
						T::Polls::access_poll(poll_index, |poll_status| {
509
							if let PollStatus::Ongoing(tally, _) = poll_status {
510
								tally.increase(vote.aye, amount);
511
							}
512
						});
513
					}
514
				}
515
102
				votes.len() as u32
516
			},
517
136
		})
518
102
	}
519

            
520
	/// Return the number of votes for `who`.
521
	fn reduce_upstream_delegation(
522
		who: &T::AccountId,
523
		class: &ClassOf<T, I>,
524
		amount: Delegations<BalanceOf<T, I>>,
525
	) -> u32 {
526
		VotingFor::<T, I>::mutate(who, class, |voting| match voting {
527
			Voting::Delegating(Delegating { delegations, .. }) => {
528
				// We don't support second level delegating, so we don't need to do anything more.
529
				*delegations = delegations.saturating_sub(amount);
530
				1
531
			},
532
			Voting::Casting(Casting { votes, delegations, .. }) => {
533
				*delegations = delegations.saturating_sub(amount);
534
				for &(poll_index, account_vote) in votes.iter() {
535
					if let AccountVote::Standard { vote, .. } = account_vote {
536
						T::Polls::access_poll(poll_index, |poll_status| {
537
							if let PollStatus::Ongoing(tally, _) = poll_status {
538
								tally.reduce(vote.aye, amount);
539
							}
540
						});
541
					}
542
				}
543
				votes.len() as u32
544
			},
545
		})
546
	}
547

            
548
	/// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`.
549
	///
550
	/// Return the upstream number of votes.
551
321
	fn try_delegate(
552
321
		who: T::AccountId,
553
321
		class: ClassOf<T, I>,
554
321
		target: T::AccountId,
555
321
		conviction: Conviction,
556
321
		balance: BalanceOf<T, I>,
557
321
	) -> Result<u32, DispatchError> {
558
321
		ensure!(who != target, Error::<T, I>::Nonsense);
559
318
		T::Polls::classes().binary_search(&class).map_err(|_| Error::<T, I>::BadClass)?;
560
228
		ensure!(balance <= T::Currency::total_balance(&who), Error::<T, I>::InsufficientFunds);
561
102
		let votes =
562
220
			VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
563
165
				let old = core::mem::replace(
564
165
					voting,
565
165
					Voting::Delegating(Delegating {
566
165
						balance,
567
165
						target: target.clone(),
568
165
						conviction,
569
165
						delegations: Default::default(),
570
165
						prior: Default::default(),
571
165
					}),
572
165
				);
573
165
				match old {
574
					Voting::Delegating(Delegating { .. }) =>
575
63
						return Err(Error::<T, I>::AlreadyDelegating.into()),
576
102
					Voting::Casting(Casting { votes, delegations, prior }) => {
577
102
						// here we just ensure that we're currently idling with no votes recorded.
578
102
						ensure!(votes.is_empty(), Error::<T, I>::AlreadyVoting);
579
102
						voting.set_common(delegations, prior);
580
102
					},
581
102
				}
582
102

            
583
102
				let votes =
584
102
					Self::increase_upstream_delegation(&target, &class, conviction.votes(balance));
585
102
				// Extend the lock to `balance` (rather than setting it) since we don't know what
586
102
				// other votes are in place.
587
102
				Self::extend_lock(&who, &class, balance);
588
102
				Ok(votes)
589
220
			})?;
590
102
		Self::deposit_event(Event::<T, I>::Delegated(who, target));
591
102
		Ok(votes)
592
321
	}
593

            
594
	/// Attempt to end the current delegation.
595
	///
596
	/// Return the number of votes of upstream.
597
84
	fn try_undelegate(who: T::AccountId, class: ClassOf<T, I>) -> Result<u32, DispatchError> {
598
		let votes =
599
112
			VotingFor::<T, I>::try_mutate(&who, &class, |voting| -> Result<u32, DispatchError> {
600
84
				match core::mem::replace(voting, Voting::default()) {
601
					Voting::Delegating(Delegating {
602
						balance,
603
						target,
604
						conviction,
605
						delegations,
606
						mut prior,
607
					}) => {
608
						// remove any delegation votes to our current target.
609
						let votes = Self::reduce_upstream_delegation(
610
							&target,
611
							&class,
612
							conviction.votes(balance),
613
						);
614
						let now = frame_system::Pallet::<T>::block_number();
615
						let lock_periods = conviction.lock_periods().into();
616
						prior.accumulate(
617
							now.saturating_add(
618
								T::VoteLockingPeriod::get().saturating_mul(lock_periods),
619
							),
620
							balance,
621
						);
622
						voting.set_common(delegations, prior);
623

            
624
						Ok(votes)
625
					},
626
84
					Voting::Casting(_) => Err(Error::<T, I>::NotDelegating.into()),
627
				}
628
112
			})?;
629
		Self::deposit_event(Event::<T, I>::Undelegated(who));
630
		Ok(votes)
631
84
	}
632

            
633
150
	fn extend_lock(who: &T::AccountId, class: &ClassOf<T, I>, amount: BalanceOf<T, I>) {
634
200
		ClassLocksFor::<T, I>::mutate(who, |locks| {
635
150
			match locks.iter().position(|x| &x.0 == class) {
636
24
				Some(i) => locks[i].1 = locks[i].1.max(amount),
637
				None => {
638
126
					let ok = locks.try_push((class.clone(), amount)).is_ok();
639
126
					debug_assert!(
640
						ok,
641
						"Vec bounded by number of classes; \
642
						all items in Vec associated with a unique class; \
643
						qed"
644
					);
645
				},
646
			}
647
200
		});
648
150
		T::Currency::extend_lock(
649
150
			CONVICTION_VOTING_ID,
650
150
			who,
651
150
			amount,
652
150
			WithdrawReasons::except(WithdrawReasons::RESERVE),
653
150
		);
654
150
	}
655

            
656
	/// Rejig the lock on an account. It will never get more stringent (since that would indicate
657
	/// a security hole) but may be reduced from what they are currently.
658
315
	fn update_lock(class: &ClassOf<T, I>, who: &T::AccountId) {
659
420
		let class_lock_needed = VotingFor::<T, I>::mutate(who, class, |voting| {
660
315
			voting.rejig(frame_system::Pallet::<T>::block_number());
661
315
			voting.locked_balance()
662
420
		});
663
420
		let lock_needed = ClassLocksFor::<T, I>::mutate(who, |locks| {
664
315
			locks.retain(|x| &x.0 != class);
665
315
			if !class_lock_needed.is_zero() {
666
				let ok = locks.try_push((class.clone(), class_lock_needed)).is_ok();
667
				debug_assert!(
668
					ok,
669
					"Vec bounded by number of classes; \
670
					all items in Vec associated with a unique class; \
671
					qed"
672
				);
673
315
			}
674
315
			locks.iter().map(|x| x.1).max().unwrap_or(Zero::zero())
675
420
		});
676
315
		if lock_needed.is_zero() {
677
315
			T::Currency::remove_lock(CONVICTION_VOTING_ID, who);
678
315
		} else {
679
			T::Currency::set_lock(
680
				CONVICTION_VOTING_ID,
681
				who,
682
				lock_needed,
683
				WithdrawReasons::except(WithdrawReasons::RESERVE),
684
			);
685
		}
686
315
	}
687
}