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,
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, Debug, 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
279			// Lock once per account, after every schedule is stored. `set_lock` replaces a
280			// lock of the same ID, so locking inside the loop above would leave an account
281			// with several entries holding only the last entry's amount.
282			let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
283			for (who, schedules) in Vesting::<T>::iter() {
284				let locked = schedules
285					.iter()
286					.map(|s| s.locked())
287					.fold(BalanceOf::<T>::zero(), |a, b| a.saturating_add(b));
288				T::Currency::set_lock(VESTING_ID, &who, locked, reasons);
289			}
290		}
291	}
292
293	#[pallet::event]
294	#[pallet::generate_deposit(pub(super) fn deposit_event)]
295	pub enum Event<T: Config> {
296		/// A vesting schedule has been created.
297		VestingCreated { account: T::AccountId, schedule_index: u32 },
298		/// The amount vested has been updated. This could indicate a change in funds available.
299		/// The balance given is the amount which is left unvested (and thus locked).
300		VestingUpdated { account: T::AccountId, unvested: BalanceOf<T> },
301		/// An \[account\] has become fully vested.
302		VestingCompleted { account: T::AccountId },
303	}
304
305	/// Error for the vesting pallet.
306	#[pallet::error]
307	pub enum Error<T> {
308		/// The account given is not vesting.
309		NotVesting,
310		/// The account already has `MaxVestingSchedules` count of schedules and thus
311		/// cannot add another one. Consider merging existing schedules in order to add another.
312		AtMaxVestingSchedules,
313		/// Amount being transferred is too low to create a vesting schedule.
314		AmountLow,
315		/// An index was out of bounds of the vesting schedules.
316		ScheduleIndexOutOfBounds,
317		/// Failed to create a new schedule because some parameter was invalid.
318		InvalidScheduleParams,
319	}
320
321	#[pallet::call]
322	impl<T: Config> Pallet<T> {
323		/// Unlock any vested funds of the sender account.
324		///
325		/// The dispatch origin for this call must be _Signed_ and the sender must have funds still
326		/// locked under this pallet.
327		///
328		/// Emits either `VestingCompleted` or `VestingUpdated`.
329		///
330		/// ## Complexity
331		/// - `O(1)`.
332		#[pallet::call_index(0)]
333		#[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
334			.max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
335		)]
336		pub fn vest(origin: OriginFor<T>) -> DispatchResult {
337			let who = ensure_signed(origin)?;
338			Self::do_vest(who)
339		}
340
341		/// Unlock any vested funds of a `target` account.
342		///
343		/// The dispatch origin for this call must be _Signed_.
344		///
345		/// - `target`: The account whose vested funds should be unlocked. Must have funds still
346		/// locked under this pallet.
347		///
348		/// Emits either `VestingCompleted` or `VestingUpdated`.
349		///
350		/// ## Complexity
351		/// - `O(1)`.
352		#[pallet::call_index(1)]
353		#[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
354			.max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
355		)]
356		pub fn vest_other(origin: OriginFor<T>, target: AccountIdLookupOf<T>) -> DispatchResult {
357			ensure_signed(origin)?;
358			let who = T::Lookup::lookup(target)?;
359			Self::do_vest(who)
360		}
361
362		/// Create a vested transfer.
363		///
364		/// The dispatch origin for this call must be _Signed_.
365		///
366		/// - `target`: The account receiving the vested funds.
367		/// - `schedule`: The vesting schedule attached to the transfer.
368		///
369		/// Emits `VestingCreated`.
370		///
371		/// NOTE: This will unlock all schedules through the current block.
372		///
373		/// ## Complexity
374		/// - `O(1)`.
375		#[pallet::call_index(2)]
376		#[pallet::weight(
377			T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
378		)]
379		pub fn vested_transfer(
380			origin: OriginFor<T>,
381			target: AccountIdLookupOf<T>,
382			schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
383		) -> DispatchResult {
384			let transactor = ensure_signed(origin)?;
385			let target = T::Lookup::lookup(target)?;
386			Self::do_vested_transfer(&transactor, &target, schedule)
387		}
388
389		/// Force a vested transfer.
390		///
391		/// The dispatch origin for this call must be _Root_.
392		///
393		/// - `source`: The account whose funds should be transferred.
394		/// - `target`: The account that should be transferred the vested funds.
395		/// - `schedule`: The vesting schedule attached to the transfer.
396		///
397		/// Emits `VestingCreated`.
398		///
399		/// NOTE: This will unlock all schedules through the current block.
400		///
401		/// ## Complexity
402		/// - `O(1)`.
403		#[pallet::call_index(3)]
404		#[pallet::weight(
405			T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
406		)]
407		pub fn force_vested_transfer(
408			origin: OriginFor<T>,
409			source: AccountIdLookupOf<T>,
410			target: AccountIdLookupOf<T>,
411			schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
412		) -> DispatchResult {
413			ensure_root(origin)?;
414			let target = T::Lookup::lookup(target)?;
415			let source = T::Lookup::lookup(source)?;
416			Self::do_vested_transfer(&source, &target, schedule)
417		}
418
419		/// Merge two vesting schedules together, creating a new vesting schedule that unlocks over
420		/// the highest possible start and end blocks. If both schedules have already started the
421		/// current block will be used as the schedule start; with the caveat that if one schedule
422		/// is finished by the current block, the other will be treated as the new merged schedule,
423		/// unmodified.
424		///
425		/// NOTE: If `schedule1_index == schedule2_index` this is a no-op.
426		/// NOTE: This will unlock all schedules through the current block prior to merging.
427		/// NOTE: If both schedules have ended by the current block, no new schedule will be created
428		/// and both will be removed.
429		///
430		/// Merged schedule attributes:
431		/// - `starting_block`: `MAX(schedule1.starting_block, scheduled2.starting_block,
432		///   current_block)`.
433		/// - `ending_block`: `MAX(schedule1.ending_block, schedule2.ending_block)`.
434		/// - `locked`: `schedule1.locked_at(current_block) + schedule2.locked_at(current_block)`.
435		///
436		/// The dispatch origin for this call must be _Signed_.
437		///
438		/// - `schedule1_index`: index of the first schedule to merge.
439		/// - `schedule2_index`: index of the second schedule to merge.
440		#[pallet::call_index(4)]
441		#[pallet::weight(
442			T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
443			.max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
444		)]
445		pub fn merge_schedules(
446			origin: OriginFor<T>,
447			schedule1_index: u32,
448			schedule2_index: u32,
449		) -> DispatchResult {
450			let who = ensure_signed(origin)?;
451			if schedule1_index == schedule2_index {
452				return Ok(());
453			};
454			let schedule1_index = schedule1_index as usize;
455			let schedule2_index = schedule2_index as usize;
456
457			let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
458			let merge_action =
459				VestingAction::Merge { index1: schedule1_index, index2: schedule2_index };
460
461			let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?;
462
463			Self::write_vesting(&who, schedules)?;
464			Self::write_lock(&who, locked_now);
465
466			Ok(())
467		}
468
469		/// Force remove a vesting schedule
470		///
471		/// The dispatch origin for this call must be _Root_.
472		///
473		/// - `target`: An account that has a vesting schedule
474		/// - `schedule_index`: The vesting schedule index that should be removed
475		#[pallet::call_index(5)]
476		#[pallet::weight(
477			T::WeightInfo::force_remove_vesting_schedule(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
478		)]
479		pub fn force_remove_vesting_schedule(
480			origin: OriginFor<T>,
481			target: <T::Lookup as StaticLookup>::Source,
482			schedule_index: u32,
483		) -> DispatchResultWithPostInfo {
484			ensure_root(origin)?;
485			let who = T::Lookup::lookup(target)?;
486
487			let schedules_count = Vesting::<T>::decode_len(&who).unwrap_or_default();
488			ensure!(schedule_index < schedules_count as u32, Error::<T>::InvalidScheduleParams);
489
490			Self::remove_vesting_schedule(&who, schedule_index)?;
491
492			Ok(Some(T::WeightInfo::force_remove_vesting_schedule(
493				MaxLocksOf::<T>::get(),
494				schedules_count as u32,
495			))
496			.into())
497		}
498	}
499}
500
501impl<T: Config> Pallet<T> {
502	// Public function for accessing vesting storage
503	pub fn vesting(
504		account: T::AccountId,
505	) -> Option<BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>>
506	{
507		Vesting::<T>::get(account)
508	}
509
510	// Create a new `VestingInfo`, based off of two other `VestingInfo`s.
511	// NOTE: We assume both schedules have had funds unlocked up through the current block.
512	fn merge_vesting_info(
513		now: BlockNumberFor<T>,
514		schedule1: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
515		schedule2: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
516	) -> Option<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>> {
517		let schedule1_ending_block = schedule1.ending_block_as_balance::<T::BlockNumberToBalance>();
518		let schedule2_ending_block = schedule2.ending_block_as_balance::<T::BlockNumberToBalance>();
519		let now_as_balance = T::BlockNumberToBalance::convert(now);
520
521		// Check if one or both schedules have ended.
522		match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
523			// If both schedules have ended, we don't merge and exit early.
524			(true, true) => return None,
525			// If one schedule has ended, we treat the one that has not ended as the new
526			// merged schedule.
527			(true, false) => return Some(schedule2),
528			(false, true) => return Some(schedule1),
529			// If neither schedule has ended don't exit early.
530			_ => {},
531		}
532
533		let locked = schedule1
534			.locked_at::<T::BlockNumberToBalance>(now)
535			.saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
536		// This shouldn't happen because we know at least one ending block is greater than now,
537		// thus at least a schedule a some locked balance.
538		debug_assert!(
539			!locked.is_zero(),
540			"merge_vesting_info validation checks failed to catch a locked of 0"
541		);
542
543		let ending_block = schedule1_ending_block.max(schedule2_ending_block);
544		let starting_block = now.max(schedule1.starting_block()).max(schedule2.starting_block());
545
546		let per_block = {
547			let duration = ending_block
548				.saturating_sub(T::BlockNumberToBalance::convert(starting_block))
549				.max(One::one());
550			(locked / duration).max(One::one())
551		};
552
553		let schedule = VestingInfo::new(locked, per_block, starting_block);
554		debug_assert!(schedule.is_valid(), "merge_vesting_info schedule validation check failed");
555
556		Some(schedule)
557	}
558
559	// Execute a vested transfer from `source` to `target` with the given `schedule`.
560	fn do_vested_transfer(
561		source: &T::AccountId,
562		target: &T::AccountId,
563		schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
564	) -> DispatchResult {
565		// Validate user inputs.
566		ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
567		if !schedule.is_valid() {
568			return Err(Error::<T>::InvalidScheduleParams.into());
569		};
570
571		// Check we can add to this account prior to any storage writes.
572		Self::can_add_vesting_schedule(
573			target,
574			schedule.locked(),
575			schedule.per_block(),
576			schedule.starting_block(),
577		)?;
578
579		T::Currency::transfer(source, target, schedule.locked(), ExistenceRequirement::AllowDeath)?;
580
581		// We can't let this fail because the currency transfer has already happened.
582		// Must be successful as it has been checked before.
583		// Better to return error on failure anyway.
584		let res = Self::add_vesting_schedule(
585			target,
586			schedule.locked(),
587			schedule.per_block(),
588			schedule.starting_block(),
589		);
590		debug_assert!(res.is_ok(), "Failed to add a schedule when we had to succeed.");
591
592		Ok(())
593	}
594
595	/// Iterate through the schedules to track the current locked amount and
596	/// filter out completed and specified schedules.
597	///
598	/// Returns a tuple that consists of:
599	/// - Vec of vesting schedules, where completed schedules and those specified
600	/// 	by filter are removed. (Note the vec is not checked for respecting
601	/// 	bounded length.)
602	/// - The amount locked at the current block number based on the given schedules.
603	///
604	/// NOTE: the amount locked does not include any schedules that are filtered out via `action`.
605	fn report_schedule_updates(
606		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
607		action: VestingAction,
608	) -> (Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>) {
609		let now = T::BlockNumberProvider::current_block_number();
610
611		let mut total_locked_now: BalanceOf<T> = Zero::zero();
612		let filtered_schedules = action
613			.pick_schedules::<T>(schedules)
614			.filter(|schedule| {
615				let locked_now = schedule.locked_at::<T::BlockNumberToBalance>(now);
616				let keep = !locked_now.is_zero();
617				if keep {
618					total_locked_now = total_locked_now.saturating_add(locked_now);
619				}
620				keep
621			})
622			.collect::<Vec<_>>();
623
624		(filtered_schedules, total_locked_now)
625	}
626
627	/// Write an accounts updated vesting lock to storage.
628	fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf<T>) {
629		if total_locked_now.is_zero() {
630			T::Currency::remove_lock(VESTING_ID, who);
631			Self::deposit_event(Event::<T>::VestingCompleted { account: who.clone() });
632		} else {
633			let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
634			T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons);
635			Self::deposit_event(Event::<T>::VestingUpdated {
636				account: who.clone(),
637				unvested: total_locked_now,
638			});
639		};
640	}
641
642	/// Write an accounts updated vesting schedules to storage.
643	fn write_vesting(
644		who: &T::AccountId,
645		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
646	) -> Result<(), DispatchError> {
647		let schedules: BoundedVec<
648			VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
649			MaxVestingSchedulesGet<T>,
650		> = schedules.try_into().map_err(|_| Error::<T>::AtMaxVestingSchedules)?;
651
652		if schedules.len() == 0 {
653			Vesting::<T>::remove(&who);
654		} else {
655			Vesting::<T>::insert(who, schedules)
656		}
657
658		Ok(())
659	}
660
661	/// Unlock any vested funds of `who`.
662	fn do_vest(who: T::AccountId) -> DispatchResult {
663		let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
664
665		let (schedules, locked_now) =
666			Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
667
668		Self::write_vesting(&who, schedules)?;
669		Self::write_lock(&who, locked_now);
670
671		Ok(())
672	}
673
674	/// Execute a `VestingAction` against the given `schedules`. Returns the updated schedules
675	/// and locked amount.
676	fn exec_action(
677		schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
678		action: VestingAction,
679	) -> Result<(Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>), DispatchError> {
680		let (schedules, locked_now) = match action {
681			VestingAction::Merge { index1: idx1, index2: idx2 } => {
682				// The schedule index is based off of the schedule ordering prior to filtering out
683				// any schedules that may be ending at this block.
684				let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
685				let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
686
687				// The length of `schedules` decreases by 2 here since we filter out 2 schedules.
688				// Thus we know below that we can push the new merged schedule without error
689				// (assuming initial state was valid).
690				let (mut schedules, mut locked_now) =
691					Self::report_schedule_updates(schedules.to_vec(), action);
692
693				let now = T::BlockNumberProvider::current_block_number();
694				if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) {
695					// Merging created a new schedule so we:
696					// 1) need to add it to the accounts vesting schedule collection,
697					schedules.push(new_schedule);
698					// (we use `locked_at` in case this is a schedule that started in the past)
699					let new_schedule_locked =
700						new_schedule.locked_at::<T::BlockNumberToBalance>(now);
701					// and 2) update the locked amount to reflect the schedule we just added.
702					locked_now = locked_now.saturating_add(new_schedule_locked);
703				} // In the None case there was no new schedule to account for.
704
705				(schedules, locked_now)
706			},
707			_ => Self::report_schedule_updates(schedules.to_vec(), action),
708		};
709
710		debug_assert!(
711			locked_now > Zero::zero() && schedules.len() > 0 ||
712				locked_now == Zero::zero() && schedules.len() == 0
713		);
714
715		Ok((schedules, locked_now))
716	}
717}
718
719impl<T: Config> frame_support::traits::tokens::VestedPayout<T::AccountId, BalanceOf<T>>
720	for Pallet<T>
721where
722	BalanceOf<T>: MaybeSerializeDeserialize + Debug,
723{
724	type BlockNumber = BlockNumberFor<T>;
725
726	fn vested_transfer(
727		source: &T::AccountId,
728		dest: &T::AccountId,
729		amount: BalanceOf<T>,
730		duration: BlockNumberFor<T>,
731		start_at: Option<BlockNumberFor<T>>,
732	) -> DispatchResult {
733		if amount.is_zero() {
734			return Ok(());
735		}
736
737		if duration.is_zero() {
738			// Zero duration means liquid transfer with no vesting schedule.
739			T::Currency::transfer(source, dest, amount, ExistenceRequirement::AllowDeath)
740		} else {
741			let starting_block =
742				start_at.unwrap_or_else(|| T::BlockNumberProvider::current_block_number());
743			let duration_as_balance = T::BlockNumberToBalance::convert(duration);
744			// Round up so that vesting completes within `duration` blocks, not longer.
745			let per_block =
746				((amount.saturating_add(duration_as_balance).saturating_sub(One::one())) /
747					duration_as_balance)
748					.max(One::one());
749			let schedule = VestingInfo::new(amount, per_block, starting_block);
750			Self::do_vested_transfer(source, dest, schedule)
751		}
752	}
753}
754
755impl<T: Config> VestingSchedule<T::AccountId> for Pallet<T>
756where
757	BalanceOf<T>: MaybeSerializeDeserialize + Debug,
758{
759	type Currency = T::Currency;
760	type Moment = BlockNumberFor<T>;
761
762	/// Get the amount that is currently being vested and cannot be transferred out of this account.
763	fn vesting_balance(who: &T::AccountId) -> Option<BalanceOf<T>> {
764		if let Some(v) = Vesting::<T>::get(who) {
765			let now = T::BlockNumberProvider::current_block_number();
766			let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| {
767				schedule.locked_at::<T::BlockNumberToBalance>(now).saturating_add(total)
768			});
769			Some(T::Currency::free_balance(who).min(total_locked_now))
770		} else {
771			None
772		}
773	}
774
775	/// Adds a vesting schedule to a given account.
776	///
777	/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
778	/// is updated.
779	///
780	/// On success, a linearly reducing amount of funds will be locked. In order to realise any
781	/// reduction of the lock over time as it diminishes, the account owner must use `vest` or
782	/// `vest_other`.
783	///
784	/// It is a no-op if the amount to be vested is zero.
785	///
786	/// NOTE: This doesn't alter the free balance of the account.
787	fn add_vesting_schedule(
788		who: &T::AccountId,
789		locked: BalanceOf<T>,
790		per_block: BalanceOf<T>,
791		starting_block: BlockNumberFor<T>,
792	) -> DispatchResult {
793		if locked.is_zero() {
794			return Ok(());
795		}
796
797		let vesting_schedule = VestingInfo::new(locked, per_block, starting_block);
798		// Check for `per_block` or `locked` of 0.
799		if !vesting_schedule.is_valid() {
800			return Err(Error::<T>::InvalidScheduleParams.into());
801		};
802
803		let mut schedules = Vesting::<T>::get(who).unwrap_or_default();
804
805		// NOTE: we must push the new schedule so that `exec_action`
806		// will give the correct new locked amount.
807		ensure!(schedules.try_push(vesting_schedule).is_ok(), Error::<T>::AtMaxVestingSchedules);
808
809		debug_assert!(schedules.len() > 0, "schedules cannot be empty after insertion");
810		let schedule_index = schedules.len() - 1;
811		Self::deposit_event(Event::<T>::VestingCreated {
812			account: who.clone(),
813			schedule_index: schedule_index as u32,
814		});
815
816		let (schedules, locked_now) =
817			Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
818
819		Self::write_vesting(who, schedules)?;
820		Self::write_lock(who, locked_now);
821
822		Ok(())
823	}
824
825	/// Ensure we can call `add_vesting_schedule` without error. This should always
826	/// be called prior to `add_vesting_schedule`.
827	fn can_add_vesting_schedule(
828		who: &T::AccountId,
829		locked: BalanceOf<T>,
830		per_block: BalanceOf<T>,
831		starting_block: BlockNumberFor<T>,
832	) -> DispatchResult {
833		// Check for `per_block` or `locked` of 0.
834		if !VestingInfo::new(locked, per_block, starting_block).is_valid() {
835			return Err(Error::<T>::InvalidScheduleParams.into());
836		}
837
838		ensure!(
839			(Vesting::<T>::decode_len(who).unwrap_or_default() as u32) < T::MAX_VESTING_SCHEDULES,
840			Error::<T>::AtMaxVestingSchedules
841		);
842
843		Ok(())
844	}
845
846	/// Remove a vesting schedule for a given account.
847	fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult {
848		let schedules = Vesting::<T>::get(who).ok_or(Error::<T>::NotVesting)?;
849		let remove_action = VestingAction::Remove { index: schedule_index as usize };
850
851		let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?;
852
853		Self::write_vesting(who, schedules)?;
854		Self::write_lock(who, locked_now);
855		Ok(())
856	}
857}
858
859/// An implementation that allows the Vesting Pallet to handle a vested transfer
860/// on behalf of another Pallet.
861impl<T: Config> VestedTransfer<T::AccountId> for Pallet<T>
862where
863	BalanceOf<T>: MaybeSerializeDeserialize + Debug,
864{
865	type Currency = T::Currency;
866	type Moment = BlockNumberFor<T>;
867
868	fn vested_transfer(
869		source: &T::AccountId,
870		target: &T::AccountId,
871		locked: BalanceOf<T>,
872		per_block: BalanceOf<T>,
873		starting_block: BlockNumberFor<T>,
874	) -> DispatchResult {
875		use frame_support::storage::{with_transaction, TransactionOutcome};
876		let schedule = VestingInfo::new(locked, per_block, starting_block);
877		with_transaction(|| -> TransactionOutcome<DispatchResult> {
878			let result = Self::do_vested_transfer(source, target, schedule);
879
880			match &result {
881				Ok(()) => TransactionOutcome::Commit(result),
882				_ => TransactionOutcome::Rollback(result),
883			}
884		})
885	}
886}