48
48
rustdoc:: invalid_codeblock_attributes,
49
49
missing_docs
50
50
) ]
51
+ use sp_std:: ops:: { Add , Mul } ;
51
52
52
53
use frame_support:: {
53
54
dispatch:: DispatchResult ,
@@ -60,7 +61,6 @@ use sp_runtime::{
60
61
traits:: { CheckedAdd , CheckedDiv , One , Saturating , Zero } ,
61
62
ArithmeticError , DispatchError , Perbill ,
62
63
} ;
63
- use sp_std:: ops:: Mul ;
64
64
65
65
pub use common_primitives:: {
66
66
capacity:: { Nontransferable , Replenishable , TargetValidator } ,
@@ -90,12 +90,12 @@ type BalanceOf<T> =
90
90
<<T as Config >:: Currency as Currency < <T as frame_system:: Config >:: AccountId > >:: Balance ;
91
91
92
92
const STAKING_ID : LockIdentifier = * b"netstkng" ;
93
-
94
93
#[ frame_support:: pallet]
95
94
pub mod pallet {
96
95
use super :: * ;
97
96
use codec:: EncodeLike ;
98
97
98
+ use common_primitives:: capacity:: StakingType ;
99
99
use frame_support:: { pallet_prelude:: * , Twox64Concat } ;
100
100
use frame_system:: pallet_prelude:: * ;
101
101
use sp_runtime:: traits:: { AtLeast32BitUnsigned , MaybeDisplay } ;
@@ -177,7 +177,7 @@ pub mod pallet {
177
177
178
178
/// The maximum number of eras over which one can claim rewards
179
179
#[ pallet:: constant]
180
- type StakingRewardsPastErasMax : Get < u32 > ;
180
+ type StakingRewardsPastErasMax : Get < Self :: RewardEra > ;
181
181
182
182
/// The StakingRewardsProvider used by this pallet in a given runtime
183
183
type RewardsProvider : StakingRewardsProvider < Self > ;
@@ -186,7 +186,7 @@ pub mod pallet {
186
186
/// thaw chunk to expire. If the staker has called change_staking_target MaxUnlockingChunks
187
187
/// times, then at least one of the chunks must have expired before the next call
188
188
/// will succeed.
189
- type ChangeStakingTargetThawEras : Get < u32 > ;
189
+ type ChangeStakingTargetThawEras : Get < Self :: RewardEra > ;
190
190
}
191
191
192
192
/// Storage for keeping a ledger of staked token amounts for accounts.
@@ -242,8 +242,9 @@ pub mod pallet {
242
242
pub type EpochLength < T : Config > =
243
243
StorageValue < _ , T :: BlockNumber , ValueQuery , EpochLengthDefault < T > > ;
244
244
245
- /// Information about the current staking reward era.
245
+ /// Information about the current staking reward era. Checked every block.
246
246
#[ pallet:: storage]
247
+ #[ pallet:: whitelist_storage]
247
248
#[ pallet:: getter( fn get_current_era) ]
248
249
pub type CurrentEraInfo < T : Config > =
249
250
StorageValue < _ , RewardEraInfo < T :: RewardEra , T :: BlockNumber > , ValueQuery > ;
@@ -252,10 +253,8 @@ pub mod pallet {
252
253
#[ pallet:: storage]
253
254
#[ pallet:: getter( fn get_reward_pool_for_era) ]
254
255
pub type StakingRewardPool < T : Config > =
255
- StorageMap < _ , Twox64Concat , T :: RewardEra , RewardPoolInfo < BalanceOf < T > > > ;
256
+ CountedStorageMap < _ , Twox64Concat , T :: RewardEra , RewardPoolInfo < BalanceOf < T > > > ;
256
257
257
- // Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
258
- // method.
259
258
#[ pallet:: pallet]
260
259
pub struct Pallet < T > ( _ ) ;
261
260
@@ -356,6 +355,8 @@ pub mod pallet {
356
355
IneligibleForPayoutInEraRange ,
357
356
/// Attempted to retarget but from and to Provider MSA Ids were the same
358
357
CannotRetargetToSameProvider ,
358
+ /// Rewards were already paid out this era
359
+ AlreadyClaimedRewardsThisEra ,
359
360
}
360
361
361
362
#[ pallet:: hooks]
@@ -448,10 +449,9 @@ pub mod pallet {
448
449
requested_amount : BalanceOf < T > ,
449
450
) -> DispatchResult {
450
451
let unstaker = ensure_signed ( origin) ?;
451
- Self :: ensure_can_unstake ( & unstaker) ?;
452
-
453
452
ensure ! ( requested_amount > Zero :: zero( ) , Error :: <T >:: UnstakedAmountIsZero ) ;
454
453
454
+ Self :: ensure_can_unstake ( & unstaker) ?;
455
455
let actual_amount = Self :: decrease_active_staking_balance ( & unstaker, requested_amount) ?;
456
456
let capacity_reduction = Self :: reduce_capacity ( & unstaker, target, actual_amount) ?;
457
457
@@ -487,13 +487,16 @@ pub mod pallet {
487
487
/// This adds a chunk to `StakingAccountDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`.
488
488
/// The staked amount and Capacity generated by `amount` originally targeted to the `from` MSA Id is reassigned to the `to` MSA Id.
489
489
/// Does not affect unstaking process or additional stake amounts.
490
+ /// Changing a staking target to a Provider when Origin has nothing staked them will retain the staking type.
491
+ /// Changing a staking target to a Provider when Origin has any amount staked to them will error if the staking types are not the same.
490
492
/// ### Errors
491
493
/// - [`Error::NotAStakingAccount`] if origin does not have a staking account
492
494
/// - [`Error::MaxUnlockingChunksExceeded`] if `stake_change_unlocking_chunks` == `T::MaxUnlockingChunks`
493
495
/// - [`Error::StakerTargetRelationshipNotFound`] if `from` is not a target for Origin's staking account.
494
496
/// - [`Error::StakingAmountBelowMinimum`] if `amount` to retarget is below the minimum staking amount.
495
497
/// - [`Error::InsufficientStakingBalance`] if `amount` to retarget exceeds what the staker has targeted to `from` MSA Id.
496
498
/// - [`Error::InvalidTarget`] if `to` does not belong to a registered Provider.
499
+ /// - [`Error::CannotChangeStakingType`] if origin already has funds staked for `to` and the staking type for `from` is different.
497
500
#[ pallet:: call_index( 4 ) ]
498
501
#[ pallet:: weight( T :: WeightInfo :: unstake( ) ) ]
499
502
pub fn change_staking_target (
@@ -764,18 +767,41 @@ impl<T: Config> Pallet<T> {
764
767
}
765
768
766
769
fn start_new_reward_era_if_needed ( current_block : T :: BlockNumber ) -> Weight {
767
- let current_era_info: RewardEraInfo < T :: RewardEra , T :: BlockNumber > = Self :: get_current_era ( ) ;
770
+ let current_era_info: RewardEraInfo < T :: RewardEra , T :: BlockNumber > = Self :: get_current_era ( ) ; // 1r
771
+
768
772
if current_block. saturating_sub ( current_era_info. started_at ) >= T :: EraLength :: get ( ) . into ( ) {
769
- CurrentEraInfo :: < T > :: set ( RewardEraInfo {
773
+ let new_era_info = RewardEraInfo {
770
774
era_index : current_era_info. era_index . saturating_add ( One :: one ( ) ) ,
771
775
started_at : current_block,
772
- } ) ;
773
- // TODO: modify reads/writes as needed when RewardPoolInfo stuff is added
776
+ } ;
777
+
778
+ let current_reward_pool_info =
779
+ Self :: get_reward_pool_for_era ( current_era_info. era_index ) . unwrap_or_default ( ) ; // 1r
780
+
781
+ let past_eras_max = T :: StakingRewardsPastErasMax :: get ( ) ;
782
+ let entries: u32 = StakingRewardPool :: < T > :: count ( ) ; // 1r
783
+
784
+ if past_eras_max. eq ( & entries. into ( ) ) {
785
+ let earliest_era =
786
+ current_era_info. era_index . saturating_sub ( past_eras_max) . add ( One :: one ( ) ) ;
787
+ StakingRewardPool :: < T > :: remove ( earliest_era) ; // 1w
788
+ }
789
+ CurrentEraInfo :: < T > :: set ( new_era_info) ; // 1w
790
+
791
+ let total_reward_pool =
792
+ T :: RewardsProvider :: reward_pool_size ( current_reward_pool_info. total_staked_token ) ;
793
+ let new_reward_pool = RewardPoolInfo {
794
+ total_staked_token : current_reward_pool_info. total_staked_token ,
795
+ total_reward_pool,
796
+ unclaimed_balance : total_reward_pool,
797
+ } ;
798
+ StakingRewardPool :: < T > :: insert ( new_era_info. era_index , new_reward_pool) ; // 1w
799
+
774
800
T :: WeightInfo :: on_initialize ( )
775
- . saturating_add ( T :: DbWeight :: get ( ) . reads ( 1 ) )
776
- . saturating_add ( T :: DbWeight :: get ( ) . writes ( 1 ) )
801
+ . saturating_add ( T :: DbWeight :: get ( ) . reads ( 3 ) )
802
+ . saturating_add ( T :: DbWeight :: get ( ) . writes ( 3 ) )
777
803
} else {
778
- T :: DbWeight :: get ( ) . reads ( 2 ) . saturating_add ( RocksDbWeight :: get ( ) . writes ( 1 ) )
804
+ T :: DbWeight :: get ( ) . reads ( 1 )
779
805
}
780
806
}
781
807
@@ -796,7 +822,7 @@ impl<T: Config> Pallet<T> {
796
822
Self :: get_staking_account_for ( staker) . ok_or ( Error :: < T > :: NotAStakingAccount ) ?;
797
823
798
824
let current_era: T :: RewardEra = Self :: get_current_era ( ) . era_index ;
799
- let thaw_at = current_era. saturating_add ( T :: ChangeStakingTargetThawEras :: get ( ) . into ( ) ) ;
825
+ let thaw_at = current_era. saturating_add ( T :: ChangeStakingTargetThawEras :: get ( ) ) ;
800
826
staking_account_details. update_stake_change_unlocking ( amount, & thaw_at, & current_era) ?;
801
827
Self :: set_staking_account ( staker, & staking_account_details) ;
802
828
Ok ( ( ) )
@@ -814,15 +840,22 @@ impl<T: Config> Pallet<T> {
814
840
let capacity_withdrawn = Self :: reduce_capacity ( staker, * from_msa, * amount) ?;
815
841
816
842
let mut to_msa_target = Self :: get_target_for ( staker, to_msa) . unwrap_or_default ( ) ;
843
+
844
+ if to_msa_target. amount . is_zero ( ) {
845
+ // it's a new StakingTargetDetails record.
846
+ to_msa_target. staking_type = staking_type. clone ( ) ;
847
+ } else {
848
+ // make sure they are not retargeting to a StakingTargetDetails with a different staking
849
+ // type, otherwise it could interfere with staking rewards.
850
+ ensure ! (
851
+ to_msa_target. staking_type. eq( staking_type) ,
852
+ Error :: <T >:: CannotChangeStakingType
853
+ ) ;
854
+ }
817
855
to_msa_target
818
856
. deposit ( * amount, capacity_withdrawn)
819
857
. ok_or ( ArithmeticError :: Overflow ) ?;
820
858
821
- // TODO: document
822
- // if someone wants to switch staking type they must unstake completely and restake regardless of
823
- // whether it is with an existing or new provider.
824
- to_msa_target. staking_type = staking_type. clone ( ) ;
825
-
826
859
let mut capacity_details = Self :: get_capacity_for ( to_msa) . unwrap_or_default ( ) ;
827
860
capacity_details
828
861
. deposit ( amount, & capacity_withdrawn)
@@ -925,19 +958,13 @@ impl<T: Config> StakingRewardsProvider<T> for Pallet<T> {
925
958
926
959
// Calculate the size of the reward pool for the current era, based on current staked token
927
960
// and the other determined factors of the current economic model
928
- fn reward_pool_size ( ) -> Result < BalanceOf < T > , DispatchError > {
929
- let current_era_info = CurrentEraInfo :: < T > :: get ( ) ;
930
- let current_staked =
931
- StakingRewardPool :: < T > :: get ( current_era_info. era_index ) . unwrap_or_default ( ) ;
932
- if current_staked. total_staked_token . is_zero ( ) {
933
- return Ok ( BalanceOf :: < T > :: zero ( ) )
961
+ fn reward_pool_size ( total_staked : BalanceOf < T > ) -> BalanceOf < T > {
962
+ if total_staked. is_zero ( ) {
963
+ return BalanceOf :: < T > :: zero ( )
934
964
}
935
965
936
966
// For now reward pool size is set to 10% of total staked token
937
- Ok ( current_staked
938
- . total_staked_token
939
- . checked_div ( & BalanceOf :: < T > :: from ( 10u8 ) )
940
- . unwrap_or_default ( ) )
967
+ total_staked. checked_div ( & BalanceOf :: < T > :: from ( 10u8 ) ) . unwrap_or_default ( )
941
968
}
942
969
943
970
// Performs range checks plus a reward calculation based on economic model for the era range
@@ -946,9 +973,8 @@ impl<T: Config> StakingRewardsProvider<T> for Pallet<T> {
946
973
from_era : T :: RewardEra ,
947
974
to_era : T :: RewardEra ,
948
975
) -> Result < BalanceOf < T > , DispatchError > {
949
- let max_eras = T :: RewardEra :: from ( T :: StakingRewardsPastErasMax :: get ( ) ) ;
950
976
let era_range = from_era. saturating_sub ( to_era) ;
951
- ensure ! ( era_range. le( & max_eras ) , Error :: <T >:: EraOutOfRange ) ;
977
+ ensure ! ( era_range. le( & T :: StakingRewardsPastErasMax :: get ( ) ) , Error :: <T >:: EraOutOfRange ) ;
952
978
ensure ! ( from_era. le( & to_era) , Error :: <T >:: EraOutOfRange ) ;
953
979
let current_era_info = Self :: get_current_era ( ) ;
954
980
ensure ! ( to_era. lt( & current_era_info. era_index) , Error :: <T >:: EraOutOfRange ) ;
0 commit comments