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,
427 Decode,
428 DecodeWithMemTracking,
429 MaxEncodedLen,
430 TypeInfo,
431 RuntimeDebugNoBound,
432 PartialEq,
433 Clone,
434)]
435pub enum ConfigOp<T: Codec + Debug> {
436 Noop,
438 Set(T),
440 Remove,
442}
443
444pub enum BondType {
446 Create,
448 Extra,
450}
451
452#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
454pub enum BondExtra<Balance> {
455 FreeBalance(Balance),
457 Rewards,
459}
460
461#[derive(Encode, Decode)]
463enum AccountType {
464 Bonded,
465 Reward,
466}
467
468#[derive(
470 Encode,
471 Decode,
472 DecodeWithMemTracking,
473 MaxEncodedLen,
474 Clone,
475 Copy,
476 Debug,
477 PartialEq,
478 Eq,
479 TypeInfo,
480)]
481pub enum ClaimPermission {
482 Permissioned,
484 PermissionlessCompound,
486 PermissionlessWithdraw,
488 PermissionlessAll,
490}
491
492impl Default for ClaimPermission {
493 fn default() -> Self {
494 Self::PermissionlessWithdraw
495 }
496}
497
498impl ClaimPermission {
499 fn can_bond_extra(&self) -> bool {
502 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
503 }
504
505 fn can_claim_payout(&self) -> bool {
508 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
509 }
510}
511
512#[derive(
514 Encode,
515 Decode,
516 MaxEncodedLen,
517 TypeInfo,
518 RuntimeDebugNoBound,
519 CloneNoBound,
520 PartialEqNoBound,
521 EqNoBound,
522)]
523#[cfg_attr(feature = "std", derive(DefaultNoBound))]
524#[scale_info(skip_type_params(T))]
525pub struct PoolMember<T: Config> {
526 pub pool_id: PoolId,
528 pub points: BalanceOf<T>,
531 pub last_recorded_reward_counter: T::RewardCounter,
533 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
536}
537
538impl<T: Config> PoolMember<T> {
539 fn pending_rewards(
541 &self,
542 current_reward_counter: T::RewardCounter,
543 ) -> Result<BalanceOf<T>, Error<T>> {
544 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
562 .checked_mul_int(self.active_points())
563 .ok_or(Error::<T>::OverflowRisk)
564 }
565
566 fn active_balance(&self) -> BalanceOf<T> {
571 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
572 pool.points_to_balance(self.points)
573 } else {
574 Zero::zero()
575 }
576 }
577
578 pub fn total_balance(&self) -> BalanceOf<T> {
584 let pool = match BondedPool::<T>::get(self.pool_id) {
585 Some(pool) => pool,
586 None => {
587 defensive!("pool should exist; qed");
589 return Zero::zero();
590 },
591 };
592
593 let active_balance = pool.points_to_balance(self.active_points());
594
595 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
596 Some(sub_pools) => sub_pools,
597 None => return active_balance,
598 };
599
600 let unbonding_balance = self.unbonding_eras.iter().fold(
601 BalanceOf::<T>::zero(),
602 |accumulator, (era, unlocked_points)| {
603 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
606 accumulator + (era_pool.point_to_balance(*unlocked_points))
607 },
608 );
609
610 active_balance + unbonding_balance
611 }
612
613 fn total_points(&self) -> BalanceOf<T> {
615 self.active_points().saturating_add(self.unbonding_points())
616 }
617
618 fn active_points(&self) -> BalanceOf<T> {
620 self.points
621 }
622
623 fn unbonding_points(&self) -> BalanceOf<T> {
625 self.unbonding_eras
626 .as_ref()
627 .iter()
628 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
629 }
630
631 fn try_unbond(
639 &mut self,
640 points_dissolved: BalanceOf<T>,
641 points_issued: BalanceOf<T>,
642 unbonding_era: EraIndex,
643 ) -> Result<(), Error<T>> {
644 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
645 match self.unbonding_eras.get_mut(&unbonding_era) {
646 Some(already_unbonding_points) =>
647 *already_unbonding_points =
648 already_unbonding_points.saturating_add(points_issued),
649 None => self
650 .unbonding_eras
651 .try_insert(unbonding_era, points_issued)
652 .map(|old| {
653 if old.is_some() {
654 defensive!("value checked to not exist in the map; qed");
655 }
656 })
657 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
658 }
659 self.points = new_points;
660 Ok(())
661 } else {
662 Err(Error::<T>::MinimumBondNotMet)
663 }
664 }
665
666 fn withdraw_unlocked(
673 &mut self,
674 current_era: EraIndex,
675 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
676 let mut removed_points =
678 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
679 self.unbonding_eras.retain(|e, p| {
680 if *e > current_era {
681 true
682 } else {
683 removed_points
684 .try_insert(*e, *p)
685 .expect("source map is bounded, this is a subset, will be bounded; qed");
686 false
687 }
688 });
689 removed_points
690 }
691}
692
693#[derive(
695 Encode,
696 Decode,
697 DecodeWithMemTracking,
698 MaxEncodedLen,
699 TypeInfo,
700 PartialEq,
701 RuntimeDebugNoBound,
702 Clone,
703 Copy,
704)]
705pub enum PoolState {
706 Open,
708 Blocked,
710 Destroying,
715}
716
717#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)]
723pub struct PoolRoles<AccountId> {
724 pub depositor: AccountId,
727 pub root: Option<AccountId>,
730 pub nominator: Option<AccountId>,
732 pub bouncer: Option<AccountId>,
734}
735
736#[derive(
738 PartialEq,
739 Eq,
740 Copy,
741 Clone,
742 Encode,
743 Decode,
744 DecodeWithMemTracking,
745 RuntimeDebug,
746 TypeInfo,
747 MaxEncodedLen,
748)]
749pub enum CommissionClaimPermission<AccountId> {
750 Permissionless,
751 Account(AccountId),
752}
753
754#[derive(
767 Encode, Decode, DefaultNoBound, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Copy, Clone,
768)]
769#[codec(mel_bound(T: Config))]
770#[scale_info(skip_type_params(T))]
771pub struct Commission<T: Config> {
772 pub current: Option<(Perbill, T::AccountId)>,
774 pub max: Option<Perbill>,
777 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
780 pub throttle_from: Option<BlockNumberFor<T>>,
783 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
786}
787
788impl<T: Config> Commission<T> {
789 fn throttling(&self, to: &Perbill) -> bool {
796 if let Some(t) = self.change_rate.as_ref() {
797 let commission_as_percent =
798 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
799
800 if *to <= commission_as_percent {
802 return false
803 }
804 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
808 return true
809 }
810
811 return self.throttle_from.map_or_else(
816 || {
817 defensive!("throttle_from should exist if change_rate is set");
818 true
819 },
820 |f| {
821 if t.min_delay == Zero::zero() {
823 false
824 } else {
825 let blocks_surpassed =
827 T::BlockNumberProvider::current_block_number().saturating_sub(f);
828 blocks_surpassed < t.min_delay
829 }
830 },
831 )
832 }
833 false
834 }
835
836 fn current(&self) -> Perbill {
839 self.current
840 .as_ref()
841 .map_or(Perbill::zero(), |(c, _)| *c)
842 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
843 }
844
845 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
851 self.current = match current {
852 None => None,
853 Some((commission, payee)) => {
854 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
855 ensure!(
856 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
857 Error::<T>::CommissionExceedsGlobalMaximum
858 );
859 ensure!(
860 self.max.map_or(true, |m| commission <= &m),
861 Error::<T>::CommissionExceedsMaximum
862 );
863 if commission.is_zero() {
864 None
865 } else {
866 Some((*commission, payee.clone()))
867 }
868 },
869 };
870 self.register_update();
871 Ok(())
872 }
873
874 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
883 ensure!(
884 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
885 Error::<T>::CommissionExceedsGlobalMaximum
886 );
887 if let Some(old) = self.max.as_mut() {
888 if new_max > *old {
889 return Err(Error::<T>::MaxCommissionRestricted.into())
890 }
891 *old = new_max;
892 } else {
893 self.max = Some(new_max)
894 };
895 let updated_current = self
896 .current
897 .as_mut()
898 .map(|(c, _)| {
899 let u = *c > new_max;
900 *c = (*c).min(new_max);
901 u
902 })
903 .unwrap_or(false);
904
905 if updated_current {
906 if let Some((_, payee)) = self.current.as_ref() {
907 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
908 pool_id,
909 current: Some((new_max, payee.clone())),
910 });
911 }
912 self.register_update();
913 }
914 Ok(())
915 }
916
917 fn try_update_change_rate(
926 &mut self,
927 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
928 ) -> DispatchResult {
929 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
930
931 if self.change_rate.is_none() {
932 self.register_update();
933 }
934 self.change_rate = Some(change_rate);
935 Ok(())
936 }
937
938 fn register_update(&mut self) {
940 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
941 }
942
943 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
948 self.change_rate
949 .as_ref()
950 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
951 .unwrap_or(false)
952 }
953}
954
955#[derive(
963 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
964)]
965pub struct CommissionChangeRate<BlockNumber> {
966 pub max_increase: Perbill,
968 pub min_delay: BlockNumber,
970}
971
972#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)]
974#[codec(mel_bound(T: Config))]
975#[scale_info(skip_type_params(T))]
976pub struct BondedPoolInner<T: Config> {
977 pub commission: Commission<T>,
979 pub member_counter: u32,
981 pub points: BalanceOf<T>,
983 pub roles: PoolRoles<T::AccountId>,
985 pub state: PoolState,
987}
988
989#[derive(RuntimeDebugNoBound)]
994#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
995pub struct BondedPool<T: Config> {
996 id: PoolId,
998 inner: BondedPoolInner<T>,
1000}
1001
1002impl<T: Config> core::ops::Deref for BondedPool<T> {
1003 type Target = BondedPoolInner<T>;
1004 fn deref(&self) -> &Self::Target {
1005 &self.inner
1006 }
1007}
1008
1009impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1010 fn deref_mut(&mut self) -> &mut Self::Target {
1011 &mut self.inner
1012 }
1013}
1014
1015impl<T: Config> BondedPool<T> {
1016 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1018 Self {
1019 id,
1020 inner: BondedPoolInner {
1021 commission: Commission::default(),
1022 member_counter: Zero::zero(),
1023 points: Zero::zero(),
1024 roles,
1025 state: PoolState::Open,
1026 },
1027 }
1028 }
1029
1030 pub fn get(id: PoolId) -> Option<Self> {
1032 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1033 }
1034
1035 fn bonded_account(&self) -> T::AccountId {
1037 Pallet::<T>::generate_bonded_account(self.id)
1038 }
1039
1040 fn reward_account(&self) -> T::AccountId {
1042 Pallet::<T>::generate_reward_account(self.id)
1043 }
1044
1045 fn put(self) {
1047 BondedPools::<T>::insert(self.id, self.inner);
1048 }
1049
1050 fn remove(self) {
1052 BondedPools::<T>::remove(self.id);
1053 }
1054
1055 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1059 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1060 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1061 }
1062
1063 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1067 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1068 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1069 }
1070
1071 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1073 let points_to_issue = self.balance_to_point(new_funds);
1074 self.points = self.points.saturating_add(points_to_issue);
1075 points_to_issue
1076 }
1077
1078 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1085 let balance = self.points_to_balance(points);
1088 self.points = self.points.saturating_sub(points);
1089 balance
1090 }
1091
1092 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1095 ensure!(
1096 MaxPoolMembersPerPool::<T>::get()
1097 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1098 Error::<T>::MaxPoolMembers
1099 );
1100 ensure!(
1101 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1102 Error::<T>::MaxPoolMembers
1103 );
1104 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1105 Ok(())
1106 }
1107
1108 fn dec_members(mut self) -> Self {
1110 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1111 self
1112 }
1113
1114 fn is_root(&self, who: &T::AccountId) -> bool {
1115 self.roles.root.as_ref().map_or(false, |root| root == who)
1116 }
1117
1118 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1119 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1120 }
1121
1122 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1123 self.is_root(who)
1124 }
1125
1126 fn can_nominate(&self, who: &T::AccountId) -> bool {
1127 self.is_root(who) ||
1128 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1129 }
1130
1131 fn can_kick(&self, who: &T::AccountId) -> bool {
1132 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1133 }
1134
1135 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1136 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1137 }
1138
1139 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1140 self.is_root(who) || self.is_bouncer(who)
1141 }
1142
1143 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1144 self.is_root(who)
1145 }
1146
1147 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1148 if let Some(permission) = self.commission.claim_permission.as_ref() {
1149 match permission {
1150 CommissionClaimPermission::Permissionless => true,
1151 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1152 }
1153 } else {
1154 self.is_root(who)
1155 }
1156 }
1157
1158 fn is_destroying(&self) -> bool {
1159 matches!(self.state, PoolState::Destroying)
1160 }
1161
1162 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1163 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1170 }
1171
1172 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1175 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1176
1177 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1178 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1179
1180 let points_to_balance_ratio_floor = self
1181 .points
1182 .div(bonded_balance);
1184
1185 let max_points_to_balance = T::MaxPointsToBalance::get();
1186
1187 ensure!(
1191 points_to_balance_ratio_floor < max_points_to_balance.into(),
1192 Error::<T>::OverflowRisk
1193 );
1194
1195 Ok(())
1199 }
1200
1201 fn ok_to_join(&self) -> Result<(), DispatchError> {
1203 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1204 self.ok_to_be_open()?;
1205 Ok(())
1206 }
1207
1208 fn ok_to_unbond_with(
1209 &self,
1210 caller: &T::AccountId,
1211 target_account: &T::AccountId,
1212 target_member: &PoolMember<T>,
1213 unbonding_points: BalanceOf<T>,
1214 ) -> Result<(), DispatchError> {
1215 let is_permissioned = caller == target_account;
1216 let is_depositor = *target_account == self.roles.depositor;
1217 let is_full_unbond = unbonding_points == target_member.active_points();
1218
1219 let balance_after_unbond = {
1220 let new_depositor_points =
1221 target_member.active_points().saturating_sub(unbonding_points);
1222 let mut target_member_after_unbond = (*target_member).clone();
1223 target_member_after_unbond.points = new_depositor_points;
1224 target_member_after_unbond.active_balance()
1225 };
1226
1227 ensure!(
1229 is_permissioned || is_full_unbond,
1230 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1231 );
1232
1233 ensure!(
1235 is_full_unbond ||
1236 balance_after_unbond >=
1237 if is_depositor {
1238 Pallet::<T>::depositor_min_bond()
1239 } else {
1240 MinJoinBond::<T>::get()
1241 },
1242 Error::<T>::MinimumBondNotMet
1243 );
1244
1245 match (is_permissioned, is_depositor) {
1247 (true, false) => (),
1248 (true, true) => {
1249 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1252 } else {
1254 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1256 }
1257 },
1258 (false, false) => {
1259 debug_assert!(is_full_unbond);
1262 ensure!(
1263 self.can_kick(caller) || self.is_destroying(),
1264 Error::<T>::NotKickerOrDestroying
1265 )
1266 },
1267 (false, true) => {
1268 return Err(Error::<T>::DoesNotHavePermission.into())
1270 },
1271 };
1272
1273 Ok(())
1274 }
1275
1276 fn ok_to_withdraw_unbonded_with(
1280 &self,
1281 caller: &T::AccountId,
1282 target_account: &T::AccountId,
1283 ) -> Result<(), DispatchError> {
1284 let is_permissioned = caller == target_account;
1286 ensure!(
1287 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1288 Error::<T>::NotKickerOrDestroying
1289 );
1290 Ok(())
1291 }
1292
1293 fn try_bond_funds(
1301 &mut self,
1302 who: &T::AccountId,
1303 amount: BalanceOf<T>,
1304 ty: BondType,
1305 ) -> Result<BalanceOf<T>, DispatchError> {
1306 let points_issued = self.issue(amount);
1309
1310 T::StakeAdapter::pledge_bond(
1311 Member::from(who.clone()),
1312 Pool::from(self.bonded_account()),
1313 &self.reward_account(),
1314 amount,
1315 ty,
1316 )?;
1317 TotalValueLocked::<T>::mutate(|tvl| {
1318 tvl.saturating_accrue(amount);
1319 });
1320
1321 Ok(points_issued)
1322 }
1323
1324 fn set_state(&mut self, state: PoolState) {
1327 if self.state != state {
1328 self.state = state;
1329 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1330 pool_id: self.id,
1331 new_state: state,
1332 });
1333 };
1334 }
1335}
1336
1337#[derive(
1343 Encode,
1344 Decode,
1345 MaxEncodedLen,
1346 TypeInfo,
1347 CloneNoBound,
1348 PartialEqNoBound,
1349 EqNoBound,
1350 RuntimeDebugNoBound,
1351)]
1352#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1353#[codec(mel_bound(T: Config))]
1354#[scale_info(skip_type_params(T))]
1355pub struct RewardPool<T: Config> {
1356 pub last_recorded_reward_counter: T::RewardCounter,
1361 pub last_recorded_total_payouts: BalanceOf<T>,
1367 pub total_rewards_claimed: BalanceOf<T>,
1369 pub total_commission_pending: BalanceOf<T>,
1371 pub total_commission_claimed: BalanceOf<T>,
1373}
1374
1375impl<T: Config> RewardPool<T> {
1376 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1378 self.last_recorded_reward_counter
1379 }
1380
1381 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1383 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1384 }
1385
1386 fn update_records(
1393 &mut self,
1394 id: PoolId,
1395 bonded_points: BalanceOf<T>,
1396 commission: Perbill,
1397 ) -> Result<(), Error<T>> {
1398 let balance = Self::current_balance(id);
1399
1400 let (current_reward_counter, new_pending_commission) =
1401 self.current_reward_counter(id, bonded_points, commission)?;
1402
1403 self.last_recorded_reward_counter = current_reward_counter;
1407
1408 self.total_commission_pending =
1411 self.total_commission_pending.saturating_add(new_pending_commission);
1412
1413 let last_recorded_total_payouts = balance
1417 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1418 .ok_or(Error::<T>::OverflowRisk)?;
1419
1420 self.last_recorded_total_payouts =
1427 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1428
1429 Ok(())
1430 }
1431
1432 fn current_reward_counter(
1435 &self,
1436 id: PoolId,
1437 bonded_points: BalanceOf<T>,
1438 commission: Perbill,
1439 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1440 let balance = Self::current_balance(id);
1441
1442 let current_payout_balance = balance
1447 .saturating_add(self.total_rewards_claimed)
1448 .saturating_add(self.total_commission_claimed)
1449 .saturating_sub(self.last_recorded_total_payouts);
1450
1451 let new_pending_commission = commission * current_payout_balance;
1454 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1455
1456 let current_reward_counter =
1491 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1492 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1493 .ok_or(Error::<T>::OverflowRisk)?;
1494
1495 Ok((current_reward_counter, new_pending_commission))
1496 }
1497
1498 fn current_balance(id: PoolId) -> BalanceOf<T> {
1502 T::Currency::reducible_balance(
1503 &Pallet::<T>::generate_reward_account(id),
1504 Preservation::Expendable,
1505 Fortitude::Polite,
1506 )
1507 }
1508}
1509
1510#[derive(
1512 Encode,
1513 Decode,
1514 MaxEncodedLen,
1515 TypeInfo,
1516 DefaultNoBound,
1517 RuntimeDebugNoBound,
1518 CloneNoBound,
1519 PartialEqNoBound,
1520 EqNoBound,
1521)]
1522#[codec(mel_bound(T: Config))]
1523#[scale_info(skip_type_params(T))]
1524pub struct UnbondPool<T: Config> {
1525 pub points: BalanceOf<T>,
1527 pub balance: BalanceOf<T>,
1529}
1530
1531impl<T: Config> UnbondPool<T> {
1532 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1533 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1534 }
1535
1536 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1537 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1538 }
1539
1540 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1544 let new_points = self.balance_to_point(new_funds);
1545 self.points = self.points.saturating_add(new_points);
1546 self.balance = self.balance.saturating_add(new_funds);
1547 new_points
1548 }
1549
1550 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1555 let balance_to_unbond = self.point_to_balance(points);
1556 self.points = self.points.saturating_sub(points);
1557 self.balance = self.balance.saturating_sub(balance_to_unbond);
1558
1559 balance_to_unbond
1560 }
1561}
1562
1563#[derive(
1564 Encode,
1565 Decode,
1566 MaxEncodedLen,
1567 TypeInfo,
1568 DefaultNoBound,
1569 RuntimeDebugNoBound,
1570 CloneNoBound,
1571 PartialEqNoBound,
1572 EqNoBound,
1573)]
1574#[codec(mel_bound(T: Config))]
1575#[scale_info(skip_type_params(T))]
1576pub struct SubPools<T: Config> {
1577 pub no_era: UnbondPool<T>,
1581 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1583}
1584
1585impl<T: Config> SubPools<T> {
1586 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1591 if let Some(newest_era_to_remove) =
1595 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1596 {
1597 self.with_era.retain(|k, v| {
1598 if *k > newest_era_to_remove {
1599 true
1601 } else {
1602 self.no_era.points = self.no_era.points.saturating_add(v.points);
1604 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1605 false
1606 }
1607 });
1608 }
1609
1610 self
1611 }
1612
1613 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1615 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1616 self.no_era.balance.saturating_add(
1617 self.with_era
1618 .values()
1619 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1620 )
1621 }
1622}
1623
1624pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1628
1629impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1630 fn get() -> u32 {
1631 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1635 }
1636}
1637
1638#[frame_support::pallet]
1639pub mod pallet {
1640 use super::*;
1641 use frame_support::traits::StorageVersion;
1642 use frame_system::pallet_prelude::{
1643 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1644 };
1645 use sp_runtime::Perbill;
1646
1647 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1649
1650 #[pallet::pallet]
1651 #[pallet::storage_version(STORAGE_VERSION)]
1652 pub struct Pallet<T>(_);
1653
1654 #[pallet::config]
1655 pub trait Config: frame_system::Config {
1656 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1658
1659 type WeightInfo: weights::WeightInfo;
1661
1662 type Currency: Mutate<Self::AccountId>
1664 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1665
1666 type RuntimeFreezeReason: From<FreezeReason>;
1668
1669 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1682
1683 #[pallet::constant]
1685 type PalletId: Get<frame_support::PalletId>;
1686
1687 #[pallet::constant]
1700 type MaxPointsToBalance: Get<u8>;
1701
1702 #[pallet::constant]
1704 type MaxUnbonding: Get<u32>;
1705
1706 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1708
1709 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1711
1712 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1716
1717 type PostUnbondingPoolsWindow: Get<u32>;
1723
1724 type MaxMetadataLen: Get<u32>;
1726
1727 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1729
1730 type BlockNumberProvider: BlockNumberProvider;
1732
1733 type Filter: Contains<Self::AccountId>;
1735 }
1736
1737 #[pallet::storage]
1743 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1744
1745 #[pallet::storage]
1747 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1748
1749 #[pallet::storage]
1757 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1758
1759 #[pallet::storage]
1762 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1763
1764 #[pallet::storage]
1767 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1768
1769 #[pallet::storage]
1772 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1773
1774 #[pallet::storage]
1778 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1779
1780 #[pallet::storage]
1784 pub type PoolMembers<T: Config> =
1785 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1786
1787 #[pallet::storage]
1790 pub type BondedPools<T: Config> =
1791 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1792
1793 #[pallet::storage]
1796 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1797
1798 #[pallet::storage]
1801 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1802
1803 #[pallet::storage]
1805 pub type Metadata<T: Config> =
1806 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1807
1808 #[pallet::storage]
1810 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1811
1812 #[pallet::storage]
1817 pub type ReversePoolIdLookup<T: Config> =
1818 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1819
1820 #[pallet::storage]
1822 pub type ClaimPermissions<T: Config> =
1823 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1824
1825 #[pallet::genesis_config]
1826 pub struct GenesisConfig<T: Config> {
1827 pub min_join_bond: BalanceOf<T>,
1828 pub min_create_bond: BalanceOf<T>,
1829 pub max_pools: Option<u32>,
1830 pub max_members_per_pool: Option<u32>,
1831 pub max_members: Option<u32>,
1832 pub global_max_commission: Option<Perbill>,
1833 }
1834
1835 impl<T: Config> Default for GenesisConfig<T> {
1836 fn default() -> Self {
1837 Self {
1838 min_join_bond: Zero::zero(),
1839 min_create_bond: Zero::zero(),
1840 max_pools: Some(16),
1841 max_members_per_pool: Some(32),
1842 max_members: Some(16 * 32),
1843 global_max_commission: None,
1844 }
1845 }
1846 }
1847
1848 #[pallet::genesis_build]
1849 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1850 fn build(&self) {
1851 MinJoinBond::<T>::put(self.min_join_bond);
1852 MinCreateBond::<T>::put(self.min_create_bond);
1853
1854 if let Some(max_pools) = self.max_pools {
1855 MaxPools::<T>::put(max_pools);
1856 }
1857 if let Some(max_members_per_pool) = self.max_members_per_pool {
1858 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1859 }
1860 if let Some(max_members) = self.max_members {
1861 MaxPoolMembers::<T>::put(max_members);
1862 }
1863 if let Some(global_max_commission) = self.global_max_commission {
1864 GlobalMaxCommission::<T>::put(global_max_commission);
1865 }
1866 }
1867 }
1868
1869 #[pallet::event]
1871 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1872 pub enum Event<T: Config> {
1873 Created { depositor: T::AccountId, pool_id: PoolId },
1875 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1877 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1879 Unbonded {
1891 member: T::AccountId,
1892 pool_id: PoolId,
1893 balance: BalanceOf<T>,
1894 points: BalanceOf<T>,
1895 era: EraIndex,
1896 },
1897 Withdrawn {
1904 member: T::AccountId,
1905 pool_id: PoolId,
1906 balance: BalanceOf<T>,
1907 points: BalanceOf<T>,
1908 },
1909 Destroyed { pool_id: PoolId },
1911 StateChanged { pool_id: PoolId, new_state: PoolState },
1913 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1919 RolesUpdated {
1922 root: Option<T::AccountId>,
1923 bouncer: Option<T::AccountId>,
1924 nominator: Option<T::AccountId>,
1925 },
1926 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1928 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1930 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1932 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1934 PoolCommissionChangeRateUpdated {
1936 pool_id: PoolId,
1937 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1938 },
1939 PoolCommissionClaimPermissionUpdated {
1941 pool_id: PoolId,
1942 permission: Option<CommissionClaimPermission<T::AccountId>>,
1943 },
1944 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1946 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1948 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1950 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1952 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1954 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1957 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1959 GlobalParamsUpdated {
1961 min_join_bond: BalanceOf<T>,
1962 min_create_bond: BalanceOf<T>,
1963 max_pools: Option<u32>,
1964 max_members: Option<u32>,
1965 max_members_per_pool: Option<u32>,
1966 global_max_commission: Option<Perbill>,
1967 },
1968 }
1969
1970 #[pallet::error]
1971 #[cfg_attr(test, derive(PartialEq))]
1972 pub enum Error<T> {
1973 PoolNotFound,
1975 PoolMemberNotFound,
1977 RewardPoolNotFound,
1979 SubPoolsNotFound,
1981 AccountBelongsToOtherPool,
1984 FullyUnbonding,
1987 MaxUnbondingLimit,
1989 CannotWithdrawAny,
1991 MinimumBondNotMet,
1997 OverflowRisk,
1999 NotDestroying,
2002 NotNominator,
2004 NotKickerOrDestroying,
2006 NotOpen,
2008 MaxPools,
2010 MaxPoolMembers,
2012 CanNotChangeState,
2014 DoesNotHavePermission,
2016 MetadataExceedsMaxLen,
2018 Defensive(DefensiveError),
2021 PartialUnbondNotAllowedPermissionlessly,
2023 MaxCommissionRestricted,
2025 CommissionExceedsMaximum,
2027 CommissionExceedsGlobalMaximum,
2029 CommissionChangeThrottled,
2031 CommissionChangeRateNotAllowed,
2033 NoPendingCommission,
2035 NoCommissionCurrentSet,
2037 PoolIdInUse,
2039 InvalidPoolId,
2041 BondExtraRestricted,
2043 NothingToAdjust,
2045 NothingToSlash,
2047 SlashTooLow,
2049 AlreadyMigrated,
2051 NotMigrated,
2053 NotSupported,
2055 Restricted,
2058 }
2059
2060 #[derive(
2061 Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, RuntimeDebug,
2062 )]
2063 pub enum DefensiveError {
2064 NotEnoughSpaceInUnbondPool,
2066 PoolNotFound,
2068 RewardPoolNotFound,
2070 SubPoolsNotFound,
2072 BondedStashKilledPrematurely,
2075 DelegationUnsupported,
2077 SlashNotApplied,
2079 }
2080
2081 impl<T> From<DefensiveError> for Error<T> {
2082 fn from(e: DefensiveError) -> Error<T> {
2083 Error::<T>::Defensive(e)
2084 }
2085 }
2086
2087 #[pallet::composite_enum]
2089 pub enum FreezeReason {
2090 #[codec(index = 0)]
2092 PoolMinBalance,
2093 }
2094
2095 #[pallet::call]
2096 impl<T: Config> Pallet<T> {
2097 #[pallet::call_index(0)]
2114 #[pallet::weight(T::WeightInfo::join())]
2115 pub fn join(
2116 origin: OriginFor<T>,
2117 #[pallet::compact] amount: BalanceOf<T>,
2118 pool_id: PoolId,
2119 ) -> DispatchResult {
2120 let who = ensure_signed(origin)?;
2121 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2123
2124 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2126
2127 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2128 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2130
2131 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2132 bonded_pool.ok_to_join()?;
2133
2134 let mut reward_pool = RewardPools::<T>::get(pool_id)
2135 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2136 reward_pool.update_records(
2138 pool_id,
2139 bonded_pool.points,
2140 bonded_pool.commission.current(),
2141 )?;
2142
2143 bonded_pool.try_inc_members()?;
2144 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2145
2146 PoolMembers::insert(
2147 who.clone(),
2148 PoolMember::<T> {
2149 pool_id,
2150 points: points_issued,
2151 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2154 unbonding_eras: Default::default(),
2155 },
2156 );
2157
2158 Self::deposit_event(Event::<T>::Bonded {
2159 member: who,
2160 pool_id,
2161 bonded: amount,
2162 joined: true,
2163 });
2164
2165 bonded_pool.put();
2166 RewardPools::<T>::insert(pool_id, reward_pool);
2167
2168 Ok(())
2169 }
2170
2171 #[pallet::call_index(1)]
2182 #[pallet::weight(
2183 T::WeightInfo::bond_extra_transfer()
2184 .max(T::WeightInfo::bond_extra_other())
2185 )]
2186 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2187 let who = ensure_signed(origin)?;
2188
2189 ensure!(
2191 !Self::api_member_needs_delegate_migration(who.clone()),
2192 Error::<T>::NotMigrated
2193 );
2194
2195 Self::do_bond_extra(who.clone(), who, extra)
2196 }
2197
2198 #[pallet::call_index(2)]
2207 #[pallet::weight(T::WeightInfo::claim_payout())]
2208 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2209 let signer = ensure_signed(origin)?;
2210 ensure!(
2212 !Self::api_member_needs_delegate_migration(signer.clone()),
2213 Error::<T>::NotMigrated
2214 );
2215
2216 Self::do_claim_payout(signer.clone(), signer)
2217 }
2218
2219 #[pallet::call_index(3)]
2251 #[pallet::weight(T::WeightInfo::unbond())]
2252 pub fn unbond(
2253 origin: OriginFor<T>,
2254 member_account: AccountIdLookupOf<T>,
2255 #[pallet::compact] unbonding_points: BalanceOf<T>,
2256 ) -> DispatchResult {
2257 let who = ensure_signed(origin)?;
2258 let member_account = T::Lookup::lookup(member_account)?;
2259 ensure!(
2261 !Self::api_member_needs_delegate_migration(member_account.clone()),
2262 Error::<T>::NotMigrated
2263 );
2264
2265 let (mut member, mut bonded_pool, mut reward_pool) =
2266 Self::get_member_with_pools(&member_account)?;
2267
2268 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2269
2270 reward_pool.update_records(
2274 bonded_pool.id,
2275 bonded_pool.points,
2276 bonded_pool.commission.current(),
2277 )?;
2278 let _ = Self::do_reward_payout(
2279 &member_account,
2280 &mut member,
2281 &mut bonded_pool,
2282 &mut reward_pool,
2283 )?;
2284
2285 let current_era = T::StakeAdapter::current_era();
2286 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
2287
2288 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2290 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2291
2292 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2294 .unwrap_or_default()
2295 .maybe_merge_pools(current_era);
2296
2297 if !sub_pools.with_era.contains_key(&unbond_era) {
2300 sub_pools
2301 .with_era
2302 .try_insert(unbond_era, UnbondPool::default())
2303 .defensive_map_err::<Error<T>, _>(|_| {
2306 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2307 })?;
2308 }
2309
2310 let points_unbonded = sub_pools
2311 .with_era
2312 .get_mut(&unbond_era)
2313 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2315 .issue(unbonding_balance);
2316
2317 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2319
2320 Self::deposit_event(Event::<T>::Unbonded {
2321 member: member_account.clone(),
2322 pool_id: member.pool_id,
2323 points: points_unbonded,
2324 balance: unbonding_balance,
2325 era: unbond_era,
2326 });
2327
2328 SubPoolsStorage::insert(member.pool_id, sub_pools);
2330 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2331 Ok(())
2332 }
2333
2334 #[pallet::call_index(4)]
2341 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2342 pub fn pool_withdraw_unbonded(
2343 origin: OriginFor<T>,
2344 pool_id: PoolId,
2345 num_slashing_spans: u32,
2346 ) -> DispatchResult {
2347 let _ = ensure_signed(origin)?;
2348 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2350
2351 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2352
2353 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2356 T::StakeAdapter::withdraw_unbonded(
2357 Pool::from(pool.bonded_account()),
2358 num_slashing_spans,
2359 )?;
2360
2361 Ok(())
2362 }
2363
2364 #[pallet::call_index(5)]
2387 #[pallet::weight(
2388 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2389 )]
2390 pub fn withdraw_unbonded(
2391 origin: OriginFor<T>,
2392 member_account: AccountIdLookupOf<T>,
2393 num_slashing_spans: u32,
2394 ) -> DispatchResultWithPostInfo {
2395 let caller = ensure_signed(origin)?;
2396 let member_account = T::Lookup::lookup(member_account)?;
2397 ensure!(
2399 !Self::api_member_needs_delegate_migration(member_account.clone()),
2400 Error::<T>::NotMigrated
2401 );
2402
2403 let mut member =
2404 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2405 let current_era = T::StakeAdapter::current_era();
2406
2407 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2408 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2409 let mut sub_pools =
2410 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2411
2412 let slash_weight =
2413 match Self::do_apply_slash(&member_account, None, false) {
2415 Ok(_) => T::WeightInfo::apply_slash(),
2416 Err(e) => {
2417 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2418 if Err(e) == no_pending_slash {
2420 T::WeightInfo::apply_slash_fail()
2421 } else {
2422 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2424 }
2425 }
2426
2427 };
2428
2429 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2430 let pool_account = bonded_pool.bonded_account();
2431
2432 let withdrawn_points = member.withdraw_unlocked(current_era);
2434 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2435
2436 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2439 Pool::from(bonded_pool.bonded_account()),
2440 num_slashing_spans,
2441 )?;
2442
2443 ensure!(
2446 !stash_killed || caller == bonded_pool.roles.depositor,
2447 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2448 );
2449
2450 if stash_killed {
2451 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2453 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2454 }
2455
2456 }
2463
2464 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2465 let balance_to_unbond = withdrawn_points
2466 .iter()
2467 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2468 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2469 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2470 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2471 if era_pool.points.is_zero() {
2472 sub_pools.with_era.remove(era);
2473 }
2474 accumulator.saturating_add(balance_to_unbond)
2475 } else {
2476 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2479 }
2480 })
2481 .min(T::StakeAdapter::transferable_balance(
2489 Pool::from(bonded_pool.bonded_account()),
2490 Member::from(member_account.clone()),
2491 ));
2492
2493 T::StakeAdapter::member_withdraw(
2496 Member::from(member_account.clone()),
2497 Pool::from(bonded_pool.bonded_account()),
2498 balance_to_unbond,
2499 num_slashing_spans,
2500 )?;
2501
2502 Self::deposit_event(Event::<T>::Withdrawn {
2503 member: member_account.clone(),
2504 pool_id: member.pool_id,
2505 points: sum_unlocked_points,
2506 balance: balance_to_unbond,
2507 });
2508
2509 let post_info_weight = if member.total_points().is_zero() {
2510 ClaimPermissions::<T>::remove(&member_account);
2512
2513 PoolMembers::<T>::remove(&member_account);
2515
2516 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2518 Member::from(member_account.clone()),
2519 ) {
2520 Some(dangling_delegation) => {
2521 T::StakeAdapter::member_withdraw(
2522 Member::from(member_account.clone()),
2523 Pool::from(bonded_pool.bonded_account()),
2524 dangling_delegation,
2525 num_slashing_spans,
2526 )?;
2527 dangling_delegation
2528 },
2529 None => Zero::zero(),
2530 };
2531
2532 Self::deposit_event(Event::<T>::MemberRemoved {
2533 pool_id: member.pool_id,
2534 member: member_account.clone(),
2535 released_balance: dangling_withdrawal,
2536 });
2537
2538 if member_account == bonded_pool.roles.depositor {
2539 Pallet::<T>::dissolve_pool(bonded_pool);
2540 Weight::default()
2541 } else {
2542 bonded_pool.dec_members().put();
2543 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2544 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2545 }
2546 } else {
2547 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2549 PoolMembers::<T>::insert(&member_account, member);
2550 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2551 };
2552
2553 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2554 }
2555
2556 #[pallet::call_index(6)]
2574 #[pallet::weight(T::WeightInfo::create())]
2575 pub fn create(
2576 origin: OriginFor<T>,
2577 #[pallet::compact] amount: BalanceOf<T>,
2578 root: AccountIdLookupOf<T>,
2579 nominator: AccountIdLookupOf<T>,
2580 bouncer: AccountIdLookupOf<T>,
2581 ) -> DispatchResult {
2582 let depositor = ensure_signed(origin)?;
2583
2584 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2585 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2586 Ok(*id)
2587 })?;
2588
2589 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2590 }
2591
2592 #[pallet::call_index(7)]
2599 #[pallet::weight(T::WeightInfo::create())]
2600 pub fn create_with_pool_id(
2601 origin: OriginFor<T>,
2602 #[pallet::compact] amount: BalanceOf<T>,
2603 root: AccountIdLookupOf<T>,
2604 nominator: AccountIdLookupOf<T>,
2605 bouncer: AccountIdLookupOf<T>,
2606 pool_id: PoolId,
2607 ) -> DispatchResult {
2608 let depositor = ensure_signed(origin)?;
2609
2610 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2611 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2612
2613 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2614 }
2615
2616 #[pallet::call_index(8)]
2629 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2630 pub fn nominate(
2631 origin: OriginFor<T>,
2632 pool_id: PoolId,
2633 validators: Vec<T::AccountId>,
2634 ) -> DispatchResult {
2635 let who = ensure_signed(origin)?;
2636 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2637 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2639 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2640
2641 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2642 .ok_or(Error::<T>::PoolMemberNotFound)?
2643 .active_points();
2644
2645 ensure!(
2646 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2647 Error::<T>::MinimumBondNotMet
2648 );
2649
2650 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2651 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2652 )
2653 }
2654
2655 #[pallet::call_index(9)]
2666 #[pallet::weight(T::WeightInfo::set_state())]
2667 pub fn set_state(
2668 origin: OriginFor<T>,
2669 pool_id: PoolId,
2670 state: PoolState,
2671 ) -> DispatchResult {
2672 let who = ensure_signed(origin)?;
2673 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2674 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2675 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2677
2678 if bonded_pool.can_toggle_state(&who) {
2679 bonded_pool.set_state(state);
2680 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2681 bonded_pool.set_state(PoolState::Destroying);
2683 } else {
2684 Err(Error::<T>::CanNotChangeState)?;
2685 }
2686
2687 bonded_pool.put();
2688
2689 Ok(())
2690 }
2691
2692 #[pallet::call_index(10)]
2697 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2698 pub fn set_metadata(
2699 origin: OriginFor<T>,
2700 pool_id: PoolId,
2701 metadata: Vec<u8>,
2702 ) -> DispatchResult {
2703 let who = ensure_signed(origin)?;
2704 let metadata: BoundedVec<_, _> =
2705 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2706 ensure!(
2707 BondedPool::<T>::get(pool_id)
2708 .ok_or(Error::<T>::PoolNotFound)?
2709 .can_set_metadata(&who),
2710 Error::<T>::DoesNotHavePermission
2711 );
2712 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2714
2715 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2716
2717 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2718
2719 Ok(())
2720 }
2721
2722 #[pallet::call_index(11)]
2734 #[pallet::weight(T::WeightInfo::set_configs())]
2735 pub fn set_configs(
2736 origin: OriginFor<T>,
2737 min_join_bond: ConfigOp<BalanceOf<T>>,
2738 min_create_bond: ConfigOp<BalanceOf<T>>,
2739 max_pools: ConfigOp<u32>,
2740 max_members: ConfigOp<u32>,
2741 max_members_per_pool: ConfigOp<u32>,
2742 global_max_commission: ConfigOp<Perbill>,
2743 ) -> DispatchResult {
2744 T::AdminOrigin::ensure_origin(origin)?;
2745
2746 macro_rules! config_op_exp {
2747 ($storage:ty, $op:ident) => {
2748 match $op {
2749 ConfigOp::Noop => (),
2750 ConfigOp::Set(v) => <$storage>::put(v),
2751 ConfigOp::Remove => <$storage>::kill(),
2752 }
2753 };
2754 }
2755
2756 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2757 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2758 config_op_exp!(MaxPools::<T>, max_pools);
2759 config_op_exp!(MaxPoolMembers::<T>, max_members);
2760 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2761 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2762
2763 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2764 min_join_bond: MinJoinBond::<T>::get(),
2765 min_create_bond: MinCreateBond::<T>::get(),
2766 max_pools: MaxPools::<T>::get(),
2767 max_members: MaxPoolMembers::<T>::get(),
2768 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2769 global_max_commission: GlobalMaxCommission::<T>::get(),
2770 });
2771
2772 Ok(())
2773 }
2774
2775 #[pallet::call_index(12)]
2783 #[pallet::weight(T::WeightInfo::update_roles())]
2784 pub fn update_roles(
2785 origin: OriginFor<T>,
2786 pool_id: PoolId,
2787 new_root: ConfigOp<T::AccountId>,
2788 new_nominator: ConfigOp<T::AccountId>,
2789 new_bouncer: ConfigOp<T::AccountId>,
2790 ) -> DispatchResult {
2791 let mut bonded_pool = match ensure_root(origin.clone()) {
2792 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2793 Err(sp_runtime::traits::BadOrigin) => {
2794 let who = ensure_signed(origin)?;
2795 let bonded_pool =
2796 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2797 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2798 bonded_pool
2799 },
2800 };
2801
2802 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2804
2805 match new_root {
2806 ConfigOp::Noop => (),
2807 ConfigOp::Remove => bonded_pool.roles.root = None,
2808 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2809 };
2810 match new_nominator {
2811 ConfigOp::Noop => (),
2812 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2813 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2814 };
2815 match new_bouncer {
2816 ConfigOp::Noop => (),
2817 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2818 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2819 };
2820
2821 Self::deposit_event(Event::<T>::RolesUpdated {
2822 root: bonded_pool.roles.root.clone(),
2823 nominator: bonded_pool.roles.nominator.clone(),
2824 bouncer: bonded_pool.roles.bouncer.clone(),
2825 });
2826
2827 bonded_pool.put();
2828 Ok(())
2829 }
2830
2831 #[pallet::call_index(13)]
2849 #[pallet::weight(T::WeightInfo::chill())]
2850 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2851 let who = ensure_signed(origin)?;
2852 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2853 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2855
2856 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2857 .ok_or(Error::<T>::PoolMemberNotFound)?
2858 .active_points();
2859
2860 if bonded_pool.points_to_balance(depositor_points) >=
2861 T::StakeAdapter::minimum_nominator_bond()
2862 {
2863 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2864 }
2865
2866 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2867 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2868 })
2869 }
2870
2871 #[pallet::call_index(14)]
2881 #[pallet::weight(
2882 T::WeightInfo::bond_extra_transfer()
2883 .max(T::WeightInfo::bond_extra_other())
2884 )]
2885 pub fn bond_extra_other(
2886 origin: OriginFor<T>,
2887 member: AccountIdLookupOf<T>,
2888 extra: BondExtra<BalanceOf<T>>,
2889 ) -> DispatchResult {
2890 let who = ensure_signed(origin)?;
2891 let member_account = T::Lookup::lookup(member)?;
2892 ensure!(
2894 !Self::api_member_needs_delegate_migration(member_account.clone()),
2895 Error::<T>::NotMigrated
2896 );
2897
2898 Self::do_bond_extra(who, member_account, extra)
2899 }
2900
2901 #[pallet::call_index(15)]
2909 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2910 pub fn set_claim_permission(
2911 origin: OriginFor<T>,
2912 permission: ClaimPermission,
2913 ) -> DispatchResult {
2914 let who = ensure_signed(origin)?;
2915 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2916
2917 ensure!(
2919 !Self::api_member_needs_delegate_migration(who.clone()),
2920 Error::<T>::NotMigrated
2921 );
2922
2923 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2924 *source = permission;
2925 });
2926
2927 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2928 member: who,
2929 permission,
2930 });
2931
2932 Ok(())
2933 }
2934
2935 #[pallet::call_index(16)]
2940 #[pallet::weight(T::WeightInfo::claim_payout())]
2941 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2942 let signer = ensure_signed(origin)?;
2943 ensure!(
2945 !Self::api_member_needs_delegate_migration(other.clone()),
2946 Error::<T>::NotMigrated
2947 );
2948
2949 Self::do_claim_payout(signer, other)
2950 }
2951
2952 #[pallet::call_index(17)]
2959 #[pallet::weight(T::WeightInfo::set_commission())]
2960 pub fn set_commission(
2961 origin: OriginFor<T>,
2962 pool_id: PoolId,
2963 new_commission: Option<(Perbill, T::AccountId)>,
2964 ) -> DispatchResult {
2965 let who = ensure_signed(origin)?;
2966 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2967 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2969
2970 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2971
2972 let mut reward_pool = RewardPools::<T>::get(pool_id)
2973 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2974 reward_pool.update_records(
2977 pool_id,
2978 bonded_pool.points,
2979 bonded_pool.commission.current(),
2980 )?;
2981 RewardPools::insert(pool_id, reward_pool);
2982
2983 bonded_pool.commission.try_update_current(&new_commission)?;
2984 bonded_pool.put();
2985 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
2986 pool_id,
2987 current: new_commission,
2988 });
2989 Ok(())
2990 }
2991
2992 #[pallet::call_index(18)]
2998 #[pallet::weight(T::WeightInfo::set_commission_max())]
2999 pub fn set_commission_max(
3000 origin: OriginFor<T>,
3001 pool_id: PoolId,
3002 max_commission: Perbill,
3003 ) -> DispatchResult {
3004 let who = ensure_signed(origin)?;
3005 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3006 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3008
3009 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3010
3011 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3012 bonded_pool.put();
3013
3014 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3015 Ok(())
3016 }
3017
3018 #[pallet::call_index(19)]
3023 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3024 pub fn set_commission_change_rate(
3025 origin: OriginFor<T>,
3026 pool_id: PoolId,
3027 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3028 ) -> DispatchResult {
3029 let who = ensure_signed(origin)?;
3030 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3031 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3033 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3034
3035 bonded_pool.commission.try_update_change_rate(change_rate)?;
3036 bonded_pool.put();
3037
3038 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3039 pool_id,
3040 change_rate,
3041 });
3042 Ok(())
3043 }
3044
3045 #[pallet::call_index(20)]
3062 #[pallet::weight(T::WeightInfo::claim_commission())]
3063 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3064 let who = ensure_signed(origin)?;
3065 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3067
3068 Self::do_claim_commission(who, pool_id)
3069 }
3070
3071 #[pallet::call_index(21)]
3079 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3080 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3081 let who = ensure_signed(origin)?;
3082 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3084
3085 Self::do_adjust_pool_deposit(who, pool_id)
3086 }
3087
3088 #[pallet::call_index(22)]
3093 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3094 pub fn set_commission_claim_permission(
3095 origin: OriginFor<T>,
3096 pool_id: PoolId,
3097 permission: Option<CommissionClaimPermission<T::AccountId>>,
3098 ) -> DispatchResult {
3099 let who = ensure_signed(origin)?;
3100 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3101 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3103 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3104
3105 bonded_pool.commission.claim_permission = permission.clone();
3106 bonded_pool.put();
3107
3108 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3109 pool_id,
3110 permission,
3111 });
3112
3113 Ok(())
3114 }
3115
3116 #[pallet::call_index(23)]
3126 #[pallet::weight(T::WeightInfo::apply_slash())]
3127 pub fn apply_slash(
3128 origin: OriginFor<T>,
3129 member_account: AccountIdLookupOf<T>,
3130 ) -> DispatchResultWithPostInfo {
3131 ensure!(
3132 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3133 Error::<T>::NotSupported
3134 );
3135
3136 let who = ensure_signed(origin)?;
3137 let member_account = T::Lookup::lookup(member_account)?;
3138 Self::do_apply_slash(&member_account, Some(who), true)?;
3139
3140 Ok(Pays::No.into())
3142 }
3143
3144 #[pallet::call_index(24)]
3154 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3155 pub fn migrate_delegation(
3156 origin: OriginFor<T>,
3157 member_account: AccountIdLookupOf<T>,
3158 ) -> DispatchResultWithPostInfo {
3159 let _caller = ensure_signed(origin)?;
3160
3161 ensure!(
3163 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3164 Error::<T>::NotSupported
3165 );
3166
3167 let member_account = T::Lookup::lookup(member_account)?;
3169 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3170
3171 let member =
3172 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3173
3174 ensure!(
3176 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3177 member.pool_id
3178 ))) == adapter::StakeStrategyType::Delegate,
3179 Error::<T>::NotMigrated
3180 );
3181
3182 let pool_contribution = member.total_balance();
3183 ensure!(
3186 pool_contribution >= T::Currency::minimum_balance(),
3187 Error::<T>::MinimumBondNotMet
3188 );
3189
3190 let delegation =
3191 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3192 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3194
3195 T::StakeAdapter::migrate_delegation(
3196 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3197 Member::from(member_account),
3198 pool_contribution,
3199 )?;
3200
3201 Ok(Pays::No.into())
3203 }
3204
3205 #[pallet::call_index(25)]
3215 #[pallet::weight(T::WeightInfo::pool_migrate())]
3216 pub fn migrate_pool_to_delegate_stake(
3217 origin: OriginFor<T>,
3218 pool_id: PoolId,
3219 ) -> DispatchResultWithPostInfo {
3220 ensure!(
3222 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3223 Error::<T>::NotSupported
3224 );
3225
3226 let _caller = ensure_signed(origin)?;
3227 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3229 ensure!(
3230 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3231 adapter::StakeStrategyType::Transfer,
3232 Error::<T>::AlreadyMigrated
3233 );
3234
3235 Self::migrate_to_delegate_stake(pool_id)?;
3236 Ok(Pays::No.into())
3237 }
3238 }
3239
3240 #[pallet::hooks]
3241 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3242 #[cfg(feature = "try-runtime")]
3243 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3244 Self::do_try_state(u8::MAX)
3245 }
3246
3247 fn integrity_test() {
3248 assert!(
3249 T::MaxPointsToBalance::get() > 0,
3250 "Minimum points to balance ratio must be greater than 0"
3251 );
3252 assert!(
3253 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3254 "There must be more unbonding pools then the bonding duration /
3255 so a slash can be applied to relevant unbonding pools. (We assume /
3256 the bonding duration > slash deffer duration.",
3257 );
3258 }
3259 }
3260}
3261
3262impl<T: Config> Pallet<T> {
3263 pub fn depositor_min_bond() -> BalanceOf<T> {
3271 T::StakeAdapter::minimum_nominator_bond()
3272 .max(MinCreateBond::<T>::get())
3273 .max(MinJoinBond::<T>::get())
3274 .max(T::Currency::minimum_balance())
3275 }
3276 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3281 let reward_account = bonded_pool.reward_account();
3282 let bonded_account = bonded_pool.bonded_account();
3283
3284 ReversePoolIdLookup::<T>::remove(&bonded_account);
3285 RewardPools::<T>::remove(bonded_pool.id);
3286 SubPoolsStorage::<T>::remove(bonded_pool.id);
3287
3288 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3290
3291 defensive_assert!(
3299 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3300 "reward account of dissolving pool should have no consumers"
3301 );
3302 defensive_assert!(
3303 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3304 "bonded account of dissolving pool should have no consumers"
3305 );
3306 defensive_assert!(
3307 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3308 "dissolving pool should not have any stake in the staking pallet"
3309 );
3310
3311 let reward_pool_remaining = T::Currency::reducible_balance(
3314 &reward_account,
3315 Preservation::Expendable,
3316 Fortitude::Polite,
3317 );
3318 let _ = T::Currency::transfer(
3319 &reward_account,
3320 &bonded_pool.roles.depositor,
3321 reward_pool_remaining,
3322 Preservation::Expendable,
3323 );
3324
3325 defensive_assert!(
3326 T::Currency::total_balance(&reward_account) == Zero::zero(),
3327 "could not transfer all amount to depositor while dissolving pool"
3328 );
3329 T::Currency::set_balance(&reward_account, Zero::zero());
3331
3332 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3334
3335 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3336 Metadata::<T>::remove(bonded_pool.id);
3338
3339 bonded_pool.remove();
3340 }
3341
3342 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3344 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3345 }
3346
3347 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3348 T::StakeAdapter::migrate_nominator_to_agent(
3349 Pool::from(Self::generate_bonded_account(id)),
3350 &Self::generate_reward_account(id),
3351 )
3352 }
3353
3354 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3356 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3359 }
3360
3361 fn get_member_with_pools(
3363 who: &T::AccountId,
3364 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3365 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3366 let bonded_pool =
3367 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3368 let reward_pool =
3369 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3370 Ok((member, bonded_pool, reward_pool))
3371 }
3372
3373 fn put_member_with_pools(
3376 member_account: &T::AccountId,
3377 member: PoolMember<T>,
3378 bonded_pool: BondedPool<T>,
3379 reward_pool: RewardPool<T>,
3380 ) {
3381 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3384 debug_assert_eq!(member.pool_id, bonded_pool.id);
3385
3386 bonded_pool.put();
3387 RewardPools::insert(member.pool_id, reward_pool);
3388 PoolMembers::<T>::insert(member_account, member);
3389 }
3390
3391 fn balance_to_point(
3394 current_balance: BalanceOf<T>,
3395 current_points: BalanceOf<T>,
3396 new_funds: BalanceOf<T>,
3397 ) -> BalanceOf<T> {
3398 let u256 = T::BalanceToU256::convert;
3399 let balance = T::U256ToBalance::convert;
3400 match (current_balance.is_zero(), current_points.is_zero()) {
3401 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3402 (true, false) => {
3403 new_funds.saturating_mul(current_points)
3406 },
3407 (false, false) => {
3408 balance(
3410 u256(current_points)
3411 .saturating_mul(u256(new_funds))
3412 .div(u256(current_balance)),
3414 )
3415 },
3416 }
3417 }
3418
3419 fn point_to_balance(
3422 current_balance: BalanceOf<T>,
3423 current_points: BalanceOf<T>,
3424 points: BalanceOf<T>,
3425 ) -> BalanceOf<T> {
3426 let u256 = T::BalanceToU256::convert;
3427 let balance = T::U256ToBalance::convert;
3428 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3429 return Zero::zero()
3431 }
3432
3433 balance(
3435 u256(current_balance)
3436 .saturating_mul(u256(points))
3437 .div(u256(current_points)),
3439 )
3440 }
3441
3442 fn do_reward_payout(
3446 member_account: &T::AccountId,
3447 member: &mut PoolMember<T>,
3448 bonded_pool: &mut BondedPool<T>,
3449 reward_pool: &mut RewardPool<T>,
3450 ) -> Result<BalanceOf<T>, DispatchError> {
3451 debug_assert_eq!(member.pool_id, bonded_pool.id);
3452 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3453
3454 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3456
3457 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3458 bonded_pool.id,
3459 bonded_pool.points,
3460 bonded_pool.commission.current(),
3461 )?;
3462
3463 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3466 if pending_rewards.is_zero() {
3467 return Ok(pending_rewards)
3468 }
3469
3470 member.last_recorded_reward_counter = current_reward_counter;
3472 reward_pool.register_claimed_reward(pending_rewards);
3473
3474 T::Currency::transfer(
3475 &bonded_pool.reward_account(),
3476 member_account,
3477 pending_rewards,
3478 Preservation::Preserve,
3481 )?;
3482
3483 Self::deposit_event(Event::<T>::PaidOut {
3484 member: member_account.clone(),
3485 pool_id: member.pool_id,
3486 payout: pending_rewards,
3487 });
3488 Ok(pending_rewards)
3489 }
3490
3491 fn do_create(
3492 who: T::AccountId,
3493 amount: BalanceOf<T>,
3494 root: AccountIdLookupOf<T>,
3495 nominator: AccountIdLookupOf<T>,
3496 bouncer: AccountIdLookupOf<T>,
3497 pool_id: PoolId,
3498 ) -> DispatchResult {
3499 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3501
3502 let root = T::Lookup::lookup(root)?;
3503 let nominator = T::Lookup::lookup(nominator)?;
3504 let bouncer = T::Lookup::lookup(bouncer)?;
3505
3506 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3507 ensure!(
3508 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3509 Error::<T>::MaxPools
3510 );
3511 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3512 let mut bonded_pool = BondedPool::<T>::new(
3513 pool_id,
3514 PoolRoles {
3515 root: Some(root),
3516 nominator: Some(nominator),
3517 bouncer: Some(bouncer),
3518 depositor: who.clone(),
3519 },
3520 );
3521
3522 bonded_pool.try_inc_members()?;
3523 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3524
3525 T::Currency::transfer(
3527 &who,
3528 &bonded_pool.reward_account(),
3529 T::Currency::minimum_balance(),
3530 Preservation::Expendable,
3531 )?;
3532
3533 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3535
3536 PoolMembers::<T>::insert(
3537 who.clone(),
3538 PoolMember::<T> {
3539 pool_id,
3540 points,
3541 last_recorded_reward_counter: Zero::zero(),
3542 unbonding_eras: Default::default(),
3543 },
3544 );
3545 RewardPools::<T>::insert(
3546 pool_id,
3547 RewardPool::<T> {
3548 last_recorded_reward_counter: Zero::zero(),
3549 last_recorded_total_payouts: Zero::zero(),
3550 total_rewards_claimed: Zero::zero(),
3551 total_commission_pending: Zero::zero(),
3552 total_commission_claimed: Zero::zero(),
3553 },
3554 );
3555 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3556
3557 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3558
3559 Self::deposit_event(Event::<T>::Bonded {
3560 member: who,
3561 pool_id,
3562 bonded: amount,
3563 joined: true,
3564 });
3565 bonded_pool.put();
3566
3567 Ok(())
3568 }
3569
3570 fn do_bond_extra(
3571 signer: T::AccountId,
3572 member_account: T::AccountId,
3573 extra: BondExtra<BalanceOf<T>>,
3574 ) -> DispatchResult {
3575 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3577
3578 if signer != member_account {
3579 ensure!(
3580 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3581 Error::<T>::DoesNotHavePermission
3582 );
3583 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3584 }
3585
3586 let (mut member, mut bonded_pool, mut reward_pool) =
3587 Self::get_member_with_pools(&member_account)?;
3588
3589 reward_pool.update_records(
3592 bonded_pool.id,
3593 bonded_pool.points,
3594 bonded_pool.commission.current(),
3595 )?;
3596 let claimed = Self::do_reward_payout(
3597 &member_account,
3598 &mut member,
3599 &mut bonded_pool,
3600 &mut reward_pool,
3601 )?;
3602
3603 let (points_issued, bonded) = match extra {
3604 BondExtra::FreeBalance(amount) =>
3605 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
3606 BondExtra::Rewards =>
3607 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
3608 };
3609
3610 bonded_pool.ok_to_be_open()?;
3611 member.points =
3612 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3613
3614 Self::deposit_event(Event::<T>::Bonded {
3615 member: member_account.clone(),
3616 pool_id: member.pool_id,
3617 bonded,
3618 joined: false,
3619 });
3620 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3621
3622 Ok(())
3623 }
3624
3625 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3626 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3627 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3628
3629 let mut reward_pool = RewardPools::<T>::get(pool_id)
3630 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3631
3632 reward_pool.update_records(
3635 pool_id,
3636 bonded_pool.points,
3637 bonded_pool.commission.current(),
3638 )?;
3639
3640 let commission = reward_pool.total_commission_pending;
3641 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3642
3643 let payee = bonded_pool
3644 .commission
3645 .current
3646 .as_ref()
3647 .map(|(_, p)| p.clone())
3648 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3649
3650 T::Currency::transfer(
3652 &bonded_pool.reward_account(),
3653 &payee,
3654 commission,
3655 Preservation::Preserve,
3656 )?;
3657
3658 reward_pool.total_commission_claimed =
3660 reward_pool.total_commission_claimed.saturating_add(commission);
3661 reward_pool.total_commission_pending = Zero::zero();
3663 RewardPools::<T>::insert(pool_id, reward_pool);
3664
3665 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3666 Ok(())
3667 }
3668
3669 pub(crate) fn do_claim_payout(
3670 signer: T::AccountId,
3671 member_account: T::AccountId,
3672 ) -> DispatchResult {
3673 if signer != member_account {
3674 ensure!(
3675 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3676 Error::<T>::DoesNotHavePermission
3677 );
3678 }
3679 let (mut member, mut bonded_pool, mut reward_pool) =
3680 Self::get_member_with_pools(&member_account)?;
3681
3682 let _ = Self::do_reward_payout(
3683 &member_account,
3684 &mut member,
3685 &mut bonded_pool,
3686 &mut reward_pool,
3687 )?;
3688
3689 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3690 Ok(())
3691 }
3692
3693 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3694 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3695
3696 let reward_acc = &bonded_pool.reward_account();
3697 let pre_frozen_balance =
3698 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3699 let min_balance = T::Currency::minimum_balance();
3700
3701 if pre_frozen_balance == min_balance {
3702 return Err(Error::<T>::NothingToAdjust.into())
3703 }
3704
3705 Self::freeze_pool_deposit(reward_acc)?;
3707
3708 if pre_frozen_balance > min_balance {
3709 let excess = pre_frozen_balance.saturating_sub(min_balance);
3711 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3712 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3713 pool_id: pool,
3714 amount: excess,
3715 });
3716 } else {
3717 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3719 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3720 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3721 pool_id: pool,
3722 amount: deficit,
3723 });
3724 }
3725
3726 Ok(())
3727 }
3728
3729 fn do_apply_slash(
3731 member_account: &T::AccountId,
3732 reporter: Option<T::AccountId>,
3733 enforce_min_slash: bool,
3734 ) -> DispatchResult {
3735 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3736
3737 let pending_slash =
3738 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3739
3740 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3742
3743 if enforce_min_slash {
3744 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3746 }
3747
3748 T::StakeAdapter::member_slash(
3749 Member::from(member_account.clone()),
3750 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3751 pending_slash,
3752 reporter,
3753 )
3754 }
3755
3756 fn member_pending_slash(
3760 member_account: Member<T::AccountId>,
3761 pool_member: PoolMember<T>,
3762 ) -> Result<BalanceOf<T>, DispatchError> {
3763 debug_assert!(
3765 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3766 pool_member
3767 );
3768
3769 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3770 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3773 return Ok(Zero::zero())
3774 }
3775
3776 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3778 .ok_or(Error::<T>::NotMigrated)?;
3780
3781 let expected_balance = pool_member.total_balance();
3783
3784 Ok(actual_balance.saturating_sub(expected_balance))
3786 }
3787
3788 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3790 T::Currency::set_freeze(
3791 &FreezeReason::PoolMinBalance.into(),
3792 reward_acc,
3793 T::Currency::minimum_balance(),
3794 )
3795 }
3796
3797 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3799 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3800 }
3801
3802 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3839 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3840 if level.is_zero() {
3841 return Ok(())
3842 }
3843 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3846 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3847 ensure!(
3848 bonded_pools == reward_pools,
3849 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3850 );
3851
3852 ensure!(
3853 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3854 "`SubPoolsStorage` must be a subset of the above superset."
3855 );
3856 ensure!(
3857 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3858 "`Metadata` keys must be a subset of the above superset."
3859 );
3860
3861 ensure!(
3862 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3863 Error::<T>::MaxPools
3864 );
3865
3866 for id in reward_pools {
3867 let account = Self::generate_reward_account(id);
3868 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3869 T::Currency::minimum_balance()
3870 {
3871 log!(
3872 warn,
3873 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3874 changed recently. Pool operators should be notified to top up the reward \
3875 account",
3876 id,
3877 T::Currency::reducible_balance(
3878 &account,
3879 Preservation::Expendable,
3880 Fortitude::Polite
3881 ),
3882 T::Currency::minimum_balance(),
3883 )
3884 }
3885 }
3886
3887 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3888 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3889 let mut all_members = 0u32;
3890 let mut total_balance_members = Default::default();
3891 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3892 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3893 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3894 *pools_members.entry(d.pool_id).or_default() += 1;
3895 all_members += 1;
3896
3897 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3898 if !bonded_pool.points.is_zero() {
3899 let commission = bonded_pool.commission.current();
3900 let (current_rc, _) = reward_pool
3901 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3902 .unwrap();
3903 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3904 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3905 } total_balance_members += d.total_balance();
3907
3908 Ok(())
3909 })?;
3910
3911 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3912 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3915 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3916
3917 if !pending_rewards_lt_leftover_bal {
3920 log::warn!(
3921 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
3922 id,
3923 pools_members_pending_rewards.get(&id),
3924 RewardPool::<T>::current_balance(id)
3925 );
3926 }
3927 Ok(())
3928 })?;
3929
3930 let mut expected_tvl: BalanceOf<T> = Default::default();
3931 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
3932 let bonded_pool = BondedPool { id, inner };
3933 ensure!(
3934 pools_members.get(&id).copied().unwrap_or_default() ==
3935 bonded_pool.member_counter,
3936 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
3937 );
3938 ensure!(
3939 MaxPoolMembersPerPool::<T>::get()
3940 .map_or(true, |max| bonded_pool.member_counter <= max),
3941 Error::<T>::MaxPoolMembers
3942 );
3943
3944 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
3945 ensure!(
3946 bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) ||
3947 depositor.active_points() >= MinCreateBond::<T>::get(),
3948 "depositor must always have MinCreateBond stake in the pool, except for when the \
3949 pool is being destroyed and the depositor is the last member",
3950 );
3951
3952 ensure!(
3953 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
3954 "Each `BondedPool.points` must never be lower than the pool's balance"
3955 );
3956
3957 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
3958
3959 Ok(())
3960 })?;
3961
3962 ensure!(
3963 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
3964 Error::<T>::MaxPoolMembers
3965 );
3966
3967 ensure!(
3968 TotalValueLocked::<T>::get() == expected_tvl,
3969 "TVL deviates from the actual sum of funds of all Pools."
3970 );
3971
3972 ensure!(
3973 TotalValueLocked::<T>::get() <= total_balance_members,
3974 "TVL must be equal to or less than the total balance of all PoolMembers."
3975 );
3976
3977 if level <= 1 {
3978 return Ok(())
3979 }
3980
3981 for (pool_id, _pool) in BondedPools::<T>::iter() {
3982 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
3983 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
3984
3985 let sum_unbonding_balance = subs.sum_unbonding_balance();
3986 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
3987 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
3988 .unwrap_or(T::Currency::total_balance(&pool_account));
3991
3992 assert!(
3993 total_balance >= bonded_balance + sum_unbonding_balance,
3994 "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
3995 pool_id,
3996 _pool,
3997 total_balance,
3998 bonded_balance,
3999 sum_unbonding_balance
4000 );
4001 }
4002
4003 let _ = Self::check_ed_imbalance()?;
4006
4007 Ok(())
4008 }
4009
4010 #[cfg(any(
4014 feature = "try-runtime",
4015 feature = "runtime-benchmarks",
4016 feature = "fuzzing",
4017 test,
4018 debug_assertions
4019 ))]
4020 pub fn check_ed_imbalance() -> Result<(), DispatchError> {
4021 let mut failed: u32 = 0;
4022 BondedPools::<T>::iter_keys().for_each(|id| {
4023 let reward_acc = Self::generate_reward_account(id);
4024 let frozen_balance =
4025 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4026
4027 let expected_frozen_balance = T::Currency::minimum_balance();
4028 if frozen_balance != expected_frozen_balance {
4029 failed += 1;
4030 log::warn!(
4031 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}",
4032 id,
4033 expected_frozen_balance,
4034 frozen_balance,
4035 );
4036 }
4037 });
4038
4039 ensure!(failed == 0, "Some pools do not have correct ED frozen");
4040 Ok(())
4041 }
4042 #[cfg(any(feature = "runtime-benchmarks", test))]
4047 pub fn fully_unbond(
4048 origin: frame_system::pallet_prelude::OriginFor<T>,
4049 member: T::AccountId,
4050 ) -> DispatchResult {
4051 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4052 let member_lookup = T::Lookup::unlookup(member);
4053 Self::unbond(origin, member_lookup, points)
4054 }
4055}
4056
4057impl<T: Config> Pallet<T> {
4058 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4062 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4063 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4064 .zip(BondedPools::<T>::get(pool_member.pool_id))
4065 {
4066 let commission = bonded_pool.commission.current();
4067 let (current_reward_counter, _) = reward_pool
4068 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4069 .ok()?;
4070 return pool_member.pending_rewards(current_reward_counter).ok()
4071 }
4072 }
4073
4074 None
4075 }
4076
4077 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4081 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4082 pool.points_to_balance(points)
4083 } else {
4084 Zero::zero()
4085 }
4086 }
4087
4088 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4092 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4093 let bonded_balance =
4094 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4095 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4096 } else {
4097 Zero::zero()
4098 }
4099 }
4100
4101 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4105 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4106 }
4107
4108 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4115 PoolMembers::<T>::get(who.clone())
4116 .map(|pool_member| {
4117 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4118 })
4119 .unwrap_or_default()
4120 }
4121
4122 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4127 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4129 return false
4130 }
4131
4132 if !BondedPools::<T>::contains_key(pool_id) {
4134 return false
4135 }
4136
4137 let pool_account = Self::generate_bonded_account(pool_id);
4138
4139 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4141 adapter::StakeStrategyType::Delegate
4142 }
4143
4144 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4150 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4152 return false
4153 }
4154
4155 PoolMembers::<T>::get(who.clone())
4156 .map(|pool_member| {
4157 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4158 return false
4160 }
4161
4162 let member_balance = pool_member.total_balance();
4163 let delegated_balance =
4164 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4165
4166 delegated_balance.is_none() && !member_balance.is_zero()
4169 })
4170 .unwrap_or_default()
4171 }
4172
4173 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4178 PoolMembers::<T>::get(who.clone())
4179 .map(|m| m.total_balance())
4180 .unwrap_or_default()
4181 }
4182
4183 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4185 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4186 .unwrap_or_default()
4187 }
4188
4189 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4191 let bonded_account = Self::generate_bonded_account(pool_id);
4192 let reward_account = Self::generate_reward_account(pool_id);
4193 (bonded_account, reward_account)
4194 }
4195}
4196
4197impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4198 fn on_slash(
4204 pool_account: &T::AccountId,
4205 slashed_bonded: BalanceOf<T>,
4208 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4209 total_slashed: BalanceOf<T>,
4210 ) {
4211 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4212 TotalValueLocked::<T>::mutate(|tvl| {
4215 tvl.defensive_saturating_reduce(total_slashed);
4216 });
4217
4218 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4219 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4221 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4222 pool.balance = *slashed_balance;
4223 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4224 era: *era,
4225 pool_id,
4226 balance: *slashed_balance,
4227 });
4228 }
4229 });
4230 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4231 } else if !slashed_unlocking.is_empty() {
4232 defensive!("Expected SubPools were not found");
4233 }
4234 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4235 }
4236
4237 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4240 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4241 TotalValueLocked::<T>::mutate(|tvl| {
4242 tvl.saturating_reduce(amount);
4243 });
4244 }
4245 }
4246}
4247
4248pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4250impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4251 fn contains(t: &T::AccountId) -> bool {
4252 PoolMembers::<T>::contains_key(t)
4253 }
4254}