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, DecodeWithMemTracking};
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 Contains, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, Get,
368 },
369 DefaultNoBound, PalletError,
370};
371use scale_info::TypeInfo;
372use sp_core::U256;
373use sp_runtime::{
374 traits::{
375 AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup,
376 Zero,
377 },
378 FixedPointNumber, Perbill,
379};
380use sp_staking::{EraIndex, StakingInterface};
381
382#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
383use sp_runtime::TryRuntimeError;
384
385pub const LOG_TARGET: &str = "runtime::nomination-pools";
387#[macro_export]
389macro_rules! log {
390 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
391 log::$level!(
392 target: $crate::LOG_TARGET,
393 concat!("[{:?}] 🏊♂️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
394 )
395 };
396}
397
398#[cfg(any(test, feature = "fuzzing"))]
399pub mod mock;
400#[cfg(test)]
401mod tests;
402
403pub mod adapter;
404pub mod migration;
405pub mod weights;
406
407pub use pallet::*;
408use sp_runtime::traits::BlockNumberProvider;
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 type BlockNumberFor<T> =
420 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
421
422pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1;
423
424#[derive(
426 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
427)]
428pub enum ConfigOp<T: Codec + Debug> {
429 Noop,
431 Set(T),
433 Remove,
435}
436
437pub enum BondType {
439 Create,
441 Extra,
443}
444
445#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
447pub enum BondExtra<Balance> {
448 FreeBalance(Balance),
450 Rewards,
452}
453
454#[derive(Encode, Decode)]
456enum AccountType {
457 Bonded,
458 Reward,
459}
460
461#[derive(
463 Encode,
464 Decode,
465 DecodeWithMemTracking,
466 MaxEncodedLen,
467 Clone,
468 Copy,
469 Debug,
470 PartialEq,
471 Eq,
472 TypeInfo,
473)]
474pub enum ClaimPermission {
475 Permissioned,
477 PermissionlessCompound,
479 PermissionlessWithdraw,
481 PermissionlessAll,
483}
484
485impl Default for ClaimPermission {
486 fn default() -> Self {
487 Self::PermissionlessWithdraw
488 }
489}
490
491impl ClaimPermission {
492 fn can_bond_extra(&self) -> bool {
495 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
496 }
497
498 fn can_claim_payout(&self) -> bool {
501 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
502 }
503}
504
505#[derive(
507 Encode,
508 Decode,
509 DecodeWithMemTracking,
510 MaxEncodedLen,
511 TypeInfo,
512 DebugNoBound,
513 CloneNoBound,
514 PartialEqNoBound,
515 EqNoBound,
516)]
517#[cfg_attr(feature = "std", derive(DefaultNoBound))]
518#[scale_info(skip_type_params(T))]
519pub struct PoolMember<T: Config> {
520 pub pool_id: PoolId,
522 pub points: BalanceOf<T>,
525 pub last_recorded_reward_counter: T::RewardCounter,
527 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
530}
531
532impl<T: Config> PoolMember<T> {
533 fn pending_rewards(
535 &self,
536 current_reward_counter: T::RewardCounter,
537 ) -> Result<BalanceOf<T>, Error<T>> {
538 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
556 .checked_mul_int(self.active_points())
557 .ok_or(Error::<T>::OverflowRisk)
558 }
559
560 fn active_balance(&self) -> BalanceOf<T> {
565 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
566 pool.points_to_balance(self.points)
567 } else {
568 Zero::zero()
569 }
570 }
571
572 pub fn total_balance(&self) -> BalanceOf<T> {
578 let pool = match BondedPool::<T>::get(self.pool_id) {
579 Some(pool) => pool,
580 None => {
581 defensive!("pool should exist; qed");
583 return Zero::zero();
584 },
585 };
586
587 let active_balance = pool.points_to_balance(self.active_points());
588
589 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
590 Some(sub_pools) => sub_pools,
591 None => return active_balance,
592 };
593
594 let unbonding_balance = self.unbonding_eras.iter().fold(
595 BalanceOf::<T>::zero(),
596 |accumulator, (era, unlocked_points)| {
597 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
600 accumulator + (era_pool.point_to_balance(*unlocked_points))
601 },
602 );
603
604 active_balance + unbonding_balance
605 }
606
607 fn total_points(&self) -> BalanceOf<T> {
609 self.active_points().saturating_add(self.unbonding_points())
610 }
611
612 fn active_points(&self) -> BalanceOf<T> {
614 self.points
615 }
616
617 fn unbonding_points(&self) -> BalanceOf<T> {
619 self.unbonding_eras
620 .as_ref()
621 .iter()
622 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
623 }
624
625 fn try_unbond(
633 &mut self,
634 points_dissolved: BalanceOf<T>,
635 points_issued: BalanceOf<T>,
636 unbonding_era: EraIndex,
637 ) -> Result<(), Error<T>> {
638 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
639 match self.unbonding_eras.get_mut(&unbonding_era) {
640 Some(already_unbonding_points) =>
641 *already_unbonding_points =
642 already_unbonding_points.saturating_add(points_issued),
643 None => self
644 .unbonding_eras
645 .try_insert(unbonding_era, points_issued)
646 .map(|old| {
647 if old.is_some() {
648 defensive!("value checked to not exist in the map; qed");
649 }
650 })
651 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
652 }
653 self.points = new_points;
654 Ok(())
655 } else {
656 Err(Error::<T>::MinimumBondNotMet)
657 }
658 }
659
660 fn withdraw_unlocked(
667 &mut self,
668 current_era: EraIndex,
669 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
670 let mut removed_points =
672 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
673 self.unbonding_eras.retain(|e, p| {
674 if *e > current_era {
675 true
676 } else {
677 removed_points
678 .try_insert(*e, *p)
679 .expect("source map is bounded, this is a subset, will be bounded; qed");
680 false
681 }
682 });
683 removed_points
684 }
685}
686
687#[derive(
689 Encode,
690 Decode,
691 DecodeWithMemTracking,
692 MaxEncodedLen,
693 TypeInfo,
694 PartialEq,
695 DebugNoBound,
696 Clone,
697 Copy,
698)]
699pub enum PoolState {
700 Open,
702 Blocked,
704 Destroying,
709}
710
711#[derive(
717 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone,
718)]
719pub struct PoolRoles<AccountId> {
720 pub depositor: AccountId,
723 pub root: Option<AccountId>,
726 pub nominator: Option<AccountId>,
728 pub bouncer: Option<AccountId>,
730}
731
732#[derive(
734 PartialEq,
735 Eq,
736 Copy,
737 Clone,
738 Encode,
739 Decode,
740 DecodeWithMemTracking,
741 Debug,
742 TypeInfo,
743 MaxEncodedLen,
744)]
745pub enum CommissionClaimPermission<AccountId> {
746 Permissionless,
747 Account(AccountId),
748}
749
750#[derive(
763 Encode,
764 Decode,
765 DecodeWithMemTracking,
766 DefaultNoBound,
767 MaxEncodedLen,
768 TypeInfo,
769 DebugNoBound,
770 PartialEq,
771 Copy,
772 Clone,
773)]
774#[codec(mel_bound(T: Config))]
775#[scale_info(skip_type_params(T))]
776pub struct Commission<T: Config> {
777 pub current: Option<(Perbill, T::AccountId)>,
779 pub max: Option<Perbill>,
782 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
785 pub throttle_from: Option<BlockNumberFor<T>>,
788 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
791}
792
793impl<T: Config> Commission<T> {
794 fn throttling(&self, to: &Perbill) -> bool {
801 if let Some(t) = self.change_rate.as_ref() {
802 let commission_as_percent =
803 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
804
805 if *to <= commission_as_percent {
807 return false
808 }
809 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
813 return true
814 }
815
816 return self.throttle_from.map_or_else(
821 || {
822 defensive!("throttle_from should exist if change_rate is set");
823 true
824 },
825 |f| {
826 if t.min_delay == Zero::zero() {
828 false
829 } else {
830 let blocks_surpassed =
832 T::BlockNumberProvider::current_block_number().saturating_sub(f);
833 blocks_surpassed < t.min_delay
834 }
835 },
836 )
837 }
838 false
839 }
840
841 fn current(&self) -> Perbill {
844 self.current
845 .as_ref()
846 .map_or(Perbill::zero(), |(c, _)| *c)
847 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
848 }
849
850 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
856 self.current = match current {
857 None => None,
858 Some((commission, payee)) => {
859 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
860 ensure!(
861 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
862 Error::<T>::CommissionExceedsGlobalMaximum
863 );
864 ensure!(
865 self.max.map_or(true, |m| commission <= &m),
866 Error::<T>::CommissionExceedsMaximum
867 );
868 if commission.is_zero() {
869 None
870 } else {
871 Some((*commission, payee.clone()))
872 }
873 },
874 };
875 self.register_update();
876 Ok(())
877 }
878
879 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
888 ensure!(
889 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
890 Error::<T>::CommissionExceedsGlobalMaximum
891 );
892 if let Some(old) = self.max.as_mut() {
893 if new_max > *old {
894 return Err(Error::<T>::MaxCommissionRestricted.into())
895 }
896 *old = new_max;
897 } else {
898 self.max = Some(new_max)
899 };
900 let updated_current = self
901 .current
902 .as_mut()
903 .map(|(c, _)| {
904 let u = *c > new_max;
905 *c = (*c).min(new_max);
906 u
907 })
908 .unwrap_or(false);
909
910 if updated_current {
911 if let Some((_, payee)) = self.current.as_ref() {
912 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
913 pool_id,
914 current: Some((new_max, payee.clone())),
915 });
916 }
917 self.register_update();
918 }
919 Ok(())
920 }
921
922 fn try_update_change_rate(
931 &mut self,
932 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
933 ) -> DispatchResult {
934 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
935
936 if self.change_rate.is_none() {
937 self.register_update();
938 }
939 self.change_rate = Some(change_rate);
940 Ok(())
941 }
942
943 fn register_update(&mut self) {
945 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
946 }
947
948 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
953 self.change_rate
954 .as_ref()
955 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
956 .unwrap_or(false)
957 }
958}
959
960#[derive(
968 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
969)]
970pub struct CommissionChangeRate<BlockNumber> {
971 pub max_increase: Perbill,
973 pub min_delay: BlockNumber,
975}
976
977#[derive(
979 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
980)]
981#[codec(mel_bound(T: Config))]
982#[scale_info(skip_type_params(T))]
983pub struct BondedPoolInner<T: Config> {
984 pub commission: Commission<T>,
986 pub member_counter: u32,
988 pub points: BalanceOf<T>,
990 pub roles: PoolRoles<T::AccountId>,
992 pub state: PoolState,
994}
995
996#[derive(DebugNoBound)]
1001#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1002pub struct BondedPool<T: Config> {
1003 id: PoolId,
1005 inner: BondedPoolInner<T>,
1007}
1008
1009impl<T: Config> core::ops::Deref for BondedPool<T> {
1010 type Target = BondedPoolInner<T>;
1011 fn deref(&self) -> &Self::Target {
1012 &self.inner
1013 }
1014}
1015
1016impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1017 fn deref_mut(&mut self) -> &mut Self::Target {
1018 &mut self.inner
1019 }
1020}
1021
1022impl<T: Config> BondedPool<T> {
1023 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1025 Self {
1026 id,
1027 inner: BondedPoolInner {
1028 commission: Commission::default(),
1029 member_counter: Zero::zero(),
1030 points: Zero::zero(),
1031 roles,
1032 state: PoolState::Open,
1033 },
1034 }
1035 }
1036
1037 pub fn get(id: PoolId) -> Option<Self> {
1039 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1040 }
1041
1042 fn bonded_account(&self) -> T::AccountId {
1044 Pallet::<T>::generate_bonded_account(self.id)
1045 }
1046
1047 fn reward_account(&self) -> T::AccountId {
1049 Pallet::<T>::generate_reward_account(self.id)
1050 }
1051
1052 fn put(self) {
1054 BondedPools::<T>::insert(self.id, self.inner);
1055 }
1056
1057 fn remove(self) {
1059 BondedPools::<T>::remove(self.id);
1060 }
1061
1062 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1066 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1067 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1068 }
1069
1070 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1074 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1075 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1076 }
1077
1078 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1080 let points_to_issue = self.balance_to_point(new_funds);
1081 self.points = self.points.saturating_add(points_to_issue);
1082 points_to_issue
1083 }
1084
1085 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1092 let balance = self.points_to_balance(points);
1095 self.points = self.points.saturating_sub(points);
1096 balance
1097 }
1098
1099 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1102 ensure!(
1103 MaxPoolMembersPerPool::<T>::get()
1104 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1105 Error::<T>::MaxPoolMembers
1106 );
1107 ensure!(
1108 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1109 Error::<T>::MaxPoolMembers
1110 );
1111 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1112 Ok(())
1113 }
1114
1115 fn dec_members(mut self) -> Self {
1117 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1118 self
1119 }
1120
1121 fn is_root(&self, who: &T::AccountId) -> bool {
1122 self.roles.root.as_ref().map_or(false, |root| root == who)
1123 }
1124
1125 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1126 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1127 }
1128
1129 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1130 self.is_root(who)
1131 }
1132
1133 fn can_nominate(&self, who: &T::AccountId) -> bool {
1134 self.is_root(who) ||
1135 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1136 }
1137
1138 fn can_kick(&self, who: &T::AccountId) -> bool {
1139 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1140 }
1141
1142 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1143 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1144 }
1145
1146 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1147 self.is_root(who) || self.is_bouncer(who)
1148 }
1149
1150 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1151 self.is_root(who)
1152 }
1153
1154 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1155 if let Some(permission) = self.commission.claim_permission.as_ref() {
1156 match permission {
1157 CommissionClaimPermission::Permissionless => true,
1158 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1159 }
1160 } else {
1161 self.is_root(who)
1162 }
1163 }
1164
1165 fn is_destroying(&self) -> bool {
1166 matches!(self.state, PoolState::Destroying)
1167 }
1168
1169 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1170 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1177 }
1178
1179 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1182 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1183
1184 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1185 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1186
1187 let points_to_balance_ratio_floor = self
1188 .points
1189 .div(bonded_balance);
1191
1192 let max_points_to_balance = T::MaxPointsToBalance::get();
1193
1194 ensure!(
1198 points_to_balance_ratio_floor < max_points_to_balance.into(),
1199 Error::<T>::OverflowRisk
1200 );
1201
1202 Ok(())
1206 }
1207
1208 fn ok_to_join(&self) -> Result<(), DispatchError> {
1210 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1211 self.ok_to_be_open()?;
1212 Ok(())
1213 }
1214
1215 fn ok_to_unbond_with(
1216 &self,
1217 caller: &T::AccountId,
1218 target_account: &T::AccountId,
1219 target_member: &PoolMember<T>,
1220 unbonding_points: BalanceOf<T>,
1221 ) -> Result<(), DispatchError> {
1222 let is_permissioned = caller == target_account;
1223 let is_depositor = *target_account == self.roles.depositor;
1224 let is_full_unbond = unbonding_points == target_member.active_points();
1225
1226 let balance_after_unbond = {
1227 let new_depositor_points =
1228 target_member.active_points().saturating_sub(unbonding_points);
1229 let mut target_member_after_unbond = (*target_member).clone();
1230 target_member_after_unbond.points = new_depositor_points;
1231 target_member_after_unbond.active_balance()
1232 };
1233
1234 ensure!(
1236 is_permissioned || is_full_unbond,
1237 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1238 );
1239
1240 ensure!(
1242 is_full_unbond ||
1243 balance_after_unbond >=
1244 if is_depositor {
1245 Pallet::<T>::depositor_min_bond()
1246 } else {
1247 MinJoinBond::<T>::get()
1248 },
1249 Error::<T>::MinimumBondNotMet
1250 );
1251
1252 match (is_permissioned, is_depositor) {
1254 (true, false) => (),
1255 (true, true) => {
1256 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1259 } else {
1261 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1263 }
1264 },
1265 (false, false) => {
1266 debug_assert!(is_full_unbond);
1269 ensure!(
1270 self.can_kick(caller) || self.is_destroying(),
1271 Error::<T>::NotKickerOrDestroying
1272 )
1273 },
1274 (false, true) => {
1275 return Err(Error::<T>::DoesNotHavePermission.into())
1277 },
1278 };
1279
1280 Ok(())
1281 }
1282
1283 fn ok_to_withdraw_unbonded_with(
1287 &self,
1288 caller: &T::AccountId,
1289 target_account: &T::AccountId,
1290 ) -> Result<(), DispatchError> {
1291 let is_permissioned = caller == target_account;
1293 ensure!(
1294 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1295 Error::<T>::NotKickerOrDestroying
1296 );
1297 Ok(())
1298 }
1299
1300 fn try_bond_funds(
1308 &mut self,
1309 who: &T::AccountId,
1310 amount: BalanceOf<T>,
1311 ty: BondType,
1312 ) -> Result<BalanceOf<T>, DispatchError> {
1313 let points_issued = self.issue(amount);
1316
1317 T::StakeAdapter::pledge_bond(
1318 Member::from(who.clone()),
1319 Pool::from(self.bonded_account()),
1320 &self.reward_account(),
1321 amount,
1322 ty,
1323 )?;
1324 TotalValueLocked::<T>::mutate(|tvl| {
1325 tvl.saturating_accrue(amount);
1326 });
1327
1328 Ok(points_issued)
1329 }
1330
1331 fn set_state(&mut self, state: PoolState) {
1334 if self.state != state {
1335 self.state = state;
1336 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1337 pool_id: self.id,
1338 new_state: state,
1339 });
1340 };
1341 }
1342}
1343
1344#[derive(
1350 Encode,
1351 Decode,
1352 MaxEncodedLen,
1353 DecodeWithMemTracking,
1354 TypeInfo,
1355 CloneNoBound,
1356 PartialEqNoBound,
1357 EqNoBound,
1358 DebugNoBound,
1359)]
1360#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1361#[codec(mel_bound(T: Config))]
1362#[scale_info(skip_type_params(T))]
1363pub struct RewardPool<T: Config> {
1364 pub last_recorded_reward_counter: T::RewardCounter,
1369 pub last_recorded_total_payouts: BalanceOf<T>,
1375 pub total_rewards_claimed: BalanceOf<T>,
1377 pub total_commission_pending: BalanceOf<T>,
1379 pub total_commission_claimed: BalanceOf<T>,
1381}
1382
1383impl<T: Config> RewardPool<T> {
1384 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1386 self.last_recorded_reward_counter
1387 }
1388
1389 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1391 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1392 }
1393
1394 fn update_records(
1401 &mut self,
1402 id: PoolId,
1403 bonded_points: BalanceOf<T>,
1404 commission: Perbill,
1405 ) -> Result<(), Error<T>> {
1406 let balance = Self::current_balance(id);
1407
1408 let (current_reward_counter, new_pending_commission) =
1409 self.current_reward_counter(id, bonded_points, commission)?;
1410
1411 self.last_recorded_reward_counter = current_reward_counter;
1415
1416 self.total_commission_pending =
1419 self.total_commission_pending.saturating_add(new_pending_commission);
1420
1421 let last_recorded_total_payouts = balance
1425 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1426 .ok_or(Error::<T>::OverflowRisk)?;
1427
1428 self.last_recorded_total_payouts =
1435 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1436
1437 Ok(())
1438 }
1439
1440 fn current_reward_counter(
1443 &self,
1444 id: PoolId,
1445 bonded_points: BalanceOf<T>,
1446 commission: Perbill,
1447 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1448 let balance = Self::current_balance(id);
1449
1450 let current_payout_balance = balance
1455 .saturating_add(self.total_rewards_claimed)
1456 .saturating_add(self.total_commission_claimed)
1457 .saturating_sub(self.last_recorded_total_payouts);
1458
1459 let new_pending_commission = commission * current_payout_balance;
1462 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1463
1464 let current_reward_counter =
1499 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1500 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1501 .ok_or(Error::<T>::OverflowRisk)?;
1502
1503 Ok((current_reward_counter, new_pending_commission))
1504 }
1505
1506 fn current_balance(id: PoolId) -> BalanceOf<T> {
1510 T::Currency::reducible_balance(
1511 &Pallet::<T>::generate_reward_account(id),
1512 Preservation::Expendable,
1513 Fortitude::Polite,
1514 )
1515 }
1516}
1517
1518#[derive(
1520 Encode,
1521 Decode,
1522 MaxEncodedLen,
1523 DecodeWithMemTracking,
1524 TypeInfo,
1525 DefaultNoBound,
1526 DebugNoBound,
1527 CloneNoBound,
1528 PartialEqNoBound,
1529 EqNoBound,
1530)]
1531#[codec(mel_bound(T: Config))]
1532#[scale_info(skip_type_params(T))]
1533pub struct UnbondPool<T: Config> {
1534 pub points: BalanceOf<T>,
1536 pub balance: BalanceOf<T>,
1538}
1539
1540impl<T: Config> UnbondPool<T> {
1541 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1542 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1543 }
1544
1545 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1546 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1547 }
1548
1549 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1553 let new_points = self.balance_to_point(new_funds);
1554 self.points = self.points.saturating_add(new_points);
1555 self.balance = self.balance.saturating_add(new_funds);
1556 new_points
1557 }
1558
1559 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1564 let balance_to_unbond = self.point_to_balance(points);
1565 self.points = self.points.saturating_sub(points);
1566 self.balance = self.balance.saturating_sub(balance_to_unbond);
1567
1568 balance_to_unbond
1569 }
1570}
1571
1572#[derive(
1573 Encode,
1574 Decode,
1575 MaxEncodedLen,
1576 DecodeWithMemTracking,
1577 TypeInfo,
1578 DefaultNoBound,
1579 DebugNoBound,
1580 CloneNoBound,
1581 PartialEqNoBound,
1582 EqNoBound,
1583)]
1584#[codec(mel_bound(T: Config))]
1585#[scale_info(skip_type_params(T))]
1586pub struct SubPools<T: Config> {
1587 pub no_era: UnbondPool<T>,
1591 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1593}
1594
1595impl<T: Config> SubPools<T> {
1596 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1601 if let Some(newest_era_to_remove) =
1605 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1606 {
1607 self.with_era.retain(|k, v| {
1608 if *k > newest_era_to_remove {
1609 true
1611 } else {
1612 self.no_era.points = self.no_era.points.saturating_add(v.points);
1614 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1615 false
1616 }
1617 });
1618 }
1619
1620 self
1621 }
1622
1623 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1625 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1626 self.no_era.balance.saturating_add(
1627 self.with_era
1628 .values()
1629 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1630 )
1631 }
1632}
1633
1634pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1638
1639impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1640 fn get() -> u32 {
1641 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1645 }
1646}
1647
1648#[frame_support::pallet]
1649pub mod pallet {
1650 use super::*;
1651 use frame_support::traits::StorageVersion;
1652 use frame_system::pallet_prelude::{
1653 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1654 };
1655 use sp_runtime::Perbill;
1656
1657 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1659
1660 #[pallet::pallet]
1661 #[pallet::storage_version(STORAGE_VERSION)]
1662 pub struct Pallet<T>(_);
1663
1664 #[pallet::config]
1665 pub trait Config: frame_system::Config {
1666 #[allow(deprecated)]
1668 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1669
1670 type WeightInfo: weights::WeightInfo;
1672
1673 type Currency: Mutate<Self::AccountId>
1675 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1676
1677 type RuntimeFreezeReason: From<FreezeReason>;
1679
1680 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1693
1694 #[pallet::constant]
1696 type PalletId: Get<frame_support::PalletId>;
1697
1698 #[pallet::constant]
1711 type MaxPointsToBalance: Get<u8>;
1712
1713 #[pallet::constant]
1715 type MaxUnbonding: Get<u32>;
1716
1717 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1719
1720 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1722
1723 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1727
1728 type PostUnbondingPoolsWindow: Get<u32>;
1734
1735 type MaxMetadataLen: Get<u32>;
1737
1738 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1740
1741 type BlockNumberProvider: BlockNumberProvider;
1743
1744 type Filter: Contains<Self::AccountId>;
1746 }
1747
1748 #[pallet::storage]
1754 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1755
1756 #[pallet::storage]
1758 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1759
1760 #[pallet::storage]
1768 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1769
1770 #[pallet::storage]
1773 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1774
1775 #[pallet::storage]
1778 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1779
1780 #[pallet::storage]
1783 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1784
1785 #[pallet::storage]
1789 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1790
1791 #[pallet::storage]
1795 pub type PoolMembers<T: Config> =
1796 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1797
1798 #[pallet::storage]
1801 pub type BondedPools<T: Config> =
1802 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1803
1804 #[pallet::storage]
1807 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1808
1809 #[pallet::storage]
1812 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1813
1814 #[pallet::storage]
1816 pub type Metadata<T: Config> =
1817 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1818
1819 #[pallet::storage]
1821 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1822
1823 #[pallet::storage]
1828 pub type ReversePoolIdLookup<T: Config> =
1829 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1830
1831 #[pallet::storage]
1833 pub type ClaimPermissions<T: Config> =
1834 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1835
1836 #[pallet::genesis_config]
1837 pub struct GenesisConfig<T: Config> {
1838 pub min_join_bond: BalanceOf<T>,
1839 pub min_create_bond: BalanceOf<T>,
1840 pub max_pools: Option<u32>,
1841 pub max_members_per_pool: Option<u32>,
1842 pub max_members: Option<u32>,
1843 pub global_max_commission: Option<Perbill>,
1844 }
1845
1846 impl<T: Config> Default for GenesisConfig<T> {
1847 fn default() -> Self {
1848 Self {
1849 min_join_bond: Zero::zero(),
1850 min_create_bond: Zero::zero(),
1851 max_pools: Some(16),
1852 max_members_per_pool: Some(32),
1853 max_members: Some(16 * 32),
1854 global_max_commission: None,
1855 }
1856 }
1857 }
1858
1859 #[pallet::genesis_build]
1860 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1861 fn build(&self) {
1862 MinJoinBond::<T>::put(self.min_join_bond);
1863 MinCreateBond::<T>::put(self.min_create_bond);
1864
1865 if let Some(max_pools) = self.max_pools {
1866 MaxPools::<T>::put(max_pools);
1867 }
1868 if let Some(max_members_per_pool) = self.max_members_per_pool {
1869 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1870 }
1871 if let Some(max_members) = self.max_members {
1872 MaxPoolMembers::<T>::put(max_members);
1873 }
1874 if let Some(global_max_commission) = self.global_max_commission {
1875 GlobalMaxCommission::<T>::put(global_max_commission);
1876 }
1877 }
1878 }
1879
1880 #[pallet::event]
1882 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1883 pub enum Event<T: Config> {
1884 Created { depositor: T::AccountId, pool_id: PoolId },
1886 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1888 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1890 Unbonded {
1902 member: T::AccountId,
1903 pool_id: PoolId,
1904 balance: BalanceOf<T>,
1905 points: BalanceOf<T>,
1906 era: EraIndex,
1907 },
1908 Withdrawn {
1915 member: T::AccountId,
1916 pool_id: PoolId,
1917 balance: BalanceOf<T>,
1918 points: BalanceOf<T>,
1919 },
1920 Destroyed { pool_id: PoolId },
1922 StateChanged { pool_id: PoolId, new_state: PoolState },
1924 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1930 RolesUpdated {
1933 root: Option<T::AccountId>,
1934 bouncer: Option<T::AccountId>,
1935 nominator: Option<T::AccountId>,
1936 },
1937 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1939 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1941 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1943 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1945 PoolCommissionChangeRateUpdated {
1947 pool_id: PoolId,
1948 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1949 },
1950 PoolCommissionClaimPermissionUpdated {
1952 pool_id: PoolId,
1953 permission: Option<CommissionClaimPermission<T::AccountId>>,
1954 },
1955 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1957 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1959 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1961 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1963 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1965 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1968 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1970 GlobalParamsUpdated {
1972 min_join_bond: BalanceOf<T>,
1973 min_create_bond: BalanceOf<T>,
1974 max_pools: Option<u32>,
1975 max_members: Option<u32>,
1976 max_members_per_pool: Option<u32>,
1977 global_max_commission: Option<Perbill>,
1978 },
1979 }
1980
1981 #[pallet::error]
1982 #[cfg_attr(test, derive(PartialEq))]
1983 pub enum Error<T> {
1984 PoolNotFound,
1986 PoolMemberNotFound,
1988 RewardPoolNotFound,
1990 SubPoolsNotFound,
1992 AccountBelongsToOtherPool,
1995 FullyUnbonding,
1998 MaxUnbondingLimit,
2000 CannotWithdrawAny,
2002 MinimumBondNotMet,
2008 OverflowRisk,
2010 NotDestroying,
2013 NotNominator,
2015 NotKickerOrDestroying,
2017 NotOpen,
2019 MaxPools,
2021 MaxPoolMembers,
2023 CanNotChangeState,
2025 DoesNotHavePermission,
2027 MetadataExceedsMaxLen,
2029 Defensive(DefensiveError),
2032 PartialUnbondNotAllowedPermissionlessly,
2034 MaxCommissionRestricted,
2036 CommissionExceedsMaximum,
2038 CommissionExceedsGlobalMaximum,
2040 CommissionChangeThrottled,
2042 CommissionChangeRateNotAllowed,
2044 NoPendingCommission,
2046 NoCommissionCurrentSet,
2048 PoolIdInUse,
2050 InvalidPoolId,
2052 BondExtraRestricted,
2054 NothingToAdjust,
2056 NothingToSlash,
2058 SlashTooLow,
2060 AlreadyMigrated,
2062 NotMigrated,
2064 NotSupported,
2066 Restricted,
2069 }
2070
2071 #[derive(Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, Debug)]
2072 pub enum DefensiveError {
2073 NotEnoughSpaceInUnbondPool,
2075 PoolNotFound,
2077 RewardPoolNotFound,
2079 SubPoolsNotFound,
2081 BondedStashKilledPrematurely,
2084 DelegationUnsupported,
2086 SlashNotApplied,
2088 }
2089
2090 impl<T> From<DefensiveError> for Error<T> {
2091 fn from(e: DefensiveError) -> Error<T> {
2092 Error::<T>::Defensive(e)
2093 }
2094 }
2095
2096 #[pallet::composite_enum]
2098 pub enum FreezeReason {
2099 #[codec(index = 0)]
2101 PoolMinBalance,
2102 }
2103
2104 #[pallet::call]
2105 impl<T: Config> Pallet<T> {
2106 #[pallet::call_index(0)]
2123 #[pallet::weight(T::WeightInfo::join())]
2124 pub fn join(
2125 origin: OriginFor<T>,
2126 #[pallet::compact] amount: BalanceOf<T>,
2127 pool_id: PoolId,
2128 ) -> DispatchResult {
2129 let who = ensure_signed(origin)?;
2130 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2132
2133 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2135
2136 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2137 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2139
2140 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2141 bonded_pool.ok_to_join()?;
2142
2143 let mut reward_pool = RewardPools::<T>::get(pool_id)
2144 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2145 reward_pool.update_records(
2147 pool_id,
2148 bonded_pool.points,
2149 bonded_pool.commission.current(),
2150 )?;
2151
2152 bonded_pool.try_inc_members()?;
2153 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2154
2155 PoolMembers::insert(
2156 who.clone(),
2157 PoolMember::<T> {
2158 pool_id,
2159 points: points_issued,
2160 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2163 unbonding_eras: Default::default(),
2164 },
2165 );
2166
2167 Self::deposit_event(Event::<T>::Bonded {
2168 member: who,
2169 pool_id,
2170 bonded: amount,
2171 joined: true,
2172 });
2173
2174 bonded_pool.put();
2175 RewardPools::<T>::insert(pool_id, reward_pool);
2176
2177 Ok(())
2178 }
2179
2180 #[pallet::call_index(1)]
2191 #[pallet::weight(
2192 T::WeightInfo::bond_extra_transfer()
2193 .max(T::WeightInfo::bond_extra_other())
2194 )]
2195 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2196 let who = ensure_signed(origin)?;
2197
2198 ensure!(
2200 !Self::api_member_needs_delegate_migration(who.clone()),
2201 Error::<T>::NotMigrated
2202 );
2203
2204 Self::do_bond_extra(who.clone(), who, extra)
2205 }
2206
2207 #[pallet::call_index(2)]
2216 #[pallet::weight(T::WeightInfo::claim_payout())]
2217 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2218 let signer = ensure_signed(origin)?;
2219 ensure!(
2221 !Self::api_member_needs_delegate_migration(signer.clone()),
2222 Error::<T>::NotMigrated
2223 );
2224
2225 Self::do_claim_payout(signer.clone(), signer)
2226 }
2227
2228 #[pallet::call_index(3)]
2260 #[pallet::weight(T::WeightInfo::unbond())]
2261 pub fn unbond(
2262 origin: OriginFor<T>,
2263 member_account: AccountIdLookupOf<T>,
2264 #[pallet::compact] unbonding_points: BalanceOf<T>,
2265 ) -> DispatchResult {
2266 let who = ensure_signed(origin)?;
2267 let member_account = T::Lookup::lookup(member_account)?;
2268 ensure!(
2270 !Self::api_member_needs_delegate_migration(member_account.clone()),
2271 Error::<T>::NotMigrated
2272 );
2273
2274 let (mut member, mut bonded_pool, mut reward_pool) =
2275 Self::get_member_with_pools(&member_account)?;
2276
2277 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2278
2279 reward_pool.update_records(
2283 bonded_pool.id,
2284 bonded_pool.points,
2285 bonded_pool.commission.current(),
2286 )?;
2287 Self::do_reward_payout(
2288 &member_account,
2289 &mut member,
2290 &mut bonded_pool,
2291 &mut reward_pool,
2292 )?;
2293
2294 let current_era = T::StakeAdapter::current_era();
2295 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
2296
2297 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2299 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2300
2301 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2303 .unwrap_or_default()
2304 .maybe_merge_pools(current_era);
2305
2306 if !sub_pools.with_era.contains_key(&unbond_era) {
2309 sub_pools
2310 .with_era
2311 .try_insert(unbond_era, UnbondPool::default())
2312 .defensive_map_err::<Error<T>, _>(|_| {
2315 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2316 })?;
2317 }
2318
2319 let points_unbonded = sub_pools
2320 .with_era
2321 .get_mut(&unbond_era)
2322 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2324 .issue(unbonding_balance);
2325
2326 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2328
2329 Self::deposit_event(Event::<T>::Unbonded {
2330 member: member_account.clone(),
2331 pool_id: member.pool_id,
2332 points: points_unbonded,
2333 balance: unbonding_balance,
2334 era: unbond_era,
2335 });
2336
2337 SubPoolsStorage::insert(member.pool_id, sub_pools);
2339 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2340 Ok(())
2341 }
2342
2343 #[pallet::call_index(4)]
2350 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2351 pub fn pool_withdraw_unbonded(
2352 origin: OriginFor<T>,
2353 pool_id: PoolId,
2354 num_slashing_spans: u32,
2355 ) -> DispatchResult {
2356 ensure_signed(origin)?;
2357 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2359
2360 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2361
2362 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2365 T::StakeAdapter::withdraw_unbonded(
2366 Pool::from(pool.bonded_account()),
2367 num_slashing_spans,
2368 )?;
2369
2370 Ok(())
2371 }
2372
2373 #[pallet::call_index(5)]
2396 #[pallet::weight(
2397 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2398 )]
2399 pub fn withdraw_unbonded(
2400 origin: OriginFor<T>,
2401 member_account: AccountIdLookupOf<T>,
2402 num_slashing_spans: u32,
2403 ) -> DispatchResultWithPostInfo {
2404 let caller = ensure_signed(origin)?;
2405 let member_account = T::Lookup::lookup(member_account)?;
2406 ensure!(
2408 !Self::api_member_needs_delegate_migration(member_account.clone()),
2409 Error::<T>::NotMigrated
2410 );
2411
2412 let mut member =
2413 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2414 let current_era = T::StakeAdapter::current_era();
2415
2416 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2417 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2418 let mut sub_pools =
2419 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2420
2421 let slash_weight =
2422 match Self::do_apply_slash(&member_account, None, false) {
2424 Ok(_) => T::WeightInfo::apply_slash(),
2425 Err(e) => {
2426 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2427 if Err(e) == no_pending_slash {
2429 T::WeightInfo::apply_slash_fail()
2430 } else {
2431 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2433 }
2434 }
2435
2436 };
2437
2438 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2439 let pool_account = bonded_pool.bonded_account();
2440
2441 let withdrawn_points = member.withdraw_unlocked(current_era);
2443 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2444
2445 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2448 Pool::from(bonded_pool.bonded_account()),
2449 num_slashing_spans,
2450 )?;
2451
2452 ensure!(
2455 !stash_killed || caller == bonded_pool.roles.depositor,
2456 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2457 );
2458
2459 if stash_killed {
2460 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2462 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2463 }
2464
2465 }
2472
2473 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2474 let balance_to_unbond = withdrawn_points
2475 .iter()
2476 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2477 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2478 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2479 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2480 if era_pool.points.is_zero() {
2481 sub_pools.with_era.remove(era);
2482 }
2483 accumulator.saturating_add(balance_to_unbond)
2484 } else {
2485 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2488 }
2489 })
2490 .min(T::StakeAdapter::transferable_balance(
2498 Pool::from(bonded_pool.bonded_account()),
2499 Member::from(member_account.clone()),
2500 ));
2501
2502 T::StakeAdapter::member_withdraw(
2505 Member::from(member_account.clone()),
2506 Pool::from(bonded_pool.bonded_account()),
2507 balance_to_unbond,
2508 num_slashing_spans,
2509 )?;
2510
2511 Self::deposit_event(Event::<T>::Withdrawn {
2512 member: member_account.clone(),
2513 pool_id: member.pool_id,
2514 points: sum_unlocked_points,
2515 balance: balance_to_unbond,
2516 });
2517
2518 let post_info_weight = if member.total_points().is_zero() {
2519 ClaimPermissions::<T>::remove(&member_account);
2521
2522 PoolMembers::<T>::remove(&member_account);
2524
2525 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2527 Member::from(member_account.clone()),
2528 ) {
2529 Some(dangling_delegation) => {
2530 T::StakeAdapter::member_withdraw(
2531 Member::from(member_account.clone()),
2532 Pool::from(bonded_pool.bonded_account()),
2533 dangling_delegation,
2534 num_slashing_spans,
2535 )?;
2536 dangling_delegation
2537 },
2538 None => Zero::zero(),
2539 };
2540
2541 Self::deposit_event(Event::<T>::MemberRemoved {
2542 pool_id: member.pool_id,
2543 member: member_account.clone(),
2544 released_balance: dangling_withdrawal,
2545 });
2546
2547 if member_account == bonded_pool.roles.depositor {
2548 Pallet::<T>::dissolve_pool(bonded_pool);
2549 Weight::default()
2550 } else {
2551 bonded_pool.dec_members().put();
2552 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2553 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2554 }
2555 } else {
2556 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2558 PoolMembers::<T>::insert(&member_account, member);
2559 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2560 };
2561
2562 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2563 }
2564
2565 #[pallet::call_index(6)]
2583 #[pallet::weight(T::WeightInfo::create())]
2584 pub fn create(
2585 origin: OriginFor<T>,
2586 #[pallet::compact] amount: BalanceOf<T>,
2587 root: AccountIdLookupOf<T>,
2588 nominator: AccountIdLookupOf<T>,
2589 bouncer: AccountIdLookupOf<T>,
2590 ) -> DispatchResult {
2591 let depositor = ensure_signed(origin)?;
2592
2593 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2594 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2595 Ok(*id)
2596 })?;
2597
2598 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2599 }
2600
2601 #[pallet::call_index(7)]
2608 #[pallet::weight(T::WeightInfo::create())]
2609 pub fn create_with_pool_id(
2610 origin: OriginFor<T>,
2611 #[pallet::compact] amount: BalanceOf<T>,
2612 root: AccountIdLookupOf<T>,
2613 nominator: AccountIdLookupOf<T>,
2614 bouncer: AccountIdLookupOf<T>,
2615 pool_id: PoolId,
2616 ) -> DispatchResult {
2617 let depositor = ensure_signed(origin)?;
2618
2619 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2620 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2621
2622 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2623 }
2624
2625 #[pallet::call_index(8)]
2638 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2639 pub fn nominate(
2640 origin: OriginFor<T>,
2641 pool_id: PoolId,
2642 validators: Vec<T::AccountId>,
2643 ) -> DispatchResult {
2644 let who = ensure_signed(origin)?;
2645 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2646 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2648 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2649
2650 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2651 .ok_or(Error::<T>::PoolMemberNotFound)?
2652 .active_points();
2653
2654 ensure!(
2655 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2656 Error::<T>::MinimumBondNotMet
2657 );
2658
2659 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2660 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2661 )
2662 }
2663
2664 #[pallet::call_index(9)]
2675 #[pallet::weight(T::WeightInfo::set_state())]
2676 pub fn set_state(
2677 origin: OriginFor<T>,
2678 pool_id: PoolId,
2679 state: PoolState,
2680 ) -> DispatchResult {
2681 let who = ensure_signed(origin)?;
2682 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2683 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2684 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2686
2687 if bonded_pool.can_toggle_state(&who) {
2688 bonded_pool.set_state(state);
2689 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2690 bonded_pool.set_state(PoolState::Destroying);
2692 } else {
2693 Err(Error::<T>::CanNotChangeState)?;
2694 }
2695
2696 bonded_pool.put();
2697
2698 Ok(())
2699 }
2700
2701 #[pallet::call_index(10)]
2706 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2707 pub fn set_metadata(
2708 origin: OriginFor<T>,
2709 pool_id: PoolId,
2710 metadata: Vec<u8>,
2711 ) -> DispatchResult {
2712 let who = ensure_signed(origin)?;
2713 let metadata: BoundedVec<_, _> =
2714 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2715 ensure!(
2716 BondedPool::<T>::get(pool_id)
2717 .ok_or(Error::<T>::PoolNotFound)?
2718 .can_set_metadata(&who),
2719 Error::<T>::DoesNotHavePermission
2720 );
2721 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2723
2724 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2725
2726 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2727
2728 Ok(())
2729 }
2730
2731 #[pallet::call_index(11)]
2743 #[pallet::weight(T::WeightInfo::set_configs())]
2744 pub fn set_configs(
2745 origin: OriginFor<T>,
2746 min_join_bond: ConfigOp<BalanceOf<T>>,
2747 min_create_bond: ConfigOp<BalanceOf<T>>,
2748 max_pools: ConfigOp<u32>,
2749 max_members: ConfigOp<u32>,
2750 max_members_per_pool: ConfigOp<u32>,
2751 global_max_commission: ConfigOp<Perbill>,
2752 ) -> DispatchResult {
2753 T::AdminOrigin::ensure_origin(origin)?;
2754
2755 macro_rules! config_op_exp {
2756 ($storage:ty, $op:ident) => {
2757 match $op {
2758 ConfigOp::Noop => (),
2759 ConfigOp::Set(v) => <$storage>::put(v),
2760 ConfigOp::Remove => <$storage>::kill(),
2761 }
2762 };
2763 }
2764
2765 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2766 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2767 config_op_exp!(MaxPools::<T>, max_pools);
2768 config_op_exp!(MaxPoolMembers::<T>, max_members);
2769 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2770 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2771
2772 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2773 min_join_bond: MinJoinBond::<T>::get(),
2774 min_create_bond: MinCreateBond::<T>::get(),
2775 max_pools: MaxPools::<T>::get(),
2776 max_members: MaxPoolMembers::<T>::get(),
2777 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2778 global_max_commission: GlobalMaxCommission::<T>::get(),
2779 });
2780
2781 Ok(())
2782 }
2783
2784 #[pallet::call_index(12)]
2792 #[pallet::weight(T::WeightInfo::update_roles())]
2793 pub fn update_roles(
2794 origin: OriginFor<T>,
2795 pool_id: PoolId,
2796 new_root: ConfigOp<T::AccountId>,
2797 new_nominator: ConfigOp<T::AccountId>,
2798 new_bouncer: ConfigOp<T::AccountId>,
2799 ) -> DispatchResult {
2800 let mut bonded_pool = match ensure_root(origin.clone()) {
2801 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2802 Err(sp_runtime::traits::BadOrigin) => {
2803 let who = ensure_signed(origin)?;
2804 let bonded_pool =
2805 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2806 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2807 bonded_pool
2808 },
2809 };
2810
2811 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2813
2814 match new_root {
2815 ConfigOp::Noop => (),
2816 ConfigOp::Remove => bonded_pool.roles.root = None,
2817 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2818 };
2819 match new_nominator {
2820 ConfigOp::Noop => (),
2821 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2822 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2823 };
2824 match new_bouncer {
2825 ConfigOp::Noop => (),
2826 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2827 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2828 };
2829
2830 Self::deposit_event(Event::<T>::RolesUpdated {
2831 root: bonded_pool.roles.root.clone(),
2832 nominator: bonded_pool.roles.nominator.clone(),
2833 bouncer: bonded_pool.roles.bouncer.clone(),
2834 });
2835
2836 bonded_pool.put();
2837 Ok(())
2838 }
2839
2840 #[pallet::call_index(13)]
2858 #[pallet::weight(T::WeightInfo::chill())]
2859 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2860 let who = ensure_signed(origin)?;
2861 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2862 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2864
2865 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2866 .ok_or(Error::<T>::PoolMemberNotFound)?
2867 .active_points();
2868
2869 if bonded_pool.points_to_balance(depositor_points) >=
2870 T::StakeAdapter::minimum_nominator_bond()
2871 {
2872 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2873 }
2874
2875 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2876 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2877 })
2878 }
2879
2880 #[pallet::call_index(14)]
2890 #[pallet::weight(
2891 T::WeightInfo::bond_extra_transfer()
2892 .max(T::WeightInfo::bond_extra_other())
2893 )]
2894 pub fn bond_extra_other(
2895 origin: OriginFor<T>,
2896 member: AccountIdLookupOf<T>,
2897 extra: BondExtra<BalanceOf<T>>,
2898 ) -> DispatchResult {
2899 let who = ensure_signed(origin)?;
2900 let member_account = T::Lookup::lookup(member)?;
2901 ensure!(
2903 !Self::api_member_needs_delegate_migration(member_account.clone()),
2904 Error::<T>::NotMigrated
2905 );
2906
2907 Self::do_bond_extra(who, member_account, extra)
2908 }
2909
2910 #[pallet::call_index(15)]
2918 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2919 pub fn set_claim_permission(
2920 origin: OriginFor<T>,
2921 permission: ClaimPermission,
2922 ) -> DispatchResult {
2923 let who = ensure_signed(origin)?;
2924 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2925
2926 ensure!(
2928 !Self::api_member_needs_delegate_migration(who.clone()),
2929 Error::<T>::NotMigrated
2930 );
2931
2932 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2933 *source = permission;
2934 });
2935
2936 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2937 member: who,
2938 permission,
2939 });
2940
2941 Ok(())
2942 }
2943
2944 #[pallet::call_index(16)]
2949 #[pallet::weight(T::WeightInfo::claim_payout())]
2950 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2951 let signer = ensure_signed(origin)?;
2952 ensure!(
2954 !Self::api_member_needs_delegate_migration(other.clone()),
2955 Error::<T>::NotMigrated
2956 );
2957
2958 Self::do_claim_payout(signer, other)
2959 }
2960
2961 #[pallet::call_index(17)]
2968 #[pallet::weight(T::WeightInfo::set_commission())]
2969 pub fn set_commission(
2970 origin: OriginFor<T>,
2971 pool_id: PoolId,
2972 new_commission: Option<(Perbill, T::AccountId)>,
2973 ) -> DispatchResult {
2974 let who = ensure_signed(origin)?;
2975 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2976 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2978
2979 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2980
2981 let mut reward_pool = RewardPools::<T>::get(pool_id)
2982 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2983 reward_pool.update_records(
2986 pool_id,
2987 bonded_pool.points,
2988 bonded_pool.commission.current(),
2989 )?;
2990 RewardPools::insert(pool_id, reward_pool);
2991
2992 bonded_pool.commission.try_update_current(&new_commission)?;
2993 bonded_pool.put();
2994 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
2995 pool_id,
2996 current: new_commission,
2997 });
2998 Ok(())
2999 }
3000
3001 #[pallet::call_index(18)]
3007 #[pallet::weight(T::WeightInfo::set_commission_max())]
3008 pub fn set_commission_max(
3009 origin: OriginFor<T>,
3010 pool_id: PoolId,
3011 max_commission: Perbill,
3012 ) -> DispatchResult {
3013 let who = ensure_signed(origin)?;
3014 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3015 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3017
3018 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3019
3020 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3021 bonded_pool.put();
3022
3023 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3024 Ok(())
3025 }
3026
3027 #[pallet::call_index(19)]
3032 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3033 pub fn set_commission_change_rate(
3034 origin: OriginFor<T>,
3035 pool_id: PoolId,
3036 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3037 ) -> DispatchResult {
3038 let who = ensure_signed(origin)?;
3039 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3040 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3042 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3043
3044 bonded_pool.commission.try_update_change_rate(change_rate)?;
3045 bonded_pool.put();
3046
3047 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3048 pool_id,
3049 change_rate,
3050 });
3051 Ok(())
3052 }
3053
3054 #[pallet::call_index(20)]
3071 #[pallet::weight(T::WeightInfo::claim_commission())]
3072 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3073 let who = ensure_signed(origin)?;
3074 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3076
3077 Self::do_claim_commission(who, pool_id)
3078 }
3079
3080 #[pallet::call_index(21)]
3088 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3089 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3090 let who = ensure_signed(origin)?;
3091 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3093
3094 Self::do_adjust_pool_deposit(who, pool_id)
3095 }
3096
3097 #[pallet::call_index(22)]
3102 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3103 pub fn set_commission_claim_permission(
3104 origin: OriginFor<T>,
3105 pool_id: PoolId,
3106 permission: Option<CommissionClaimPermission<T::AccountId>>,
3107 ) -> DispatchResult {
3108 let who = ensure_signed(origin)?;
3109 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3110 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3112 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3113
3114 bonded_pool.commission.claim_permission = permission.clone();
3115 bonded_pool.put();
3116
3117 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3118 pool_id,
3119 permission,
3120 });
3121
3122 Ok(())
3123 }
3124
3125 #[pallet::call_index(23)]
3135 #[pallet::weight(T::WeightInfo::apply_slash())]
3136 pub fn apply_slash(
3137 origin: OriginFor<T>,
3138 member_account: AccountIdLookupOf<T>,
3139 ) -> DispatchResultWithPostInfo {
3140 ensure!(
3141 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3142 Error::<T>::NotSupported
3143 );
3144
3145 let who = ensure_signed(origin)?;
3146 let member_account = T::Lookup::lookup(member_account)?;
3147 Self::do_apply_slash(&member_account, Some(who), true)?;
3148
3149 Ok(Pays::No.into())
3151 }
3152
3153 #[pallet::call_index(24)]
3163 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3164 pub fn migrate_delegation(
3165 origin: OriginFor<T>,
3166 member_account: AccountIdLookupOf<T>,
3167 ) -> DispatchResultWithPostInfo {
3168 let _caller = ensure_signed(origin)?;
3169
3170 ensure!(
3172 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3173 Error::<T>::NotSupported
3174 );
3175
3176 let member_account = T::Lookup::lookup(member_account)?;
3178 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3179
3180 let member =
3181 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3182
3183 ensure!(
3185 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3186 member.pool_id
3187 ))) == adapter::StakeStrategyType::Delegate,
3188 Error::<T>::NotMigrated
3189 );
3190
3191 let pool_contribution = member.total_balance();
3192 ensure!(
3195 pool_contribution >= T::Currency::minimum_balance(),
3196 Error::<T>::MinimumBondNotMet
3197 );
3198
3199 let delegation =
3200 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3201 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3203
3204 T::StakeAdapter::migrate_delegation(
3205 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3206 Member::from(member_account),
3207 pool_contribution,
3208 )?;
3209
3210 Ok(Pays::No.into())
3212 }
3213
3214 #[pallet::call_index(25)]
3224 #[pallet::weight(T::WeightInfo::pool_migrate())]
3225 pub fn migrate_pool_to_delegate_stake(
3226 origin: OriginFor<T>,
3227 pool_id: PoolId,
3228 ) -> DispatchResultWithPostInfo {
3229 ensure!(
3231 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3232 Error::<T>::NotSupported
3233 );
3234
3235 let _caller = ensure_signed(origin)?;
3236 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3238 ensure!(
3239 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3240 adapter::StakeStrategyType::Transfer,
3241 Error::<T>::AlreadyMigrated
3242 );
3243
3244 Self::migrate_to_delegate_stake(pool_id)?;
3245 Ok(Pays::No.into())
3246 }
3247 }
3248
3249 #[pallet::hooks]
3250 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3251 #[cfg(feature = "try-runtime")]
3252 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3253 Self::do_try_state(u8::MAX)
3254 }
3255
3256 fn integrity_test() {
3257 assert!(
3258 T::MaxPointsToBalance::get() > 0,
3259 "Minimum points to balance ratio must be greater than 0"
3260 );
3261 assert!(
3262 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3263 "There must be more unbonding pools then the bonding duration /
3264 so a slash can be applied to relevant unbonding pools. (We assume /
3265 the bonding duration > slash deffer duration.",
3266 );
3267 }
3268 }
3269}
3270
3271impl<T: Config> Pallet<T> {
3272 pub fn depositor_min_bond() -> BalanceOf<T> {
3280 T::StakeAdapter::minimum_nominator_bond()
3281 .max(MinCreateBond::<T>::get())
3282 .max(MinJoinBond::<T>::get())
3283 .max(T::Currency::minimum_balance())
3284 }
3285 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3290 let reward_account = bonded_pool.reward_account();
3291 let bonded_account = bonded_pool.bonded_account();
3292
3293 ReversePoolIdLookup::<T>::remove(&bonded_account);
3294 RewardPools::<T>::remove(bonded_pool.id);
3295 SubPoolsStorage::<T>::remove(bonded_pool.id);
3296
3297 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3299
3300 defensive_assert!(
3308 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3309 "reward account of dissolving pool should have no consumers"
3310 );
3311 defensive_assert!(
3312 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3313 "bonded account of dissolving pool should have no consumers"
3314 );
3315 defensive_assert!(
3316 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3317 "dissolving pool should not have any stake in the staking pallet"
3318 );
3319
3320 let reward_pool_remaining = T::Currency::reducible_balance(
3323 &reward_account,
3324 Preservation::Expendable,
3325 Fortitude::Polite,
3326 );
3327 let _ = T::Currency::transfer(
3328 &reward_account,
3329 &bonded_pool.roles.depositor,
3330 reward_pool_remaining,
3331 Preservation::Expendable,
3332 );
3333
3334 defensive_assert!(
3335 T::Currency::total_balance(&reward_account) == Zero::zero(),
3336 "could not transfer all amount to depositor while dissolving pool"
3337 );
3338 T::Currency::set_balance(&reward_account, Zero::zero());
3340
3341 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3343
3344 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3345 Metadata::<T>::remove(bonded_pool.id);
3347
3348 bonded_pool.remove();
3349 }
3350
3351 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3353 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3354 }
3355
3356 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3357 T::StakeAdapter::migrate_nominator_to_agent(
3358 Pool::from(Self::generate_bonded_account(id)),
3359 &Self::generate_reward_account(id),
3360 )
3361 }
3362
3363 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3365 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3368 }
3369
3370 fn get_member_with_pools(
3372 who: &T::AccountId,
3373 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3374 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3375 let bonded_pool =
3376 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3377 let reward_pool =
3378 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3379 Ok((member, bonded_pool, reward_pool))
3380 }
3381
3382 fn put_member_with_pools(
3385 member_account: &T::AccountId,
3386 member: PoolMember<T>,
3387 bonded_pool: BondedPool<T>,
3388 reward_pool: RewardPool<T>,
3389 ) {
3390 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3393 debug_assert_eq!(member.pool_id, bonded_pool.id);
3394
3395 bonded_pool.put();
3396 RewardPools::insert(member.pool_id, reward_pool);
3397 PoolMembers::<T>::insert(member_account, member);
3398 }
3399
3400 fn balance_to_point(
3403 current_balance: BalanceOf<T>,
3404 current_points: BalanceOf<T>,
3405 new_funds: BalanceOf<T>,
3406 ) -> BalanceOf<T> {
3407 let u256 = T::BalanceToU256::convert;
3408 let balance = T::U256ToBalance::convert;
3409 match (current_balance.is_zero(), current_points.is_zero()) {
3410 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3411 (true, false) => {
3412 new_funds.saturating_mul(current_points)
3415 },
3416 (false, false) => {
3417 balance(
3419 u256(current_points)
3420 .saturating_mul(u256(new_funds))
3421 .div(u256(current_balance)),
3423 )
3424 },
3425 }
3426 }
3427
3428 fn point_to_balance(
3431 current_balance: BalanceOf<T>,
3432 current_points: BalanceOf<T>,
3433 points: BalanceOf<T>,
3434 ) -> BalanceOf<T> {
3435 let u256 = T::BalanceToU256::convert;
3436 let balance = T::U256ToBalance::convert;
3437 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3438 return Zero::zero()
3440 }
3441
3442 balance(
3444 u256(current_balance)
3445 .saturating_mul(u256(points))
3446 .div(u256(current_points)),
3448 )
3449 }
3450
3451 fn do_reward_payout(
3455 member_account: &T::AccountId,
3456 member: &mut PoolMember<T>,
3457 bonded_pool: &mut BondedPool<T>,
3458 reward_pool: &mut RewardPool<T>,
3459 ) -> Result<BalanceOf<T>, DispatchError> {
3460 debug_assert_eq!(member.pool_id, bonded_pool.id);
3461 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3462
3463 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3465
3466 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3467 bonded_pool.id,
3468 bonded_pool.points,
3469 bonded_pool.commission.current(),
3470 )?;
3471
3472 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3475 if pending_rewards.is_zero() {
3476 return Ok(pending_rewards)
3477 }
3478
3479 member.last_recorded_reward_counter = current_reward_counter;
3481 reward_pool.register_claimed_reward(pending_rewards);
3482
3483 T::Currency::transfer(
3484 &bonded_pool.reward_account(),
3485 member_account,
3486 pending_rewards,
3487 Preservation::Preserve,
3490 )?;
3491
3492 Self::deposit_event(Event::<T>::PaidOut {
3493 member: member_account.clone(),
3494 pool_id: member.pool_id,
3495 payout: pending_rewards,
3496 });
3497 Ok(pending_rewards)
3498 }
3499
3500 fn do_create(
3501 who: T::AccountId,
3502 amount: BalanceOf<T>,
3503 root: AccountIdLookupOf<T>,
3504 nominator: AccountIdLookupOf<T>,
3505 bouncer: AccountIdLookupOf<T>,
3506 pool_id: PoolId,
3507 ) -> DispatchResult {
3508 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3510
3511 let root = T::Lookup::lookup(root)?;
3512 let nominator = T::Lookup::lookup(nominator)?;
3513 let bouncer = T::Lookup::lookup(bouncer)?;
3514
3515 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3516 ensure!(
3517 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3518 Error::<T>::MaxPools
3519 );
3520 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3521 let mut bonded_pool = BondedPool::<T>::new(
3522 pool_id,
3523 PoolRoles {
3524 root: Some(root),
3525 nominator: Some(nominator),
3526 bouncer: Some(bouncer),
3527 depositor: who.clone(),
3528 },
3529 );
3530
3531 bonded_pool.try_inc_members()?;
3532 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3533
3534 T::Currency::transfer(
3536 &who,
3537 &bonded_pool.reward_account(),
3538 T::Currency::minimum_balance(),
3539 Preservation::Expendable,
3540 )?;
3541
3542 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3544
3545 PoolMembers::<T>::insert(
3546 who.clone(),
3547 PoolMember::<T> {
3548 pool_id,
3549 points,
3550 last_recorded_reward_counter: Zero::zero(),
3551 unbonding_eras: Default::default(),
3552 },
3553 );
3554 RewardPools::<T>::insert(
3555 pool_id,
3556 RewardPool::<T> {
3557 last_recorded_reward_counter: Zero::zero(),
3558 last_recorded_total_payouts: Zero::zero(),
3559 total_rewards_claimed: Zero::zero(),
3560 total_commission_pending: Zero::zero(),
3561 total_commission_claimed: Zero::zero(),
3562 },
3563 );
3564 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3565
3566 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3567
3568 Self::deposit_event(Event::<T>::Bonded {
3569 member: who,
3570 pool_id,
3571 bonded: amount,
3572 joined: true,
3573 });
3574 bonded_pool.put();
3575
3576 Ok(())
3577 }
3578
3579 fn do_bond_extra(
3580 signer: T::AccountId,
3581 member_account: T::AccountId,
3582 extra: BondExtra<BalanceOf<T>>,
3583 ) -> DispatchResult {
3584 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3586
3587 if signer != member_account {
3588 ensure!(
3589 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3590 Error::<T>::DoesNotHavePermission
3591 );
3592 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3593 }
3594
3595 let (mut member, mut bonded_pool, mut reward_pool) =
3596 Self::get_member_with_pools(&member_account)?;
3597
3598 reward_pool.update_records(
3601 bonded_pool.id,
3602 bonded_pool.points,
3603 bonded_pool.commission.current(),
3604 )?;
3605 let claimed = Self::do_reward_payout(
3606 &member_account,
3607 &mut member,
3608 &mut bonded_pool,
3609 &mut reward_pool,
3610 )?;
3611
3612 let (points_issued, bonded) = match extra {
3613 BondExtra::FreeBalance(amount) =>
3614 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
3615 BondExtra::Rewards =>
3616 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
3617 };
3618
3619 bonded_pool.ok_to_be_open()?;
3620 member.points =
3621 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3622
3623 Self::deposit_event(Event::<T>::Bonded {
3624 member: member_account.clone(),
3625 pool_id: member.pool_id,
3626 bonded,
3627 joined: false,
3628 });
3629 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3630
3631 Ok(())
3632 }
3633
3634 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3635 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3636 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3637
3638 let mut reward_pool = RewardPools::<T>::get(pool_id)
3639 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3640
3641 reward_pool.update_records(
3644 pool_id,
3645 bonded_pool.points,
3646 bonded_pool.commission.current(),
3647 )?;
3648
3649 let commission = reward_pool.total_commission_pending;
3650 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3651
3652 let payee = bonded_pool
3653 .commission
3654 .current
3655 .as_ref()
3656 .map(|(_, p)| p.clone())
3657 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3658
3659 T::Currency::transfer(
3661 &bonded_pool.reward_account(),
3662 &payee,
3663 commission,
3664 Preservation::Preserve,
3665 )?;
3666
3667 reward_pool.total_commission_claimed =
3669 reward_pool.total_commission_claimed.saturating_add(commission);
3670 reward_pool.total_commission_pending = Zero::zero();
3672 RewardPools::<T>::insert(pool_id, reward_pool);
3673
3674 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3675 Ok(())
3676 }
3677
3678 pub(crate) fn do_claim_payout(
3679 signer: T::AccountId,
3680 member_account: T::AccountId,
3681 ) -> DispatchResult {
3682 if signer != member_account {
3683 ensure!(
3684 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3685 Error::<T>::DoesNotHavePermission
3686 );
3687 }
3688 let (mut member, mut bonded_pool, mut reward_pool) =
3689 Self::get_member_with_pools(&member_account)?;
3690
3691 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3692
3693 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3694 Ok(())
3695 }
3696
3697 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3698 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3699
3700 let reward_acc = &bonded_pool.reward_account();
3701 let pre_frozen_balance =
3702 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3703 let min_balance = T::Currency::minimum_balance();
3704
3705 if pre_frozen_balance == min_balance {
3706 return Err(Error::<T>::NothingToAdjust.into())
3707 }
3708
3709 Self::freeze_pool_deposit(reward_acc)?;
3711
3712 if pre_frozen_balance > min_balance {
3713 ensure!(
3715 who == bonded_pool.roles.depositor ||
3716 bonded_pool.roles.root.as_ref().map_or(false, |root| &who == root),
3717 Error::<T>::DoesNotHavePermission
3718 );
3719
3720 let excess = pre_frozen_balance.saturating_sub(min_balance);
3722
3723 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3724 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3725 pool_id: pool,
3726 amount: excess,
3727 });
3728 } else {
3729 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3731 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3732 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3733 pool_id: pool,
3734 amount: deficit,
3735 });
3736 }
3737
3738 Ok(())
3739 }
3740
3741 fn do_apply_slash(
3743 member_account: &T::AccountId,
3744 reporter: Option<T::AccountId>,
3745 enforce_min_slash: bool,
3746 ) -> DispatchResult {
3747 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3748
3749 let pending_slash =
3750 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3751
3752 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3754
3755 if enforce_min_slash {
3756 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3758 }
3759
3760 T::StakeAdapter::member_slash(
3761 Member::from(member_account.clone()),
3762 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3763 pending_slash,
3764 reporter,
3765 )
3766 }
3767
3768 fn member_pending_slash(
3772 member_account: Member<T::AccountId>,
3773 pool_member: PoolMember<T>,
3774 ) -> Result<BalanceOf<T>, DispatchError> {
3775 debug_assert!(
3777 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3778 pool_member
3779 );
3780
3781 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3782 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3785 return Ok(Zero::zero())
3786 }
3787
3788 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3790 .ok_or(Error::<T>::NotMigrated)?;
3792
3793 let expected_balance = pool_member.total_balance();
3795
3796 Ok(actual_balance.saturating_sub(expected_balance))
3798 }
3799
3800 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3802 T::Currency::set_freeze(
3803 &FreezeReason::PoolMinBalance.into(),
3804 reward_acc,
3805 T::Currency::minimum_balance(),
3806 )
3807 }
3808
3809 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3811 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3812 }
3813
3814 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3851 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3852 if level.is_zero() {
3853 return Ok(())
3854 }
3855 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3858 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3859 ensure!(
3860 bonded_pools == reward_pools,
3861 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3862 );
3863
3864 ensure!(
3865 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3866 "`SubPoolsStorage` must be a subset of the above superset."
3867 );
3868 ensure!(
3869 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3870 "`Metadata` keys must be a subset of the above superset."
3871 );
3872
3873 ensure!(
3874 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3875 Error::<T>::MaxPools
3876 );
3877
3878 for id in reward_pools {
3879 let account = Self::generate_reward_account(id);
3880 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3881 T::Currency::minimum_balance()
3882 {
3883 log!(
3884 warn,
3885 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3886 changed recently. Pool operators should be notified to top up the reward \
3887 account",
3888 id,
3889 T::Currency::reducible_balance(
3890 &account,
3891 Preservation::Expendable,
3892 Fortitude::Polite
3893 ),
3894 T::Currency::minimum_balance(),
3895 )
3896 }
3897 }
3898
3899 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3900 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3901 let mut all_members = 0u32;
3902 let mut total_balance_members = Default::default();
3903 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3904 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3905 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3906 *pools_members.entry(d.pool_id).or_default() += 1;
3907 all_members += 1;
3908
3909 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3910 if !bonded_pool.points.is_zero() {
3911 let commission = bonded_pool.commission.current();
3912 let (current_rc, _) = reward_pool
3913 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3914 .unwrap();
3915 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3916 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3917 } total_balance_members += d.total_balance();
3919
3920 Ok(())
3921 })?;
3922
3923 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3924 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3927 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3928
3929 if !pending_rewards_lt_leftover_bal {
3932 log!(
3933 warn,
3934 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
3935 id,
3936 pools_members_pending_rewards.get(&id),
3937 RewardPool::<T>::current_balance(id)
3938 );
3939 }
3940 Ok(())
3941 })?;
3942
3943 let mut expected_tvl: BalanceOf<T> = Default::default();
3944 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
3945 let bonded_pool = BondedPool { id, inner };
3946 ensure!(
3947 pools_members.get(&id).copied().unwrap_or_default() ==
3948 bonded_pool.member_counter,
3949 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
3950 );
3951 ensure!(
3952 MaxPoolMembersPerPool::<T>::get()
3953 .map_or(true, |max| bonded_pool.member_counter <= max),
3954 Error::<T>::MaxPoolMembers
3955 );
3956
3957 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
3958 let depositor_has_enough_stake = bonded_pool
3959 .is_destroying_and_only_depositor(depositor.active_points()) ||
3960 depositor.active_points() >= MinCreateBond::<T>::get();
3961 if !depositor_has_enough_stake {
3962 log!(
3963 warn,
3964 "pool {:?} has depositor {:?} with insufficient stake {:?}, minimum required is {:?}",
3965 id,
3966 bonded_pool.roles.depositor,
3967 depositor.active_points(),
3968 MinCreateBond::<T>::get()
3969 );
3970 }
3971
3972 ensure!(
3973 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
3974 "Each `BondedPool.points` must never be lower than the pool's balance"
3975 );
3976
3977 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
3978
3979 Ok(())
3980 })?;
3981
3982 ensure!(
3983 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
3984 Error::<T>::MaxPoolMembers
3985 );
3986
3987 ensure!(
3988 TotalValueLocked::<T>::get() == expected_tvl,
3989 "TVL deviates from the actual sum of funds of all Pools."
3990 );
3991
3992 ensure!(
3993 TotalValueLocked::<T>::get() <= total_balance_members,
3994 "TVL must be equal to or less than the total balance of all PoolMembers."
3995 );
3996
3997 if level <= 1 {
3998 return Ok(())
3999 }
4000
4001 for (pool_id, _pool) in BondedPools::<T>::iter() {
4002 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
4003 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
4004
4005 let sum_unbonding_balance = subs.sum_unbonding_balance();
4006 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
4007 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
4009 .unwrap_or(T::Currency::total_balance(&pool_account));
4012
4013 if total_balance < bonded_balance + sum_unbonding_balance {
4014 log!(
4015 warn,
4016 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4017 pool_id,
4018 _pool,
4019 total_balance,
4020 bonded_balance,
4021 sum_unbonding_balance
4022 )
4023 };
4024 }
4025
4026 let _needs_adjust = Self::check_ed_imbalance()?;
4029
4030 Ok(())
4031 }
4032
4033 #[cfg(any(
4037 feature = "try-runtime",
4038 feature = "runtime-benchmarks",
4039 feature = "fuzzing",
4040 test,
4041 debug_assertions
4042 ))]
4043 pub fn check_ed_imbalance() -> Result<u32, DispatchError> {
4044 let mut needs_adjust = 0;
4045 BondedPools::<T>::iter_keys().for_each(|id| {
4046 let reward_acc = Self::generate_reward_account(id);
4047 let frozen_balance =
4048 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4049
4050 let expected_frozen_balance = T::Currency::minimum_balance();
4051 if frozen_balance != expected_frozen_balance {
4052 needs_adjust += 1;
4053 log!(
4054 warn,
4055 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}. Use `adjust_pool_deposit` to fix it",
4056 id,
4057 expected_frozen_balance,
4058 frozen_balance,
4059 );
4060 }
4061 });
4062
4063 Ok(needs_adjust)
4064 }
4065 #[cfg(any(feature = "runtime-benchmarks", test))]
4070 pub fn fully_unbond(
4071 origin: frame_system::pallet_prelude::OriginFor<T>,
4072 member: T::AccountId,
4073 ) -> DispatchResult {
4074 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4075 let member_lookup = T::Lookup::unlookup(member);
4076 Self::unbond(origin, member_lookup, points)
4077 }
4078}
4079
4080impl<T: Config> Pallet<T> {
4081 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4085 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4086 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4087 .zip(BondedPools::<T>::get(pool_member.pool_id))
4088 {
4089 let commission = bonded_pool.commission.current();
4090 let (current_reward_counter, _) = reward_pool
4091 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4092 .ok()?;
4093 return pool_member.pending_rewards(current_reward_counter).ok()
4094 }
4095 }
4096
4097 None
4098 }
4099
4100 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4104 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4105 pool.points_to_balance(points)
4106 } else {
4107 Zero::zero()
4108 }
4109 }
4110
4111 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4115 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4116 let bonded_balance =
4117 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4118 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4119 } else {
4120 Zero::zero()
4121 }
4122 }
4123
4124 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4128 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4129 }
4130
4131 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4138 PoolMembers::<T>::get(who.clone())
4139 .map(|pool_member| {
4140 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4141 })
4142 .unwrap_or_default()
4143 }
4144
4145 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4150 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4152 return false
4153 }
4154
4155 if !BondedPools::<T>::contains_key(pool_id) {
4157 return false
4158 }
4159
4160 let pool_account = Self::generate_bonded_account(pool_id);
4161
4162 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4164 adapter::StakeStrategyType::Delegate
4165 }
4166
4167 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4173 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4175 return false
4176 }
4177
4178 PoolMembers::<T>::get(who.clone())
4179 .map(|pool_member| {
4180 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4181 return false
4183 }
4184
4185 let member_balance = pool_member.total_balance();
4186 let delegated_balance =
4187 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4188
4189 delegated_balance.is_none() && !member_balance.is_zero()
4192 })
4193 .unwrap_or_default()
4194 }
4195
4196 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4201 PoolMembers::<T>::get(who.clone())
4202 .map(|m| m.total_balance())
4203 .unwrap_or_default()
4204 }
4205
4206 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4208 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4209 .unwrap_or_default()
4210 }
4211
4212 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4214 let bonded_account = Self::generate_bonded_account(pool_id);
4215 let reward_account = Self::generate_reward_account(pool_id);
4216 (bonded_account, reward_account)
4217 }
4218}
4219
4220impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4221 fn on_slash(
4227 pool_account: &T::AccountId,
4228 slashed_bonded: BalanceOf<T>,
4231 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4232 total_slashed: BalanceOf<T>,
4233 ) {
4234 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4235 TotalValueLocked::<T>::mutate(|tvl| {
4238 tvl.defensive_saturating_reduce(total_slashed);
4239 });
4240
4241 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4242 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4244 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4245 pool.balance = *slashed_balance;
4246 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4247 era: *era,
4248 pool_id,
4249 balance: *slashed_balance,
4250 });
4251 }
4252 });
4253 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4254 } else if !slashed_unlocking.is_empty() {
4255 defensive!("Expected SubPools were not found");
4256 }
4257 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4258 }
4259
4260 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4263 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4264 TotalValueLocked::<T>::mutate(|tvl| {
4265 tvl.saturating_reduce(amount);
4266 });
4267 }
4268 }
4269}
4270
4271pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4273impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4274 fn contains(t: &T::AccountId) -> bool {
4275 PoolMembers::<T>::contains_key(t)
4276 }
4277}