1#![doc = include_str!("../README.md")]
10#![allow(clippy::expect_used)]
21#![cfg_attr(not(feature = "std"), no_std)]
22#![deny(
24 rustdoc::broken_intra_doc_links,
25 rustdoc::missing_crate_level_docs,
26 rustdoc::invalid_codeblock_attributes,
27 missing_docs
28)]
29
30use core::ops::Mul;
31use frame_support::{
32 dispatch::DispatchResult,
33 ensure,
34 traits::{
35 tokens::fungible::{Inspect as InspectFungible, InspectFreeze, Mutate, MutateFreeze},
36 Get, Hooks,
37 },
38 weights::Weight,
39};
40
41use sp_runtime::{
42 traits::{CheckedAdd, CheckedDiv, One, Saturating, Zero},
43 ArithmeticError, BoundedVec, DispatchError, Perbill, Permill,
44};
45
46pub use common_primitives::{
47 capacity::*,
48 msa::MessageSourceId,
49 node::{AccountId, Balance, BlockNumber},
50 utils::wrap_binary_data,
51};
52use frame_system::pallet_prelude::*;
53
54#[cfg(feature = "runtime-benchmarks")]
55use common_primitives::benchmarks::RegisterProviderBenchmarkHelper;
56
57pub use pallet::*;
58pub use types::*;
59pub use weights::*;
60
61pub mod types;
62
63#[cfg(feature = "runtime-benchmarks")]
64mod benchmarking;
65
66#[cfg(test)]
67mod tests;
68
69pub mod weights;
70type BalanceOf<T> =
71 <<T as Config>::Currency as InspectFungible<<T as frame_system::Config>::AccountId>>::Balance;
72type ChunkIndex = u32;
73
74#[frame_support::pallet]
75pub mod pallet {
76 use super::*;
77
78 use crate::StakingType::*;
79 use common_primitives::capacity::RewardEra;
80 use frame_support::{
81 pallet_prelude::{StorageVersion, *},
82 Twox64Concat,
83 };
84 use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeDisplay};
85
86 #[pallet::composite_enum]
89 pub enum FreezeReason {
90 CapacityStaking,
92 }
93
94 pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
96
97 #[pallet::config]
98 pub trait Config: frame_system::Config {
99 #[allow(deprecated)]
101 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
102
103 type RuntimeFreezeReason: From<FreezeReason>;
105
106 type WeightInfo: WeightInfo;
108
109 type Currency: MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
111 + Mutate<Self::AccountId>
112 + InspectFreeze<Self::AccountId>
113 + InspectFungible<Self::AccountId>;
114
115 type TargetValidator: TargetValidator;
117
118 #[pallet::constant]
120 type MinimumStakingAmount: Get<BalanceOf<Self>>;
121
122 #[pallet::constant]
124 type MinimumTokenBalance: Get<BalanceOf<Self>>;
125
126 #[pallet::constant]
129 type MaxUnlockingChunks: Get<u32>;
130
131 #[cfg(feature = "runtime-benchmarks")]
132 type BenchmarkHelper: RegisterProviderBenchmarkHelper;
134
135 #[pallet::constant]
137 type UnstakingThawPeriod: Get<u16>;
138
139 #[pallet::constant]
141 type MaxEpochLength: Get<BlockNumberFor<Self>>;
142
143 type EpochNumber: Parameter
146 + Member
147 + MaybeSerializeDeserialize
148 + MaybeDisplay
149 + AtLeast32BitUnsigned
150 + Default
151 + Copy
152 + core::hash::Hash
153 + MaxEncodedLen
154 + TypeInfo;
155
156 #[pallet::constant]
158 type CapacityPerToken: Get<Perbill>;
159
160 #[pallet::constant]
162 type EraLength: Get<u32>;
163
164 #[pallet::constant]
169 type ProviderBoostHistoryLimit: Get<u32>;
170
171 type RewardsProvider: ProviderBoostRewardsProvider<Self>;
173
174 #[pallet::constant]
176 type MaxRetargetsPerRewardEra: Get<u32>;
177
178 #[pallet::constant]
180 type RewardPoolPerEra: Get<BalanceOf<Self>>;
181
182 #[pallet::constant]
184 type RewardPercentCap: Get<Permill>;
185
186 #[pallet::constant]
189 type RewardPoolChunkLength: Get<u32>;
190 }
191
192 #[pallet::storage]
196 pub type StakingAccountLedger<T: Config> =
197 StorageMap<_, Twox64Concat, T::AccountId, StakingDetails<T>>;
198
199 #[pallet::storage]
203 pub type StakingTargetLedger<T: Config> = StorageDoubleMap<
204 _,
205 Twox64Concat,
206 T::AccountId,
207 Twox64Concat,
208 MessageSourceId,
209 StakingTargetDetails<BalanceOf<T>>,
210 >;
211
212 #[pallet::storage]
216 pub type CapacityLedger<T: Config> =
217 StorageMap<_, Twox64Concat, MessageSourceId, CapacityDetails<BalanceOf<T>, T::EpochNumber>>;
218
219 #[pallet::storage]
221 #[pallet::whitelist_storage]
222 pub type CurrentEpoch<T: Config> = StorageValue<_, T::EpochNumber, ValueQuery>;
223
224 #[pallet::storage]
226 pub type CurrentEpochInfo<T: Config> =
227 StorageValue<_, EpochInfo<BlockNumberFor<T>>, ValueQuery>;
228
229 #[pallet::type_value]
230 pub fn EpochLengthDefault<T: Config>() -> BlockNumberFor<T> {
232 100u32.into()
233 }
234
235 #[pallet::storage]
237 pub type EpochLength<T: Config> =
238 StorageValue<_, BlockNumberFor<T>, ValueQuery, EpochLengthDefault<T>>;
239
240 #[pallet::storage]
241 pub type UnstakeUnlocks<T: Config> =
242 StorageMap<_, Twox64Concat, T::AccountId, UnlockChunkList<T>>;
243
244 #[pallet::storage]
246 pub type Retargets<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, RetargetInfo<T>>;
247
248 #[pallet::storage]
250 #[pallet::whitelist_storage]
251 pub type CurrentEraInfo<T: Config> =
252 StorageValue<_, RewardEraInfo<RewardEra, BlockNumberFor<T>>, ValueQuery>;
253
254 #[pallet::storage]
258 pub type ProviderBoostRewardPools<T: Config> =
259 StorageMap<_, Twox64Concat, ChunkIndex, RewardPoolHistoryChunk<T>>;
260
261 #[pallet::storage]
263 pub type CurrentEraProviderBoostTotal<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
264
265 #[pallet::storage]
267 pub type ProviderBoostHistories<T: Config> =
268 StorageMap<_, Twox64Concat, T::AccountId, ProviderBoostHistory<T>>;
269
270 #[pallet::pallet]
273 #[pallet::storage_version(STORAGE_VERSION)]
274 pub struct Pallet<T>(_);
275
276 #[pallet::event]
277 #[pallet::generate_deposit(pub (super) fn deposit_event)]
278 pub enum Event<T: Config> {
279 Staked {
281 account: T::AccountId,
283 target: MessageSourceId,
285 amount: BalanceOf<T>,
287 capacity: BalanceOf<T>,
289 },
290 StakeWithdrawn {
292 account: T::AccountId,
294 amount: BalanceOf<T>,
296 },
297 UnStaked {
299 account: T::AccountId,
301 target: MessageSourceId,
303 amount: BalanceOf<T>,
305 capacity: BalanceOf<T>,
307 },
308 EpochLengthUpdated {
310 blocks: BlockNumberFor<T>,
312 },
313 CapacityWithdrawn {
315 msa_id: MessageSourceId,
317 amount: BalanceOf<T>,
319 },
320 StakingTargetChanged {
322 account: T::AccountId,
324 from_msa: MessageSourceId,
326 to_msa: MessageSourceId,
328 amount: BalanceOf<T>,
330 },
331 ProviderBoosted {
333 account: T::AccountId,
335 target: MessageSourceId,
337 amount: BalanceOf<T>,
339 capacity: BalanceOf<T>,
341 },
342 ProviderBoostRewardClaimed {
344 account: T::AccountId,
346 reward_amount: BalanceOf<T>,
348 },
349 }
350
351 #[pallet::error]
352 pub enum Error<T> {
353 InvalidTarget,
355 InsufficientCapacityBalance,
357 StakingAmountBelowMinimum,
359 ZeroAmountNotAllowed,
362 NotAStakingAccount,
364 NoUnstakedTokensAvailable,
367 UnstakedAmountIsZero,
369 InsufficientStakingBalance,
371 StakerTargetRelationshipNotFound,
373 TargetCapacityNotFound,
375 MaxUnlockingChunksExceeded,
378 IncreaseExceedsAvailable,
380 MaxEpochLengthExceeded,
382 BalanceTooLowtoStake,
384 NoThawedTokenAvailable,
386 CannotChangeStakingType,
388 EraOutOfRange,
390 CannotRetargetToSameProvider,
392 NoRewardsEligibleToClaim,
395 MustFirstClaimRewards,
397 MaxRetargetsExceeded,
399 CollectionBoundExceeded,
401 NotAProviderBoostAccount,
403 }
404
405 #[pallet::hooks]
406 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
407 fn on_initialize(current: BlockNumberFor<T>) -> Weight {
408 Self::start_new_epoch_if_needed(current)
409 .saturating_add(Self::start_new_reward_era_if_needed(current))
410 }
411 }
412
413 #[pallet::call]
414 impl<T: Config> Pallet<T> {
415 #[pallet::call_index(0)]
423 #[pallet::weight(T::WeightInfo::stake())]
424 pub fn stake(
425 origin: OriginFor<T>,
426 target: MessageSourceId,
427 amount: BalanceOf<T>,
428 ) -> DispatchResult {
429 let staker = ensure_signed(origin)?;
430
431 let (mut staking_account, actual_amount) =
432 Self::ensure_can_stake(&staker, target, amount, MaximumCapacity)?;
433
434 let capacity = Self::increase_stake_and_issue_capacity(
435 &staker,
436 &mut staking_account,
437 target,
438 actual_amount,
439 )?;
440
441 Self::deposit_event(Event::Staked {
442 account: staker,
443 amount: actual_amount,
444 target,
445 capacity,
446 });
447
448 Ok(())
449 }
450
451 #[pallet::call_index(1)]
458 #[pallet::weight(T::WeightInfo::withdraw_unstaked())]
459 pub fn withdraw_unstaked(origin: OriginFor<T>) -> DispatchResult {
460 let staker = ensure_signed(origin)?;
461 let amount_withdrawn = Self::do_withdraw_unstaked(&staker)?;
462 Self::deposit_event(Event::<T>::StakeWithdrawn {
463 account: staker,
464 amount: amount_withdrawn,
465 });
466 Ok(())
467 }
468
469 #[pallet::call_index(2)]
479 #[pallet::weight(T::WeightInfo::unstake())]
480 pub fn unstake(
481 origin: OriginFor<T>,
482 target: MessageSourceId,
483 requested_amount: BalanceOf<T>,
484 ) -> DispatchResult {
485 let unstaker = ensure_signed(origin)?;
486
487 ensure!(requested_amount > Zero::zero(), Error::<T>::UnstakedAmountIsZero);
488
489 ensure!(!Self::has_unclaimed_rewards(&unstaker), Error::<T>::MustFirstClaimRewards);
490
491 let (actual_amount, staking_type) =
492 Self::decrease_active_staking_balance(&unstaker, requested_amount)?;
493 Self::add_unlock_chunk(&unstaker, actual_amount)?;
494
495 let capacity_reduction =
496 Self::reduce_capacity(&unstaker, target, actual_amount, staking_type)?;
497
498 Self::deposit_event(Event::UnStaked {
499 account: unstaker,
500 target,
501 amount: actual_amount,
502 capacity: capacity_reduction,
503 });
504 Ok(())
505 }
506
507 #[pallet::call_index(3)]
515 #[pallet::weight(T::WeightInfo::set_epoch_length())]
516 pub fn set_epoch_length(origin: OriginFor<T>, length: BlockNumberFor<T>) -> DispatchResult {
517 ensure_root(origin)?;
518 ensure!(length <= T::MaxEpochLength::get(), Error::<T>::MaxEpochLengthExceeded);
519
520 EpochLength::<T>::set(length);
521
522 Self::deposit_event(Event::EpochLengthUpdated { blocks: length });
523 Ok(())
524 }
525
526 #[pallet::call_index(4)]
540 #[pallet::weight(T::WeightInfo::change_staking_target())]
541 pub fn change_staking_target(
542 origin: OriginFor<T>,
543 from: MessageSourceId,
544 to: MessageSourceId,
545 amount: BalanceOf<T>,
546 ) -> DispatchResult {
547 let staker = ensure_signed(origin)?;
548 Self::update_retarget_record(&staker)?;
550 ensure!(from.ne(&to), Error::<T>::CannotRetargetToSameProvider);
551 ensure!(
552 amount >= T::MinimumStakingAmount::get(),
553 Error::<T>::StakingAmountBelowMinimum
554 );
555
556 ensure!(T::TargetValidator::validate(to), Error::<T>::InvalidTarget);
557
558 Self::do_retarget(&staker, &from, &to, &amount)?;
559
560 Self::deposit_event(Event::StakingTargetChanged {
561 account: staker,
562 from_msa: from,
563 to_msa: to,
564 amount,
565 });
566 Ok(())
567 }
568 #[pallet::call_index(5)]
576 #[pallet::weight(T::WeightInfo::provider_boost())]
577 pub fn provider_boost(
578 origin: OriginFor<T>,
579 target: MessageSourceId,
580 amount: BalanceOf<T>,
581 ) -> DispatchResult {
582 let staker = ensure_signed(origin)?;
583 let (mut boosting_details, actual_amount) =
584 Self::ensure_can_boost(&staker, &target, &amount)?;
585
586 let capacity = Self::increase_stake_and_issue_boost_capacity(
587 &staker,
588 &mut boosting_details,
589 &target,
590 &actual_amount,
591 )?;
592
593 Self::deposit_event(Event::ProviderBoosted {
594 account: staker,
595 amount: actual_amount,
596 target,
597 capacity,
598 });
599
600 Ok(())
601 }
602
603 #[pallet::call_index(6)]
610 #[pallet::weight(T::WeightInfo::claim_staking_rewards())]
611 pub fn claim_staking_rewards(origin: OriginFor<T>) -> DispatchResult {
612 let staker = ensure_signed(origin)?;
613 ensure!(
614 ProviderBoostHistories::<T>::contains_key(staker.clone()),
615 Error::<T>::NotAProviderBoostAccount
616 );
617 let total_to_mint = Self::do_claim_rewards(&staker)?;
618 Self::deposit_event(Event::ProviderBoostRewardClaimed {
619 account: staker.clone(),
620 reward_amount: total_to_mint,
621 });
622 Ok(())
623 }
624 }
625}
626
627impl<T: Config> Pallet<T> {
628 fn ensure_can_stake(
639 staker: &T::AccountId,
640 target: MessageSourceId,
641 amount: BalanceOf<T>,
642 staking_type: StakingType,
643 ) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
644 ensure!(amount > Zero::zero(), Error::<T>::ZeroAmountNotAllowed);
645 ensure!(T::TargetValidator::validate(target), Error::<T>::InvalidTarget);
646
647 let staking_details = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
648 if !staking_details.active.is_zero() {
649 ensure!(
650 staking_details.staking_type.eq(&staking_type),
651 Error::<T>::CannotChangeStakingType
652 );
653 }
654
655 let stakable_amount = Self::get_stakable_amount_for(staker, amount);
656
657 ensure!(stakable_amount > Zero::zero(), Error::<T>::BalanceTooLowtoStake);
658 ensure!(
659 stakable_amount >= T::MinimumStakingAmount::get(),
660 Error::<T>::StakingAmountBelowMinimum
661 );
662
663 Ok((staking_details, stakable_amount))
664 }
665
666 fn ensure_can_boost(
667 staker: &T::AccountId,
668 target: &MessageSourceId,
669 amount: &BalanceOf<T>,
670 ) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
671 let (mut staking_details, stakable_amount) =
672 Self::ensure_can_stake(staker, *target, *amount, StakingType::ProviderBoost)?;
673 staking_details.staking_type = StakingType::ProviderBoost;
674 Ok((staking_details, stakable_amount))
675 }
676
677 fn increase_stake_and_issue_capacity(
680 staker: &T::AccountId,
681 staking_account: &mut StakingDetails<T>,
682 target: MessageSourceId,
683 amount: BalanceOf<T>,
684 ) -> Result<BalanceOf<T>, DispatchError> {
685 staking_account.deposit(amount).ok_or(ArithmeticError::Overflow)?;
686
687 let capacity = Self::capacity_generated(amount);
688 let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
689 target_details.deposit(amount, capacity).ok_or(ArithmeticError::Overflow)?;
690
691 let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
692 capacity_details.deposit(&amount, &capacity).ok_or(ArithmeticError::Overflow)?;
693
694 Self::set_staking_account_and_lock(staker, staking_account)?;
695
696 Self::set_target_details_for(staker, target, target_details);
697 Self::set_capacity_for(target, capacity_details);
698
699 Ok(capacity)
700 }
701
702 fn increase_stake_and_issue_boost_capacity(
703 staker: &T::AccountId,
704 staking_details: &mut StakingDetails<T>,
705 target: &MessageSourceId,
706 amount: &BalanceOf<T>,
707 ) -> Result<BalanceOf<T>, DispatchError> {
708 staking_details.deposit(*amount).ok_or(ArithmeticError::Overflow)?;
709 Self::set_staking_account_and_lock(staker, staking_details)?;
710
711 let capacity = Self::capacity_generated(T::RewardsProvider::capacity_boost(*amount));
713
714 let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
715
716 target_details.deposit(*amount, capacity).ok_or(ArithmeticError::Overflow)?;
717 Self::set_target_details_for(staker, *target, target_details);
718
719 let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
720 capacity_details.deposit(amount, &capacity).ok_or(ArithmeticError::Overflow)?;
721 Self::set_capacity_for(*target, capacity_details);
722
723 let era = CurrentEraInfo::<T>::get().era_index;
724 Self::upsert_boost_history(staker, era, *amount, true)?;
725
726 let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
727 CurrentEraProviderBoostTotal::<T>::set(reward_pool_total.saturating_add(*amount));
728
729 Ok(capacity)
730 }
731
732 fn set_staking_account_and_lock(
734 staker: &T::AccountId,
735 staking_account: &StakingDetails<T>,
736 ) -> Result<(), DispatchError> {
737 let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
738 let total_to_lock: BalanceOf<T> = staking_account
739 .active
740 .checked_add(&unlock_chunks_total::<T>(&unlocks))
741 .ok_or(ArithmeticError::Overflow)?;
742 T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_to_lock)?;
743 Self::set_staking_account(staker, staking_account);
744 Ok(())
745 }
746
747 fn set_staking_account(staker: &T::AccountId, staking_account: &StakingDetails<T>) {
748 if staking_account.active.is_zero() {
749 StakingAccountLedger::<T>::set(staker, None);
750 } else {
751 StakingAccountLedger::<T>::insert(staker, staking_account);
752 }
753 }
754
755 fn set_target_details_for(
757 staker: &T::AccountId,
758 target: MessageSourceId,
759 target_details: StakingTargetDetails<BalanceOf<T>>,
760 ) {
761 if target_details.amount.is_zero() {
762 StakingTargetLedger::<T>::remove(staker, target);
763 } else {
764 StakingTargetLedger::<T>::insert(staker, target, target_details);
765 }
766 }
767
768 pub fn set_capacity_for(
770 target: MessageSourceId,
771 capacity_details: CapacityDetails<BalanceOf<T>, T::EpochNumber>,
772 ) {
773 CapacityLedger::<T>::insert(target, capacity_details);
774 }
775
776 fn decrease_active_staking_balance(
781 unstaker: &T::AccountId,
782 amount: BalanceOf<T>,
783 ) -> Result<(BalanceOf<T>, StakingType), DispatchError> {
784 let mut staking_account =
785 StakingAccountLedger::<T>::get(unstaker).ok_or(Error::<T>::NotAStakingAccount)?;
786 ensure!(amount <= staking_account.active, Error::<T>::InsufficientStakingBalance);
787
788 let actual_unstaked_amount = staking_account.withdraw(amount)?;
789 Self::set_staking_account(unstaker, &staking_account);
790
791 let staking_type = staking_account.staking_type;
792 if staking_type == StakingType::ProviderBoost {
793 let era = CurrentEraInfo::<T>::get().era_index;
794 Self::upsert_boost_history(unstaker, era, actual_unstaked_amount, false)?;
795 let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
796 CurrentEraProviderBoostTotal::<T>::set(
797 reward_pool_total.saturating_sub(actual_unstaked_amount),
798 );
799 }
800 Ok((actual_unstaked_amount, staking_type))
801 }
802
803 fn add_unlock_chunk(
804 unstaker: &T::AccountId,
805 actual_unstaked_amount: BalanceOf<T>,
806 ) -> Result<(), DispatchError> {
807 let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
808 let thaw_at =
809 current_epoch.saturating_add(T::EpochNumber::from(T::UnstakingThawPeriod::get()));
810 let mut unlocks = UnstakeUnlocks::<T>::get(unstaker).unwrap_or_default();
811
812 match unlocks.iter_mut().find(|chunk| chunk.thaw_at == thaw_at) {
813 Some(chunk) => {
814 chunk.value += actual_unstaked_amount;
815 },
816 None => {
817 let unlock_chunk: UnlockChunk<BalanceOf<T>, T::EpochNumber> =
818 UnlockChunk { value: actual_unstaked_amount, thaw_at };
819 unlocks
820 .try_push(unlock_chunk)
821 .map_err(|_| Error::<T>::MaxUnlockingChunksExceeded)?;
822 },
823 }
824
825 UnstakeUnlocks::<T>::set(unstaker, Some(unlocks));
826 Ok(())
827 }
828
829 pub(crate) fn get_stakable_amount_for(
831 staker: &T::AccountId,
832 proposed_amount: BalanceOf<T>,
833 ) -> BalanceOf<T> {
834 let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
835
836 let unlock_chunks_sum = unlock_chunks_total::<T>(&unlocks);
837 let freezable_balance = T::Currency::balance_freezable(staker);
838 let current_staking_balance =
839 StakingAccountLedger::<T>::get(staker).unwrap_or_default().active;
840 let stakable_amount = freezable_balance
841 .saturating_sub(current_staking_balance)
842 .saturating_sub(unlock_chunks_sum)
843 .saturating_sub(T::MinimumTokenBalance::get());
844 if stakable_amount >= proposed_amount {
845 proposed_amount
846 } else {
847 Zero::zero()
848 }
849 }
850
851 pub(crate) fn do_withdraw_unstaked(
852 staker: &T::AccountId,
853 ) -> Result<BalanceOf<T>, DispatchError> {
854 let current_epoch = CurrentEpoch::<T>::get();
855 let mut total_unlocking: BalanceOf<T> = Zero::zero();
856
857 let mut unlocks =
858 UnstakeUnlocks::<T>::get(staker).ok_or(Error::<T>::NoUnstakedTokensAvailable)?;
859 let amount_withdrawn = unlock_chunks_reap_thawed::<T>(&mut unlocks, current_epoch);
860 ensure!(!amount_withdrawn.is_zero(), Error::<T>::NoThawedTokenAvailable);
861
862 if unlocks.is_empty() {
863 UnstakeUnlocks::<T>::set(staker, None);
864 } else {
865 total_unlocking = unlock_chunks_total::<T>(&unlocks);
866 UnstakeUnlocks::<T>::set(staker, Some(unlocks));
867 }
868
869 let staking_account = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
870 let total_locked = staking_account.active.saturating_add(total_unlocking);
871 if total_locked.is_zero() {
872 T::Currency::thaw(&FreezeReason::CapacityStaking.into(), staker)?;
873 } else {
874 T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_locked)?;
875 }
876 Ok(amount_withdrawn)
877 }
878
879 #[allow(unused)]
880 fn get_thaw_at_epoch() -> <T as Config>::EpochNumber {
881 let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
882 let thaw_period = T::UnstakingThawPeriod::get();
883 current_epoch.saturating_add(thaw_period.into())
884 }
885
886 fn reduce_capacity(
888 unstaker: &T::AccountId,
889 target: MessageSourceId,
890 amount: BalanceOf<T>,
891 staking_type: StakingType,
892 ) -> Result<BalanceOf<T>, DispatchError> {
893 let mut staking_target_details = StakingTargetLedger::<T>::get(unstaker, target)
894 .ok_or(Error::<T>::StakerTargetRelationshipNotFound)?;
895
896 ensure!(amount.le(&staking_target_details.amount), Error::<T>::InsufficientStakingBalance);
897
898 let mut capacity_details =
899 CapacityLedger::<T>::get(target).ok_or(Error::<T>::TargetCapacityNotFound)?;
900
901 let capacity_to_withdraw = if staking_target_details.amount.eq(&amount) {
902 staking_target_details.capacity
903 } else if staking_type.eq(&StakingType::ProviderBoost) {
904 Perbill::from_rational(amount, staking_target_details.amount)
905 .mul_ceil(staking_target_details.capacity)
906 } else {
907 Self::calculate_capacity_reduction(
910 amount,
911 capacity_details.total_tokens_staked,
912 capacity_details.total_capacity_issued,
913 )
914 };
915
916 let (actual_amount, actual_capacity) = staking_target_details.withdraw(
917 amount,
918 capacity_to_withdraw,
919 T::MinimumStakingAmount::get(),
920 );
921
922 capacity_details.withdraw(actual_capacity, actual_amount);
923
924 Self::set_capacity_for(target, capacity_details);
925 Self::set_target_details_for(unstaker, target, staking_target_details);
926
927 Ok(capacity_to_withdraw)
928 }
929
930 fn capacity_generated(amount: BalanceOf<T>) -> BalanceOf<T> {
932 let cpt = T::CapacityPerToken::get();
933 cpt.mul(amount)
934 }
935
936 fn calculate_capacity_reduction(
939 unstaking_amount: BalanceOf<T>,
940 total_amount_staked: BalanceOf<T>,
941 total_capacity: BalanceOf<T>,
942 ) -> BalanceOf<T> {
943 Perbill::from_rational(unstaking_amount, total_amount_staked).mul_ceil(total_capacity)
944 }
945
946 fn start_new_epoch_if_needed(current_block: BlockNumberFor<T>) -> Weight {
947 if current_block.saturating_sub(CurrentEpochInfo::<T>::get().epoch_start) >=
949 EpochLength::<T>::get()
950 {
951 let current_epoch = CurrentEpoch::<T>::get();
952 CurrentEpoch::<T>::set(current_epoch.saturating_add(1u32.into()));
953 CurrentEpochInfo::<T>::set(EpochInfo { epoch_start: current_block });
954 T::WeightInfo::start_new_epoch_if_needed()
955 } else {
956 T::DbWeight::get().reads(2u64).saturating_add(T::DbWeight::get().writes(1))
958 }
959 }
960
961 fn start_new_reward_era_if_needed(current_block: BlockNumberFor<T>) -> Weight {
962 let current_era_info: RewardEraInfo<RewardEra, BlockNumberFor<T>> =
963 CurrentEraInfo::<T>::get(); if current_block.saturating_sub(current_era_info.started_at) >= T::EraLength::get().into() {
966 let new_era_info = RewardEraInfo {
968 era_index: current_era_info.era_index.saturating_add(One::one()),
969 started_at: current_block,
970 };
971 CurrentEraInfo::<T>::set(new_era_info); let current_reward_pool_total: BalanceOf<T> = CurrentEraProviderBoostTotal::<T>::get(); Self::update_provider_boost_reward_pool(
976 current_era_info.era_index,
977 current_reward_pool_total,
978 );
979 T::WeightInfo::start_new_reward_era_if_needed()
980 } else {
981 T::DbWeight::get().reads(1)
982 }
983 }
984
985 fn update_retarget_record(staker: &T::AccountId) -> Result<(), DispatchError> {
989 let current_era: RewardEra = CurrentEraInfo::<T>::get().era_index;
990 let mut retargets = Retargets::<T>::get(staker).unwrap_or_default();
991 ensure!(retargets.update(current_era).is_some(), Error::<T>::MaxRetargetsExceeded);
992 Retargets::<T>::set(staker, Some(retargets));
993 Ok(())
994 }
995
996 pub(crate) fn do_retarget(
999 staker: &T::AccountId,
1000 from_msa: &MessageSourceId,
1001 to_msa: &MessageSourceId,
1002 amount: &BalanceOf<T>,
1003 ) -> Result<(), DispatchError> {
1004 let staking_type = StakingAccountLedger::<T>::get(staker).unwrap_or_default().staking_type;
1005 let capacity_withdrawn = Self::reduce_capacity(staker, *from_msa, *amount, staking_type)?;
1006
1007 let mut to_msa_target = StakingTargetLedger::<T>::get(staker, to_msa).unwrap_or_default();
1008
1009 to_msa_target
1010 .deposit(*amount, capacity_withdrawn)
1011 .ok_or(ArithmeticError::Overflow)?;
1012
1013 let mut capacity_details = CapacityLedger::<T>::get(to_msa).unwrap_or_default();
1014 capacity_details
1015 .deposit(amount, &capacity_withdrawn)
1016 .ok_or(ArithmeticError::Overflow)?;
1017
1018 Self::set_target_details_for(staker, *to_msa, to_msa_target);
1019 Self::set_capacity_for(*to_msa, capacity_details);
1020 Ok(())
1021 }
1022
1023 pub(crate) fn upsert_boost_history(
1026 account: &T::AccountId,
1027 current_era: RewardEra,
1028 boost_amount: BalanceOf<T>,
1029 add: bool,
1030 ) -> Result<(), DispatchError> {
1031 let mut boost_history = ProviderBoostHistories::<T>::get(account).unwrap_or_default();
1032
1033 let upsert_result = if add {
1034 boost_history.add_era_balance(¤t_era, &boost_amount)
1035 } else {
1036 boost_history.subtract_era_balance(¤t_era, &boost_amount)
1037 };
1038 match upsert_result {
1039 Some(0usize) => ProviderBoostHistories::<T>::remove(account),
1040 None => return Err(DispatchError::from(Error::<T>::EraOutOfRange)),
1041 _ => ProviderBoostHistories::<T>::set(account, Some(boost_history)),
1042 }
1043 Ok(())
1044 }
1045
1046 pub(crate) fn has_unclaimed_rewards(account: &T::AccountId) -> bool {
1047 let current_era = CurrentEraInfo::<T>::get().era_index;
1048 match ProviderBoostHistories::<T>::get(account) {
1049 Some(provider_boost_history) =>
1056 matches!(provider_boost_history.get_earliest_reward_era(),
1057 Some(era) if era < ¤t_era.saturating_sub(1u32)),
1058 None => false,
1059 }
1060 }
1061
1062 pub fn list_unclaimed_rewards(
1065 account: &T::AccountId,
1066 ) -> Result<
1067 BoundedVec<
1068 UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1069 T::ProviderBoostHistoryLimit,
1070 >,
1071 DispatchError,
1072 > {
1073 if !Self::has_unclaimed_rewards(account) {
1074 return Ok(BoundedVec::new());
1075 }
1076
1077 let staking_history = ProviderBoostHistories::<T>::get(account)
1078 .ok_or(Error::<T>::NotAProviderBoostAccount)?; let current_era_info = CurrentEraInfo::<T>::get(); let max_history: u32 = T::ProviderBoostHistoryLimit::get();
1082
1083 let start_era = current_era_info.era_index.saturating_sub(max_history);
1084 let end_era = current_era_info.era_index.saturating_sub(One::one()); let mut previous_amount: BalanceOf<T> = match start_era {
1088 0 => 0u32.into(),
1089 _ => staking_history.get_amount_staked_for_era(&(start_era.saturating_sub(1u32))),
1090 };
1091 let mut unclaimed_rewards: BoundedVec<
1092 UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1093 T::ProviderBoostHistoryLimit,
1094 > = BoundedVec::new();
1095 for reward_era in start_era..=end_era {
1096 let staked_amount = staking_history.get_amount_staked_for_era(&reward_era);
1097 if !staked_amount.is_zero() {
1098 let expires_at_era = reward_era.saturating_add(max_history);
1099 let expires_at_block = Self::block_at_end_of_era(expires_at_era);
1100 let eligible_amount = staked_amount.min(previous_amount);
1101 let total_for_era =
1102 Self::get_total_stake_for_past_era(reward_era, current_era_info.era_index)?;
1103 let earned_amount = <T>::RewardsProvider::era_staking_reward(
1104 eligible_amount,
1105 total_for_era,
1106 T::RewardPoolPerEra::get(),
1107 );
1108 unclaimed_rewards
1109 .try_push(UnclaimedRewardInfo {
1110 reward_era,
1111 expires_at_block,
1112 staked_amount,
1113 eligible_amount,
1114 earned_amount,
1115 })
1116 .map_err(|_e| Error::<T>::CollectionBoundExceeded)?;
1117 previous_amount = staked_amount;
1119 }
1120 } Ok(unclaimed_rewards)
1122 }
1123
1124 pub(crate) fn block_at_end_of_era(era: RewardEra) -> BlockNumberFor<T> {
1127 let current_era_info = CurrentEraInfo::<T>::get();
1128 let era_length: BlockNumberFor<T> = T::EraLength::get().into();
1129
1130 let era_diff = if current_era_info.era_index.eq(&era) {
1131 1u32
1132 } else {
1133 era.saturating_sub(current_era_info.era_index).saturating_add(1u32)
1134 };
1135 current_era_info.started_at + era_length.mul(era_diff.into()) - 1u32.into()
1136 }
1137
1138 pub(crate) fn get_total_stake_for_past_era(
1140 reward_era: RewardEra,
1141 current_era: RewardEra,
1142 ) -> Result<BalanceOf<T>, DispatchError> {
1143 let era_range = current_era.saturating_sub(reward_era);
1145 ensure!(
1146 current_era.gt(&reward_era) && era_range.le(&T::ProviderBoostHistoryLimit::get()),
1147 Error::<T>::EraOutOfRange
1148 );
1149
1150 let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(reward_era);
1151 let reward_pool_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); let total_for_era =
1153 reward_pool_chunk.total_for_era(&reward_era).ok_or(Error::<T>::EraOutOfRange)?;
1154 Ok(*total_for_era)
1155 }
1156
1157 pub(crate) fn get_chunk_index_for_era(era: RewardEra) -> u32 {
1170 let history_limit: u32 = T::ProviderBoostHistoryLimit::get();
1171 let chunk_len = T::RewardPoolChunkLength::get();
1172 let era_u32: u32 = era;
1173
1174 let cycle: u32 = era_u32 % history_limit.saturating_add(chunk_len);
1176 cycle.saturating_div(chunk_len)
1177 }
1178
1179 pub(crate) fn update_provider_boost_reward_pool(era: RewardEra, boost_total: BalanceOf<T>) {
1181 let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(era);
1183 let mut new_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); if new_chunk.is_full() {
1188 new_chunk = RewardPoolHistoryChunk::new();
1189 };
1190
1191 if new_chunk.try_insert(era, boost_total).is_err() {
1192 log::warn!("could not insert a new chunk into provider boost reward pool")
1194 }
1195 ProviderBoostRewardPools::<T>::set(chunk_idx, Some(new_chunk)); }
1197 fn do_claim_rewards(staker: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
1198 let rewards = Self::list_unclaimed_rewards(staker)?;
1199 ensure!(!rewards.len().is_zero(), Error::<T>::NoRewardsEligibleToClaim);
1200 let zero_balance: BalanceOf<T> = 0u32.into();
1201 let total_to_mint: BalanceOf<T> = rewards
1202 .iter()
1203 .fold(zero_balance, |acc, reward_info| acc.saturating_add(reward_info.earned_amount));
1204 ensure!(total_to_mint.gt(&Zero::zero()), Error::<T>::NoRewardsEligibleToClaim);
1205 let _minted_unused = T::Currency::mint_into(staker, total_to_mint)?;
1206
1207 let mut new_history: ProviderBoostHistory<T> = ProviderBoostHistory::new();
1208 let last_staked_amount =
1209 rewards.last().unwrap_or(&UnclaimedRewardInfo::default()).staked_amount;
1210 let current_era = CurrentEraInfo::<T>::get().era_index;
1211 ensure!(
1214 new_history
1215 .add_era_balance(¤t_era.saturating_sub(1u32), &last_staked_amount)
1216 .is_some(),
1217 Error::<T>::CollectionBoundExceeded
1218 );
1219 ProviderBoostHistories::<T>::set(staker, Some(new_history));
1220
1221 Ok(total_to_mint)
1222 }
1223}
1224
1225impl<T: Config> Nontransferable for Pallet<T> {
1228 type Balance = BalanceOf<T>;
1229
1230 fn balance(msa_id: MessageSourceId) -> Self::Balance {
1232 match CapacityLedger::<T>::get(msa_id) {
1233 Some(capacity_details) => capacity_details.remaining_capacity,
1234 None => BalanceOf::<T>::zero(),
1235 }
1236 }
1237
1238 fn replenishable_balance(msa_id: MessageSourceId) -> Self::Balance {
1239 match CapacityLedger::<T>::get(msa_id) {
1240 Some(capacity_details) => capacity_details.total_capacity_issued,
1241 None => BalanceOf::<T>::zero(),
1242 }
1243 }
1244
1245 fn deduct(msa_id: MessageSourceId, amount: Self::Balance) -> Result<(), DispatchError> {
1247 let mut capacity_details =
1248 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1249
1250 capacity_details
1251 .deduct_capacity_by_amount(amount)
1252 .map_err(|_| Error::<T>::InsufficientCapacityBalance)?;
1253
1254 Self::set_capacity_for(msa_id, capacity_details);
1255
1256 Self::deposit_event(Event::CapacityWithdrawn { msa_id, amount });
1257 Ok(())
1258 }
1259
1260 fn deposit(
1262 msa_id: MessageSourceId,
1263 token_amount: Self::Balance,
1264 capacity_amount: Self::Balance,
1265 ) -> Result<(), DispatchError> {
1266 let mut capacity_details =
1267 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1268 capacity_details.deposit(&token_amount, &capacity_amount);
1269 Self::set_capacity_for(msa_id, capacity_details);
1270 Ok(())
1271 }
1272}
1273
1274impl<T: Config> Replenishable for Pallet<T> {
1275 type Balance = BalanceOf<T>;
1276
1277 fn replenish_all_for(msa_id: MessageSourceId) -> Result<(), DispatchError> {
1278 let mut capacity_details =
1279 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1280
1281 capacity_details.replenish_all(&CurrentEpoch::<T>::get());
1282
1283 Self::set_capacity_for(msa_id, capacity_details);
1284
1285 Ok(())
1286 }
1287
1288 fn replenish_by_amount(
1292 msa_id: MessageSourceId,
1293 amount: Self::Balance,
1294 ) -> Result<(), DispatchError> {
1295 let mut capacity_details =
1296 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1297 capacity_details.replenish_by_amount(amount, &CurrentEpoch::<T>::get());
1298 Ok(())
1299 }
1300
1301 fn can_replenish(msa_id: MessageSourceId) -> bool {
1302 if let Some(capacity_details) = CapacityLedger::<T>::get(msa_id) {
1303 return capacity_details.can_replenish(CurrentEpoch::<T>::get());
1304 }
1305 false
1306 }
1307}
1308
1309impl<T: Config> ProviderBoostRewardsProvider<T> for Pallet<T> {
1310 type Balance = BalanceOf<T>;
1311
1312 fn reward_pool_size(_total_staked: Self::Balance) -> Self::Balance {
1313 T::RewardPoolPerEra::get()
1314 }
1315
1316 fn era_staking_reward(
1319 era_amount_staked: Self::Balance,
1320 era_total_staked: Self::Balance,
1321 era_reward_pool_size: Self::Balance,
1322 ) -> Self::Balance {
1323 let capped_reward = T::RewardPercentCap::get().mul(era_amount_staked);
1324 let proportional_reward = era_reward_pool_size
1325 .saturating_mul(era_amount_staked)
1326 .checked_div(&era_total_staked)
1327 .unwrap_or_else(Zero::zero);
1328 proportional_reward.min(capped_reward)
1329 }
1330
1331 fn capacity_boost(amount: Self::Balance) -> Self::Balance {
1333 Perbill::from_percent(STAKED_PERCENTAGE_TO_BOOST).mul(amount)
1334 }
1335}