1#![cfg_attr(not(feature = "std"), no_std)]
37
38use frame_support::{
39 pallet_prelude::*,
40 traits::{
41 fungible::{Inspect as FunInspect, MutateFreeze as FunMutateFreeze},
42 SafeModeNotify, StorageVersion,
43 },
44 weights::Weight,
45};
46use frame_system::pallet_prelude::*;
47use sp_arithmetic::fixed_point::FixedU128;
48use sp_runtime::{
49 traits::{One, Saturating, UniqueSaturatedInto, Zero},
50 Perbill, Permill, SaturatedConversion,
51};
52pub use sp_std::vec::Vec;
53
54use astar_primitives::{
55 dapp_staking::{
56 AccountCheck, CycleConfiguration, DAppId, EraNumber, Observer as DAppStakingObserver,
57 PeriodNumber, Rank, RankedTier, SmartContractHandle, StakingRewardHandler, TierId,
58 TierSlots as TierSlotFunc, STANDARD_TIER_SLOTS_ARGS,
59 },
60 oracle::PriceProvider,
61 Balance, BlockNumber,
62};
63
64pub use pallet::*;
65
66#[cfg(test)]
67mod test;
68
69#[cfg(feature = "runtime-benchmarks")]
70mod benchmarking;
71
72mod types;
73pub use types::*;
74
75pub mod migration;
76pub mod weights;
77
78pub use weights::WeightInfo;
79
80const LOG_TARGET: &str = "dapp-staking";
81
82pub(crate) enum TierAssignment {
84 Real,
86 #[cfg(feature = "runtime-benchmarks")]
88 Dummy,
89}
90
91#[doc = include_str!("../README.md")]
92#[frame_support::pallet]
93pub mod pallet {
94 use super::*;
95
96 pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(10);
98
99 #[pallet::pallet]
100 #[pallet::storage_version(STORAGE_VERSION)]
101 pub struct Pallet<T>(_);
102
103 #[cfg(feature = "runtime-benchmarks")]
104 pub trait BenchmarkHelper<SmartContract, AccountId> {
105 fn get_smart_contract(id: u32) -> SmartContract;
106
107 fn set_balance(account: &AccountId, balance: Balance);
108 }
109
110 #[pallet::config]
111 pub trait Config: frame_system::Config {
112 #[allow(deprecated)]
114 type RuntimeEvent: From<Event<Self>>
115 + IsType<<Self as frame_system::Config>::RuntimeEvent>
116 + TryInto<Event<Self>>;
117
118 type RuntimeFreezeReason: From<FreezeReason>;
120
121 type Currency: FunMutateFreeze<
124 Self::AccountId,
125 Id = Self::RuntimeFreezeReason,
126 Balance = Balance,
127 >;
128
129 type SmartContract: Parameter
131 + Member
132 + MaxEncodedLen
133 + SmartContractHandle<Self::AccountId>;
134
135 type ContractRegisterOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
137
138 type ContractUnregisterOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
140
141 type ManagerOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
143
144 type NativePriceProvider: PriceProvider;
146
147 type StakingRewardHandler: StakingRewardHandler<Self::AccountId>;
149
150 type CycleConfiguration: CycleConfiguration;
152
153 type Observers: DAppStakingObserver;
155
156 type AccountCheck: AccountCheck<Self::AccountId>;
158
159 type TierSlots: TierSlotFunc;
161
162 #[pallet::constant]
171 type BaseNativeCurrencyPrice: Get<FixedU128>;
172
173 #[pallet::constant]
175 type EraRewardSpanLength: Get<u32>;
176
177 #[pallet::constant]
180 type RewardRetentionInPeriods: Get<PeriodNumber>;
181
182 #[pallet::constant]
184 type MaxNumberOfContracts: Get<u32>;
185
186 #[pallet::constant]
188 type MaxUnlockingChunks: Get<u32>;
189
190 #[pallet::constant]
192 type MinimumLockedAmount: Get<Balance>;
193
194 #[pallet::constant]
197 type UnlockingPeriod: Get<EraNumber>;
198
199 #[pallet::constant]
201 type MaxNumberOfStakedContracts: Get<u32>;
202
203 #[pallet::constant]
205 type MinimumStakeAmount: Get<Balance>;
206
207 #[pallet::constant]
209 type NumberOfTiers: Get<u32>;
210
211 #[pallet::constant]
213 type RankingEnabled: Get<bool>;
214
215 #[pallet::constant]
219 type MaxBonusSafeMovesPerPeriod: Get<u8>;
220
221 type WeightInfo: WeightInfo;
223
224 #[cfg(feature = "runtime-benchmarks")]
226 type BenchmarkHelper: BenchmarkHelper<Self::SmartContract, Self::AccountId>;
227 }
228
229 #[pallet::event]
230 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
231 pub enum Event<T: Config> {
232 MaintenanceMode { enabled: bool },
234 NewEra { era: EraNumber },
236 NewSubperiod {
238 subperiod: Subperiod,
239 number: PeriodNumber,
240 },
241 DAppRegistered {
243 owner: T::AccountId,
244 smart_contract: T::SmartContract,
245 dapp_id: DAppId,
246 },
247 DAppRewardDestinationUpdated {
249 smart_contract: T::SmartContract,
250 beneficiary: Option<T::AccountId>,
251 },
252 DAppOwnerChanged {
254 smart_contract: T::SmartContract,
255 new_owner: T::AccountId,
256 },
257 DAppUnregistered {
259 smart_contract: T::SmartContract,
260 era: EraNumber,
261 },
262 Locked {
264 account: T::AccountId,
265 amount: Balance,
266 },
267 Unlocking {
269 account: T::AccountId,
270 amount: Balance,
271 },
272 ClaimedUnlocked {
274 account: T::AccountId,
275 amount: Balance,
276 },
277 Relock {
279 account: T::AccountId,
280 amount: Balance,
281 },
282 Stake {
284 account: T::AccountId,
285 smart_contract: T::SmartContract,
286 amount: Balance,
287 },
288 Unstake {
290 account: T::AccountId,
291 smart_contract: T::SmartContract,
292 amount: Balance,
293 },
294 Reward {
296 account: T::AccountId,
297 era: EraNumber,
298 amount: Balance,
299 },
300 BonusReward {
302 account: T::AccountId,
303 smart_contract: T::SmartContract,
304 period: PeriodNumber,
305 amount: Balance,
306 },
307 DAppReward {
309 beneficiary: T::AccountId,
310 smart_contract: T::SmartContract,
311 tier_id: TierId,
312 rank: Rank,
313 era: EraNumber,
314 amount: Balance,
315 },
316 UnstakeFromUnregistered {
318 account: T::AccountId,
319 smart_contract: T::SmartContract,
320 amount: Balance,
321 },
322 ExpiredEntriesRemoved { account: T::AccountId, count: u16 },
324 Force { forcing_type: ForcingType },
326 StakeMoved {
328 account: T::AccountId,
329 source_contract: T::SmartContract,
330 destination_contract: T::SmartContract,
331 amount: Balance,
332 },
333 NewTierParameters {
335 params: TierParameters<T::NumberOfTiers>,
336 },
337 }
338
339 #[pallet::error]
340 pub enum Error<T> {
341 Disabled,
343 ContractAlreadyExists,
345 ExceededMaxNumberOfContracts,
347 NewDAppIdUnavailable,
350 ContractNotFound,
352 OriginNotOwner,
354 ZeroAmount,
356 LockedAmountBelowThreshold,
358 AccountNotAvailableForDappStaking,
360 TooManyUnlockingChunks,
362 RemainingStakePreventsFullUnlock,
364 NoUnlockedChunksToClaim,
366 NoUnlockingChunks,
368 UnavailableStakeFunds,
370 UnclaimedRewards,
372 InternalStakeError,
374 InsufficientStakeAmount,
376 PeriodEndsInNextEra,
378 UnstakeFromPastPeriod,
380 UnstakeAmountTooLarge,
382 NoStakingInfo,
384 InternalUnstakeError,
386 RewardExpired,
388 RewardPayoutFailed,
390 NoClaimableRewards,
392 InternalClaimStakerError,
394 NotEligibleForBonusReward,
396 InternalClaimBonusError,
398 InvalidClaimEra,
400 NoDAppTierInfo,
403 InternalClaimDAppError,
405 ContractStillActive,
407 TooManyStakedContracts,
409 NoExpiredEntries,
411 ForceNotAllowed,
413 InvalidTierParams,
415 SameContracts,
417 }
418
419 #[pallet::storage]
421 #[pallet::whitelist_storage]
422 pub type ActiveProtocolState<T: Config> = StorageValue<_, ProtocolState, ValueQuery>;
423
424 #[pallet::storage]
426 pub type NextDAppId<T: Config> = StorageValue<_, DAppId, ValueQuery>;
427
428 #[pallet::storage]
433 pub type IntegratedDApps<T: Config> = CountedStorageMap<
434 Hasher = Blake2_128Concat,
435 Key = T::SmartContract,
436 Value = DAppInfo<T::AccountId>,
437 QueryKind = OptionQuery,
438 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
439 >;
440
441 #[pallet::storage]
443 pub type Ledger<T: Config> =
444 StorageMap<_, Blake2_128Concat, T::AccountId, AccountLedgerFor<T>, ValueQuery>;
445
446 #[pallet::storage]
448 pub type StakerInfo<T: Config> = StorageDoubleMap<
449 _,
450 Blake2_128Concat,
451 T::AccountId,
452 Blake2_128Concat,
453 T::SmartContract,
454 SingularStakingInfo,
455 OptionQuery,
456 >;
457
458 #[pallet::storage]
460 pub type ContractStake<T: Config> = StorageMap<
461 Hasher = Twox64Concat,
462 Key = DAppId,
463 Value = ContractStakeAmount,
464 QueryKind = ValueQuery,
465 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
466 >;
467
468 #[pallet::storage]
470 pub type CurrentEraInfo<T: Config> = StorageValue<_, EraInfo, ValueQuery>;
471
472 #[pallet::storage]
482 pub type EraRewards<T: Config> =
483 StorageMap<_, Twox64Concat, EraNumber, EraRewardSpan<T::EraRewardSpanLength>, OptionQuery>;
484
485 #[pallet::storage]
487 pub type PeriodEnd<T: Config> =
488 StorageMap<_, Twox64Concat, PeriodNumber, PeriodEndInfo, OptionQuery>;
489
490 #[pallet::storage]
492 pub type StaticTierParams<T: Config> =
493 StorageValue<_, TierParameters<T::NumberOfTiers>, ValueQuery>;
494
495 #[pallet::storage]
497 pub type TierConfig<T: Config> = StorageValue<
498 _,
499 TiersConfiguration<T::NumberOfTiers, T::TierSlots, T::BaseNativeCurrencyPrice>,
500 ValueQuery,
501 >;
502
503 #[pallet::storage]
505 pub type DAppTiers<T: Config> =
506 StorageMap<_, Twox64Concat, EraNumber, DAppTierRewardsFor<T>, OptionQuery>;
507
508 #[pallet::storage]
510 pub type HistoryCleanupMarker<T: Config> = StorageValue<_, CleanupMarker, ValueQuery>;
511
512 #[pallet::type_value]
513 pub fn DefaultSafeguard<T: Config>() -> bool {
514 true
517 }
518
519 #[pallet::storage]
523 pub type Safeguard<T: Config> = StorageValue<_, bool, ValueQuery, DefaultSafeguard<T>>;
524
525 #[pallet::genesis_config]
526 pub struct GenesisConfig<T: Config> {
527 pub reward_portion: Vec<Permill>,
528 pub slot_distribution: Vec<Permill>,
529 pub tier_thresholds: Vec<TierThreshold>,
530 pub slot_number_args: (u64, u64),
531 pub slots_per_tier: Vec<u16>,
532 pub safeguard: Option<bool>,
533 #[serde(skip)]
534 pub _config: PhantomData<T>,
535 }
536
537 impl<T: Config> Default for GenesisConfig<T> {
538 fn default() -> Self {
539 use sp_std::vec;
540 let num_tiers = T::NumberOfTiers::get();
541 Self {
542 reward_portion: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize],
543 slot_distribution: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize],
544 tier_thresholds: (0..num_tiers)
545 .rev()
546 .map(|i| TierThreshold::FixedPercentage {
547 required_percentage: Perbill::from_percent(i),
548 })
549 .collect(),
550 slot_number_args: STANDARD_TIER_SLOTS_ARGS,
551 slots_per_tier: vec![100; num_tiers as usize],
552 safeguard: None,
553 _config: Default::default(),
554 }
555 }
556 }
557
558 #[pallet::genesis_build]
559 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
560 fn build(&self) {
561 let tier_params = TierParameters::<T::NumberOfTiers> {
563 reward_portion: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
564 self.reward_portion.clone(),
565 )
566 .expect("Invalid number of reward portions provided."),
567 slot_distribution: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
568 self.slot_distribution.clone(),
569 )
570 .expect("Invalid number of slot distributions provided."),
571 tier_thresholds: BoundedVec::<TierThreshold, T::NumberOfTiers>::try_from(
572 self.tier_thresholds.clone(),
573 )
574 .expect("Invalid number of tier thresholds provided."),
575 slot_number_args: self.slot_number_args,
576 };
577 assert!(
578 tier_params.is_valid(),
579 "Invalid tier parameters values provided."
580 );
581
582 let total_issuance = T::Currency::total_issuance();
583 let tier_thresholds = tier_params
584 .tier_thresholds
585 .iter()
586 .map(|t| t.threshold(total_issuance))
587 .collect::<Vec<Balance>>()
588 .try_into()
589 .expect("Invalid number of tier thresholds provided.");
590
591 let tier_config =
592 TiersConfiguration::<T::NumberOfTiers, T::TierSlots, T::BaseNativeCurrencyPrice> {
593 slots_per_tier: BoundedVec::<u16, T::NumberOfTiers>::try_from(
594 self.slots_per_tier.clone(),
595 )
596 .expect("Invalid number of slots per tier entries provided."),
597 reward_portion: tier_params.reward_portion.clone(),
598 tier_thresholds,
599 _phantom: Default::default(),
600 };
601 assert!(
602 tier_config.is_valid(),
603 "Invalid tier config values provided."
604 );
605
606 let protocol_state = ProtocolState {
608 era: 1,
609 next_era_start: Pallet::<T>::blocks_per_voting_period()
610 .checked_add(1)
611 .expect("Must not overflow - especially not at genesis."),
612 period_info: PeriodInfo {
613 number: 1,
614 subperiod: Subperiod::Voting,
615 next_subperiod_start_era: 2,
616 },
617 maintenance: false,
618 };
619
620 ActiveProtocolState::<T>::put(protocol_state);
622 StaticTierParams::<T>::put(tier_params);
623 TierConfig::<T>::put(tier_config.clone());
624
625 if self.safeguard.is_some() {
626 Safeguard::<T>::put(self.safeguard.unwrap());
627 }
628 }
629 }
630
631 #[pallet::hooks]
632 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
633 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
634 let now = now.saturated_into();
635 Self::era_and_period_handler(now, TierAssignment::Real)
636 }
637
638 fn on_idle(_block: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
639 Self::expired_entry_cleanup(&remaining_weight)
640 }
641
642 fn integrity_test() {
643 assert!(T::EraRewardSpanLength::get() > 0);
646 assert!(T::RewardRetentionInPeriods::get() > 0);
647 assert!(T::MaxNumberOfContracts::get() > 0);
648 assert!(T::MaxUnlockingChunks::get() > 0);
649 assert!(T::UnlockingPeriod::get() > 0);
650 assert!(T::MaxNumberOfStakedContracts::get() > 0);
651
652 assert!(T::MinimumLockedAmount::get() > 0);
653 assert!(T::MinimumStakeAmount::get() > 0);
654 assert!(T::MinimumLockedAmount::get() >= T::MinimumStakeAmount::get());
655
656 assert!(T::CycleConfiguration::periods_per_cycle() > 0);
658 assert!(T::CycleConfiguration::eras_per_voting_subperiod() > 0);
659 assert!(T::CycleConfiguration::eras_per_build_and_earn_subperiod() > 0);
660 assert!(T::CycleConfiguration::blocks_per_era() > 0);
661 }
662
663 #[cfg(feature = "try-runtime")]
664 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
665 Self::do_try_state()?;
666 Ok(())
667 }
668 }
669
670 #[pallet::composite_enum]
672 pub enum FreezeReason {
673 #[codec(index = 0)]
675 DAppStaking,
676 }
677
678 #[pallet::call]
679 impl<T: Config> Pallet<T> {
680 #[pallet::call_index(4)]
684 #[pallet::weight(T::WeightInfo::unlock())]
685 pub fn unbond_and_unstake(
686 origin: OriginFor<T>,
687 _contract_id: T::SmartContract,
688 #[pallet::compact] value: Balance,
689 ) -> DispatchResult {
690 Self::unlock(origin, value)
692 }
693
694 #[pallet::call_index(5)]
699 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
700 pub fn withdraw_unbonded(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
701 Self::claim_unlocked(origin)
702 }
703
704 #[pallet::call_index(0)]
707 #[pallet::weight(T::WeightInfo::maintenance_mode())]
708 pub fn maintenance_mode(origin: OriginFor<T>, enabled: bool) -> DispatchResult {
709 T::ManagerOrigin::ensure_origin(origin)?;
710 Self::set_maintenance_mode(enabled);
711 Ok(())
712 }
713
714 #[pallet::call_index(1)]
719 #[pallet::weight(T::WeightInfo::register())]
720 pub fn register(
721 origin: OriginFor<T>,
722 owner: T::AccountId,
723 smart_contract: T::SmartContract,
724 ) -> DispatchResult {
725 Self::ensure_pallet_enabled()?;
726 T::ContractRegisterOrigin::ensure_origin(origin)?;
727
728 ensure!(
729 !IntegratedDApps::<T>::contains_key(&smart_contract),
730 Error::<T>::ContractAlreadyExists,
731 );
732
733 ensure!(
734 IntegratedDApps::<T>::count() < T::MaxNumberOfContracts::get().into(),
735 Error::<T>::ExceededMaxNumberOfContracts
736 );
737
738 let dapp_id = NextDAppId::<T>::get();
739 ensure!(dapp_id < DAppId::MAX, Error::<T>::NewDAppIdUnavailable);
741
742 IntegratedDApps::<T>::insert(
743 &smart_contract,
744 DAppInfo {
745 owner: owner.clone(),
746 id: dapp_id,
747 reward_beneficiary: None,
748 },
749 );
750
751 NextDAppId::<T>::put(dapp_id.saturating_add(1));
752
753 Self::deposit_event(Event::<T>::DAppRegistered {
754 owner,
755 smart_contract,
756 dapp_id,
757 });
758
759 Ok(())
760 }
761
762 #[pallet::call_index(2)]
768 #[pallet::weight(T::WeightInfo::set_dapp_reward_beneficiary())]
769 pub fn set_dapp_reward_beneficiary(
770 origin: OriginFor<T>,
771 smart_contract: T::SmartContract,
772 beneficiary: Option<T::AccountId>,
773 ) -> DispatchResult {
774 Self::ensure_pallet_enabled()?;
775 let dev_account = ensure_signed(origin)?;
776
777 IntegratedDApps::<T>::try_mutate(
778 &smart_contract,
779 |maybe_dapp_info| -> DispatchResult {
780 let dapp_info = maybe_dapp_info
781 .as_mut()
782 .ok_or(Error::<T>::ContractNotFound)?;
783
784 ensure!(dapp_info.owner == dev_account, Error::<T>::OriginNotOwner);
785
786 dapp_info.reward_beneficiary = beneficiary.clone();
787
788 Ok(())
789 },
790 )?;
791
792 Self::deposit_event(Event::<T>::DAppRewardDestinationUpdated {
793 smart_contract,
794 beneficiary,
795 });
796
797 Ok(())
798 }
799
800 #[pallet::call_index(3)]
807 #[pallet::weight(T::WeightInfo::set_dapp_owner())]
808 pub fn set_dapp_owner(
809 origin: OriginFor<T>,
810 smart_contract: T::SmartContract,
811 new_owner: T::AccountId,
812 ) -> DispatchResult {
813 Self::ensure_pallet_enabled()?;
814 let origin = ensure_signed_or_root(origin)?;
815
816 IntegratedDApps::<T>::try_mutate(
817 &smart_contract,
818 |maybe_dapp_info| -> DispatchResult {
819 let dapp_info = maybe_dapp_info
820 .as_mut()
821 .ok_or(Error::<T>::ContractNotFound)?;
822
823 if let Some(caller) = origin {
825 ensure!(dapp_info.owner == caller, Error::<T>::OriginNotOwner);
826 }
827
828 dapp_info.owner = new_owner.clone();
829
830 Ok(())
831 },
832 )?;
833
834 Self::deposit_event(Event::<T>::DAppOwnerChanged {
835 smart_contract,
836 new_owner,
837 });
838
839 Ok(())
840 }
841
842 #[pallet::call_index(6)]
847 #[pallet::weight(T::WeightInfo::unregister())]
848 pub fn unregister(
849 origin: OriginFor<T>,
850 smart_contract: T::SmartContract,
851 ) -> DispatchResult {
852 Self::ensure_pallet_enabled()?;
853 T::ContractUnregisterOrigin::ensure_origin(origin)?;
854
855 let dapp_info =
856 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
857
858 ContractStake::<T>::remove(&dapp_info.id);
859 IntegratedDApps::<T>::remove(&smart_contract);
860
861 let current_era = ActiveProtocolState::<T>::get().era;
862 Self::deposit_event(Event::<T>::DAppUnregistered {
863 smart_contract,
864 era: current_era,
865 });
866
867 Ok(())
868 }
869
870 #[pallet::call_index(7)]
877 #[pallet::weight(T::WeightInfo::lock_new_account().max(T::WeightInfo::lock_existing_account()))]
878 pub fn lock(
879 origin: OriginFor<T>,
880 #[pallet::compact] amount: Balance,
881 ) -> DispatchResultWithPostInfo {
882 Self::ensure_pallet_enabled()?;
883 let account = ensure_signed(origin)?;
884
885 let mut ledger = Ledger::<T>::get(&account);
886
887 let is_new_account = ledger.is_empty();
891 if is_new_account {
892 ensure!(
893 T::AccountCheck::allowed_to_stake(&account),
894 Error::<T>::AccountNotAvailableForDappStaking
895 );
896 }
897
898 let available_balance =
900 T::Currency::total_balance(&account).saturating_sub(ledger.total_locked_amount());
901 let amount_to_lock = available_balance.min(amount);
902 ensure!(!amount_to_lock.is_zero(), Error::<T>::ZeroAmount);
903
904 ledger.add_lock_amount(amount_to_lock);
905
906 ensure!(
907 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
908 Error::<T>::LockedAmountBelowThreshold
909 );
910
911 Self::update_ledger(&account, ledger)?;
912 CurrentEraInfo::<T>::mutate(|era_info| {
913 era_info.add_locked(amount_to_lock);
914 });
915
916 Self::deposit_event(Event::<T>::Locked {
917 account,
918 amount: amount_to_lock,
919 });
920
921 Ok(Some(if is_new_account {
922 T::WeightInfo::lock_new_account()
923 } else {
924 T::WeightInfo::lock_existing_account()
925 })
926 .into())
927 }
928
929 #[pallet::call_index(8)]
935 #[pallet::weight(T::WeightInfo::unlock())]
936 pub fn unlock(origin: OriginFor<T>, #[pallet::compact] amount: Balance) -> DispatchResult {
937 Self::ensure_pallet_enabled()?;
938 let account = ensure_signed(origin)?;
939
940 let state = ActiveProtocolState::<T>::get();
941 let mut ledger = Ledger::<T>::get(&account);
942
943 let available_for_unlocking = ledger.unlockable_amount(state.period_info.number);
944 let amount_to_unlock = available_for_unlocking.min(amount);
945
946 let remaining_amount = ledger
948 .active_locked_amount()
949 .saturating_sub(amount_to_unlock);
950 let amount_to_unlock = if remaining_amount < T::MinimumLockedAmount::get() {
951 ensure!(
952 ledger.staked_amount(state.period_info.number).is_zero(),
953 Error::<T>::RemainingStakePreventsFullUnlock
954 );
955 ledger.active_locked_amount()
956 } else {
957 amount_to_unlock
958 };
959
960 ensure!(!amount_to_unlock.is_zero(), Error::<T>::ZeroAmount);
962
963 ledger.subtract_lock_amount(amount_to_unlock);
965
966 let current_block = frame_system::Pallet::<T>::block_number();
967 let unlock_block = current_block.saturating_add(Self::unlocking_period().into());
968 ledger
969 .add_unlocking_chunk(amount_to_unlock, unlock_block.saturated_into())
970 .map_err(|_| Error::<T>::TooManyUnlockingChunks)?;
971
972 Self::update_ledger(&account, ledger)?;
974 CurrentEraInfo::<T>::mutate(|era_info| {
975 era_info.unlocking_started(amount_to_unlock);
976 });
977
978 Self::deposit_event(Event::<T>::Unlocking {
979 account,
980 amount: amount_to_unlock,
981 });
982
983 Ok(())
984 }
985
986 #[pallet::call_index(9)]
988 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
989 pub fn claim_unlocked(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
990 Self::ensure_pallet_enabled()?;
991 let account = ensure_signed(origin)?;
992
993 Self::internal_claim_unlocked(account)
994 }
995
996 #[pallet::call_index(10)]
997 #[pallet::weight(T::WeightInfo::relock_unlocking())]
998 pub fn relock_unlocking(origin: OriginFor<T>) -> DispatchResult {
999 Self::ensure_pallet_enabled()?;
1000 let account = ensure_signed(origin)?;
1001
1002 let mut ledger = Ledger::<T>::get(&account);
1003
1004 ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockingChunks);
1005
1006 let amount = ledger.consume_unlocking_chunks();
1007
1008 ledger.add_lock_amount(amount);
1009 ensure!(
1010 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
1011 Error::<T>::LockedAmountBelowThreshold
1012 );
1013
1014 Self::update_ledger(&account, ledger)?;
1015 CurrentEraInfo::<T>::mutate(|era_info| {
1016 era_info.add_locked(amount);
1017 era_info.unlocking_removed(amount);
1018 });
1019
1020 Self::deposit_event(Event::<T>::Relock { account, amount });
1021
1022 Ok(())
1023 }
1024
1025 #[pallet::call_index(11)]
1034 #[pallet::weight(T::WeightInfo::stake())]
1035 pub fn stake(
1036 origin: OriginFor<T>,
1037 smart_contract: T::SmartContract,
1038 #[pallet::compact] amount: Balance,
1039 ) -> DispatchResult {
1040 Self::ensure_pallet_enabled()?;
1041 let account = ensure_signed(origin)?;
1042
1043 let protocol_state = ActiveProtocolState::<T>::get();
1049 let (stake_amount, bonus_status) = match protocol_state.subperiod() {
1050 Subperiod::Voting => (
1051 StakeAmount {
1052 voting: amount,
1053 build_and_earn: 0,
1054 era: protocol_state.era,
1055 period: protocol_state.period_number(),
1056 },
1057 *BonusStatusWrapperFor::<T>::default(),
1058 ),
1059 Subperiod::BuildAndEarn => (
1060 StakeAmount {
1061 voting: 0,
1062 build_and_earn: amount,
1063 era: protocol_state.era,
1064 period: protocol_state.period_number(),
1065 },
1066 0,
1067 ),
1068 };
1069
1070 Self::inner_stake(&account, &smart_contract, stake_amount, bonus_status)?;
1072
1073 Self::deposit_event(Event::<T>::Stake {
1074 account,
1075 smart_contract,
1076 amount,
1077 });
1078
1079 Ok(())
1080 }
1081
1082 #[pallet::call_index(12)]
1092 #[pallet::weight(T::WeightInfo::unstake())]
1093 pub fn unstake(
1094 origin: OriginFor<T>,
1095 smart_contract: T::SmartContract,
1096 #[pallet::compact] amount: Balance,
1097 ) -> DispatchResult {
1098 Self::ensure_pallet_enabled()?;
1099 let account = ensure_signed(origin)?;
1100
1101 let (unstake_amount, _) = Self::inner_unstake(&account, &smart_contract, amount)?;
1102
1103 Self::deposit_event(Event::<T>::Unstake {
1104 account,
1105 smart_contract,
1106 amount: unstake_amount.total(),
1107 });
1108
1109 Ok(())
1110 }
1111
1112 #[pallet::call_index(13)]
1115 #[pallet::weight({
1116 let max_span_length = T::EraRewardSpanLength::get();
1117 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1118 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1119 })]
1120 pub fn claim_staker_rewards(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1121 Self::ensure_pallet_enabled()?;
1122 let account = ensure_signed(origin)?;
1123
1124 Self::internal_claim_staker_rewards_for(account)
1125 }
1126
1127 #[pallet::call_index(14)]
1129 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1130 pub fn claim_bonus_reward(
1131 origin: OriginFor<T>,
1132 smart_contract: T::SmartContract,
1133 ) -> DispatchResult {
1134 Self::ensure_pallet_enabled()?;
1135 let account = ensure_signed(origin)?;
1136
1137 Self::internal_claim_bonus_reward_for(account, smart_contract)
1138 }
1139
1140 #[pallet::call_index(15)]
1142 #[pallet::weight(T::WeightInfo::claim_dapp_reward())]
1143 pub fn claim_dapp_reward(
1144 origin: OriginFor<T>,
1145 smart_contract: T::SmartContract,
1146 #[pallet::compact] era: EraNumber,
1147 ) -> DispatchResult {
1148 Self::ensure_pallet_enabled()?;
1149
1150 let _ = ensure_signed(origin)?;
1152
1153 let dapp_info =
1154 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1155
1156 let protocol_state = ActiveProtocolState::<T>::get();
1158 ensure!(era < protocol_state.era, Error::<T>::InvalidClaimEra);
1159
1160 let mut dapp_tiers = DAppTiers::<T>::get(&era).ok_or(Error::<T>::NoDAppTierInfo)?;
1162 ensure!(
1163 dapp_tiers.period >= Self::oldest_claimable_period(protocol_state.period_number()),
1164 Error::<T>::RewardExpired
1165 );
1166
1167 let (amount, ranked_tier) =
1168 dapp_tiers
1169 .try_claim(dapp_info.id)
1170 .map_err(|error| match error {
1171 DAppTierError::NoDAppInTiers => Error::<T>::NoClaimableRewards,
1172 _ => Error::<T>::InternalClaimDAppError,
1173 })?;
1174
1175 let (tier_id, rank) = ranked_tier.deconstruct();
1176
1177 let beneficiary = dapp_info.reward_beneficiary();
1179 T::StakingRewardHandler::payout_reward(&beneficiary, amount)
1180 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
1181
1182 DAppTiers::<T>::insert(&era, dapp_tiers);
1184
1185 Self::deposit_event(Event::<T>::DAppReward {
1186 beneficiary: beneficiary.clone(),
1187 smart_contract,
1188 tier_id,
1189 rank,
1190 era,
1191 amount,
1192 });
1193
1194 Ok(())
1195 }
1196
1197 #[pallet::call_index(16)]
1200 #[pallet::weight(T::WeightInfo::unstake_from_unregistered())]
1201 pub fn unstake_from_unregistered(
1202 origin: OriginFor<T>,
1203 smart_contract: T::SmartContract,
1204 ) -> DispatchResult {
1205 Self::ensure_pallet_enabled()?;
1206 let account = ensure_signed(origin)?;
1207
1208 let (unstake_amount, _) =
1209 Self::inner_unstake_from_unregistered(&account, &smart_contract)?;
1210
1211 Self::deposit_event(Event::<T>::UnstakeFromUnregistered {
1212 account,
1213 smart_contract,
1214 amount: unstake_amount.total(),
1215 });
1216
1217 Ok(())
1218 }
1219
1220 #[pallet::call_index(17)]
1226 #[pallet::weight(T::WeightInfo::cleanup_expired_entries(
1227 T::MaxNumberOfStakedContracts::get()
1228 ))]
1229 pub fn cleanup_expired_entries(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1230 Self::ensure_pallet_enabled()?;
1231 let account = ensure_signed(origin)?;
1232
1233 let protocol_state = ActiveProtocolState::<T>::get();
1234 let current_period = protocol_state.period_number();
1235 let threshold_period = Self::oldest_claimable_period(current_period);
1236
1237 let mut remaining: u32 = 0;
1238 let mut to_be_deleted: Vec<T::SmartContract> = Vec::new();
1239
1240 for (smart_contract, stake_info) in StakerInfo::<T>::iter_prefix(&account) {
1245 let stake_period = stake_info.period_number();
1246
1247 let should_keep = stake_period == current_period
1249 || (stake_period >= threshold_period
1250 && stake_period < current_period
1251 && stake_info.is_bonus_eligible());
1252
1253 if should_keep {
1254 remaining = remaining.saturating_add(1);
1255 } else {
1256 to_be_deleted.push(smart_contract);
1257 }
1258 }
1259 let entries_to_delete = to_be_deleted.len();
1260
1261 ensure!(!entries_to_delete.is_zero(), Error::<T>::NoExpiredEntries);
1262
1263 for smart_contract in to_be_deleted {
1265 StakerInfo::<T>::remove(&account, &smart_contract);
1266 }
1267
1268 let mut ledger = Ledger::<T>::get(&account);
1270 ledger.contract_stake_count = remaining;
1271 ledger.maybe_cleanup_expired(threshold_period); Self::update_ledger(&account, ledger)?;
1273
1274 Self::deposit_event(Event::<T>::ExpiredEntriesRemoved {
1275 account,
1276 count: entries_to_delete.unique_saturated_into(),
1277 });
1278
1279 Ok(Some(T::WeightInfo::cleanup_expired_entries(
1280 entries_to_delete.unique_saturated_into(),
1281 ))
1282 .into())
1283 }
1284
1285 #[pallet::call_index(18)]
1293 #[pallet::weight(T::WeightInfo::force())]
1294 pub fn force(origin: OriginFor<T>, forcing_type: ForcingType) -> DispatchResult {
1295 Self::ensure_pallet_enabled()?;
1296 ensure_root(origin)?;
1297
1298 ensure!(!Safeguard::<T>::get(), Error::<T>::ForceNotAllowed);
1299
1300 ActiveProtocolState::<T>::mutate(|state| {
1302 let current_block = frame_system::Pallet::<T>::block_number();
1303 state.next_era_start = current_block.saturating_add(One::one()).saturated_into();
1304
1305 match forcing_type {
1306 ForcingType::Era => (),
1307 ForcingType::Subperiod => {
1308 state.period_info.next_subperiod_start_era = state.era.saturating_add(1);
1309 }
1310 }
1311
1312 Self::notify_block_before_new_era(&state);
1317 });
1318
1319 Self::deposit_event(Event::<T>::Force { forcing_type });
1320
1321 Ok(())
1322 }
1323
1324 #[pallet::call_index(19)]
1327 #[pallet::weight({
1328 let max_span_length = T::EraRewardSpanLength::get();
1329 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1330 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1331 })]
1332 pub fn claim_staker_rewards_for(
1333 origin: OriginFor<T>,
1334 account: T::AccountId,
1335 ) -> DispatchResultWithPostInfo {
1336 Self::ensure_pallet_enabled()?;
1337 ensure_signed(origin)?;
1338
1339 Self::internal_claim_staker_rewards_for(account)
1340 }
1341
1342 #[pallet::call_index(20)]
1344 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1345 pub fn claim_bonus_reward_for(
1346 origin: OriginFor<T>,
1347 account: T::AccountId,
1348 smart_contract: T::SmartContract,
1349 ) -> DispatchResult {
1350 Self::ensure_pallet_enabled()?;
1351 ensure_signed(origin)?;
1352
1353 Self::internal_claim_bonus_reward_for(account, smart_contract)
1354 }
1355
1356 #[pallet::call_index(21)]
1359 #[pallet::weight(T::WeightInfo::move_stake_unregistered_source().max(T::WeightInfo::move_stake_from_registered_source()))]
1360 pub fn move_stake(
1361 origin: OriginFor<T>,
1362 source_contract: T::SmartContract,
1363 destination_contract: T::SmartContract,
1364 #[pallet::compact] amount: Balance,
1365 ) -> DispatchResultWithPostInfo {
1366 Self::ensure_pallet_enabled()?;
1367 let account = ensure_signed(origin)?;
1368
1369 ensure!(
1370 !source_contract.eq(&destination_contract),
1371 Error::<T>::SameContracts
1372 );
1373
1374 ensure!(
1375 IntegratedDApps::<T>::contains_key(&destination_contract),
1376 Error::<T>::ContractNotFound
1377 );
1378
1379 let maybe_source_dapp_info = IntegratedDApps::<T>::get(&source_contract);
1380 let is_source_unregistered = maybe_source_dapp_info.is_none();
1381
1382 let (mut move_amount, bonus_status) = if is_source_unregistered {
1383 Self::inner_unstake_from_unregistered(&account, &source_contract)?
1384 } else {
1385 Self::inner_unstake(&account, &source_contract, amount)?
1386 };
1387
1388 if bonus_status == 0 && move_amount.voting > 0 {
1390 move_amount.convert_bonus_into_regular_stake();
1391 }
1392
1393 Self::inner_stake(&account, &destination_contract, move_amount, bonus_status)?;
1394
1395 Self::deposit_event(Event::<T>::StakeMoved {
1396 account,
1397 source_contract,
1398 destination_contract,
1399 amount: move_amount.total(),
1400 });
1401
1402 Ok(Some(if is_source_unregistered {
1403 T::WeightInfo::move_stake_unregistered_source()
1404 } else {
1405 T::WeightInfo::move_stake_from_registered_source()
1406 })
1407 .into())
1408 }
1409
1410 #[pallet::call_index(22)]
1416 #[pallet::weight(T::WeightInfo::set_static_tier_params())]
1417 pub fn set_static_tier_params(
1418 origin: OriginFor<T>,
1419 params: TierParameters<T::NumberOfTiers>,
1420 ) -> DispatchResult {
1421 Self::ensure_pallet_enabled()?;
1422 ensure_root(origin)?;
1423 ensure!(params.is_valid(), Error::<T>::InvalidTierParams);
1424
1425 StaticTierParams::<T>::set(params.clone());
1426
1427 Self::deposit_event(Event::<T>::NewTierParameters { params });
1428
1429 Ok(())
1430 }
1431 }
1432
1433 impl<T: Config> Pallet<T> {
1434 pub fn inner_unstake(
1441 account: &T::AccountId,
1442 smart_contract: &T::SmartContract,
1443 amount: Balance,
1444 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1445 ensure!(amount > 0, Error::<T>::ZeroAmount);
1446 let dapp_info =
1447 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1448
1449 let protocol_state = ActiveProtocolState::<T>::get();
1450 let current_era = protocol_state.era;
1451
1452 let mut ledger = Ledger::<T>::get(&account);
1453
1454 let (new_staking_info, amount, stake_amount_iter, updated_bonus_status) =
1457 match StakerInfo::<T>::get(&account, &smart_contract) {
1458 Some(mut staking_info) => {
1459 ensure!(
1460 staking_info.period_number() == protocol_state.period_number(),
1461 Error::<T>::UnstakeFromPastPeriod
1462 );
1463 ensure!(
1464 staking_info.total_staked_amount() >= amount,
1465 Error::<T>::UnstakeAmountTooLarge
1466 );
1467
1468 let amount = if staking_info.total_staked_amount().saturating_sub(amount)
1471 < T::MinimumStakeAmount::get()
1472 {
1473 staking_info.total_staked_amount()
1474 } else {
1475 amount
1476 };
1477
1478 let (stake_amount_iter, updated_bonus_status) =
1479 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1480
1481 (
1482 staking_info,
1483 amount,
1484 stake_amount_iter,
1485 updated_bonus_status,
1486 )
1487 }
1488 None => {
1489 return Err(Error::<T>::NoStakingInfo.into());
1490 }
1491 };
1492
1493 ledger
1496 .unstake_amount(amount, current_era, protocol_state.period_info)
1497 .map_err(|err| match err {
1498 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1499 Error::<T>::UnclaimedRewards
1500 }
1501 AccountLedgerError::UnstakeAmountLargerThanStake => {
1503 Error::<T>::UnstakeAmountTooLarge
1504 }
1505 _ => Error::<T>::InternalUnstakeError,
1506 })?;
1507
1508 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1511 contract_stake_info.unstake(
1512 &stake_amount_iter,
1513 protocol_state.period_info,
1514 current_era,
1515 );
1516
1517 CurrentEraInfo::<T>::mutate(|era_info| {
1520 era_info.unstake_amount(stake_amount_iter.clone());
1521 });
1522
1523 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1526
1527 if new_staking_info.is_empty() {
1528 ledger.contract_stake_count.saturating_dec();
1529 StakerInfo::<T>::remove(&account, &smart_contract);
1530 } else {
1531 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1532 }
1533
1534 Self::update_ledger(&account, ledger)?;
1535
1536 let mut unstake_amount = stake_amount_iter
1538 .iter()
1539 .max_by(|a, b| a.total().cmp(&b.total()))
1540 .ok_or(Error::<T>::InternalUnstakeError)?
1542 .clone();
1543
1544 unstake_amount.era = current_era;
1546
1547 Ok((unstake_amount, updated_bonus_status))
1548 }
1549
1550 pub fn inner_unstake_from_unregistered(
1556 account: &T::AccountId,
1557 smart_contract: &T::SmartContract,
1558 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1559 ensure!(
1560 !IntegratedDApps::<T>::contains_key(&smart_contract),
1561 Error::<T>::ContractStillActive
1562 );
1563
1564 let protocol_state = ActiveProtocolState::<T>::get();
1565 let current_era = protocol_state.era;
1566
1567 let (amount, unstake_amount_iter, preserved_bonus_status) =
1569 match StakerInfo::<T>::get(&account, &smart_contract) {
1570 Some(mut staking_info) => {
1571 ensure!(
1572 staking_info.period_number() == protocol_state.period_number(),
1573 Error::<T>::UnstakeFromPastPeriod
1574 );
1575
1576 let preserved_bonus_status = staking_info.bonus_status;
1577 let amount = staking_info.staked.total();
1578
1579 let (unstake_amount_iter, _) =
1580 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1581
1582 (amount, unstake_amount_iter, preserved_bonus_status)
1583 }
1584 None => {
1585 return Err(Error::<T>::NoStakingInfo.into());
1586 }
1587 };
1588
1589 let mut ledger = Ledger::<T>::get(&account);
1591 ledger
1592 .unstake_amount(amount, current_era, protocol_state.period_info)
1593 .map_err(|err| match err {
1594 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1596 Error::<T>::UnclaimedRewards
1597 }
1598 _ => Error::<T>::InternalUnstakeError,
1599 })?;
1600 ledger.contract_stake_count.saturating_dec();
1601
1602 CurrentEraInfo::<T>::mutate(|era_info| {
1606 era_info.unstake_amount(unstake_amount_iter.clone());
1607 });
1608
1609 Self::update_ledger(&account, ledger)?;
1611 StakerInfo::<T>::remove(&account, &smart_contract);
1612
1613 let mut unstake_amount = unstake_amount_iter
1615 .iter()
1616 .max_by(|a, b| a.total().cmp(&b.total()))
1617 .ok_or(Error::<T>::InternalUnstakeError)?
1619 .clone();
1620
1621 unstake_amount.era = current_era;
1623
1624 Ok((unstake_amount, preserved_bonus_status))
1625 }
1626
1627 pub fn inner_stake(
1632 account: &T::AccountId,
1633 smart_contract: &T::SmartContract,
1634 amount: StakeAmount,
1635 bonus_status: BonusStatus,
1636 ) -> Result<(), DispatchError> {
1637 ensure!(amount.total() > 0, Error::<T>::ZeroAmount);
1638
1639 let dapp_info =
1640 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1641
1642 let protocol_state = ActiveProtocolState::<T>::get();
1643 let current_era = protocol_state.era;
1644 let period_number = protocol_state.period_info.number;
1645 ensure!(
1646 !protocol_state
1647 .period_info
1648 .is_next_period(current_era.saturating_add(1)),
1649 Error::<T>::PeriodEndsInNextEra
1650 );
1651
1652 let mut ledger = Ledger::<T>::get(&account);
1653
1654 let threshold_period = Self::oldest_claimable_period(protocol_state.period_number());
1656 let _ignore = ledger.maybe_cleanup_expired(threshold_period);
1657
1658 ledger
1661 .add_stake_amount(amount, current_era, protocol_state.period_info)
1662 .map_err(|err| match err {
1663 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1664 Error::<T>::UnclaimedRewards
1665 }
1666 AccountLedgerError::UnavailableStakeFunds => Error::<T>::UnavailableStakeFunds,
1667 _ => Error::<T>::InternalStakeError,
1669 })?;
1670
1671 let (mut new_staking_info, is_new_entry, replacing_old_entry) =
1683 match StakerInfo::<T>::get(&account, &smart_contract) {
1684 Some(staking_info)
1686 if staking_info.period_number() == protocol_state.period_number() =>
1687 {
1688 (staking_info, false, false)
1689 }
1690 Some(staking_info)
1692 if staking_info.period_number() >= threshold_period
1693 && staking_info.is_bonus_eligible() =>
1694 {
1695 return Err(Error::<T>::UnclaimedRewards.into());
1696 }
1697 Some(_old_entry) => {
1699 StakerInfo::<T>::remove(&account, &smart_contract);
1701 (
1702 SingularStakingInfo::new(protocol_state.period_number(), bonus_status),
1703 true, true, )
1706 }
1707 None => (
1709 SingularStakingInfo::new(
1710 protocol_state.period_number(),
1711 bonus_status,
1714 ),
1715 true,
1716 false,
1717 ),
1718 };
1719
1720 new_staking_info.stake(amount, current_era, bonus_status);
1721 ensure!(
1722 new_staking_info.total_staked_amount() >= T::MinimumStakeAmount::get(),
1723 Error::<T>::InsufficientStakeAmount
1724 );
1725
1726 if is_new_entry && !replacing_old_entry {
1728 ledger.contract_stake_count.saturating_inc();
1729 ensure!(
1730 ledger.contract_stake_count <= T::MaxNumberOfStakedContracts::get(),
1731 Error::<T>::TooManyStakedContracts
1732 );
1733 }
1734
1735 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1738 contract_stake_info.stake(amount, current_era, period_number);
1739
1740 CurrentEraInfo::<T>::mutate(|era_info| {
1743 era_info.add_stake_amount(amount);
1744 });
1745
1746 Self::update_ledger(&account, ledger)?;
1749 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1750 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1751
1752 Ok(())
1753 }
1754
1755 pub fn is_staker(account: &T::AccountId) -> bool {
1757 Ledger::<T>::contains_key(account)
1758 }
1759
1760 pub(crate) fn ensure_pallet_enabled() -> Result<(), Error<T>> {
1762 if ActiveProtocolState::<T>::get().maintenance {
1763 Err(Error::<T>::Disabled)
1764 } else {
1765 Ok(())
1766 }
1767 }
1768
1769 pub(crate) fn update_ledger(
1776 account: &T::AccountId,
1777 ledger: AccountLedgerFor<T>,
1778 ) -> Result<(), DispatchError> {
1779 if ledger.is_empty() {
1780 Ledger::<T>::remove(&account);
1781 T::Currency::thaw(&FreezeReason::DAppStaking.into(), account)?;
1782 } else {
1783 T::Currency::set_freeze(
1784 &FreezeReason::DAppStaking.into(),
1785 account,
1786 ledger.total_locked_amount(),
1787 )?;
1788 Ledger::<T>::insert(account, ledger);
1789 }
1790
1791 Ok(())
1792 }
1793
1794 pub(crate) fn blocks_per_voting_period() -> BlockNumber {
1796 T::CycleConfiguration::blocks_per_era()
1797 .saturating_mul(T::CycleConfiguration::eras_per_voting_subperiod().into())
1798 }
1799
1800 pub fn era_reward_span_index(era: EraNumber) -> EraNumber {
1802 era.saturating_sub(era % T::EraRewardSpanLength::get())
1803 }
1804
1805 pub(crate) fn oldest_claimable_period(current_period: PeriodNumber) -> PeriodNumber {
1808 current_period.saturating_sub(T::RewardRetentionInPeriods::get())
1809 }
1810
1811 pub fn unlocking_period() -> BlockNumber {
1813 T::CycleConfiguration::blocks_per_era().saturating_mul(T::UnlockingPeriod::get().into())
1814 }
1815
1816 pub fn get_dapp_tier_assignment() -> BTreeMap<DAppId, RankedTier> {
1818 let protocol_state = ActiveProtocolState::<T>::get();
1819
1820 let (dapp_tiers, _count) = Self::get_dapp_tier_assignment_and_rewards(
1821 protocol_state.era,
1822 protocol_state.period_number(),
1823 Balance::zero(),
1824 );
1825
1826 dapp_tiers.dapps.into_inner()
1827 }
1828
1829 pub(crate) fn get_dapp_tier_assignment_and_rewards(
1859 era: EraNumber,
1860 period: PeriodNumber,
1861 dapp_reward_pool: Balance,
1862 ) -> (DAppTierRewardsFor<T>, DAppId) {
1863 let mut dapp_stakes = Vec::with_capacity(T::MaxNumberOfContracts::get() as usize);
1864
1865 let mut counter = 0;
1869 for (dapp_id, stake_amount) in ContractStake::<T>::iter() {
1870 counter.saturating_inc();
1871
1872 if let Some(stake_amount) = stake_amount.get(era, period) {
1874 if !stake_amount.total().is_zero() {
1875 dapp_stakes.push((dapp_id, stake_amount.total()));
1876 }
1877 }
1878 }
1879
1880 dapp_stakes.sort_unstable_by(|(_, amount_1), (_, amount_2)| amount_2.cmp(amount_1));
1883
1884 let tier_config = TierConfig::<T>::get();
1885
1886 let tier_rewards = tier_config
1895 .reward_portion
1896 .iter()
1897 .zip(tier_config.slots_per_tier.iter())
1898 .map(|(percent, slots)| {
1899 if slots.is_zero() {
1900 Zero::zero()
1901 } else {
1902 *percent * dapp_reward_pool / <u16 as Into<Balance>>::into(*slots)
1903 }
1904 })
1905 .collect::<Vec<_>>();
1906
1907 let mut dapp_tiers = BTreeMap::new();
1912 let mut tier_slots = BTreeMap::new();
1913
1914 let mut upper_bound = Balance::zero();
1915 let mut rank_rewards = Vec::new();
1916
1917 for (tier_id, (tier_capacity, lower_bound)) in tier_config
1918 .slots_per_tier
1919 .iter()
1920 .zip(tier_config.tier_thresholds.iter())
1921 .enumerate()
1922 {
1923 for (dapp_id, staked_amount) in dapp_stakes
1927 .iter()
1928 .skip(dapp_tiers.len())
1929 .take_while(|(_, amount)| amount.ge(lower_bound))
1930 .take(*tier_capacity as usize)
1931 {
1932 let rank = if T::RankingEnabled::get() {
1933 RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount)
1934 } else {
1935 0
1936 };
1937 tier_slots.insert(*dapp_id, RankedTier::new_saturated(tier_id as u8, rank));
1938 }
1939
1940 let ranks_sum = tier_slots
1942 .iter()
1943 .fold(0u32, |accum, (_, x)| accum.saturating_add(x.rank().into()));
1944
1945 let reward_per_rank = if ranks_sum.is_zero() {
1946 Balance::zero()
1947 } else {
1948 let tier_reward = tier_rewards.get(tier_id).copied().unwrap_or_default();
1950 let empty_slots = tier_capacity.saturating_sub(tier_slots.len() as u16);
1951 let remaining_reward = tier_reward.saturating_mul(empty_slots.into());
1952 let reward_per_rank = tier_reward.saturating_div(RankedTier::MAX_RANK.into());
1954 let expected_reward_for_ranks =
1955 reward_per_rank.saturating_mul(ranks_sum.into());
1956 let reward_for_ranks = expected_reward_for_ranks.min(remaining_reward);
1957 reward_for_ranks.saturating_div(ranks_sum.into())
1959 };
1960
1961 rank_rewards.push(reward_per_rank);
1962 dapp_tiers.append(&mut tier_slots);
1963 upper_bound = *lower_bound; }
1965
1966 (
1970 DAppTierRewards::<T::MaxNumberOfContracts, T::NumberOfTiers>::new(
1971 dapp_tiers,
1972 tier_rewards,
1973 period,
1974 rank_rewards,
1975 )
1976 .unwrap_or_default(),
1977 counter,
1978 )
1979 }
1980
1981 pub(crate) fn era_and_period_handler(
1983 now: BlockNumber,
1984 tier_assignment: TierAssignment,
1985 ) -> Weight {
1986 let mut protocol_state = ActiveProtocolState::<T>::get();
1987
1988 let mut consumed_weight = T::DbWeight::get().reads(1);
1990
1991 if protocol_state.maintenance {
1995 return consumed_weight;
1996 }
1997
1998 if protocol_state.next_era_start == now.saturating_add(1) {
2000 consumed_weight
2001 .saturating_accrue(Self::notify_block_before_new_era(&protocol_state));
2002 }
2003
2004 if !protocol_state.is_new_era(now) {
2006 return consumed_weight;
2007 }
2008
2009 let mut era_info = CurrentEraInfo::<T>::get();
2011
2012 let current_era = protocol_state.era;
2013 let next_era = current_era.saturating_add(1);
2014 let (maybe_period_event, era_reward) = match protocol_state.subperiod() {
2015 Subperiod::Voting => {
2017 let era_reward = EraReward {
2019 staker_reward_pool: Balance::zero(),
2020 staked: era_info.total_staked_amount(),
2021 dapp_reward_pool: Balance::zero(),
2022 };
2023
2024 let next_subperiod_start_era = next_era
2025 .saturating_add(T::CycleConfiguration::eras_per_build_and_earn_subperiod());
2026 let build_and_earn_start_block =
2027 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2028 protocol_state.advance_to_next_subperiod(
2029 next_subperiod_start_era,
2030 build_and_earn_start_block,
2031 );
2032
2033 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2034
2035 consumed_weight
2036 .saturating_accrue(T::WeightInfo::on_initialize_voting_to_build_and_earn());
2037
2038 (
2039 Some(Event::<T>::NewSubperiod {
2040 subperiod: protocol_state.subperiod(),
2041 number: protocol_state.period_number(),
2042 }),
2043 era_reward,
2044 )
2045 }
2046 Subperiod::BuildAndEarn => {
2047 let staked = era_info.total_staked_amount();
2048 let (staker_reward_pool, dapp_reward_pool) =
2049 T::StakingRewardHandler::staker_and_dapp_reward_pools(staked);
2050 let era_reward = EraReward {
2051 staker_reward_pool,
2052 staked,
2053 dapp_reward_pool,
2054 };
2055
2056 let (dapp_tier_rewards, counter) = match tier_assignment {
2061 TierAssignment::Real => Self::get_dapp_tier_assignment_and_rewards(
2062 current_era,
2063 protocol_state.period_number(),
2064 dapp_reward_pool,
2065 ),
2066 #[cfg(feature = "runtime-benchmarks")]
2067 TierAssignment::Dummy => (DAppTierRewardsFor::<T>::default(), 0),
2068 };
2069 DAppTiers::<T>::insert(¤t_era, dapp_tier_rewards);
2070
2071 consumed_weight
2072 .saturating_accrue(T::WeightInfo::dapp_tier_assignment(counter.into()));
2073
2074 if protocol_state.period_info.is_next_period(next_era) {
2076 let bonus_reward_pool = T::StakingRewardHandler::bonus_reward_pool();
2078 PeriodEnd::<T>::insert(
2079 &protocol_state.period_number(),
2080 PeriodEndInfo {
2081 bonus_reward_pool,
2082 total_vp_stake: era_info.staked_amount(Subperiod::Voting),
2083 final_era: current_era,
2084 },
2085 );
2086
2087 let next_subperiod_start_era = next_era.saturating_add(1);
2090 let voting_period_length = Self::blocks_per_voting_period();
2091 let next_era_start_block = now.saturating_add(voting_period_length);
2092
2093 protocol_state.advance_to_next_subperiod(
2094 next_subperiod_start_era,
2095 next_era_start_block,
2096 );
2097
2098 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2099
2100 Self::update_cleanup_marker(protocol_state.period_number());
2103
2104 consumed_weight.saturating_accrue(
2105 T::WeightInfo::on_initialize_build_and_earn_to_voting(),
2106 );
2107
2108 (
2109 Some(Event::<T>::NewSubperiod {
2110 subperiod: protocol_state.subperiod(),
2111 number: protocol_state.period_number(),
2112 }),
2113 era_reward,
2114 )
2115 } else {
2116 let next_era_start_block =
2117 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2118 protocol_state.next_era_start = next_era_start_block;
2119
2120 era_info.migrate_to_next_era(None);
2121
2122 consumed_weight.saturating_accrue(
2123 T::WeightInfo::on_initialize_build_and_earn_to_build_and_earn(),
2124 );
2125
2126 (None, era_reward)
2127 }
2128 }
2129 };
2130
2131 protocol_state.era = next_era;
2133 ActiveProtocolState::<T>::put(protocol_state);
2134
2135 CurrentEraInfo::<T>::put(era_info);
2136
2137 let era_span_index = Self::era_reward_span_index(current_era);
2138 let mut span = EraRewards::<T>::get(&era_span_index).unwrap_or(EraRewardSpan::new());
2139 if let Err(_) = span.push(current_era, era_reward) {
2140 log::error!(
2142 target: LOG_TARGET,
2143 "Failed to push era {} into the era reward span.",
2144 current_era
2145 );
2146 }
2147 EraRewards::<T>::insert(&era_span_index, span);
2148
2149 let tier_params = StaticTierParams::<T>::get();
2151 let average_price = T::NativePriceProvider::average_price();
2152 let total_issuance = T::Currency::total_issuance();
2153
2154 let new_tier_config =
2155 TierConfig::<T>::get().calculate_new(&tier_params, average_price, total_issuance);
2156
2157 if new_tier_config.is_valid() {
2159 TierConfig::<T>::put(new_tier_config);
2160 } else {
2161 log::warn!(
2162 target: LOG_TARGET,
2163 "New tier configuration is invalid for era {}, preserving old one.",
2164 next_era
2165 );
2166 }
2167
2168 Self::deposit_event(Event::<T>::NewEra { era: next_era });
2169 if let Some(period_event) = maybe_period_event {
2170 Self::deposit_event(period_event);
2171 }
2172
2173 consumed_weight
2174 }
2175
2176 fn notify_block_before_new_era(protocol_state: &ProtocolState) -> Weight {
2178 let next_era = protocol_state.era.saturating_add(1);
2179 T::Observers::block_before_new_era(next_era)
2180 }
2181
2182 fn update_cleanup_marker(new_period_number: PeriodNumber) {
2186 let latest_expired_period = match new_period_number
2188 .checked_sub(T::RewardRetentionInPeriods::get().saturating_add(1))
2189 {
2190 Some(period) if !period.is_zero() => period,
2191 _ => return,
2193 };
2194
2195 let oldest_valid_era = match PeriodEnd::<T>::take(latest_expired_period) {
2200 Some(period_end_info) => period_end_info.final_era.saturating_add(1),
2201 None => {
2202 log::error!(
2204 target: LOG_TARGET,
2205 "No `PeriodEnd` entry for the expired period: {}",
2206 latest_expired_period
2207 );
2208 return;
2209 }
2210 };
2211
2212 HistoryCleanupMarker::<T>::mutate(|marker| {
2214 marker.oldest_valid_era = oldest_valid_era;
2215 });
2216 }
2217
2218 fn expired_entry_cleanup(remaining_weight: &Weight) -> Weight {
2222 if remaining_weight.any_lt(T::WeightInfo::on_idle_cleanup()) {
2224 return Weight::zero();
2225 }
2226
2227 let mut cleanup_marker = HistoryCleanupMarker::<T>::get();
2229 if !cleanup_marker.has_pending_cleanups() {
2230 return T::DbWeight::get().reads(1);
2231 }
2232
2233 if cleanup_marker.era_reward_index < cleanup_marker.oldest_valid_era {
2235 if let Some(era_reward) = EraRewards::<T>::get(cleanup_marker.era_reward_index) {
2236 if era_reward.last_era() < cleanup_marker.oldest_valid_era {
2238 EraRewards::<T>::remove(cleanup_marker.era_reward_index);
2239 cleanup_marker
2240 .era_reward_index
2241 .saturating_accrue(T::EraRewardSpanLength::get());
2242 }
2243 } else {
2244 log::warn!(
2246 target: LOG_TARGET,
2247 "Era rewards span for era {} is missing, but cleanup marker is set.",
2248 cleanup_marker.era_reward_index
2249 );
2250 cleanup_marker
2251 .era_reward_index
2252 .saturating_accrue(T::EraRewardSpanLength::get());
2253 }
2254 }
2255
2256 if cleanup_marker.dapp_tiers_index < cleanup_marker.oldest_valid_era {
2258 DAppTiers::<T>::remove(cleanup_marker.dapp_tiers_index);
2259 cleanup_marker.dapp_tiers_index.saturating_inc();
2260 }
2261
2262 HistoryCleanupMarker::<T>::put(cleanup_marker);
2264
2265 T::WeightInfo::on_idle_cleanup()
2271 }
2272
2273 fn internal_claim_unlocked(account: T::AccountId) -> DispatchResultWithPostInfo {
2275 let mut ledger = Ledger::<T>::get(&account);
2276
2277 let current_block = frame_system::Pallet::<T>::block_number();
2278 let amount = ledger.claim_unlocked(current_block.saturated_into());
2279 ensure!(amount > Zero::zero(), Error::<T>::NoUnlockedChunksToClaim);
2280
2281 let removed_entries = if ledger.is_empty() {
2283 let _ = StakerInfo::<T>::clear_prefix(&account, ledger.contract_stake_count, None);
2284 ledger.contract_stake_count
2285 } else {
2286 0
2287 };
2288
2289 Self::update_ledger(&account, ledger)?;
2290 CurrentEraInfo::<T>::mutate(|era_info| {
2291 era_info.unlocking_removed(amount);
2292 });
2293
2294 Self::deposit_event(Event::<T>::ClaimedUnlocked { account, amount });
2295
2296 Ok(Some(T::WeightInfo::claim_unlocked(removed_entries)).into())
2297 }
2298
2299 fn internal_claim_staker_rewards_for(account: T::AccountId) -> DispatchResultWithPostInfo {
2301 let mut ledger = Ledger::<T>::get(&account);
2302 let staked_period = ledger
2303 .staked_period()
2304 .ok_or(Error::<T>::NoClaimableRewards)?;
2305
2306 let protocol_state = ActiveProtocolState::<T>::get();
2308 ensure!(
2309 staked_period >= Self::oldest_claimable_period(protocol_state.period_number()),
2310 Error::<T>::RewardExpired
2311 );
2312
2313 let earliest_staked_era = ledger
2315 .earliest_staked_era()
2316 .ok_or(Error::<T>::InternalClaimStakerError)?;
2317 let era_rewards =
2318 EraRewards::<T>::get(Self::era_reward_span_index(earliest_staked_era))
2319 .ok_or(Error::<T>::NoClaimableRewards)?;
2320
2321 let (last_period_era, period_end) = if staked_period == protocol_state.period_number() {
2324 (protocol_state.era.saturating_sub(1), None)
2325 } else {
2326 PeriodEnd::<T>::get(&staked_period)
2327 .map(|info| (info.final_era, Some(info.final_era)))
2328 .ok_or(Error::<T>::InternalClaimStakerError)?
2329 };
2330
2331 let last_claim_era = era_rewards.last_era().min(last_period_era);
2333
2334 let rewards_iter =
2336 ledger
2337 .claim_up_to_era(last_claim_era, period_end)
2338 .map_err(|err| match err {
2339 AccountLedgerError::NothingToClaim => Error::<T>::NoClaimableRewards,
2340 _ => Error::<T>::InternalClaimStakerError,
2341 })?;
2342
2343 let mut rewards: Vec<_> = Vec::new();
2345 let mut reward_sum = Balance::zero();
2346 for (era, amount) in rewards_iter {
2347 let era_reward = era_rewards
2348 .get(era)
2349 .ok_or(Error::<T>::InternalClaimStakerError)?;
2350
2351 if amount.is_zero() || era_reward.staked.is_zero() {
2353 continue;
2354 }
2355 let staker_reward = Perbill::from_rational(amount, era_reward.staked)
2356 * era_reward.staker_reward_pool;
2357
2358 rewards.push((era, staker_reward));
2359 reward_sum.saturating_accrue(staker_reward);
2360 }
2361 let rewards_len: u32 = rewards.len().unique_saturated_into();
2362
2363 T::StakingRewardHandler::payout_reward(&account, reward_sum)
2364 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2365
2366 Self::update_ledger(&account, ledger)?;
2367
2368 rewards.into_iter().for_each(|(era, reward)| {
2369 Self::deposit_event(Event::<T>::Reward {
2370 account: account.clone(),
2371 era,
2372 amount: reward,
2373 });
2374 });
2375
2376 Ok(Some(if period_end.is_some() {
2377 T::WeightInfo::claim_staker_rewards_past_period(rewards_len)
2378 } else {
2379 T::WeightInfo::claim_staker_rewards_ongoing_period(rewards_len)
2380 })
2381 .into())
2382 }
2383
2384 fn internal_claim_bonus_reward_for(
2386 account: T::AccountId,
2387 smart_contract: T::SmartContract,
2388 ) -> DispatchResult {
2389 let staker_info = StakerInfo::<T>::get(&account, &smart_contract)
2390 .ok_or(Error::<T>::NoClaimableRewards)?;
2391 let protocol_state = ActiveProtocolState::<T>::get();
2392
2393 let staked_period = staker_info.period_number();
2398 ensure!(
2399 staked_period < protocol_state.period_number(),
2400 Error::<T>::NoClaimableRewards
2401 );
2402 ensure!(
2403 staker_info.is_bonus_eligible(),
2404 Error::<T>::NotEligibleForBonusReward
2405 );
2406 ensure!(
2407 staker_info.period_number()
2408 >= Self::oldest_claimable_period(protocol_state.period_number()),
2409 Error::<T>::RewardExpired
2410 );
2411
2412 let period_end_info =
2413 PeriodEnd::<T>::get(&staked_period).ok_or(Error::<T>::InternalClaimBonusError)?;
2414 ensure!(
2416 !period_end_info.total_vp_stake.is_zero(),
2417 Error::<T>::InternalClaimBonusError
2418 );
2419
2420 let eligible_amount = staker_info.staked_amount(Subperiod::Voting);
2421 let bonus_reward =
2422 Perbill::from_rational(eligible_amount, period_end_info.total_vp_stake)
2423 * period_end_info.bonus_reward_pool;
2424
2425 T::StakingRewardHandler::payout_reward(&account, bonus_reward)
2426 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2427
2428 StakerInfo::<T>::remove(&account, &smart_contract);
2430 Ledger::<T>::mutate(&account, |ledger| {
2431 ledger.contract_stake_count.saturating_dec();
2432 });
2433
2434 Self::deposit_event(Event::<T>::BonusReward {
2435 account: account.clone(),
2436 smart_contract,
2437 period: staked_period,
2438 amount: bonus_reward,
2439 });
2440
2441 Ok(())
2442 }
2443
2444 fn set_maintenance_mode(enabled: bool) {
2448 ActiveProtocolState::<T>::mutate(|state| state.maintenance = enabled);
2449 Self::deposit_event(Event::<T>::MaintenanceMode { enabled });
2450 }
2451
2452 #[cfg(any(feature = "try-runtime", test))]
2454 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
2455 Self::try_state_protocol()?;
2456 Self::try_state_next_dapp_id()?;
2457 Self::try_state_integrated_dapps()?;
2458 Self::try_state_tiers()?;
2459 Self::try_state_ledger()?;
2460 Self::try_state_contract_stake()?;
2461 Self::try_state_era_rewards()?;
2462 Self::try_state_era_info()?;
2463
2464 Ok(())
2465 }
2466
2467 #[cfg(any(feature = "try-runtime", test))]
2472 pub fn try_state_protocol() -> Result<(), sp_runtime::TryRuntimeError> {
2473 let protocol_state = ActiveProtocolState::<T>::get();
2474
2475 if PeriodEnd::<T>::iter().count() >= protocol_state.period_info.number as usize {
2477 return Err("Number of periods in `PeriodEnd` exceeds or is equal to actual `PeriodInfo` number.".into());
2478 }
2479
2480 if protocol_state.era == 0 {
2482 return Err("Invalid era number in ActiveProtocolState.".into());
2483 }
2484
2485 let current_block: BlockNumber =
2486 frame_system::Pallet::<T>::block_number().saturated_into();
2487 if current_block > protocol_state.next_era_start {
2488 return Err(
2489 "Next era start block number is in the past in ActiveProtocolState.".into(),
2490 );
2491 }
2492
2493 Ok(())
2494 }
2495
2496 #[cfg(any(feature = "try-runtime", test))]
2501 pub fn try_state_next_dapp_id() -> Result<(), sp_runtime::TryRuntimeError> {
2502 let next_dapp_id = NextDAppId::<T>::get();
2503
2504 if next_dapp_id < IntegratedDApps::<T>::count() as u16 {
2506 return Err("Number of integrated dapps is greater than NextDAppId.".into());
2507 }
2508
2509 if next_dapp_id < ContractStake::<T>::iter().count() as u16 {
2511 return Err("Number of contract stake infos is greater than NextDAppId.".into());
2512 }
2513
2514 Ok(())
2515 }
2516
2517 #[cfg(any(feature = "try-runtime", test))]
2521 pub fn try_state_integrated_dapps() -> Result<(), sp_runtime::TryRuntimeError> {
2522 let integrated_dapps_count = IntegratedDApps::<T>::count();
2523 let max_number_of_contracts = T::MaxNumberOfContracts::get();
2524
2525 if integrated_dapps_count > max_number_of_contracts {
2526 return Err("Number of integrated dapps exceeds the maximum allowed.".into());
2527 }
2528
2529 Ok(())
2530 }
2531
2532 #[cfg(any(feature = "try-runtime", test))]
2537 pub fn try_state_tiers() -> Result<(), sp_runtime::TryRuntimeError> {
2538 let nb_tiers = T::NumberOfTiers::get();
2539 let tier_params = StaticTierParams::<T>::get();
2540 let tier_config = TierConfig::<T>::get();
2541
2542 if nb_tiers != tier_params.slot_distribution.len() as u32 {
2544 return Err(
2545 "Number of tiers is incorrect in slot_distribution in StaticTierParams.".into(),
2546 );
2547 }
2548 if nb_tiers != tier_params.reward_portion.len() as u32 {
2549 return Err(
2550 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2551 );
2552 }
2553 if nb_tiers != tier_params.tier_thresholds.len() as u32 {
2554 return Err(
2555 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2556 );
2557 }
2558
2559 if nb_tiers != tier_config.slots_per_tier.len() as u32 {
2561 return Err(
2562 "Number of tiers is incorrect in slots_per_tier in StaticTierParams.".into(),
2563 );
2564 }
2565 if nb_tiers != tier_config.reward_portion.len() as u32 {
2566 return Err(
2567 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2568 );
2569 }
2570 if nb_tiers != tier_config.tier_thresholds.len() as u32 {
2571 return Err(
2572 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2573 );
2574 }
2575
2576 Ok(())
2577 }
2578
2579 #[cfg(any(feature = "try-runtime", test))]
2587 pub fn try_state_ledger() -> Result<(), sp_runtime::TryRuntimeError> {
2588 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2589 let current_era_info = CurrentEraInfo::<T>::get();
2590 let next_era_total_stake = current_era_info.total_staked_amount_next_era();
2591
2592 let mut ledger_total_stake = Balance::zero();
2594 let mut ledger_total_locked = Balance::zero();
2595 let mut ledger_total_unlocking = Balance::zero();
2596
2597 for (_, ledger) in Ledger::<T>::iter() {
2598 let account_stake = ledger.staked_amount(current_period_number);
2599
2600 ledger_total_stake += account_stake;
2601 ledger_total_locked += ledger.active_locked_amount();
2602 ledger_total_unlocking += ledger.unlocking_amount();
2603
2604 if ledger.unlocking.len() > T::MaxUnlockingChunks::get() as usize {
2606 return Err("An account exceeds the maximum unlocking chunks.".into());
2607 }
2608
2609 if account_stake > Balance::zero() && account_stake < T::MinimumStakeAmount::get() {
2611 return Err(
2612 "An account has a stake amount lower than the minimum allowed.".into(),
2613 );
2614 }
2615
2616 if ledger.active_locked_amount() > Balance::zero()
2618 && ledger.active_locked_amount() < T::MinimumLockedAmount::get()
2619 {
2620 return Err(
2621 "An account has a locked amount lower than the minimum allowed.".into(),
2622 );
2623 }
2624
2625 if ledger.contract_stake_count > T::MaxNumberOfStakedContracts::get() {
2627 return Err("An account exceeds the maximum number of staked contracts.".into());
2628 }
2629 }
2630
2631 if ledger_total_stake != next_era_total_stake {
2633 return Err(
2634 "Mismatch between Ledger total staked amounts and CurrentEraInfo total.".into(),
2635 );
2636 }
2637
2638 if ledger_total_locked != current_era_info.total_locked {
2639 return Err(
2640 "Mismatch between Ledger total locked amounts and CurrentEraInfo total.".into(),
2641 );
2642 }
2643
2644 if ledger_total_unlocking != current_era_info.unlocking {
2645 return Err(
2646 "Mismatch between Ledger total unlocked amounts and CurrentEraInfo total."
2647 .into(),
2648 );
2649 }
2650
2651 Ok(())
2652 }
2653
2654 #[cfg(any(feature = "try-runtime", test))]
2658 pub fn try_state_contract_stake() -> Result<(), sp_runtime::TryRuntimeError> {
2659 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2660
2661 for (_, contract) in ContractStake::<T>::iter() {
2662 let contract_stake = contract.total_staked_amount(current_period_number);
2663
2664 if contract_stake > Balance::zero() && contract_stake < T::MinimumStakeAmount::get()
2666 {
2667 return Err(
2668 "A contract has a staked amount lower than the minimum allowed.".into(),
2669 );
2670 }
2671 }
2672
2673 Ok(())
2674 }
2675
2676 #[cfg(any(feature = "try-runtime", test))]
2681 pub fn try_state_era_rewards() -> Result<(), sp_runtime::TryRuntimeError> {
2682 let era_rewards = EraRewards::<T>::iter().collect::<Vec<_>>();
2683 let dapp_tiers = DAppTiers::<T>::iter().collect::<Vec<_>>();
2684
2685 for (era, _) in &dapp_tiers {
2687 let mut found = false;
2688 for (_, span) in &era_rewards {
2689 if *era >= span.first_era() && *era <= span.last_era() {
2690 found = true;
2691 break;
2692 }
2693 }
2694
2695 if !found {
2697 return Err("Era in DAppTiers is not found in any span in EraRewards.".into());
2698 }
2699 }
2700
2701 for (_, span) in &era_rewards {
2702 if span.len() > T::EraRewardSpanLength::get() as usize {
2704 return Err(
2705 "Span length for a era exceeds the maximum allowed span length.".into(),
2706 );
2707 }
2708 }
2709
2710 Ok(())
2711 }
2712
2713 #[cfg(any(feature = "try-runtime", test))]
2718 pub fn try_state_era_info() -> Result<(), sp_runtime::TryRuntimeError> {
2719 let protocol_state = ActiveProtocolState::<T>::get();
2720 let current_period = protocol_state.period_number();
2721 let era_info = CurrentEraInfo::<T>::get();
2722
2723 let current_voting = era_info.staked_amount(Subperiod::Voting);
2724 let next_voting = era_info.staked_amount_next_era(Subperiod::Voting);
2725
2726 let voting_total_staked: Balance = StakerInfo::<T>::iter()
2728 .filter(|(_, _, info)| info.period_number() == current_period)
2729 .map(|(_, _, info)| info.staked_amount(Subperiod::Voting))
2730 .sum();
2731
2732 ensure!(
2734 voting_total_staked == next_voting,
2735 "StakerInfo voting total != CurrentEraInfo.next voting stake"
2736 );
2737
2738 ensure!(
2740 current_voting <= next_voting,
2741 "Current voting stake > Next voting stake for same period"
2742 );
2743
2744 Ok(())
2745 }
2746 }
2747
2748 impl<T: Config> SafeModeNotify for Pallet<T> {
2752 fn entered() {
2753 Self::set_maintenance_mode(true);
2754 }
2755
2756 fn exited() {
2757 Self::set_maintenance_mode(false);
2758 }
2759 }
2760}