referrerpolicy=no-referrer-when-downgrade

pallet_vesting/
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//! # Vesting Pallet
19//!
20//! - [`Config`]
21//! - [`Call`]
22//!
23//! ## Overview
24//!
25//! A simple pallet providing a means of placing a linear curve on an account's locked balance. This
26//! pallet ensures that there is a lock in place preventing the balance to drop below the *unvested*
27//! amount for any reason other than the ones specified in `UnvestedFundsAllowedWithdrawReasons`
28//! configuration value.
29//!
30//! As the amount vested increases over time, the amount unvested reduces. However, locks remain in
31//! place and explicit action is needed on behalf of the user to ensure that the amount locked is
32//! equivalent to the amount remaining to be vested. This is done through a dispatchable function,
33//! either `vest` (in typical case where the sender is calling on their own behalf) or `vest_other`
34//! in case the sender is calling on another account's behalf.
35//!
36//! ## Interface
37//!
38//! This pallet implements the `VestingSchedule` trait.
39//!
40//! ### Dispatchable Functions
41//!
42//! - `vest` - Update the lock, reducing it in line with the amount "vested" so far.
43//! - `vest_other` - Update the lock of another account, reducing it in line with the amount
44//!   "vested" so far.
45
46#![cfg_attr(not(feature = "std"), no_std)]
47
48mod benchmarking;
49
50#[cfg(test)]
51mod mock;
52#[cfg(test)]
53mod tests;
54mod vesting_info;
55
56pub mod migrations;
57pub mod weights;
58
59extern crate alloc;
60
61use alloc::vec::Vec;
62use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
63use core::{fmt::Debug, marker::PhantomData};
64use frame_support::{
65	dispatch::DispatchResult,
66	ensure,
67	storage::bounded_vec::BoundedVec,
68	traits::{
69		Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestedTransfer,
70		VestingSchedule, WithdrawReasons,
71	},
72	weights::Weight,
73};
74use frame_system::pallet_prelude::BlockNumberFor;
75use scale_info::TypeInfo;
76use sp_runtime::{
77	traits::{
78		AtLeast32BitUnsigned, BlockNumberProvider, Bounded, Convert, MaybeSerializeDeserialize,
79		One, Saturating, StaticLookup, Zero,
80	},
81	DispatchError, RuntimeDebug,
82};
83
84pub use pallet::*;
85pub use vesting_info::*;
86pub use weights::WeightInfo;
87
88type BalanceOf<T> =
89	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
90type MaxLocksOf<T> =
91	<<T as Config>::Currency as LockableCurrency<<T as frame_system::Config>::AccountId>>::MaxLocks;
92type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
93
94const VESTING_ID: LockIdentifier = *b"vesting ";
95
96// A value placed in storage that represents the current version of the Vesting storage.
97// This value is used by `on_runtime_upgrade` to determine whether we run storage migration logic.
98#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
99pub enum Releases {
100	V0,
101	V1,
102}
103
104impl Default for Releases {
105	fn default() -> Self {
106		Releases::V0
107	}
108}
109
110/// Actions to take against a user's `Vesting` storage entry.
111#[derive(Clone, Copy)]
112enum VestingAction {
113	/// Do not actively remove any schedules.
114	Passive,
115	/// Remove the schedule specified by the index.
116	Remove { index: usize },
117	/// Remove the two schedules, specified by index, so they can be merged.
118	Merge { index1: usize, index2: usize },
119}
120
121impl VestingAction {
122	/// Whether or not the filter says the schedule index should be removed.
123	fn should_remove(&self, index: usize) -> bool {
124		match self {
125			Self::Passive => false,
126			Self::Remove { index: index1 } => *index1 == index,
127			Self::Merge { index1, index2 } => *index1 == index || *index2 == index,
128		}
129	}
130
131	/// Pick the schedules that this action dictates should continue vesting undisturbed.
132	fn pick_schedules<T: Config>(
133		&self,
134		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
135	) -> impl Iterator<Item = VestingInfo<BalanceOf<T>, BlockNumberFor<T>>> + '_ {
136		schedules.into_iter().enumerate().filter_map(move |(index, schedule)| {
137			if self.should_remove(index) {
138				None
139			} else {
140				Some(schedule)
141			}
142		})
143	}
144}
145
146// Wrapper for `T::MAX_VESTING_SCHEDULES` to satisfy `trait Get`.
147pub struct MaxVestingSchedulesGet<T>(PhantomData<T>);
148impl<T: Config> Get<u32> for MaxVestingSchedulesGet<T> {
149	fn get() -> u32 {
150		T::MAX_VESTING_SCHEDULES
151	}
152}
153
154#[frame_support::pallet]
155pub mod pallet {
156	use super::*;
157	use frame_support::pallet_prelude::*;
158	use frame_system::pallet_prelude::*;
159
160	#[pallet::config]
161	pub trait Config: frame_system::Config {
162		/// The overarching event type.
163		#[allow(deprecated)]
164		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
165
166		/// The currency trait.
167		type Currency: LockableCurrency<Self::AccountId>;
168
169		/// Convert the block number into a balance.
170		type BlockNumberToBalance: Convert<BlockNumberFor<Self>, BalanceOf<Self>>;
171
172		/// The minimum amount transferred to call `vested_transfer`.
173		#[pallet::constant]
174		type MinVestedTransfer: Get<BalanceOf<Self>>;
175
176		/// Weight information for extrinsics in this pallet.
177		type WeightInfo: WeightInfo;
178
179		/// Reasons that determine under which conditions the balance may drop below
180		/// the unvested amount.
181		type UnvestedFundsAllowedWithdrawReasons: Get<WithdrawReasons>;
182
183		/// Query the current block number.
184		///
185		/// Must return monotonically increasing values when called from consecutive blocks.
186		/// Can be configured to return either:
187		/// - the local block number of the runtime via `frame_system::Pallet`
188		/// - a remote block number, eg from the relay chain through `RelaychainDataProvider`
189		/// - an arbitrary value through a custom implementation of the trait
190		///
191		/// There is currently no migration provided to "hot-swap" block number providers and it may
192		/// result in undefined behavior when doing so. Parachains are therefore best off setting
193		/// this to their local block number provider if they have the pallet already deployed.
194		///
195		/// Suggested values:
196		/// - Solo- and Relay-chains: `frame_system::Pallet`
197		/// - Parachains that may produce blocks sparingly or only when needed (on-demand):
198		///   - already have the pallet deployed: `frame_system::Pallet`
199		///   - are freshly deploying this pallet: `RelaychainDataProvider`
200		/// - Parachains with a reliably block production rate (PLO or bulk-coretime):
201		///   - already have the pallet deployed: `frame_system::Pallet`
202		///   - are freshly deploying this pallet: no strong recommendation. Both local and remote
203		///     providers can be used. Relay provider can be a bit better in cases where the
204		///     parachain is lagging its block production to avoid clock skew.
205		type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
206
207		/// Maximum number of vesting schedules an account may have at a given moment.
208		const MAX_VESTING_SCHEDULES: u32;
209	}
210
211	#[pallet::extra_constants]
212	impl<T: Config> Pallet<T> {
213		#[pallet::constant_name(MaxVestingSchedules)]
214		fn max_vesting_schedules() -> u32 {
215			T::MAX_VESTING_SCHEDULES
216		}
217	}
218
219	#[pallet::hooks]
220	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
221		fn integrity_test() {
222			assert!(T::MAX_VESTING_SCHEDULES > 0, "`MaxVestingSchedules` must be greater than 0");
223		}
224	}
225
226	/// Information regarding the vesting of a given account.
227	#[pallet::storage]
228	pub type Vesting<T: Config> = StorageMap<
229		_,
230		Blake2_128Concat,
231		T::AccountId,
232		BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>,
233	>;
234
235	/// Storage version of the pallet.
236	///
237	/// New networks start with latest version, as determined by the genesis build.
238	#[pallet::storage]
239	pub type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
240
241	#[pallet::pallet]
242	pub struct Pallet<T>(_);
243
244	#[pallet::genesis_config]
245	#[derive(frame_support::DefaultNoBound)]
246	pub struct GenesisConfig<T: Config> {
247		pub vesting: Vec<(T::AccountId, BlockNumberFor<T>, BlockNumberFor<T>, BalanceOf<T>)>,
248	}
249
250	#[pallet::genesis_build]
251	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
252		fn build(&self) {
253			use sp_runtime::traits::Saturating;
254
255			// Genesis uses the latest storage version.
256			StorageVersion::<T>::put(Releases::V1);
257
258			// Generate initial vesting configuration
259			// * who - Account which we are generating vesting configuration for
260			// * begin - Block when the account will start to vest
261			// * length - Number of blocks from `begin` until fully vested
262			// * liquid - Number of units which can be spent before vesting begins
263			for &(ref who, begin, length, liquid) in self.vesting.iter() {
264				let balance = T::Currency::free_balance(who);
265				assert!(!balance.is_zero(), "Currencies must be init'd before vesting");
266				// Total genesis `balance` minus `liquid` equals funds locked for vesting
267				let locked = balance.saturating_sub(liquid);
268				let length_as_balance = T::BlockNumberToBalance::convert(length);
269				let per_block = locked / length_as_balance.max(sp_runtime::traits::One::one());
270				let vesting_info = VestingInfo::new(locked, per_block, begin);
271				if !vesting_info.is_valid() {
272					panic!("Invalid VestingInfo params at genesis")
273				};
274
275				Vesting::<T>::try_append(who, vesting_info)
276					.expect("Too many vesting schedules at genesis.");
277
278				let reasons =
279					WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
280
281				T::Currency::set_lock(VESTING_ID, who, locked, reasons);
282			}
283		}
284	}
285
286	#[pallet::event]
287	#[pallet::generate_deposit(pub(super) fn deposit_event)]
288	pub enum Event<T: Config> {
289		/// A vesting schedule has been created.
290		VestingCreated { account: T::AccountId, schedule_index: u32 },
291		/// The amount vested has been updated. This could indicate a change in funds available.
292		/// The balance given is the amount which is left unvested (and thus locked).
293		VestingUpdated { account: T::AccountId, unvested: BalanceOf<T> },
294		/// An \[account\] has become fully vested.
295		VestingCompleted { account: T::AccountId },
296	}
297
298	/// Error for the vesting pallet.
299	#[pallet::error]
300	pub enum Error<T> {
301		/// The account given is not vesting.
302		NotVesting,
303		/// The account already has `MaxVestingSchedules` count of schedules and thus
304		/// cannot add another one. Consider merging existing schedules in order to add another.
305		AtMaxVestingSchedules,
306		/// Amount being transferred is too low to create a vesting schedule.
307		AmountLow,
308		/// An index was out of bounds of the vesting schedules.
309		ScheduleIndexOutOfBounds,
310		/// Failed to create a new schedule because some parameter was invalid.
311		InvalidScheduleParams,
312	}
313
314	#[pallet::call]
315	impl<T: Config> Pallet<T> {
316		/// Unlock any vested funds of the sender account.
317		///
318		/// The dispatch origin for this call must be _Signed_ and the sender must have funds still
319		/// locked under this pallet.
320		///
321		/// Emits either `VestingCompleted` or `VestingUpdated`.
322		///
323		/// ## Complexity
324		/// - `O(1)`.
325		#[pallet::call_index(0)]
326		#[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
327			.max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
328		)]
329		pub fn vest(origin: OriginFor<T>) -> DispatchResult {
330			let who = ensure_signed(origin)?;
331			Self::do_vest(who)
332		}
333
334		/// Unlock any vested funds of a `target` account.
335		///
336		/// The dispatch origin for this call must be _Signed_.
337		///
338		/// - `target`: The account whose vested funds should be unlocked. Must have funds still
339		/// locked under this pallet.
340		///
341		/// Emits either `VestingCompleted` or `VestingUpdated`.
342		///
343		/// ## Complexity
344		/// - `O(1)`.
345		#[pallet::call_index(1)]
346		#[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
347			.max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
348		)]
349		pub fn vest_other(origin: OriginFor<T>, target: AccountIdLookupOf<T>) -> DispatchResult {
350			ensure_signed(origin)?;
351			let who = T::Lookup::lookup(target)?;
352			Self::do_vest(who)
353		}
354
355		/// Create a vested transfer.
356		///
357		/// The dispatch origin for this call must be _Signed_.
358		///
359		/// - `target`: The account receiving the vested funds.
360		/// - `schedule`: The vesting schedule attached to the transfer.
361		///
362		/// Emits `VestingCreated`.
363		///
364		/// NOTE: This will unlock all schedules through the current block.
365		///
366		/// ## Complexity
367		/// - `O(1)`.
368		#[pallet::call_index(2)]
369		#[pallet::weight(
370			T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
371		)]
372		pub fn vested_transfer(
373			origin: OriginFor<T>,
374			target: AccountIdLookupOf<T>,
375			schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
376		) -> DispatchResult {
377			let transactor = ensure_signed(origin)?;
378			let target = T::Lookup::lookup(target)?;
379			Self::do_vested_transfer(&transactor, &target, schedule)
380		}
381
382		/// Force a vested transfer.
383		///
384		/// The dispatch origin for this call must be _Root_.
385		///
386		/// - `source`: The account whose funds should be transferred.
387		/// - `target`: The account that should be transferred the vested funds.
388		/// - `schedule`: The vesting schedule attached to the transfer.
389		///
390		/// Emits `VestingCreated`.
391		///
392		/// NOTE: This will unlock all schedules through the current block.
393		///
394		/// ## Complexity
395		/// - `O(1)`.
396		#[pallet::call_index(3)]
397		#[pallet::weight(
398			T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
399		)]
400		pub fn force_vested_transfer(
401			origin: OriginFor<T>,
402			source: AccountIdLookupOf<T>,
403			target: AccountIdLookupOf<T>,
404			schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
405		) -> DispatchResult {
406			ensure_root(origin)?;
407			let target = T::Lookup::lookup(target)?;
408			let source = T::Lookup::lookup(source)?;
409			Self::do_vested_transfer(&source, &target, schedule)
410		}
411
412		/// Merge two vesting schedules together, creating a new vesting schedule that unlocks over
413		/// the highest possible start and end blocks. If both schedules have already started the
414		/// current block will be used as the schedule start; with the caveat that if one schedule
415		/// is finished by the current block, the other will be treated as the new merged schedule,
416		/// unmodified.
417		///
418		/// NOTE: If `schedule1_index == schedule2_index` this is a no-op.
419		/// NOTE: This will unlock all schedules through the current block prior to merging.
420		/// NOTE: If both schedules have ended by the current block, no new schedule will be created
421		/// and both will be removed.
422		///
423		/// Merged schedule attributes:
424		/// - `starting_block`: `MAX(schedule1.starting_block, scheduled2.starting_block,
425		///   current_block)`.
426		/// - `ending_block`: `MAX(schedule1.ending_block, schedule2.ending_block)`.
427		/// - `locked`: `schedule1.locked_at(current_block) + schedule2.locked_at(current_block)`.
428		///
429		/// The dispatch origin for this call must be _Signed_.
430		///
431		/// - `schedule1_index`: index of the first schedule to merge.
432		/// - `schedule2_index`: index of the second schedule to merge.
433		#[pallet::call_index(4)]
434		#[pallet::weight(
435			T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
436			.max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
437		)]
438		pub fn merge_schedules(
439			origin: OriginFor<T>,
440			schedule1_index: u32,
441			schedule2_index: u32,
442		) -> DispatchResult {
443			let who = ensure_signed(origin)?;
444			if schedule1_index == schedule2_index {
445				return Ok(())
446			};
447			let schedule1_index = schedule1_index as usize;
448			let schedule2_index = schedule2_index as usize;
449
450			let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
451			let merge_action =
452				VestingAction::Merge { index1: schedule1_index, index2: schedule2_index };
453
454			let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?;
455
456			Self::write_vesting(&who, schedules)?;
457			Self::write_lock(&who, locked_now);
458
459			Ok(())
460		}
461
462		/// Force remove a vesting schedule
463		///
464		/// The dispatch origin for this call must be _Root_.
465		///
466		/// - `target`: An account that has a vesting schedule
467		/// - `schedule_index`: The vesting schedule index that should be removed
468		#[pallet::call_index(5)]
469		#[pallet::weight(
470			T::WeightInfo::force_remove_vesting_schedule(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
471		)]
472		pub fn force_remove_vesting_schedule(
473			origin: OriginFor<T>,
474			target: <T::Lookup as StaticLookup>::Source,
475			schedule_index: u32,
476		) -> DispatchResultWithPostInfo {
477			ensure_root(origin)?;
478			let who = T::Lookup::lookup(target)?;
479
480			let schedules_count = Vesting::<T>::decode_len(&who).unwrap_or_default();
481			ensure!(schedule_index < schedules_count as u32, Error::<T>::InvalidScheduleParams);
482
483			Self::remove_vesting_schedule(&who, schedule_index)?;
484
485			Ok(Some(T::WeightInfo::force_remove_vesting_schedule(
486				MaxLocksOf::<T>::get(),
487				schedules_count as u32,
488			))
489			.into())
490		}
491	}
492}
493
494impl<T: Config> Pallet<T> {
495	// Public function for accessing vesting storage
496	pub fn vesting(
497		account: T::AccountId,
498	) -> Option<BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>>
499	{
500		Vesting::<T>::get(account)
501	}
502
503	// Create a new `VestingInfo`, based off of two other `VestingInfo`s.
504	// NOTE: We assume both schedules have had funds unlocked up through the current block.
505	fn merge_vesting_info(
506		now: BlockNumberFor<T>,
507		schedule1: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
508		schedule2: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
509	) -> Option<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>> {
510		let schedule1_ending_block = schedule1.ending_block_as_balance::<T::BlockNumberToBalance>();
511		let schedule2_ending_block = schedule2.ending_block_as_balance::<T::BlockNumberToBalance>();
512		let now_as_balance = T::BlockNumberToBalance::convert(now);
513
514		// Check if one or both schedules have ended.
515		match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
516			// If both schedules have ended, we don't merge and exit early.
517			(true, true) => return None,
518			// If one schedule has ended, we treat the one that has not ended as the new
519			// merged schedule.
520			(true, false) => return Some(schedule2),
521			(false, true) => return Some(schedule1),
522			// If neither schedule has ended don't exit early.
523			_ => {},
524		}
525
526		let locked = schedule1
527			.locked_at::<T::BlockNumberToBalance>(now)
528			.saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
529		// This shouldn't happen because we know at least one ending block is greater than now,
530		// thus at least a schedule a some locked balance.
531		debug_assert!(
532			!locked.is_zero(),
533			"merge_vesting_info validation checks failed to catch a locked of 0"
534		);
535
536		let ending_block = schedule1_ending_block.max(schedule2_ending_block);
537		let starting_block = now.max(schedule1.starting_block()).max(schedule2.starting_block());
538
539		let per_block = {
540			let duration = ending_block
541				.saturating_sub(T::BlockNumberToBalance::convert(starting_block))
542				.max(One::one());
543			(locked / duration).max(One::one())
544		};
545
546		let schedule = VestingInfo::new(locked, per_block, starting_block);
547		debug_assert!(schedule.is_valid(), "merge_vesting_info schedule validation check failed");
548
549		Some(schedule)
550	}
551
552	// Execute a vested transfer from `source` to `target` with the given `schedule`.
553	fn do_vested_transfer(
554		source: &T::AccountId,
555		target: &T::AccountId,
556		schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
557	) -> DispatchResult {
558		// Validate user inputs.
559		ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
560		if !schedule.is_valid() {
561			return Err(Error::<T>::InvalidScheduleParams.into())
562		};
563
564		// Check we can add to this account prior to any storage writes.
565		Self::can_add_vesting_schedule(
566			target,
567			schedule.locked(),
568			schedule.per_block(),
569			schedule.starting_block(),
570		)?;
571
572		T::Currency::transfer(source, target, schedule.locked(), ExistenceRequirement::AllowDeath)?;
573
574		// We can't let this fail because the currency transfer has already happened.
575		// Must be successful as it has been checked before.
576		// Better to return error on failure anyway.
577		let res = Self::add_vesting_schedule(
578			target,
579			schedule.locked(),
580			schedule.per_block(),
581			schedule.starting_block(),
582		);
583		debug_assert!(res.is_ok(), "Failed to add a schedule when we had to succeed.");
584
585		Ok(())
586	}
587
588	/// Iterate through the schedules to track the current locked amount and
589	/// filter out completed and specified schedules.
590	///
591	/// Returns a tuple that consists of:
592	/// - Vec of vesting schedules, where completed schedules and those specified
593	/// 	by filter are removed. (Note the vec is not checked for respecting
594	/// 	bounded length.)
595	/// - The amount locked at the current block number based on the given schedules.
596	///
597	/// NOTE: the amount locked does not include any schedules that are filtered out via `action`.
598	fn report_schedule_updates(
599		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
600		action: VestingAction,
601	) -> (Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>) {
602		let now = T::BlockNumberProvider::current_block_number();
603
604		let mut total_locked_now: BalanceOf<T> = Zero::zero();
605		let filtered_schedules = action
606			.pick_schedules::<T>(schedules)
607			.filter(|schedule| {
608				let locked_now = schedule.locked_at::<T::BlockNumberToBalance>(now);
609				let keep = !locked_now.is_zero();
610				if keep {
611					total_locked_now = total_locked_now.saturating_add(locked_now);
612				}
613				keep
614			})
615			.collect::<Vec<_>>();
616
617		(filtered_schedules, total_locked_now)
618	}
619
620	/// Write an accounts updated vesting lock to storage.
621	fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf<T>) {
622		if total_locked_now.is_zero() {
623			T::Currency::remove_lock(VESTING_ID, who);
624			Self::deposit_event(Event::<T>::VestingCompleted { account: who.clone() });
625		} else {
626			let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
627			T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons);
628			Self::deposit_event(Event::<T>::VestingUpdated {
629				account: who.clone(),
630				unvested: total_locked_now,
631			});
632		};
633	}
634
635	/// Write an accounts updated vesting schedules to storage.
636	fn write_vesting(
637		who: &T::AccountId,
638		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
639	) -> Result<(), DispatchError> {
640		let schedules: BoundedVec<
641			VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
642			MaxVestingSchedulesGet<T>,
643		> = schedules.try_into().map_err(|_| Error::<T>::AtMaxVestingSchedules)?;
644
645		if schedules.len() == 0 {
646			Vesting::<T>::remove(&who);
647		} else {
648			Vesting::<T>::insert(who, schedules)
649		}
650
651		Ok(())
652	}
653
654	/// Unlock any vested funds of `who`.
655	fn do_vest(who: T::AccountId) -> DispatchResult {
656		let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
657
658		let (schedules, locked_now) =
659			Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
660
661		Self::write_vesting(&who, schedules)?;
662		Self::write_lock(&who, locked_now);
663
664		Ok(())
665	}
666
667	/// Execute a `VestingAction` against the given `schedules`. Returns the updated schedules
668	/// and locked amount.
669	fn exec_action(
670		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
671		action: VestingAction,
672	) -> Result<(Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>), DispatchError> {
673		let (schedules, locked_now) = match action {
674			VestingAction::Merge { index1: idx1, index2: idx2 } => {
675				// The schedule index is based off of the schedule ordering prior to filtering out
676				// any schedules that may be ending at this block.
677				let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
678				let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
679
680				// The length of `schedules` decreases by 2 here since we filter out 2 schedules.
681				// Thus we know below that we can push the new merged schedule without error
682				// (assuming initial state was valid).
683				let (mut schedules, mut locked_now) =
684					Self::report_schedule_updates(schedules.to_vec(), action);
685
686				let now = T::BlockNumberProvider::current_block_number();
687				if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) {
688					// Merging created a new schedule so we:
689					// 1) need to add it to the accounts vesting schedule collection,
690					schedules.push(new_schedule);
691					// (we use `locked_at` in case this is a schedule that started in the past)
692					let new_schedule_locked =
693						new_schedule.locked_at::<T::BlockNumberToBalance>(now);
694					// and 2) update the locked amount to reflect the schedule we just added.
695					locked_now = locked_now.saturating_add(new_schedule_locked);
696				} // In the None case there was no new schedule to account for.
697
698				(schedules, locked_now)
699			},
700			_ => Self::report_schedule_updates(schedules.to_vec(), action),
701		};
702
703		debug_assert!(
704			locked_now > Zero::zero() && schedules.len() > 0 ||
705				locked_now == Zero::zero() && schedules.len() == 0
706		);
707
708		Ok((schedules, locked_now))
709	}
710}
711
712impl<T: Config> VestingSchedule<T::AccountId> for Pallet<T>
713where
714	BalanceOf<T>: MaybeSerializeDeserialize + Debug,
715{
716	type Currency = T::Currency;
717	type Moment = BlockNumberFor<T>;
718
719	/// Get the amount that is currently being vested and cannot be transferred out of this account.
720	fn vesting_balance(who: &T::AccountId) -> Option<BalanceOf<T>> {
721		if let Some(v) = Vesting::<T>::get(who) {
722			let now = T::BlockNumberProvider::current_block_number();
723			let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| {
724				schedule.locked_at::<T::BlockNumberToBalance>(now).saturating_add(total)
725			});
726			Some(T::Currency::free_balance(who).min(total_locked_now))
727		} else {
728			None
729		}
730	}
731
732	/// Adds a vesting schedule to a given account.
733	///
734	/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
735	/// is updated.
736	///
737	/// On success, a linearly reducing amount of funds will be locked. In order to realise any
738	/// reduction of the lock over time as it diminishes, the account owner must use `vest` or
739	/// `vest_other`.
740	///
741	/// It is a no-op if the amount to be vested is zero.
742	///
743	/// NOTE: This doesn't alter the free balance of the account.
744	fn add_vesting_schedule(
745		who: &T::AccountId,
746		locked: BalanceOf<T>,
747		per_block: BalanceOf<T>,
748		starting_block: BlockNumberFor<T>,
749	) -> DispatchResult {
750		if locked.is_zero() {
751			return Ok(())
752		}
753
754		let vesting_schedule = VestingInfo::new(locked, per_block, starting_block);
755		// Check for `per_block` or `locked` of 0.
756		if !vesting_schedule.is_valid() {
757			return Err(Error::<T>::InvalidScheduleParams.into())
758		};
759
760		let mut schedules = Vesting::<T>::get(who).unwrap_or_default();
761
762		// NOTE: we must push the new schedule so that `exec_action`
763		// will give the correct new locked amount.
764		ensure!(schedules.try_push(vesting_schedule).is_ok(), Error::<T>::AtMaxVestingSchedules);
765
766		debug_assert!(schedules.len() > 0, "schedules cannot be empty after insertion");
767		let schedule_index = schedules.len() - 1;
768		Self::deposit_event(Event::<T>::VestingCreated {
769			account: who.clone(),
770			schedule_index: schedule_index as u32,
771		});
772
773		let (schedules, locked_now) =
774			Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
775
776		Self::write_vesting(who, schedules)?;
777		Self::write_lock(who, locked_now);
778
779		Ok(())
780	}
781
782	/// Ensure we can call `add_vesting_schedule` without error. This should always
783	/// be called prior to `add_vesting_schedule`.
784	fn can_add_vesting_schedule(
785		who: &T::AccountId,
786		locked: BalanceOf<T>,
787		per_block: BalanceOf<T>,
788		starting_block: BlockNumberFor<T>,
789	) -> DispatchResult {
790		// Check for `per_block` or `locked` of 0.
791		if !VestingInfo::new(locked, per_block, starting_block).is_valid() {
792			return Err(Error::<T>::InvalidScheduleParams.into())
793		}
794
795		ensure!(
796			(Vesting::<T>::decode_len(who).unwrap_or_default() as u32) < T::MAX_VESTING_SCHEDULES,
797			Error::<T>::AtMaxVestingSchedules
798		);
799
800		Ok(())
801	}
802
803	/// Remove a vesting schedule for a given account.
804	fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult {
805		let schedules = Vesting::<T>::get(who).ok_or(Error::<T>::NotVesting)?;
806		let remove_action = VestingAction::Remove { index: schedule_index as usize };
807
808		let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?;
809
810		Self::write_vesting(who, schedules)?;
811		Self::write_lock(who, locked_now);
812		Ok(())
813	}
814}
815
816/// An implementation that allows the Vesting Pallet to handle a vested transfer
817/// on behalf of another Pallet.
818impl<T: Config> VestedTransfer<T::AccountId> for Pallet<T>
819where
820	BalanceOf<T>: MaybeSerializeDeserialize + Debug,
821{
822	type Currency = T::Currency;
823	type Moment = BlockNumberFor<T>;
824
825	fn vested_transfer(
826		source: &T::AccountId,
827		target: &T::AccountId,
828		locked: BalanceOf<T>,
829		per_block: BalanceOf<T>,
830		starting_block: BlockNumberFor<T>,
831	) -> DispatchResult {
832		use frame_support::storage::{with_transaction, TransactionOutcome};
833		let schedule = VestingInfo::new(locked, per_block, starting_block);
834		with_transaction(|| -> TransactionOutcome<DispatchResult> {
835			let result = Self::do_vested_transfer(source, target, schedule);
836
837			match &result {
838				Ok(()) => TransactionOutcome::Commit(result),
839				_ => TransactionOutcome::Rollback(result),
840			}
841		})
842	}
843}