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 },
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(12);
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 StakingRewardHandler: StakingRewardHandler<Self::AccountId>;
146
147 type CycleConfiguration: CycleConfiguration;
149
150 type Observers: DAppStakingObserver;
152
153 type AccountCheck: AccountCheck<Self::AccountId>;
155
156 #[pallet::constant]
158 type EraRewardSpanLength: Get<u32>;
159
160 #[pallet::constant]
163 type RewardRetentionInPeriods: Get<PeriodNumber>;
164
165 #[pallet::constant]
167 type MaxNumberOfContracts: Get<u32>;
168
169 #[pallet::constant]
171 type MaxUnlockingChunks: Get<u32>;
172
173 #[pallet::constant]
175 type MinimumLockedAmount: Get<Balance>;
176
177 #[pallet::constant]
180 type UnlockingPeriod: Get<EraNumber>;
181
182 #[pallet::constant]
184 type MaxNumberOfStakedContracts: Get<u32>;
185
186 #[pallet::constant]
188 type MinimumStakeAmount: Get<Balance>;
189
190 #[pallet::constant]
192 type NumberOfTiers: Get<u32>;
193
194 #[pallet::constant]
196 type RankingEnabled: Get<bool>;
197
198 #[pallet::constant]
202 type MaxBonusSafeMovesPerPeriod: Get<u8>;
203
204 type WeightInfo: WeightInfo;
206
207 #[cfg(feature = "runtime-benchmarks")]
209 type BenchmarkHelper: BenchmarkHelper<Self::SmartContract, Self::AccountId>;
210 }
211
212 #[pallet::event]
213 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
214 pub enum Event<T: Config> {
215 MaintenanceMode { enabled: bool },
217 NewEra { era: EraNumber },
219 NewSubperiod {
221 subperiod: Subperiod,
222 number: PeriodNumber,
223 },
224 DAppRegistered {
226 owner: T::AccountId,
227 smart_contract: T::SmartContract,
228 dapp_id: DAppId,
229 },
230 DAppRewardDestinationUpdated {
232 smart_contract: T::SmartContract,
233 beneficiary: Option<T::AccountId>,
234 },
235 DAppOwnerChanged {
237 smart_contract: T::SmartContract,
238 new_owner: T::AccountId,
239 },
240 DAppUnregistered {
242 smart_contract: T::SmartContract,
243 era: EraNumber,
244 },
245 Locked {
247 account: T::AccountId,
248 amount: Balance,
249 },
250 Unlocking {
252 account: T::AccountId,
253 amount: Balance,
254 },
255 ClaimedUnlocked {
257 account: T::AccountId,
258 amount: Balance,
259 },
260 Relock {
262 account: T::AccountId,
263 amount: Balance,
264 },
265 Stake {
267 account: T::AccountId,
268 smart_contract: T::SmartContract,
269 amount: Balance,
270 },
271 Unstake {
273 account: T::AccountId,
274 smart_contract: T::SmartContract,
275 amount: Balance,
276 },
277 Reward {
279 account: T::AccountId,
280 era: EraNumber,
281 amount: Balance,
282 },
283 BonusReward {
285 account: T::AccountId,
286 smart_contract: T::SmartContract,
287 period: PeriodNumber,
288 amount: Balance,
289 },
290 DAppReward {
292 beneficiary: T::AccountId,
293 smart_contract: T::SmartContract,
294 tier_id: TierId,
295 rank: Rank,
296 era: EraNumber,
297 amount: Balance,
298 },
299 UnstakeFromUnregistered {
301 account: T::AccountId,
302 smart_contract: T::SmartContract,
303 amount: Balance,
304 },
305 ExpiredEntriesRemoved { account: T::AccountId, count: u16 },
307 Force { forcing_type: ForcingType },
309 StakeMoved {
311 account: T::AccountId,
312 source_contract: T::SmartContract,
313 destination_contract: T::SmartContract,
314 amount: Balance,
315 },
316 NewTierParameters {
318 params: TierParameters<T::NumberOfTiers>,
319 },
320 }
321
322 #[pallet::error]
323 pub enum Error<T> {
324 Disabled,
326 ContractAlreadyExists,
328 ExceededMaxNumberOfContracts,
330 NewDAppIdUnavailable,
333 ContractNotFound,
335 OriginNotOwner,
337 ZeroAmount,
339 LockedAmountBelowThreshold,
341 AccountNotAvailableForDappStaking,
343 TooManyUnlockingChunks,
345 RemainingStakePreventsFullUnlock,
347 NoUnlockedChunksToClaim,
349 NoUnlockingChunks,
351 UnavailableStakeFunds,
353 UnclaimedRewards,
355 InternalStakeError,
357 InsufficientStakeAmount,
359 PeriodEndsInNextEra,
361 UnstakeFromPastPeriod,
363 UnstakeAmountTooLarge,
365 NoStakingInfo,
367 InternalUnstakeError,
369 RewardExpired,
371 RewardPayoutFailed,
373 NoClaimableRewards,
375 InternalClaimStakerError,
377 NotEligibleForBonusReward,
379 InternalClaimBonusError,
381 InvalidClaimEra,
383 NoDAppTierInfo,
386 InternalClaimDAppError,
388 ContractStillActive,
390 TooManyStakedContracts,
392 NoExpiredEntries,
394 ForceNotAllowed,
396 InvalidTierParams,
398 SameContracts,
400 }
401
402 #[pallet::storage]
404 #[pallet::whitelist_storage]
405 pub type ActiveProtocolState<T: Config> = StorageValue<_, ProtocolState, ValueQuery>;
406
407 #[pallet::storage]
409 pub type NextDAppId<T: Config> = StorageValue<_, DAppId, ValueQuery>;
410
411 #[pallet::storage]
416 pub type IntegratedDApps<T: Config> = CountedStorageMap<
417 Hasher = Blake2_128Concat,
418 Key = T::SmartContract,
419 Value = DAppInfo<T::AccountId>,
420 QueryKind = OptionQuery,
421 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
422 >;
423
424 #[pallet::storage]
426 pub type Ledger<T: Config> =
427 StorageMap<_, Blake2_128Concat, T::AccountId, AccountLedgerFor<T>, ValueQuery>;
428
429 #[pallet::storage]
431 pub type StakerInfo<T: Config> = StorageDoubleMap<
432 _,
433 Blake2_128Concat,
434 T::AccountId,
435 Blake2_128Concat,
436 T::SmartContract,
437 SingularStakingInfo,
438 OptionQuery,
439 >;
440
441 #[pallet::storage]
443 pub type ContractStake<T: Config> = StorageMap<
444 Hasher = Twox64Concat,
445 Key = DAppId,
446 Value = ContractStakeAmount,
447 QueryKind = ValueQuery,
448 MaxValues = ConstU32<{ DAppId::MAX as u32 }>,
449 >;
450
451 #[pallet::storage]
453 pub type CurrentEraInfo<T: Config> = StorageValue<_, EraInfo, ValueQuery>;
454
455 #[pallet::storage]
465 pub type EraRewards<T: Config> =
466 StorageMap<_, Twox64Concat, EraNumber, EraRewardSpan<T::EraRewardSpanLength>, OptionQuery>;
467
468 #[pallet::storage]
470 pub type PeriodEnd<T: Config> =
471 StorageMap<_, Twox64Concat, PeriodNumber, PeriodEndInfo, OptionQuery>;
472
473 #[pallet::storage]
475 pub type StaticTierParams<T: Config> =
476 StorageValue<_, TierParameters<T::NumberOfTiers>, ValueQuery>;
477
478 #[pallet::storage]
480 pub type TierConfig<T: Config> =
481 StorageValue<_, TiersConfiguration<T::NumberOfTiers>, ValueQuery>;
482
483 #[pallet::storage]
485 pub type DAppTiers<T: Config> =
486 StorageMap<_, Twox64Concat, EraNumber, DAppTierRewardsFor<T>, OptionQuery>;
487
488 #[pallet::storage]
490 pub type HistoryCleanupMarker<T: Config> = StorageValue<_, CleanupMarker, ValueQuery>;
491
492 #[pallet::type_value]
493 pub fn DefaultSafeguard<T: Config>() -> bool {
494 true
497 }
498
499 #[pallet::storage]
503 pub type Safeguard<T: Config> = StorageValue<_, bool, ValueQuery, DefaultSafeguard<T>>;
504
505 #[pallet::genesis_config]
506 pub struct GenesisConfig<T: Config> {
507 pub reward_portion: Vec<Permill>,
508 pub slot_distribution: Vec<Permill>,
509 pub tier_thresholds: Vec<TierThreshold>,
510 pub slots_per_tier: Vec<u16>,
511 pub safeguard: Option<bool>,
512 pub tier_rank_multipliers: Vec<u32>,
513 #[serde(skip)]
514 pub _config: PhantomData<T>,
515 }
516
517 impl<T: Config> Default for GenesisConfig<T> {
518 fn default() -> Self {
519 let num_tiers = T::NumberOfTiers::get();
520 Self {
521 reward_portion: vec![
522 Permill::zero(), Permill::from_percent(70), Permill::from_percent(30), Permill::zero(), ],
527 slot_distribution: vec![
528 Permill::zero(),
529 Permill::from_parts(375_000), Permill::from_parts(625_000), Permill::zero(),
532 ],
533 tier_thresholds: vec![
534 TierThreshold::FixedPercentage {
535 required_percentage: Perbill::from_percent(3),
536 },
537 TierThreshold::FixedPercentage {
538 required_percentage: Perbill::from_percent(2),
539 },
540 TierThreshold::FixedPercentage {
541 required_percentage: Perbill::from_percent(1),
542 },
543 TierThreshold::FixedPercentage {
544 required_percentage: Perbill::zero(),
545 },
546 ],
547 slots_per_tier: vec![100; num_tiers as usize],
548 safeguard: None,
549 tier_rank_multipliers: vec![0u32, 24_000, 46_700, 0],
550 _config: Default::default(),
551 }
552 }
553 }
554
555 #[pallet::genesis_build]
556 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
557 fn build(&self) {
558 let tier_params = TierParameters::<T::NumberOfTiers> {
560 reward_portion: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
561 self.reward_portion.clone(),
562 )
563 .expect("Invalid number of reward portions provided."),
564 slot_distribution: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
565 self.slot_distribution.clone(),
566 )
567 .expect("Invalid number of slot distributions provided."),
568 tier_thresholds: BoundedVec::<TierThreshold, T::NumberOfTiers>::try_from(
569 self.tier_thresholds.clone(),
570 )
571 .expect("Invalid number of tier thresholds provided."),
572 tier_rank_multipliers: BoundedVec::<u32, T::NumberOfTiers>::try_from(
573 self.tier_rank_multipliers.clone(),
574 )
575 .expect("Invalid number of tier points"),
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 = TiersConfiguration::<T::NumberOfTiers> {
592 slots_per_tier: BoundedVec::<u16, T::NumberOfTiers>::try_from(
593 self.slots_per_tier.clone(),
594 )
595 .expect("Invalid number of slots per tier entries provided."),
596 reward_portion: tier_params.reward_portion.clone(),
597 tier_thresholds,
598 };
599 assert!(
600 tier_config.is_valid(),
601 "Invalid tier config values provided."
602 );
603
604 let protocol_state = ProtocolState {
606 era: 1,
607 next_era_start: Pallet::<T>::blocks_per_voting_period()
608 .checked_add(1)
609 .expect("Must not overflow - especially not at genesis."),
610 period_info: PeriodInfo {
611 number: 1,
612 subperiod: Subperiod::Voting,
613 next_subperiod_start_era: 2,
614 },
615 maintenance: false,
616 };
617
618 ActiveProtocolState::<T>::put(protocol_state);
620 StaticTierParams::<T>::put(tier_params);
621 TierConfig::<T>::put(tier_config.clone());
622
623 if self.safeguard.is_some() {
624 Safeguard::<T>::put(self.safeguard.unwrap());
625 }
626 }
627 }
628
629 #[pallet::hooks]
630 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
631 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
632 let now = now.saturated_into();
633 Self::era_and_period_handler(now, TierAssignment::Real)
634 }
635
636 fn on_idle(_block: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
637 Self::expired_entry_cleanup(&remaining_weight)
638 }
639
640 fn integrity_test() {
641 assert!(T::EraRewardSpanLength::get() > 0);
644 assert!(T::RewardRetentionInPeriods::get() > 0);
645 assert!(T::MaxNumberOfContracts::get() > 0);
646 assert!(T::MaxUnlockingChunks::get() > 0);
647 assert!(T::UnlockingPeriod::get() > 0);
648 assert!(T::MaxNumberOfStakedContracts::get() > 0);
649
650 assert!(T::MinimumLockedAmount::get() > 0);
651 assert!(T::MinimumStakeAmount::get() > 0);
652 assert!(T::MinimumLockedAmount::get() >= T::MinimumStakeAmount::get());
653
654 assert!(T::CycleConfiguration::periods_per_cycle() > 0);
656 assert!(T::CycleConfiguration::eras_per_voting_subperiod() > 0);
657 assert!(T::CycleConfiguration::eras_per_build_and_earn_subperiod() > 0);
658 assert!(T::CycleConfiguration::blocks_per_era() > 0);
659 }
660
661 #[cfg(feature = "try-runtime")]
662 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
663 Self::do_try_state()?;
664 Ok(())
665 }
666 }
667
668 #[pallet::composite_enum]
670 pub enum FreezeReason {
671 #[codec(index = 0)]
673 DAppStaking,
674 }
675
676 #[pallet::call]
677 impl<T: Config> Pallet<T> {
678 #[pallet::call_index(4)]
682 #[pallet::weight(T::WeightInfo::unlock())]
683 pub fn unbond_and_unstake(
684 origin: OriginFor<T>,
685 _contract_id: T::SmartContract,
686 #[pallet::compact] value: Balance,
687 ) -> DispatchResult {
688 Self::unlock(origin, value)
690 }
691
692 #[pallet::call_index(5)]
697 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
698 pub fn withdraw_unbonded(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
699 Self::claim_unlocked(origin)
700 }
701
702 #[pallet::call_index(0)]
705 #[pallet::weight(T::WeightInfo::maintenance_mode())]
706 pub fn maintenance_mode(origin: OriginFor<T>, enabled: bool) -> DispatchResult {
707 T::ManagerOrigin::ensure_origin(origin)?;
708 Self::set_maintenance_mode(enabled);
709 Ok(())
710 }
711
712 #[pallet::call_index(1)]
717 #[pallet::weight(T::WeightInfo::register())]
718 pub fn register(
719 origin: OriginFor<T>,
720 owner: T::AccountId,
721 smart_contract: T::SmartContract,
722 ) -> DispatchResult {
723 Self::ensure_pallet_enabled()?;
724 T::ContractRegisterOrigin::ensure_origin(origin)?;
725
726 ensure!(
727 !IntegratedDApps::<T>::contains_key(&smart_contract),
728 Error::<T>::ContractAlreadyExists,
729 );
730
731 ensure!(
732 IntegratedDApps::<T>::count() < T::MaxNumberOfContracts::get().into(),
733 Error::<T>::ExceededMaxNumberOfContracts
734 );
735
736 let dapp_id = NextDAppId::<T>::get();
737 ensure!(dapp_id < DAppId::MAX, Error::<T>::NewDAppIdUnavailable);
739
740 IntegratedDApps::<T>::insert(
741 &smart_contract,
742 DAppInfo {
743 owner: owner.clone(),
744 id: dapp_id,
745 reward_beneficiary: None,
746 },
747 );
748
749 NextDAppId::<T>::put(dapp_id.saturating_add(1));
750
751 Self::deposit_event(Event::<T>::DAppRegistered {
752 owner,
753 smart_contract,
754 dapp_id,
755 });
756
757 Ok(())
758 }
759
760 #[pallet::call_index(2)]
766 #[pallet::weight(T::WeightInfo::set_dapp_reward_beneficiary())]
767 pub fn set_dapp_reward_beneficiary(
768 origin: OriginFor<T>,
769 smart_contract: T::SmartContract,
770 beneficiary: Option<T::AccountId>,
771 ) -> DispatchResult {
772 Self::ensure_pallet_enabled()?;
773 let dev_account = ensure_signed(origin)?;
774
775 IntegratedDApps::<T>::try_mutate(
776 &smart_contract,
777 |maybe_dapp_info| -> DispatchResult {
778 let dapp_info = maybe_dapp_info
779 .as_mut()
780 .ok_or(Error::<T>::ContractNotFound)?;
781
782 ensure!(dapp_info.owner == dev_account, Error::<T>::OriginNotOwner);
783
784 dapp_info.reward_beneficiary = beneficiary.clone();
785
786 Ok(())
787 },
788 )?;
789
790 Self::deposit_event(Event::<T>::DAppRewardDestinationUpdated {
791 smart_contract,
792 beneficiary,
793 });
794
795 Ok(())
796 }
797
798 #[pallet::call_index(3)]
805 #[pallet::weight(T::WeightInfo::set_dapp_owner())]
806 pub fn set_dapp_owner(
807 origin: OriginFor<T>,
808 smart_contract: T::SmartContract,
809 new_owner: T::AccountId,
810 ) -> DispatchResult {
811 Self::ensure_pallet_enabled()?;
812 let origin = ensure_signed_or_root(origin)?;
813
814 IntegratedDApps::<T>::try_mutate(
815 &smart_contract,
816 |maybe_dapp_info| -> DispatchResult {
817 let dapp_info = maybe_dapp_info
818 .as_mut()
819 .ok_or(Error::<T>::ContractNotFound)?;
820
821 if let Some(caller) = origin {
823 ensure!(dapp_info.owner == caller, Error::<T>::OriginNotOwner);
824 }
825
826 dapp_info.owner = new_owner.clone();
827
828 Ok(())
829 },
830 )?;
831
832 Self::deposit_event(Event::<T>::DAppOwnerChanged {
833 smart_contract,
834 new_owner,
835 });
836
837 Ok(())
838 }
839
840 #[pallet::call_index(6)]
845 #[pallet::weight(T::WeightInfo::unregister())]
846 pub fn unregister(
847 origin: OriginFor<T>,
848 smart_contract: T::SmartContract,
849 ) -> DispatchResult {
850 Self::ensure_pallet_enabled()?;
851 T::ContractUnregisterOrigin::ensure_origin(origin)?;
852
853 let dapp_info =
854 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
855
856 ContractStake::<T>::remove(&dapp_info.id);
857 IntegratedDApps::<T>::remove(&smart_contract);
858
859 let current_era = ActiveProtocolState::<T>::get().era;
860 Self::deposit_event(Event::<T>::DAppUnregistered {
861 smart_contract,
862 era: current_era,
863 });
864
865 Ok(())
866 }
867
868 #[pallet::call_index(7)]
875 #[pallet::weight(T::WeightInfo::lock_new_account().max(T::WeightInfo::lock_existing_account()))]
876 pub fn lock(
877 origin: OriginFor<T>,
878 #[pallet::compact] amount: Balance,
879 ) -> DispatchResultWithPostInfo {
880 Self::ensure_pallet_enabled()?;
881 let account = ensure_signed(origin)?;
882
883 let mut ledger = Ledger::<T>::get(&account);
884
885 let is_new_account = ledger.is_empty();
889 if is_new_account {
890 ensure!(
891 T::AccountCheck::allowed_to_stake(&account),
892 Error::<T>::AccountNotAvailableForDappStaking
893 );
894 }
895
896 let available_balance =
898 T::Currency::total_balance(&account).saturating_sub(ledger.total_locked_amount());
899 let amount_to_lock = available_balance.min(amount);
900 ensure!(!amount_to_lock.is_zero(), Error::<T>::ZeroAmount);
901
902 ledger.add_lock_amount(amount_to_lock);
903
904 ensure!(
905 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
906 Error::<T>::LockedAmountBelowThreshold
907 );
908
909 Self::update_ledger(&account, ledger)?;
910 CurrentEraInfo::<T>::mutate(|era_info| {
911 era_info.add_locked(amount_to_lock);
912 });
913
914 Self::deposit_event(Event::<T>::Locked {
915 account,
916 amount: amount_to_lock,
917 });
918
919 Ok(Some(if is_new_account {
920 T::WeightInfo::lock_new_account()
921 } else {
922 T::WeightInfo::lock_existing_account()
923 })
924 .into())
925 }
926
927 #[pallet::call_index(8)]
933 #[pallet::weight(T::WeightInfo::unlock())]
934 pub fn unlock(origin: OriginFor<T>, #[pallet::compact] amount: Balance) -> DispatchResult {
935 Self::ensure_pallet_enabled()?;
936 let account = ensure_signed(origin)?;
937
938 let state = ActiveProtocolState::<T>::get();
939 let mut ledger = Ledger::<T>::get(&account);
940
941 let available_for_unlocking = ledger.unlockable_amount(state.period_info.number);
942 let amount_to_unlock = available_for_unlocking.min(amount);
943
944 let remaining_amount = ledger
946 .active_locked_amount()
947 .saturating_sub(amount_to_unlock);
948 let amount_to_unlock = if remaining_amount < T::MinimumLockedAmount::get() {
949 ensure!(
950 ledger.staked_amount(state.period_info.number).is_zero(),
951 Error::<T>::RemainingStakePreventsFullUnlock
952 );
953 ledger.active_locked_amount()
954 } else {
955 amount_to_unlock
956 };
957
958 ensure!(!amount_to_unlock.is_zero(), Error::<T>::ZeroAmount);
960
961 ledger.subtract_lock_amount(amount_to_unlock);
963
964 let current_block = frame_system::Pallet::<T>::block_number();
965 let unlock_block = current_block.saturating_add(Self::unlocking_period().into());
966 ledger
967 .add_unlocking_chunk(amount_to_unlock, unlock_block.saturated_into())
968 .map_err(|_| Error::<T>::TooManyUnlockingChunks)?;
969
970 Self::update_ledger(&account, ledger)?;
972 CurrentEraInfo::<T>::mutate(|era_info| {
973 era_info.unlocking_started(amount_to_unlock);
974 });
975
976 Self::deposit_event(Event::<T>::Unlocking {
977 account,
978 amount: amount_to_unlock,
979 });
980
981 Ok(())
982 }
983
984 #[pallet::call_index(9)]
986 #[pallet::weight(T::WeightInfo::claim_unlocked(T::MaxNumberOfStakedContracts::get()))]
987 pub fn claim_unlocked(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
988 Self::ensure_pallet_enabled()?;
989 let account = ensure_signed(origin)?;
990
991 Self::internal_claim_unlocked(account)
992 }
993
994 #[pallet::call_index(10)]
995 #[pallet::weight(T::WeightInfo::relock_unlocking())]
996 pub fn relock_unlocking(origin: OriginFor<T>) -> DispatchResult {
997 Self::ensure_pallet_enabled()?;
998 let account = ensure_signed(origin)?;
999
1000 let mut ledger = Ledger::<T>::get(&account);
1001
1002 ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockingChunks);
1003
1004 let amount = ledger.consume_unlocking_chunks();
1005
1006 ledger.add_lock_amount(amount);
1007 ensure!(
1008 ledger.active_locked_amount() >= T::MinimumLockedAmount::get(),
1009 Error::<T>::LockedAmountBelowThreshold
1010 );
1011
1012 Self::update_ledger(&account, ledger)?;
1013 CurrentEraInfo::<T>::mutate(|era_info| {
1014 era_info.add_locked(amount);
1015 era_info.unlocking_removed(amount);
1016 });
1017
1018 Self::deposit_event(Event::<T>::Relock { account, amount });
1019
1020 Ok(())
1021 }
1022
1023 #[pallet::call_index(11)]
1032 #[pallet::weight(T::WeightInfo::stake())]
1033 pub fn stake(
1034 origin: OriginFor<T>,
1035 smart_contract: T::SmartContract,
1036 #[pallet::compact] amount: Balance,
1037 ) -> DispatchResult {
1038 Self::ensure_pallet_enabled()?;
1039 let account = ensure_signed(origin)?;
1040
1041 let protocol_state = ActiveProtocolState::<T>::get();
1047 let (stake_amount, bonus_status) = match protocol_state.subperiod() {
1048 Subperiod::Voting => (
1049 StakeAmount {
1050 voting: amount,
1051 build_and_earn: 0,
1052 era: protocol_state.era,
1053 period: protocol_state.period_number(),
1054 },
1055 *BonusStatusWrapperFor::<T>::default(),
1056 ),
1057 Subperiod::BuildAndEarn => (
1058 StakeAmount {
1059 voting: 0,
1060 build_and_earn: amount,
1061 era: protocol_state.era,
1062 period: protocol_state.period_number(),
1063 },
1064 0,
1065 ),
1066 };
1067
1068 Self::inner_stake(&account, &smart_contract, stake_amount, bonus_status)?;
1070
1071 Self::deposit_event(Event::<T>::Stake {
1072 account,
1073 smart_contract,
1074 amount,
1075 });
1076
1077 Ok(())
1078 }
1079
1080 #[pallet::call_index(12)]
1090 #[pallet::weight(T::WeightInfo::unstake())]
1091 pub fn unstake(
1092 origin: OriginFor<T>,
1093 smart_contract: T::SmartContract,
1094 #[pallet::compact] amount: Balance,
1095 ) -> DispatchResult {
1096 Self::ensure_pallet_enabled()?;
1097 let account = ensure_signed(origin)?;
1098
1099 let (unstake_amount, _) = Self::inner_unstake(&account, &smart_contract, amount)?;
1100
1101 Self::deposit_event(Event::<T>::Unstake {
1102 account,
1103 smart_contract,
1104 amount: unstake_amount.total(),
1105 });
1106
1107 Ok(())
1108 }
1109
1110 #[pallet::call_index(13)]
1113 #[pallet::weight({
1114 let max_span_length = T::EraRewardSpanLength::get();
1115 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1116 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1117 })]
1118 pub fn claim_staker_rewards(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1119 Self::ensure_pallet_enabled()?;
1120 let account = ensure_signed(origin)?;
1121
1122 Self::internal_claim_staker_rewards_for(account)
1123 }
1124
1125 #[pallet::call_index(14)]
1127 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1128 pub fn claim_bonus_reward(
1129 origin: OriginFor<T>,
1130 smart_contract: T::SmartContract,
1131 ) -> DispatchResult {
1132 Self::ensure_pallet_enabled()?;
1133 let account = ensure_signed(origin)?;
1134
1135 Self::internal_claim_bonus_reward_for(account, smart_contract)
1136 }
1137
1138 #[pallet::call_index(15)]
1140 #[pallet::weight(T::WeightInfo::claim_dapp_reward())]
1141 pub fn claim_dapp_reward(
1142 origin: OriginFor<T>,
1143 smart_contract: T::SmartContract,
1144 #[pallet::compact] era: EraNumber,
1145 ) -> DispatchResult {
1146 Self::ensure_pallet_enabled()?;
1147
1148 let _ = ensure_signed(origin)?;
1150
1151 let dapp_info =
1152 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1153
1154 let protocol_state = ActiveProtocolState::<T>::get();
1156 ensure!(era < protocol_state.era, Error::<T>::InvalidClaimEra);
1157
1158 let mut dapp_tiers = DAppTiers::<T>::get(&era).ok_or(Error::<T>::NoDAppTierInfo)?;
1160 ensure!(
1161 dapp_tiers.period >= Self::oldest_claimable_period(protocol_state.period_number()),
1162 Error::<T>::RewardExpired
1163 );
1164
1165 let (amount, ranked_tier) =
1166 dapp_tiers
1167 .try_claim(dapp_info.id)
1168 .map_err(|error| match error {
1169 DAppTierError::NoDAppInTiers => Error::<T>::NoClaimableRewards,
1170 _ => Error::<T>::InternalClaimDAppError,
1171 })?;
1172
1173 let (tier_id, rank) = ranked_tier.deconstruct();
1174
1175 let beneficiary = dapp_info.reward_beneficiary();
1177 T::StakingRewardHandler::payout_reward(&beneficiary, amount)
1178 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
1179
1180 DAppTiers::<T>::insert(&era, dapp_tiers);
1182
1183 Self::deposit_event(Event::<T>::DAppReward {
1184 beneficiary: beneficiary.clone(),
1185 smart_contract,
1186 tier_id,
1187 rank,
1188 era,
1189 amount,
1190 });
1191
1192 Ok(())
1193 }
1194
1195 #[pallet::call_index(16)]
1198 #[pallet::weight(T::WeightInfo::unstake_from_unregistered())]
1199 pub fn unstake_from_unregistered(
1200 origin: OriginFor<T>,
1201 smart_contract: T::SmartContract,
1202 ) -> DispatchResult {
1203 Self::ensure_pallet_enabled()?;
1204 let account = ensure_signed(origin)?;
1205
1206 let (unstake_amount, _) =
1207 Self::inner_unstake_from_unregistered(&account, &smart_contract)?;
1208
1209 Self::deposit_event(Event::<T>::UnstakeFromUnregistered {
1210 account,
1211 smart_contract,
1212 amount: unstake_amount.total(),
1213 });
1214
1215 Ok(())
1216 }
1217
1218 #[pallet::call_index(17)]
1224 #[pallet::weight(T::WeightInfo::cleanup_expired_entries(
1225 T::MaxNumberOfStakedContracts::get()
1226 ))]
1227 pub fn cleanup_expired_entries(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1228 Self::ensure_pallet_enabled()?;
1229 let account = ensure_signed(origin)?;
1230
1231 let protocol_state = ActiveProtocolState::<T>::get();
1232 let current_period = protocol_state.period_number();
1233 let threshold_period = Self::oldest_claimable_period(current_period);
1234
1235 let mut remaining: u32 = 0;
1236 let mut to_be_deleted: Vec<T::SmartContract> = Vec::new();
1237
1238 for (smart_contract, stake_info) in StakerInfo::<T>::iter_prefix(&account) {
1243 let stake_period = stake_info.period_number();
1244
1245 let should_keep = stake_period == current_period
1247 || (stake_period >= threshold_period
1248 && stake_period < current_period
1249 && stake_info.is_bonus_eligible());
1250
1251 if should_keep {
1252 remaining = remaining.saturating_add(1);
1253 } else {
1254 to_be_deleted.push(smart_contract);
1255 }
1256 }
1257 let entries_to_delete = to_be_deleted.len();
1258
1259 ensure!(!entries_to_delete.is_zero(), Error::<T>::NoExpiredEntries);
1260
1261 for smart_contract in to_be_deleted {
1263 StakerInfo::<T>::remove(&account, &smart_contract);
1264 }
1265
1266 let mut ledger = Ledger::<T>::get(&account);
1268 ledger.contract_stake_count = remaining;
1269 ledger.maybe_cleanup_expired(threshold_period); Self::update_ledger(&account, ledger)?;
1271
1272 Self::deposit_event(Event::<T>::ExpiredEntriesRemoved {
1273 account,
1274 count: entries_to_delete.unique_saturated_into(),
1275 });
1276
1277 Ok(Some(T::WeightInfo::cleanup_expired_entries(
1278 entries_to_delete.unique_saturated_into(),
1279 ))
1280 .into())
1281 }
1282
1283 #[pallet::call_index(18)]
1291 #[pallet::weight(T::WeightInfo::force())]
1292 pub fn force(origin: OriginFor<T>, forcing_type: ForcingType) -> DispatchResult {
1293 Self::ensure_pallet_enabled()?;
1294 ensure_root(origin)?;
1295
1296 ensure!(!Safeguard::<T>::get(), Error::<T>::ForceNotAllowed);
1297
1298 ActiveProtocolState::<T>::mutate(|state| {
1300 let current_block = frame_system::Pallet::<T>::block_number();
1301 state.next_era_start = current_block.saturating_add(One::one()).saturated_into();
1302
1303 match forcing_type {
1304 ForcingType::Era => (),
1305 ForcingType::Subperiod => {
1306 state.period_info.next_subperiod_start_era = state.era.saturating_add(1);
1307 }
1308 }
1309
1310 Self::notify_block_before_new_era(&state);
1315 });
1316
1317 Self::deposit_event(Event::<T>::Force { forcing_type });
1318
1319 Ok(())
1320 }
1321
1322 #[pallet::call_index(19)]
1325 #[pallet::weight({
1326 let max_span_length = T::EraRewardSpanLength::get();
1327 T::WeightInfo::claim_staker_rewards_ongoing_period(max_span_length)
1328 .max(T::WeightInfo::claim_staker_rewards_past_period(max_span_length))
1329 })]
1330 pub fn claim_staker_rewards_for(
1331 origin: OriginFor<T>,
1332 account: T::AccountId,
1333 ) -> DispatchResultWithPostInfo {
1334 Self::ensure_pallet_enabled()?;
1335 ensure_signed(origin)?;
1336
1337 Self::internal_claim_staker_rewards_for(account)
1338 }
1339
1340 #[pallet::call_index(20)]
1342 #[pallet::weight(T::WeightInfo::claim_bonus_reward())]
1343 pub fn claim_bonus_reward_for(
1344 origin: OriginFor<T>,
1345 account: T::AccountId,
1346 smart_contract: T::SmartContract,
1347 ) -> DispatchResult {
1348 Self::ensure_pallet_enabled()?;
1349 ensure_signed(origin)?;
1350
1351 Self::internal_claim_bonus_reward_for(account, smart_contract)
1352 }
1353
1354 #[pallet::call_index(21)]
1357 #[pallet::weight(T::WeightInfo::move_stake_unregistered_source().max(T::WeightInfo::move_stake_from_registered_source()))]
1358 pub fn move_stake(
1359 origin: OriginFor<T>,
1360 source_contract: T::SmartContract,
1361 destination_contract: T::SmartContract,
1362 #[pallet::compact] amount: Balance,
1363 ) -> DispatchResultWithPostInfo {
1364 Self::ensure_pallet_enabled()?;
1365 let account = ensure_signed(origin)?;
1366
1367 ensure!(
1368 !source_contract.eq(&destination_contract),
1369 Error::<T>::SameContracts
1370 );
1371
1372 ensure!(
1373 IntegratedDApps::<T>::contains_key(&destination_contract),
1374 Error::<T>::ContractNotFound
1375 );
1376
1377 let maybe_source_dapp_info = IntegratedDApps::<T>::get(&source_contract);
1378 let is_source_unregistered = maybe_source_dapp_info.is_none();
1379
1380 let (mut move_amount, bonus_status) = if is_source_unregistered {
1381 Self::inner_unstake_from_unregistered(&account, &source_contract)?
1382 } else {
1383 Self::inner_unstake(&account, &source_contract, amount)?
1384 };
1385
1386 if bonus_status == 0 && move_amount.voting > 0 {
1388 move_amount.convert_bonus_into_regular_stake();
1389 }
1390
1391 Self::inner_stake(&account, &destination_contract, move_amount, bonus_status)?;
1392
1393 Self::deposit_event(Event::<T>::StakeMoved {
1394 account,
1395 source_contract,
1396 destination_contract,
1397 amount: move_amount.total(),
1398 });
1399
1400 Ok(Some(if is_source_unregistered {
1401 T::WeightInfo::move_stake_unregistered_source()
1402 } else {
1403 T::WeightInfo::move_stake_from_registered_source()
1404 })
1405 .into())
1406 }
1407
1408 #[pallet::call_index(22)]
1414 #[pallet::weight(T::WeightInfo::set_static_tier_params())]
1415 pub fn set_static_tier_params(
1416 origin: OriginFor<T>,
1417 params: TierParameters<T::NumberOfTiers>,
1418 ) -> DispatchResult {
1419 Self::ensure_pallet_enabled()?;
1420 ensure_root(origin)?;
1421 ensure!(params.is_valid(), Error::<T>::InvalidTierParams);
1422
1423 StaticTierParams::<T>::set(params.clone());
1424
1425 Self::deposit_event(Event::<T>::NewTierParameters { params });
1426
1427 Ok(())
1428 }
1429 }
1430
1431 impl<T: Config> Pallet<T> {
1432 pub fn inner_unstake(
1439 account: &T::AccountId,
1440 smart_contract: &T::SmartContract,
1441 amount: Balance,
1442 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1443 ensure!(amount > 0, Error::<T>::ZeroAmount);
1444 let dapp_info =
1445 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1446
1447 let protocol_state = ActiveProtocolState::<T>::get();
1448 let current_era = protocol_state.era;
1449
1450 let mut ledger = Ledger::<T>::get(&account);
1451
1452 let (new_staking_info, amount, stake_amount_iter, updated_bonus_status) =
1455 match StakerInfo::<T>::get(&account, &smart_contract) {
1456 Some(mut staking_info) => {
1457 ensure!(
1458 staking_info.period_number() == protocol_state.period_number(),
1459 Error::<T>::UnstakeFromPastPeriod
1460 );
1461 ensure!(
1462 staking_info.total_staked_amount() >= amount,
1463 Error::<T>::UnstakeAmountTooLarge
1464 );
1465
1466 let amount = if staking_info.total_staked_amount().saturating_sub(amount)
1469 < T::MinimumStakeAmount::get()
1470 {
1471 staking_info.total_staked_amount()
1472 } else {
1473 amount
1474 };
1475
1476 let (stake_amount_iter, updated_bonus_status) =
1477 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1478
1479 (
1480 staking_info,
1481 amount,
1482 stake_amount_iter,
1483 updated_bonus_status,
1484 )
1485 }
1486 None => {
1487 return Err(Error::<T>::NoStakingInfo.into());
1488 }
1489 };
1490
1491 ledger
1494 .unstake_amount(amount, current_era, protocol_state.period_info)
1495 .map_err(|err| match err {
1496 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1497 Error::<T>::UnclaimedRewards
1498 }
1499 AccountLedgerError::UnstakeAmountLargerThanStake => {
1501 Error::<T>::UnstakeAmountTooLarge
1502 }
1503 _ => Error::<T>::InternalUnstakeError,
1504 })?;
1505
1506 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1509 contract_stake_info.unstake(
1510 &stake_amount_iter,
1511 protocol_state.period_info,
1512 current_era,
1513 );
1514
1515 CurrentEraInfo::<T>::mutate(|era_info| {
1518 era_info.unstake_amount(stake_amount_iter.clone());
1519 });
1520
1521 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1524
1525 if new_staking_info.is_empty() {
1526 ledger.contract_stake_count.saturating_dec();
1527 StakerInfo::<T>::remove(&account, &smart_contract);
1528 } else {
1529 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1530 }
1531
1532 Self::update_ledger(&account, ledger)?;
1533
1534 let mut unstake_amount = stake_amount_iter
1536 .iter()
1537 .max_by(|a, b| a.total().cmp(&b.total()))
1538 .ok_or(Error::<T>::InternalUnstakeError)?
1540 .clone();
1541
1542 unstake_amount.era = current_era;
1544
1545 Ok((unstake_amount, updated_bonus_status))
1546 }
1547
1548 pub fn inner_unstake_from_unregistered(
1554 account: &T::AccountId,
1555 smart_contract: &T::SmartContract,
1556 ) -> Result<(StakeAmount, BonusStatus), DispatchError> {
1557 ensure!(
1558 !IntegratedDApps::<T>::contains_key(&smart_contract),
1559 Error::<T>::ContractStillActive
1560 );
1561
1562 let protocol_state = ActiveProtocolState::<T>::get();
1563 let current_era = protocol_state.era;
1564
1565 let (amount, unstake_amount_iter, preserved_bonus_status) =
1567 match StakerInfo::<T>::get(&account, &smart_contract) {
1568 Some(mut staking_info) => {
1569 ensure!(
1570 staking_info.period_number() == protocol_state.period_number(),
1571 Error::<T>::UnstakeFromPastPeriod
1572 );
1573
1574 let preserved_bonus_status = staking_info.bonus_status;
1575 let amount = staking_info.staked.total();
1576
1577 let (unstake_amount_iter, _) =
1578 staking_info.unstake(amount, current_era, protocol_state.subperiod());
1579
1580 (amount, unstake_amount_iter, preserved_bonus_status)
1581 }
1582 None => {
1583 return Err(Error::<T>::NoStakingInfo.into());
1584 }
1585 };
1586
1587 let mut ledger = Ledger::<T>::get(&account);
1589 ledger
1590 .unstake_amount(amount, current_era, protocol_state.period_info)
1591 .map_err(|err| match err {
1592 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1594 Error::<T>::UnclaimedRewards
1595 }
1596 _ => Error::<T>::InternalUnstakeError,
1597 })?;
1598 ledger.contract_stake_count.saturating_dec();
1599
1600 CurrentEraInfo::<T>::mutate(|era_info| {
1604 era_info.unstake_amount(unstake_amount_iter.clone());
1605 });
1606
1607 Self::update_ledger(&account, ledger)?;
1609 StakerInfo::<T>::remove(&account, &smart_contract);
1610
1611 let mut unstake_amount = unstake_amount_iter
1613 .iter()
1614 .max_by(|a, b| a.total().cmp(&b.total()))
1615 .ok_or(Error::<T>::InternalUnstakeError)?
1617 .clone();
1618
1619 unstake_amount.era = current_era;
1621
1622 Ok((unstake_amount, preserved_bonus_status))
1623 }
1624
1625 pub fn inner_stake(
1630 account: &T::AccountId,
1631 smart_contract: &T::SmartContract,
1632 amount: StakeAmount,
1633 bonus_status: BonusStatus,
1634 ) -> Result<(), DispatchError> {
1635 ensure!(amount.total() > 0, Error::<T>::ZeroAmount);
1636
1637 let dapp_info =
1638 IntegratedDApps::<T>::get(&smart_contract).ok_or(Error::<T>::ContractNotFound)?;
1639
1640 let protocol_state = ActiveProtocolState::<T>::get();
1641 let current_era = protocol_state.era;
1642 let period_number = protocol_state.period_info.number;
1643 ensure!(
1644 !protocol_state
1645 .period_info
1646 .is_next_period(current_era.saturating_add(1)),
1647 Error::<T>::PeriodEndsInNextEra
1648 );
1649
1650 let mut ledger = Ledger::<T>::get(&account);
1651
1652 let threshold_period = Self::oldest_claimable_period(protocol_state.period_number());
1654 let _ignore = ledger.maybe_cleanup_expired(threshold_period);
1655
1656 ledger
1659 .add_stake_amount(amount, current_era, protocol_state.period_info)
1660 .map_err(|err| match err {
1661 AccountLedgerError::InvalidPeriod | AccountLedgerError::InvalidEra => {
1662 Error::<T>::UnclaimedRewards
1663 }
1664 AccountLedgerError::UnavailableStakeFunds => Error::<T>::UnavailableStakeFunds,
1665 _ => Error::<T>::InternalStakeError,
1667 })?;
1668
1669 let (mut new_staking_info, is_new_entry, replacing_old_entry) =
1681 match StakerInfo::<T>::get(&account, &smart_contract) {
1682 Some(staking_info)
1684 if staking_info.period_number() == protocol_state.period_number() =>
1685 {
1686 (staking_info, false, false)
1687 }
1688 Some(staking_info)
1690 if staking_info.period_number() >= threshold_period
1691 && staking_info.is_bonus_eligible() =>
1692 {
1693 return Err(Error::<T>::UnclaimedRewards.into());
1694 }
1695 Some(_old_entry) => {
1697 StakerInfo::<T>::remove(&account, &smart_contract);
1699 (
1700 SingularStakingInfo::new(protocol_state.period_number(), bonus_status),
1701 true, true, )
1704 }
1705 None => (
1707 SingularStakingInfo::new(
1708 protocol_state.period_number(),
1709 bonus_status,
1712 ),
1713 true,
1714 false,
1715 ),
1716 };
1717
1718 new_staking_info.stake(amount, current_era, bonus_status);
1719 ensure!(
1720 new_staking_info.total_staked_amount() >= T::MinimumStakeAmount::get(),
1721 Error::<T>::InsufficientStakeAmount
1722 );
1723
1724 if is_new_entry && !replacing_old_entry {
1726 ledger.contract_stake_count.saturating_inc();
1727 ensure!(
1728 ledger.contract_stake_count <= T::MaxNumberOfStakedContracts::get(),
1729 Error::<T>::TooManyStakedContracts
1730 );
1731 }
1732
1733 let mut contract_stake_info = ContractStake::<T>::get(&dapp_info.id);
1736 contract_stake_info.stake(amount, current_era, period_number);
1737
1738 CurrentEraInfo::<T>::mutate(|era_info| {
1741 era_info.add_stake_amount(amount);
1742 });
1743
1744 Self::update_ledger(&account, ledger)?;
1747 StakerInfo::<T>::insert(&account, &smart_contract, new_staking_info);
1748 ContractStake::<T>::insert(&dapp_info.id, contract_stake_info);
1749
1750 Ok(())
1751 }
1752
1753 pub fn is_staker(account: &T::AccountId) -> bool {
1755 Ledger::<T>::contains_key(account)
1756 }
1757
1758 pub(crate) fn ensure_pallet_enabled() -> Result<(), Error<T>> {
1760 if ActiveProtocolState::<T>::get().maintenance {
1761 Err(Error::<T>::Disabled)
1762 } else {
1763 Ok(())
1764 }
1765 }
1766
1767 pub(crate) fn update_ledger(
1774 account: &T::AccountId,
1775 ledger: AccountLedgerFor<T>,
1776 ) -> Result<(), DispatchError> {
1777 if ledger.is_empty() {
1778 Ledger::<T>::remove(&account);
1779 T::Currency::thaw(&FreezeReason::DAppStaking.into(), account)?;
1780 } else {
1781 T::Currency::set_freeze(
1782 &FreezeReason::DAppStaking.into(),
1783 account,
1784 ledger.total_locked_amount(),
1785 )?;
1786 Ledger::<T>::insert(account, ledger);
1787 }
1788
1789 Ok(())
1790 }
1791
1792 pub(crate) fn blocks_per_voting_period() -> BlockNumber {
1794 T::CycleConfiguration::blocks_per_era()
1795 .saturating_mul(T::CycleConfiguration::eras_per_voting_subperiod().into())
1796 }
1797
1798 pub fn era_reward_span_index(era: EraNumber) -> EraNumber {
1800 era.saturating_sub(era % T::EraRewardSpanLength::get())
1801 }
1802
1803 pub(crate) fn oldest_claimable_period(current_period: PeriodNumber) -> PeriodNumber {
1806 current_period.saturating_sub(T::RewardRetentionInPeriods::get())
1807 }
1808
1809 pub fn unlocking_period() -> BlockNumber {
1811 T::CycleConfiguration::blocks_per_era().saturating_mul(T::UnlockingPeriod::get().into())
1812 }
1813
1814 pub fn get_dapp_tier_assignment() -> BTreeMap<DAppId, RankedTier> {
1816 let protocol_state = ActiveProtocolState::<T>::get();
1817
1818 let (dapp_tiers, _count) = Self::get_dapp_tier_assignment_and_rewards(
1819 protocol_state.era,
1820 protocol_state.period_number(),
1821 Balance::zero(),
1822 );
1823
1824 dapp_tiers.dapps.into_inner()
1825 }
1826
1827 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 let tier_params = StaticTierParams::<T>::get();
1886
1887 let mut dapp_tiers = BTreeMap::new();
1899 let mut tier_rewards = Vec::with_capacity(tier_config.slots_per_tier.len());
1900 let mut rank_rewards = Vec::with_capacity(tier_config.slots_per_tier.len());
1901
1902 let mut upper_bound = Balance::zero();
1903
1904 for (tier_id, (tier_capacity, lower_bound)) in tier_config
1905 .slots_per_tier
1906 .iter()
1907 .zip(tier_config.tier_thresholds.iter())
1908 .enumerate()
1909 {
1910 let mut tier_slots = BTreeMap::new();
1911
1912 for (dapp_id, staked_amount) in dapp_stakes
1916 .iter()
1917 .skip(dapp_tiers.len())
1918 .take_while(|(_, amount)| amount.ge(lower_bound))
1919 .take(*tier_capacity as usize)
1920 {
1921 let rank = if T::RankingEnabled::get() {
1922 RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount)
1923 } else {
1924 0
1925 };
1926 tier_slots.insert(*dapp_id, RankedTier::new_saturated(tier_id as u8, rank));
1927 }
1928
1929 let filled_slots = tier_slots.len() as u32;
1931 let ranks_sum = tier_slots
1933 .iter()
1934 .fold(0u32, |accum, (_, x)| accum.saturating_add(x.rank().into()));
1935
1936 let multiplier_bips = tier_params
1937 .tier_rank_multipliers
1938 .get(tier_id)
1939 .copied()
1940 .unwrap_or(10_000);
1941
1942 let tier_allocation = tier_config
1943 .reward_portion
1944 .get(tier_id)
1945 .copied()
1946 .unwrap_or(Permill::zero())
1947 * dapp_reward_pool;
1948
1949 let (tier_reward, rank_reward) = Self::compute_tier_rewards(
1950 tier_allocation,
1951 *tier_capacity,
1952 filled_slots,
1953 ranks_sum,
1954 multiplier_bips,
1955 );
1956
1957 tier_rewards.push(tier_reward);
1958 rank_rewards.push(rank_reward);
1959 dapp_tiers.append(&mut tier_slots);
1960 upper_bound = *lower_bound; }
1962
1963 (
1967 DAppTierRewards::<T::MaxNumberOfContracts, T::NumberOfTiers>::new(
1968 dapp_tiers,
1969 tier_rewards,
1970 period,
1971 rank_rewards,
1972 )
1973 .unwrap_or_default(),
1974 counter,
1975 )
1976 }
1977
1978 pub(crate) fn era_and_period_handler(
1980 now: BlockNumber,
1981 tier_assignment: TierAssignment,
1982 ) -> Weight {
1983 let mut protocol_state = ActiveProtocolState::<T>::get();
1984
1985 let mut consumed_weight = T::DbWeight::get().reads(1);
1987
1988 if protocol_state.maintenance {
1992 return consumed_weight;
1993 }
1994
1995 if protocol_state.next_era_start == now.saturating_add(1) {
1997 consumed_weight
1998 .saturating_accrue(Self::notify_block_before_new_era(&protocol_state));
1999 }
2000
2001 if !protocol_state.is_new_era(now) {
2003 return consumed_weight;
2004 }
2005
2006 let mut era_info = CurrentEraInfo::<T>::get();
2008
2009 let current_era = protocol_state.era;
2010 let next_era = current_era.saturating_add(1);
2011 let (maybe_period_event, era_reward) = match protocol_state.subperiod() {
2012 Subperiod::Voting => {
2014 let era_reward = EraReward {
2016 staker_reward_pool: Balance::zero(),
2017 staked: era_info.total_staked_amount(),
2018 dapp_reward_pool: Balance::zero(),
2019 };
2020
2021 let next_subperiod_start_era = next_era
2022 .saturating_add(T::CycleConfiguration::eras_per_build_and_earn_subperiod());
2023 let build_and_earn_start_block =
2024 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2025 protocol_state.advance_to_next_subperiod(
2026 next_subperiod_start_era,
2027 build_and_earn_start_block,
2028 );
2029
2030 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2031
2032 consumed_weight
2033 .saturating_accrue(T::WeightInfo::on_initialize_voting_to_build_and_earn());
2034
2035 (
2036 Some(Event::<T>::NewSubperiod {
2037 subperiod: protocol_state.subperiod(),
2038 number: protocol_state.period_number(),
2039 }),
2040 era_reward,
2041 )
2042 }
2043 Subperiod::BuildAndEarn => {
2044 let staked = era_info.total_staked_amount();
2045 let (staker_reward_pool, dapp_reward_pool) =
2046 T::StakingRewardHandler::staker_and_dapp_reward_pools(staked);
2047 let era_reward = EraReward {
2048 staker_reward_pool,
2049 staked,
2050 dapp_reward_pool,
2051 };
2052
2053 let (dapp_tier_rewards, counter) = match tier_assignment {
2058 TierAssignment::Real => Self::get_dapp_tier_assignment_and_rewards(
2059 current_era,
2060 protocol_state.period_number(),
2061 dapp_reward_pool,
2062 ),
2063 #[cfg(feature = "runtime-benchmarks")]
2064 TierAssignment::Dummy => (DAppTierRewardsFor::<T>::default(), 0),
2065 };
2066 DAppTiers::<T>::insert(¤t_era, dapp_tier_rewards);
2067
2068 consumed_weight
2069 .saturating_accrue(T::WeightInfo::dapp_tier_assignment(counter.into()));
2070
2071 if protocol_state.period_info.is_next_period(next_era) {
2073 let bonus_reward_pool = T::StakingRewardHandler::bonus_reward_pool();
2075 PeriodEnd::<T>::insert(
2076 &protocol_state.period_number(),
2077 PeriodEndInfo {
2078 bonus_reward_pool,
2079 total_vp_stake: era_info.staked_amount(Subperiod::Voting),
2080 final_era: current_era,
2081 },
2082 );
2083
2084 let next_subperiod_start_era = next_era.saturating_add(1);
2087 let voting_period_length = Self::blocks_per_voting_period();
2088 let next_era_start_block = now.saturating_add(voting_period_length);
2089
2090 protocol_state.advance_to_next_subperiod(
2091 next_subperiod_start_era,
2092 next_era_start_block,
2093 );
2094
2095 era_info.migrate_to_next_era(Some(protocol_state.subperiod()));
2096
2097 Self::update_cleanup_marker(protocol_state.period_number());
2100
2101 consumed_weight.saturating_accrue(
2102 T::WeightInfo::on_initialize_build_and_earn_to_voting(),
2103 );
2104
2105 (
2106 Some(Event::<T>::NewSubperiod {
2107 subperiod: protocol_state.subperiod(),
2108 number: protocol_state.period_number(),
2109 }),
2110 era_reward,
2111 )
2112 } else {
2113 let next_era_start_block =
2114 now.saturating_add(T::CycleConfiguration::blocks_per_era());
2115 protocol_state.next_era_start = next_era_start_block;
2116
2117 era_info.migrate_to_next_era(None);
2118
2119 consumed_weight.saturating_accrue(
2120 T::WeightInfo::on_initialize_build_and_earn_to_build_and_earn(),
2121 );
2122
2123 (None, era_reward)
2124 }
2125 }
2126 };
2127
2128 protocol_state.era = next_era;
2130 ActiveProtocolState::<T>::put(protocol_state);
2131
2132 CurrentEraInfo::<T>::put(era_info);
2133
2134 let era_span_index = Self::era_reward_span_index(current_era);
2135 let mut span = EraRewards::<T>::get(&era_span_index).unwrap_or(EraRewardSpan::new());
2136 if let Err(_) = span.push(current_era, era_reward) {
2137 log::error!(
2139 target: LOG_TARGET,
2140 "Failed to push era {} into the era reward span.",
2141 current_era
2142 );
2143 }
2144 EraRewards::<T>::insert(&era_span_index, span);
2145
2146 let tier_params = StaticTierParams::<T>::get();
2148 let total_issuance = T::Currency::total_issuance();
2149
2150 let new_tier_config =
2151 TierConfig::<T>::get().calculate_new(&tier_params, total_issuance);
2152
2153 if new_tier_config.is_valid() {
2155 TierConfig::<T>::put(new_tier_config);
2156 } else {
2157 log::warn!(
2158 target: LOG_TARGET,
2159 "New tier configuration is invalid for era {}, preserving old one.",
2160 next_era
2161 );
2162 }
2163
2164 Self::deposit_event(Event::<T>::NewEra { era: next_era });
2165 if let Some(period_event) = maybe_period_event {
2166 Self::deposit_event(period_event);
2167 }
2168
2169 consumed_weight
2170 }
2171
2172 fn notify_block_before_new_era(protocol_state: &ProtocolState) -> Weight {
2174 let next_era = protocol_state.era.saturating_add(1);
2175 T::Observers::block_before_new_era(next_era)
2176 }
2177
2178 fn update_cleanup_marker(new_period_number: PeriodNumber) {
2182 let latest_expired_period = match new_period_number
2184 .checked_sub(T::RewardRetentionInPeriods::get().saturating_add(1))
2185 {
2186 Some(period) if !period.is_zero() => period,
2187 _ => return,
2189 };
2190
2191 let oldest_valid_era = match PeriodEnd::<T>::take(latest_expired_period) {
2196 Some(period_end_info) => period_end_info.final_era.saturating_add(1),
2197 None => {
2198 log::error!(
2200 target: LOG_TARGET,
2201 "No `PeriodEnd` entry for the expired period: {}",
2202 latest_expired_period
2203 );
2204 return;
2205 }
2206 };
2207
2208 HistoryCleanupMarker::<T>::mutate(|marker| {
2210 marker.oldest_valid_era = oldest_valid_era;
2211 });
2212 }
2213
2214 fn expired_entry_cleanup(remaining_weight: &Weight) -> Weight {
2218 if remaining_weight.any_lt(T::WeightInfo::on_idle_cleanup()) {
2220 return Weight::zero();
2221 }
2222
2223 let mut cleanup_marker = HistoryCleanupMarker::<T>::get();
2225 if !cleanup_marker.has_pending_cleanups() {
2226 return T::DbWeight::get().reads(1);
2227 }
2228
2229 if cleanup_marker.era_reward_index < cleanup_marker.oldest_valid_era {
2231 if let Some(era_reward) = EraRewards::<T>::get(cleanup_marker.era_reward_index) {
2232 if era_reward.last_era() < cleanup_marker.oldest_valid_era {
2234 EraRewards::<T>::remove(cleanup_marker.era_reward_index);
2235 cleanup_marker
2236 .era_reward_index
2237 .saturating_accrue(T::EraRewardSpanLength::get());
2238 }
2239 } else {
2240 log::warn!(
2242 target: LOG_TARGET,
2243 "Era rewards span for era {} is missing, but cleanup marker is set.",
2244 cleanup_marker.era_reward_index
2245 );
2246 cleanup_marker
2247 .era_reward_index
2248 .saturating_accrue(T::EraRewardSpanLength::get());
2249 }
2250 }
2251
2252 if cleanup_marker.dapp_tiers_index < cleanup_marker.oldest_valid_era {
2254 DAppTiers::<T>::remove(cleanup_marker.dapp_tiers_index);
2255 cleanup_marker.dapp_tiers_index.saturating_inc();
2256 }
2257
2258 HistoryCleanupMarker::<T>::put(cleanup_marker);
2260
2261 T::WeightInfo::on_idle_cleanup()
2267 }
2268
2269 fn internal_claim_unlocked(account: T::AccountId) -> DispatchResultWithPostInfo {
2271 let mut ledger = Ledger::<T>::get(&account);
2272
2273 let current_block = frame_system::Pallet::<T>::block_number();
2274 let amount = ledger.claim_unlocked(current_block.saturated_into());
2275 ensure!(amount > Zero::zero(), Error::<T>::NoUnlockedChunksToClaim);
2276
2277 let removed_entries = if ledger.is_empty() {
2279 let _ = StakerInfo::<T>::clear_prefix(&account, ledger.contract_stake_count, None);
2280 ledger.contract_stake_count
2281 } else {
2282 0
2283 };
2284
2285 Self::update_ledger(&account, ledger)?;
2286 CurrentEraInfo::<T>::mutate(|era_info| {
2287 era_info.unlocking_removed(amount);
2288 });
2289
2290 Self::deposit_event(Event::<T>::ClaimedUnlocked { account, amount });
2291
2292 Ok(Some(T::WeightInfo::claim_unlocked(removed_entries)).into())
2293 }
2294
2295 fn internal_claim_staker_rewards_for(account: T::AccountId) -> DispatchResultWithPostInfo {
2297 let mut ledger = Ledger::<T>::get(&account);
2298 let staked_period = ledger
2299 .staked_period()
2300 .ok_or(Error::<T>::NoClaimableRewards)?;
2301
2302 let protocol_state = ActiveProtocolState::<T>::get();
2304 ensure!(
2305 staked_period >= Self::oldest_claimable_period(protocol_state.period_number()),
2306 Error::<T>::RewardExpired
2307 );
2308
2309 let earliest_staked_era = ledger
2311 .earliest_staked_era()
2312 .ok_or(Error::<T>::InternalClaimStakerError)?;
2313 let era_rewards =
2314 EraRewards::<T>::get(Self::era_reward_span_index(earliest_staked_era))
2315 .ok_or(Error::<T>::NoClaimableRewards)?;
2316
2317 let (last_period_era, period_end) = if staked_period == protocol_state.period_number() {
2320 (protocol_state.era.saturating_sub(1), None)
2321 } else {
2322 PeriodEnd::<T>::get(&staked_period)
2323 .map(|info| (info.final_era, Some(info.final_era)))
2324 .ok_or(Error::<T>::InternalClaimStakerError)?
2325 };
2326
2327 let last_claim_era = era_rewards.last_era().min(last_period_era);
2329
2330 let rewards_iter =
2332 ledger
2333 .claim_up_to_era(last_claim_era, period_end)
2334 .map_err(|err| match err {
2335 AccountLedgerError::NothingToClaim => Error::<T>::NoClaimableRewards,
2336 _ => Error::<T>::InternalClaimStakerError,
2337 })?;
2338
2339 let mut rewards: Vec<_> = Vec::new();
2341 let mut reward_sum = Balance::zero();
2342 for (era, amount) in rewards_iter {
2343 let era_reward = era_rewards
2344 .get(era)
2345 .ok_or(Error::<T>::InternalClaimStakerError)?;
2346
2347 if amount.is_zero() || era_reward.staked.is_zero() {
2349 continue;
2350 }
2351 let staker_reward = Perbill::from_rational(amount, era_reward.staked)
2352 * era_reward.staker_reward_pool;
2353
2354 rewards.push((era, staker_reward));
2355 reward_sum.saturating_accrue(staker_reward);
2356 }
2357 let rewards_len: u32 = rewards.len().unique_saturated_into();
2358
2359 T::StakingRewardHandler::payout_reward(&account, reward_sum)
2360 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2361
2362 Self::update_ledger(&account, ledger)?;
2363
2364 rewards.into_iter().for_each(|(era, reward)| {
2365 Self::deposit_event(Event::<T>::Reward {
2366 account: account.clone(),
2367 era,
2368 amount: reward,
2369 });
2370 });
2371
2372 Ok(Some(if period_end.is_some() {
2373 T::WeightInfo::claim_staker_rewards_past_period(rewards_len)
2374 } else {
2375 T::WeightInfo::claim_staker_rewards_ongoing_period(rewards_len)
2376 })
2377 .into())
2378 }
2379
2380 fn internal_claim_bonus_reward_for(
2382 account: T::AccountId,
2383 smart_contract: T::SmartContract,
2384 ) -> DispatchResult {
2385 let staker_info = StakerInfo::<T>::get(&account, &smart_contract)
2386 .ok_or(Error::<T>::NoClaimableRewards)?;
2387 let protocol_state = ActiveProtocolState::<T>::get();
2388
2389 let staked_period = staker_info.period_number();
2394 ensure!(
2395 staked_period < protocol_state.period_number(),
2396 Error::<T>::NoClaimableRewards
2397 );
2398 ensure!(
2399 staker_info.is_bonus_eligible(),
2400 Error::<T>::NotEligibleForBonusReward
2401 );
2402 ensure!(
2403 staker_info.period_number()
2404 >= Self::oldest_claimable_period(protocol_state.period_number()),
2405 Error::<T>::RewardExpired
2406 );
2407
2408 let period_end_info =
2409 PeriodEnd::<T>::get(&staked_period).ok_or(Error::<T>::InternalClaimBonusError)?;
2410 ensure!(
2412 !period_end_info.total_vp_stake.is_zero(),
2413 Error::<T>::InternalClaimBonusError
2414 );
2415
2416 let eligible_amount = staker_info.staked_amount(Subperiod::Voting);
2417 let bonus_reward =
2418 Perbill::from_rational(eligible_amount, period_end_info.total_vp_stake)
2419 * period_end_info.bonus_reward_pool;
2420
2421 T::StakingRewardHandler::payout_reward(&account, bonus_reward)
2422 .map_err(|_| Error::<T>::RewardPayoutFailed)?;
2423
2424 StakerInfo::<T>::remove(&account, &smart_contract);
2426 Ledger::<T>::mutate(&account, |ledger| {
2427 ledger.contract_stake_count.saturating_dec();
2428 });
2429
2430 Self::deposit_event(Event::<T>::BonusReward {
2431 account: account.clone(),
2432 smart_contract,
2433 period: staked_period,
2434 amount: bonus_reward,
2435 });
2436
2437 Ok(())
2438 }
2439
2440 pub(crate) fn compute_tier_rewards(
2445 tier_allocation: Balance,
2446 tier_capacity: u16,
2447 filled_slots: u32,
2448 ranks_sum: u32,
2449 rank10_multiplier_bips: u32,
2450 ) -> (Balance, Balance) {
2451 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 {
2458 return (Balance::zero(), Balance::zero());
2459 }
2460
2461 let step_weight_bips: u128 = (rank10_multiplier_bips as u128)
2466 .saturating_sub(BASE_WEIGHT_BIPS)
2467 .saturating_div(max_rank);
2468
2469 let observed_total_weight: u128 = (filled_slots as u128)
2476 .saturating_mul(BASE_WEIGHT_BIPS)
2477 .saturating_add((ranks_sum as u128).saturating_mul(step_weight_bips));
2478
2479 let avg_slot_weight_bips: u128 =
2485 BASE_WEIGHT_BIPS.saturating_add(avg_rank.saturating_mul(step_weight_bips));
2486 let expected_full_weight: u128 =
2487 (tier_capacity as u128).saturating_mul(avg_slot_weight_bips);
2488
2489 let normalization_weight: u128 = observed_total_weight.max(expected_full_weight);
2493 if normalization_weight == 0 {
2494 return (Balance::zero(), Balance::zero());
2495 }
2496
2497 let alloc: u128 = tier_allocation.into();
2498 let tier_base_reward0_u128 =
2499 alloc.saturating_mul(BASE_WEIGHT_BIPS) / normalization_weight;
2500 let reward_per_rank_step_u128 =
2501 alloc.saturating_mul(step_weight_bips) / normalization_weight;
2502
2503 (
2504 tier_base_reward0_u128.saturated_into::<Balance>(),
2505 reward_per_rank_step_u128.saturated_into::<Balance>(),
2506 )
2507 }
2508
2509 fn set_maintenance_mode(enabled: bool) {
2513 ActiveProtocolState::<T>::mutate(|state| state.maintenance = enabled);
2514 Self::deposit_event(Event::<T>::MaintenanceMode { enabled });
2515 }
2516
2517 #[cfg(any(feature = "try-runtime", test))]
2519 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
2520 Self::try_state_protocol()?;
2521 Self::try_state_next_dapp_id()?;
2522 Self::try_state_integrated_dapps()?;
2523 Self::try_state_tiers()?;
2524 Self::try_state_ledger()?;
2525 Self::try_state_contract_stake()?;
2526 Self::try_state_era_rewards()?;
2527 Self::try_state_era_info()?;
2528
2529 Ok(())
2530 }
2531
2532 #[cfg(any(feature = "try-runtime", test))]
2537 pub fn try_state_protocol() -> Result<(), sp_runtime::TryRuntimeError> {
2538 let protocol_state = ActiveProtocolState::<T>::get();
2539
2540 if PeriodEnd::<T>::iter().count() >= protocol_state.period_info.number as usize {
2542 return Err("Number of periods in `PeriodEnd` exceeds or is equal to actual `PeriodInfo` number.".into());
2543 }
2544
2545 if protocol_state.era == 0 {
2547 return Err("Invalid era number in ActiveProtocolState.".into());
2548 }
2549
2550 let current_block: BlockNumber =
2551 frame_system::Pallet::<T>::block_number().saturated_into();
2552 if current_block > protocol_state.next_era_start {
2553 return Err(
2554 "Next era start block number is in the past in ActiveProtocolState.".into(),
2555 );
2556 }
2557
2558 Ok(())
2559 }
2560
2561 #[cfg(any(feature = "try-runtime", test))]
2566 pub fn try_state_next_dapp_id() -> Result<(), sp_runtime::TryRuntimeError> {
2567 let next_dapp_id = NextDAppId::<T>::get();
2568
2569 if next_dapp_id < IntegratedDApps::<T>::count() as u16 {
2571 return Err("Number of integrated dapps is greater than NextDAppId.".into());
2572 }
2573
2574 if next_dapp_id < ContractStake::<T>::iter().count() as u16 {
2576 return Err("Number of contract stake infos is greater than NextDAppId.".into());
2577 }
2578
2579 Ok(())
2580 }
2581
2582 #[cfg(any(feature = "try-runtime", test))]
2586 pub fn try_state_integrated_dapps() -> Result<(), sp_runtime::TryRuntimeError> {
2587 let integrated_dapps_count = IntegratedDApps::<T>::count();
2588 let max_number_of_contracts = T::MaxNumberOfContracts::get();
2589
2590 if integrated_dapps_count > max_number_of_contracts {
2591 return Err("Number of integrated dapps exceeds the maximum allowed.".into());
2592 }
2593
2594 Ok(())
2595 }
2596
2597 #[cfg(any(feature = "try-runtime", test))]
2602 pub fn try_state_tiers() -> Result<(), sp_runtime::TryRuntimeError> {
2603 let nb_tiers = T::NumberOfTiers::get();
2604 let tier_params = StaticTierParams::<T>::get();
2605 let tier_config = TierConfig::<T>::get();
2606
2607 if nb_tiers != tier_params.slot_distribution.len() as u32 {
2609 return Err(
2610 "Number of tiers is incorrect in slot_distribution in StaticTierParams.".into(),
2611 );
2612 }
2613 if nb_tiers != tier_params.reward_portion.len() as u32 {
2614 return Err(
2615 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2616 );
2617 }
2618 if nb_tiers != tier_params.tier_thresholds.len() as u32 {
2619 return Err(
2620 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2621 );
2622 }
2623
2624 if nb_tiers != tier_config.slots_per_tier.len() as u32 {
2626 return Err(
2627 "Number of tiers is incorrect in slots_per_tier in StaticTierParams.".into(),
2628 );
2629 }
2630 if nb_tiers != tier_config.reward_portion.len() as u32 {
2631 return Err(
2632 "Number of tiers is incorrect in reward_portion in StaticTierParams.".into(),
2633 );
2634 }
2635 if nb_tiers != tier_config.tier_thresholds.len() as u32 {
2636 return Err(
2637 "Number of tiers is incorrect in tier_thresholds in StaticTierParams.".into(),
2638 );
2639 }
2640
2641 Ok(())
2642 }
2643
2644 #[cfg(any(feature = "try-runtime", test))]
2652 pub fn try_state_ledger() -> Result<(), sp_runtime::TryRuntimeError> {
2653 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2654 let current_era_info = CurrentEraInfo::<T>::get();
2655 let next_era_total_stake = current_era_info.total_staked_amount_next_era();
2656
2657 let mut ledger_total_stake = Balance::zero();
2659 let mut ledger_total_locked = Balance::zero();
2660 let mut ledger_total_unlocking = Balance::zero();
2661
2662 for (_, ledger) in Ledger::<T>::iter() {
2663 let account_stake = ledger.staked_amount(current_period_number);
2664
2665 ledger_total_stake += account_stake;
2666 ledger_total_locked += ledger.active_locked_amount();
2667 ledger_total_unlocking += ledger.unlocking_amount();
2668
2669 if ledger.unlocking.len() > T::MaxUnlockingChunks::get() as usize {
2671 return Err("An account exceeds the maximum unlocking chunks.".into());
2672 }
2673
2674 if account_stake > Balance::zero() && account_stake < T::MinimumStakeAmount::get() {
2676 return Err(
2677 "An account has a stake amount lower than the minimum allowed.".into(),
2678 );
2679 }
2680
2681 if ledger.active_locked_amount() > Balance::zero()
2683 && ledger.active_locked_amount() < T::MinimumLockedAmount::get()
2684 {
2685 return Err(
2686 "An account has a locked amount lower than the minimum allowed.".into(),
2687 );
2688 }
2689
2690 if ledger.contract_stake_count > T::MaxNumberOfStakedContracts::get() {
2692 return Err("An account exceeds the maximum number of staked contracts.".into());
2693 }
2694 }
2695
2696 if ledger_total_stake != next_era_total_stake {
2698 return Err(
2699 "Mismatch between Ledger total staked amounts and CurrentEraInfo total.".into(),
2700 );
2701 }
2702
2703 if ledger_total_locked != current_era_info.total_locked {
2704 return Err(
2705 "Mismatch between Ledger total locked amounts and CurrentEraInfo total.".into(),
2706 );
2707 }
2708
2709 if ledger_total_unlocking != current_era_info.unlocking {
2710 return Err(
2711 "Mismatch between Ledger total unlocked amounts and CurrentEraInfo total."
2712 .into(),
2713 );
2714 }
2715
2716 Ok(())
2717 }
2718
2719 #[cfg(any(feature = "try-runtime", test))]
2723 pub fn try_state_contract_stake() -> Result<(), sp_runtime::TryRuntimeError> {
2724 let current_period_number = ActiveProtocolState::<T>::get().period_number();
2725
2726 for (_, contract) in ContractStake::<T>::iter() {
2727 let contract_stake = contract.total_staked_amount(current_period_number);
2728
2729 if contract_stake > Balance::zero() && contract_stake < T::MinimumStakeAmount::get()
2731 {
2732 return Err(
2733 "A contract has a staked amount lower than the minimum allowed.".into(),
2734 );
2735 }
2736 }
2737
2738 Ok(())
2739 }
2740
2741 #[cfg(any(feature = "try-runtime", test))]
2746 pub fn try_state_era_rewards() -> Result<(), sp_runtime::TryRuntimeError> {
2747 let era_rewards = EraRewards::<T>::iter().collect::<Vec<_>>();
2748 let dapp_tiers = DAppTiers::<T>::iter().collect::<Vec<_>>();
2749
2750 for (era, _) in &dapp_tiers {
2752 let mut found = false;
2753 for (_, span) in &era_rewards {
2754 if *era >= span.first_era() && *era <= span.last_era() {
2755 found = true;
2756 break;
2757 }
2758 }
2759
2760 if !found {
2762 return Err("Era in DAppTiers is not found in any span in EraRewards.".into());
2763 }
2764 }
2765
2766 for (_, span) in &era_rewards {
2767 if span.len() > T::EraRewardSpanLength::get() as usize {
2769 return Err(
2770 "Span length for a era exceeds the maximum allowed span length.".into(),
2771 );
2772 }
2773 }
2774
2775 Ok(())
2776 }
2777
2778 #[cfg(any(feature = "try-runtime", test))]
2783 pub fn try_state_era_info() -> Result<(), sp_runtime::TryRuntimeError> {
2784 let protocol_state = ActiveProtocolState::<T>::get();
2785 let current_period = protocol_state.period_number();
2786 let era_info = CurrentEraInfo::<T>::get();
2787
2788 let current_voting = era_info.staked_amount(Subperiod::Voting);
2789 let next_voting = era_info.staked_amount_next_era(Subperiod::Voting);
2790
2791 let voting_total_staked: Balance = StakerInfo::<T>::iter()
2793 .filter(|(_, _, info)| info.period_number() == current_period)
2794 .map(|(_, _, info)| info.staked_amount(Subperiod::Voting))
2795 .sum();
2796
2797 ensure!(
2799 voting_total_staked == next_voting,
2800 "StakerInfo voting total != CurrentEraInfo.next voting stake"
2801 );
2802
2803 ensure!(
2805 current_voting <= next_voting,
2806 "Current voting stake > Next voting stake for same period"
2807 );
2808
2809 Ok(())
2810 }
2811 }
2812
2813 impl<T: Config> SafeModeNotify for Pallet<T> {
2817 fn entered() {
2818 Self::set_maintenance_mode(true);
2819 }
2820
2821 fn exited() {
2822 Self::set_maintenance_mode(false);
2823 }
2824 }
2825}