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