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, 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)]
99enum 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>>;
184
185 const MAX_VESTING_SCHEDULES: u32;
187 }
188
189 #[pallet::extra_constants]
190 impl<T: Config> Pallet<T> {
191 #[pallet::constant_name(MaxVestingSchedules)]
192 fn max_vesting_schedules() -> u32 {
193 T::MAX_VESTING_SCHEDULES
194 }
195 }
196
197 #[pallet::hooks]
198 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
199 fn integrity_test() {
200 assert!(T::MAX_VESTING_SCHEDULES > 0, "`MaxVestingSchedules` must ge greater than 0");
201 }
202 }
203
204 #[pallet::storage]
206 pub type Vesting<T: Config> = StorageMap<
207 _,
208 Blake2_128Concat,
209 T::AccountId,
210 BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>,
211 >;
212
213 #[pallet::storage]
217 pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
218
219 #[pallet::pallet]
220 pub struct Pallet<T>(_);
221
222 #[pallet::genesis_config]
223 #[derive(frame_support::DefaultNoBound)]
224 pub struct GenesisConfig<T: Config> {
225 pub vesting: Vec<(T::AccountId, BlockNumberFor<T>, BlockNumberFor<T>, BalanceOf<T>)>,
226 }
227
228 #[pallet::genesis_build]
229 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
230 fn build(&self) {
231 use sp_runtime::traits::Saturating;
232
233 StorageVersion::<T>::put(Releases::V1);
235
236 for &(ref who, begin, length, liquid) in self.vesting.iter() {
242 let balance = T::Currency::free_balance(who);
243 assert!(!balance.is_zero(), "Currencies must be init'd before vesting");
244 let locked = balance.saturating_sub(liquid);
246 let length_as_balance = T::BlockNumberToBalance::convert(length);
247 let per_block = locked / length_as_balance.max(sp_runtime::traits::One::one());
248 let vesting_info = VestingInfo::new(locked, per_block, begin);
249 if !vesting_info.is_valid() {
250 panic!("Invalid VestingInfo params at genesis")
251 };
252
253 Vesting::<T>::try_append(who, vesting_info)
254 .expect("Too many vesting schedules at genesis.");
255
256 let reasons =
257 WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
258
259 T::Currency::set_lock(VESTING_ID, who, locked, reasons);
260 }
261 }
262 }
263
264 #[pallet::event]
265 #[pallet::generate_deposit(pub(super) fn deposit_event)]
266 pub enum Event<T: Config> {
267 VestingUpdated { account: T::AccountId, unvested: BalanceOf<T> },
270 VestingCompleted { account: T::AccountId },
272 }
273
274 #[pallet::error]
276 pub enum Error<T> {
277 NotVesting,
279 AtMaxVestingSchedules,
282 AmountLow,
284 ScheduleIndexOutOfBounds,
286 InvalidScheduleParams,
288 }
289
290 #[pallet::call]
291 impl<T: Config> Pallet<T> {
292 #[pallet::call_index(0)]
302 #[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
303 .max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
304 )]
305 pub fn vest(origin: OriginFor<T>) -> DispatchResult {
306 let who = ensure_signed(origin)?;
307 Self::do_vest(who)
308 }
309
310 #[pallet::call_index(1)]
322 #[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
323 .max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
324 )]
325 pub fn vest_other(origin: OriginFor<T>, target: AccountIdLookupOf<T>) -> DispatchResult {
326 ensure_signed(origin)?;
327 let who = T::Lookup::lookup(target)?;
328 Self::do_vest(who)
329 }
330
331 #[pallet::call_index(2)]
345 #[pallet::weight(
346 T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
347 )]
348 pub fn vested_transfer(
349 origin: OriginFor<T>,
350 target: AccountIdLookupOf<T>,
351 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
352 ) -> DispatchResult {
353 let transactor = ensure_signed(origin)?;
354 let target = T::Lookup::lookup(target)?;
355 Self::do_vested_transfer(&transactor, &target, schedule)
356 }
357
358 #[pallet::call_index(3)]
373 #[pallet::weight(
374 T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
375 )]
376 pub fn force_vested_transfer(
377 origin: OriginFor<T>,
378 source: AccountIdLookupOf<T>,
379 target: AccountIdLookupOf<T>,
380 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
381 ) -> DispatchResult {
382 ensure_root(origin)?;
383 let target = T::Lookup::lookup(target)?;
384 let source = T::Lookup::lookup(source)?;
385 Self::do_vested_transfer(&source, &target, schedule)
386 }
387
388 #[pallet::call_index(4)]
410 #[pallet::weight(
411 T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
412 .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
413 )]
414 pub fn merge_schedules(
415 origin: OriginFor<T>,
416 schedule1_index: u32,
417 schedule2_index: u32,
418 ) -> DispatchResult {
419 let who = ensure_signed(origin)?;
420 if schedule1_index == schedule2_index {
421 return Ok(())
422 };
423 let schedule1_index = schedule1_index as usize;
424 let schedule2_index = schedule2_index as usize;
425
426 let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
427 let merge_action =
428 VestingAction::Merge { index1: schedule1_index, index2: schedule2_index };
429
430 let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?;
431
432 Self::write_vesting(&who, schedules)?;
433 Self::write_lock(&who, locked_now);
434
435 Ok(())
436 }
437
438 #[pallet::call_index(5)]
445 #[pallet::weight(
446 T::WeightInfo::force_remove_vesting_schedule(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
447 )]
448 pub fn force_remove_vesting_schedule(
449 origin: OriginFor<T>,
450 target: <T::Lookup as StaticLookup>::Source,
451 schedule_index: u32,
452 ) -> DispatchResultWithPostInfo {
453 ensure_root(origin)?;
454 let who = T::Lookup::lookup(target)?;
455
456 let schedules_count = Vesting::<T>::decode_len(&who).unwrap_or_default();
457 ensure!(schedule_index < schedules_count as u32, Error::<T>::InvalidScheduleParams);
458
459 Self::remove_vesting_schedule(&who, schedule_index)?;
460
461 Ok(Some(T::WeightInfo::force_remove_vesting_schedule(
462 MaxLocksOf::<T>::get(),
463 schedules_count as u32,
464 ))
465 .into())
466 }
467 }
468}
469
470impl<T: Config> Pallet<T> {
471 pub fn vesting(
473 account: T::AccountId,
474 ) -> Option<BoundedVec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>, MaxVestingSchedulesGet<T>>>
475 {
476 Vesting::<T>::get(account)
477 }
478
479 fn merge_vesting_info(
482 now: BlockNumberFor<T>,
483 schedule1: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
484 schedule2: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
485 ) -> Option<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>> {
486 let schedule1_ending_block = schedule1.ending_block_as_balance::<T::BlockNumberToBalance>();
487 let schedule2_ending_block = schedule2.ending_block_as_balance::<T::BlockNumberToBalance>();
488 let now_as_balance = T::BlockNumberToBalance::convert(now);
489
490 match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
492 (true, true) => return None,
494 (true, false) => return Some(schedule2),
497 (false, true) => return Some(schedule1),
498 _ => {},
500 }
501
502 let locked = schedule1
503 .locked_at::<T::BlockNumberToBalance>(now)
504 .saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
505 debug_assert!(
508 !locked.is_zero(),
509 "merge_vesting_info validation checks failed to catch a locked of 0"
510 );
511
512 let ending_block = schedule1_ending_block.max(schedule2_ending_block);
513 let starting_block = now.max(schedule1.starting_block()).max(schedule2.starting_block());
514
515 let per_block = {
516 let duration = ending_block
517 .saturating_sub(T::BlockNumberToBalance::convert(starting_block))
518 .max(One::one());
519 (locked / duration).max(One::one())
520 };
521
522 let schedule = VestingInfo::new(locked, per_block, starting_block);
523 debug_assert!(schedule.is_valid(), "merge_vesting_info schedule validation check failed");
524
525 Some(schedule)
526 }
527
528 fn do_vested_transfer(
530 source: &T::AccountId,
531 target: &T::AccountId,
532 schedule: VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
533 ) -> DispatchResult {
534 ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
536 if !schedule.is_valid() {
537 return Err(Error::<T>::InvalidScheduleParams.into())
538 };
539
540 Self::can_add_vesting_schedule(
542 target,
543 schedule.locked(),
544 schedule.per_block(),
545 schedule.starting_block(),
546 )?;
547
548 T::Currency::transfer(source, target, schedule.locked(), ExistenceRequirement::AllowDeath)?;
549
550 let res = Self::add_vesting_schedule(
554 target,
555 schedule.locked(),
556 schedule.per_block(),
557 schedule.starting_block(),
558 );
559 debug_assert!(res.is_ok(), "Failed to add a schedule when we had to succeed.");
560
561 Ok(())
562 }
563
564 fn report_schedule_updates(
575 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
576 action: VestingAction,
577 ) -> (Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>) {
578 let now = T::BlockNumberProvider::current_block_number();
579
580 let mut total_locked_now: BalanceOf<T> = Zero::zero();
581 let filtered_schedules = action
582 .pick_schedules::<T>(schedules)
583 .filter(|schedule| {
584 let locked_now = schedule.locked_at::<T::BlockNumberToBalance>(now);
585 let keep = !locked_now.is_zero();
586 if keep {
587 total_locked_now = total_locked_now.saturating_add(locked_now);
588 }
589 keep
590 })
591 .collect::<Vec<_>>();
592
593 (filtered_schedules, total_locked_now)
594 }
595
596 fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf<T>) {
598 if total_locked_now.is_zero() {
599 T::Currency::remove_lock(VESTING_ID, who);
600 Self::deposit_event(Event::<T>::VestingCompleted { account: who.clone() });
601 } else {
602 let reasons = WithdrawReasons::except(T::UnvestedFundsAllowedWithdrawReasons::get());
603 T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons);
604 Self::deposit_event(Event::<T>::VestingUpdated {
605 account: who.clone(),
606 unvested: total_locked_now,
607 });
608 };
609 }
610
611 fn write_vesting(
613 who: &T::AccountId,
614 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
615 ) -> Result<(), DispatchError> {
616 let schedules: BoundedVec<
617 VestingInfo<BalanceOf<T>, BlockNumberFor<T>>,
618 MaxVestingSchedulesGet<T>,
619 > = schedules.try_into().map_err(|_| Error::<T>::AtMaxVestingSchedules)?;
620
621 if schedules.len() == 0 {
622 Vesting::<T>::remove(&who);
623 } else {
624 Vesting::<T>::insert(who, schedules)
625 }
626
627 Ok(())
628 }
629
630 fn do_vest(who: T::AccountId) -> DispatchResult {
632 let schedules = Vesting::<T>::get(&who).ok_or(Error::<T>::NotVesting)?;
633
634 let (schedules, locked_now) =
635 Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
636
637 Self::write_vesting(&who, schedules)?;
638 Self::write_lock(&who, locked_now);
639
640 Ok(())
641 }
642
643 fn exec_action(
646 schedules: Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>,
647 action: VestingAction,
648 ) -> Result<(Vec<VestingInfo<BalanceOf<T>, BlockNumberFor<T>>>, BalanceOf<T>), DispatchError> {
649 let (schedules, locked_now) = match action {
650 VestingAction::Merge { index1: idx1, index2: idx2 } => {
651 let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
654 let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
655
656 let (mut schedules, mut locked_now) =
660 Self::report_schedule_updates(schedules.to_vec(), action);
661
662 let now = T::BlockNumberProvider::current_block_number();
663 if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) {
664 schedules.push(new_schedule);
667 let new_schedule_locked =
669 new_schedule.locked_at::<T::BlockNumberToBalance>(now);
670 locked_now = locked_now.saturating_add(new_schedule_locked);
672 } (schedules, locked_now)
675 },
676 _ => Self::report_schedule_updates(schedules.to_vec(), action),
677 };
678
679 debug_assert!(
680 locked_now > Zero::zero() && schedules.len() > 0 ||
681 locked_now == Zero::zero() && schedules.len() == 0
682 );
683
684 Ok((schedules, locked_now))
685 }
686}
687
688impl<T: Config> VestingSchedule<T::AccountId> for Pallet<T>
689where
690 BalanceOf<T>: MaybeSerializeDeserialize + Debug,
691{
692 type Currency = T::Currency;
693 type Moment = BlockNumberFor<T>;
694
695 fn vesting_balance(who: &T::AccountId) -> Option<BalanceOf<T>> {
697 if let Some(v) = Vesting::<T>::get(who) {
698 let now = T::BlockNumberProvider::current_block_number();
699 let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| {
700 schedule.locked_at::<T::BlockNumberToBalance>(now).saturating_add(total)
701 });
702 Some(T::Currency::free_balance(who).min(total_locked_now))
703 } else {
704 None
705 }
706 }
707
708 fn add_vesting_schedule(
721 who: &T::AccountId,
722 locked: BalanceOf<T>,
723 per_block: BalanceOf<T>,
724 starting_block: BlockNumberFor<T>,
725 ) -> DispatchResult {
726 if locked.is_zero() {
727 return Ok(())
728 }
729
730 let vesting_schedule = VestingInfo::new(locked, per_block, starting_block);
731 if !vesting_schedule.is_valid() {
733 return Err(Error::<T>::InvalidScheduleParams.into())
734 };
735
736 let mut schedules = Vesting::<T>::get(who).unwrap_or_default();
737
738 ensure!(schedules.try_push(vesting_schedule).is_ok(), Error::<T>::AtMaxVestingSchedules);
741
742 let (schedules, locked_now) =
743 Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
744
745 Self::write_vesting(who, schedules)?;
746 Self::write_lock(who, locked_now);
747
748 Ok(())
749 }
750
751 fn can_add_vesting_schedule(
754 who: &T::AccountId,
755 locked: BalanceOf<T>,
756 per_block: BalanceOf<T>,
757 starting_block: BlockNumberFor<T>,
758 ) -> DispatchResult {
759 if !VestingInfo::new(locked, per_block, starting_block).is_valid() {
761 return Err(Error::<T>::InvalidScheduleParams.into())
762 }
763
764 ensure!(
765 (Vesting::<T>::decode_len(who).unwrap_or_default() as u32) < T::MAX_VESTING_SCHEDULES,
766 Error::<T>::AtMaxVestingSchedules
767 );
768
769 Ok(())
770 }
771
772 fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult {
774 let schedules = Vesting::<T>::get(who).ok_or(Error::<T>::NotVesting)?;
775 let remove_action = VestingAction::Remove { index: schedule_index as usize };
776
777 let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?;
778
779 Self::write_vesting(who, schedules)?;
780 Self::write_lock(who, locked_now);
781 Ok(())
782 }
783}
784
785impl<T: Config> VestedTransfer<T::AccountId> for Pallet<T>
788where
789 BalanceOf<T>: MaybeSerializeDeserialize + Debug,
790{
791 type Currency = T::Currency;
792 type Moment = BlockNumberFor<T>;
793
794 fn vested_transfer(
795 source: &T::AccountId,
796 target: &T::AccountId,
797 locked: BalanceOf<T>,
798 per_block: BalanceOf<T>,
799 starting_block: BlockNumberFor<T>,
800 ) -> DispatchResult {
801 use frame_support::storage::{with_transaction, TransactionOutcome};
802 let schedule = VestingInfo::new(locked, per_block, starting_block);
803 with_transaction(|| -> TransactionOutcome<DispatchResult> {
804 let result = Self::do_vested_transfer(source, target, schedule);
805
806 match &result {
807 Ok(()) => TransactionOutcome::Commit(result),
808 _ => TransactionOutcome::Rollback(result),
809 }
810 })
811 }
812}