1#![cfg_attr(not(feature = "std"), no_std)]
353
354extern crate alloc;
355
356use adapter::{Member, Pool, StakeStrategy};
357use alloc::{collections::btree_map::BTreeMap, vec::Vec};
358use codec::Codec;
359use core::{fmt::Debug, ops::Div};
360use frame_support::{
361 defensive, defensive_assert, ensure,
362 pallet_prelude::{MaxEncodedLen, *},
363 storage::bounded_btree_map::BoundedBTreeMap,
364 traits::{
365 fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze},
366 tokens::{Fortitude, Preservation},
367 Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, Get,
368 },
369 DefaultNoBound, PalletError,
370};
371use frame_system::pallet_prelude::BlockNumberFor;
372use scale_info::TypeInfo;
373use sp_core::U256;
374use sp_runtime::{
375 traits::{
376 AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup,
377 Zero,
378 },
379 FixedPointNumber, Perbill,
380};
381use sp_staking::{EraIndex, StakingInterface};
382
383#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
384use sp_runtime::TryRuntimeError;
385
386pub const LOG_TARGET: &str = "runtime::nomination-pools";
388#[macro_export]
390macro_rules! log {
391 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
392 log::$level!(
393 target: $crate::LOG_TARGET,
394 concat!("[{:?}] 🏊♂️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
395 )
396 };
397}
398
399#[cfg(any(test, feature = "fuzzing"))]
400pub mod mock;
401#[cfg(test)]
402mod tests;
403
404pub mod adapter;
405pub mod migration;
406pub mod weights;
407
408pub use pallet::*;
409pub use weights::WeightInfo;
410
411pub type BalanceOf<T> =
413 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
414pub type PoolId = u32;
416
417type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
418
419pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1;
420
421#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)]
423pub enum ConfigOp<T: Codec + Debug> {
424 Noop,
426 Set(T),
428 Remove,
430}
431
432pub enum BondType {
434 Create,
436 Extra,
438}
439
440#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
442pub enum BondExtra<Balance> {
443 FreeBalance(Balance),
445 Rewards,
447}
448
449#[derive(Encode, Decode)]
451enum AccountType {
452 Bonded,
453 Reward,
454}
455
456#[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
458pub enum ClaimPermission {
459 Permissioned,
461 PermissionlessCompound,
463 PermissionlessWithdraw,
465 PermissionlessAll,
467}
468
469impl Default for ClaimPermission {
470 fn default() -> Self {
471 Self::PermissionlessWithdraw
472 }
473}
474
475impl ClaimPermission {
476 fn can_bond_extra(&self) -> bool {
479 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
480 }
481
482 fn can_claim_payout(&self) -> bool {
485 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
486 }
487}
488
489#[derive(
491 Encode,
492 Decode,
493 MaxEncodedLen,
494 TypeInfo,
495 RuntimeDebugNoBound,
496 CloneNoBound,
497 frame_support::PartialEqNoBound,
498)]
499#[cfg_attr(feature = "std", derive(DefaultNoBound))]
500#[scale_info(skip_type_params(T))]
501pub struct PoolMember<T: Config> {
502 pub pool_id: PoolId,
504 pub points: BalanceOf<T>,
507 pub last_recorded_reward_counter: T::RewardCounter,
509 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
512}
513
514impl<T: Config> PoolMember<T> {
515 fn pending_rewards(
517 &self,
518 current_reward_counter: T::RewardCounter,
519 ) -> Result<BalanceOf<T>, Error<T>> {
520 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
538 .checked_mul_int(self.active_points())
539 .ok_or(Error::<T>::OverflowRisk)
540 }
541
542 fn active_balance(&self) -> BalanceOf<T> {
547 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
548 pool.points_to_balance(self.points)
549 } else {
550 Zero::zero()
551 }
552 }
553
554 pub fn total_balance(&self) -> BalanceOf<T> {
560 let pool = match BondedPool::<T>::get(self.pool_id) {
561 Some(pool) => pool,
562 None => {
563 defensive!("pool should exist; qed");
565 return Zero::zero();
566 },
567 };
568
569 let active_balance = pool.points_to_balance(self.active_points());
570
571 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
572 Some(sub_pools) => sub_pools,
573 None => return active_balance,
574 };
575
576 let unbonding_balance = self.unbonding_eras.iter().fold(
577 BalanceOf::<T>::zero(),
578 |accumulator, (era, unlocked_points)| {
579 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
582 accumulator + (era_pool.point_to_balance(*unlocked_points))
583 },
584 );
585
586 active_balance + unbonding_balance
587 }
588
589 fn total_points(&self) -> BalanceOf<T> {
591 self.active_points().saturating_add(self.unbonding_points())
592 }
593
594 fn active_points(&self) -> BalanceOf<T> {
596 self.points
597 }
598
599 fn unbonding_points(&self) -> BalanceOf<T> {
601 self.unbonding_eras
602 .as_ref()
603 .iter()
604 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
605 }
606
607 fn try_unbond(
615 &mut self,
616 points_dissolved: BalanceOf<T>,
617 points_issued: BalanceOf<T>,
618 unbonding_era: EraIndex,
619 ) -> Result<(), Error<T>> {
620 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
621 match self.unbonding_eras.get_mut(&unbonding_era) {
622 Some(already_unbonding_points) =>
623 *already_unbonding_points =
624 already_unbonding_points.saturating_add(points_issued),
625 None => self
626 .unbonding_eras
627 .try_insert(unbonding_era, points_issued)
628 .map(|old| {
629 if old.is_some() {
630 defensive!("value checked to not exist in the map; qed");
631 }
632 })
633 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
634 }
635 self.points = new_points;
636 Ok(())
637 } else {
638 Err(Error::<T>::MinimumBondNotMet)
639 }
640 }
641
642 fn withdraw_unlocked(
649 &mut self,
650 current_era: EraIndex,
651 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
652 let mut removed_points =
654 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
655 self.unbonding_eras.retain(|e, p| {
656 if *e > current_era {
657 true
658 } else {
659 removed_points
660 .try_insert(*e, *p)
661 .expect("source map is bounded, this is a subset, will be bounded; qed");
662 false
663 }
664 });
665 removed_points
666 }
667}
668
669#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone, Copy)]
671pub enum PoolState {
672 Open,
674 Blocked,
676 Destroying,
681}
682
683#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)]
689pub struct PoolRoles<AccountId> {
690 pub depositor: AccountId,
693 pub root: Option<AccountId>,
696 pub nominator: Option<AccountId>,
698 pub bouncer: Option<AccountId>,
700}
701
702#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
704pub enum CommissionClaimPermission<AccountId> {
705 Permissionless,
706 Account(AccountId),
707}
708
709#[derive(
722 Encode, Decode, DefaultNoBound, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Copy, Clone,
723)]
724#[codec(mel_bound(T: Config))]
725#[scale_info(skip_type_params(T))]
726pub struct Commission<T: Config> {
727 pub current: Option<(Perbill, T::AccountId)>,
729 pub max: Option<Perbill>,
732 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
735 pub throttle_from: Option<BlockNumberFor<T>>,
738 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
741}
742
743impl<T: Config> Commission<T> {
744 fn throttling(&self, to: &Perbill) -> bool {
751 if let Some(t) = self.change_rate.as_ref() {
752 let commission_as_percent =
753 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
754
755 if *to <= commission_as_percent {
757 return false
758 }
759 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
763 return true
764 }
765
766 return self.throttle_from.map_or_else(
771 || {
772 defensive!("throttle_from should exist if change_rate is set");
773 true
774 },
775 |f| {
776 if t.min_delay == Zero::zero() {
778 false
779 } else {
780 let blocks_surpassed =
782 <frame_system::Pallet<T>>::block_number().saturating_sub(f);
783 blocks_surpassed < t.min_delay
784 }
785 },
786 )
787 }
788 false
789 }
790
791 fn current(&self) -> Perbill {
794 self.current
795 .as_ref()
796 .map_or(Perbill::zero(), |(c, _)| *c)
797 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
798 }
799
800 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
806 self.current = match current {
807 None => None,
808 Some((commission, payee)) => {
809 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
810 ensure!(
811 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
812 Error::<T>::CommissionExceedsGlobalMaximum
813 );
814 ensure!(
815 self.max.map_or(true, |m| commission <= &m),
816 Error::<T>::CommissionExceedsMaximum
817 );
818 if commission.is_zero() {
819 None
820 } else {
821 Some((*commission, payee.clone()))
822 }
823 },
824 };
825 self.register_update();
826 Ok(())
827 }
828
829 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
838 ensure!(
839 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
840 Error::<T>::CommissionExceedsGlobalMaximum
841 );
842 if let Some(old) = self.max.as_mut() {
843 if new_max > *old {
844 return Err(Error::<T>::MaxCommissionRestricted.into())
845 }
846 *old = new_max;
847 } else {
848 self.max = Some(new_max)
849 };
850 let updated_current = self
851 .current
852 .as_mut()
853 .map(|(c, _)| {
854 let u = *c > new_max;
855 *c = (*c).min(new_max);
856 u
857 })
858 .unwrap_or(false);
859
860 if updated_current {
861 if let Some((_, payee)) = self.current.as_ref() {
862 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
863 pool_id,
864 current: Some((new_max, payee.clone())),
865 });
866 }
867 self.register_update();
868 }
869 Ok(())
870 }
871
872 fn try_update_change_rate(
881 &mut self,
882 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
883 ) -> DispatchResult {
884 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
885
886 if self.change_rate.is_none() {
887 self.register_update();
888 }
889 self.change_rate = Some(change_rate);
890 Ok(())
891 }
892
893 fn register_update(&mut self) {
895 self.throttle_from = Some(<frame_system::Pallet<T>>::block_number());
896 }
897
898 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
903 self.change_rate
904 .as_ref()
905 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
906 .unwrap_or(false)
907 }
908}
909
910#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone)]
918pub struct CommissionChangeRate<BlockNumber> {
919 pub max_increase: Perbill,
921 pub min_delay: BlockNumber,
923}
924
925#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)]
927#[codec(mel_bound(T: Config))]
928#[scale_info(skip_type_params(T))]
929pub struct BondedPoolInner<T: Config> {
930 pub commission: Commission<T>,
932 pub member_counter: u32,
934 pub points: BalanceOf<T>,
936 pub roles: PoolRoles<T::AccountId>,
938 pub state: PoolState,
940}
941
942#[derive(RuntimeDebugNoBound)]
947#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
948pub struct BondedPool<T: Config> {
949 id: PoolId,
951 inner: BondedPoolInner<T>,
953}
954
955impl<T: Config> core::ops::Deref for BondedPool<T> {
956 type Target = BondedPoolInner<T>;
957 fn deref(&self) -> &Self::Target {
958 &self.inner
959 }
960}
961
962impl<T: Config> core::ops::DerefMut for BondedPool<T> {
963 fn deref_mut(&mut self) -> &mut Self::Target {
964 &mut self.inner
965 }
966}
967
968impl<T: Config> BondedPool<T> {
969 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
971 Self {
972 id,
973 inner: BondedPoolInner {
974 commission: Commission::default(),
975 member_counter: Zero::zero(),
976 points: Zero::zero(),
977 roles,
978 state: PoolState::Open,
979 },
980 }
981 }
982
983 pub fn get(id: PoolId) -> Option<Self> {
985 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
986 }
987
988 fn bonded_account(&self) -> T::AccountId {
990 Pallet::<T>::generate_bonded_account(self.id)
991 }
992
993 fn reward_account(&self) -> T::AccountId {
995 Pallet::<T>::generate_reward_account(self.id)
996 }
997
998 fn put(self) {
1000 BondedPools::<T>::insert(self.id, self.inner);
1001 }
1002
1003 fn remove(self) {
1005 BondedPools::<T>::remove(self.id);
1006 }
1007
1008 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1012 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1013 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1014 }
1015
1016 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1020 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1021 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1022 }
1023
1024 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1026 let points_to_issue = self.balance_to_point(new_funds);
1027 self.points = self.points.saturating_add(points_to_issue);
1028 points_to_issue
1029 }
1030
1031 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1038 let balance = self.points_to_balance(points);
1041 self.points = self.points.saturating_sub(points);
1042 balance
1043 }
1044
1045 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1048 ensure!(
1049 MaxPoolMembersPerPool::<T>::get()
1050 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1051 Error::<T>::MaxPoolMembers
1052 );
1053 ensure!(
1054 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1055 Error::<T>::MaxPoolMembers
1056 );
1057 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1058 Ok(())
1059 }
1060
1061 fn dec_members(mut self) -> Self {
1063 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1064 self
1065 }
1066
1067 fn is_root(&self, who: &T::AccountId) -> bool {
1068 self.roles.root.as_ref().map_or(false, |root| root == who)
1069 }
1070
1071 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1072 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1073 }
1074
1075 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1076 self.is_root(who)
1077 }
1078
1079 fn can_nominate(&self, who: &T::AccountId) -> bool {
1080 self.is_root(who) ||
1081 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1082 }
1083
1084 fn can_kick(&self, who: &T::AccountId) -> bool {
1085 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1086 }
1087
1088 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1089 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1090 }
1091
1092 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1093 self.is_root(who) || self.is_bouncer(who)
1094 }
1095
1096 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1097 self.is_root(who)
1098 }
1099
1100 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1101 if let Some(permission) = self.commission.claim_permission.as_ref() {
1102 match permission {
1103 CommissionClaimPermission::Permissionless => true,
1104 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1105 }
1106 } else {
1107 self.is_root(who)
1108 }
1109 }
1110
1111 fn is_destroying(&self) -> bool {
1112 matches!(self.state, PoolState::Destroying)
1113 }
1114
1115 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1116 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1123 }
1124
1125 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1128 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1129
1130 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1131 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1132
1133 let points_to_balance_ratio_floor = self
1134 .points
1135 .div(bonded_balance);
1137
1138 let max_points_to_balance = T::MaxPointsToBalance::get();
1139
1140 ensure!(
1144 points_to_balance_ratio_floor < max_points_to_balance.into(),
1145 Error::<T>::OverflowRisk
1146 );
1147
1148 Ok(())
1152 }
1153
1154 fn ok_to_join(&self) -> Result<(), DispatchError> {
1156 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1157 self.ok_to_be_open()?;
1158 Ok(())
1159 }
1160
1161 fn ok_to_unbond_with(
1162 &self,
1163 caller: &T::AccountId,
1164 target_account: &T::AccountId,
1165 target_member: &PoolMember<T>,
1166 unbonding_points: BalanceOf<T>,
1167 ) -> Result<(), DispatchError> {
1168 let is_permissioned = caller == target_account;
1169 let is_depositor = *target_account == self.roles.depositor;
1170 let is_full_unbond = unbonding_points == target_member.active_points();
1171
1172 let balance_after_unbond = {
1173 let new_depositor_points =
1174 target_member.active_points().saturating_sub(unbonding_points);
1175 let mut target_member_after_unbond = (*target_member).clone();
1176 target_member_after_unbond.points = new_depositor_points;
1177 target_member_after_unbond.active_balance()
1178 };
1179
1180 ensure!(
1182 is_permissioned || is_full_unbond,
1183 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1184 );
1185
1186 ensure!(
1188 is_full_unbond ||
1189 balance_after_unbond >=
1190 if is_depositor {
1191 Pallet::<T>::depositor_min_bond()
1192 } else {
1193 MinJoinBond::<T>::get()
1194 },
1195 Error::<T>::MinimumBondNotMet
1196 );
1197
1198 match (is_permissioned, is_depositor) {
1200 (true, false) => (),
1201 (true, true) => {
1202 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1205 } else {
1207 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1209 }
1210 },
1211 (false, false) => {
1212 debug_assert!(is_full_unbond);
1215 ensure!(
1216 self.can_kick(caller) || self.is_destroying(),
1217 Error::<T>::NotKickerOrDestroying
1218 )
1219 },
1220 (false, true) => {
1221 return Err(Error::<T>::DoesNotHavePermission.into())
1223 },
1224 };
1225
1226 Ok(())
1227 }
1228
1229 fn ok_to_withdraw_unbonded_with(
1233 &self,
1234 caller: &T::AccountId,
1235 target_account: &T::AccountId,
1236 ) -> Result<(), DispatchError> {
1237 let is_permissioned = caller == target_account;
1239 ensure!(
1240 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1241 Error::<T>::NotKickerOrDestroying
1242 );
1243 Ok(())
1244 }
1245
1246 fn try_bond_funds(
1254 &mut self,
1255 who: &T::AccountId,
1256 amount: BalanceOf<T>,
1257 ty: BondType,
1258 ) -> Result<BalanceOf<T>, DispatchError> {
1259 let points_issued = self.issue(amount);
1262
1263 T::StakeAdapter::pledge_bond(
1264 Member::from(who.clone()),
1265 Pool::from(self.bonded_account()),
1266 &self.reward_account(),
1267 amount,
1268 ty,
1269 )?;
1270 TotalValueLocked::<T>::mutate(|tvl| {
1271 tvl.saturating_accrue(amount);
1272 });
1273
1274 Ok(points_issued)
1275 }
1276
1277 fn set_state(&mut self, state: PoolState) {
1280 if self.state != state {
1281 self.state = state;
1282 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1283 pool_id: self.id,
1284 new_state: state,
1285 });
1286 };
1287 }
1288}
1289
1290#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)]
1296#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))]
1297#[codec(mel_bound(T: Config))]
1298#[scale_info(skip_type_params(T))]
1299pub struct RewardPool<T: Config> {
1300 last_recorded_reward_counter: T::RewardCounter,
1305 last_recorded_total_payouts: BalanceOf<T>,
1311 total_rewards_claimed: BalanceOf<T>,
1313 total_commission_pending: BalanceOf<T>,
1315 total_commission_claimed: BalanceOf<T>,
1317}
1318
1319impl<T: Config> RewardPool<T> {
1320 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1322 self.last_recorded_reward_counter
1323 }
1324
1325 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1327 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1328 }
1329
1330 fn update_records(
1337 &mut self,
1338 id: PoolId,
1339 bonded_points: BalanceOf<T>,
1340 commission: Perbill,
1341 ) -> Result<(), Error<T>> {
1342 let balance = Self::current_balance(id);
1343
1344 let (current_reward_counter, new_pending_commission) =
1345 self.current_reward_counter(id, bonded_points, commission)?;
1346
1347 self.last_recorded_reward_counter = current_reward_counter;
1351
1352 self.total_commission_pending =
1355 self.total_commission_pending.saturating_add(new_pending_commission);
1356
1357 let last_recorded_total_payouts = balance
1361 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1362 .ok_or(Error::<T>::OverflowRisk)?;
1363
1364 self.last_recorded_total_payouts =
1371 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1372
1373 Ok(())
1374 }
1375
1376 fn current_reward_counter(
1379 &self,
1380 id: PoolId,
1381 bonded_points: BalanceOf<T>,
1382 commission: Perbill,
1383 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1384 let balance = Self::current_balance(id);
1385
1386 let current_payout_balance = balance
1391 .saturating_add(self.total_rewards_claimed)
1392 .saturating_add(self.total_commission_claimed)
1393 .saturating_sub(self.last_recorded_total_payouts);
1394
1395 let new_pending_commission = commission * current_payout_balance;
1398 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1399
1400 let current_reward_counter =
1435 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1436 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1437 .ok_or(Error::<T>::OverflowRisk)?;
1438
1439 Ok((current_reward_counter, new_pending_commission))
1440 }
1441
1442 fn current_balance(id: PoolId) -> BalanceOf<T> {
1446 T::Currency::reducible_balance(
1447 &Pallet::<T>::generate_reward_account(id),
1448 Preservation::Expendable,
1449 Fortitude::Polite,
1450 )
1451 }
1452}
1453
1454#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)]
1456#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))]
1457#[codec(mel_bound(T: Config))]
1458#[scale_info(skip_type_params(T))]
1459pub struct UnbondPool<T: Config> {
1460 points: BalanceOf<T>,
1462 balance: BalanceOf<T>,
1464}
1465
1466impl<T: Config> UnbondPool<T> {
1467 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1468 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1469 }
1470
1471 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1472 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1473 }
1474
1475 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1479 let new_points = self.balance_to_point(new_funds);
1480 self.points = self.points.saturating_add(new_points);
1481 self.balance = self.balance.saturating_add(new_funds);
1482 new_points
1483 }
1484
1485 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1490 let balance_to_unbond = self.point_to_balance(points);
1491 self.points = self.points.saturating_sub(points);
1492 self.balance = self.balance.saturating_sub(balance_to_unbond);
1493
1494 balance_to_unbond
1495 }
1496}
1497
1498#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)]
1499#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1500#[codec(mel_bound(T: Config))]
1501#[scale_info(skip_type_params(T))]
1502pub struct SubPools<T: Config> {
1503 no_era: UnbondPool<T>,
1507 with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1509}
1510
1511impl<T: Config> SubPools<T> {
1512 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1517 if let Some(newest_era_to_remove) =
1521 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1522 {
1523 self.with_era.retain(|k, v| {
1524 if *k > newest_era_to_remove {
1525 true
1527 } else {
1528 self.no_era.points = self.no_era.points.saturating_add(v.points);
1530 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1531 false
1532 }
1533 });
1534 }
1535
1536 self
1537 }
1538
1539 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1541 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1542 self.no_era.balance.saturating_add(
1543 self.with_era
1544 .values()
1545 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1546 )
1547 }
1548}
1549
1550pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1554
1555impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1556 fn get() -> u32 {
1557 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1561 }
1562}
1563
1564#[frame_support::pallet]
1565pub mod pallet {
1566 use super::*;
1567 use frame_support::traits::StorageVersion;
1568 use frame_system::{ensure_signed, pallet_prelude::*};
1569 use sp_runtime::Perbill;
1570
1571 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1573
1574 #[pallet::pallet]
1575 #[pallet::storage_version(STORAGE_VERSION)]
1576 pub struct Pallet<T>(_);
1577
1578 #[pallet::config]
1579 pub trait Config: frame_system::Config {
1580 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1582
1583 type WeightInfo: weights::WeightInfo;
1585
1586 type Currency: Mutate<Self::AccountId>
1588 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1589
1590 type RuntimeFreezeReason: From<FreezeReason>;
1592
1593 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1606
1607 #[pallet::constant]
1609 type PalletId: Get<frame_support::PalletId>;
1610
1611 #[pallet::constant]
1624 type MaxPointsToBalance: Get<u8>;
1625
1626 #[pallet::constant]
1628 type MaxUnbonding: Get<u32>;
1629
1630 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1632
1633 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1635
1636 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1640
1641 type PostUnbondingPoolsWindow: Get<u32>;
1647
1648 type MaxMetadataLen: Get<u32>;
1650
1651 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1653 }
1654
1655 #[pallet::storage]
1661 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1662
1663 #[pallet::storage]
1665 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1666
1667 #[pallet::storage]
1675 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1676
1677 #[pallet::storage]
1680 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1681
1682 #[pallet::storage]
1685 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1686
1687 #[pallet::storage]
1690 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1691
1692 #[pallet::storage]
1696 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1697
1698 #[pallet::storage]
1702 pub type PoolMembers<T: Config> =
1703 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1704
1705 #[pallet::storage]
1708 pub type BondedPools<T: Config> =
1709 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1710
1711 #[pallet::storage]
1714 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1715
1716 #[pallet::storage]
1719 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1720
1721 #[pallet::storage]
1723 pub type Metadata<T: Config> =
1724 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1725
1726 #[pallet::storage]
1728 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1729
1730 #[pallet::storage]
1735 pub type ReversePoolIdLookup<T: Config> =
1736 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1737
1738 #[pallet::storage]
1740 pub type ClaimPermissions<T: Config> =
1741 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1742
1743 #[pallet::genesis_config]
1744 pub struct GenesisConfig<T: Config> {
1745 pub min_join_bond: BalanceOf<T>,
1746 pub min_create_bond: BalanceOf<T>,
1747 pub max_pools: Option<u32>,
1748 pub max_members_per_pool: Option<u32>,
1749 pub max_members: Option<u32>,
1750 pub global_max_commission: Option<Perbill>,
1751 }
1752
1753 impl<T: Config> Default for GenesisConfig<T> {
1754 fn default() -> Self {
1755 Self {
1756 min_join_bond: Zero::zero(),
1757 min_create_bond: Zero::zero(),
1758 max_pools: Some(16),
1759 max_members_per_pool: Some(32),
1760 max_members: Some(16 * 32),
1761 global_max_commission: None,
1762 }
1763 }
1764 }
1765
1766 #[pallet::genesis_build]
1767 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1768 fn build(&self) {
1769 MinJoinBond::<T>::put(self.min_join_bond);
1770 MinCreateBond::<T>::put(self.min_create_bond);
1771
1772 if let Some(max_pools) = self.max_pools {
1773 MaxPools::<T>::put(max_pools);
1774 }
1775 if let Some(max_members_per_pool) = self.max_members_per_pool {
1776 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1777 }
1778 if let Some(max_members) = self.max_members {
1779 MaxPoolMembers::<T>::put(max_members);
1780 }
1781 if let Some(global_max_commission) = self.global_max_commission {
1782 GlobalMaxCommission::<T>::put(global_max_commission);
1783 }
1784 }
1785 }
1786
1787 #[pallet::event]
1789 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1790 pub enum Event<T: Config> {
1791 Created { depositor: T::AccountId, pool_id: PoolId },
1793 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1795 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1797 Unbonded {
1809 member: T::AccountId,
1810 pool_id: PoolId,
1811 balance: BalanceOf<T>,
1812 points: BalanceOf<T>,
1813 era: EraIndex,
1814 },
1815 Withdrawn {
1822 member: T::AccountId,
1823 pool_id: PoolId,
1824 balance: BalanceOf<T>,
1825 points: BalanceOf<T>,
1826 },
1827 Destroyed { pool_id: PoolId },
1829 StateChanged { pool_id: PoolId, new_state: PoolState },
1831 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1837 RolesUpdated {
1840 root: Option<T::AccountId>,
1841 bouncer: Option<T::AccountId>,
1842 nominator: Option<T::AccountId>,
1843 },
1844 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1846 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1848 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1850 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1852 PoolCommissionChangeRateUpdated {
1854 pool_id: PoolId,
1855 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1856 },
1857 PoolCommissionClaimPermissionUpdated {
1859 pool_id: PoolId,
1860 permission: Option<CommissionClaimPermission<T::AccountId>>,
1861 },
1862 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1864 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1866 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1868 }
1869
1870 #[pallet::error]
1871 #[cfg_attr(test, derive(PartialEq))]
1872 pub enum Error<T> {
1873 PoolNotFound,
1875 PoolMemberNotFound,
1877 RewardPoolNotFound,
1879 SubPoolsNotFound,
1881 AccountBelongsToOtherPool,
1884 FullyUnbonding,
1887 MaxUnbondingLimit,
1889 CannotWithdrawAny,
1891 MinimumBondNotMet,
1897 OverflowRisk,
1899 NotDestroying,
1902 NotNominator,
1904 NotKickerOrDestroying,
1906 NotOpen,
1908 MaxPools,
1910 MaxPoolMembers,
1912 CanNotChangeState,
1914 DoesNotHavePermission,
1916 MetadataExceedsMaxLen,
1918 Defensive(DefensiveError),
1921 PartialUnbondNotAllowedPermissionlessly,
1923 MaxCommissionRestricted,
1925 CommissionExceedsMaximum,
1927 CommissionExceedsGlobalMaximum,
1929 CommissionChangeThrottled,
1931 CommissionChangeRateNotAllowed,
1933 NoPendingCommission,
1935 NoCommissionCurrentSet,
1937 PoolIdInUse,
1939 InvalidPoolId,
1941 BondExtraRestricted,
1943 NothingToAdjust,
1945 NothingToSlash,
1947 AlreadyMigrated,
1949 NotMigrated,
1951 NotSupported,
1953 }
1954
1955 #[derive(Encode, Decode, PartialEq, TypeInfo, PalletError, RuntimeDebug)]
1956 pub enum DefensiveError {
1957 NotEnoughSpaceInUnbondPool,
1959 PoolNotFound,
1961 RewardPoolNotFound,
1963 SubPoolsNotFound,
1965 BondedStashKilledPrematurely,
1968 DelegationUnsupported,
1970 SlashNotApplied,
1972 }
1973
1974 impl<T> From<DefensiveError> for Error<T> {
1975 fn from(e: DefensiveError) -> Error<T> {
1976 Error::<T>::Defensive(e)
1977 }
1978 }
1979
1980 #[pallet::composite_enum]
1982 pub enum FreezeReason {
1983 #[codec(index = 0)]
1985 PoolMinBalance,
1986 }
1987
1988 #[pallet::call]
1989 impl<T: Config> Pallet<T> {
1990 #[pallet::call_index(0)]
2006 #[pallet::weight(T::WeightInfo::join())]
2007 pub fn join(
2008 origin: OriginFor<T>,
2009 #[pallet::compact] amount: BalanceOf<T>,
2010 pool_id: PoolId,
2011 ) -> DispatchResult {
2012 let who = ensure_signed(origin)?;
2013 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2015
2016 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2017 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2019
2020 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2021 bonded_pool.ok_to_join()?;
2022
2023 let mut reward_pool = RewardPools::<T>::get(pool_id)
2024 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2025 reward_pool.update_records(
2027 pool_id,
2028 bonded_pool.points,
2029 bonded_pool.commission.current(),
2030 )?;
2031
2032 bonded_pool.try_inc_members()?;
2033 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2034
2035 PoolMembers::insert(
2036 who.clone(),
2037 PoolMember::<T> {
2038 pool_id,
2039 points: points_issued,
2040 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2043 unbonding_eras: Default::default(),
2044 },
2045 );
2046
2047 Self::deposit_event(Event::<T>::Bonded {
2048 member: who,
2049 pool_id,
2050 bonded: amount,
2051 joined: true,
2052 });
2053
2054 bonded_pool.put();
2055 RewardPools::<T>::insert(pool_id, reward_pool);
2056
2057 Ok(())
2058 }
2059
2060 #[pallet::call_index(1)]
2071 #[pallet::weight(
2072 T::WeightInfo::bond_extra_transfer()
2073 .max(T::WeightInfo::bond_extra_other())
2074 )]
2075 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2076 let who = ensure_signed(origin)?;
2077
2078 ensure!(
2080 !Self::api_member_needs_delegate_migration(who.clone()),
2081 Error::<T>::NotMigrated
2082 );
2083
2084 Self::do_bond_extra(who.clone(), who, extra)
2085 }
2086
2087 #[pallet::call_index(2)]
2096 #[pallet::weight(T::WeightInfo::claim_payout())]
2097 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2098 let signer = ensure_signed(origin)?;
2099 ensure!(
2101 !Self::api_member_needs_delegate_migration(signer.clone()),
2102 Error::<T>::NotMigrated
2103 );
2104
2105 Self::do_claim_payout(signer.clone(), signer)
2106 }
2107
2108 #[pallet::call_index(3)]
2140 #[pallet::weight(T::WeightInfo::unbond())]
2141 pub fn unbond(
2142 origin: OriginFor<T>,
2143 member_account: AccountIdLookupOf<T>,
2144 #[pallet::compact] unbonding_points: BalanceOf<T>,
2145 ) -> DispatchResult {
2146 let who = ensure_signed(origin)?;
2147 let member_account = T::Lookup::lookup(member_account)?;
2148 ensure!(
2150 !Self::api_member_needs_delegate_migration(member_account.clone()),
2151 Error::<T>::NotMigrated
2152 );
2153
2154 let (mut member, mut bonded_pool, mut reward_pool) =
2155 Self::get_member_with_pools(&member_account)?;
2156
2157 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2158
2159 reward_pool.update_records(
2163 bonded_pool.id,
2164 bonded_pool.points,
2165 bonded_pool.commission.current(),
2166 )?;
2167 let _ = Self::do_reward_payout(
2168 &member_account,
2169 &mut member,
2170 &mut bonded_pool,
2171 &mut reward_pool,
2172 )?;
2173
2174 let current_era = T::StakeAdapter::current_era();
2175 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
2176
2177 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2179 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2180
2181 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2183 .unwrap_or_default()
2184 .maybe_merge_pools(current_era);
2185
2186 if !sub_pools.with_era.contains_key(&unbond_era) {
2189 sub_pools
2190 .with_era
2191 .try_insert(unbond_era, UnbondPool::default())
2192 .defensive_map_err::<Error<T>, _>(|_| {
2195 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2196 })?;
2197 }
2198
2199 let points_unbonded = sub_pools
2200 .with_era
2201 .get_mut(&unbond_era)
2202 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2204 .issue(unbonding_balance);
2205
2206 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2208
2209 Self::deposit_event(Event::<T>::Unbonded {
2210 member: member_account.clone(),
2211 pool_id: member.pool_id,
2212 points: points_unbonded,
2213 balance: unbonding_balance,
2214 era: unbond_era,
2215 });
2216
2217 SubPoolsStorage::insert(member.pool_id, sub_pools);
2219 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2220 Ok(())
2221 }
2222
2223 #[pallet::call_index(4)]
2230 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2231 pub fn pool_withdraw_unbonded(
2232 origin: OriginFor<T>,
2233 pool_id: PoolId,
2234 num_slashing_spans: u32,
2235 ) -> DispatchResult {
2236 let _ = ensure_signed(origin)?;
2237 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2239
2240 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2241
2242 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2245 T::StakeAdapter::withdraw_unbonded(
2246 Pool::from(pool.bonded_account()),
2247 num_slashing_spans,
2248 )?;
2249
2250 Ok(())
2251 }
2252
2253 #[pallet::call_index(5)]
2276 #[pallet::weight(
2277 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2278 )]
2279 pub fn withdraw_unbonded(
2280 origin: OriginFor<T>,
2281 member_account: AccountIdLookupOf<T>,
2282 num_slashing_spans: u32,
2283 ) -> DispatchResultWithPostInfo {
2284 let caller = ensure_signed(origin)?;
2285 let member_account = T::Lookup::lookup(member_account)?;
2286 ensure!(
2288 !Self::api_member_needs_delegate_migration(member_account.clone()),
2289 Error::<T>::NotMigrated
2290 );
2291
2292 let mut member =
2293 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2294 let current_era = T::StakeAdapter::current_era();
2295
2296 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2297 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2298 let mut sub_pools =
2299 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2300
2301 let slash_weight =
2302 match Self::do_apply_slash(&member_account, None) {
2304 Ok(_) => T::WeightInfo::apply_slash(),
2305 Err(e) => {
2306 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2307 if Err(e) == no_pending_slash {
2309 T::WeightInfo::apply_slash_fail()
2310 } else {
2311 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2313 }
2314 }
2315
2316 };
2317
2318 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2319 let pool_account = bonded_pool.bonded_account();
2320
2321 let withdrawn_points = member.withdraw_unlocked(current_era);
2323 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2324
2325 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2328 Pool::from(bonded_pool.bonded_account()),
2329 num_slashing_spans,
2330 )?;
2331
2332 ensure!(
2335 !stash_killed || caller == bonded_pool.roles.depositor,
2336 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2337 );
2338
2339 if stash_killed {
2340 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2342 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2343 }
2344
2345 }
2352
2353 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2354 let balance_to_unbond = withdrawn_points
2355 .iter()
2356 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2357 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2358 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2359 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2360 if era_pool.points.is_zero() {
2361 sub_pools.with_era.remove(era);
2362 }
2363 accumulator.saturating_add(balance_to_unbond)
2364 } else {
2365 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2368 }
2369 })
2370 .min(T::StakeAdapter::transferable_balance(
2378 Pool::from(bonded_pool.bonded_account()),
2379 Member::from(member_account.clone()),
2380 ));
2381
2382 T::StakeAdapter::member_withdraw(
2385 Member::from(member_account.clone()),
2386 Pool::from(bonded_pool.bonded_account()),
2387 balance_to_unbond,
2388 num_slashing_spans,
2389 )?;
2390
2391 Self::deposit_event(Event::<T>::Withdrawn {
2392 member: member_account.clone(),
2393 pool_id: member.pool_id,
2394 points: sum_unlocked_points,
2395 balance: balance_to_unbond,
2396 });
2397
2398 let post_info_weight = if member.total_points().is_zero() {
2399 ClaimPermissions::<T>::remove(&member_account);
2401
2402 PoolMembers::<T>::remove(&member_account);
2404
2405 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2407 Member::from(member_account.clone()),
2408 ) {
2409 Some(dangling_delegation) => {
2410 T::StakeAdapter::member_withdraw(
2411 Member::from(member_account.clone()),
2412 Pool::from(bonded_pool.bonded_account()),
2413 dangling_delegation,
2414 num_slashing_spans,
2415 )?;
2416 dangling_delegation
2417 },
2418 None => Zero::zero(),
2419 };
2420
2421 Self::deposit_event(Event::<T>::MemberRemoved {
2422 pool_id: member.pool_id,
2423 member: member_account.clone(),
2424 released_balance: dangling_withdrawal,
2425 });
2426
2427 if member_account == bonded_pool.roles.depositor {
2428 Pallet::<T>::dissolve_pool(bonded_pool);
2429 Weight::default()
2430 } else {
2431 bonded_pool.dec_members().put();
2432 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2433 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2434 }
2435 } else {
2436 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2438 PoolMembers::<T>::insert(&member_account, member);
2439 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2440 };
2441
2442 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2443 }
2444
2445 #[pallet::call_index(6)]
2463 #[pallet::weight(T::WeightInfo::create())]
2464 pub fn create(
2465 origin: OriginFor<T>,
2466 #[pallet::compact] amount: BalanceOf<T>,
2467 root: AccountIdLookupOf<T>,
2468 nominator: AccountIdLookupOf<T>,
2469 bouncer: AccountIdLookupOf<T>,
2470 ) -> DispatchResult {
2471 let depositor = ensure_signed(origin)?;
2472
2473 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2474 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2475 Ok(*id)
2476 })?;
2477
2478 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2479 }
2480
2481 #[pallet::call_index(7)]
2488 #[pallet::weight(T::WeightInfo::create())]
2489 pub fn create_with_pool_id(
2490 origin: OriginFor<T>,
2491 #[pallet::compact] amount: BalanceOf<T>,
2492 root: AccountIdLookupOf<T>,
2493 nominator: AccountIdLookupOf<T>,
2494 bouncer: AccountIdLookupOf<T>,
2495 pool_id: PoolId,
2496 ) -> DispatchResult {
2497 let depositor = ensure_signed(origin)?;
2498
2499 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2500 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2501
2502 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2503 }
2504
2505 #[pallet::call_index(8)]
2518 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2519 pub fn nominate(
2520 origin: OriginFor<T>,
2521 pool_id: PoolId,
2522 validators: Vec<T::AccountId>,
2523 ) -> DispatchResult {
2524 let who = ensure_signed(origin)?;
2525 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2526 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2528 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2529
2530 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2531 .ok_or(Error::<T>::PoolMemberNotFound)?
2532 .active_points();
2533
2534 ensure!(
2535 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2536 Error::<T>::MinimumBondNotMet
2537 );
2538
2539 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators)
2540 }
2541
2542 #[pallet::call_index(9)]
2553 #[pallet::weight(T::WeightInfo::set_state())]
2554 pub fn set_state(
2555 origin: OriginFor<T>,
2556 pool_id: PoolId,
2557 state: PoolState,
2558 ) -> DispatchResult {
2559 let who = ensure_signed(origin)?;
2560 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2561 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2562 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2564
2565 if bonded_pool.can_toggle_state(&who) {
2566 bonded_pool.set_state(state);
2567 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2568 bonded_pool.set_state(PoolState::Destroying);
2570 } else {
2571 Err(Error::<T>::CanNotChangeState)?;
2572 }
2573
2574 bonded_pool.put();
2575
2576 Ok(())
2577 }
2578
2579 #[pallet::call_index(10)]
2584 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2585 pub fn set_metadata(
2586 origin: OriginFor<T>,
2587 pool_id: PoolId,
2588 metadata: Vec<u8>,
2589 ) -> DispatchResult {
2590 let who = ensure_signed(origin)?;
2591 let metadata: BoundedVec<_, _> =
2592 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2593 ensure!(
2594 BondedPool::<T>::get(pool_id)
2595 .ok_or(Error::<T>::PoolNotFound)?
2596 .can_set_metadata(&who),
2597 Error::<T>::DoesNotHavePermission
2598 );
2599 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2601
2602 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2603
2604 Ok(())
2605 }
2606
2607 #[pallet::call_index(11)]
2619 #[pallet::weight(T::WeightInfo::set_configs())]
2620 pub fn set_configs(
2621 origin: OriginFor<T>,
2622 min_join_bond: ConfigOp<BalanceOf<T>>,
2623 min_create_bond: ConfigOp<BalanceOf<T>>,
2624 max_pools: ConfigOp<u32>,
2625 max_members: ConfigOp<u32>,
2626 max_members_per_pool: ConfigOp<u32>,
2627 global_max_commission: ConfigOp<Perbill>,
2628 ) -> DispatchResult {
2629 T::AdminOrigin::ensure_origin(origin)?;
2630
2631 macro_rules! config_op_exp {
2632 ($storage:ty, $op:ident) => {
2633 match $op {
2634 ConfigOp::Noop => (),
2635 ConfigOp::Set(v) => <$storage>::put(v),
2636 ConfigOp::Remove => <$storage>::kill(),
2637 }
2638 };
2639 }
2640
2641 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2642 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2643 config_op_exp!(MaxPools::<T>, max_pools);
2644 config_op_exp!(MaxPoolMembers::<T>, max_members);
2645 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2646 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2647 Ok(())
2648 }
2649
2650 #[pallet::call_index(12)]
2658 #[pallet::weight(T::WeightInfo::update_roles())]
2659 pub fn update_roles(
2660 origin: OriginFor<T>,
2661 pool_id: PoolId,
2662 new_root: ConfigOp<T::AccountId>,
2663 new_nominator: ConfigOp<T::AccountId>,
2664 new_bouncer: ConfigOp<T::AccountId>,
2665 ) -> DispatchResult {
2666 let mut bonded_pool = match ensure_root(origin.clone()) {
2667 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2668 Err(sp_runtime::traits::BadOrigin) => {
2669 let who = ensure_signed(origin)?;
2670 let bonded_pool =
2671 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2672 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2673 bonded_pool
2674 },
2675 };
2676
2677 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2679
2680 match new_root {
2681 ConfigOp::Noop => (),
2682 ConfigOp::Remove => bonded_pool.roles.root = None,
2683 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2684 };
2685 match new_nominator {
2686 ConfigOp::Noop => (),
2687 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2688 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2689 };
2690 match new_bouncer {
2691 ConfigOp::Noop => (),
2692 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2693 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2694 };
2695
2696 Self::deposit_event(Event::<T>::RolesUpdated {
2697 root: bonded_pool.roles.root.clone(),
2698 nominator: bonded_pool.roles.nominator.clone(),
2699 bouncer: bonded_pool.roles.bouncer.clone(),
2700 });
2701
2702 bonded_pool.put();
2703 Ok(())
2704 }
2705
2706 #[pallet::call_index(13)]
2723 #[pallet::weight(T::WeightInfo::chill())]
2724 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2725 let who = ensure_signed(origin)?;
2726 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2727 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2729
2730 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2731 .ok_or(Error::<T>::PoolMemberNotFound)?
2732 .active_points();
2733
2734 if bonded_pool.points_to_balance(depositor_points) >=
2735 T::StakeAdapter::minimum_nominator_bond()
2736 {
2737 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2738 }
2739
2740 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account()))
2741 }
2742
2743 #[pallet::call_index(14)]
2753 #[pallet::weight(
2754 T::WeightInfo::bond_extra_transfer()
2755 .max(T::WeightInfo::bond_extra_other())
2756 )]
2757 pub fn bond_extra_other(
2758 origin: OriginFor<T>,
2759 member: AccountIdLookupOf<T>,
2760 extra: BondExtra<BalanceOf<T>>,
2761 ) -> DispatchResult {
2762 let who = ensure_signed(origin)?;
2763 let member_account = T::Lookup::lookup(member)?;
2764 ensure!(
2766 !Self::api_member_needs_delegate_migration(member_account.clone()),
2767 Error::<T>::NotMigrated
2768 );
2769
2770 Self::do_bond_extra(who, member_account, extra)
2771 }
2772
2773 #[pallet::call_index(15)]
2781 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2782 pub fn set_claim_permission(
2783 origin: OriginFor<T>,
2784 permission: ClaimPermission,
2785 ) -> DispatchResult {
2786 let who = ensure_signed(origin)?;
2787 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2788
2789 ensure!(
2791 !Self::api_member_needs_delegate_migration(who.clone()),
2792 Error::<T>::NotMigrated
2793 );
2794
2795 ClaimPermissions::<T>::mutate(who, |source| {
2796 *source = permission;
2797 });
2798
2799 Ok(())
2800 }
2801
2802 #[pallet::call_index(16)]
2807 #[pallet::weight(T::WeightInfo::claim_payout())]
2808 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2809 let signer = ensure_signed(origin)?;
2810 ensure!(
2812 !Self::api_member_needs_delegate_migration(other.clone()),
2813 Error::<T>::NotMigrated
2814 );
2815
2816 Self::do_claim_payout(signer, other)
2817 }
2818
2819 #[pallet::call_index(17)]
2826 #[pallet::weight(T::WeightInfo::set_commission())]
2827 pub fn set_commission(
2828 origin: OriginFor<T>,
2829 pool_id: PoolId,
2830 new_commission: Option<(Perbill, T::AccountId)>,
2831 ) -> DispatchResult {
2832 let who = ensure_signed(origin)?;
2833 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2834 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2836
2837 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2838
2839 let mut reward_pool = RewardPools::<T>::get(pool_id)
2840 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2841 reward_pool.update_records(
2844 pool_id,
2845 bonded_pool.points,
2846 bonded_pool.commission.current(),
2847 )?;
2848 RewardPools::insert(pool_id, reward_pool);
2849
2850 bonded_pool.commission.try_update_current(&new_commission)?;
2851 bonded_pool.put();
2852 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
2853 pool_id,
2854 current: new_commission,
2855 });
2856 Ok(())
2857 }
2858
2859 #[pallet::call_index(18)]
2865 #[pallet::weight(T::WeightInfo::set_commission_max())]
2866 pub fn set_commission_max(
2867 origin: OriginFor<T>,
2868 pool_id: PoolId,
2869 max_commission: Perbill,
2870 ) -> DispatchResult {
2871 let who = ensure_signed(origin)?;
2872 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2873 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2875
2876 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2877
2878 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
2879 bonded_pool.put();
2880
2881 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
2882 Ok(())
2883 }
2884
2885 #[pallet::call_index(19)]
2890 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
2891 pub fn set_commission_change_rate(
2892 origin: OriginFor<T>,
2893 pool_id: PoolId,
2894 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
2895 ) -> DispatchResult {
2896 let who = ensure_signed(origin)?;
2897 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2898 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2900 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2901
2902 bonded_pool.commission.try_update_change_rate(change_rate)?;
2903 bonded_pool.put();
2904
2905 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
2906 pool_id,
2907 change_rate,
2908 });
2909 Ok(())
2910 }
2911
2912 #[pallet::call_index(20)]
2918 #[pallet::weight(T::WeightInfo::claim_commission())]
2919 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2920 let who = ensure_signed(origin)?;
2921 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2923
2924 Self::do_claim_commission(who, pool_id)
2925 }
2926
2927 #[pallet::call_index(21)]
2935 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
2936 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2937 let who = ensure_signed(origin)?;
2938 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2940
2941 Self::do_adjust_pool_deposit(who, pool_id)
2942 }
2943
2944 #[pallet::call_index(22)]
2949 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
2950 pub fn set_commission_claim_permission(
2951 origin: OriginFor<T>,
2952 pool_id: PoolId,
2953 permission: Option<CommissionClaimPermission<T::AccountId>>,
2954 ) -> DispatchResult {
2955 let who = ensure_signed(origin)?;
2956 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2957 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2959 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2960
2961 bonded_pool.commission.claim_permission = permission.clone();
2962 bonded_pool.put();
2963
2964 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
2965 pool_id,
2966 permission,
2967 });
2968
2969 Ok(())
2970 }
2971
2972 #[pallet::call_index(23)]
2980 #[pallet::weight(T::WeightInfo::apply_slash())]
2981 pub fn apply_slash(
2982 origin: OriginFor<T>,
2983 member_account: AccountIdLookupOf<T>,
2984 ) -> DispatchResultWithPostInfo {
2985 ensure!(
2986 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
2987 Error::<T>::NotSupported
2988 );
2989
2990 let who = ensure_signed(origin)?;
2991 let member_account = T::Lookup::lookup(member_account)?;
2992 Self::do_apply_slash(&member_account, Some(who))?;
2993
2994 Ok(Pays::No.into())
2996 }
2997
2998 #[pallet::call_index(24)]
3008 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3009 pub fn migrate_delegation(
3010 origin: OriginFor<T>,
3011 member_account: AccountIdLookupOf<T>,
3012 ) -> DispatchResultWithPostInfo {
3013 let _caller = ensure_signed(origin)?;
3014
3015 ensure!(
3016 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3017 Error::<T>::NotSupported
3018 );
3019
3020 let member_account = T::Lookup::lookup(member_account)?;
3021 let member =
3022 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3023
3024 ensure!(
3026 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3027 member.pool_id
3028 ))) == adapter::StakeStrategyType::Delegate,
3029 Error::<T>::NotMigrated
3030 );
3031
3032 let pool_contribution = member.total_balance();
3033 ensure!(
3036 pool_contribution >= T::Currency::minimum_balance(),
3037 Error::<T>::MinimumBondNotMet
3038 );
3039
3040 let delegation =
3041 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3042 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3044
3045 T::StakeAdapter::migrate_delegation(
3046 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3047 Member::from(member_account),
3048 pool_contribution,
3049 )?;
3050
3051 Ok(Pays::No.into())
3053 }
3054
3055 #[pallet::call_index(25)]
3065 #[pallet::weight(T::WeightInfo::pool_migrate())]
3066 pub fn migrate_pool_to_delegate_stake(
3067 origin: OriginFor<T>,
3068 pool_id: PoolId,
3069 ) -> DispatchResultWithPostInfo {
3070 ensure!(
3072 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3073 Error::<T>::NotSupported
3074 );
3075
3076 let _caller = ensure_signed(origin)?;
3077 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3079 ensure!(
3080 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3081 adapter::StakeStrategyType::Transfer,
3082 Error::<T>::AlreadyMigrated
3083 );
3084
3085 Self::migrate_to_delegate_stake(pool_id)?;
3086 Ok(Pays::No.into())
3087 }
3088 }
3089
3090 #[pallet::hooks]
3091 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
3092 #[cfg(feature = "try-runtime")]
3093 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3094 Self::do_try_state(u8::MAX)
3095 }
3096
3097 fn integrity_test() {
3098 assert!(
3099 T::MaxPointsToBalance::get() > 0,
3100 "Minimum points to balance ratio must be greater than 0"
3101 );
3102 assert!(
3103 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3104 "There must be more unbonding pools then the bonding duration /
3105 so a slash can be applied to relevant unbonding pools. (We assume /
3106 the bonding duration > slash deffer duration.",
3107 );
3108 }
3109 }
3110}
3111
3112impl<T: Config> Pallet<T> {
3113 pub fn depositor_min_bond() -> BalanceOf<T> {
3121 T::StakeAdapter::minimum_nominator_bond()
3122 .max(MinCreateBond::<T>::get())
3123 .max(MinJoinBond::<T>::get())
3124 .max(T::Currency::minimum_balance())
3125 }
3126 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3131 let reward_account = bonded_pool.reward_account();
3132 let bonded_account = bonded_pool.bonded_account();
3133
3134 ReversePoolIdLookup::<T>::remove(&bonded_account);
3135 RewardPools::<T>::remove(bonded_pool.id);
3136 SubPoolsStorage::<T>::remove(bonded_pool.id);
3137
3138 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3140
3141 defensive_assert!(
3149 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3150 "reward account of dissolving pool should have no consumers"
3151 );
3152 defensive_assert!(
3153 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3154 "bonded account of dissolving pool should have no consumers"
3155 );
3156 defensive_assert!(
3157 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3158 "dissolving pool should not have any stake in the staking pallet"
3159 );
3160
3161 let reward_pool_remaining = T::Currency::reducible_balance(
3164 &reward_account,
3165 Preservation::Expendable,
3166 Fortitude::Polite,
3167 );
3168 let _ = T::Currency::transfer(
3169 &reward_account,
3170 &bonded_pool.roles.depositor,
3171 reward_pool_remaining,
3172 Preservation::Expendable,
3173 );
3174
3175 defensive_assert!(
3176 T::Currency::total_balance(&reward_account) == Zero::zero(),
3177 "could not transfer all amount to depositor while dissolving pool"
3178 );
3179 T::Currency::set_balance(&reward_account, Zero::zero());
3181
3182 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3184
3185 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3186 Metadata::<T>::remove(bonded_pool.id);
3188
3189 bonded_pool.remove();
3190 }
3191
3192 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3194 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3195 }
3196
3197 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3198 T::StakeAdapter::migrate_nominator_to_agent(
3199 Pool::from(Self::generate_bonded_account(id)),
3200 &Self::generate_reward_account(id),
3201 )
3202 }
3203
3204 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3206 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3209 }
3210
3211 fn get_member_with_pools(
3213 who: &T::AccountId,
3214 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3215 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3216 let bonded_pool =
3217 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3218 let reward_pool =
3219 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3220 Ok((member, bonded_pool, reward_pool))
3221 }
3222
3223 fn put_member_with_pools(
3226 member_account: &T::AccountId,
3227 member: PoolMember<T>,
3228 bonded_pool: BondedPool<T>,
3229 reward_pool: RewardPool<T>,
3230 ) {
3231 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3234 debug_assert_eq!(member.pool_id, bonded_pool.id);
3235
3236 bonded_pool.put();
3237 RewardPools::insert(member.pool_id, reward_pool);
3238 PoolMembers::<T>::insert(member_account, member);
3239 }
3240
3241 fn balance_to_point(
3244 current_balance: BalanceOf<T>,
3245 current_points: BalanceOf<T>,
3246 new_funds: BalanceOf<T>,
3247 ) -> BalanceOf<T> {
3248 let u256 = T::BalanceToU256::convert;
3249 let balance = T::U256ToBalance::convert;
3250 match (current_balance.is_zero(), current_points.is_zero()) {
3251 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3252 (true, false) => {
3253 new_funds.saturating_mul(current_points)
3256 },
3257 (false, false) => {
3258 balance(
3260 u256(current_points)
3261 .saturating_mul(u256(new_funds))
3262 .div(u256(current_balance)),
3264 )
3265 },
3266 }
3267 }
3268
3269 fn point_to_balance(
3272 current_balance: BalanceOf<T>,
3273 current_points: BalanceOf<T>,
3274 points: BalanceOf<T>,
3275 ) -> BalanceOf<T> {
3276 let u256 = T::BalanceToU256::convert;
3277 let balance = T::U256ToBalance::convert;
3278 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3279 return Zero::zero()
3281 }
3282
3283 balance(
3285 u256(current_balance)
3286 .saturating_mul(u256(points))
3287 .div(u256(current_points)),
3289 )
3290 }
3291
3292 fn do_reward_payout(
3296 member_account: &T::AccountId,
3297 member: &mut PoolMember<T>,
3298 bonded_pool: &mut BondedPool<T>,
3299 reward_pool: &mut RewardPool<T>,
3300 ) -> Result<BalanceOf<T>, DispatchError> {
3301 debug_assert_eq!(member.pool_id, bonded_pool.id);
3302 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3303
3304 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3306
3307 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3308 bonded_pool.id,
3309 bonded_pool.points,
3310 bonded_pool.commission.current(),
3311 )?;
3312
3313 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3316 if pending_rewards.is_zero() {
3317 return Ok(pending_rewards)
3318 }
3319
3320 member.last_recorded_reward_counter = current_reward_counter;
3322 reward_pool.register_claimed_reward(pending_rewards);
3323
3324 T::Currency::transfer(
3325 &bonded_pool.reward_account(),
3326 member_account,
3327 pending_rewards,
3328 Preservation::Preserve,
3331 )?;
3332
3333 Self::deposit_event(Event::<T>::PaidOut {
3334 member: member_account.clone(),
3335 pool_id: member.pool_id,
3336 payout: pending_rewards,
3337 });
3338 Ok(pending_rewards)
3339 }
3340
3341 fn do_create(
3342 who: T::AccountId,
3343 amount: BalanceOf<T>,
3344 root: AccountIdLookupOf<T>,
3345 nominator: AccountIdLookupOf<T>,
3346 bouncer: AccountIdLookupOf<T>,
3347 pool_id: PoolId,
3348 ) -> DispatchResult {
3349 let root = T::Lookup::lookup(root)?;
3350 let nominator = T::Lookup::lookup(nominator)?;
3351 let bouncer = T::Lookup::lookup(bouncer)?;
3352
3353 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3354 ensure!(
3355 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3356 Error::<T>::MaxPools
3357 );
3358 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3359 let mut bonded_pool = BondedPool::<T>::new(
3360 pool_id,
3361 PoolRoles {
3362 root: Some(root),
3363 nominator: Some(nominator),
3364 bouncer: Some(bouncer),
3365 depositor: who.clone(),
3366 },
3367 );
3368
3369 bonded_pool.try_inc_members()?;
3370 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3371
3372 T::Currency::transfer(
3374 &who,
3375 &bonded_pool.reward_account(),
3376 T::Currency::minimum_balance(),
3377 Preservation::Expendable,
3378 )?;
3379
3380 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3382
3383 PoolMembers::<T>::insert(
3384 who.clone(),
3385 PoolMember::<T> {
3386 pool_id,
3387 points,
3388 last_recorded_reward_counter: Zero::zero(),
3389 unbonding_eras: Default::default(),
3390 },
3391 );
3392 RewardPools::<T>::insert(
3393 pool_id,
3394 RewardPool::<T> {
3395 last_recorded_reward_counter: Zero::zero(),
3396 last_recorded_total_payouts: Zero::zero(),
3397 total_rewards_claimed: Zero::zero(),
3398 total_commission_pending: Zero::zero(),
3399 total_commission_claimed: Zero::zero(),
3400 },
3401 );
3402 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3403
3404 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3405
3406 Self::deposit_event(Event::<T>::Bonded {
3407 member: who,
3408 pool_id,
3409 bonded: amount,
3410 joined: true,
3411 });
3412 bonded_pool.put();
3413
3414 Ok(())
3415 }
3416
3417 fn do_bond_extra(
3418 signer: T::AccountId,
3419 member_account: T::AccountId,
3420 extra: BondExtra<BalanceOf<T>>,
3421 ) -> DispatchResult {
3422 if signer != member_account {
3423 ensure!(
3424 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3425 Error::<T>::DoesNotHavePermission
3426 );
3427 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3428 }
3429
3430 let (mut member, mut bonded_pool, mut reward_pool) =
3431 Self::get_member_with_pools(&member_account)?;
3432
3433 reward_pool.update_records(
3436 bonded_pool.id,
3437 bonded_pool.points,
3438 bonded_pool.commission.current(),
3439 )?;
3440 let claimed = Self::do_reward_payout(
3441 &member_account,
3442 &mut member,
3443 &mut bonded_pool,
3444 &mut reward_pool,
3445 )?;
3446
3447 let (points_issued, bonded) = match extra {
3448 BondExtra::FreeBalance(amount) =>
3449 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
3450 BondExtra::Rewards =>
3451 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
3452 };
3453
3454 bonded_pool.ok_to_be_open()?;
3455 member.points =
3456 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3457
3458 Self::deposit_event(Event::<T>::Bonded {
3459 member: member_account.clone(),
3460 pool_id: member.pool_id,
3461 bonded,
3462 joined: false,
3463 });
3464 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3465
3466 Ok(())
3467 }
3468
3469 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3470 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3471 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3472
3473 let mut reward_pool = RewardPools::<T>::get(pool_id)
3474 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3475
3476 reward_pool.update_records(
3479 pool_id,
3480 bonded_pool.points,
3481 bonded_pool.commission.current(),
3482 )?;
3483
3484 let commission = reward_pool.total_commission_pending;
3485 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3486
3487 let payee = bonded_pool
3488 .commission
3489 .current
3490 .as_ref()
3491 .map(|(_, p)| p.clone())
3492 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3493
3494 T::Currency::transfer(
3496 &bonded_pool.reward_account(),
3497 &payee,
3498 commission,
3499 Preservation::Preserve,
3500 )?;
3501
3502 reward_pool.total_commission_claimed =
3504 reward_pool.total_commission_claimed.saturating_add(commission);
3505 reward_pool.total_commission_pending = Zero::zero();
3507 RewardPools::<T>::insert(pool_id, reward_pool);
3508
3509 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3510 Ok(())
3511 }
3512
3513 pub(crate) fn do_claim_payout(
3514 signer: T::AccountId,
3515 member_account: T::AccountId,
3516 ) -> DispatchResult {
3517 if signer != member_account {
3518 ensure!(
3519 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3520 Error::<T>::DoesNotHavePermission
3521 );
3522 }
3523 let (mut member, mut bonded_pool, mut reward_pool) =
3524 Self::get_member_with_pools(&member_account)?;
3525
3526 let _ = Self::do_reward_payout(
3527 &member_account,
3528 &mut member,
3529 &mut bonded_pool,
3530 &mut reward_pool,
3531 )?;
3532
3533 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3534 Ok(())
3535 }
3536
3537 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3538 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3539
3540 let reward_acc = &bonded_pool.reward_account();
3541 let pre_frozen_balance =
3542 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3543 let min_balance = T::Currency::minimum_balance();
3544
3545 if pre_frozen_balance == min_balance {
3546 return Err(Error::<T>::NothingToAdjust.into())
3547 }
3548
3549 Self::freeze_pool_deposit(reward_acc)?;
3551
3552 if pre_frozen_balance > min_balance {
3553 let excess = pre_frozen_balance.saturating_sub(min_balance);
3555 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3556 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3557 pool_id: pool,
3558 amount: excess,
3559 });
3560 } else {
3561 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3563 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3564 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3565 pool_id: pool,
3566 amount: deficit,
3567 });
3568 }
3569
3570 Ok(())
3571 }
3572
3573 fn do_apply_slash(
3575 member_account: &T::AccountId,
3576 reporter: Option<T::AccountId>,
3577 ) -> DispatchResult {
3578 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3579
3580 let pending_slash =
3581 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3582
3583 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3585
3586 T::StakeAdapter::member_slash(
3587 Member::from(member_account.clone()),
3588 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3589 pending_slash,
3590 reporter,
3591 )
3592 }
3593
3594 fn member_pending_slash(
3598 member_account: Member<T::AccountId>,
3599 pool_member: PoolMember<T>,
3600 ) -> Result<BalanceOf<T>, DispatchError> {
3601 debug_assert!(
3603 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3604 pool_member
3605 );
3606
3607 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3608 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3611 return Ok(Zero::zero())
3612 }
3613
3614 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3616 .ok_or(Error::<T>::NotMigrated)?;
3618
3619 let expected_balance = pool_member.total_balance();
3621
3622 Ok(actual_balance.saturating_sub(expected_balance))
3624 }
3625
3626 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3628 T::Currency::set_freeze(
3629 &FreezeReason::PoolMinBalance.into(),
3630 reward_acc,
3631 T::Currency::minimum_balance(),
3632 )
3633 }
3634
3635 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3637 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3638 }
3639
3640 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3677 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3678 if level.is_zero() {
3679 return Ok(())
3680 }
3681 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3684 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3685 ensure!(
3686 bonded_pools == reward_pools,
3687 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3688 );
3689
3690 ensure!(
3691 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3692 "`SubPoolsStorage` must be a subset of the above superset."
3693 );
3694 ensure!(
3695 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3696 "`Metadata` keys must be a subset of the above superset."
3697 );
3698
3699 ensure!(
3700 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3701 Error::<T>::MaxPools
3702 );
3703
3704 for id in reward_pools {
3705 let account = Self::generate_reward_account(id);
3706 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3707 T::Currency::minimum_balance()
3708 {
3709 log!(
3710 warn,
3711 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3712 changed recently. Pool operators should be notified to top up the reward \
3713 account",
3714 id,
3715 T::Currency::reducible_balance(
3716 &account,
3717 Preservation::Expendable,
3718 Fortitude::Polite
3719 ),
3720 T::Currency::minimum_balance(),
3721 )
3722 }
3723 }
3724
3725 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3726 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3727 let mut all_members = 0u32;
3728 let mut total_balance_members = Default::default();
3729 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3730 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3731 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3732 *pools_members.entry(d.pool_id).or_default() += 1;
3733 all_members += 1;
3734
3735 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3736 if !bonded_pool.points.is_zero() {
3737 let commission = bonded_pool.commission.current();
3738 let (current_rc, _) = reward_pool
3739 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3740 .unwrap();
3741 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3742 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3743 } total_balance_members += d.total_balance();
3745
3746 Ok(())
3747 })?;
3748
3749 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3750 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3753 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3754
3755 if !pending_rewards_lt_leftover_bal {
3758 log::warn!(
3759 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
3760 id,
3761 pools_members_pending_rewards.get(&id),
3762 RewardPool::<T>::current_balance(id)
3763 );
3764 }
3765 Ok(())
3766 })?;
3767
3768 let mut expected_tvl: BalanceOf<T> = Default::default();
3769 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
3770 let bonded_pool = BondedPool { id, inner };
3771 ensure!(
3772 pools_members.get(&id).copied().unwrap_or_default() ==
3773 bonded_pool.member_counter,
3774 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
3775 );
3776 ensure!(
3777 MaxPoolMembersPerPool::<T>::get()
3778 .map_or(true, |max| bonded_pool.member_counter <= max),
3779 Error::<T>::MaxPoolMembers
3780 );
3781
3782 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
3783 ensure!(
3784 bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) ||
3785 depositor.active_points() >= MinCreateBond::<T>::get(),
3786 "depositor must always have MinCreateBond stake in the pool, except for when the \
3787 pool is being destroyed and the depositor is the last member",
3788 );
3789
3790 ensure!(
3791 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
3792 "Each `BondedPool.points` must never be lower than the pool's balance"
3793 );
3794
3795 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
3796
3797 Ok(())
3798 })?;
3799
3800 ensure!(
3801 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
3802 Error::<T>::MaxPoolMembers
3803 );
3804
3805 ensure!(
3806 TotalValueLocked::<T>::get() == expected_tvl,
3807 "TVL deviates from the actual sum of funds of all Pools."
3808 );
3809
3810 ensure!(
3811 TotalValueLocked::<T>::get() <= total_balance_members,
3812 "TVL must be equal to or less than the total balance of all PoolMembers."
3813 );
3814
3815 if level <= 1 {
3816 return Ok(())
3817 }
3818
3819 for (pool_id, _pool) in BondedPools::<T>::iter() {
3820 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
3821 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
3822
3823 let sum_unbonding_balance = subs.sum_unbonding_balance();
3824 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
3825 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
3826 .unwrap_or(T::Currency::total_balance(&pool_account));
3829
3830 assert!(
3831 total_balance >= bonded_balance + sum_unbonding_balance,
3832 "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
3833 pool_id,
3834 _pool,
3835 total_balance,
3836 bonded_balance,
3837 sum_unbonding_balance
3838 );
3839 }
3840
3841 let _ = Self::check_ed_imbalance()?;
3844
3845 Ok(())
3846 }
3847
3848 #[cfg(any(
3852 feature = "try-runtime",
3853 feature = "runtime-benchmarks",
3854 feature = "fuzzing",
3855 test,
3856 debug_assertions
3857 ))]
3858 pub fn check_ed_imbalance() -> Result<(), DispatchError> {
3859 let mut failed: u32 = 0;
3860 BondedPools::<T>::iter_keys().for_each(|id| {
3861 let reward_acc = Self::generate_reward_account(id);
3862 let frozen_balance =
3863 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
3864
3865 let expected_frozen_balance = T::Currency::minimum_balance();
3866 if frozen_balance != expected_frozen_balance {
3867 failed += 1;
3868 log::warn!(
3869 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}",
3870 id,
3871 expected_frozen_balance,
3872 frozen_balance,
3873 );
3874 }
3875 });
3876
3877 ensure!(failed == 0, "Some pools do not have correct ED frozen");
3878 Ok(())
3879 }
3880 #[cfg(any(feature = "runtime-benchmarks", test))]
3885 pub fn fully_unbond(
3886 origin: frame_system::pallet_prelude::OriginFor<T>,
3887 member: T::AccountId,
3888 ) -> DispatchResult {
3889 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
3890 let member_lookup = T::Lookup::unlookup(member);
3891 Self::unbond(origin, member_lookup, points)
3892 }
3893}
3894
3895impl<T: Config> Pallet<T> {
3896 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
3900 if let Some(pool_member) = PoolMembers::<T>::get(who) {
3901 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
3902 .zip(BondedPools::<T>::get(pool_member.pool_id))
3903 {
3904 let commission = bonded_pool.commission.current();
3905 let (current_reward_counter, _) = reward_pool
3906 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
3907 .ok()?;
3908 return pool_member.pending_rewards(current_reward_counter).ok()
3909 }
3910 }
3911
3912 None
3913 }
3914
3915 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
3919 if let Some(pool) = BondedPool::<T>::get(pool_id) {
3920 pool.points_to_balance(points)
3921 } else {
3922 Zero::zero()
3923 }
3924 }
3925
3926 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
3930 if let Some(pool) = BondedPool::<T>::get(pool_id) {
3931 let bonded_balance =
3932 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
3933 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
3934 } else {
3935 Zero::zero()
3936 }
3937 }
3938
3939 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
3943 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
3944 }
3945
3946 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
3950 PoolMembers::<T>::get(who.clone())
3951 .map(|pool_member| {
3952 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
3953 })
3954 .unwrap_or_default()
3955 }
3956
3957 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
3962 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
3964 return false
3965 }
3966
3967 if !BondedPools::<T>::contains_key(pool_id) {
3969 return false
3970 }
3971
3972 let pool_account = Self::generate_bonded_account(pool_id);
3973
3974 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
3976 adapter::StakeStrategyType::Delegate
3977 }
3978
3979 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
3985 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
3987 return false
3988 }
3989
3990 PoolMembers::<T>::get(who.clone())
3991 .map(|pool_member| {
3992 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
3993 return false
3995 }
3996
3997 let member_balance = pool_member.total_balance();
3998 let delegated_balance =
3999 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4000
4001 delegated_balance.is_none() && !member_balance.is_zero()
4004 })
4005 .unwrap_or_default()
4006 }
4007
4008 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4013 PoolMembers::<T>::get(who.clone())
4014 .map(|m| m.total_balance())
4015 .unwrap_or_default()
4016 }
4017
4018 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4020 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4021 .unwrap_or_default()
4022 }
4023
4024 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4026 let bonded_account = Self::generate_bonded_account(pool_id);
4027 let reward_account = Self::generate_reward_account(pool_id);
4028 (bonded_account, reward_account)
4029 }
4030}
4031
4032impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4033 fn on_slash(
4039 pool_account: &T::AccountId,
4040 slashed_bonded: BalanceOf<T>,
4043 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4044 total_slashed: BalanceOf<T>,
4045 ) {
4046 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4047 TotalValueLocked::<T>::mutate(|tvl| {
4050 tvl.defensive_saturating_reduce(total_slashed);
4051 });
4052
4053 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4054 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4056 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4057 pool.balance = *slashed_balance;
4058 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4059 era: *era,
4060 pool_id,
4061 balance: *slashed_balance,
4062 });
4063 }
4064 });
4065 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4066 } else if !slashed_unlocking.is_empty() {
4067 defensive!("Expected SubPools were not found");
4068 }
4069 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4070 }
4071
4072 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4075 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4076 TotalValueLocked::<T>::mutate(|tvl| {
4077 tvl.saturating_reduce(amount);
4078 });
4079 }
4080 }
4081}