1use super::*;
3use common_primitives::capacity::RewardEra;
4use frame_support::{
5 pallet_prelude::PhantomData, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
6};
7use parity_scale_codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
8use scale_info::TypeInfo;
9use sp_runtime::{
10 traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Get, Saturating, Zero},
11 BoundedBTreeMap, RuntimeDebug,
12};
13#[cfg(any(feature = "runtime-benchmarks", test))]
14extern crate alloc;
15#[cfg(any(feature = "runtime-benchmarks", test))]
16use alloc::vec::Vec;
17
18pub const STAKED_PERCENTAGE_TO_BOOST: u32 = 50;
21
22#[derive(
23 Clone, Copy, Debug, Decode, Encode, TypeInfo, Eq, MaxEncodedLen, PartialEq, PartialOrd,
24)]
25pub enum StakingType {
27 MaximumCapacity,
29 ProviderBoost,
32}
33
34#[derive(
36 TypeInfo, RuntimeDebugNoBound, PartialEqNoBound, EqNoBound, Clone, Decode, Encode, MaxEncodedLen,
37)]
38#[scale_info(skip_type_params(T))]
39pub struct StakingDetails<T: Config> {
40 pub active: BalanceOf<T>,
42 pub staking_type: StakingType,
44}
45
46#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
48pub struct UnlockChunk<Balance, EpochNumber> {
49 pub value: Balance,
51 pub thaw_at: EpochNumber,
53}
54
55impl<T: Config> StakingDetails<T> {
56 pub fn deposit(&mut self, amount: BalanceOf<T>) -> Option<()> {
58 self.active = amount.checked_add(&self.active)?;
59 Some(())
60 }
61
62 pub fn withdraw(&mut self, amount: BalanceOf<T>) -> Result<BalanceOf<T>, DispatchError> {
64 let current_active = self.active;
65
66 let mut new_active = self.active.saturating_sub(amount);
67 let mut actual_unstaked: BalanceOf<T> = amount;
68
69 if new_active.le(&T::MinimumStakingAmount::get()) {
70 actual_unstaked = current_active;
71 new_active = Zero::zero();
72 }
73
74 self.active = new_active;
75 Ok(actual_unstaked)
76 }
77}
78
79impl<T: Config> Default for StakingDetails<T> {
80 fn default() -> Self {
81 Self { active: Zero::zero(), staking_type: StakingType::MaximumCapacity }
82 }
83}
84
85#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
88pub struct StakingTargetDetails<Balance>
89where
90 Balance: Default + Saturating + Copy + CheckedAdd + CheckedSub,
91{
92 pub amount: Balance,
94 pub capacity: Balance,
96}
97
98impl<Balance: Default + Saturating + Copy + CheckedAdd + CheckedSub + PartialOrd>
99 StakingTargetDetails<Balance>
100{
101 pub fn deposit(&mut self, amount: Balance, capacity: Balance) -> Option<()> {
103 self.amount = amount.checked_add(&self.amount)?;
104 self.capacity = capacity.checked_add(&self.capacity)?;
105 Some(())
106 }
107
108 pub fn withdraw(
112 &mut self,
113 amount: Balance,
114 capacity: Balance,
115 minimum: Balance,
116 ) -> (Balance, Balance) {
117 let entire_amount = self.amount;
118 let entire_capacity = self.capacity;
119 self.amount = self.amount.saturating_sub(amount);
120 if self.amount.lt(&minimum) {
121 *self = Self::default();
122 return (entire_amount, entire_capacity);
123 } else {
124 self.capacity = self.capacity.saturating_sub(capacity);
125 }
126 (amount, capacity)
127 }
128}
129
130#[derive(PartialEq, Eq, Clone, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
132pub struct CapacityDetails<Balance, EpochNumber> {
133 pub remaining_capacity: Balance,
135 pub total_tokens_staked: Balance,
137 pub total_capacity_issued: Balance,
139 pub last_replenished_epoch: EpochNumber,
141}
142
143impl<Balance, EpochNumber> CapacityDetails<Balance, EpochNumber>
144where
145 Balance: Saturating + Copy + CheckedAdd + CheckedSub,
146 EpochNumber: Clone + PartialOrd + PartialEq,
147{
148 pub fn deposit(&mut self, amount: &Balance, capacity: &Balance) -> Option<()> {
151 self.total_tokens_staked = amount.checked_add(&self.total_tokens_staked)?;
152 self.remaining_capacity = capacity.checked_add(&self.remaining_capacity)?;
153 self.total_capacity_issued = capacity.checked_add(&self.total_capacity_issued)?;
154
155 Some(())
161 }
162
163 pub fn can_replenish(&self, current_epoch: EpochNumber) -> bool {
165 self.last_replenished_epoch.lt(¤t_epoch)
166 }
167
168 pub fn replenish_all(&mut self, current_epoch: &EpochNumber) {
171 self.remaining_capacity = self.total_capacity_issued;
172 self.last_replenished_epoch = current_epoch.clone();
173 }
174
175 pub fn replenish_by_amount(&mut self, amount: Balance, current_epoch: &EpochNumber) {
178 self.remaining_capacity = amount.saturating_add(self.remaining_capacity);
179 self.last_replenished_epoch = current_epoch.clone();
180 }
181
182 pub fn deduct_capacity_by_amount(&mut self, amount: Balance) -> Result<(), ArithmeticError> {
185 let new_remaining =
186 self.remaining_capacity.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?;
187 self.remaining_capacity = new_remaining;
188 Ok(())
189 }
190
191 pub fn withdraw(&mut self, capacity_deduction: Balance, tokens_staked_deduction: Balance) {
194 self.total_tokens_staked = self.total_tokens_staked.saturating_sub(tokens_staked_deduction);
195 self.total_capacity_issued = self.total_capacity_issued.saturating_sub(capacity_deduction);
196 self.remaining_capacity = self.remaining_capacity.saturating_sub(capacity_deduction);
197 }
198}
199
200#[derive(
203 PartialEq, Eq, Clone, Default, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
204)]
205pub struct EpochInfo<BlockNumber> {
206 pub epoch_start: BlockNumber,
208}
209
210pub type UnlockChunkList<T> = BoundedVec<
212 UnlockChunk<BalanceOf<T>, <T as Config>::EpochNumber>,
213 <T as Config>::MaxUnlockingChunks,
214>;
215
216pub fn unlock_chunks_total<T: Config>(unlock_chunks: &UnlockChunkList<T>) -> BalanceOf<T> {
218 unlock_chunks
219 .iter()
220 .fold(Zero::zero(), |acc: BalanceOf<T>, chunk| acc.saturating_add(chunk.value))
221}
222
223pub fn unlock_chunks_reap_thawed<T: Config>(
227 unlock_chunks: &mut UnlockChunkList<T>,
228 current_epoch: <T>::EpochNumber,
229) -> BalanceOf<T> {
230 let mut total_reaped: BalanceOf<T> = 0u32.into();
231 unlock_chunks.retain(|chunk| {
232 if current_epoch.ge(&chunk.thaw_at) {
233 total_reaped = total_reaped.saturating_add(chunk.value);
234 false
235 } else {
236 true
237 }
238 });
239 total_reaped
240}
241#[cfg(any(feature = "runtime-benchmarks", test))]
242#[allow(clippy::unwrap_used)]
243pub fn unlock_chunks_from_vec<T: Config>(chunks: &[(u32, u32)]) -> UnlockChunkList<T> {
247 let result: Vec<UnlockChunk<BalanceOf<T>, <T>::EpochNumber>> = chunks
248 .iter()
249 .map(|chunk| UnlockChunk { value: chunk.0.into(), thaw_at: chunk.1.into() })
250 .collect();
251 BoundedVec::try_from(result).unwrap()
253}
254
255#[derive(
257 PartialEq,
258 Eq,
259 Clone,
260 Copy,
261 Default,
262 PartialOrd,
263 Encode,
264 Decode,
265 RuntimeDebug,
266 TypeInfo,
267 MaxEncodedLen,
268)]
269pub struct RewardEraInfo<RewardEra, BlockNumber>
270where
271 RewardEra: AtLeast32BitUnsigned + EncodeLike,
272 BlockNumber: AtLeast32BitUnsigned + EncodeLike,
273{
274 pub era_index: RewardEra,
276 pub started_at: BlockNumber,
278}
279
280#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
285#[scale_info(skip_type_params(T))]
286pub struct RewardPoolHistoryChunk<T: Config>(
287 BoundedBTreeMap<RewardEra, BalanceOf<T>, T::RewardPoolChunkLength>,
288);
289impl<T: Config> Default for RewardPoolHistoryChunk<T> {
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295impl<T: Config> Clone for RewardPoolHistoryChunk<T> {
296 fn clone(&self) -> Self {
297 Self(self.0.clone())
298 }
299}
300
301impl<T: Config> RewardPoolHistoryChunk<T> {
302 pub fn new() -> Self {
304 RewardPoolHistoryChunk(BoundedBTreeMap::new())
305 }
306
307 pub fn total_for_era(&self, reward_era: &RewardEra) -> Option<&BalanceOf<T>> {
310 self.0.get(reward_era)
311 }
312
313 #[cfg(test)]
315 pub fn era_range(&self) -> (RewardEra, RewardEra) {
316 let zero_reward_era: RewardEra = Zero::zero();
317 let zero_balance: BalanceOf<T> = Zero::zero();
318 let (first, _vf) = self.0.first_key_value().unwrap_or((&zero_reward_era, &zero_balance));
319 let (last, _vl) = self.0.last_key_value().unwrap_or((&zero_reward_era, &zero_balance));
320 (*first, *last)
321 }
322
323 pub fn try_insert(
325 &mut self,
326 reward_era: RewardEra,
327 total: BalanceOf<T>,
328 ) -> Result<Option<BalanceOf<T>>, (RewardEra, BalanceOf<T>)> {
329 self.0.try_insert(reward_era, total)
330 }
331
332 #[cfg(test)]
334 pub fn earliest_era(&self) -> Option<&RewardEra> {
335 if let Some((first_era, _first_total)) = self.0.first_key_value() {
336 return Some(first_era);
337 }
338 None
339 }
340
341 pub fn is_full(&self) -> bool {
343 self.0.len().eq(&(T::RewardPoolChunkLength::get() as usize))
344 }
345}
346
347#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
349#[scale_info(skip_type_params(T))]
350pub struct ProviderBoostHistory<T: Config>(
351 BoundedBTreeMap<RewardEra, BalanceOf<T>, T::ProviderBoostHistoryLimit>,
352);
353
354impl<T: Config> Default for ProviderBoostHistory<T> {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360impl<T: Config> ProviderBoostHistory<T> {
361 pub fn new() -> Self {
363 ProviderBoostHistory(BoundedBTreeMap::new())
364 }
365
366 pub fn add_era_balance(
370 &mut self,
371 reward_era: &RewardEra,
372 add_amount: &BalanceOf<T>,
373 ) -> Option<usize> {
374 if let Some(entry) = self.0.get_mut(reward_era) {
375 *entry = entry.saturating_add(*add_amount);
377 } else {
378 self.remove_oldest_entry_if_full(); let current_staking_amount = self.get_last_staking_amount();
381 if self
382 .0
383 .try_insert(*reward_era, current_staking_amount.saturating_add(*add_amount))
384 .is_err()
385 {
386 return None;
387 };
388 }
389
390 Some(self.count())
391 }
392
393 pub fn subtract_era_balance(
398 &mut self,
399 reward_era: &RewardEra,
400 subtract_amount: &BalanceOf<T>,
401 ) -> Option<usize> {
402 if self.count().is_zero() {
403 return None;
404 };
405
406 let current_staking_amount = self.get_last_staking_amount();
407 if current_staking_amount.eq(subtract_amount) && self.count().eq(&1usize) {
408 return Some(0usize);
412 }
413
414 if let Some(entry) = self.0.get_mut(reward_era) {
415 *entry = entry.saturating_sub(*subtract_amount);
416 } else {
417 self.remove_oldest_entry_if_full();
418 if self
419 .0
420 .try_insert(*reward_era, current_staking_amount.saturating_sub(*subtract_amount))
421 .is_err()
422 {
423 return None;
424 }
425 }
426 Some(self.count())
427 }
428
429 #[cfg(test)]
431 pub(crate) fn get_entry_for_era(&self, reward_era: &RewardEra) -> Option<&BalanceOf<T>> {
432 self.0.get(reward_era)
433 }
434
435 pub(crate) fn get_amount_staked_for_era(&self, reward_era: &RewardEra) -> BalanceOf<T> {
441 let bmap_iter = self.0.iter();
443 let mut eligible_amount: BalanceOf<T> = Zero::zero();
444 for (era, balance) in bmap_iter {
445 if era.eq(reward_era) {
446 return *balance;
447 }
448 else if era.gt(reward_era) {
450 return eligible_amount;
451 } eligible_amount = *balance;
453 }
454 eligible_amount
455 }
456
457 pub fn count(&self) -> usize {
459 self.0.len()
460 }
461
462 fn remove_oldest_entry_if_full(&mut self) {
463 if self.is_full() {
464 if let Some((earliest_key, _earliest_val)) = self.0.first_key_value() {
466 self.0.remove(&earliest_key.clone());
467 }
468 }
469 }
470
471 fn get_last_staking_amount(&self) -> BalanceOf<T> {
472 if let Some((_last_key, last_value)) = self.0.last_key_value() {
474 return *last_value;
475 };
476 Zero::zero()
477 }
478
479 pub fn get_earliest_reward_era(&self) -> Option<&RewardEra> {
481 self.0.first_key_value().map(|(key, _value)| key)
482 }
483
484 fn is_full(&self) -> bool {
485 self.count().eq(&(T::ProviderBoostHistoryLimit::get() as usize))
486 }
487}
488
489#[derive(Debug, TypeInfo, PartialEqNoBound, EqNoBound, Clone, Decode, Encode, MaxEncodedLen)]
491#[scale_info(skip_type_params(T))]
492pub struct RetargetInfo<T: Config> {
493 pub retarget_count: u32,
495 pub last_retarget_at: RewardEra,
497 _marker: PhantomData<T>,
498}
499
500impl<T: Config> Default for RetargetInfo<T> {
501 fn default() -> Self {
502 Self { retarget_count: 0u32, last_retarget_at: Zero::zero(), _marker: Default::default() }
503 }
504}
505
506impl<T: Config> RetargetInfo<T> {
507 pub fn new(retarget_count: u32, last_retarget_at: RewardEra) -> Self {
509 Self { retarget_count, last_retarget_at, _marker: Default::default() }
510 }
511 pub fn update(&mut self, current_era: RewardEra) -> Option<()> {
514 let max_retargets = T::MaxRetargetsPerRewardEra::get();
515 if self.retarget_count.ge(&max_retargets) && self.last_retarget_at.eq(¤t_era) {
516 return None;
517 }
518 if self.last_retarget_at.lt(¤t_era) {
519 self.last_retarget_at = current_era;
520 self.retarget_count = 1;
521 } else {
522 self.retarget_count = self.retarget_count.saturating_add(1u32);
523 }
524 Some(())
525 }
526}
527
528pub trait ProviderBoostRewardsProvider<T: Config> {
530 type Balance;
532
533 fn reward_pool_size(total_staked: BalanceOf<T>) -> BalanceOf<T>;
535
536 fn era_staking_reward(
539 era_amount_staked: BalanceOf<T>, era_total_staked: BalanceOf<T>, era_reward_pool_size: BalanceOf<T>, ) -> BalanceOf<T>;
543
544 fn capacity_boost(amount: BalanceOf<T>) -> BalanceOf<T>;
547}