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 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
164
165 type Currency: LockableCurrency<Self::AccountId>;
167
168 type BlockNumberToBalance: Convert<BlockNumberFor<Self>, BalanceOf<Self>>;
170
171 #[pallet::constant]
173 type MinVestedTransfer: Get<BalanceOf<Self>>;
174
175 type WeightInfo: WeightInfo;
177
178 type UnvestedFundsAllowedWithdrawReasons: Get<WithdrawReasons>;
181
182 type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
205
206 const MAX_VESTING_SCHEDULES: u32;
208 }
209
210 #[pallet::extra_constants]
211 impl<T: Config> Pallet<T> {
212 #[pallet::constant_name(MaxVestingSchedules)]
213 fn max_vesting_schedules() -> u32 {
214 T::MAX_VESTING_SCHEDULES
215 }
216 }
217
218 #[pallet::hooks]
219 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
220 fn integrity_test() {
221 assert!(T::MAX_VESTING_SCHEDULES > 0, "`MaxVestingSchedules` must ge greater than 0");
222 }
223 }
224
225 #[pallet::storage]
227 pub type Vesting<T: Config> = StorageMap<
228 _,
229 Blake2_128Concat,
230 T::AccountId,
231 BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>,
232 >;
233
234 #[pallet::storage]
238 pub type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
239
240 #[pallet::pallet]
241 pub struct Pallet<T>(_);
242
243 #[pallet::genesis_config]
244 #[derive(frame_support::DefaultNoBound)]
245 pub struct GenesisConfig<T: Config> {
246 pub vesting: Vec<(T::AccountId, BlockNumberFor<T>, BlockNumberFor<T>, BalanceOf<T>)>,
247 }
248
249 #[pallet::genesis_build]
250 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
251 fn build(&self) {
252 use sp_runtime::traits::Saturating;
253
254 StorageVersion::<T>::put(Releases::V1);
256
257 for &(ref who, begin, length, liquid) in self.vesting.iter() {
263 let balance = T::Currency::free_balance(who);
264 assert!(!balance.is_zero(), "Currencies must be init'd before vesting");
265 let locked = balance.saturating_sub(liquid);
267 let length_as_balance = T::BlockNumberToBalance::convert(length);
268 let per_block = locked / length_as_balance.max(sp_runtime::traits::One::one());
269 let vesting_info = VestingInfo::new(locked, per_block, begin);
270 if !vesting_info.is_valid() {
271 panic!("Invalid VestingInfo params at genesis")
272 };
273
274 Vesting::<T>::try_append(who, vesting_info)
275 .expect("Too many vesting schedules at genesis.");
276
277 let reasons =
278 WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
279
280 T::Currency::set_lock(VESTING_ID, who, locked, reasons);
281 }
282 }
283 }
284
285 #[pallet::event]
286 #[pallet::generate_deposit(pub(super) fn deposit_event)]
287 pub enum Event<T: Config> {
288 VestingUpdated { account: T::AccountId, unvested: BalanceOf<T> },
291 VestingCompleted { account: T::AccountId },
293 }
294
295 #[pallet::error]
297 pub enum Error<T> {
298 NotVesting,
300 AtMaxVestingSchedules,
303 AmountLow,
305 ScheduleIndexOutOfBounds,
307 InvalidScheduleParams,
309 }
310
311 #[pallet::call]
312 impl<T: Config> Pallet<T> {
313 #[pallet::call_index(0)]
323 #[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
324 .max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
325 )]
326 pub fn vest(origin: OriginFor<T>) -> DispatchResult {
327 let who = ensure_signed(origin)?;
328 Self::do_vest(who)
329 }
330
331 #[pallet::call_index(1)]
343 #[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
344 .max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
345 )]
346 pub fn vest_other(origin: OriginFor<T>, target: AccountIdLookupOf<T>) -> DispatchResult {
347 ensure_signed(origin)?;
348 let who = T::Lookup::lookup(target)?;
349 Self::do_vest(who)
350 }
351
352 #[pallet::call_index(2)]
366 #[pallet::weight(
367 T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
368 )]
369 pub fn vested_transfer(
370 origin: OriginFor<T>,
371 target: AccountIdLookupOf<T>,
372 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
373 ) -> DispatchResult {
374 let transactor = ensure_signed(origin)?;
375 let target = T::Lookup::lookup(target)?;
376 Self::do_vested_transfer(&transactor, &target, schedule)
377 }
378
379 #[pallet::call_index(3)]
394 #[pallet::weight(
395 T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
396 )]
397 pub fn force_vested_transfer(
398 origin: OriginFor<T>,
399 source: AccountIdLookupOf<T>,
400 target: AccountIdLookupOf<T>,
401 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
402 ) -> DispatchResult {
403 ensure_root(origin)?;
404 let target = T::Lookup::lookup(target)?;
405 let source = T::Lookup::lookup(source)?;
406 Self::do_vested_transfer(&source, &target, schedule)
407 }
408
409 #[pallet::call_index(4)]
431 #[pallet::weight(
432 T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
433 .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
434 )]
435 pub fn merge_schedules(
436 origin: OriginFor<T>,
437 schedule1_index: u32,
438 schedule2_index: u32,
439 ) -> DispatchResult {
440 let who = ensure_signed(origin)?;
441 if schedule1_index == schedule2_index {
442 return Ok(())
443 };
444 let schedule1_index = schedule1_index as usize;
445 let schedule2_index = schedule2_index as usize;
446
447 let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
448 let merge_action =
449 VestingAction::Merge { index1: schedule1_index, index2: schedule2_index };
450
451 let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?;
452
453 Self::write_vesting(&who, schedules)?;
454 Self::write_lock(&who, locked_now);
455
456 Ok(())
457 }
458
459 #[pallet::call_index(5)]
466 #[pallet::weight(
467 T::WeightInfo::force_remove_vesting_schedule(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
468 )]
469 pub fn force_remove_vesting_schedule(
470 origin: OriginFor<T>,
471 target: <T::Lookup as StaticLookup>::Source,
472 schedule_index: u32,
473 ) -> DispatchResultWithPostInfo {
474 ensure_root(origin)?;
475 let who = T::Lookup::lookup(target)?;
476
477 let schedules_count = Vesting::<T>::decode_len(&who).unwrap_or_default();
478 ensure!(schedule_index < schedules_count as u32, Error::<T>::InvalidScheduleParams);
479
480 Self::remove_vesting_schedule(&who, schedule_index)?;
481
482 Ok(Some(T::WeightInfo::force_remove_vesting_schedule(
483 MaxLocksOf::<T>::get(),
484 schedules_count as u32,
485 ))
486 .into())
487 }
488 }
489}
490
491impl<T: Config> Pallet<T> {
492 pub fn vesting(
494 account: T::AccountId,
495 ) -> Option<BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>>
496 {
497 Vesting::<T>::get(account)
498 }
499
500 fn merge_vesting_info(
503 now: BlockNumberFor<T>,
504 schedule1: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
505 schedule2: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
506 ) -> Option<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>> {
507 let schedule1_ending_block = schedule1.ending_block_as_balance::<T::BlockNumberToBalance>();
508 let schedule2_ending_block = schedule2.ending_block_as_balance::<T::BlockNumberToBalance>();
509 let now_as_balance = T::BlockNumberToBalance::convert(now);
510
511 match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
513 (true, true) => return None,
515 (true, false) => return Some(schedule2),
518 (false, true) => return Some(schedule1),
519 _ => {},
521 }
522
523 let locked = schedule1
524 .locked_at::<T::BlockNumberToBalance>(now)
525 .saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
526 debug_assert!(
529 !locked.is_zero(),
530 "merge_vesting_info validation checks failed to catch a locked of 0"
531 );
532
533 let ending_block = schedule1_ending_block.max(schedule2_ending_block);
534 let starting_block = now.max(schedule1.starting_block()).max(schedule2.starting_block());
535
536 let per_block = {
537 let duration = ending_block
538 .saturating_sub(T::BlockNumberToBalance::convert(starting_block))
539 .max(One::one());
540 (locked / duration).max(One::one())
541 };
542
543 let schedule = VestingInfo::new(locked, per_block, starting_block);
544 debug_assert!(schedule.is_valid(), "merge_vesting_info schedule validation check failed");
545
546 Some(schedule)
547 }
548
549 fn do_vested_transfer(
551 source: &T::AccountId,
552 target: &T::AccountId,
553 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
554 ) -> DispatchResult {
555 ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
557 if !schedule.is_valid() {
558 return Err(Error::<T>::InvalidScheduleParams.into())
559 };
560
561 Self::can_add_vesting_schedule(
563 target,
564 schedule.locked(),
565 schedule.per_block(),
566 schedule.starting_block(),
567 )?;
568
569 T::Currency::transfer(source, target, schedule.locked(), ExistenceRequirement::AllowDeath)?;
570
571 let res = Self::add_vesting_schedule(
575 target,
576 schedule.locked(),
577 schedule.per_block(),
578 schedule.starting_block(),
579 );
580 debug_assert!(res.is_ok(), "Failed to add a schedule when we had to succeed.");
581
582 Ok(())
583 }
584
585 fn report_schedule_updates(
596 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
597 action: VestingAction,
598 ) -> (Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>) {
599 let now = T::BlockNumberProvider::current_block_number();
600
601 let mut total_locked_now: BalanceOf<T> = Zero::zero();
602 let filtered_schedules = action
603 .pick_schedules::<T>(schedules)
604 .filter(|schedule| {
605 let locked_now = schedule.locked_at::<T::BlockNumberToBalance>(now);
606 let keep = !locked_now.is_zero();
607 if keep {
608 total_locked_now = total_locked_now.saturating_add(locked_now);
609 }
610 keep
611 })
612 .collect::<Vec<_>>();
613
614 (filtered_schedules, total_locked_now)
615 }
616
617 fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf<T>) {
619 if total_locked_now.is_zero() {
620 T::Currency::remove_lock(VESTING_ID, who);
621 Self::deposit_event(Event::<T>::VestingCompleted { account: who.clone() });
622 } else {
623 let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
624 T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons);
625 Self::deposit_event(Event::<T>::VestingUpdated {
626 account: who.clone(),
627 unvested: total_locked_now,
628 });
629 };
630 }
631
632 fn write_vesting(
634 who: &T::AccountId,
635 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
636 ) -> Result<(), DispatchError> {
637 let schedules: BoundedVec<
638 VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
639 MaxVestingSchedulesGet<T>,
640 > = schedules.try_into().map_err(|_| Error::<T>::AtMaxVestingSchedules)?;
641
642 if schedules.len() == 0 {
643 Vesting::<T>::remove(&who);
644 } else {
645 Vesting::<T>::insert(who, schedules)
646 }
647
648 Ok(())
649 }
650
651 fn do_vest(who: T::AccountId) -> DispatchResult {
653 let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
654
655 let (schedules, locked_now) =
656 Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
657
658 Self::write_vesting(&who, schedules)?;
659 Self::write_lock(&who, locked_now);
660
661 Ok(())
662 }
663
664 fn exec_action(
667 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
668 action: VestingAction,
669 ) -> Result<(Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>), DispatchError> {
670 let (schedules, locked_now) = match action {
671 VestingAction::Merge { index1: idx1, index2: idx2 } => {
672 let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
675 let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
676
677 let (mut schedules, mut locked_now) =
681 Self::report_schedule_updates(schedules.to_vec(), action);
682
683 let now = T::BlockNumberProvider::current_block_number();
684 if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) {
685 schedules.push(new_schedule);
688 let new_schedule_locked =
690 new_schedule.locked_at::<T::BlockNumberToBalance>(now);
691 locked_now = locked_now.saturating_add(new_schedule_locked);
693 } (schedules, locked_now)
696 },
697 _ => Self::report_schedule_updates(schedules.to_vec(), action),
698 };
699
700 debug_assert!(
701 locked_now > Zero::zero() && schedules.len() > 0 ||
702 locked_now == Zero::zero() && schedules.len() == 0
703 );
704
705 Ok((schedules, locked_now))
706 }
707}
708
709impl<T: Config> VestingSchedule<T::AccountId> for Pallet<T>
710where
711 BalanceOf<T>: MaybeSerializeDeserialize + Debug,
712{
713 type Currency = T::Currency;
714 type Moment = BlockNumberFor<T>;
715
716 fn vesting_balance(who: &T::AccountId) -> Option<BalanceOf<T>> {
718 if let Some(v) = Vesting::<T>::get(who) {
719 let now = T::BlockNumberProvider::current_block_number();
720 let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| {
721 schedule.locked_at::<T::BlockNumberToBalance>(now).saturating_add(total)
722 });
723 Some(T::Currency::free_balance(who).min(total_locked_now))
724 } else {
725 None
726 }
727 }
728
729 fn add_vesting_schedule(
742 who: &T::AccountId,
743 locked: BalanceOf<T>,
744 per_block: BalanceOf<T>,
745 starting_block: BlockNumberFor<T>,
746 ) -> DispatchResult {
747 if locked.is_zero() {
748 return Ok(())
749 }
750
751 let vesting_schedule = VestingInfo::new(locked, per_block, starting_block);
752 if !vesting_schedule.is_valid() {
754 return Err(Error::<T>::InvalidScheduleParams.into())
755 };
756
757 let mut schedules = Vesting::<T>::get(who).unwrap_or_default();
758
759 ensure!(schedules.try_push(vesting_schedule).is_ok(), Error::<T>::AtMaxVestingSchedules);
762
763 let (schedules, locked_now) =
764 Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
765
766 Self::write_vesting(who, schedules)?;
767 Self::write_lock(who, locked_now);
768
769 Ok(())
770 }
771
772 fn can_add_vesting_schedule(
775 who: &T::AccountId,
776 locked: BalanceOf<T>,
777 per_block: BalanceOf<T>,
778 starting_block: BlockNumberFor<T>,
779 ) -> DispatchResult {
780 if !VestingInfo::new(locked, per_block, starting_block).is_valid() {
782 return Err(Error::<T>::InvalidScheduleParams.into())
783 }
784
785 ensure!(
786 (Vesting::<T>::decode_len(who).unwrap_or_default() as u32) < T::MAX_VESTING_SCHEDULES,
787 Error::<T>::AtMaxVestingSchedules
788 );
789
790 Ok(())
791 }
792
793 fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult {
795 let schedules = Vesting::<T>::get(who).ok_or(Error::<T>::NotVesting)?;
796 let remove_action = VestingAction::Remove { index: schedule_index as usize };
797
798 let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?;
799
800 Self::write_vesting(who, schedules)?;
801 Self::write_lock(who, locked_now);
802 Ok(())
803 }
804}
805
806impl<T: Config> VestedTransfer<T::AccountId> for Pallet<T>
809where
810 BalanceOf<T>: MaybeSerializeDeserialize + Debug,
811{
812 type Currency = T::Currency;
813 type Moment = BlockNumberFor<T>;
814
815 fn vested_transfer(
816 source: &T::AccountId,
817 target: &T::AccountId,
818 locked: BalanceOf<T>,
819 per_block: BalanceOf<T>,
820 starting_block: BlockNumberFor<T>,
821 ) -> DispatchResult {
822 use frame_support::storage::{with_transaction, TransactionOutcome};
823 let schedule = VestingInfo::new(locked, per_block, starting_block);
824 with_transaction(|| -> TransactionOutcome<DispatchResult> {
825 let result = Self::do_vested_transfer(source, target, schedule);
826
827 match &result {
828 Ok(()) => TransactionOutcome::Commit(result),
829 _ => TransactionOutcome::Rollback(result),
830 }
831 })
832 }
833}