pallet_capacity/
lib.rs

1//! Managages staking to the network for Capacity
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `CapacityRuntimeApi`](../pallet_capacity_runtime_api/trait.CapacityRuntimeApi.html)
7//! - [Event Enum: `Event`](Event)
8//! - [Error Enum: `Error`](Error)
9#![doc = include_str!("../README.md")]
10//!
11//! ## Lazy Capacity Refill
12//!
13//! Capacity is refilled on an as needed basis.
14//! Thus, the provider's capacity balance retains the information of the last epoch.
15//! Upon use, if the last Epoch is less than the current Epoch, the balance is assumed to be the maximum as the reload "has" happened.
16//! Thus, the first use of Capacity in an Epoch will update the last Epoch number to match the current Epoch.
17//! If a provider does not use any Capacity in an Epoch, the provider's capacity balance information is never updated for that Epoch.
18//!
19// Substrate macros are tripping the clippy::expect_used lint.
20#![allow(clippy::expect_used)]
21#![cfg_attr(not(feature = "std"), no_std)]
22// Strong Documentation Lints
23#![deny(
24	rustdoc::broken_intra_doc_links,
25	rustdoc::missing_crate_level_docs,
26	rustdoc::invalid_codeblock_attributes,
27	missing_docs
28)]
29
30use core::ops::Mul;
31use frame_support::{
32	dispatch::DispatchResult,
33	ensure,
34	traits::{
35		tokens::fungible::{Inspect as InspectFungible, InspectFreeze, Mutate, MutateFreeze},
36		Get, Hooks,
37	},
38	weights::Weight,
39};
40
41use sp_runtime::{
42	traits::{CheckedAdd, CheckedDiv, One, Saturating, Zero},
43	ArithmeticError, BoundedVec, DispatchError, Perbill, Permill,
44};
45
46pub use common_primitives::{
47	capacity::*,
48	msa::MessageSourceId,
49	node::{AccountId, Balance, BlockNumber},
50	utils::wrap_binary_data,
51};
52use frame_system::pallet_prelude::*;
53
54#[cfg(feature = "runtime-benchmarks")]
55use common_primitives::benchmarks::RegisterProviderBenchmarkHelper;
56
57pub use pallet::*;
58pub use types::*;
59pub use weights::*;
60
61pub mod types;
62
63#[cfg(feature = "runtime-benchmarks")]
64mod benchmarking;
65
66#[cfg(test)]
67mod tests;
68
69pub mod weights;
70type BalanceOf<T> =
71	<<T as Config>::Currency as InspectFungible<<T as frame_system::Config>::AccountId>>::Balance;
72type ChunkIndex = u32;
73
74#[frame_support::pallet]
75pub mod pallet {
76	use super::*;
77
78	use crate::StakingType::*;
79	use common_primitives::capacity::RewardEra;
80	use frame_support::{
81		pallet_prelude::{StorageVersion, *},
82		Twox64Concat,
83	};
84	use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeDisplay};
85
86	/// A reason for freezing funds.
87	/// Creates a freeze reason for this pallet that is aggregated by `construct_runtime`.
88	#[pallet::composite_enum]
89	pub enum FreezeReason {
90		/// The account has staked tokens to the Frequency network.
91		CapacityStaking,
92	}
93
94	/// the storage version for this pallet
95	pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
96
97	#[pallet::config]
98	pub trait Config: frame_system::Config {
99		/// The overarching event type.
100		#[allow(deprecated)]
101		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
102
103		/// The overarching freeze reason.
104		type RuntimeFreezeReason: From<FreezeReason>;
105
106		/// Weight information for extrinsics in this pallet.
107		type WeightInfo: WeightInfo;
108
109		/// Functions that allow a fungible balance to be changed or frozen.
110		type Currency: MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>
111			+ Mutate<Self::AccountId>
112			+ InspectFreeze<Self::AccountId>
113			+ InspectFungible<Self::AccountId>;
114
115		/// Function that checks if an MSA is a valid target.
116		type TargetValidator: TargetValidator;
117
118		/// The minimum required token amount to stake. It facilitates cleaning dust when unstaking.
119		#[pallet::constant]
120		type MinimumStakingAmount: Get<BalanceOf<Self>>;
121
122		/// The minimum required token amount to remain in the account after staking.
123		#[pallet::constant]
124		type MinimumTokenBalance: Get<BalanceOf<Self>>;
125
126		/// The maximum number of unlocking chunks a StakingAccountLedger can have.
127		/// It determines how many concurrent unstaked chunks may exist.
128		#[pallet::constant]
129		type MaxUnlockingChunks: Get<u32>;
130
131		#[cfg(feature = "runtime-benchmarks")]
132		/// A set of helper functions for benchmarking.
133		type BenchmarkHelper: RegisterProviderBenchmarkHelper;
134
135		/// The number of Epochs before you can unlock tokens after unstaking.
136		#[pallet::constant]
137		type UnstakingThawPeriod: Get<u16>;
138
139		/// Maximum number of blocks an epoch can be
140		#[pallet::constant]
141		type MaxEpochLength: Get<BlockNumberFor<Self>>;
142
143		/// A type that provides an Epoch number
144		/// traits pulled from frame_system::Config::BlockNumber
145		type EpochNumber: Parameter
146			+ Member
147			+ MaybeSerializeDeserialize
148			+ MaybeDisplay
149			+ AtLeast32BitUnsigned
150			+ Default
151			+ Copy
152			+ core::hash::Hash
153			+ MaxEncodedLen
154			+ TypeInfo;
155
156		/// How much FRQCY one unit of Capacity costs
157		#[pallet::constant]
158		type CapacityPerToken: Get<Perbill>;
159
160		/// The number of blocks in a RewardEra
161		#[pallet::constant]
162		type EraLength: Get<u32>;
163
164		/// The maximum number of eras over which one can claim rewards
165		/// Note that you can claim rewards even if you no longer are boosting, because you
166		/// may claim rewards for past eras up to the history limit.
167		/// MUST be a multiple of [`Self::RewardPoolChunkLength`]
168		#[pallet::constant]
169		type ProviderBoostHistoryLimit: Get<u32>;
170
171		/// The ProviderBoostRewardsProvider used by this pallet in a given runtime
172		type RewardsProvider: ProviderBoostRewardsProvider<Self>;
173
174		/// A staker may not retarget more than MaxRetargetsPerRewardEra
175		#[pallet::constant]
176		type MaxRetargetsPerRewardEra: Get<u32>;
177
178		/// The fixed size of the reward pool in each Reward Era.
179		#[pallet::constant]
180		type RewardPoolPerEra: Get<BalanceOf<Self>>;
181
182		/// the percentage cap per era of an individual Provider Boost reward
183		#[pallet::constant]
184		type RewardPercentCap: Get<Permill>;
185
186		/// The number of chunks of Reward Pool history we expect to store
187		/// Is a divisor of [`Self::ProviderBoostHistoryLimit`]
188		#[pallet::constant]
189		type RewardPoolChunkLength: Get<u32>;
190	}
191
192	/// Storage for keeping a ledger of staked token amounts for accounts.
193	/// - Keys: AccountId
194	/// - Value: [`StakingDetails`](types::StakingDetails)
195	#[pallet::storage]
196	pub type StakingAccountLedger<T: Config> =
197		StorageMap<_, Twox64Concat, T::AccountId, StakingDetails<T>>;
198
199	/// Storage to record how many tokens were targeted to an MSA.
200	/// - Keys: AccountId, MSA Id
201	/// - Value: [`StakingTargetDetails`](types::StakingTargetDetails)
202	#[pallet::storage]
203	pub type StakingTargetLedger<T: Config> = StorageDoubleMap<
204		_,
205		Twox64Concat,
206		T::AccountId,
207		Twox64Concat,
208		MessageSourceId,
209		StakingTargetDetails<BalanceOf<T>>,
210	>;
211
212	/// Storage for target Capacity usage.
213	/// - Keys: MSA Id
214	/// - Value: [`CapacityDetails`](types::CapacityDetails)
215	#[pallet::storage]
216	pub type CapacityLedger<T: Config> =
217		StorageMap<_, Twox64Concat, MessageSourceId, CapacityDetails<BalanceOf<T>, T::EpochNumber>>;
218
219	/// Storage for the current epoch number
220	#[pallet::storage]
221	#[pallet::whitelist_storage]
222	pub type CurrentEpoch<T: Config> = StorageValue<_, T::EpochNumber, ValueQuery>;
223
224	/// Storage for the current epoch info
225	#[pallet::storage]
226	pub type CurrentEpochInfo<T: Config> =
227		StorageValue<_, EpochInfo<BlockNumberFor<T>>, ValueQuery>;
228
229	#[pallet::type_value]
230	/// EpochLength defaults to 100 blocks when not set
231	pub fn EpochLengthDefault<T: Config>() -> BlockNumberFor<T> {
232		100u32.into()
233	}
234
235	/// Storage for the epoch length
236	#[pallet::storage]
237	pub type EpochLength<T: Config> =
238		StorageValue<_, BlockNumberFor<T>, ValueQuery, EpochLengthDefault<T>>;
239
240	#[pallet::storage]
241	pub type UnstakeUnlocks<T: Config> =
242		StorageMap<_, Twox64Concat, T::AccountId, UnlockChunkList<T>>;
243
244	/// stores how many times an account has retargeted, and when it last retargeted.
245	#[pallet::storage]
246	pub type Retargets<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, RetargetInfo<T>>;
247
248	/// Information about the current reward era. Checked every block.
249	#[pallet::storage]
250	#[pallet::whitelist_storage]
251	pub type CurrentEraInfo<T: Config> =
252		StorageValue<_, RewardEraInfo<RewardEra, BlockNumberFor<T>>, ValueQuery>;
253
254	/// Reward Pool history is divided into chunks of size RewardPoolChunkLength.
255	/// ProviderBoostHistoryLimit is the total number of items, the key is the
256	/// chunk number.
257	#[pallet::storage]
258	pub type ProviderBoostRewardPools<T: Config> =
259		StorageMap<_, Twox64Concat, ChunkIndex, RewardPoolHistoryChunk<T>>;
260
261	/// How much is staked this era
262	#[pallet::storage]
263	pub type CurrentEraProviderBoostTotal<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
264
265	/// Individual history for each account that has Provider-Boosted.
266	#[pallet::storage]
267	pub type ProviderBoostHistories<T: Config> =
268		StorageMap<_, Twox64Concat, T::AccountId, ProviderBoostHistory<T>>;
269
270	// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
271	// method.
272	#[pallet::pallet]
273	#[pallet::storage_version(STORAGE_VERSION)]
274	pub struct Pallet<T>(_);
275
276	#[pallet::event]
277	#[pallet::generate_deposit(pub (super) fn deposit_event)]
278	pub enum Event<T: Config> {
279		/// Tokens have been staked to the Frequency network.
280		Staked {
281			/// The token account that staked tokens to the network.
282			account: T::AccountId,
283			/// The MSA that a token account targeted to receive Capacity based on this staking amount.
284			target: MessageSourceId,
285			/// An amount that was staked.
286			amount: BalanceOf<T>,
287			/// The Capacity amount issued to the target as a result of the stake.
288			capacity: BalanceOf<T>,
289		},
290		/// Unstaked token that has thawed was unlocked for the given account
291		StakeWithdrawn {
292			/// the account that withdrew its stake
293			account: T::AccountId,
294			/// the total amount withdrawn, i.e. put back into free balance.
295			amount: BalanceOf<T>,
296		},
297		/// A token account has unstaked the Frequency network.
298		UnStaked {
299			/// The token account that unstaked tokens from the network.
300			account: T::AccountId,
301			/// The MSA target that will now have Capacity reduced as a result of unstaking.
302			target: MessageSourceId,
303			/// The amount that was unstaked.
304			amount: BalanceOf<T>,
305			/// The Capacity amount that was reduced from a target.
306			capacity: BalanceOf<T>,
307		},
308		/// The Capacity epoch length was changed.
309		EpochLengthUpdated {
310			/// The new length of an epoch in blocks.
311			blocks: BlockNumberFor<T>,
312		},
313		/// Capacity has been withdrawn from a MessageSourceId.
314		CapacityWithdrawn {
315			/// The MSA from which Capacity has been withdrawn.
316			msa_id: MessageSourceId,
317			/// The amount of Capacity withdrawn from MSA.
318			amount: BalanceOf<T>,
319		},
320		/// The target of a staked amount was changed to a new MessageSourceId
321		StakingTargetChanged {
322			/// The account that retargeted the staking amount
323			account: T::AccountId,
324			/// The Provider MSA that the staking amount is taken from
325			from_msa: MessageSourceId,
326			/// The Provider MSA that the staking amount is retargeted to
327			to_msa: MessageSourceId,
328			/// The amount in token that was retargeted
329			amount: BalanceOf<T>,
330		},
331		/// Tokens have been staked on the network for Provider Boosting
332		ProviderBoosted {
333			/// The token account that staked tokens to the network.
334			account: T::AccountId,
335			/// The MSA that a token account targeted to receive Capacity based on this staking amount.
336			target: MessageSourceId,
337			/// An amount that was staked.
338			amount: BalanceOf<T>,
339			/// The Capacity amount issued to the target as a result of the stake.
340			capacity: BalanceOf<T>,
341		},
342		/// Provider Boost Token Rewards have been minted and transferred to the staking account.
343		ProviderBoostRewardClaimed {
344			/// The token account claiming and receiving the reward from ProviderBoost staking
345			account: T::AccountId,
346			/// The reward amount
347			reward_amount: BalanceOf<T>,
348		},
349	}
350
351	#[pallet::error]
352	pub enum Error<T> {
353		/// Staker attempted to stake to an invalid staking target.
354		InvalidTarget,
355		/// Capacity is not available for the given MSA.
356		InsufficientCapacityBalance,
357		/// Staker is attempting to stake an amount below the minimum amount.
358		StakingAmountBelowMinimum,
359		/// Staker is attempting to stake a zero amount.  DEPRECATED
360		/// #[deprecated(since = "1.13.0", note = "Use StakingAmountBelowMinimum instead")]
361		ZeroAmountNotAllowed,
362		/// This AccountId does not have a staking account.
363		NotAStakingAccount,
364		/// No staked value is available for withdrawal; either nothing is being unstaked,
365		/// or nothing has passed the thaw period.  (5)
366		NoUnstakedTokensAvailable,
367		/// Unstaking amount should be greater than zero.
368		UnstakedAmountIsZero,
369		/// Amount to unstake or change targets is greater than the amount staked.
370		InsufficientStakingBalance,
371		/// Attempted to get a staker / target relationship that does not exist.
372		StakerTargetRelationshipNotFound,
373		/// Attempted to get the target's capacity that does not exist.
374		TargetCapacityNotFound,
375		/// Staker has reached the limit of unlocking chunks and must wait for at least one thaw period
376		/// to complete. (10)
377		MaxUnlockingChunksExceeded,
378		/// Capacity increase exceeds the total available Capacity for target.
379		IncreaseExceedsAvailable,
380		/// Attempted to set the Epoch length to a value greater than the max Epoch length.
381		MaxEpochLengthExceeded,
382		/// Staker is attempting to stake an amount that leaves a token balance below the minimum amount.
383		BalanceTooLowtoStake,
384		/// There are no unstaked token amounts that have passed their thaw period.
385		NoThawedTokenAvailable,
386		/// Staker tried to change StakingType on an existing account
387		CannotChangeStakingType,
388		/// The Era specified is too far in the past or is in the future (15)
389		EraOutOfRange,
390		/// Attempted to retarget but from and to Provider MSA Ids were the same
391		CannotRetargetToSameProvider,
392		/// There are no rewards eligible to claim.  Rewards have expired, have already been
393		/// claimed, or boosting has never been done before the current era.
394		NoRewardsEligibleToClaim,
395		/// Caller must claim rewards before unstaking.
396		MustFirstClaimRewards,
397		/// Too many change_staking_target calls made in this RewardEra. (20)
398		MaxRetargetsExceeded,
399		/// Tried to exceed bounds of a some Bounded collection
400		CollectionBoundExceeded,
401		/// This origin has nothing staked for ProviderBoost.
402		NotAProviderBoostAccount,
403	}
404
405	#[pallet::hooks]
406	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
407		fn on_initialize(current: BlockNumberFor<T>) -> Weight {
408			Self::start_new_epoch_if_needed(current)
409				.saturating_add(Self::start_new_reward_era_if_needed(current))
410		}
411	}
412
413	#[pallet::call]
414	impl<T: Config> Pallet<T> {
415		/// Stakes some amount of tokens to the network and generates Capacity.
416		///
417		/// ### Errors
418		///
419		/// - Returns Error::InvalidTarget if attempting to stake to an invalid target.
420		/// - Returns Error::StakingAmountBelowMinimum if attempting to stake an amount below the minimum amount.
421		/// - Returns Error::CannotChangeStakingType if the staking account is a ProviderBoost account
422		#[pallet::call_index(0)]
423		#[pallet::weight(T::WeightInfo::stake())]
424		pub fn stake(
425			origin: OriginFor<T>,
426			target: MessageSourceId,
427			amount: BalanceOf<T>,
428		) -> DispatchResult {
429			let staker = ensure_signed(origin)?;
430
431			let (mut staking_account, actual_amount) =
432				Self::ensure_can_stake(&staker, target, amount, MaximumCapacity)?;
433
434			let capacity = Self::increase_stake_and_issue_capacity(
435				&staker,
436				&mut staking_account,
437				target,
438				actual_amount,
439			)?;
440
441			Self::deposit_event(Event::Staked {
442				account: staker,
443				amount: actual_amount,
444				target,
445				capacity,
446			});
447
448			Ok(())
449		}
450
451		/// Removes all thawed UnlockChunks from caller's UnstakeUnlocks and thaws(unfreezes) the sum of the thawed values
452		/// in the caller's token account.
453		///
454		/// ### Errors
455		///   - Returns `Error::NoUnstakedTokensAvailable` if the account has no unstaking chunks.
456		///   - Returns `Error::NoThawedTokenAvailable` if there are unstaking chunks, but none are thawed.
457		#[pallet::call_index(1)]
458		#[pallet::weight(T::WeightInfo::withdraw_unstaked())]
459		pub fn withdraw_unstaked(origin: OriginFor<T>) -> DispatchResult {
460			let staker = ensure_signed(origin)?;
461			let amount_withdrawn = Self::do_withdraw_unstaked(&staker)?;
462			Self::deposit_event(Event::<T>::StakeWithdrawn {
463				account: staker,
464				amount: amount_withdrawn,
465			});
466			Ok(())
467		}
468
469		/// Schedules an amount of the stake to be unlocked.
470		/// ### Errors
471		///
472		/// - Returns `Error::UnstakedAmountIsZero` if `amount` is not greater than zero.
473		/// - Returns `Error::MaxUnlockingChunksExceeded` if attempting to unlock more times than config::MaxUnlockingChunks.
474		/// - Returns `Error::AmountToUnstakeExceedsAmountStaked` if `amount` exceeds the amount currently staked.
475		/// - Returns `Error::InvalidTarget` if `target` is not a valid staking target (not a Provider)
476		/// - Returns `Error::NotAStakingAccount` if `origin` has nothing staked at all
477		/// - Returns `Error::StakerTargetRelationshipNotFound` if `origin` has nothing staked to `target`
478		#[pallet::call_index(2)]
479		#[pallet::weight(T::WeightInfo::unstake())]
480		pub fn unstake(
481			origin: OriginFor<T>,
482			target: MessageSourceId,
483			requested_amount: BalanceOf<T>,
484		) -> DispatchResult {
485			let unstaker = ensure_signed(origin)?;
486
487			ensure!(requested_amount > Zero::zero(), Error::<T>::UnstakedAmountIsZero);
488
489			ensure!(!Self::has_unclaimed_rewards(&unstaker), Error::<T>::MustFirstClaimRewards);
490
491			let (actual_amount, staking_type) =
492				Self::decrease_active_staking_balance(&unstaker, requested_amount)?;
493			Self::add_unlock_chunk(&unstaker, actual_amount)?;
494
495			let capacity_reduction =
496				Self::reduce_capacity(&unstaker, target, actual_amount, staking_type)?;
497
498			Self::deposit_event(Event::UnStaked {
499				account: unstaker,
500				target,
501				amount: actual_amount,
502				capacity: capacity_reduction,
503			});
504			Ok(())
505		}
506
507		/// Sets the epoch period length (in blocks).
508		///
509		/// # Requires
510		/// * Root Origin
511		///
512		/// ### Errors
513		/// - Returns `Error::MaxEpochLengthExceeded` if `length` is greater than T::MaxEpochLength.
514		#[pallet::call_index(3)]
515		#[pallet::weight(T::WeightInfo::set_epoch_length())]
516		pub fn set_epoch_length(origin: OriginFor<T>, length: BlockNumberFor<T>) -> DispatchResult {
517			ensure_root(origin)?;
518			ensure!(length <= T::MaxEpochLength::get(), Error::<T>::MaxEpochLengthExceeded);
519
520			EpochLength::<T>::set(length);
521
522			Self::deposit_event(Event::EpochLengthUpdated { blocks: length });
523			Ok(())
524		}
525
526		/// Sets the target of the staking capacity to a new target.
527		/// This adds a chunk to `StakingDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`.
528		/// The staked amount and Capacity generated by `amount` originally targeted to the `from` MSA Id is reassigned to the `to` MSA Id.
529		/// Does not affect unstaking process or additional stake amounts.
530		/// Changing a staking target to a Provider when Origin has nothing staked them will retain the staking type.
531		/// 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.
532		/// ### Errors
533		/// - [`Error::MaxUnlockingChunksExceeded`] if `stake_change_unlocking_chunks` == `T::MaxUnlockingChunks`
534		/// - [`Error::StakerTargetRelationshipNotFound`] if `from` is not a target for Origin's staking account.
535		/// - [`Error::StakingAmountBelowMinimum`] if `amount` to retarget is below the minimum staking amount.
536		/// - [`Error::InsufficientStakingBalance`] if `amount` to retarget exceeds what the staker has targeted to `from` MSA Id.
537		/// - [`Error::InvalidTarget`] if `to` does not belong to a registered Provider.
538		/// - [`Error::MaxRetargetsExceeded`] if origin has reached the maximimum number of retargets for the current RewardEra.
539		#[pallet::call_index(4)]
540		#[pallet::weight(T::WeightInfo::change_staking_target())]
541		pub fn change_staking_target(
542			origin: OriginFor<T>,
543			from: MessageSourceId,
544			to: MessageSourceId,
545			amount: BalanceOf<T>,
546		) -> DispatchResult {
547			let staker = ensure_signed(origin)?;
548			// This will bounce immediately if they've tried to do this too many times.
549			Self::update_retarget_record(&staker)?;
550			ensure!(from.ne(&to), Error::<T>::CannotRetargetToSameProvider);
551			ensure!(
552				amount >= T::MinimumStakingAmount::get(),
553				Error::<T>::StakingAmountBelowMinimum
554			);
555
556			ensure!(T::TargetValidator::validate(to), Error::<T>::InvalidTarget);
557
558			Self::do_retarget(&staker, &from, &to, &amount)?;
559
560			Self::deposit_event(Event::StakingTargetChanged {
561				account: staker,
562				from_msa: from,
563				to_msa: to,
564				amount,
565			});
566			Ok(())
567		}
568		/// Stakes some amount of tokens to the network and generates a comparatively small amount of Capacity
569		/// for the target, and gives periodic rewards to origin.
570		/// ### Errors
571		///
572		/// - Error::InvalidTarget if attempting to stake to an invalid target.
573		/// - Error::StakingAmountBelowMinimum if attempting to stake an amount below the minimum amount.
574		/// - Error::CannotChangeStakingType if the staking account exists and staking_type is MaximumCapacity
575		#[pallet::call_index(5)]
576		#[pallet::weight(T::WeightInfo::provider_boost())]
577		pub fn provider_boost(
578			origin: OriginFor<T>,
579			target: MessageSourceId,
580			amount: BalanceOf<T>,
581		) -> DispatchResult {
582			let staker = ensure_signed(origin)?;
583			let (mut boosting_details, actual_amount) =
584				Self::ensure_can_boost(&staker, &target, &amount)?;
585
586			let capacity = Self::increase_stake_and_issue_boost_capacity(
587				&staker,
588				&mut boosting_details,
589				&target,
590				&actual_amount,
591			)?;
592
593			Self::deposit_event(Event::ProviderBoosted {
594				account: staker,
595				amount: actual_amount,
596				target,
597				capacity,
598			});
599
600			Ok(())
601		}
602
603		/// Claim all outstanding Provider Boost rewards, up to ProviderBoostHistoryLimit Reward Eras
604		/// in the past.  Accounts should check for unclaimed rewards before calling this extrinsic
605		/// to avoid needless transaction fees.
606		/// ### Errors:
607		/// - NotAProviderBoostAccount:  if Origin has nothing staked for ProviderBoost
608		/// - NoRewardsEligibleToClaim:  if Origin has no unclaimed rewards to pay out.
609		#[pallet::call_index(6)]
610		#[pallet::weight(T::WeightInfo::claim_staking_rewards())]
611		pub fn claim_staking_rewards(origin: OriginFor<T>) -> DispatchResult {
612			let staker = ensure_signed(origin)?;
613			ensure!(
614				ProviderBoostHistories::<T>::contains_key(staker.clone()),
615				Error::<T>::NotAProviderBoostAccount
616			);
617			let total_to_mint = Self::do_claim_rewards(&staker)?;
618			Self::deposit_event(Event::ProviderBoostRewardClaimed {
619				account: staker.clone(),
620				reward_amount: total_to_mint,
621			});
622			Ok(())
623		}
624	}
625}
626
627impl<T: Config> Pallet<T> {
628	/// Checks to see if staker has sufficient free-balance to stake the minimum required staking amount,
629	/// and leave the minimum required free balance after staking.
630	///
631	/// # Errors
632	/// * [`Error::ZeroAmountNotAllowed`]
633	/// * [`Error::InvalidTarget`]
634	/// * [`Error::CannotChangeStakingType`]
635	/// * [`Error::BalanceTooLowtoStake`]
636	/// * [`Error::StakingAmountBelowMinimum`]
637	///
638	fn ensure_can_stake(
639		staker: &T::AccountId,
640		target: MessageSourceId,
641		amount: BalanceOf<T>,
642		staking_type: StakingType,
643	) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
644		ensure!(amount > Zero::zero(), Error::<T>::ZeroAmountNotAllowed);
645		ensure!(T::TargetValidator::validate(target), Error::<T>::InvalidTarget);
646
647		let staking_details = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
648		if !staking_details.active.is_zero() {
649			ensure!(
650				staking_details.staking_type.eq(&staking_type),
651				Error::<T>::CannotChangeStakingType
652			);
653		}
654
655		let stakable_amount = Self::get_stakable_amount_for(staker, amount);
656
657		ensure!(stakable_amount > Zero::zero(), Error::<T>::BalanceTooLowtoStake);
658		ensure!(
659			stakable_amount >= T::MinimumStakingAmount::get(),
660			Error::<T>::StakingAmountBelowMinimum
661		);
662
663		Ok((staking_details, stakable_amount))
664	}
665
666	fn ensure_can_boost(
667		staker: &T::AccountId,
668		target: &MessageSourceId,
669		amount: &BalanceOf<T>,
670	) -> Result<(StakingDetails<T>, BalanceOf<T>), DispatchError> {
671		let (mut staking_details, stakable_amount) =
672			Self::ensure_can_stake(staker, *target, *amount, StakingType::ProviderBoost)?;
673		staking_details.staking_type = StakingType::ProviderBoost;
674		Ok((staking_details, stakable_amount))
675	}
676
677	/// Increase a staking account and target account balances by amount.
678	/// Additionally, it issues Capacity to the MSA target.
679	fn increase_stake_and_issue_capacity(
680		staker: &T::AccountId,
681		staking_account: &mut StakingDetails<T>,
682		target: MessageSourceId,
683		amount: BalanceOf<T>,
684	) -> Result<BalanceOf<T>, DispatchError> {
685		staking_account.deposit(amount).ok_or(ArithmeticError::Overflow)?;
686
687		let capacity = Self::capacity_generated(amount);
688		let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
689		target_details.deposit(amount, capacity).ok_or(ArithmeticError::Overflow)?;
690
691		let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
692		capacity_details.deposit(&amount, &capacity).ok_or(ArithmeticError::Overflow)?;
693
694		Self::set_staking_account_and_lock(staker, staking_account)?;
695
696		Self::set_target_details_for(staker, target, target_details);
697		Self::set_capacity_for(target, capacity_details);
698
699		Ok(capacity)
700	}
701
702	fn increase_stake_and_issue_boost_capacity(
703		staker: &T::AccountId,
704		staking_details: &mut StakingDetails<T>,
705		target: &MessageSourceId,
706		amount: &BalanceOf<T>,
707	) -> Result<BalanceOf<T>, DispatchError> {
708		staking_details.deposit(*amount).ok_or(ArithmeticError::Overflow)?;
709		Self::set_staking_account_and_lock(staker, staking_details)?;
710
711		// get the capacity generated by a Provider Boost
712		let capacity = Self::capacity_generated(T::RewardsProvider::capacity_boost(*amount));
713
714		let mut target_details = StakingTargetLedger::<T>::get(staker, target).unwrap_or_default();
715
716		target_details.deposit(*amount, capacity).ok_or(ArithmeticError::Overflow)?;
717		Self::set_target_details_for(staker, *target, target_details);
718
719		let mut capacity_details = CapacityLedger::<T>::get(target).unwrap_or_default();
720		capacity_details.deposit(amount, &capacity).ok_or(ArithmeticError::Overflow)?;
721		Self::set_capacity_for(*target, capacity_details);
722
723		let era = CurrentEraInfo::<T>::get().era_index;
724		Self::upsert_boost_history(staker, era, *amount, true)?;
725
726		let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
727		CurrentEraProviderBoostTotal::<T>::set(reward_pool_total.saturating_add(*amount));
728
729		Ok(capacity)
730	}
731
732	/// Sets staking account details after a deposit
733	fn set_staking_account_and_lock(
734		staker: &T::AccountId,
735		staking_account: &StakingDetails<T>,
736	) -> Result<(), DispatchError> {
737		let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
738		let total_to_lock: BalanceOf<T> = staking_account
739			.active
740			.checked_add(&unlock_chunks_total::<T>(&unlocks))
741			.ok_or(ArithmeticError::Overflow)?;
742		T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_to_lock)?;
743		Self::set_staking_account(staker, staking_account);
744		Ok(())
745	}
746
747	fn set_staking_account(staker: &T::AccountId, staking_account: &StakingDetails<T>) {
748		if staking_account.active.is_zero() {
749			StakingAccountLedger::<T>::set(staker, None);
750		} else {
751			StakingAccountLedger::<T>::insert(staker, staking_account);
752		}
753	}
754
755	/// Sets target account details.
756	fn set_target_details_for(
757		staker: &T::AccountId,
758		target: MessageSourceId,
759		target_details: StakingTargetDetails<BalanceOf<T>>,
760	) {
761		if target_details.amount.is_zero() {
762			StakingTargetLedger::<T>::remove(staker, target);
763		} else {
764			StakingTargetLedger::<T>::insert(staker, target, target_details);
765		}
766	}
767
768	/// Sets targets Capacity.
769	pub fn set_capacity_for(
770		target: MessageSourceId,
771		capacity_details: CapacityDetails<BalanceOf<T>, T::EpochNumber>,
772	) {
773		CapacityLedger::<T>::insert(target, capacity_details);
774	}
775
776	/// Decrease a staking account's active token and reap if it goes below the minimum.
777	/// Returns: actual amount unstaked, plus the staking type + StakingDetails,
778	/// since StakingDetails may be reaped and staking type must be used to calculate the
779	/// capacity reduction later.
780	fn decrease_active_staking_balance(
781		unstaker: &T::AccountId,
782		amount: BalanceOf<T>,
783	) -> Result<(BalanceOf<T>, StakingType), DispatchError> {
784		let mut staking_account =
785			StakingAccountLedger::<T>::get(unstaker).ok_or(Error::<T>::NotAStakingAccount)?;
786		ensure!(amount <= staking_account.active, Error::<T>::InsufficientStakingBalance);
787
788		let actual_unstaked_amount = staking_account.withdraw(amount)?;
789		Self::set_staking_account(unstaker, &staking_account);
790
791		let staking_type = staking_account.staking_type;
792		if staking_type == StakingType::ProviderBoost {
793			let era = CurrentEraInfo::<T>::get().era_index;
794			Self::upsert_boost_history(unstaker, era, actual_unstaked_amount, false)?;
795			let reward_pool_total = CurrentEraProviderBoostTotal::<T>::get();
796			CurrentEraProviderBoostTotal::<T>::set(
797				reward_pool_total.saturating_sub(actual_unstaked_amount),
798			);
799		}
800		Ok((actual_unstaked_amount, staking_type))
801	}
802
803	fn add_unlock_chunk(
804		unstaker: &T::AccountId,
805		actual_unstaked_amount: BalanceOf<T>,
806	) -> Result<(), DispatchError> {
807		let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
808		let thaw_at =
809			current_epoch.saturating_add(T::EpochNumber::from(T::UnstakingThawPeriod::get()));
810		let mut unlocks = UnstakeUnlocks::<T>::get(unstaker).unwrap_or_default();
811
812		match unlocks.iter_mut().find(|chunk| chunk.thaw_at == thaw_at) {
813			Some(chunk) => {
814				chunk.value += actual_unstaked_amount;
815			},
816			None => {
817				let unlock_chunk: UnlockChunk<BalanceOf<T>, T::EpochNumber> =
818					UnlockChunk { value: actual_unstaked_amount, thaw_at };
819				unlocks
820					.try_push(unlock_chunk)
821					.map_err(|_| Error::<T>::MaxUnlockingChunksExceeded)?;
822			},
823		}
824
825		UnstakeUnlocks::<T>::set(unstaker, Some(unlocks));
826		Ok(())
827	}
828
829	// Calculates a stakable amount from a proposed amount.
830	pub(crate) fn get_stakable_amount_for(
831		staker: &T::AccountId,
832		proposed_amount: BalanceOf<T>,
833	) -> BalanceOf<T> {
834		let unlocks = UnstakeUnlocks::<T>::get(staker).unwrap_or_default();
835
836		let unlock_chunks_sum = unlock_chunks_total::<T>(&unlocks);
837		let freezable_balance = T::Currency::balance_freezable(staker);
838		let current_staking_balance =
839			StakingAccountLedger::<T>::get(staker).unwrap_or_default().active;
840		let stakable_amount = freezable_balance
841			.saturating_sub(current_staking_balance)
842			.saturating_sub(unlock_chunks_sum)
843			.saturating_sub(T::MinimumTokenBalance::get());
844		if stakable_amount >= proposed_amount {
845			proposed_amount
846		} else {
847			Zero::zero()
848		}
849	}
850
851	pub(crate) fn do_withdraw_unstaked(
852		staker: &T::AccountId,
853	) -> Result<BalanceOf<T>, DispatchError> {
854		let current_epoch = CurrentEpoch::<T>::get();
855		let mut total_unlocking: BalanceOf<T> = Zero::zero();
856
857		let mut unlocks =
858			UnstakeUnlocks::<T>::get(staker).ok_or(Error::<T>::NoUnstakedTokensAvailable)?;
859		let amount_withdrawn = unlock_chunks_reap_thawed::<T>(&mut unlocks, current_epoch);
860		ensure!(!amount_withdrawn.is_zero(), Error::<T>::NoThawedTokenAvailable);
861
862		if unlocks.is_empty() {
863			UnstakeUnlocks::<T>::set(staker, None);
864		} else {
865			total_unlocking = unlock_chunks_total::<T>(&unlocks);
866			UnstakeUnlocks::<T>::set(staker, Some(unlocks));
867		}
868
869		let staking_account = StakingAccountLedger::<T>::get(staker).unwrap_or_default();
870		let total_locked = staking_account.active.saturating_add(total_unlocking);
871		if total_locked.is_zero() {
872			T::Currency::thaw(&FreezeReason::CapacityStaking.into(), staker)?;
873		} else {
874			T::Currency::set_freeze(&FreezeReason::CapacityStaking.into(), staker, total_locked)?;
875		}
876		Ok(amount_withdrawn)
877	}
878
879	#[allow(unused)]
880	fn get_thaw_at_epoch() -> <T as Config>::EpochNumber {
881		let current_epoch: T::EpochNumber = CurrentEpoch::<T>::get();
882		let thaw_period = T::UnstakingThawPeriod::get();
883		current_epoch.saturating_add(thaw_period.into())
884	}
885
886	/// Reduce available capacity of target and return the amount of capacity reduction.
887	fn reduce_capacity(
888		unstaker: &T::AccountId,
889		target: MessageSourceId,
890		amount: BalanceOf<T>,
891		staking_type: StakingType,
892	) -> Result<BalanceOf<T>, DispatchError> {
893		let mut staking_target_details = StakingTargetLedger::<T>::get(unstaker, target)
894			.ok_or(Error::<T>::StakerTargetRelationshipNotFound)?;
895
896		ensure!(amount.le(&staking_target_details.amount), Error::<T>::InsufficientStakingBalance);
897
898		let mut capacity_details =
899			CapacityLedger::<T>::get(target).ok_or(Error::<T>::TargetCapacityNotFound)?;
900
901		let capacity_to_withdraw = if staking_target_details.amount.eq(&amount) {
902			staking_target_details.capacity
903		} else if staking_type.eq(&StakingType::ProviderBoost) {
904			Perbill::from_rational(amount, staking_target_details.amount)
905				.mul_ceil(staking_target_details.capacity)
906		} else {
907			// this call will return an amount > than requested if the resulting StakingTargetDetails balance
908			// is below the minimum. This ensures we withdraw the same amounts as for staking_target_details.
909			Self::calculate_capacity_reduction(
910				amount,
911				capacity_details.total_tokens_staked,
912				capacity_details.total_capacity_issued,
913			)
914		};
915
916		let (actual_amount, actual_capacity) = staking_target_details.withdraw(
917			amount,
918			capacity_to_withdraw,
919			T::MinimumStakingAmount::get(),
920		);
921
922		capacity_details.withdraw(actual_capacity, actual_amount);
923
924		Self::set_capacity_for(target, capacity_details);
925		Self::set_target_details_for(unstaker, target, staking_target_details);
926
927		Ok(capacity_to_withdraw)
928	}
929
930	/// Calculates Capacity generated for given FRQCY
931	fn capacity_generated(amount: BalanceOf<T>) -> BalanceOf<T> {
932		let cpt = T::CapacityPerToken::get();
933		cpt.mul(amount)
934	}
935
936	/// Determine the capacity reduction when given total_capacity, unstaking_amount, and total_amount_staked,
937	/// based on ratios
938	fn calculate_capacity_reduction(
939		unstaking_amount: BalanceOf<T>,
940		total_amount_staked: BalanceOf<T>,
941		total_capacity: BalanceOf<T>,
942	) -> BalanceOf<T> {
943		Perbill::from_rational(unstaking_amount, total_amount_staked).mul_ceil(total_capacity)
944	}
945
946	fn start_new_epoch_if_needed(current_block: BlockNumberFor<T>) -> Weight {
947		// Should we start a new epoch?
948		if current_block.saturating_sub(CurrentEpochInfo::<T>::get().epoch_start) >=
949			EpochLength::<T>::get()
950		{
951			let current_epoch = CurrentEpoch::<T>::get();
952			CurrentEpoch::<T>::set(current_epoch.saturating_add(1u32.into()));
953			CurrentEpochInfo::<T>::set(EpochInfo { epoch_start: current_block });
954			T::WeightInfo::start_new_epoch_if_needed()
955		} else {
956			// 1 for get_current_epoch_info, 1 for get_epoch_length
957			T::DbWeight::get().reads(2u64).saturating_add(T::DbWeight::get().writes(1))
958		}
959	}
960
961	fn start_new_reward_era_if_needed(current_block: BlockNumberFor<T>) -> Weight {
962		let current_era_info: RewardEraInfo<RewardEra, BlockNumberFor<T>> =
963			CurrentEraInfo::<T>::get(); // 1r
964
965		if current_block.saturating_sub(current_era_info.started_at) >= T::EraLength::get().into() {
966			// 1r
967			let new_era_info = RewardEraInfo {
968				era_index: current_era_info.era_index.saturating_add(One::one()),
969				started_at: current_block,
970			};
971			CurrentEraInfo::<T>::set(new_era_info); // 1w
972
973			// carry over the current reward pool total
974			let current_reward_pool_total: BalanceOf<T> = CurrentEraProviderBoostTotal::<T>::get(); // 1
975			Self::update_provider_boost_reward_pool(
976				current_era_info.era_index,
977				current_reward_pool_total,
978			);
979			T::WeightInfo::start_new_reward_era_if_needed()
980		} else {
981			T::DbWeight::get().reads(1)
982		}
983	}
984
985	/// attempts to increment number of retargets this RewardEra
986	/// Returns:
987	///     Error::MaxRetargetsExceeded if they try to retarget too many times in one era.
988	fn update_retarget_record(staker: &T::AccountId) -> Result<(), DispatchError> {
989		let current_era: RewardEra = CurrentEraInfo::<T>::get().era_index;
990		let mut retargets = Retargets::<T>::get(staker).unwrap_or_default();
991		ensure!(retargets.update(current_era).is_some(), Error::<T>::MaxRetargetsExceeded);
992		Retargets::<T>::set(staker, Some(retargets));
993		Ok(())
994	}
995
996	/// Performs the work of withdrawing the requested amount from the old staker-provider target details, and
997	/// from the Provider's capacity details, and depositing it into the new staker-provider target details.
998	pub(crate) fn do_retarget(
999		staker: &T::AccountId,
1000		from_msa: &MessageSourceId,
1001		to_msa: &MessageSourceId,
1002		amount: &BalanceOf<T>,
1003	) -> Result<(), DispatchError> {
1004		let staking_type = StakingAccountLedger::<T>::get(staker).unwrap_or_default().staking_type;
1005		let capacity_withdrawn = Self::reduce_capacity(staker, *from_msa, *amount, staking_type)?;
1006
1007		let mut to_msa_target = StakingTargetLedger::<T>::get(staker, to_msa).unwrap_or_default();
1008
1009		to_msa_target
1010			.deposit(*amount, capacity_withdrawn)
1011			.ok_or(ArithmeticError::Overflow)?;
1012
1013		let mut capacity_details = CapacityLedger::<T>::get(to_msa).unwrap_or_default();
1014		capacity_details
1015			.deposit(amount, &capacity_withdrawn)
1016			.ok_or(ArithmeticError::Overflow)?;
1017
1018		Self::set_target_details_for(staker, *to_msa, to_msa_target);
1019		Self::set_capacity_for(*to_msa, capacity_details);
1020		Ok(())
1021	}
1022
1023	/// updates or inserts a new boost history record for current_era.   Pass 'add' = true for an increase (provider_boost),
1024	/// pass 'false' for a decrease (unstake)
1025	pub(crate) fn upsert_boost_history(
1026		account: &T::AccountId,
1027		current_era: RewardEra,
1028		boost_amount: BalanceOf<T>,
1029		add: bool,
1030	) -> Result<(), DispatchError> {
1031		let mut boost_history = ProviderBoostHistories::<T>::get(account).unwrap_or_default();
1032
1033		let upsert_result = if add {
1034			boost_history.add_era_balance(&current_era, &boost_amount)
1035		} else {
1036			boost_history.subtract_era_balance(&current_era, &boost_amount)
1037		};
1038		match upsert_result {
1039			Some(0usize) => ProviderBoostHistories::<T>::remove(account),
1040			None => return Err(DispatchError::from(Error::<T>::EraOutOfRange)),
1041			_ => ProviderBoostHistories::<T>::set(account, Some(boost_history)),
1042		}
1043		Ok(())
1044	}
1045
1046	pub(crate) fn has_unclaimed_rewards(account: &T::AccountId) -> bool {
1047		let current_era = CurrentEraInfo::<T>::get().era_index;
1048		match ProviderBoostHistories::<T>::get(account) {
1049			// We can ignore any entries for the current or prior era, since:
1050			//   - if it's for the previous era, it means we've already paid out rewards for that era,
1051			//     or they just staked in that era & hence aren't eligible for rewards yet.
1052			//   - if it's for the current era, then they've only just started staking
1053			// If there are any entries for eras earlier than one prior to the current era, then there
1054			// are unclaimed rewards.
1055			Some(provider_boost_history) =>
1056				matches!(provider_boost_history.get_earliest_reward_era(),
1057						 Some(era) if era < &current_era.saturating_sub(1u32)),
1058			None => false,
1059		}
1060	}
1061
1062	/// Get all unclaimed rewards information for each eligible Reward Era.
1063	/// If no unclaimed rewards, returns empty list.
1064	pub fn list_unclaimed_rewards(
1065		account: &T::AccountId,
1066	) -> Result<
1067		BoundedVec<
1068			UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1069			T::ProviderBoostHistoryLimit,
1070		>,
1071		DispatchError,
1072	> {
1073		if !Self::has_unclaimed_rewards(account) {
1074			return Ok(BoundedVec::new());
1075		}
1076
1077		let staking_history = ProviderBoostHistories::<T>::get(account)
1078			.ok_or(Error::<T>::NotAProviderBoostAccount)?; // cached read
1079
1080		let current_era_info = CurrentEraInfo::<T>::get(); // cached read, ditto
1081		let max_history: u32 = T::ProviderBoostHistoryLimit::get();
1082
1083		let start_era = current_era_info.era_index.saturating_sub(max_history);
1084		let end_era = current_era_info.era_index.saturating_sub(One::one()); // stop at previous era
1085
1086		// start with how much was staked in the era before the earliest for which there are eligible rewards.
1087		let mut previous_amount: BalanceOf<T> = match start_era {
1088			0 => 0u32.into(),
1089			_ => staking_history.get_amount_staked_for_era(&(start_era.saturating_sub(1u32))),
1090		};
1091		let mut unclaimed_rewards: BoundedVec<
1092			UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
1093			T::ProviderBoostHistoryLimit,
1094		> = BoundedVec::new();
1095		for reward_era in start_era..=end_era {
1096			let staked_amount = staking_history.get_amount_staked_for_era(&reward_era);
1097			if !staked_amount.is_zero() {
1098				let expires_at_era = reward_era.saturating_add(max_history);
1099				let expires_at_block = Self::block_at_end_of_era(expires_at_era);
1100				let eligible_amount = staked_amount.min(previous_amount);
1101				let total_for_era =
1102					Self::get_total_stake_for_past_era(reward_era, current_era_info.era_index)?;
1103				let earned_amount = <T>::RewardsProvider::era_staking_reward(
1104					eligible_amount,
1105					total_for_era,
1106					T::RewardPoolPerEra::get(),
1107				);
1108				unclaimed_rewards
1109					.try_push(UnclaimedRewardInfo {
1110						reward_era,
1111						expires_at_block,
1112						staked_amount,
1113						eligible_amount,
1114						earned_amount,
1115					})
1116					.map_err(|_e| Error::<T>::CollectionBoundExceeded)?;
1117				// ^^ there's no good reason for this ever to fail in production but it must be handled.
1118				previous_amount = staked_amount;
1119			}
1120		} // 1r * up to ProviderBoostHistoryLimit-1, if they staked every RewardEra.
1121		Ok(unclaimed_rewards)
1122	}
1123
1124	// Returns the block number for the end of the provided era. Assumes `era` is at least this
1125	// era or in the future
1126	pub(crate) fn block_at_end_of_era(era: RewardEra) -> BlockNumberFor<T> {
1127		let current_era_info = CurrentEraInfo::<T>::get();
1128		let era_length: BlockNumberFor<T> = T::EraLength::get().into();
1129
1130		let era_diff = if current_era_info.era_index.eq(&era) {
1131			1u32
1132		} else {
1133			era.saturating_sub(current_era_info.era_index).saturating_add(1u32)
1134		};
1135		current_era_info.started_at + era_length.mul(era_diff.into()) - 1u32.into()
1136	}
1137
1138	// Figure out the history chunk that a given era is in and pull out the total stake for that era.
1139	pub(crate) fn get_total_stake_for_past_era(
1140		reward_era: RewardEra,
1141		current_era: RewardEra,
1142	) -> Result<BalanceOf<T>, DispatchError> {
1143		// Make sure that the past era is not too old
1144		let era_range = current_era.saturating_sub(reward_era);
1145		ensure!(
1146			current_era.gt(&reward_era) && era_range.le(&T::ProviderBoostHistoryLimit::get()),
1147			Error::<T>::EraOutOfRange
1148		);
1149
1150		let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(reward_era);
1151		let reward_pool_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); // 1r
1152		let total_for_era =
1153			reward_pool_chunk.total_for_era(&reward_era).ok_or(Error::<T>::EraOutOfRange)?;
1154		Ok(*total_for_era)
1155	}
1156
1157	/// Get the index of the chunk for a given era, history limit, and chunk length
1158	/// Example with history limit of 6 and chunk length 3:
1159	/// - Arrange the chunks such that we overwrite a complete chunk only when it is not needed
1160	/// - The cycle is thus era modulo (history limit + chunk length)
1161	/// - `[0,1,2],[3,4,5],[6,7,8],[]`
1162	///
1163	/// Note Chunks stored = (History Length / Chunk size) + 1
1164	/// - The second step is which chunk to add to:
1165	/// - Divide the cycle by the chunk length and take the floor
1166	/// - Floor(5 / 3) = 1
1167	///
1168	/// Chunk Index = Floor((era % (History Length + chunk size)) / chunk size)
1169	pub(crate) fn get_chunk_index_for_era(era: RewardEra) -> u32 {
1170		let history_limit: u32 = T::ProviderBoostHistoryLimit::get();
1171		let chunk_len = T::RewardPoolChunkLength::get();
1172		let era_u32: u32 = era;
1173
1174		// Add one chunk so that we always have the full history limit in our chunks
1175		let cycle: u32 = era_u32 % history_limit.saturating_add(chunk_len);
1176		cycle.saturating_div(chunk_len)
1177	}
1178
1179	// This is where the reward pool gets updated.
1180	pub(crate) fn update_provider_boost_reward_pool(era: RewardEra, boost_total: BalanceOf<T>) {
1181		// Current era is this era
1182		let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(era);
1183		let mut new_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); // 1r
1184
1185		// If it is full we are resetting.
1186		// This assumes that the chunk length is a divisor of the history limit
1187		if new_chunk.is_full() {
1188			new_chunk = RewardPoolHistoryChunk::new();
1189		};
1190
1191		if new_chunk.try_insert(era, boost_total).is_err() {
1192			// Handle the error case that should never happen
1193			log::warn!("could not insert a new chunk into provider boost reward pool")
1194		}
1195		ProviderBoostRewardPools::<T>::set(chunk_idx, Some(new_chunk)); // 1w
1196	}
1197	fn do_claim_rewards(staker: &T::AccountId) -> Result<BalanceOf<T>, DispatchError> {
1198		let rewards = Self::list_unclaimed_rewards(staker)?;
1199		ensure!(!rewards.len().is_zero(), Error::<T>::NoRewardsEligibleToClaim);
1200		let zero_balance: BalanceOf<T> = 0u32.into();
1201		let total_to_mint: BalanceOf<T> = rewards
1202			.iter()
1203			.fold(zero_balance, |acc, reward_info| acc.saturating_add(reward_info.earned_amount));
1204		ensure!(total_to_mint.gt(&Zero::zero()), Error::<T>::NoRewardsEligibleToClaim);
1205		let _minted_unused = T::Currency::mint_into(staker, total_to_mint)?;
1206
1207		let mut new_history: ProviderBoostHistory<T> = ProviderBoostHistory::new();
1208		let last_staked_amount =
1209			rewards.last().unwrap_or(&UnclaimedRewardInfo::default()).staked_amount;
1210		let current_era = CurrentEraInfo::<T>::get().era_index;
1211		// We have already paid out for the previous era. Put one entry for the previous era as if that is when they staked,
1212		// so they will be credited for current_era.
1213		ensure!(
1214			new_history
1215				.add_era_balance(&current_era.saturating_sub(1u32), &last_staked_amount)
1216				.is_some(),
1217			Error::<T>::CollectionBoundExceeded
1218		);
1219		ProviderBoostHistories::<T>::set(staker, Some(new_history));
1220
1221		Ok(total_to_mint)
1222	}
1223}
1224
1225/// Nontransferable functions are intended for capacity spend and recharge.
1226/// Implementations of Nontransferable MUST NOT be concerned with StakingType.
1227impl<T: Config> Nontransferable for Pallet<T> {
1228	type Balance = BalanceOf<T>;
1229
1230	/// Return the remaining capacity for the Provider MSA Id
1231	fn balance(msa_id: MessageSourceId) -> Self::Balance {
1232		match CapacityLedger::<T>::get(msa_id) {
1233			Some(capacity_details) => capacity_details.remaining_capacity,
1234			None => BalanceOf::<T>::zero(),
1235		}
1236	}
1237
1238	fn replenishable_balance(msa_id: MessageSourceId) -> Self::Balance {
1239		match CapacityLedger::<T>::get(msa_id) {
1240			Some(capacity_details) => capacity_details.total_capacity_issued,
1241			None => BalanceOf::<T>::zero(),
1242		}
1243	}
1244
1245	/// Spend capacity: reduce remaining capacity by the given amount
1246	fn deduct(msa_id: MessageSourceId, amount: Self::Balance) -> Result<(), DispatchError> {
1247		let mut capacity_details =
1248			CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1249
1250		capacity_details
1251			.deduct_capacity_by_amount(amount)
1252			.map_err(|_| Error::<T>::InsufficientCapacityBalance)?;
1253
1254		Self::set_capacity_for(msa_id, capacity_details);
1255
1256		Self::deposit_event(Event::CapacityWithdrawn { msa_id, amount });
1257		Ok(())
1258	}
1259
1260	/// Increase all totals for the MSA's CapacityDetails.
1261	fn deposit(
1262		msa_id: MessageSourceId,
1263		token_amount: Self::Balance,
1264		capacity_amount: Self::Balance,
1265	) -> Result<(), DispatchError> {
1266		let mut capacity_details =
1267			CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1268		capacity_details.deposit(&token_amount, &capacity_amount);
1269		Self::set_capacity_for(msa_id, capacity_details);
1270		Ok(())
1271	}
1272}
1273
1274impl<T: Config> Replenishable for Pallet<T> {
1275	type Balance = BalanceOf<T>;
1276
1277	fn replenish_all_for(msa_id: MessageSourceId) -> Result<(), DispatchError> {
1278		let mut capacity_details =
1279			CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1280
1281		capacity_details.replenish_all(&CurrentEpoch::<T>::get());
1282
1283		Self::set_capacity_for(msa_id, capacity_details);
1284
1285		Ok(())
1286	}
1287
1288	/// Change: now calls new fn replenish_by_amount on the capacity_details,
1289	/// which does what this (actually Self::deposit) used to do
1290	/// Currently unused.
1291	fn replenish_by_amount(
1292		msa_id: MessageSourceId,
1293		amount: Self::Balance,
1294	) -> Result<(), DispatchError> {
1295		let mut capacity_details =
1296			CapacityLedger::<T>::get(msa_id).ok_or(Error::<T>::TargetCapacityNotFound)?;
1297		capacity_details.replenish_by_amount(amount, &CurrentEpoch::<T>::get());
1298		Ok(())
1299	}
1300
1301	fn can_replenish(msa_id: MessageSourceId) -> bool {
1302		if let Some(capacity_details) = CapacityLedger::<T>::get(msa_id) {
1303			return capacity_details.can_replenish(CurrentEpoch::<T>::get());
1304		}
1305		false
1306	}
1307}
1308
1309impl<T: Config> ProviderBoostRewardsProvider<T> for Pallet<T> {
1310	type Balance = BalanceOf<T>;
1311
1312	fn reward_pool_size(_total_staked: Self::Balance) -> Self::Balance {
1313		T::RewardPoolPerEra::get()
1314	}
1315
1316	/// Calculate the reward for a single era.  We don't care about the era number,
1317	/// just the values.
1318	fn era_staking_reward(
1319		era_amount_staked: Self::Balance,
1320		era_total_staked: Self::Balance,
1321		era_reward_pool_size: Self::Balance,
1322	) -> Self::Balance {
1323		let capped_reward = T::RewardPercentCap::get().mul(era_amount_staked);
1324		let proportional_reward = era_reward_pool_size
1325			.saturating_mul(era_amount_staked)
1326			.checked_div(&era_total_staked)
1327			.unwrap_or_else(Zero::zero);
1328		proportional_reward.min(capped_reward)
1329	}
1330
1331	/// How much, as a percentage of staked token, to boost a targeted Provider when staking.
1332	fn capacity_boost(amount: Self::Balance) -> Self::Balance {
1333		Perbill::from_percent(STAKED_PERCENTAGE_TO_BOOST).mul(amount)
1334	}
1335}