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 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
101
102 type RuntimeFreezeReason: From<FreezeReason>;
104
105 type WeightInfo: WeightInfo;
107
108 type Currency: MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
110 + Mutate<Self::AccountId>
111 + InspectFreeze<Self::AccountId>
112 + InspectFungible<Self::AccountId>;
113
114 type TargetValidator: TargetValidator;
116
117 #[pallet::constant]
119 type MinimumStakingAmount: Get<BalanceOf<Self>>;
120
121 #[pallet::constant]
123 type MinimumTokenBalance: Get<BalanceOf<Self>>;
124
125 #[pallet::constant]
128 type MaxUnlockingChunks: Get<u32>;
129
130 #[cfg(feature = "runtime-benchmarks")]
131 type BenchmarkHelper: RegisterProviderBenchmarkHelper;
133
134 #[pallet::constant]
136 type UnstakingThawPeriod: Get<u16>;
137
138 #[pallet::constant]
140 type MaxEpochLength: Get<BlockNumberFor<Self>>;
141
142 type EpochNumber: Parameter
145 + Member
146 + MaybeSerializeDeserialize
147 + MaybeDisplay
148 + AtLeast32BitUnsigned
149 + Default
150 + Copy
151 + core::hash::Hash
152 + MaxEncodedLen
153 + TypeInfo;
154
155 #[pallet::constant]
157 type CapacityPerToken: Get<Perbill>;
158
159 #[pallet::constant]
161 type EraLength: Get<u32>;
162
163 #[pallet::constant]
168 type ProviderBoostHistoryLimit: Get<u32>;
169
170 type RewardsProvider: ProviderBoostRewardsProvider<Self>;
172
173 #[pallet::constant]
175 type MaxRetargetsPerRewardEra: Get<u32>;
176
177 #[pallet::constant]
179 type RewardPoolPerEra: Get<BalanceOf<Self>>;
180
181 #[pallet::constant]
183 type RewardPercentCap: Get<Permill>;
184
185 #[pallet::constant]
188 type RewardPoolChunkLength: Get<u32>;
189 }
190
191 #[pallet::storage]
195 pub type StakingAccountLedger<T: Config> =
196 StorageMap<_, Twox64Concat, T::AccountId, StakingDetails<T>>;
197
198 #[pallet::storage]
202 pub type StakingTargetLedger<T: Config> = StorageDoubleMap<
203 _,
204 Twox64Concat,
205 T::AccountId,
206 Twox64Concat,
207 MessageSourceId,
208 StakingTargetDetails<BalanceOf<T>>,
209 >;
210
211 #[pallet::storage]
215 pub type CapacityLedger<T: Config> =
216 StorageMap<_, Twox64Concat, MessageSourceId, CapacityDetails<BalanceOf<T>, T::EpochNumber>>;
217
218 #[pallet::storage]
220 #[pallet::whitelist_storage]
221 pub type CurrentEpoch<T: Config> = StorageValue<_, T::EpochNumber, ValueQuery>;
222
223 #[pallet::storage]
225 pub type CurrentEpochInfo<T: Config> =
226 StorageValue<_, EpochInfo<BlockNumberFor<T>>, ValueQuery>;
227
228 #[pallet::type_value]
229 pub fn EpochLengthDefault<T: Config>() -> BlockNumberFor<T> {
231 100u32.into()
232 }
233
234 #[pallet::storage]
236 pub type EpochLength<T: Config> =
237 StorageValue<_, BlockNumberFor<T>, ValueQuery, EpochLengthDefault<T>>;
238
239 #[pallet::storage]
240 pub type UnstakeUnlocks<T: Config> =
241 StorageMap<_, Twox64Concat, T::AccountId, UnlockChunkList<T>>;
242
243 #[pallet::storage]
245 pub type Retargets<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, RetargetInfo<T>>;
246
247 #[pallet::storage]
249 #[pallet::whitelist_storage]
250 pub type CurrentEraInfo<T: Config> =
251 StorageValue<_, RewardEraInfo<RewardEra, BlockNumberFor<T>>, ValueQuery>;
252
253 #[pallet::storage]
257 pub type ProviderBoostRewardPools<T: Config> =
258 StorageMap<_, Twox64Concat, ChunkIndex, RewardPoolHistoryChunk<T>>;
259
260 #[pallet::storage]
262 pub type CurrentEraProviderBoostTotal<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
263
264 #[pallet::storage]
266 pub type ProviderBoostHistories<T: Config> =
267 StorageMap<_, Twox64Concat, T::AccountId, ProviderBoostHistory<T>>;
268
269 #[pallet::pallet]
272 #[pallet::storage_version(STORAGE_VERSION)]
273 pub struct Pallet<T>(_);
274
275 #[pallet::event]
276 #[pallet::generate_deposit(pub (super) fn deposit_event)]
277 pub enum Event<T: Config> {
278 Staked {
280 account: T::AccountId,
282 target: MessageSourceId,
284 amount: BalanceOf<T>,
286 capacity: BalanceOf<T>,
288 },
289 StakeWithdrawn {
291 account: T::AccountId,
293 amount: BalanceOf<T>,
295 },
296 UnStaked {
298 account: T::AccountId,
300 target: MessageSourceId,
302 amount: BalanceOf<T>,
304 capacity: BalanceOf<T>,
306 },
307 EpochLengthUpdated {
309 blocks: BlockNumberFor<T>,
311 },
312 CapacityWithdrawn {
314 msa_id: MessageSourceId,
316 amount: BalanceOf<T>,
318 },
319 StakingTargetChanged {
321 account: T::AccountId,
323 from_msa: MessageSourceId,
325 to_msa: MessageSourceId,
327 amount: BalanceOf<T>,
329 },
330 ProviderBoosted {
332 account: T::AccountId,
334 target: MessageSourceId,
336 amount: BalanceOf<T>,
338 capacity: BalanceOf<T>,
340 },
341 ProviderBoostRewardClaimed {
343 account: T::AccountId,
345 reward_amount: BalanceOf<T>,
347 },
348 }
349
350 #[pallet::error]
351 pub enum Error<T> {
352 InvalidTarget,
354 InsufficientCapacityBalance,
356 StakingAmountBelowMinimum,
358 ZeroAmountNotAllowed,
361 NotAStakingAccount,
363 NoUnstakedTokensAvailable,
366 UnstakedAmountIsZero,
368 InsufficientStakingBalance,
370 StakerTargetRelationshipNotFound,
372 TargetCapacityNotFound,
374 MaxUnlockingChunksExceeded,
377 IncreaseExceedsAvailable,
379 MaxEpochLengthExceeded,
381 BalanceTooLowtoStake,
383 NoThawedTokenAvailable,
385 CannotChangeStakingType,
387 EraOutOfRange,
389 CannotRetargetToSameProvider,
391 NoRewardsEligibleToClaim,
394 MustFirstClaimRewards,
396 MaxRetargetsExceeded,
398 CollectionBoundExceeded,
400 NotAProviderBoostAccount,
402 }
403
404 #[pallet::hooks]
405 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
406 fn on_initialize(current: BlockNumberFor<T>) -> Weight {
407 Self::start_new_epoch_if_needed(current)
408 .saturating_add(Self::start_new_reward_era_if_needed(current))
409 }
410 }
411
412 #[pallet::call]
413 impl<T: Config> Pallet<T> {
414 #[pallet::call_index(0)]
422 #[pallet::weight(T::WeightInfo::stake())]
423 pub fn stake(
424 origin: OriginFor<T>,
425 target: MessageSourceId,
426 amount: BalanceOf<T>,
427 ) -> DispatchResult {
428 let staker = ensure_signed(origin)?;
429
430 let (mut staking_account, actual_amount) =
431 Self::ensure_can_stake(&staker, target, amount, MaximumCapacity)?;
432
433 let capacity = Self::increase_stake_and_issue_capacity(
434 &staker,
435 &mut staking_account,
436 target,
437 actual_amount,
438 )?;
439
440 Self::deposit_event(Event::Staked {
441 account: staker,
442 amount: actual_amount,
443 target,
444 capacity,
445 });
446
447 Ok(())
448 }
449
450 #[pallet::call_index(1)]
457 #[pallet::weight(T::WeightInfo::withdraw_unstaked())]
458 pub fn withdraw_unstaked(origin: OriginFor<T>) -> DispatchResult {
459 let staker = ensure_signed(origin)?;
460 let amount_withdrawn = Self::do_withdraw_unstaked(&staker)?;
461 Self::deposit_event(Event::<T>::StakeWithdrawn {
462 account: staker,
463 amount: amount_withdrawn,
464 });
465 Ok(())
466 }
467
468 #[pallet::call_index(2)]
478 #[pallet::weight(T::WeightInfo::unstake())]
479 pub fn unstake(
480 origin: OriginFor<T>,
481 target: MessageSourceId,
482 requested_amount: BalanceOf<T>,
483 ) -> DispatchResult {
484 let unstaker = ensure_signed(origin)?;
485
486 ensure!(requested_amount > Zero::zero(), Error::<T>::UnstakedAmountIsZero);
487
488 ensure!(!Self::has_unclaimed_rewards(&unstaker), Error::<T>::MustFirstClaimRewards);
489
490 let (actual_amount, staking_type) =
491 Self::decrease_active_staking_balance(&unstaker, requested_amount)?;
492 Self::add_unlock_chunk(&unstaker, actual_amount)?;
493
494 let capacity_reduction =
495 Self::reduce_capacity(&unstaker, target, actual_amount, staking_type)?;
496
497 Self::deposit_event(Event::UnStaked {
498 account: unstaker,
499 target,
500 amount: actual_amount,
501 capacity: capacity_reduction,
502 });
503 Ok(())
504 }
505
506 #[pallet::call_index(3)]
514 #[pallet::weight(T::WeightInfo::set_epoch_length())]
515 pub fn set_epoch_length(origin: OriginFor<T>, length: BlockNumberFor<T>) -> DispatchResult {
516 ensure_root(origin)?;
517 ensure!(length <= T::MaxEpochLength::get(), Error::<T>::MaxEpochLengthExceeded);
518
519 EpochLength::<T>::set(length);
520
521 Self::deposit_event(Event::EpochLengthUpdated { blocks: length });
522 Ok(())
523 }
524
525 #[pallet::call_index(4)]
539 #[pallet::weight(T::WeightInfo::change_staking_target())]
540 pub fn change_staking_target(
541 origin: OriginFor<T>,
542 from: MessageSourceId,
543 to: MessageSourceId,
544 amount: BalanceOf<T>,
545 ) -> DispatchResult {
546 let staker = ensure_signed(origin)?;
547 Self::update_retarget_record(&staker)?;
549 ensure!(from.ne(&to), Error::<T>::CannotRetargetToSameProvider);
550 ensure!(
551 amount >= T::MinimumStakingAmount::get(),
552 Error::<T>::StakingAmountBelowMinimum
553 );
554
555 ensure!(T::TargetValidator::validate(to), Error::<T>::InvalidTarget);
556
557 Self::do_retarget(&staker, &from, &to, &amount)?;
558
559 Self::deposit_event(Event::StakingTargetChanged {
560 account: staker,
561 from_msa: from,
562 to_msa: to,
563 amount,
564 });
565 Ok(())
566 }
567 #[pallet::call_index(5)]
575 #[pallet::weight(T::WeightInfo::provider_boost())]
576 pub fn provider_boost(
577 origin: OriginFor<T>,
578 target: MessageSourceId,
579 amount: BalanceOf<T>,
580 ) -> DispatchResult {
581 let staker = ensure_signed(origin)?;
582 let (mut boosting_details, actual_amount) =
583 Self::ensure_can_boost(&staker, &target, &amount)?;
584
585 let capacity = Self::increase_stake_and_issue_boost_capacity(
586 &staker,
587 &mut boosting_details,
588 &target,
589 &actual_amount,
590 )?;
591
592 Self::deposit_event(Event::ProviderBoosted {
593 account: staker,
594 amount: actual_amount,
595 target,
596 capacity,
597 });
598
599 Ok(())
600 }
601
602 #[pallet::call_index(6)]
609 #[pallet::weight(T::WeightInfo::claim_staking_rewards())]
610 pub fn claim_staking_rewards(origin: OriginFor<T>) -> DispatchResult {
611 let staker = ensure_signed(origin)?;
612 ensure!(
613 ProviderBoostHistories::<T>::contains_key(staker.clone()),
614 Error::<T>::NotAProviderBoostAccount
615 );
616 let total_to_mint = Self::do_claim_rewards(&staker)?;
617 Self::deposit_event(Event::ProviderBoostRewardClaimed {
618 account: staker.clone(),
619 reward_amount: total_to_mint,
620 });
621 Ok(())
622 }
623 }
624}
625
626impl<T: Config> Pallet<T> {
627 fn ensure_can_stake(
638 staker: &T::AccountId,
639 target: MessageSourceId,
640 amount: BalanceOf<T>,
641 staking_type: StakingType,
642 ) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
643 ensure!(amount > Zero::zero(), Error::<T>::ZeroAmountNotAllowed);
644 ensure!(T::TargetValidator::validate(target), Error::<T>::InvalidTarget);
645
646 let staking_details = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
647 if !staking_details.active.is_zero() {
648 ensure!(
649 staking_details.staking_type.eq(&staking_type),
650 Error::<T>::CannotChangeStakingType
651 );
652 }
653
654 let stakable_amount = Self::get_stakable_amount_for(staker, amount);
655
656 ensure!(stakable_amount > Zero::zero(), Error::<T>::BalanceTooLowtoStake);
657 ensure!(
658 stakable_amount >= T::MinimumStakingAmount::get(),
659 Error::<T>::StakingAmountBelowMinimum
660 );
661
662 Ok((staking_details, stakable_amount))
663 }
664
665 fn ensure_can_boost(
666 staker: &T::AccountId,
667 target: &MessageSourceId,
668 amount: &BalanceOf<T>,
669 ) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
670 let (mut staking_details, stakable_amount) =
671 Self::ensure_can_stake(staker, *target, *amount, StakingType::ProviderBoost)?;
672 staking_details.staking_type = StakingType::ProviderBoost;
673 Ok((staking_details, stakable_amount))
674 }
675
676 fn increase_stake_and_issue_capacity(
679 staker: &T::AccountId,
680 staking_account: &mut StakingDetails<T>,
681 target: MessageSourceId,
682 amount: BalanceOf<T>,
683 ) -> Result<BalanceOf<T>, DispatchError> {
684 staking_account.deposit(amount).ok_or(ArithmeticError::Overflow)?;
685
686 let capacity = Self::capacity_generated(amount);
687 let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
688 target_details.deposit(amount, capacity).ok_or(ArithmeticError::Overflow)?;
689
690 let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
691 capacity_details.deposit(&amount, &capacity).ok_or(ArithmeticError::Overflow)?;
692
693 Self::set_staking_account_and_lock(staker, staking_account)?;
694
695 Self::set_target_details_for(staker, target, target_details);
696 Self::set_capacity_for(target, capacity_details);
697
698 Ok(capacity)
699 }
700
701 fn increase_stake_and_issue_boost_capacity(
702 staker: &T::AccountId,
703 staking_details: &mut StakingDetails<T>,
704 target: &MessageSourceId,
705 amount: &BalanceOf<T>,
706 ) -> Result<BalanceOf<T>, DispatchError> {
707 staking_details.deposit(*amount).ok_or(ArithmeticError::Overflow)?;
708 Self::set_staking_account_and_lock(staker, staking_details)?;
709
710 let capacity = Self::capacity_generated(T::RewardsProvider::capacity_boost(*amount));
712
713 let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
714
715 target_details.deposit(*amount, capacity).ok_or(ArithmeticError::Overflow)?;
716 Self::set_target_details_for(staker, *target, target_details);
717
718 let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
719 capacity_details.deposit(amount, &capacity).ok_or(ArithmeticError::Overflow)?;
720 Self::set_capacity_for(*target, capacity_details);
721
722 let era = CurrentEraInfo::<T>::get().era_index;
723 Self::upsert_boost_history(staker, era, *amount, true)?;
724
725 let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
726 CurrentEraProviderBoostTotal::<T>::set(reward_pool_total.saturating_add(*amount));
727
728 Ok(capacity)
729 }
730
731 fn set_staking_account_and_lock(
733 staker: &T::AccountId,
734 staking_account: &StakingDetails<T>,
735 ) -> Result<(), DispatchError> {
736 let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
737 let total_to_lock: BalanceOf<T> = staking_account
738 .active
739 .checked_add(&unlock_chunks_total::<T>(&unlocks))
740 .ok_or(ArithmeticError::Overflow)?;
741 T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_to_lock)?;
742 Self::set_staking_account(staker, staking_account);
743 Ok(())
744 }
745
746 fn set_staking_account(staker: &T::AccountId, staking_account: &StakingDetails<T>) {
747 if staking_account.active.is_zero() {
748 StakingAccountLedger::<T>::set(staker, None);
749 } else {
750 StakingAccountLedger::<T>::insert(staker, staking_account);
751 }
752 }
753
754 fn set_target_details_for(
756 staker: &T::AccountId,
757 target: MessageSourceId,
758 target_details: StakingTargetDetails<BalanceOf<T>>,
759 ) {
760 if target_details.amount.is_zero() {
761 StakingTargetLedger::<T>::remove(staker, target);
762 } else {
763 StakingTargetLedger::<T>::insert(staker, target, target_details);
764 }
765 }
766
767 pub fn set_capacity_for(
769 target: MessageSourceId,
770 capacity_details: CapacityDetails<BalanceOf<T>, T::EpochNumber>,
771 ) {
772 CapacityLedger::<T>::insert(target, capacity_details);
773 }
774
775 fn decrease_active_staking_balance(
780 unstaker: &T::AccountId,
781 amount: BalanceOf<T>,
782 ) -> Result<(BalanceOf<T>, StakingType), DispatchError> {
783 let mut staking_account =
784 StakingAccountLedger::<T>::get(unstaker).ok_or(Error::<T>::NotAStakingAccount)?;
785 ensure!(amount <= staking_account.active, Error::<T>::InsufficientStakingBalance);
786
787 let actual_unstaked_amount = staking_account.withdraw(amount)?;
788 Self::set_staking_account(unstaker, &staking_account);
789
790 let staking_type = staking_account.staking_type;
791 if staking_type == StakingType::ProviderBoost {
792 let era = CurrentEraInfo::<T>::get().era_index;
793 Self::upsert_boost_history(unstaker, era, actual_unstaked_amount, false)?;
794 let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
795 CurrentEraProviderBoostTotal::<T>::set(
796 reward_pool_total.saturating_sub(actual_unstaked_amount),
797 );
798 }
799 Ok((actual_unstaked_amount, staking_type))
800 }
801
802 fn add_unlock_chunk(
803 unstaker: &T::AccountId,
804 actual_unstaked_amount: BalanceOf<T>,
805 ) -> Result<(), DispatchError> {
806 let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
807 let thaw_at =
808 current_epoch.saturating_add(T::EpochNumber::from(T::UnstakingThawPeriod::get()));
809 let mut unlocks = UnstakeUnlocks::<T>::get(unstaker).unwrap_or_default();
810
811 match unlocks.iter_mut().find(|chunk| chunk.thaw_at == thaw_at) {
812 Some(chunk) => {
813 chunk.value += actual_unstaked_amount;
814 },
815 None => {
816 let unlock_chunk: UnlockChunk<BalanceOf<T>, T::EpochNumber> =
817 UnlockChunk { value: actual_unstaked_amount, thaw_at };
818 unlocks
819 .try_push(unlock_chunk)
820 .map_err(|_| Error::<T>::MaxUnlockingChunksExceeded)?;
821 },
822 }
823
824 UnstakeUnlocks::<T>::set(unstaker, Some(unlocks));
825 Ok(())
826 }
827
828 pub(crate) fn get_stakable_amount_for(
830 staker: &T::AccountId,
831 proposed_amount: BalanceOf<T>,
832 ) -> BalanceOf<T> {
833 let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
834
835 let unlock_chunks_sum = unlock_chunks_total::<T>(&unlocks);
836 let freezable_balance = T::Currency::balance_freezable(staker);
837 let current_staking_balance =
838 StakingAccountLedger::<T>::get(staker).unwrap_or_default().active;
839 let stakable_amount = freezable_balance
840 .saturating_sub(current_staking_balance)
841 .saturating_sub(unlock_chunks_sum)
842 .saturating_sub(T::MinimumTokenBalance::get());
843 if stakable_amount >= proposed_amount {
844 proposed_amount
845 } else {
846 Zero::zero()
847 }
848 }
849
850 pub(crate) fn do_withdraw_unstaked(
851 staker: &T::AccountId,
852 ) -> Result<BalanceOf<T>, DispatchError> {
853 let current_epoch = CurrentEpoch::<T>::get();
854 let mut total_unlocking: BalanceOf<T> = Zero::zero();
855
856 let mut unlocks =
857 UnstakeUnlocks::<T>::get(staker).ok_or(Error::<T>::NoUnstakedTokensAvailable)?;
858 let amount_withdrawn = unlock_chunks_reap_thawed::<T>(&mut unlocks, current_epoch);
859 ensure!(!amount_withdrawn.is_zero(), Error::<T>::NoThawedTokenAvailable);
860
861 if unlocks.is_empty() {
862 UnstakeUnlocks::<T>::set(staker, None);
863 } else {
864 total_unlocking = unlock_chunks_total::<T>(&unlocks);
865 UnstakeUnlocks::<T>::set(staker, Some(unlocks));
866 }
867
868 let staking_account = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
869 let total_locked = staking_account.active.saturating_add(total_unlocking);
870 if total_locked.is_zero() {
871 T::Currency::thaw(&FreezeReason::CapacityStaking.into(), staker)?;
872 } else {
873 T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_locked)?;
874 }
875 Ok(amount_withdrawn)
876 }
877
878 #[allow(unused)]
879 fn get_thaw_at_epoch() -> <T as Config>::EpochNumber {
880 let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
881 let thaw_period = T::UnstakingThawPeriod::get();
882 current_epoch.saturating_add(thaw_period.into())
883 }
884
885 fn reduce_capacity(
887 unstaker: &T::AccountId,
888 target: MessageSourceId,
889 amount: BalanceOf<T>,
890 staking_type: StakingType,
891 ) -> Result<BalanceOf<T>, DispatchError> {
892 let mut staking_target_details = StakingTargetLedger::<T>::get(unstaker, target)
893 .ok_or(Error::<T>::StakerTargetRelationshipNotFound)?;
894
895 ensure!(amount.le(&staking_target_details.amount), Error::<T>::InsufficientStakingBalance);
896
897 let mut capacity_details =
898 CapacityLedger::<T>::get(target).ok_or(Error::<T>::TargetCapacityNotFound)?;
899
900 let capacity_to_withdraw = if staking_target_details.amount.eq(&amount) {
901 staking_target_details.capacity
902 } else if staking_type.eq(&StakingType::ProviderBoost) {
903 Perbill::from_rational(amount, staking_target_details.amount)
904 .mul_ceil(staking_target_details.capacity)
905 } else {
906 Self::calculate_capacity_reduction(
909 amount,
910 capacity_details.total_tokens_staked,
911 capacity_details.total_capacity_issued,
912 )
913 };
914
915 let (actual_amount, actual_capacity) = staking_target_details.withdraw(
916 amount,
917 capacity_to_withdraw,
918 T::MinimumStakingAmount::get(),
919 );
920
921 capacity_details.withdraw(actual_capacity, actual_amount);
922
923 Self::set_capacity_for(target, capacity_details);
924 Self::set_target_details_for(unstaker, target, staking_target_details);
925
926 Ok(capacity_to_withdraw)
927 }
928
929 fn capacity_generated(amount: BalanceOf<T>) -> BalanceOf<T> {
931 let cpt = T::CapacityPerToken::get();
932 cpt.mul(amount)
933 }
934
935 fn calculate_capacity_reduction(
938 unstaking_amount: BalanceOf<T>,
939 total_amount_staked: BalanceOf<T>,
940 total_capacity: BalanceOf<T>,
941 ) -> BalanceOf<T> {
942 Perbill::from_rational(unstaking_amount, total_amount_staked).mul_ceil(total_capacity)
943 }
944
945 fn start_new_epoch_if_needed(current_block: BlockNumberFor<T>) -> Weight {
946 if current_block.saturating_sub(CurrentEpochInfo::<T>::get().epoch_start) >=
948 EpochLength::<T>::get()
949 {
950 let current_epoch = CurrentEpoch::<T>::get();
951 CurrentEpoch::<T>::set(current_epoch.saturating_add(1u32.into()));
952 CurrentEpochInfo::<T>::set(EpochInfo { epoch_start: current_block });
953 T::WeightInfo::start_new_epoch_if_needed()
954 } else {
955 T::DbWeight::get().reads(2u64).saturating_add(T::DbWeight::get().writes(1))
957 }
958 }
959
960 fn start_new_reward_era_if_needed(current_block: BlockNumberFor<T>) -> Weight {
961 let current_era_info: RewardEraInfo<RewardEra, BlockNumberFor<T>> =
962 CurrentEraInfo::<T>::get(); if current_block.saturating_sub(current_era_info.started_at) >= T::EraLength::get().into() {
965 let new_era_info = RewardEraInfo {
967 era_index: current_era_info.era_index.saturating_add(One::one()),
968 started_at: current_block,
969 };
970 CurrentEraInfo::<T>::set(new_era_info); let current_reward_pool_total: BalanceOf<T> = CurrentEraProviderBoostTotal::<T>::get(); Self::update_provider_boost_reward_pool(
975 current_era_info.era_index,
976 current_reward_pool_total,
977 );
978 T::WeightInfo::start_new_reward_era_if_needed()
979 } else {
980 T::DbWeight::get().reads(1)
981 }
982 }
983
984 fn update_retarget_record(staker: &T::AccountId) -> Result<(), DispatchError> {
988 let current_era: RewardEra = CurrentEraInfo::<T>::get().era_index;
989 let mut retargets = Retargets::<T>::get(staker).unwrap_or_default();
990 ensure!(retargets.update(current_era).is_some(), Error::<T>::MaxRetargetsExceeded);
991 Retargets::<T>::set(staker, Some(retargets));
992 Ok(())
993 }
994
995 pub(crate) fn do_retarget(
998 staker: &T::AccountId,
999 from_msa: &MessageSourceId,
1000 to_msa: &MessageSourceId,
1001 amount: &BalanceOf<T>,
1002 ) -> Result<(), DispatchError> {
1003 let staking_type = StakingAccountLedger::<T>::get(staker).unwrap_or_default().staking_type;
1004 let capacity_withdrawn = Self::reduce_capacity(staker, *from_msa, *amount, staking_type)?;
1005
1006 let mut to_msa_target = StakingTargetLedger::<T>::get(staker, to_msa).unwrap_or_default();
1007
1008 to_msa_target
1009 .deposit(*amount, capacity_withdrawn)
1010 .ok_or(ArithmeticError::Overflow)?;
1011
1012 let mut capacity_details = CapacityLedger::<T>::get(to_msa).unwrap_or_default();
1013 capacity_details
1014 .deposit(amount, &capacity_withdrawn)
1015 .ok_or(ArithmeticError::Overflow)?;
1016
1017 Self::set_target_details_for(staker, *to_msa, to_msa_target);
1018 Self::set_capacity_for(*to_msa, capacity_details);
1019 Ok(())
1020 }
1021
1022 pub(crate) fn upsert_boost_history(
1025 account: &T::AccountId,
1026 current_era: RewardEra,
1027 boost_amount: BalanceOf<T>,
1028 add: bool,
1029 ) -> Result<(), DispatchError> {
1030 let mut boost_history = ProviderBoostHistories::<T>::get(account).unwrap_or_default();
1031
1032 let upsert_result = if add {
1033 boost_history.add_era_balance(¤t_era, &boost_amount)
1034 } else {
1035 boost_history.subtract_era_balance(¤t_era, &boost_amount)
1036 };
1037 match upsert_result {
1038 Some(0usize) => ProviderBoostHistories::<T>::remove(account),
1039 None => return Err(DispatchError::from(Error::<T>::EraOutOfRange)),
1040 _ => ProviderBoostHistories::<T>::set(account, Some(boost_history)),
1041 }
1042 Ok(())
1043 }
1044
1045 pub(crate) fn has_unclaimed_rewards(account: &T::AccountId) -> bool {
1046 let current_era = CurrentEraInfo::<T>::get().era_index;
1047 match ProviderBoostHistories::<T>::get(account) {
1048 Some(provider_boost_history) =>
1055 matches!(provider_boost_history.get_earliest_reward_era(),
1056 Some(era) if era < ¤t_era.saturating_sub(1u32)),
1057 None => false,
1058 }
1059 }
1060
1061 pub fn list_unclaimed_rewards(
1064 account: &T::AccountId,
1065 ) -> Result<
1066 BoundedVec<
1067 UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1068 T::ProviderBoostHistoryLimit,
1069 >,
1070 DispatchError,
1071 > {
1072 if !Self::has_unclaimed_rewards(account) {
1073 return Ok(BoundedVec::new());
1074 }
1075
1076 let staking_history = ProviderBoostHistories::<T>::get(account)
1077 .ok_or(Error::<T>::NotAProviderBoostAccount)?; let current_era_info = CurrentEraInfo::<T>::get(); let max_history: u32 = T::ProviderBoostHistoryLimit::get();
1081
1082 let start_era = current_era_info.era_index.saturating_sub(max_history);
1083 let end_era = current_era_info.era_index.saturating_sub(One::one()); let mut previous_amount: BalanceOf<T> = match start_era {
1087 0 => 0u32.into(),
1088 _ => staking_history.get_amount_staked_for_era(&(start_era.saturating_sub(1u32))),
1089 };
1090 let mut unclaimed_rewards: BoundedVec<
1091 UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1092 T::ProviderBoostHistoryLimit,
1093 > = BoundedVec::new();
1094 for reward_era in start_era..=end_era {
1095 let staked_amount = staking_history.get_amount_staked_for_era(&reward_era);
1096 if !staked_amount.is_zero() {
1097 let expires_at_era = reward_era.saturating_add(max_history);
1098 let expires_at_block = Self::block_at_end_of_era(expires_at_era);
1099 let eligible_amount = staked_amount.min(previous_amount);
1100 let total_for_era =
1101 Self::get_total_stake_for_past_era(reward_era, current_era_info.era_index)?;
1102 let earned_amount = <T>::RewardsProvider::era_staking_reward(
1103 eligible_amount,
1104 total_for_era,
1105 T::RewardPoolPerEra::get(),
1106 );
1107 unclaimed_rewards
1108 .try_push(UnclaimedRewardInfo {
1109 reward_era,
1110 expires_at_block,
1111 staked_amount,
1112 eligible_amount,
1113 earned_amount,
1114 })
1115 .map_err(|_e| Error::<T>::CollectionBoundExceeded)?;
1116 previous_amount = staked_amount;
1118 }
1119 } Ok(unclaimed_rewards)
1121 }
1122
1123 pub(crate) fn block_at_end_of_era(era: RewardEra) -> BlockNumberFor<T> {
1126 let current_era_info = CurrentEraInfo::<T>::get();
1127 let era_length: BlockNumberFor<T> = T::EraLength::get().into();
1128
1129 let era_diff = if current_era_info.era_index.eq(&era) {
1130 1u32
1131 } else {
1132 era.saturating_sub(current_era_info.era_index).saturating_add(1u32)
1133 };
1134 current_era_info.started_at + era_length.mul(era_diff.into()) - 1u32.into()
1135 }
1136
1137 pub(crate) fn get_total_stake_for_past_era(
1139 reward_era: RewardEra,
1140 current_era: RewardEra,
1141 ) -> Result<BalanceOf<T>, DispatchError> {
1142 let era_range = current_era.saturating_sub(reward_era);
1144 ensure!(
1145 current_era.gt(&reward_era) && era_range.le(&T::ProviderBoostHistoryLimit::get()),
1146 Error::<T>::EraOutOfRange
1147 );
1148
1149 let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(reward_era);
1150 let reward_pool_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); let total_for_era =
1152 reward_pool_chunk.total_for_era(&reward_era).ok_or(Error::<T>::EraOutOfRange)?;
1153 Ok(*total_for_era)
1154 }
1155
1156 pub(crate) fn get_chunk_index_for_era(era: RewardEra) -> u32 {
1169 let history_limit: u32 = T::ProviderBoostHistoryLimit::get();
1170 let chunk_len = T::RewardPoolChunkLength::get();
1171 let era_u32: u32 = era;
1172
1173 let cycle: u32 = era_u32 % history_limit.saturating_add(chunk_len);
1175 cycle.saturating_div(chunk_len)
1176 }
1177
1178 pub(crate) fn update_provider_boost_reward_pool(era: RewardEra, boost_total: BalanceOf<T>) {
1180 let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(era);
1182 let mut new_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); if new_chunk.is_full() {
1187 new_chunk = RewardPoolHistoryChunk::new();
1188 };
1189
1190 if new_chunk.try_insert(era, boost_total).is_err() {
1191 log::warn!("could not insert a new chunk into provider boost reward pool")
1193 }
1194 ProviderBoostRewardPools::<T>::set(chunk_idx, Some(new_chunk)); }
1196 fn do_claim_rewards(staker: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
1197 let rewards = Self::list_unclaimed_rewards(staker)?;
1198 ensure!(!rewards.len().is_zero(), Error::<T>::NoRewardsEligibleToClaim);
1199 let zero_balance: BalanceOf<T> = 0u32.into();
1200 let total_to_mint: BalanceOf<T> = rewards
1201 .iter()
1202 .fold(zero_balance, |acc, reward_info| acc.saturating_add(reward_info.earned_amount));
1203 ensure!(total_to_mint.gt(&Zero::zero()), Error::<T>::NoRewardsEligibleToClaim);
1204 let _minted_unused = T::Currency::mint_into(staker, total_to_mint)?;
1205
1206 let mut new_history: ProviderBoostHistory<T> = ProviderBoostHistory::new();
1207 let last_staked_amount =
1208 rewards.last().unwrap_or(&UnclaimedRewardInfo::default()).staked_amount;
1209 let current_era = CurrentEraInfo::<T>::get().era_index;
1210 ensure!(
1213 new_history
1214 .add_era_balance(¤t_era.saturating_sub(1u32), &last_staked_amount)
1215 .is_some(),
1216 Error::<T>::CollectionBoundExceeded
1217 );
1218 ProviderBoostHistories::<T>::set(staker, Some(new_history));
1219
1220 Ok(total_to_mint)
1221 }
1222}
1223
1224impl<T: Config> Nontransferable for Pallet<T> {
1227 type Balance = BalanceOf<T>;
1228
1229 fn balance(msa_id: MessageSourceId) -> Self::Balance {
1231 match CapacityLedger::<T>::get(msa_id) {
1232 Some(capacity_details) => capacity_details.remaining_capacity,
1233 None => BalanceOf::<T>::zero(),
1234 }
1235 }
1236
1237 fn replenishable_balance(msa_id: MessageSourceId) -> Self::Balance {
1238 match CapacityLedger::<T>::get(msa_id) {
1239 Some(capacity_details) => capacity_details.total_capacity_issued,
1240 None => BalanceOf::<T>::zero(),
1241 }
1242 }
1243
1244 fn deduct(msa_id: MessageSourceId, amount: Self::Balance) -> Result<(), DispatchError> {
1246 let mut capacity_details =
1247 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1248
1249 capacity_details
1250 .deduct_capacity_by_amount(amount)
1251 .map_err(|_| Error::<T>::InsufficientCapacityBalance)?;
1252
1253 Self::set_capacity_for(msa_id, capacity_details);
1254
1255 Self::deposit_event(Event::CapacityWithdrawn { msa_id, amount });
1256 Ok(())
1257 }
1258
1259 fn deposit(
1261 msa_id: MessageSourceId,
1262 token_amount: Self::Balance,
1263 capacity_amount: Self::Balance,
1264 ) -> Result<(), DispatchError> {
1265 let mut capacity_details =
1266 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1267 capacity_details.deposit(&token_amount, &capacity_amount);
1268 Self::set_capacity_for(msa_id, capacity_details);
1269 Ok(())
1270 }
1271}
1272
1273impl<T: Config> Replenishable for Pallet<T> {
1274 type Balance = BalanceOf<T>;
1275
1276 fn replenish_all_for(msa_id: MessageSourceId) -> Result<(), DispatchError> {
1277 let mut capacity_details =
1278 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1279
1280 capacity_details.replenish_all(&CurrentEpoch::<T>::get());
1281
1282 Self::set_capacity_for(msa_id, capacity_details);
1283
1284 Ok(())
1285 }
1286
1287 fn replenish_by_amount(
1291 msa_id: MessageSourceId,
1292 amount: Self::Balance,
1293 ) -> Result<(), DispatchError> {
1294 let mut capacity_details =
1295 CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1296 capacity_details.replenish_by_amount(amount, &CurrentEpoch::<T>::get());
1297 Ok(())
1298 }
1299
1300 fn can_replenish(msa_id: MessageSourceId) -> bool {
1301 if let Some(capacity_details) = CapacityLedger::<T>::get(msa_id) {
1302 return capacity_details.can_replenish(CurrentEpoch::<T>::get());
1303 }
1304 false
1305 }
1306}
1307
1308impl<T: Config> ProviderBoostRewardsProvider<T> for Pallet<T> {
1309 type Balance = BalanceOf<T>;
1310
1311 fn reward_pool_size(_total_staked: Self::Balance) -> Self::Balance {
1312 T::RewardPoolPerEra::get()
1313 }
1314
1315 fn era_staking_reward(
1318 era_amount_staked: Self::Balance,
1319 era_total_staked: Self::Balance,
1320 era_reward_pool_size: Self::Balance,
1321 ) -> Self::Balance {
1322 let capped_reward = T::RewardPercentCap::get().mul(era_amount_staked);
1323 let proportional_reward = era_reward_pool_size
1324 .saturating_mul(era_amount_staked)
1325 .checked_div(&era_total_staked)
1326 .unwrap_or_else(Zero::zero);
1327 proportional_reward.min(capped_reward)
1328 }
1329
1330 fn capacity_boost(amount: Self::Balance) -> Self::Balance {
1332 Perbill::from_percent(STAKED_PERCENTAGE_TO_BOOST).mul(amount)
1333 }
1334}