1#![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#[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#[derive(Clone, Copy)]
112enum VestingAction {
113 Passive,
115 Remove { index: usize },
117 Merge { index1: usize, index2: usize },
119}
120
121impl VestingAction {
122 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 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
146pub 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 #[allow(deprecated)]
164 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
165
166 type Currency: LockableCurrency<Self::AccountId>;
168
169 type BlockNumberToBalance: Convert<BlockNumberFor<Self>, BalanceOf<Self>>;
171
172 #[pallet::constant]
174 type MinVestedTransfer: Get<BalanceOf<Self>>;
175
176 type WeightInfo: WeightInfo;
178
179 type UnvestedFundsAllowedWithdrawReasons: Get<WithdrawReasons>;
182
183 type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
206
207 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 #[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 #[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 StorageVersion::<T>::put(Releases::V1);
257
258 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 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 VestingCreated { account: T::AccountId, schedule_index: u32 },
291 VestingUpdated { account: T::AccountId, unvested: BalanceOf<T> },
294 VestingCompleted { account: T::AccountId },
296 }
297
298 #[pallet::error]
300 pub enum Error<T> {
301 NotVesting,
303 AtMaxVestingSchedules,
306 AmountLow,
308 ScheduleIndexOutOfBounds,
310 InvalidScheduleParams,
312 }
313
314 #[pallet::call]
315 impl<T: Config> Pallet<T> {
316 #[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 #[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 #[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 #[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 #[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 #[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 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 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 match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
516 (true, true) => return None,
518 (true, false) => return Some(schedule2),
521 (false, true) => return Some(schedule1),
522 _ => {},
524 }
525
526 let locked = schedule1
527 .locked_at::<T::BlockNumberToBalance>(now)
528 .saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
529 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 fn do_vested_transfer(
554 source: &T::AccountId,
555 target: &T::AccountId,
556 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
557 ) -> DispatchResult {
558 ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
560 if !schedule.is_valid() {
561 return Err(Error::<T>::InvalidScheduleParams.into())
562 };
563
564 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 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 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 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 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 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 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 let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
678 let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
679
680 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 schedules.push(new_schedule);
691 let new_schedule_locked =
693 new_schedule.locked_at::<T::BlockNumberToBalance>(now);
694 locked_now = locked_now.saturating_add(new_schedule_locked);
696 } (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 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 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 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 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 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 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 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
816impl<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}