1#![cfg_attr(not(feature = "std"), no_std)]
37
38extern crate alloc;
39
40use alloc::vec;
41pub use alloc::vec::Vec;
42use frame_support::{
43 pallet_prelude::*,
44 traits::{
45 fungible::{Inspect as FunInspect, MutateFreeze as FunMutateFreeze},
46 SafeModeNotify, StorageVersion,
47 },
48 weights::Weight,
49};
50use frame_system::pallet_prelude::*;
51use sp_runtime::{
52 traits::{One, Saturating, UniqueSaturatedInto, Zero},
53 Perbill, Permill, SaturatedConversion,
54};
55
56use astar_primitives::{
57 dapp_staking::{
58 AccountCheck, CycleConfiguration, DAppId, EraNumber, Observer as DAppStakingObserver,
59 PeriodNumber, Rank, RankedTier, SmartContractHandle, StakingRewardHandler, TierId,
60 FIXED_TIER_SLOTS_ARGS,
61 },
62 Balance, BlockNumber,
63};
64
65pub use pallet::*;
66
67#[cfg(test)]
68mod test;
69
70#[cfg(feature = "runtime-benchmarks")]
71mod benchmarking;
72
73mod types;
74pub use types::*;
75
76pub mod migration;
77pub mod weights;
78
79pub use weights::WeightInfo;
80
81const LOG_TARGET: &str = "dapp-staking";
82
83pub(crate) enum TierAssignment {
85 Real,
87 #[cfg(feature = "runtime-benchmarks")]
89 Dummy,
90}
91
92#[doc = include_str!("../README.md")]
93#[frame_support::pallet]
94pub mod pallet {
95 use super::*;
96
97 pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
99
100 #[pallet::pallet]
101 #[pallet::storage_version(STORAGE_VERSION)]
102 pub struct Pallet<T>(_);
103
104 #[cfg(feature = "runtime-benchmarks")]
105 pub trait BenchmarkHelper<SmartContract, AccountId> {
106 fn get_smart_contract(id: u32) -> SmartContract;
107
108 fn set_balance(account: &AccountId, balance: Balance);
109 }
110
111 #[pallet::config]
112 pub trait Config: frame_system::Config {
113 #[allow(deprecated)]
115 type RuntimeEvent: From<Event<Self>>
116 + IsType<<Self as frame_system::Config>::RuntimeEvent>
117 + TryInto<Event<Self>>;
118
119 type RuntimeFreezeReason: From<FreezeReason>;
121
122 type Currency: FunMutateFreeze<
125 Self::AccountId,
126 Id = Self::RuntimeFreezeReason,
127 Balance = Balance,
128 >;
129
130 type SmartContract: Parameter
132 + Member
133 + MaxEncodedLen
134 + SmartContractHandle<Self::AccountId>;
135
136 type ContractRegisterOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
138
139 type ContractUnregisterOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
141
142 type ManagerOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
144
145 type StakingRewardHandler: StakingRewardHandler<Self::AccountId>;
147
148 type CycleConfiguration: CycleConfiguration;
150
151 type Observers: DAppStakingObserver;
153
154 type AccountCheck: AccountCheck<Self::AccountId>;
156
157 #[pallet::constant]
159 type EraRewardSpanLength: Get<u32>;
160
161 #[pallet::constant]
164 type RewardRetentionInPeriods: Get<PeriodNumber>;
165
166 #[pallet::constant]
168 type MaxNumberOfContracts: Get<u32>;
169
170 #[pallet::constant]
173 type MaxNumberOfContractsLegacy: Get<u32>;
174
175 #[pallet::constant]
177 type MaxUnlockingChunks: Get<u32>;
178
179 #[pallet::constant]
181 type MinimumLockedAmount: Get<Balance>;
182
183 #[pallet::constant]
186 type UnlockingPeriod: Get<EraNumber>;
187
188 #[pallet::constant]
190 type MaxNumberOfStakedContracts: Get<u32>;
191
192 #[pallet::constant]
194 type MinimumStakeAmount: Get<Balance>;
195
196 #[pallet::constant]
198 type NumberOfTiers: Get<u32>;
199
200 #[pallet::constant]
202 type RankingEnabled: Get<bool>;
203
204 #[pallet::constant]
208 type MaxBonusSafeMovesPerPeriod: Get<u8>;
209
210 type WeightInfo: WeightInfo;
212
213 #[cfg(feature = "runtime-benchmarks")]
215 type BenchmarkHelper: BenchmarkHelper<Self::SmartContract, Self::AccountId>;
216 }
217
218 #[pallet::event]
219 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
220 pub enum Event<T: Config> {
221 MaintenanceMode { enabled: bool },
223 NewEra { era: EraNumber },
225 NewSubperiod {
227 subperiod: Subperiod,
228 number: PeriodNumber,
229 },
230 DAppRegistered {
232 owner: T::AccountId,
233 smart_contract: T::SmartContract,
234 dapp_id: DAppId,
235 },
236 DAppRewardDestinationUpdated {
238 smart_contract: T::SmartContract,
239 beneficiary: Option<T::AccountId>,
240 },
241 DAppOwnerChanged {
243 smart_contract: T::SmartContract,
244 new_owner: T::AccountId,
245 },
246 DAppUnregistered {
248 smart_contract: T::SmartContract,
249 era: EraNumber,
250 },
251 Locked {
253 account: T::AccountId,
254 amount: Balance,
255 },
256 Unlocking {
258 account: T::AccountId,
259 amount: Balance,
260 },
261 ClaimedUnlocked {
263 account: T::AccountId,
264 amount: Balance,
265 },
266 Relock {
268 account: T::AccountId,
269 amount: Balance,
270 },
271 Stake {
273 account: T::AccountId,
274 smart_contract: T::SmartContract,
275 amount: Balance,
276 },
277 Unstake {
279 account: T::AccountId,
280 smart_contract: T::SmartContract,
281 amount: Balance,
282 },
283 Reward {
285 account: T::AccountId,
286 era: EraNumber,
287 amount: Balance,
288 },
289 BonusReward {
291 account: T::AccountId,
292 smart_contract: T::SmartContract,
293 period: PeriodNumber,
294 amount: Balance,
295 },
296 DAppReward {
298 beneficiary: T::AccountId,
299 smart_contract: T::SmartContract,
300 tier_id: TierId,
301 rank: Rank,
302 era: EraNumber,
303 amount: Balance,
304 },
305 UnstakeFromUnregistered {
307 account: T::AccountId,
308 smart_contract: T::SmartContract,
309 amount: Balance,
310 },
311 ExpiredEntriesRemoved { account: T::AccountId, count: u16 },
313 Force { forcing_type: ForcingType },
315 StakeMoved {
317 account: T::AccountId,
318 source_contract: T::SmartContract,
319 destination_contract: T::SmartContract,
320 amount: Balance,
321 },
322 NewTierParameters {
324 params: TierParameters<T::NumberOfTiers>,
325 },
326 }
327
328 #[pallet::error]
329 pub enum Error<T> {
330 Disabled,
332 ContractAlreadyExists,
334 ExceededMaxNumberOfContracts,
336 NewDAppIdUnavailable,
339 ContractNotFound,
341 OriginNotOwner,
343 ZeroAmount,
345 LockedAmountBelowThreshold,
347 AccountNotAvailableForDappStaking,
349 TooManyUnlockingChunks,
351 RemainingStakePreventsFullUnlock,
353 NoUnlockedChunksToClaim,
355 NoUnlockingChunks,
357 UnavailableStakeFunds,
359 UnclaimedRewards,
361 InternalStakeError,
363 InsufficientStakeAmount,
365 PeriodEndsInNextEra,
367 UnstakeFromPastPeriod,
369 UnstakeAmountTooLarge,
371 NoStakingInfo,
373 InternalUnstakeError,
375 RewardExpired,
377 RewardPayoutFailed,
379 NoClaimableRewards,
381 InternalClaimStakerError,
383 NotEligibleForBonusReward,
385 InternalClaimBonusError,
387 InvalidClaimEra,
389 NoDAppTierInfo,
392 InternalClaimDAppError,
394 ContractStillActive,
396 TooManyStakedContracts,
398 NoExpiredEntries,
400 ForceNotAllowed,
402 InvalidTierParams,
404 SameContracts,
406 }
407
408 #[pallet::storage]
410 #[pallet::whitelist_storage]
411 pub type ActiveProtocolState<T: Config> = StorageValue<_, ProtocolState, ValueQuery>;
412
413 #[pallet::storage]
415 pub type NextDAppId<T: Config> = StorageValue<_, DAppId, ValueQuery>;
416
417 #[pallet::storage]
422 pub type IntegratedDApps<T: Config> = CountedStorageMap<
423 Hasher = Blake2_128Concat,
424 Key = T::SmartContract,
425 Value = DAppInfo<T::AccountId>,
426 QueryKind = OptionQuery,
427 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
428 >;
429
430 #[pallet::storage]
432 pub type Ledger<T: Config> =
433 StorageMap<_, Blake2_128Concat, T::AccountId, AccountLedgerFor<T>, ValueQuery>;
434
435 #[pallet::storage]
437 pub type StakerInfo<T: Config> = StorageDoubleMap<
438 _,
439 Blake2_128Concat,
440 T::AccountId,
441 Blake2_128Concat,
442 T::SmartContract,
443 SingularStakingInfo,
444 OptionQuery,
445 >;
446
447 #[pallet::storage]
449 pub type ContractStake<T: Config> = StorageMap<
450 Hasher = Twox64Concat,
451 Key = DAppId,
452 Value = ContractStakeAmount,
453 QueryKind = ValueQuery,
454 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
455 >;
456
457 #[pallet::storage]
459 pub type CurrentEraInfo<T: Config> = StorageValue<_, EraInfo, ValueQuery>;
460
461 #[pallet::storage]
471 pub type EraRewards<T: Config> =
472 StorageMap<_, Twox64Concat, EraNumber, EraRewardSpan<T::EraRewardSpanLength>, OptionQuery>;
473
474 #[pallet::storage]
476 pub type PeriodEnd<T: Config> =
477 StorageMap<_, Twox64Concat, PeriodNumber, PeriodEndInfo, OptionQuery>;
478
479 #[pallet::storage]
481 pub type StaticTierParams<T: Config> =
482 StorageValue<_, TierParameters<T::NumberOfTiers>, ValueQuery>;
483
484 #[pallet::storage]
486 pub type TierConfig<T: Config> =
487 StorageValue<_, TiersConfiguration<T::NumberOfTiers>, ValueQuery>;
488
489 #[pallet::storage]
491 pub type DAppTiers<T: Config> =
492 StorageMap<_, Twox64Concat, EraNumber, DAppTierRewardsFor<T>, OptionQuery>;
493
494 #[pallet::storage]
496 pub type HistoryCleanupMarker<T: Config> = StorageValue<_, CleanupMarker, ValueQuery>;
497
498 #[pallet::type_value]
499 pub fn DefaultSafeguard<T: Config>() -> bool {
500 true
503 }
504
505 #[pallet::storage]
509 pub type Safeguard<T: Config> = StorageValue<_, bool, ValueQuery, DefaultSafeguard<T>>;
510
511 #[pallet::genesis_config]
512 pub struct GenesisConfig<T: Config> {
513 pub reward_portion: Vec<Permill>,
514 pub slot_distribution: Vec<Permill>,
515 pub tier_thresholds: Vec<TierThreshold>,
516 pub slot_number_args: (u64, u64),
517 pub slots_per_tier: Vec<u16>,
518 pub safeguard: Option<bool>,
519 pub tier_rank_multipliers: Vec<u32>,
520 #[serde(skip)]
521 pub _config: PhantomData<T>,
522 }
523
524 impl<T: Config> Default for GenesisConfig<T> {
525 fn default() -> Self {
526 let num_tiers = T::NumberOfTiers::get();
527 Self {
528 reward_portion: vec![
529 Permill::zero(), Permill::from_percent(70), Permill::from_percent(30), Permill::zero(), ],
534 slot_distribution: vec![
535 Permill::zero(),
536 Permill::from_parts(375_000), Permill::from_parts(625_000), Permill::zero(),
539 ],
540 tier_thresholds: vec![
541 TierThreshold::FixedPercentage {
542 required_percentage: Perbill::from_percent(3),
543 },
544 TierThreshold::FixedPercentage {
545 required_percentage: Perbill::from_percent(2),
546 },
547 TierThreshold::FixedPercentage {
548 required_percentage: Perbill::from_percent(1),
549 },
550 TierThreshold::FixedPercentage {
551 required_percentage: Perbill::zero(),
552 },
553 ],
554 slot_number_args: FIXED_TIER_SLOTS_ARGS,
555 slots_per_tier: vec![100; num_tiers as usize],
556 safeguard: None,
557 tier_rank_multipliers: vec![0u32, 24_000, 46_700, 0],
558 _config: Default::default(),
559 }
560 }
561 }
562
563 #[pallet::genesis_build]
564 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
565 fn build(&self) {
566 let tier_params = TierParameters::<T::NumberOfTiers> {
568 reward_portion: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
569 self.reward_portion.clone(),
570 )
571 .expect("Invalid number of reward portions provided."),
572 slot_distribution: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
573 self.slot_distribution.clone(),
574 )
575 .expect("Invalid number of slot distributions provided."),
576 tier_thresholds: BoundedVec::<TierThreshold, T::NumberOfTiers>::try_from(
577 self.tier_thresholds.clone(),
578 )
579 .expect("Invalid number of tier thresholds provided."),
580 slot_number_args: self.slot_number_args,
581 tier_rank_multipliers: BoundedVec::<u32, T::NumberOfTiers>::try_from(
582 self.tier_rank_multipliers.clone(),
583 )
584 .expect("Invalid number of tier points"),
585 };
586 assert!(
587 tier_params.is_valid(),
588 "Invalid tier parameters values provided."
589 );
590
591 let total_issuance = T::Currency::total_issuance();
592 let tier_thresholds = tier_params
593 .tier_thresholds
594 .iter()
595 .map(|t| t.threshold(total_issuance))
596 .collect::<Vec<Balance>>()
597 .try_into()
598 .expect("Invalid number of tier thresholds provided.");
599
600 let tier_config = TiersConfiguration::<T::NumberOfTiers> {
601 slots_per_tier: BoundedVec::<u16, T::NumberOfTiers>::try_from(
602 self.slots_per_tier.clone(),
603 )
604 .expect("Invalid number of slots per tier entries provided."),
605 reward_portion: tier_params.reward_portion.clone(),
606 tier_thresholds,
607 };
608 assert!(
609 tier_config.is_valid(),
610 "Invalid tier config values provided."
611 );
612
613 let protocol_state = ProtocolState {
615 era: 1,
616 next_era_start: Pallet::<T>::blocks_per_voting_period()
617 .checked_add(1)
618 .expect("Must not overflow - especially not at genesis."),
619 period_info: PeriodInfo {
620 number: 1,
621 subperiod: Subperiod::Voting,
622 next_subperiod_start_era: 2,
623 },
624 maintenance: false,
625 };
626
627 ActiveProtocolState::<T>::put(protocol_state);
629 StaticTierParams::<T>::put(tier_params);
630 TierConfig::<T>::put(tier_config.clone());
631
632 if self.safeguard.is_some() {
633 Safeguard::<T>::put(self.safeguard.unwrap());
634 }
635 }
636 }
637
638 #[pallet::hooks]
639 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
640 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
641 let now = now.saturated_into();
642 Self::era_and_period_handler(now, TierAssignment::Real)
643 }
644
645 fn on_idle(_block: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
646 Self::expired_entry_cleanup(&remaining_weight)
647 }
648
649 fn integrity_test() {
650 assert!(T::EraRewardSpanLength::get() > 0);
653 assert!(T::RewardRetentionInPeriods::get() > 0);
654 assert!(T::MaxNumberOfContracts::get() > 0);
655 assert!(T::MaxUnlockingChunks::get() > 0);
656 assert!(T::UnlockingPeriod::get() > 0);
657 assert!(T::MaxNumberOfStakedContracts::get() > 0);
658
659 assert!(T::MinimumLockedAmount::get() > 0);
660 assert!(T::MinimumStakeAmount::get() > 0);
661 assert!(T::MinimumLockedAmount::get() >= T::MinimumStakeAmount::get());
662
663 assert!(T::CycleConfiguration::periods_per_cycle() > 0);
665 assert!(T::CycleConfiguration::eras_per_voting_subperiod() > 0);
666 assert!(T::CycleConfiguration::eras_per_build_and_earn_subperiod() > 0);
667 assert!(T::CycleConfiguration::blocks_per_era() > 0);
668 }
669
670 #[cfg(feature = "try-runtime")]
671 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
672 Self::do_try_state()?;
673 Ok(())
674 }
675 }
676
677 #[pallet::composite_enum]
679 pub enum FreezeReason {
680 #[codec(index = 0)]
682 DAppStaking,
683 }
684
685 #[pallet::call]
686 impl<T: Config> Pallet<T> {
687 #[pallet::call_index(4)]
691 #[pallet::weight(T::WeightInfo::unlock())]
692 pub fn unbond_and_unstake(
693 origin: OriginFor<T>,
694 _contract_id: T::SmartContract,
695 #[pallet::compact] value: Balance,
696 ) -> DispatchResult {
697 Self::unlock(origin, value)
699 }
700
701 #[pallet::call_index(5)]
706 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
707 pub fn withdraw_unbonded(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
708 Self::claim_unlocked(origin)
709 }
710
711 #[pallet::call_index(0)]
714 #[pallet::weight(T::WeightInfo::maintenance_mode())]
715 pub fn maintenance_mode(origin: OriginFor<T>, enabled: bool) -> DispatchResult {
716 T::ManagerOrigin::ensure_origin(origin)?;
717 Self::set_maintenance_mode(enabled);
718 Ok(())
719 }
720
721 #[pallet::call_index(1)]
726 #[pallet::weight(T::WeightInfo::register())]
727 pub fn register(
728 origin: OriginFor<T>,
729 owner: T::AccountId,
730 smart_contract: T::SmartContract,
731 ) -> DispatchResult {
732 Self::ensure_pallet_enabled()?;
733 T::ContractRegisterOrigin::ensure_origin(origin)?;
734
735 ensure!(
736 !IntegratedDApps::<T>::contains_key(&smart_contract),
737 Error::<T>::ContractAlreadyExists,
738 );
739
740 ensure!(
741 IntegratedDApps::<T>::count() < T::MaxNumberOfContracts::get().into(),
742 Error::<T>::ExceededMaxNumberOfContracts
743 );
744
745 let dapp_id = NextDAppId::<T>::get();
746 ensure!(dapp_id < DAppId::MAX, Error::<T>::NewDAppIdUnavailable);
748
749 IntegratedDApps::<T>::insert(
750 &smart_contract,
751 DAppInfo {
752 owner: owner.clone(),
753 id: dapp_id,
754 reward_beneficiary: None,
755 },
756 );
757
758 NextDAppId::<T>::put(dapp_id.saturating_add(1));
759
760 Self::deposit_event(Event::<T>::DAppRegistered {
761 owner,
762 smart_contract,
763 dapp_id,
764 });
765
766 Ok(())
767 }
768
769 #[pallet::call_index(2)]
775 #[pallet::weight(T::WeightInfo::set_dapp_reward_beneficiary())]
776 pub fn set_dapp_reward_beneficiary(
777 origin: OriginFor<T>,
778 smart_contract: T::SmartContract,
779 beneficiary: Option<T::AccountId>,
780 ) -> DispatchResult {
781 Self::ensure_pallet_enabled()?;
782 let dev_account = ensure_signed(origin)?;
783
784 IntegratedDApps::<T>::try_mutate(
785 &smart_contract,
786 |maybe_dapp_info| -> DispatchResult {
787 let dapp_info = maybe_dapp_info
788 .as_mut()
789 .ok_or(Error::<T>::ContractNotFound)?;
790
791 ensure!(dapp_info.owner == dev_account, Error::<T>::OriginNotOwner);
792
793 dapp_info.reward_beneficiary = beneficiary.clone();
794
795 Ok(())
796 },
797 )?;
798
799 Self::deposit_event(Event::<T>::DAppRewardDestinationUpdated {
800 smart_contract,
801 beneficiary,
802 });
803
804 Ok(())
805 }
806
807 #[pallet::call_index(3)]
814 #[pallet::weight(T::WeightInfo::set_dapp_owner())]
815 pub fn set_dapp_owner(
816 origin: OriginFor<T>,
817 smart_contract: T::SmartContract,
818 new_owner: T::AccountId,
819 ) -> DispatchResult {
820 Self::ensure_pallet_enabled()?;
821 let origin = ensure_signed_or_root(origin)?;
822
823 IntegratedDApps::<T>::try_mutate(
824 &smart_contract,
825 |maybe_dapp_info| -> DispatchResult {
826 let dapp_info = maybe_dapp_info
827 .as_mut()
828 .ok_or(Error::<T>::ContractNotFound)?;
829
830 if let Some(caller) = origin {
832 ensure!(dapp_info.owner == caller, Error::<T>::OriginNotOwner);
833 }
834
835 dapp_info.owner = new_owner.clone();
836
837 Ok(())
838 },
839 )?;
840
841 Self::deposit_event(Event::<T>::DAppOwnerChanged {
842 smart_contract,
843 new_owner,
844 });
845
846 Ok(())
847 }
848
849 #[pallet::call_index(6)]
854 #[pallet::weight(T::WeightInfo::unregister())]
855 pub fn unregister(
856 origin: OriginFor<T>,
857 smart_contract: T::SmartContract,
858 ) -> DispatchResult {
859 Self::ensure_pallet_enabled()?;
860 T::ContractUnregisterOrigin::ensure_origin(origin)?;
861
862 let dapp_info =
863 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
864
865 ContractStake::<T>::remove(&dapp_info.id);
866 IntegratedDApps::<T>::remove(&smart_contract);
867
868 let current_era = ActiveProtocolState::<T>::get().era;
869 Self::deposit_event(Event::<T>::DAppUnregistered {
870 smart_contract,
871 era: current_era,
872 });
873
874 Ok(())
875 }
876
877 #[pallet::call_index(7)]
884 #[pallet::weight(T::WeightInfo::lock_new_account().max(T::WeightInfo::lock_existing_account()))]
885 pub fn lock(
886 origin: OriginFor<T>,
887 #[pallet::compact] amount: Balance,
888 ) -> DispatchResultWithPostInfo {
889 Self::ensure_pallet_enabled()?;
890 let account = ensure_signed(origin)?;
891
892 let mut ledger = Ledger::<T>::get(&account);
893
894 let is_new_account = ledger.is_empty();
898 if is_new_account {
899 ensure!(
900 T::AccountCheck::allowed_to_stake(&account),
901 Error::<T>::AccountNotAvailableForDappStaking
902 );
903 }
904
905 let available_balance =
907 T::Currency::total_balance(&account).saturating_sub(ledger.total_locked_amount());
908 let amount_to_lock = available_balance.min(amount);
909 ensure!(!amount_to_lock.is_zero(), Error::<T>::ZeroAmount);
910
911 ledger.add_lock_amount(amount_to_lock);
912
913 ensure!(
914 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
915 Error::<T>::LockedAmountBelowThreshold
916 );
917
918 Self::update_ledger(&account, ledger)?;
919 CurrentEraInfo::<T>::mutate(|era_info| {
920 era_info.add_locked(amount_to_lock);
921 });
922
923 Self::deposit_event(Event::<T>::Locked {
924 account,
925 amount: amount_to_lock,
926 });
927
928 Ok(Some(if is_new_account {
929 T::WeightInfo::lock_new_account()
930 } else {
931 T::WeightInfo::lock_existing_account()
932 })
933 .into())
934 }
935
936 #[pallet::call_index(8)]
942 #[pallet::weight(T::WeightInfo::unlock())]
943 pub fn unlock(origin: OriginFor<T>, #[pallet::compact] amount: Balance) -> DispatchResult {
944 Self::ensure_pallet_enabled()?;
945 let account = ensure_signed(origin)?;
946
947 let state = ActiveProtocolState::<T>::get();
948 let mut ledger = Ledger::<T>::get(&account);
949
950 let available_for_unlocking = ledger.unlockable_amount(state.period_info.number);
951 let amount_to_unlock = available_for_unlocking.min(amount);
952
953 let remaining_amount = ledger
955 .active_locked_amount()
956 .saturating_sub(amount_to_unlock);
957 let amount_to_unlock = if remaining_amount < T::MinimumLockedAmount::get() {
958 ensure!(
959 ledger.staked_amount(state.period_info.number).is_zero(),
960 Error::<T>::RemainingStakePreventsFullUnlock
961 );
962 ledger.active_locked_amount()
963 } else {
964 amount_to_unlock
965 };
966
967 ensure!(!amount_to_unlock.is_zero(), Error::<T>::ZeroAmount);
969
970 ledger.subtract_lock_amount(amount_to_unlock);
972
973 let current_block = frame_system::Pallet::<T>::block_number();
974 let unlock_block = current_block.saturating_add(Self::unlocking_period().into());
975 ledger
976 .add_unlocking_chunk(amount_to_unlock, unlock_block.saturated_into())
977 .map_err(|_| Error::<T>::TooManyUnlockingChunks)?;
978
979 Self::update_ledger(&account, ledger)?;
981 CurrentEraInfo::<T>::mutate(|era_info| {
982 era_info.unlocking_started(amount_to_unlock);
983 });
984
985 Self::deposit_event(Event::<T>::Unlocking {
986 account,
987 amount: amount_to_unlock,
988 });
989
990 Ok(())
991 }
992
993 #[pallet::call_index(9)]
995 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
996 pub fn claim_unlocked(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
997 Self::ensure_pallet_enabled()?;
998 let account = ensure_signed(origin)?;
999
1000 Self::internal_claim_unlocked(account)
1001 }
1002
1003 #[pallet::call_index(10)]
1004 #[pallet::weight(T::WeightInfo::relock_unlocking())]
1005 pub fn relock_unlocking(origin: OriginFor<T>) -> DispatchResult {
1006 Self::ensure_pallet_enabled()?;
1007 let account = ensure_signed(origin)?;
1008
1009 let mut ledger = Ledger::<T>::get(&account);
1010
1011 ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockingChunks);
1012
1013 let amount = ledger.consume_unlocking_chunks();
1014
1015 ledger.add_lock_amount(amount);
1016 ensure!(
1017 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
1018 Error::<T>::LockedAmountBelowThreshold
1019 );
1020
1021 Self::update_ledger(&account, ledger)?;
1022 CurrentEraInfo::<T>::mutate(|era_info| {
1023 era_info.add_locked(amount);
1024 era_info.unlocking_removed(amount);
1025 });
1026
1027 Self::deposit_event(Event::<T>::Relock { account, amount });
1028
1029 Ok(())
1030 }
1031
1032 #[pallet::call_index(11)]
1041 #[pallet::weight(T::WeightInfo::stake())]
1042 pub fn stake(
1043 origin: OriginFor<T>,
1044 smart_contract: T::SmartContract,
1045 #[pallet::compact] amount: Balance,
1046 ) -> DispatchResult {
1047 Self::ensure_pallet_enabled()?;
1048 let account = ensure_signed(origin)?;
1049
1050 let protocol_state = ActiveProtocolState::<T>::get();
1056 let (stake_amount, bonus_status) = match protocol_state.subperiod() {
1057 Subperiod::Voting => (
1058 StakeAmount {
1059 voting: amount,
1060 build_and_earn: 0,
1061 era: protocol_state.era,
1062 period: protocol_state.period_number(),
1063 },
1064 *BonusStatusWrapperFor::<T>::default(),
1065 ),
1066 Subperiod::BuildAndEarn => (
1067 StakeAmount {
1068 voting: 0,
1069 build_and_earn: amount,
1070 era: protocol_state.era,
1071 period: protocol_state.period_number(),
1072 },
1073 0,
1074 ),
1075 };
1076
1077 Self::inner_stake(&account, &smart_contract, stake_amount, bonus_status)?;
1079
1080 Self::deposit_event(Event::<T>::Stake {
1081 account,
1082 smart_contract,
1083 amount,
1084 });
1085
1086 Ok(())
1087 }
1088
1089 #[pallet::call_index(12)]
1099 #[pallet::weight(T::WeightInfo::unstake())]
1100 pub fn unstake(
1101 origin: OriginFor<T>,
1102 smart_contract: T::SmartContract,
1103 #[pallet::compact] amount: Balance,
1104 ) -> DispatchResult {
1105 Self::ensure_pallet_enabled()?;
1106 let account = ensure_signed(origin)?;
1107
1108 let (unstake_amount, _) = Self::inner_unstake(&account, &smart_contract, amount)?;
1109
1110 Self::deposit_event(Event::<T>::Unstake {
1111 account,
1112 smart_contract,
1113 amount: unstake_amount.total(),
1114 });
1115
1116 Ok(())
1117 }
1118
1119 #[pallet::call_index(13)]
1122 #[pallet::weight({
1123 let max_span_length = T::EraRewardSpanLength::get();
1124 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1125 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1126 })]
1127 pub fn claim_staker_rewards(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1128 Self::ensure_pallet_enabled()?;
1129 let account = ensure_signed(origin)?;
1130
1131 Self::internal_claim_staker_rewards_for(account)
1132 }
1133
1134 #[pallet::call_index(14)]
1136 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1137 pub fn claim_bonus_reward(
1138 origin: OriginFor<T>,
1139 smart_contract: T::SmartContract,
1140 ) -> DispatchResult {
1141 Self::ensure_pallet_enabled()?;
1142 let account = ensure_signed(origin)?;
1143
1144 Self::internal_claim_bonus_reward_for(account, smart_contract)
1145 }
1146
1147 #[pallet::call_index(15)]
1149 #[pallet::weight(T::WeightInfo::claim_dapp_reward())]
1150 pub fn claim_dapp_reward(
1151 origin: OriginFor<T>,
1152 smart_contract: T::SmartContract,
1153 #[pallet::compact] era: EraNumber,
1154 ) -> DispatchResult {
1155 Self::ensure_pallet_enabled()?;
1156
1157 let _ = ensure_signed(origin)?;
1159
1160 let dapp_info =
1161 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1162
1163 let protocol_state = ActiveProtocolState::<T>::get();
1165 ensure!(era < protocol_state.era, Error::<T>::InvalidClaimEra);
1166
1167 let mut dapp_tiers = DAppTiers::<T>::get(&era).ok_or(Error::<T>::NoDAppTierInfo)?;
1169 ensure!(
1170 dapp_tiers.period >= Self::oldest_claimable_period(protocol_state.period_number()),
1171 Error::<T>::RewardExpired
1172 );
1173
1174 let (amount, ranked_tier) =
1175 dapp_tiers
1176 .try_claim(dapp_info.id)
1177 .map_err(|error| match error {
1178 DAppTierError::NoDAppInTiers => Error::<T>::NoClaimableRewards,
1179 _ => Error::<T>::InternalClaimDAppError,
1180 })?;
1181
1182 let (tier_id, rank) = ranked_tier.deconstruct();
1183
1184 let beneficiary = dapp_info.reward_beneficiary();
1186 T::StakingRewardHandler::payout_reward(&beneficiary, amount)
1187 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
1188
1189 DAppTiers::<T>::insert(&era, dapp_tiers);
1191
1192 Self::deposit_event(Event::<T>::DAppReward {
1193 beneficiary: beneficiary.clone(),
1194 smart_contract,
1195 tier_id,
1196 rank,
1197 era,
1198 amount,
1199 });
1200
1201 Ok(())
1202 }
1203
1204 #[pallet::call_index(16)]
1207 #[pallet::weight(T::WeightInfo::unstake_from_unregistered())]
1208 pub fn unstake_from_unregistered(
1209 origin: OriginFor<T>,
1210 smart_contract: T::SmartContract,
1211 ) -> DispatchResult {
1212 Self::ensure_pallet_enabled()?;
1213 let account = ensure_signed(origin)?;
1214
1215 let (unstake_amount, _) =
1216 Self::inner_unstake_from_unregistered(&account, &smart_contract)?;
1217
1218 Self::deposit_event(Event::<T>::UnstakeFromUnregistered {
1219 account,
1220 smart_contract,
1221 amount: unstake_amount.total(),
1222 });
1223
1224 Ok(())
1225 }
1226
1227 #[pallet::call_index(17)]
1233 #[pallet::weight(T::WeightInfo::cleanup_expired_entries(
1234 T::MaxNumberOfStakedContracts::get()
1235 ))]
1236 pub fn cleanup_expired_entries(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1237 Self::ensure_pallet_enabled()?;
1238 let account = ensure_signed(origin)?;
1239
1240 let protocol_state = ActiveProtocolState::<T>::get();
1241 let current_period = protocol_state.period_number();
1242 let threshold_period = Self::oldest_claimable_period(current_period);
1243
1244 let mut remaining: u32 = 0;
1245 let mut to_be_deleted: Vec<T::SmartContract> = Vec::new();
1246
1247 for (smart_contract, stake_info) in StakerInfo::<T>::iter_prefix(&account) {
1252 let stake_period = stake_info.period_number();
1253
1254 let should_keep = stake_period == current_period
1256 || (stake_period >= threshold_period
1257 && stake_period < current_period
1258 && stake_info.is_bonus_eligible());
1259
1260 if should_keep {
1261 remaining = remaining.saturating_add(1);
1262 } else {
1263 to_be_deleted.push(smart_contract);
1264 }
1265 }
1266 let entries_to_delete = to_be_deleted.len();
1267
1268 ensure!(!entries_to_delete.is_zero(), Error::<T>::NoExpiredEntries);
1269
1270 for smart_contract in to_be_deleted {
1272 StakerInfo::<T>::remove(&account, &smart_contract);
1273 }
1274
1275 let mut ledger = Ledger::<T>::get(&account);
1277 ledger.contract_stake_count = remaining;
1278 ledger.maybe_cleanup_expired(threshold_period); Self::update_ledger(&account, ledger)?;
1280
1281 Self::deposit_event(Event::<T>::ExpiredEntriesRemoved {
1282 account,
1283 count: entries_to_delete.unique_saturated_into(),
1284 });
1285
1286 Ok(Some(T::WeightInfo::cleanup_expired_entries(
1287 entries_to_delete.unique_saturated_into(),
1288 ))
1289 .into())
1290 }
1291
1292 #[pallet::call_index(18)]
1300 #[pallet::weight(T::WeightInfo::force())]
1301 pub fn force(origin: OriginFor<T>, forcing_type: ForcingType) -> DispatchResult {
1302 Self::ensure_pallet_enabled()?;
1303 ensure_root(origin)?;
1304
1305 ensure!(!Safeguard::<T>::get(), Error::<T>::ForceNotAllowed);
1306
1307 ActiveProtocolState::<T>::mutate(|state| {
1309 let current_block = frame_system::Pallet::<T>::block_number();
1310 state.next_era_start = current_block.saturating_add(One::one()).saturated_into();
1311
1312 match forcing_type {
1313 ForcingType::Era => (),
1314 ForcingType::Subperiod => {
1315 state.period_info.next_subperiod_start_era = state.era.saturating_add(1);
1316 }
1317 }
1318
1319 Self::notify_block_before_new_era(&state);
1324 });
1325
1326 Self::deposit_event(Event::<T>::Force { forcing_type });
1327
1328 Ok(())
1329 }
1330
1331 #[pallet::call_index(19)]
1334 #[pallet::weight({
1335 let max_span_length = T::EraRewardSpanLength::get();
1336 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1337 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1338 })]
1339 pub fn claim_staker_rewards_for(
1340 origin: OriginFor<T>,
1341 account: T::AccountId,
1342 ) -> DispatchResultWithPostInfo {
1343 Self::ensure_pallet_enabled()?;
1344 ensure_signed(origin)?;
1345
1346 Self::internal_claim_staker_rewards_for(account)
1347 }
1348
1349 #[pallet::call_index(20)]
1351 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1352 pub fn claim_bonus_reward_for(
1353 origin: OriginFor<T>,
1354 account: T::AccountId,
1355 smart_contract: T::SmartContract,
1356 ) -> DispatchResult {
1357 Self::ensure_pallet_enabled()?;
1358 ensure_signed(origin)?;
1359
1360 Self::internal_claim_bonus_reward_for(account, smart_contract)
1361 }
1362
1363 #[pallet::call_index(21)]
1366 #[pallet::weight(T::WeightInfo::move_stake_unregistered_source().max(T::WeightInfo::move_stake_from_registered_source()))]
1367 pub fn move_stake(
1368 origin: OriginFor<T>,
1369 source_contract: T::SmartContract,
1370 destination_contract: T::SmartContract,
1371 #[pallet::compact] amount: Balance,
1372 ) -> DispatchResultWithPostInfo {
1373 Self::ensure_pallet_enabled()?;
1374 let account = ensure_signed(origin)?;
1375
1376 ensure!(
1377 !source_contract.eq(&destination_contract),
1378 Error::<T>::SameContracts
1379 );
1380
1381 ensure!(
1382 IntegratedDApps::<T>::contains_key(&destination_contract),
1383 Error::<T>::ContractNotFound
1384 );
1385
1386 let maybe_source_dapp_info = IntegratedDApps::<T>::get(&source_contract);
1387 let is_source_unregistered = maybe_source_dapp_info.is_none();
1388
1389 let (mut move_amount, bonus_status) = if is_source_unregistered {
1390 Self::inner_unstake_from_unregistered(&account, &source_contract)?
1391 } else {
1392 Self::inner_unstake(&account, &source_contract, amount)?
1393 };
1394
1395 if bonus_status == 0 && move_amount.voting > 0 {
1397 move_amount.convert_bonus_into_regular_stake();
1398 }
1399
1400 Self::inner_stake(&account, &destination_contract, move_amount, bonus_status)?;
1401
1402 Self::deposit_event(Event::<T>::StakeMoved {
1403 account,
1404 source_contract,
1405 destination_contract,
1406 amount: move_amount.total(),
1407 });
1408
1409 Ok(Some(if is_source_unregistered {
1410 T::WeightInfo::move_stake_unregistered_source()
1411 } else {
1412 T::WeightInfo::move_stake_from_registered_source()
1413 })
1414 .into())
1415 }
1416
1417 #[pallet::call_index(22)]
1423 #[pallet::weight(T::WeightInfo::set_static_tier_params())]
1424 pub fn set_static_tier_params(
1425 origin: OriginFor<T>,
1426 params: TierParameters<T::NumberOfTiers>,
1427 ) -> DispatchResult {
1428 Self::ensure_pallet_enabled()?;
1429 ensure_root(origin)?;
1430 ensure!(params.is_valid(), Error::<T>::InvalidTierParams);
1431
1432 StaticTierParams::<T>::set(params.clone());
1433
1434 Self::deposit_event(Event::<T>::NewTierParameters { params });
1435
1436 Ok(())
1437 }
1438 }
1439
1440 impl<T: Config> Pallet<T> {
1441 pub fn inner_unstake(
1448 account: &T::AccountId,
1449 smart_contract: &T::SmartContract,
1450 amount: Balance,
1451 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1452 ensure!(amount > 0, Error::<T>::ZeroAmount);
1453 let dapp_info =
1454 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1455
1456 let protocol_state = ActiveProtocolState::<T>::get();
1457 let current_era = protocol_state.era;
1458
1459 let mut ledger = Ledger::<T>::get(&account);
1460
1461 let (new_staking_info, amount, stake_amount_iter, updated_bonus_status) =
1464 match StakerInfo::<T>::get(&account, &smart_contract) {
1465 Some(mut staking_info) => {
1466 ensure!(
1467 staking_info.period_number() == protocol_state.period_number(),
1468 Error::<T>::UnstakeFromPastPeriod
1469 );
1470 ensure!(
1471 staking_info.total_staked_amount() >= amount,
1472 Error::<T>::UnstakeAmountTooLarge
1473 );
1474
1475 let amount = if staking_info.total_staked_amount().saturating_sub(amount)
1478 < T::MinimumStakeAmount::get()
1479 {
1480 staking_info.total_staked_amount()
1481 } else {
1482 amount
1483 };
1484
1485 let (stake_amount_iter, updated_bonus_status) =
1486 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1487
1488 (
1489 staking_info,
1490 amount,
1491 stake_amount_iter,
1492 updated_bonus_status,
1493 )
1494 }
1495 None => {
1496 return Err(Error::<T>::NoStakingInfo.into());
1497 }
1498 };
1499
1500 ledger
1503 .unstake_amount(amount, current_era, protocol_state.period_info)
1504 .map_err(|err| match err {
1505 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1506 Error::<T>::UnclaimedRewards
1507 }
1508 AccountLedgerError::UnstakeAmountLargerThanStake => {
1510 Error::<T>::UnstakeAmountTooLarge
1511 }
1512 _ => Error::<T>::InternalUnstakeError,
1513 })?;
1514
1515 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1518 contract_stake_info.unstake(
1519 &stake_amount_iter,
1520 protocol_state.period_info,
1521 current_era,
1522 );
1523
1524 CurrentEraInfo::<T>::mutate(|era_info| {
1527 era_info.unstake_amount(stake_amount_iter.clone());
1528 });
1529
1530 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1533
1534 if new_staking_info.is_empty() {
1535 ledger.contract_stake_count.saturating_dec();
1536 StakerInfo::<T>::remove(&account, &smart_contract);
1537 } else {
1538 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1539 }
1540
1541 Self::update_ledger(&account, ledger)?;
1542
1543 let mut unstake_amount = stake_amount_iter
1545 .iter()
1546 .max_by(|a, b| a.total().cmp(&b.total()))
1547 .ok_or(Error::<T>::InternalUnstakeError)?
1549 .clone();
1550
1551 unstake_amount.era = current_era;
1553
1554 Ok((unstake_amount, updated_bonus_status))
1555 }
1556
1557 pub fn inner_unstake_from_unregistered(
1563 account: &T::AccountId,
1564 smart_contract: &T::SmartContract,
1565 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1566 ensure!(
1567 !IntegratedDApps::<T>::contains_key(&smart_contract),
1568 Error::<T>::ContractStillActive
1569 );
1570
1571 let protocol_state = ActiveProtocolState::<T>::get();
1572 let current_era = protocol_state.era;
1573
1574 let (amount, unstake_amount_iter, preserved_bonus_status) =
1576 match StakerInfo::<T>::get(&account, &smart_contract) {
1577 Some(mut staking_info) => {
1578 ensure!(
1579 staking_info.period_number() == protocol_state.period_number(),
1580 Error::<T>::UnstakeFromPastPeriod
1581 );
1582
1583 let preserved_bonus_status = staking_info.bonus_status;
1584 let amount = staking_info.staked.total();
1585
1586 let (unstake_amount_iter, _) =
1587 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1588
1589 (amount, unstake_amount_iter, preserved_bonus_status)
1590 }
1591 None => {
1592 return Err(Error::<T>::NoStakingInfo.into());
1593 }
1594 };
1595
1596 let mut ledger = Ledger::<T>::get(&account);
1598 ledger
1599 .unstake_amount(amount, current_era, protocol_state.period_info)
1600 .map_err(|err| match err {
1601 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1603 Error::<T>::UnclaimedRewards
1604 }
1605 _ => Error::<T>::InternalUnstakeError,
1606 })?;
1607 ledger.contract_stake_count.saturating_dec();
1608
1609 CurrentEraInfo::<T>::mutate(|era_info| {
1613 era_info.unstake_amount(unstake_amount_iter.clone());
1614 });
1615
1616 Self::update_ledger(&account, ledger)?;
1618 StakerInfo::<T>::remove(&account, &smart_contract);
1619
1620 let mut unstake_amount = unstake_amount_iter
1622 .iter()
1623 .max_by(|a, b| a.total().cmp(&b.total()))
1624 .ok_or(Error::<T>::InternalUnstakeError)?
1626 .clone();
1627
1628 unstake_amount.era = current_era;
1630
1631 Ok((unstake_amount, preserved_bonus_status))
1632 }
1633
1634 pub fn inner_stake(
1639 account: &T::AccountId,
1640 smart_contract: &T::SmartContract,
1641 amount: StakeAmount,
1642 bonus_status: BonusStatus,
1643 ) -> Result<(), DispatchError> {
1644 ensure!(amount.total() > 0, Error::<T>::ZeroAmount);
1645
1646 let dapp_info =
1647 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1648
1649 let protocol_state = ActiveProtocolState::<T>::get();
1650 let current_era = protocol_state.era;
1651 let period_number = protocol_state.period_info.number;
1652 ensure!(
1653 !protocol_state
1654 .period_info
1655 .is_next_period(current_era.saturating_add(1)),
1656 Error::<T>::PeriodEndsInNextEra
1657 );
1658
1659 let mut ledger = Ledger::<T>::get(&account);
1660
1661 let threshold_period = Self::oldest_claimable_period(protocol_state.period_number());
1663 let _ignore = ledger.maybe_cleanup_expired(threshold_period);
1664
1665 ledger
1668 .add_stake_amount(amount, current_era, protocol_state.period_info)
1669 .map_err(|err| match err {
1670 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1671 Error::<T>::UnclaimedRewards
1672 }
1673 AccountLedgerError::UnavailableStakeFunds => Error::<T>::UnavailableStakeFunds,
1674 _ => Error::<T>::InternalStakeError,
1676 })?;
1677
1678 let (mut new_staking_info, is_new_entry, replacing_old_entry) =
1690 match StakerInfo::<T>::get(&account, &smart_contract) {
1691 Some(staking_info)
1693 if staking_info.period_number() == protocol_state.period_number() =>
1694 {
1695 (staking_info, false, false)
1696 }
1697 Some(staking_info)
1699 if staking_info.period_number() >= threshold_period
1700 && staking_info.is_bonus_eligible() =>
1701 {
1702 return Err(Error::<T>::UnclaimedRewards.into());
1703 }
1704 Some(_old_entry) => {
1706 StakerInfo::<T>::remove(&account, &smart_contract);
1708 (
1709 SingularStakingInfo::new(protocol_state.period_number(), bonus_status),
1710 true, true, )
1713 }
1714 None => (
1716 SingularStakingInfo::new(
1717 protocol_state.period_number(),
1718 bonus_status,
1721 ),
1722 true,
1723 false,
1724 ),
1725 };
1726
1727 new_staking_info.stake(amount, current_era, bonus_status);
1728 ensure!(
1729 new_staking_info.total_staked_amount() >= T::MinimumStakeAmount::get(),
1730 Error::<T>::InsufficientStakeAmount
1731 );
1732
1733 if is_new_entry && !replacing_old_entry {
1735 ledger.contract_stake_count.saturating_inc();
1736 ensure!(
1737 ledger.contract_stake_count <= T::MaxNumberOfStakedContracts::get(),
1738 Error::<T>::TooManyStakedContracts
1739 );
1740 }
1741
1742 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1745 contract_stake_info.stake(amount, current_era, period_number);
1746
1747 CurrentEraInfo::<T>::mutate(|era_info| {
1750 era_info.add_stake_amount(amount);
1751 });
1752
1753 Self::update_ledger(&account, ledger)?;
1756 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1757 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1758
1759 Ok(())
1760 }
1761
1762 pub fn is_staker(account: &T::AccountId) -> bool {
1764 Ledger::<T>::contains_key(account)
1765 }
1766
1767 pub(crate) fn ensure_pallet_enabled() -> Result<(), Error<T>> {
1769 if ActiveProtocolState::<T>::get().maintenance {
1770 Err(Error::<T>::Disabled)
1771 } else {
1772 Ok(())
1773 }
1774 }
1775
1776 pub(crate) fn update_ledger(
1783 account: &T::AccountId,
1784 ledger: AccountLedgerFor<T>,
1785 ) -> Result<(), DispatchError> {
1786 if ledger.is_empty() {
1787 Ledger::<T>::remove(&account);
1788 T::Currency::thaw(&FreezeReason::DAppStaking.into(), account)?;
1789 } else {
1790 T::Currency::set_freeze(
1791 &FreezeReason::DAppStaking.into(),
1792 account,
1793 ledger.total_locked_amount(),
1794 )?;
1795 Ledger::<T>::insert(account, ledger);
1796 }
1797
1798 Ok(())
1799 }
1800
1801 pub(crate) fn blocks_per_voting_period() -> BlockNumber {
1803 T::CycleConfiguration::blocks_per_era()
1804 .saturating_mul(T::CycleConfiguration::eras_per_voting_subperiod().into())
1805 }
1806
1807 pub fn era_reward_span_index(era: EraNumber) -> EraNumber {
1809 era.saturating_sub(era % T::EraRewardSpanLength::get())
1810 }
1811
1812 pub(crate) fn oldest_claimable_period(current_period: PeriodNumber) -> PeriodNumber {
1815 current_period.saturating_sub(T::RewardRetentionInPeriods::get())
1816 }
1817
1818 pub fn unlocking_period() -> BlockNumber {
1820 T::CycleConfiguration::blocks_per_era().saturating_mul(T::UnlockingPeriod::get().into())
1821 }
1822
1823 pub fn get_dapp_tier_assignment() -> BTreeMap<DAppId, RankedTier> {
1825 let protocol_state = ActiveProtocolState::<T>::get();
1826
1827 let (dapp_tiers, _count) = Self::get_dapp_tier_assignment_and_rewards(
1828 protocol_state.era,
1829 protocol_state.period_number(),
1830 Balance::zero(),
1831 );
1832
1833 dapp_tiers.dapps.into_inner()
1834 }
1835
1836 pub(crate) fn get_dapp_tier_assignment_and_rewards(
1868 era: EraNumber,
1869 period: PeriodNumber,
1870 dapp_reward_pool: Balance,
1871 ) -> (DAppTierRewardsFor<T>, DAppId) {
1872 let mut dapp_stakes = Vec::with_capacity(T::MaxNumberOfContracts::get() as usize);
1873
1874 let mut counter = 0;
1878 for (dapp_id, stake_amount) in ContractStake::<T>::iter() {
1879 counter.saturating_inc();
1880
1881 if let Some(stake_amount) = stake_amount.get(era, period) {
1883 if !stake_amount.total().is_zero() {
1884 dapp_stakes.push((dapp_id, stake_amount.total()));
1885 }
1886 }
1887 }
1888
1889 dapp_stakes.sort_unstable_by(|(_, amount_1), (_, amount_2)| amount_2.cmp(amount_1));
1892
1893 let tier_config = TierConfig::<T>::get();
1894 let tier_params = StaticTierParams::<T>::get();
1895
1896 let mut dapp_tiers = BTreeMap::new();
1908 let mut tier_rewards = Vec::with_capacity(tier_config.slots_per_tier.len());
1909 let mut rank_rewards = Vec::with_capacity(tier_config.slots_per_tier.len());
1910
1911 let mut upper_bound = Balance::zero();
1912
1913 for (tier_id, (tier_capacity, lower_bound)) in tier_config
1914 .slots_per_tier
1915 .iter()
1916 .zip(tier_config.tier_thresholds.iter())
1917 .enumerate()
1918 {
1919 let mut tier_slots = BTreeMap::new();
1920
1921 for (dapp_id, staked_amount) in dapp_stakes
1925 .iter()
1926 .skip(dapp_tiers.len())
1927 .take_while(|(_, amount)| amount.ge(lower_bound))
1928 .take(*tier_capacity as usize)
1929 {
1930 let rank = if T::RankingEnabled::get() {
1931 RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount)
1932 } else {
1933 0
1934 };
1935 tier_slots.insert(*dapp_id, RankedTier::new_saturated(tier_id as u8, rank));
1936 }
1937
1938 let filled_slots = tier_slots.len() as u32;
1940 let ranks_sum = tier_slots
1942 .iter()
1943 .fold(0u32, |accum, (_, x)| accum.saturating_add(x.rank().into()));
1944
1945 let multiplier_bips = tier_params
1946 .tier_rank_multipliers
1947 .get(tier_id)
1948 .copied()
1949 .unwrap_or(10_000);
1950
1951 let tier_allocation = tier_config
1952 .reward_portion
1953 .get(tier_id)
1954 .copied()
1955 .unwrap_or(Permill::zero())
1956 * dapp_reward_pool;
1957
1958 let (tier_reward, rank_reward) = Self::compute_tier_rewards(
1959 tier_allocation,
1960 *tier_capacity,
1961 filled_slots,
1962 ranks_sum,
1963 multiplier_bips,
1964 );
1965
1966 tier_rewards.push(tier_reward);
1967 rank_rewards.push(rank_reward);
1968 dapp_tiers.append(&mut tier_slots);
1969 upper_bound = *lower_bound; }
1971
1972 (
1976 DAppTierRewards::<T::MaxNumberOfContractsLegacy, T::NumberOfTiers>::new(
1977 dapp_tiers,
1978 tier_rewards,
1979 period,
1980 rank_rewards,
1981 )
1982 .unwrap_or_default(),
1983 counter,
1984 )
1985 }
1986
1987 pub(crate) fn era_and_period_handler(
1989 now: BlockNumber,
1990 tier_assignment: TierAssignment,
1991 ) -> Weight {
1992 let mut protocol_state = ActiveProtocolState::<T>::get();
1993
1994 let mut consumed_weight = T::DbWeight::get().reads(1);
1996
1997 if protocol_state.maintenance {
2001 return consumed_weight;
2002 }
2003
2004 if protocol_state.next_era_start == now.saturating_add(1) {
2006 consumed_weight
2007 .saturating_accrue(Self::notify_block_before_new_era(&protocol_state));
2008 }
2009
2010 if !protocol_state.is_new_era(now) {
2012 return consumed_weight;
2013 }
2014
2015 let mut era_info = CurrentEraInfo::<T>::get();
2017
2018 let current_era = protocol_state.era;
2019 let next_era = current_era.saturating_add(1);
2020 let (maybe_period_event, era_reward) = match protocol_state.subperiod() {
2021 Subperiod::Voting => {
2023 let era_reward = EraReward {
2025 staker_reward_pool: Balance::zero(),
2026 staked: era_info.total_staked_amount(),
2027 dapp_reward_pool: Balance::zero(),
2028 };
2029
2030 let next_subperiod_start_era = next_era
2031 .saturating_add(T::CycleConfiguration::eras_per_build_and_earn_subperiod());
2032 let build_and_earn_start_block =
2033 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2034 protocol_state.advance_to_next_subperiod(
2035 next_subperiod_start_era,
2036 build_and_earn_start_block,
2037 );
2038
2039 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2040
2041 consumed_weight
2042 .saturating_accrue(T::WeightInfo::on_initialize_voting_to_build_and_earn());
2043
2044 (
2045 Some(Event::<T>::NewSubperiod {
2046 subperiod: protocol_state.subperiod(),
2047 number: protocol_state.period_number(),
2048 }),
2049 era_reward,
2050 )
2051 }
2052 Subperiod::BuildAndEarn => {
2053 let staked = era_info.total_staked_amount();
2054 let (staker_reward_pool, dapp_reward_pool) =
2055 T::StakingRewardHandler::staker_and_dapp_reward_pools(staked);
2056 let era_reward = EraReward {
2057 staker_reward_pool,
2058 staked,
2059 dapp_reward_pool,
2060 };
2061
2062 let (dapp_tier_rewards, counter) = match tier_assignment {
2067 TierAssignment::Real => Self::get_dapp_tier_assignment_and_rewards(
2068 current_era,
2069 protocol_state.period_number(),
2070 dapp_reward_pool,
2071 ),
2072 #[cfg(feature = "runtime-benchmarks")]
2073 TierAssignment::Dummy => (DAppTierRewardsFor::<T>::default(), 0),
2074 };
2075 DAppTiers::<T>::insert(¤t_era, dapp_tier_rewards);
2076
2077 consumed_weight
2078 .saturating_accrue(T::WeightInfo::dapp_tier_assignment(counter.into()));
2079
2080 if protocol_state.period_info.is_next_period(next_era) {
2082 let bonus_reward_pool = T::StakingRewardHandler::bonus_reward_pool();
2084 PeriodEnd::<T>::insert(
2085 &protocol_state.period_number(),
2086 PeriodEndInfo {
2087 bonus_reward_pool,
2088 total_vp_stake: era_info.staked_amount(Subperiod::Voting),
2089 final_era: current_era,
2090 },
2091 );
2092
2093 let next_subperiod_start_era = next_era.saturating_add(1);
2096 let voting_period_length = Self::blocks_per_voting_period();
2097 let next_era_start_block = now.saturating_add(voting_period_length);
2098
2099 protocol_state.advance_to_next_subperiod(
2100 next_subperiod_start_era,
2101 next_era_start_block,
2102 );
2103
2104 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2105
2106 Self::update_cleanup_marker(protocol_state.period_number());
2109
2110 consumed_weight.saturating_accrue(
2111 T::WeightInfo::on_initialize_build_and_earn_to_voting(),
2112 );
2113
2114 (
2115 Some(Event::<T>::NewSubperiod {
2116 subperiod: protocol_state.subperiod(),
2117 number: protocol_state.period_number(),
2118 }),
2119 era_reward,
2120 )
2121 } else {
2122 let next_era_start_block =
2123 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2124 protocol_state.next_era_start = next_era_start_block;
2125
2126 era_info.migrate_to_next_era(None);
2127
2128 consumed_weight.saturating_accrue(
2129 T::WeightInfo::on_initialize_build_and_earn_to_build_and_earn(),
2130 );
2131
2132 (None, era_reward)
2133 }
2134 }
2135 };
2136
2137 protocol_state.era = next_era;
2139 ActiveProtocolState::<T>::put(protocol_state);
2140
2141 CurrentEraInfo::<T>::put(era_info);
2142
2143 let era_span_index = Self::era_reward_span_index(current_era);
2144 let mut span = EraRewards::<T>::get(&era_span_index).unwrap_or(EraRewardSpan::new());
2145 if let Err(_) = span.push(current_era, era_reward) {
2146 log::error!(
2148 target: LOG_TARGET,
2149 "Failed to push era {} into the era reward span.",
2150 current_era
2151 );
2152 }
2153 EraRewards::<T>::insert(&era_span_index, span);
2154
2155 let tier_params = StaticTierParams::<T>::get();
2157 let total_issuance = T::Currency::total_issuance();
2158
2159 let new_tier_config =
2160 TierConfig::<T>::get().calculate_new(&tier_params, total_issuance);
2161
2162 if new_tier_config.is_valid() {
2164 TierConfig::<T>::put(new_tier_config);
2165 } else {
2166 log::warn!(
2167 target: LOG_TARGET,
2168 "New tier configuration is invalid for era {}, preserving old one.",
2169 next_era
2170 );
2171 }
2172
2173 Self::deposit_event(Event::<T>::NewEra { era: next_era });
2174 if let Some(period_event) = maybe_period_event {
2175 Self::deposit_event(period_event);
2176 }
2177
2178 consumed_weight
2179 }
2180
2181 fn notify_block_before_new_era(protocol_state: &ProtocolState) -> Weight {
2183 let next_era = protocol_state.era.saturating_add(1);
2184 T::Observers::block_before_new_era(next_era)
2185 }
2186
2187 fn update_cleanup_marker(new_period_number: PeriodNumber) {
2191 let latest_expired_period = match new_period_number
2193 .checked_sub(T::RewardRetentionInPeriods::get().saturating_add(1))
2194 {
2195 Some(period) if !period.is_zero() => period,
2196 _ => return,
2198 };
2199
2200 let oldest_valid_era = match PeriodEnd::<T>::take(latest_expired_period) {
2205 Some(period_end_info) => period_end_info.final_era.saturating_add(1),
2206 None => {
2207 log::error!(
2209 target: LOG_TARGET,
2210 "No `PeriodEnd` entry for the expired period: {}",
2211 latest_expired_period
2212 );
2213 return;
2214 }
2215 };
2216
2217 HistoryCleanupMarker::<T>::mutate(|marker| {
2219 marker.oldest_valid_era = oldest_valid_era;
2220 });
2221 }
2222
2223 fn expired_entry_cleanup(remaining_weight: &Weight) -> Weight {
2227 if remaining_weight.any_lt(T::WeightInfo::on_idle_cleanup()) {
2229 return Weight::zero();
2230 }
2231
2232 let mut cleanup_marker = HistoryCleanupMarker::<T>::get();
2234 if !cleanup_marker.has_pending_cleanups() {
2235 return T::DbWeight::get().reads(1);
2236 }
2237
2238 if cleanup_marker.era_reward_index < cleanup_marker.oldest_valid_era {
2240 if let Some(era_reward) = EraRewards::<T>::get(cleanup_marker.era_reward_index) {
2241 if era_reward.last_era() < cleanup_marker.oldest_valid_era {
2243 EraRewards::<T>::remove(cleanup_marker.era_reward_index);
2244 cleanup_marker
2245 .era_reward_index
2246 .saturating_accrue(T::EraRewardSpanLength::get());
2247 }
2248 } else {
2249 log::warn!(
2251 target: LOG_TARGET,
2252 "Era rewards span for era {} is missing, but cleanup marker is set.",
2253 cleanup_marker.era_reward_index
2254 );
2255 cleanup_marker
2256 .era_reward_index
2257 .saturating_accrue(T::EraRewardSpanLength::get());
2258 }
2259 }
2260
2261 if cleanup_marker.dapp_tiers_index < cleanup_marker.oldest_valid_era {
2263 DAppTiers::<T>::remove(cleanup_marker.dapp_tiers_index);
2264 cleanup_marker.dapp_tiers_index.saturating_inc();
2265 }
2266
2267 HistoryCleanupMarker::<T>::put(cleanup_marker);
2269
2270 T::WeightInfo::on_idle_cleanup()
2276 }
2277
2278 fn internal_claim_unlocked(account: T::AccountId) -> DispatchResultWithPostInfo {
2280 let mut ledger = Ledger::<T>::get(&account);
2281
2282 let current_block = frame_system::Pallet::<T>::block_number();
2283 let amount = ledger.claim_unlocked(current_block.saturated_into());
2284 ensure!(amount > Zero::zero(), Error::<T>::NoUnlockedChunksToClaim);
2285
2286 let removed_entries = if ledger.is_empty() {
2288 let _ = StakerInfo::<T>::clear_prefix(&account, ledger.contract_stake_count, None);
2289 ledger.contract_stake_count
2290 } else {
2291 0
2292 };
2293
2294 Self::update_ledger(&account, ledger)?;
2295 CurrentEraInfo::<T>::mutate(|era_info| {
2296 era_info.unlocking_removed(amount);
2297 });
2298
2299 Self::deposit_event(Event::<T>::ClaimedUnlocked { account, amount });
2300
2301 Ok(Some(T::WeightInfo::claim_unlocked(removed_entries)).into())
2302 }
2303
2304 fn internal_claim_staker_rewards_for(account: T::AccountId) -> DispatchResultWithPostInfo {
2306 let mut ledger = Ledger::<T>::get(&account);
2307 let staked_period = ledger
2308 .staked_period()
2309 .ok_or(Error::<T>::NoClaimableRewards)?;
2310
2311 let protocol_state = ActiveProtocolState::<T>::get();
2313 ensure!(
2314 staked_period >= Self::oldest_claimable_period(protocol_state.period_number()),
2315 Error::<T>::RewardExpired
2316 );
2317
2318 let earliest_staked_era = ledger
2320 .earliest_staked_era()
2321 .ok_or(Error::<T>::InternalClaimStakerError)?;
2322 let era_rewards =
2323 EraRewards::<T>::get(Self::era_reward_span_index(earliest_staked_era))
2324 .ok_or(Error::<T>::NoClaimableRewards)?;
2325
2326 let (last_period_era, period_end) = if staked_period == protocol_state.period_number() {
2329 (protocol_state.era.saturating_sub(1), None)
2330 } else {
2331 PeriodEnd::<T>::get(&staked_period)
2332 .map(|info| (info.final_era, Some(info.final_era)))
2333 .ok_or(Error::<T>::InternalClaimStakerError)?
2334 };
2335
2336 let last_claim_era = era_rewards.last_era().min(last_period_era);
2338
2339 let rewards_iter =
2341 ledger
2342 .claim_up_to_era(last_claim_era, period_end)
2343 .map_err(|err| match err {
2344 AccountLedgerError::NothingToClaim => Error::<T>::NoClaimableRewards,
2345 _ => Error::<T>::InternalClaimStakerError,
2346 })?;
2347
2348 let mut rewards: Vec<_> = Vec::new();
2350 let mut reward_sum = Balance::zero();
2351 for (era, amount) in rewards_iter {
2352 let era_reward = era_rewards
2353 .get(era)
2354 .ok_or(Error::<T>::InternalClaimStakerError)?;
2355
2356 if amount.is_zero() || era_reward.staked.is_zero() {
2358 continue;
2359 }
2360 let staker_reward = Perbill::from_rational(amount, era_reward.staked)
2361 * era_reward.staker_reward_pool;
2362
2363 rewards.push((era, staker_reward));
2364 reward_sum.saturating_accrue(staker_reward);
2365 }
2366 let rewards_len: u32 = rewards.len().unique_saturated_into();
2367
2368 T::StakingRewardHandler::payout_reward(&account, reward_sum)
2369 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2370
2371 Self::update_ledger(&account, ledger)?;
2372
2373 rewards.into_iter().for_each(|(era, reward)| {
2374 Self::deposit_event(Event::<T>::Reward {
2375 account: account.clone(),
2376 era,
2377 amount: reward,
2378 });
2379 });
2380
2381 Ok(Some(if period_end.is_some() {
2382 T::WeightInfo::claim_staker_rewards_past_period(rewards_len)
2383 } else {
2384 T::WeightInfo::claim_staker_rewards_ongoing_period(rewards_len)
2385 })
2386 .into())
2387 }
2388
2389 fn internal_claim_bonus_reward_for(
2391 account: T::AccountId,
2392 smart_contract: T::SmartContract,
2393 ) -> DispatchResult {
2394 let staker_info = StakerInfo::<T>::get(&account, &smart_contract)
2395 .ok_or(Error::<T>::NoClaimableRewards)?;
2396 let protocol_state = ActiveProtocolState::<T>::get();
2397
2398 let staked_period = staker_info.period_number();
2403 ensure!(
2404 staked_period < protocol_state.period_number(),
2405 Error::<T>::NoClaimableRewards
2406 );
2407 ensure!(
2408 staker_info.is_bonus_eligible(),
2409 Error::<T>::NotEligibleForBonusReward
2410 );
2411 ensure!(
2412 staker_info.period_number()
2413 >= Self::oldest_claimable_period(protocol_state.period_number()),
2414 Error::<T>::RewardExpired
2415 );
2416
2417 let period_end_info =
2418 PeriodEnd::<T>::get(&staked_period).ok_or(Error::<T>::InternalClaimBonusError)?;
2419 ensure!(
2421 !period_end_info.total_vp_stake.is_zero(),
2422 Error::<T>::InternalClaimBonusError
2423 );
2424
2425 let eligible_amount = staker_info.staked_amount(Subperiod::Voting);
2426 let bonus_reward =
2427 Perbill::from_rational(eligible_amount, period_end_info.total_vp_stake)
2428 * period_end_info.bonus_reward_pool;
2429
2430 T::StakingRewardHandler::payout_reward(&account, bonus_reward)
2431 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2432
2433 StakerInfo::<T>::remove(&account, &smart_contract);
2435 Ledger::<T>::mutate(&account, |ledger| {
2436 ledger.contract_stake_count.saturating_dec();
2437 });
2438
2439 Self::deposit_event(Event::<T>::BonusReward {
2440 account: account.clone(),
2441 smart_contract,
2442 period: staked_period,
2443 amount: bonus_reward,
2444 });
2445
2446 Ok(())
2447 }
2448
2449 pub(crate) fn compute_tier_rewards(
2454 tier_allocation: Balance,
2455 tier_capacity: u16,
2456 filled_slots: u32,
2457 ranks_sum: u32,
2458 rank10_multiplier_bips: u32,
2459 ) -> (Balance, Balance) {
2460 const BASE_WEIGHT_BIPS: u128 = 10_000; let max_rank: u128 = RankedTier::MAX_RANK as u128; let avg_rank: u128 = max_rank / 2; if tier_allocation.is_zero() || tier_capacity == 0 || filled_slots == 0 {
2467 return (Balance::zero(), Balance::zero());
2468 }
2469
2470 let step_weight_bips: u128 = (rank10_multiplier_bips as u128)
2475 .saturating_sub(BASE_WEIGHT_BIPS)
2476 .saturating_div(max_rank);
2477
2478 let observed_total_weight: u128 = (filled_slots as u128)
2485 .saturating_mul(BASE_WEIGHT_BIPS)
2486 .saturating_add((ranks_sum as u128).saturating_mul(step_weight_bips));
2487
2488 let avg_slot_weight_bips: u128 =
2494 BASE_WEIGHT_BIPS.saturating_add(avg_rank.saturating_mul(step_weight_bips));
2495 let expected_full_weight: u128 =
2496 (tier_capacity as u128).saturating_mul(avg_slot_weight_bips);
2497
2498 let normalization_weight: u128 = observed_total_weight.max(expected_full_weight);
2502 if normalization_weight == 0 {
2503 return (Balance::zero(), Balance::zero());
2504 }
2505
2506 let alloc: u128 = tier_allocation.into();
2507 let tier_base_reward0_u128 =
2508 alloc.saturating_mul(BASE_WEIGHT_BIPS) / normalization_weight;
2509 let reward_per_rank_step_u128 =
2510 alloc.saturating_mul(step_weight_bips) / normalization_weight;
2511
2512 (
2513 tier_base_reward0_u128.saturated_into::<Balance>(),
2514 reward_per_rank_step_u128.saturated_into::<Balance>(),
2515 )
2516 }
2517
2518 fn set_maintenance_mode(enabled: bool) {
2522 ActiveProtocolState::<T>::mutate(|state| state.maintenance = enabled);
2523 Self::deposit_event(Event::<T>::MaintenanceMode { enabled });
2524 }
2525
2526 #[cfg(any(feature = "try-runtime", test))]
2528 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
2529 Self::try_state_protocol()?;
2530 Self::try_state_next_dapp_id()?;
2531 Self::try_state_integrated_dapps()?;
2532 Self::try_state_tiers()?;
2533 Self::try_state_ledger()?;
2534 Self::try_state_contract_stake()?;
2535 Self::try_state_era_rewards()?;
2536 Self::try_state_era_info()?;
2537
2538 Ok(())
2539 }
2540
2541 #[cfg(any(feature = "try-runtime", test))]
2546 pub fn try_state_protocol() -> Result<(), sp_runtime::TryRuntimeError> {
2547 let protocol_state = ActiveProtocolState::<T>::get();
2548
2549 if PeriodEnd::<T>::iter().count() >= protocol_state.period_info.number as usize {
2551 return Err("Number of periods in `PeriodEnd` exceeds or is equal to actual `PeriodInfo` number.".into());
2552 }
2553
2554 if protocol_state.era == 0 {
2556 return Err("Invalid era number in ActiveProtocolState.".into());
2557 }
2558
2559 let current_block: BlockNumber =
2560 frame_system::Pallet::<T>::block_number().saturated_into();
2561 if current_block > protocol_state.next_era_start {
2562 return Err(
2563 "Next era start block number is in the past in ActiveProtocolState.".into(),
2564 );
2565 }
2566
2567 Ok(())
2568 }
2569
2570 #[cfg(any(feature = "try-runtime", test))]
2575 pub fn try_state_next_dapp_id() -> Result<(), sp_runtime::TryRuntimeError> {
2576 let next_dapp_id = NextDAppId::<T>::get();
2577
2578 if next_dapp_id < IntegratedDApps::<T>::count() as u16 {
2580 return Err("Number of integrated dapps is greater than NextDAppId.".into());
2581 }
2582
2583 if next_dapp_id < ContractStake::<T>::iter().count() as u16 {
2585 return Err("Number of contract stake infos is greater than NextDAppId.".into());
2586 }
2587
2588 Ok(())
2589 }
2590
2591 #[cfg(any(feature = "try-runtime", test))]
2595 pub fn try_state_integrated_dapps() -> Result<(), sp_runtime::TryRuntimeError> {
2596 let integrated_dapps_count = IntegratedDApps::<T>::count();
2597 let max_number_of_contracts = T::MaxNumberOfContracts::get();
2598
2599 if integrated_dapps_count > max_number_of_contracts {
2600 return Err("Number of integrated dapps exceeds the maximum allowed.".into());
2601 }
2602
2603 Ok(())
2604 }
2605
2606 #[cfg(any(feature = "try-runtime", test))]
2611 pub fn try_state_tiers() -> Result<(), sp_runtime::TryRuntimeError> {
2612 let nb_tiers = T::NumberOfTiers::get();
2613 let tier_params = StaticTierParams::<T>::get();
2614 let tier_config = TierConfig::<T>::get();
2615
2616 if nb_tiers != tier_params.slot_distribution.len() as u32 {
2618 return Err(
2619 "Number of tiers is incorrect in slot_distribution in StaticTierParams.".into(),
2620 );
2621 }
2622 if nb_tiers != tier_params.reward_portion.len() as u32 {
2623 return Err(
2624 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2625 );
2626 }
2627 if nb_tiers != tier_params.tier_thresholds.len() as u32 {
2628 return Err(
2629 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2630 );
2631 }
2632
2633 if nb_tiers != tier_config.slots_per_tier.len() as u32 {
2635 return Err(
2636 "Number of tiers is incorrect in slots_per_tier in StaticTierParams.".into(),
2637 );
2638 }
2639 if nb_tiers != tier_config.reward_portion.len() as u32 {
2640 return Err(
2641 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2642 );
2643 }
2644 if nb_tiers != tier_config.tier_thresholds.len() as u32 {
2645 return Err(
2646 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2647 );
2648 }
2649
2650 Ok(())
2651 }
2652
2653 #[cfg(any(feature = "try-runtime", test))]
2661 pub fn try_state_ledger() -> Result<(), sp_runtime::TryRuntimeError> {
2662 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2663 let current_era_info = CurrentEraInfo::<T>::get();
2664 let next_era_total_stake = current_era_info.total_staked_amount_next_era();
2665
2666 let mut ledger_total_stake = Balance::zero();
2668 let mut ledger_total_locked = Balance::zero();
2669 let mut ledger_total_unlocking = Balance::zero();
2670
2671 for (_, ledger) in Ledger::<T>::iter() {
2672 let account_stake = ledger.staked_amount(current_period_number);
2673
2674 ledger_total_stake += account_stake;
2675 ledger_total_locked += ledger.active_locked_amount();
2676 ledger_total_unlocking += ledger.unlocking_amount();
2677
2678 if ledger.unlocking.len() > T::MaxUnlockingChunks::get() as usize {
2680 return Err("An account exceeds the maximum unlocking chunks.".into());
2681 }
2682
2683 if account_stake > Balance::zero() && account_stake < T::MinimumStakeAmount::get() {
2685 return Err(
2686 "An account has a stake amount lower than the minimum allowed.".into(),
2687 );
2688 }
2689
2690 if ledger.active_locked_amount() > Balance::zero()
2692 && ledger.active_locked_amount() < T::MinimumLockedAmount::get()
2693 {
2694 return Err(
2695 "An account has a locked amount lower than the minimum allowed.".into(),
2696 );
2697 }
2698
2699 if ledger.contract_stake_count > T::MaxNumberOfStakedContracts::get() {
2701 return Err("An account exceeds the maximum number of staked contracts.".into());
2702 }
2703 }
2704
2705 if ledger_total_stake != next_era_total_stake {
2707 return Err(
2708 "Mismatch between Ledger total staked amounts and CurrentEraInfo total.".into(),
2709 );
2710 }
2711
2712 if ledger_total_locked != current_era_info.total_locked {
2713 return Err(
2714 "Mismatch between Ledger total locked amounts and CurrentEraInfo total.".into(),
2715 );
2716 }
2717
2718 if ledger_total_unlocking != current_era_info.unlocking {
2719 return Err(
2720 "Mismatch between Ledger total unlocked amounts and CurrentEraInfo total."
2721 .into(),
2722 );
2723 }
2724
2725 Ok(())
2726 }
2727
2728 #[cfg(any(feature = "try-runtime", test))]
2732 pub fn try_state_contract_stake() -> Result<(), sp_runtime::TryRuntimeError> {
2733 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2734
2735 for (_, contract) in ContractStake::<T>::iter() {
2736 let contract_stake = contract.total_staked_amount(current_period_number);
2737
2738 if contract_stake > Balance::zero() && contract_stake < T::MinimumStakeAmount::get()
2740 {
2741 return Err(
2742 "A contract has a staked amount lower than the minimum allowed.".into(),
2743 );
2744 }
2745 }
2746
2747 Ok(())
2748 }
2749
2750 #[cfg(any(feature = "try-runtime", test))]
2755 pub fn try_state_era_rewards() -> Result<(), sp_runtime::TryRuntimeError> {
2756 let era_rewards = EraRewards::<T>::iter().collect::<Vec<_>>();
2757 let dapp_tiers = DAppTiers::<T>::iter().collect::<Vec<_>>();
2758
2759 for (era, _) in &dapp_tiers {
2761 let mut found = false;
2762 for (_, span) in &era_rewards {
2763 if *era >= span.first_era() && *era <= span.last_era() {
2764 found = true;
2765 break;
2766 }
2767 }
2768
2769 if !found {
2771 return Err("Era in DAppTiers is not found in any span in EraRewards.".into());
2772 }
2773 }
2774
2775 for (_, span) in &era_rewards {
2776 if span.len() > T::EraRewardSpanLength::get() as usize {
2778 return Err(
2779 "Span length for a era exceeds the maximum allowed span length.".into(),
2780 );
2781 }
2782 }
2783
2784 Ok(())
2785 }
2786
2787 #[cfg(any(feature = "try-runtime", test))]
2792 pub fn try_state_era_info() -> Result<(), sp_runtime::TryRuntimeError> {
2793 let protocol_state = ActiveProtocolState::<T>::get();
2794 let current_period = protocol_state.period_number();
2795 let era_info = CurrentEraInfo::<T>::get();
2796
2797 let current_voting = era_info.staked_amount(Subperiod::Voting);
2798 let next_voting = era_info.staked_amount_next_era(Subperiod::Voting);
2799
2800 let voting_total_staked: Balance = StakerInfo::<T>::iter()
2802 .filter(|(_, _, info)| info.period_number() == current_period)
2803 .map(|(_, _, info)| info.staked_amount(Subperiod::Voting))
2804 .sum();
2805
2806 ensure!(
2808 voting_total_staked == next_voting,
2809 "StakerInfo voting total != CurrentEraInfo.next voting stake"
2810 );
2811
2812 ensure!(
2814 current_voting <= next_voting,
2815 "Current voting stake > Next voting stake for same period"
2816 );
2817
2818 Ok(())
2819 }
2820 }
2821
2822 impl<T: Config> SafeModeNotify for Pallet<T> {
2826 fn entered() {
2827 Self::set_maintenance_mode(true);
2828 }
2829
2830 fn exited() {
2831 Self::set_maintenance_mode(false);
2832 }
2833 }
2834}