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 DecodeWithMemTracking,
517 MaxEncodedLen,
518 TypeInfo,
519 RuntimeDebugNoBound,
520 CloneNoBound,
521 PartialEqNoBound,
522 EqNoBound,
523)]
524#[cfg_attr(feature = "std", derive(DefaultNoBound))]
525#[scale_info(skip_type_params(T))]
526pub struct PoolMember<T: Config> {
527 pub pool_id: PoolId,
529 pub points: BalanceOf<T>,
532 pub last_recorded_reward_counter: T::RewardCounter,
534 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
537}
538
539impl<T: Config> PoolMember<T> {
540 fn pending_rewards(
542 &self,
543 current_reward_counter: T::RewardCounter,
544 ) -> Result<BalanceOf<T>, Error<T>> {
545 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
563 .checked_mul_int(self.active_points())
564 .ok_or(Error::<T>::OverflowRisk)
565 }
566
567 fn active_balance(&self) -> BalanceOf<T> {
572 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
573 pool.points_to_balance(self.points)
574 } else {
575 Zero::zero()
576 }
577 }
578
579 pub fn total_balance(&self) -> BalanceOf<T> {
585 let pool = match BondedPool::<T>::get(self.pool_id) {
586 Some(pool) => pool,
587 None => {
588 defensive!("pool should exist; qed");
590 return Zero::zero();
591 },
592 };
593
594 let active_balance = pool.points_to_balance(self.active_points());
595
596 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
597 Some(sub_pools) => sub_pools,
598 None => return active_balance,
599 };
600
601 let unbonding_balance = self.unbonding_eras.iter().fold(
602 BalanceOf::<T>::zero(),
603 |accumulator, (era, unlocked_points)| {
604 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
607 accumulator + (era_pool.point_to_balance(*unlocked_points))
608 },
609 );
610
611 active_balance + unbonding_balance
612 }
613
614 fn total_points(&self) -> BalanceOf<T> {
616 self.active_points().saturating_add(self.unbonding_points())
617 }
618
619 fn active_points(&self) -> BalanceOf<T> {
621 self.points
622 }
623
624 fn unbonding_points(&self) -> BalanceOf<T> {
626 self.unbonding_eras
627 .as_ref()
628 .iter()
629 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
630 }
631
632 fn try_unbond(
640 &mut self,
641 points_dissolved: BalanceOf<T>,
642 points_issued: BalanceOf<T>,
643 unbonding_era: EraIndex,
644 ) -> Result<(), Error<T>> {
645 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
646 match self.unbonding_eras.get_mut(&unbonding_era) {
647 Some(already_unbonding_points) =>
648 *already_unbonding_points =
649 already_unbonding_points.saturating_add(points_issued),
650 None => self
651 .unbonding_eras
652 .try_insert(unbonding_era, points_issued)
653 .map(|old| {
654 if old.is_some() {
655 defensive!("value checked to not exist in the map; qed");
656 }
657 })
658 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
659 }
660 self.points = new_points;
661 Ok(())
662 } else {
663 Err(Error::<T>::MinimumBondNotMet)
664 }
665 }
666
667 fn withdraw_unlocked(
674 &mut self,
675 current_era: EraIndex,
676 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
677 let mut removed_points =
679 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
680 self.unbonding_eras.retain(|e, p| {
681 if *e > current_era {
682 true
683 } else {
684 removed_points
685 .try_insert(*e, *p)
686 .expect("source map is bounded, this is a subset, will be bounded; qed");
687 false
688 }
689 });
690 removed_points
691 }
692}
693
694#[derive(
696 Encode,
697 Decode,
698 DecodeWithMemTracking,
699 MaxEncodedLen,
700 TypeInfo,
701 PartialEq,
702 RuntimeDebugNoBound,
703 Clone,
704 Copy,
705)]
706pub enum PoolState {
707 Open,
709 Blocked,
711 Destroying,
716}
717
718#[derive(
724 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone,
725)]
726pub struct PoolRoles<AccountId> {
727 pub depositor: AccountId,
730 pub root: Option<AccountId>,
733 pub nominator: Option<AccountId>,
735 pub bouncer: Option<AccountId>,
737}
738
739#[derive(
741 PartialEq,
742 Eq,
743 Copy,
744 Clone,
745 Encode,
746 Decode,
747 DecodeWithMemTracking,
748 RuntimeDebug,
749 TypeInfo,
750 MaxEncodedLen,
751)]
752pub enum CommissionClaimPermission<AccountId> {
753 Permissionless,
754 Account(AccountId),
755}
756
757#[derive(
770 Encode,
771 Decode,
772 DecodeWithMemTracking,
773 DefaultNoBound,
774 MaxEncodedLen,
775 TypeInfo,
776 DebugNoBound,
777 PartialEq,
778 Copy,
779 Clone,
780)]
781#[codec(mel_bound(T: Config))]
782#[scale_info(skip_type_params(T))]
783pub struct Commission<T: Config> {
784 pub current: Option<(Perbill, T::AccountId)>,
786 pub max: Option<Perbill>,
789 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
792 pub throttle_from: Option<BlockNumberFor<T>>,
795 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
798}
799
800impl<T: Config> Commission<T> {
801 fn throttling(&self, to: &Perbill) -> bool {
808 if let Some(t) = self.change_rate.as_ref() {
809 let commission_as_percent =
810 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
811
812 if *to <= commission_as_percent {
814 return false
815 }
816 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
820 return true
821 }
822
823 return self.throttle_from.map_or_else(
828 || {
829 defensive!("throttle_from should exist if change_rate is set");
830 true
831 },
832 |f| {
833 if t.min_delay == Zero::zero() {
835 false
836 } else {
837 let blocks_surpassed =
839 T::BlockNumberProvider::current_block_number().saturating_sub(f);
840 blocks_surpassed < t.min_delay
841 }
842 },
843 )
844 }
845 false
846 }
847
848 fn current(&self) -> Perbill {
851 self.current
852 .as_ref()
853 .map_or(Perbill::zero(), |(c, _)| *c)
854 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
855 }
856
857 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
863 self.current = match current {
864 None => None,
865 Some((commission, payee)) => {
866 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
867 ensure!(
868 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
869 Error::<T>::CommissionExceedsGlobalMaximum
870 );
871 ensure!(
872 self.max.map_or(true, |m| commission <= &m),
873 Error::<T>::CommissionExceedsMaximum
874 );
875 if commission.is_zero() {
876 None
877 } else {
878 Some((*commission, payee.clone()))
879 }
880 },
881 };
882 self.register_update();
883 Ok(())
884 }
885
886 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
895 ensure!(
896 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
897 Error::<T>::CommissionExceedsGlobalMaximum
898 );
899 if let Some(old) = self.max.as_mut() {
900 if new_max > *old {
901 return Err(Error::<T>::MaxCommissionRestricted.into())
902 }
903 *old = new_max;
904 } else {
905 self.max = Some(new_max)
906 };
907 let updated_current = self
908 .current
909 .as_mut()
910 .map(|(c, _)| {
911 let u = *c > new_max;
912 *c = (*c).min(new_max);
913 u
914 })
915 .unwrap_or(false);
916
917 if updated_current {
918 if let Some((_, payee)) = self.current.as_ref() {
919 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
920 pool_id,
921 current: Some((new_max, payee.clone())),
922 });
923 }
924 self.register_update();
925 }
926 Ok(())
927 }
928
929 fn try_update_change_rate(
938 &mut self,
939 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
940 ) -> DispatchResult {
941 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
942
943 if self.change_rate.is_none() {
944 self.register_update();
945 }
946 self.change_rate = Some(change_rate);
947 Ok(())
948 }
949
950 fn register_update(&mut self) {
952 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
953 }
954
955 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
960 self.change_rate
961 .as_ref()
962 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
963 .unwrap_or(false)
964 }
965}
966
967#[derive(
975 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
976)]
977pub struct CommissionChangeRate<BlockNumber> {
978 pub max_increase: Perbill,
980 pub min_delay: BlockNumber,
982}
983
984#[derive(
986 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
987)]
988#[codec(mel_bound(T: Config))]
989#[scale_info(skip_type_params(T))]
990pub struct BondedPoolInner<T: Config> {
991 pub commission: Commission<T>,
993 pub member_counter: u32,
995 pub points: BalanceOf<T>,
997 pub roles: PoolRoles<T::AccountId>,
999 pub state: PoolState,
1001}
1002
1003#[derive(RuntimeDebugNoBound)]
1008#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1009pub struct BondedPool<T: Config> {
1010 id: PoolId,
1012 inner: BondedPoolInner<T>,
1014}
1015
1016impl<T: Config> core::ops::Deref for BondedPool<T> {
1017 type Target = BondedPoolInner<T>;
1018 fn deref(&self) -> &Self::Target {
1019 &self.inner
1020 }
1021}
1022
1023impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1024 fn deref_mut(&mut self) -> &mut Self::Target {
1025 &mut self.inner
1026 }
1027}
1028
1029impl<T: Config> BondedPool<T> {
1030 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1032 Self {
1033 id,
1034 inner: BondedPoolInner {
1035 commission: Commission::default(),
1036 member_counter: Zero::zero(),
1037 points: Zero::zero(),
1038 roles,
1039 state: PoolState::Open,
1040 },
1041 }
1042 }
1043
1044 pub fn get(id: PoolId) -> Option<Self> {
1046 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1047 }
1048
1049 fn bonded_account(&self) -> T::AccountId {
1051 Pallet::<T>::generate_bonded_account(self.id)
1052 }
1053
1054 fn reward_account(&self) -> T::AccountId {
1056 Pallet::<T>::generate_reward_account(self.id)
1057 }
1058
1059 fn put(self) {
1061 BondedPools::<T>::insert(self.id, self.inner);
1062 }
1063
1064 fn remove(self) {
1066 BondedPools::<T>::remove(self.id);
1067 }
1068
1069 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1073 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1074 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1075 }
1076
1077 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1081 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1082 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1083 }
1084
1085 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1087 let points_to_issue = self.balance_to_point(new_funds);
1088 self.points = self.points.saturating_add(points_to_issue);
1089 points_to_issue
1090 }
1091
1092 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1099 let balance = self.points_to_balance(points);
1102 self.points = self.points.saturating_sub(points);
1103 balance
1104 }
1105
1106 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1109 ensure!(
1110 MaxPoolMembersPerPool::<T>::get()
1111 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1112 Error::<T>::MaxPoolMembers
1113 );
1114 ensure!(
1115 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1116 Error::<T>::MaxPoolMembers
1117 );
1118 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1119 Ok(())
1120 }
1121
1122 fn dec_members(mut self) -> Self {
1124 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1125 self
1126 }
1127
1128 fn is_root(&self, who: &T::AccountId) -> bool {
1129 self.roles.root.as_ref().map_or(false, |root| root == who)
1130 }
1131
1132 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1133 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1134 }
1135
1136 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1137 self.is_root(who)
1138 }
1139
1140 fn can_nominate(&self, who: &T::AccountId) -> bool {
1141 self.is_root(who) ||
1142 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1143 }
1144
1145 fn can_kick(&self, who: &T::AccountId) -> bool {
1146 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1147 }
1148
1149 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1150 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1151 }
1152
1153 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1154 self.is_root(who) || self.is_bouncer(who)
1155 }
1156
1157 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1158 self.is_root(who)
1159 }
1160
1161 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1162 if let Some(permission) = self.commission.claim_permission.as_ref() {
1163 match permission {
1164 CommissionClaimPermission::Permissionless => true,
1165 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1166 }
1167 } else {
1168 self.is_root(who)
1169 }
1170 }
1171
1172 fn is_destroying(&self) -> bool {
1173 matches!(self.state, PoolState::Destroying)
1174 }
1175
1176 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1177 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1184 }
1185
1186 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1189 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1190
1191 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1192 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1193
1194 let points_to_balance_ratio_floor = self
1195 .points
1196 .div(bonded_balance);
1198
1199 let max_points_to_balance = T::MaxPointsToBalance::get();
1200
1201 ensure!(
1205 points_to_balance_ratio_floor < max_points_to_balance.into(),
1206 Error::<T>::OverflowRisk
1207 );
1208
1209 Ok(())
1213 }
1214
1215 fn ok_to_join(&self) -> Result<(), DispatchError> {
1217 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1218 self.ok_to_be_open()?;
1219 Ok(())
1220 }
1221
1222 fn ok_to_unbond_with(
1223 &self,
1224 caller: &T::AccountId,
1225 target_account: &T::AccountId,
1226 target_member: &PoolMember<T>,
1227 unbonding_points: BalanceOf<T>,
1228 ) -> Result<(), DispatchError> {
1229 let is_permissioned = caller == target_account;
1230 let is_depositor = *target_account == self.roles.depositor;
1231 let is_full_unbond = unbonding_points == target_member.active_points();
1232
1233 let balance_after_unbond = {
1234 let new_depositor_points =
1235 target_member.active_points().saturating_sub(unbonding_points);
1236 let mut target_member_after_unbond = (*target_member).clone();
1237 target_member_after_unbond.points = new_depositor_points;
1238 target_member_after_unbond.active_balance()
1239 };
1240
1241 ensure!(
1243 is_permissioned || is_full_unbond,
1244 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1245 );
1246
1247 ensure!(
1249 is_full_unbond ||
1250 balance_after_unbond >=
1251 if is_depositor {
1252 Pallet::<T>::depositor_min_bond()
1253 } else {
1254 MinJoinBond::<T>::get()
1255 },
1256 Error::<T>::MinimumBondNotMet
1257 );
1258
1259 match (is_permissioned, is_depositor) {
1261 (true, false) => (),
1262 (true, true) => {
1263 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1266 } else {
1268 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1270 }
1271 },
1272 (false, false) => {
1273 debug_assert!(is_full_unbond);
1276 ensure!(
1277 self.can_kick(caller) || self.is_destroying(),
1278 Error::<T>::NotKickerOrDestroying
1279 )
1280 },
1281 (false, true) => {
1282 return Err(Error::<T>::DoesNotHavePermission.into())
1284 },
1285 };
1286
1287 Ok(())
1288 }
1289
1290 fn ok_to_withdraw_unbonded_with(
1294 &self,
1295 caller: &T::AccountId,
1296 target_account: &T::AccountId,
1297 ) -> Result<(), DispatchError> {
1298 let is_permissioned = caller == target_account;
1300 ensure!(
1301 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1302 Error::<T>::NotKickerOrDestroying
1303 );
1304 Ok(())
1305 }
1306
1307 fn try_bond_funds(
1315 &mut self,
1316 who: &T::AccountId,
1317 amount: BalanceOf<T>,
1318 ty: BondType,
1319 ) -> Result<BalanceOf<T>, DispatchError> {
1320 let points_issued = self.issue(amount);
1323
1324 T::StakeAdapter::pledge_bond(
1325 Member::from(who.clone()),
1326 Pool::from(self.bonded_account()),
1327 &self.reward_account(),
1328 amount,
1329 ty,
1330 )?;
1331 TotalValueLocked::<T>::mutate(|tvl| {
1332 tvl.saturating_accrue(amount);
1333 });
1334
1335 Ok(points_issued)
1336 }
1337
1338 fn set_state(&mut self, state: PoolState) {
1341 if self.state != state {
1342 self.state = state;
1343 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1344 pool_id: self.id,
1345 new_state: state,
1346 });
1347 };
1348 }
1349}
1350
1351#[derive(
1357 Encode,
1358 Decode,
1359 MaxEncodedLen,
1360 DecodeWithMemTracking,
1361 TypeInfo,
1362 CloneNoBound,
1363 PartialEqNoBound,
1364 EqNoBound,
1365 RuntimeDebugNoBound,
1366)]
1367#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1368#[codec(mel_bound(T: Config))]
1369#[scale_info(skip_type_params(T))]
1370pub struct RewardPool<T: Config> {
1371 pub last_recorded_reward_counter: T::RewardCounter,
1376 pub last_recorded_total_payouts: BalanceOf<T>,
1382 pub total_rewards_claimed: BalanceOf<T>,
1384 pub total_commission_pending: BalanceOf<T>,
1386 pub total_commission_claimed: BalanceOf<T>,
1388}
1389
1390impl<T: Config> RewardPool<T> {
1391 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1393 self.last_recorded_reward_counter
1394 }
1395
1396 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1398 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1399 }
1400
1401 fn update_records(
1408 &mut self,
1409 id: PoolId,
1410 bonded_points: BalanceOf<T>,
1411 commission: Perbill,
1412 ) -> Result<(), Error<T>> {
1413 let balance = Self::current_balance(id);
1414
1415 let (current_reward_counter, new_pending_commission) =
1416 self.current_reward_counter(id, bonded_points, commission)?;
1417
1418 self.last_recorded_reward_counter = current_reward_counter;
1422
1423 self.total_commission_pending =
1426 self.total_commission_pending.saturating_add(new_pending_commission);
1427
1428 let last_recorded_total_payouts = balance
1432 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1433 .ok_or(Error::<T>::OverflowRisk)?;
1434
1435 self.last_recorded_total_payouts =
1442 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1443
1444 Ok(())
1445 }
1446
1447 fn current_reward_counter(
1450 &self,
1451 id: PoolId,
1452 bonded_points: BalanceOf<T>,
1453 commission: Perbill,
1454 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1455 let balance = Self::current_balance(id);
1456
1457 let current_payout_balance = balance
1462 .saturating_add(self.total_rewards_claimed)
1463 .saturating_add(self.total_commission_claimed)
1464 .saturating_sub(self.last_recorded_total_payouts);
1465
1466 let new_pending_commission = commission * current_payout_balance;
1469 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1470
1471 let current_reward_counter =
1506 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1507 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1508 .ok_or(Error::<T>::OverflowRisk)?;
1509
1510 Ok((current_reward_counter, new_pending_commission))
1511 }
1512
1513 fn current_balance(id: PoolId) -> BalanceOf<T> {
1517 T::Currency::reducible_balance(
1518 &Pallet::<T>::generate_reward_account(id),
1519 Preservation::Expendable,
1520 Fortitude::Polite,
1521 )
1522 }
1523}
1524
1525#[derive(
1527 Encode,
1528 Decode,
1529 MaxEncodedLen,
1530 DecodeWithMemTracking,
1531 TypeInfo,
1532 DefaultNoBound,
1533 RuntimeDebugNoBound,
1534 CloneNoBound,
1535 PartialEqNoBound,
1536 EqNoBound,
1537)]
1538#[codec(mel_bound(T: Config))]
1539#[scale_info(skip_type_params(T))]
1540pub struct UnbondPool<T: Config> {
1541 pub points: BalanceOf<T>,
1543 pub balance: BalanceOf<T>,
1545}
1546
1547impl<T: Config> UnbondPool<T> {
1548 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1549 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1550 }
1551
1552 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1553 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1554 }
1555
1556 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1560 let new_points = self.balance_to_point(new_funds);
1561 self.points = self.points.saturating_add(new_points);
1562 self.balance = self.balance.saturating_add(new_funds);
1563 new_points
1564 }
1565
1566 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1571 let balance_to_unbond = self.point_to_balance(points);
1572 self.points = self.points.saturating_sub(points);
1573 self.balance = self.balance.saturating_sub(balance_to_unbond);
1574
1575 balance_to_unbond
1576 }
1577}
1578
1579#[derive(
1580 Encode,
1581 Decode,
1582 MaxEncodedLen,
1583 DecodeWithMemTracking,
1584 TypeInfo,
1585 DefaultNoBound,
1586 RuntimeDebugNoBound,
1587 CloneNoBound,
1588 PartialEqNoBound,
1589 EqNoBound,
1590)]
1591#[codec(mel_bound(T: Config))]
1592#[scale_info(skip_type_params(T))]
1593pub struct SubPools<T: Config> {
1594 pub no_era: UnbondPool<T>,
1598 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1600}
1601
1602impl<T: Config> SubPools<T> {
1603 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1608 if let Some(newest_era_to_remove) =
1612 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1613 {
1614 self.with_era.retain(|k, v| {
1615 if *k > newest_era_to_remove {
1616 true
1618 } else {
1619 self.no_era.points = self.no_era.points.saturating_add(v.points);
1621 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1622 false
1623 }
1624 });
1625 }
1626
1627 self
1628 }
1629
1630 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1632 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1633 self.no_era.balance.saturating_add(
1634 self.with_era
1635 .values()
1636 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1637 )
1638 }
1639}
1640
1641pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1645
1646impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1647 fn get() -> u32 {
1648 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1652 }
1653}
1654
1655#[frame_support::pallet]
1656pub mod pallet {
1657 use super::*;
1658 use frame_support::traits::StorageVersion;
1659 use frame_system::pallet_prelude::{
1660 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1661 };
1662 use sp_runtime::Perbill;
1663
1664 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1666
1667 #[pallet::pallet]
1668 #[pallet::storage_version(STORAGE_VERSION)]
1669 pub struct Pallet<T>(_);
1670
1671 #[pallet::config]
1672 pub trait Config: frame_system::Config {
1673 #[allow(deprecated)]
1675 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1676
1677 type WeightInfo: weights::WeightInfo;
1679
1680 type Currency: Mutate<Self::AccountId>
1682 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1683
1684 type RuntimeFreezeReason: From<FreezeReason>;
1686
1687 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1700
1701 #[pallet::constant]
1703 type PalletId: Get<frame_support::PalletId>;
1704
1705 #[pallet::constant]
1718 type MaxPointsToBalance: Get<u8>;
1719
1720 #[pallet::constant]
1722 type MaxUnbonding: Get<u32>;
1723
1724 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1726
1727 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1729
1730 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1734
1735 type PostUnbondingPoolsWindow: Get<u32>;
1741
1742 type MaxMetadataLen: Get<u32>;
1744
1745 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1747
1748 type BlockNumberProvider: BlockNumberProvider;
1750
1751 type Filter: Contains<Self::AccountId>;
1753 }
1754
1755 #[pallet::storage]
1761 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1762
1763 #[pallet::storage]
1765 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1766
1767 #[pallet::storage]
1775 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1776
1777 #[pallet::storage]
1780 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1781
1782 #[pallet::storage]
1785 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1786
1787 #[pallet::storage]
1790 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1791
1792 #[pallet::storage]
1796 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1797
1798 #[pallet::storage]
1802 pub type PoolMembers<T: Config> =
1803 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1804
1805 #[pallet::storage]
1808 pub type BondedPools<T: Config> =
1809 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1810
1811 #[pallet::storage]
1814 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1815
1816 #[pallet::storage]
1819 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1820
1821 #[pallet::storage]
1823 pub type Metadata<T: Config> =
1824 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1825
1826 #[pallet::storage]
1828 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1829
1830 #[pallet::storage]
1835 pub type ReversePoolIdLookup<T: Config> =
1836 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1837
1838 #[pallet::storage]
1840 pub type ClaimPermissions<T: Config> =
1841 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1842
1843 #[pallet::genesis_config]
1844 pub struct GenesisConfig<T: Config> {
1845 pub min_join_bond: BalanceOf<T>,
1846 pub min_create_bond: BalanceOf<T>,
1847 pub max_pools: Option<u32>,
1848 pub max_members_per_pool: Option<u32>,
1849 pub max_members: Option<u32>,
1850 pub global_max_commission: Option<Perbill>,
1851 }
1852
1853 impl<T: Config> Default for GenesisConfig<T> {
1854 fn default() -> Self {
1855 Self {
1856 min_join_bond: Zero::zero(),
1857 min_create_bond: Zero::zero(),
1858 max_pools: Some(16),
1859 max_members_per_pool: Some(32),
1860 max_members: Some(16 * 32),
1861 global_max_commission: None,
1862 }
1863 }
1864 }
1865
1866 #[pallet::genesis_build]
1867 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1868 fn build(&self) {
1869 MinJoinBond::<T>::put(self.min_join_bond);
1870 MinCreateBond::<T>::put(self.min_create_bond);
1871
1872 if let Some(max_pools) = self.max_pools {
1873 MaxPools::<T>::put(max_pools);
1874 }
1875 if let Some(max_members_per_pool) = self.max_members_per_pool {
1876 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1877 }
1878 if let Some(max_members) = self.max_members {
1879 MaxPoolMembers::<T>::put(max_members);
1880 }
1881 if let Some(global_max_commission) = self.global_max_commission {
1882 GlobalMaxCommission::<T>::put(global_max_commission);
1883 }
1884 }
1885 }
1886
1887 #[pallet::event]
1889 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1890 pub enum Event<T: Config> {
1891 Created { depositor: T::AccountId, pool_id: PoolId },
1893 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1895 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1897 Unbonded {
1909 member: T::AccountId,
1910 pool_id: PoolId,
1911 balance: BalanceOf<T>,
1912 points: BalanceOf<T>,
1913 era: EraIndex,
1914 },
1915 Withdrawn {
1922 member: T::AccountId,
1923 pool_id: PoolId,
1924 balance: BalanceOf<T>,
1925 points: BalanceOf<T>,
1926 },
1927 Destroyed { pool_id: PoolId },
1929 StateChanged { pool_id: PoolId, new_state: PoolState },
1931 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1937 RolesUpdated {
1940 root: Option<T::AccountId>,
1941 bouncer: Option<T::AccountId>,
1942 nominator: Option<T::AccountId>,
1943 },
1944 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1946 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1948 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1950 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1952 PoolCommissionChangeRateUpdated {
1954 pool_id: PoolId,
1955 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1956 },
1957 PoolCommissionClaimPermissionUpdated {
1959 pool_id: PoolId,
1960 permission: Option<CommissionClaimPermission<T::AccountId>>,
1961 },
1962 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1964 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1966 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1968 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1970 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1972 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1975 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1977 GlobalParamsUpdated {
1979 min_join_bond: BalanceOf<T>,
1980 min_create_bond: BalanceOf<T>,
1981 max_pools: Option<u32>,
1982 max_members: Option<u32>,
1983 max_members_per_pool: Option<u32>,
1984 global_max_commission: Option<Perbill>,
1985 },
1986 }
1987
1988 #[pallet::error]
1989 #[cfg_attr(test, derive(PartialEq))]
1990 pub enum Error<T> {
1991 PoolNotFound,
1993 PoolMemberNotFound,
1995 RewardPoolNotFound,
1997 SubPoolsNotFound,
1999 AccountBelongsToOtherPool,
2002 FullyUnbonding,
2005 MaxUnbondingLimit,
2007 CannotWithdrawAny,
2009 MinimumBondNotMet,
2015 OverflowRisk,
2017 NotDestroying,
2020 NotNominator,
2022 NotKickerOrDestroying,
2024 NotOpen,
2026 MaxPools,
2028 MaxPoolMembers,
2030 CanNotChangeState,
2032 DoesNotHavePermission,
2034 MetadataExceedsMaxLen,
2036 Defensive(DefensiveError),
2039 PartialUnbondNotAllowedPermissionlessly,
2041 MaxCommissionRestricted,
2043 CommissionExceedsMaximum,
2045 CommissionExceedsGlobalMaximum,
2047 CommissionChangeThrottled,
2049 CommissionChangeRateNotAllowed,
2051 NoPendingCommission,
2053 NoCommissionCurrentSet,
2055 PoolIdInUse,
2057 InvalidPoolId,
2059 BondExtraRestricted,
2061 NothingToAdjust,
2063 NothingToSlash,
2065 SlashTooLow,
2067 AlreadyMigrated,
2069 NotMigrated,
2071 NotSupported,
2073 Restricted,
2076 }
2077
2078 #[derive(
2079 Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, RuntimeDebug,
2080 )]
2081 pub enum DefensiveError {
2082 NotEnoughSpaceInUnbondPool,
2084 PoolNotFound,
2086 RewardPoolNotFound,
2088 SubPoolsNotFound,
2090 BondedStashKilledPrematurely,
2093 DelegationUnsupported,
2095 SlashNotApplied,
2097 }
2098
2099 impl<T> From<DefensiveError> for Error<T> {
2100 fn from(e: DefensiveError) -> Error<T> {
2101 Error::<T>::Defensive(e)
2102 }
2103 }
2104
2105 #[pallet::composite_enum]
2107 pub enum FreezeReason {
2108 #[codec(index = 0)]
2110 PoolMinBalance,
2111 }
2112
2113 #[pallet::call]
2114 impl<T: Config> Pallet<T> {
2115 #[pallet::call_index(0)]
2132 #[pallet::weight(T::WeightInfo::join())]
2133 pub fn join(
2134 origin: OriginFor<T>,
2135 #[pallet::compact] amount: BalanceOf<T>,
2136 pool_id: PoolId,
2137 ) -> DispatchResult {
2138 let who = ensure_signed(origin)?;
2139 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2141
2142 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2144
2145 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2146 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2148
2149 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2150 bonded_pool.ok_to_join()?;
2151
2152 let mut reward_pool = RewardPools::<T>::get(pool_id)
2153 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2154 reward_pool.update_records(
2156 pool_id,
2157 bonded_pool.points,
2158 bonded_pool.commission.current(),
2159 )?;
2160
2161 bonded_pool.try_inc_members()?;
2162 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2163
2164 PoolMembers::insert(
2165 who.clone(),
2166 PoolMember::<T> {
2167 pool_id,
2168 points: points_issued,
2169 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2172 unbonding_eras: Default::default(),
2173 },
2174 );
2175
2176 Self::deposit_event(Event::<T>::Bonded {
2177 member: who,
2178 pool_id,
2179 bonded: amount,
2180 joined: true,
2181 });
2182
2183 bonded_pool.put();
2184 RewardPools::<T>::insert(pool_id, reward_pool);
2185
2186 Ok(())
2187 }
2188
2189 #[pallet::call_index(1)]
2200 #[pallet::weight(
2201 T::WeightInfo::bond_extra_transfer()
2202 .max(T::WeightInfo::bond_extra_other())
2203 )]
2204 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2205 let who = ensure_signed(origin)?;
2206
2207 ensure!(
2209 !Self::api_member_needs_delegate_migration(who.clone()),
2210 Error::<T>::NotMigrated
2211 );
2212
2213 Self::do_bond_extra(who.clone(), who, extra)
2214 }
2215
2216 #[pallet::call_index(2)]
2225 #[pallet::weight(T::WeightInfo::claim_payout())]
2226 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2227 let signer = ensure_signed(origin)?;
2228 ensure!(
2230 !Self::api_member_needs_delegate_migration(signer.clone()),
2231 Error::<T>::NotMigrated
2232 );
2233
2234 Self::do_claim_payout(signer.clone(), signer)
2235 }
2236
2237 #[pallet::call_index(3)]
2269 #[pallet::weight(T::WeightInfo::unbond())]
2270 pub fn unbond(
2271 origin: OriginFor<T>,
2272 member_account: AccountIdLookupOf<T>,
2273 #[pallet::compact] unbonding_points: BalanceOf<T>,
2274 ) -> DispatchResult {
2275 let who = ensure_signed(origin)?;
2276 let member_account = T::Lookup::lookup(member_account)?;
2277 ensure!(
2279 !Self::api_member_needs_delegate_migration(member_account.clone()),
2280 Error::<T>::NotMigrated
2281 );
2282
2283 let (mut member, mut bonded_pool, mut reward_pool) =
2284 Self::get_member_with_pools(&member_account)?;
2285
2286 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2287
2288 reward_pool.update_records(
2292 bonded_pool.id,
2293 bonded_pool.points,
2294 bonded_pool.commission.current(),
2295 )?;
2296 Self::do_reward_payout(
2297 &member_account,
2298 &mut member,
2299 &mut bonded_pool,
2300 &mut reward_pool,
2301 )?;
2302
2303 let current_era = T::StakeAdapter::current_era();
2304 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
2305
2306 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2308 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2309
2310 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2312 .unwrap_or_default()
2313 .maybe_merge_pools(current_era);
2314
2315 if !sub_pools.with_era.contains_key(&unbond_era) {
2318 sub_pools
2319 .with_era
2320 .try_insert(unbond_era, UnbondPool::default())
2321 .defensive_map_err::<Error<T>, _>(|_| {
2324 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2325 })?;
2326 }
2327
2328 let points_unbonded = sub_pools
2329 .with_era
2330 .get_mut(&unbond_era)
2331 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2333 .issue(unbonding_balance);
2334
2335 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2337
2338 Self::deposit_event(Event::<T>::Unbonded {
2339 member: member_account.clone(),
2340 pool_id: member.pool_id,
2341 points: points_unbonded,
2342 balance: unbonding_balance,
2343 era: unbond_era,
2344 });
2345
2346 SubPoolsStorage::insert(member.pool_id, sub_pools);
2348 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2349 Ok(())
2350 }
2351
2352 #[pallet::call_index(4)]
2359 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2360 pub fn pool_withdraw_unbonded(
2361 origin: OriginFor<T>,
2362 pool_id: PoolId,
2363 num_slashing_spans: u32,
2364 ) -> DispatchResult {
2365 ensure_signed(origin)?;
2366 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2368
2369 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2370
2371 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2374 T::StakeAdapter::withdraw_unbonded(
2375 Pool::from(pool.bonded_account()),
2376 num_slashing_spans,
2377 )?;
2378
2379 Ok(())
2380 }
2381
2382 #[pallet::call_index(5)]
2405 #[pallet::weight(
2406 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2407 )]
2408 pub fn withdraw_unbonded(
2409 origin: OriginFor<T>,
2410 member_account: AccountIdLookupOf<T>,
2411 num_slashing_spans: u32,
2412 ) -> DispatchResultWithPostInfo {
2413 let caller = ensure_signed(origin)?;
2414 let member_account = T::Lookup::lookup(member_account)?;
2415 ensure!(
2417 !Self::api_member_needs_delegate_migration(member_account.clone()),
2418 Error::<T>::NotMigrated
2419 );
2420
2421 let mut member =
2422 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2423 let current_era = T::StakeAdapter::current_era();
2424
2425 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2426 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2427 let mut sub_pools =
2428 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2429
2430 let slash_weight =
2431 match Self::do_apply_slash(&member_account, None, false) {
2433 Ok(_) => T::WeightInfo::apply_slash(),
2434 Err(e) => {
2435 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2436 if Err(e) == no_pending_slash {
2438 T::WeightInfo::apply_slash_fail()
2439 } else {
2440 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2442 }
2443 }
2444
2445 };
2446
2447 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2448 let pool_account = bonded_pool.bonded_account();
2449
2450 let withdrawn_points = member.withdraw_unlocked(current_era);
2452 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2453
2454 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2457 Pool::from(bonded_pool.bonded_account()),
2458 num_slashing_spans,
2459 )?;
2460
2461 ensure!(
2464 !stash_killed || caller == bonded_pool.roles.depositor,
2465 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2466 );
2467
2468 if stash_killed {
2469 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2471 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2472 }
2473
2474 }
2481
2482 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2483 let balance_to_unbond = withdrawn_points
2484 .iter()
2485 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2486 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2487 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2488 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2489 if era_pool.points.is_zero() {
2490 sub_pools.with_era.remove(era);
2491 }
2492 accumulator.saturating_add(balance_to_unbond)
2493 } else {
2494 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2497 }
2498 })
2499 .min(T::StakeAdapter::transferable_balance(
2507 Pool::from(bonded_pool.bonded_account()),
2508 Member::from(member_account.clone()),
2509 ));
2510
2511 T::StakeAdapter::member_withdraw(
2514 Member::from(member_account.clone()),
2515 Pool::from(bonded_pool.bonded_account()),
2516 balance_to_unbond,
2517 num_slashing_spans,
2518 )?;
2519
2520 Self::deposit_event(Event::<T>::Withdrawn {
2521 member: member_account.clone(),
2522 pool_id: member.pool_id,
2523 points: sum_unlocked_points,
2524 balance: balance_to_unbond,
2525 });
2526
2527 let post_info_weight = if member.total_points().is_zero() {
2528 ClaimPermissions::<T>::remove(&member_account);
2530
2531 PoolMembers::<T>::remove(&member_account);
2533
2534 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2536 Member::from(member_account.clone()),
2537 ) {
2538 Some(dangling_delegation) => {
2539 T::StakeAdapter::member_withdraw(
2540 Member::from(member_account.clone()),
2541 Pool::from(bonded_pool.bonded_account()),
2542 dangling_delegation,
2543 num_slashing_spans,
2544 )?;
2545 dangling_delegation
2546 },
2547 None => Zero::zero(),
2548 };
2549
2550 Self::deposit_event(Event::<T>::MemberRemoved {
2551 pool_id: member.pool_id,
2552 member: member_account.clone(),
2553 released_balance: dangling_withdrawal,
2554 });
2555
2556 if member_account == bonded_pool.roles.depositor {
2557 Pallet::<T>::dissolve_pool(bonded_pool);
2558 Weight::default()
2559 } else {
2560 bonded_pool.dec_members().put();
2561 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2562 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2563 }
2564 } else {
2565 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2567 PoolMembers::<T>::insert(&member_account, member);
2568 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2569 };
2570
2571 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2572 }
2573
2574 #[pallet::call_index(6)]
2592 #[pallet::weight(T::WeightInfo::create())]
2593 pub fn create(
2594 origin: OriginFor<T>,
2595 #[pallet::compact] amount: BalanceOf<T>,
2596 root: AccountIdLookupOf<T>,
2597 nominator: AccountIdLookupOf<T>,
2598 bouncer: AccountIdLookupOf<T>,
2599 ) -> DispatchResult {
2600 let depositor = ensure_signed(origin)?;
2601
2602 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2603 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2604 Ok(*id)
2605 })?;
2606
2607 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2608 }
2609
2610 #[pallet::call_index(7)]
2617 #[pallet::weight(T::WeightInfo::create())]
2618 pub fn create_with_pool_id(
2619 origin: OriginFor<T>,
2620 #[pallet::compact] amount: BalanceOf<T>,
2621 root: AccountIdLookupOf<T>,
2622 nominator: AccountIdLookupOf<T>,
2623 bouncer: AccountIdLookupOf<T>,
2624 pool_id: PoolId,
2625 ) -> DispatchResult {
2626 let depositor = ensure_signed(origin)?;
2627
2628 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2629 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2630
2631 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2632 }
2633
2634 #[pallet::call_index(8)]
2647 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2648 pub fn nominate(
2649 origin: OriginFor<T>,
2650 pool_id: PoolId,
2651 validators: Vec<T::AccountId>,
2652 ) -> DispatchResult {
2653 let who = ensure_signed(origin)?;
2654 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2655 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2657 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2658
2659 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2660 .ok_or(Error::<T>::PoolMemberNotFound)?
2661 .active_points();
2662
2663 ensure!(
2664 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2665 Error::<T>::MinimumBondNotMet
2666 );
2667
2668 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2669 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2670 )
2671 }
2672
2673 #[pallet::call_index(9)]
2684 #[pallet::weight(T::WeightInfo::set_state())]
2685 pub fn set_state(
2686 origin: OriginFor<T>,
2687 pool_id: PoolId,
2688 state: PoolState,
2689 ) -> DispatchResult {
2690 let who = ensure_signed(origin)?;
2691 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2692 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2693 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2695
2696 if bonded_pool.can_toggle_state(&who) {
2697 bonded_pool.set_state(state);
2698 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2699 bonded_pool.set_state(PoolState::Destroying);
2701 } else {
2702 Err(Error::<T>::CanNotChangeState)?;
2703 }
2704
2705 bonded_pool.put();
2706
2707 Ok(())
2708 }
2709
2710 #[pallet::call_index(10)]
2715 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2716 pub fn set_metadata(
2717 origin: OriginFor<T>,
2718 pool_id: PoolId,
2719 metadata: Vec<u8>,
2720 ) -> DispatchResult {
2721 let who = ensure_signed(origin)?;
2722 let metadata: BoundedVec<_, _> =
2723 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2724 ensure!(
2725 BondedPool::<T>::get(pool_id)
2726 .ok_or(Error::<T>::PoolNotFound)?
2727 .can_set_metadata(&who),
2728 Error::<T>::DoesNotHavePermission
2729 );
2730 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2732
2733 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2734
2735 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2736
2737 Ok(())
2738 }
2739
2740 #[pallet::call_index(11)]
2752 #[pallet::weight(T::WeightInfo::set_configs())]
2753 pub fn set_configs(
2754 origin: OriginFor<T>,
2755 min_join_bond: ConfigOp<BalanceOf<T>>,
2756 min_create_bond: ConfigOp<BalanceOf<T>>,
2757 max_pools: ConfigOp<u32>,
2758 max_members: ConfigOp<u32>,
2759 max_members_per_pool: ConfigOp<u32>,
2760 global_max_commission: ConfigOp<Perbill>,
2761 ) -> DispatchResult {
2762 T::AdminOrigin::ensure_origin(origin)?;
2763
2764 macro_rules! config_op_exp {
2765 ($storage:ty, $op:ident) => {
2766 match $op {
2767 ConfigOp::Noop => (),
2768 ConfigOp::Set(v) => <$storage>::put(v),
2769 ConfigOp::Remove => <$storage>::kill(),
2770 }
2771 };
2772 }
2773
2774 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2775 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2776 config_op_exp!(MaxPools::<T>, max_pools);
2777 config_op_exp!(MaxPoolMembers::<T>, max_members);
2778 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2779 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2780
2781 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2782 min_join_bond: MinJoinBond::<T>::get(),
2783 min_create_bond: MinCreateBond::<T>::get(),
2784 max_pools: MaxPools::<T>::get(),
2785 max_members: MaxPoolMembers::<T>::get(),
2786 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2787 global_max_commission: GlobalMaxCommission::<T>::get(),
2788 });
2789
2790 Ok(())
2791 }
2792
2793 #[pallet::call_index(12)]
2801 #[pallet::weight(T::WeightInfo::update_roles())]
2802 pub fn update_roles(
2803 origin: OriginFor<T>,
2804 pool_id: PoolId,
2805 new_root: ConfigOp<T::AccountId>,
2806 new_nominator: ConfigOp<T::AccountId>,
2807 new_bouncer: ConfigOp<T::AccountId>,
2808 ) -> DispatchResult {
2809 let mut bonded_pool = match ensure_root(origin.clone()) {
2810 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2811 Err(sp_runtime::traits::BadOrigin) => {
2812 let who = ensure_signed(origin)?;
2813 let bonded_pool =
2814 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2815 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2816 bonded_pool
2817 },
2818 };
2819
2820 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2822
2823 match new_root {
2824 ConfigOp::Noop => (),
2825 ConfigOp::Remove => bonded_pool.roles.root = None,
2826 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2827 };
2828 match new_nominator {
2829 ConfigOp::Noop => (),
2830 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2831 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2832 };
2833 match new_bouncer {
2834 ConfigOp::Noop => (),
2835 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2836 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2837 };
2838
2839 Self::deposit_event(Event::<T>::RolesUpdated {
2840 root: bonded_pool.roles.root.clone(),
2841 nominator: bonded_pool.roles.nominator.clone(),
2842 bouncer: bonded_pool.roles.bouncer.clone(),
2843 });
2844
2845 bonded_pool.put();
2846 Ok(())
2847 }
2848
2849 #[pallet::call_index(13)]
2867 #[pallet::weight(T::WeightInfo::chill())]
2868 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2869 let who = ensure_signed(origin)?;
2870 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2871 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2873
2874 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2875 .ok_or(Error::<T>::PoolMemberNotFound)?
2876 .active_points();
2877
2878 if bonded_pool.points_to_balance(depositor_points) >=
2879 T::StakeAdapter::minimum_nominator_bond()
2880 {
2881 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2882 }
2883
2884 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2885 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2886 })
2887 }
2888
2889 #[pallet::call_index(14)]
2899 #[pallet::weight(
2900 T::WeightInfo::bond_extra_transfer()
2901 .max(T::WeightInfo::bond_extra_other())
2902 )]
2903 pub fn bond_extra_other(
2904 origin: OriginFor<T>,
2905 member: AccountIdLookupOf<T>,
2906 extra: BondExtra<BalanceOf<T>>,
2907 ) -> DispatchResult {
2908 let who = ensure_signed(origin)?;
2909 let member_account = T::Lookup::lookup(member)?;
2910 ensure!(
2912 !Self::api_member_needs_delegate_migration(member_account.clone()),
2913 Error::<T>::NotMigrated
2914 );
2915
2916 Self::do_bond_extra(who, member_account, extra)
2917 }
2918
2919 #[pallet::call_index(15)]
2927 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2928 pub fn set_claim_permission(
2929 origin: OriginFor<T>,
2930 permission: ClaimPermission,
2931 ) -> DispatchResult {
2932 let who = ensure_signed(origin)?;
2933 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2934
2935 ensure!(
2937 !Self::api_member_needs_delegate_migration(who.clone()),
2938 Error::<T>::NotMigrated
2939 );
2940
2941 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2942 *source = permission;
2943 });
2944
2945 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2946 member: who,
2947 permission,
2948 });
2949
2950 Ok(())
2951 }
2952
2953 #[pallet::call_index(16)]
2958 #[pallet::weight(T::WeightInfo::claim_payout())]
2959 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2960 let signer = ensure_signed(origin)?;
2961 ensure!(
2963 !Self::api_member_needs_delegate_migration(other.clone()),
2964 Error::<T>::NotMigrated
2965 );
2966
2967 Self::do_claim_payout(signer, other)
2968 }
2969
2970 #[pallet::call_index(17)]
2977 #[pallet::weight(T::WeightInfo::set_commission())]
2978 pub fn set_commission(
2979 origin: OriginFor<T>,
2980 pool_id: PoolId,
2981 new_commission: Option<(Perbill, T::AccountId)>,
2982 ) -> DispatchResult {
2983 let who = ensure_signed(origin)?;
2984 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2985 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2987
2988 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2989
2990 let mut reward_pool = RewardPools::<T>::get(pool_id)
2991 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2992 reward_pool.update_records(
2995 pool_id,
2996 bonded_pool.points,
2997 bonded_pool.commission.current(),
2998 )?;
2999 RewardPools::insert(pool_id, reward_pool);
3000
3001 bonded_pool.commission.try_update_current(&new_commission)?;
3002 bonded_pool.put();
3003 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
3004 pool_id,
3005 current: new_commission,
3006 });
3007 Ok(())
3008 }
3009
3010 #[pallet::call_index(18)]
3016 #[pallet::weight(T::WeightInfo::set_commission_max())]
3017 pub fn set_commission_max(
3018 origin: OriginFor<T>,
3019 pool_id: PoolId,
3020 max_commission: Perbill,
3021 ) -> DispatchResult {
3022 let who = ensure_signed(origin)?;
3023 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3024 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3026
3027 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3028
3029 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3030 bonded_pool.put();
3031
3032 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3033 Ok(())
3034 }
3035
3036 #[pallet::call_index(19)]
3041 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3042 pub fn set_commission_change_rate(
3043 origin: OriginFor<T>,
3044 pool_id: PoolId,
3045 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3046 ) -> DispatchResult {
3047 let who = ensure_signed(origin)?;
3048 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3049 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3051 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3052
3053 bonded_pool.commission.try_update_change_rate(change_rate)?;
3054 bonded_pool.put();
3055
3056 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3057 pool_id,
3058 change_rate,
3059 });
3060 Ok(())
3061 }
3062
3063 #[pallet::call_index(20)]
3080 #[pallet::weight(T::WeightInfo::claim_commission())]
3081 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3082 let who = ensure_signed(origin)?;
3083 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3085
3086 Self::do_claim_commission(who, pool_id)
3087 }
3088
3089 #[pallet::call_index(21)]
3097 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3098 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3099 let who = ensure_signed(origin)?;
3100 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3102
3103 Self::do_adjust_pool_deposit(who, pool_id)
3104 }
3105
3106 #[pallet::call_index(22)]
3111 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3112 pub fn set_commission_claim_permission(
3113 origin: OriginFor<T>,
3114 pool_id: PoolId,
3115 permission: Option<CommissionClaimPermission<T::AccountId>>,
3116 ) -> DispatchResult {
3117 let who = ensure_signed(origin)?;
3118 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3119 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3121 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3122
3123 bonded_pool.commission.claim_permission = permission.clone();
3124 bonded_pool.put();
3125
3126 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3127 pool_id,
3128 permission,
3129 });
3130
3131 Ok(())
3132 }
3133
3134 #[pallet::call_index(23)]
3144 #[pallet::weight(T::WeightInfo::apply_slash())]
3145 pub fn apply_slash(
3146 origin: OriginFor<T>,
3147 member_account: AccountIdLookupOf<T>,
3148 ) -> DispatchResultWithPostInfo {
3149 ensure!(
3150 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3151 Error::<T>::NotSupported
3152 );
3153
3154 let who = ensure_signed(origin)?;
3155 let member_account = T::Lookup::lookup(member_account)?;
3156 Self::do_apply_slash(&member_account, Some(who), true)?;
3157
3158 Ok(Pays::No.into())
3160 }
3161
3162 #[pallet::call_index(24)]
3172 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3173 pub fn migrate_delegation(
3174 origin: OriginFor<T>,
3175 member_account: AccountIdLookupOf<T>,
3176 ) -> DispatchResultWithPostInfo {
3177 let _caller = ensure_signed(origin)?;
3178
3179 ensure!(
3181 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3182 Error::<T>::NotSupported
3183 );
3184
3185 let member_account = T::Lookup::lookup(member_account)?;
3187 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3188
3189 let member =
3190 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3191
3192 ensure!(
3194 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3195 member.pool_id
3196 ))) == adapter::StakeStrategyType::Delegate,
3197 Error::<T>::NotMigrated
3198 );
3199
3200 let pool_contribution = member.total_balance();
3201 ensure!(
3204 pool_contribution >= T::Currency::minimum_balance(),
3205 Error::<T>::MinimumBondNotMet
3206 );
3207
3208 let delegation =
3209 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3210 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3212
3213 T::StakeAdapter::migrate_delegation(
3214 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3215 Member::from(member_account),
3216 pool_contribution,
3217 )?;
3218
3219 Ok(Pays::No.into())
3221 }
3222
3223 #[pallet::call_index(25)]
3233 #[pallet::weight(T::WeightInfo::pool_migrate())]
3234 pub fn migrate_pool_to_delegate_stake(
3235 origin: OriginFor<T>,
3236 pool_id: PoolId,
3237 ) -> DispatchResultWithPostInfo {
3238 ensure!(
3240 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3241 Error::<T>::NotSupported
3242 );
3243
3244 let _caller = ensure_signed(origin)?;
3245 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3247 ensure!(
3248 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3249 adapter::StakeStrategyType::Transfer,
3250 Error::<T>::AlreadyMigrated
3251 );
3252
3253 Self::migrate_to_delegate_stake(pool_id)?;
3254 Ok(Pays::No.into())
3255 }
3256 }
3257
3258 #[pallet::hooks]
3259 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3260 #[cfg(feature = "try-runtime")]
3261 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3262 Self::do_try_state(u8::MAX)
3263 }
3264
3265 fn integrity_test() {
3266 assert!(
3267 T::MaxPointsToBalance::get() > 0,
3268 "Minimum points to balance ratio must be greater than 0"
3269 );
3270 assert!(
3271 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3272 "There must be more unbonding pools then the bonding duration /
3273 so a slash can be applied to relevant unbonding pools. (We assume /
3274 the bonding duration > slash deffer duration.",
3275 );
3276 }
3277 }
3278}
3279
3280impl<T: Config> Pallet<T> {
3281 pub fn depositor_min_bond() -> BalanceOf<T> {
3289 T::StakeAdapter::minimum_nominator_bond()
3290 .max(MinCreateBond::<T>::get())
3291 .max(MinJoinBond::<T>::get())
3292 .max(T::Currency::minimum_balance())
3293 }
3294 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3299 let reward_account = bonded_pool.reward_account();
3300 let bonded_account = bonded_pool.bonded_account();
3301
3302 ReversePoolIdLookup::<T>::remove(&bonded_account);
3303 RewardPools::<T>::remove(bonded_pool.id);
3304 SubPoolsStorage::<T>::remove(bonded_pool.id);
3305
3306 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3308
3309 defensive_assert!(
3317 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3318 "reward account of dissolving pool should have no consumers"
3319 );
3320 defensive_assert!(
3321 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3322 "bonded account of dissolving pool should have no consumers"
3323 );
3324 defensive_assert!(
3325 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3326 "dissolving pool should not have any stake in the staking pallet"
3327 );
3328
3329 let reward_pool_remaining = T::Currency::reducible_balance(
3332 &reward_account,
3333 Preservation::Expendable,
3334 Fortitude::Polite,
3335 );
3336 let _ = T::Currency::transfer(
3337 &reward_account,
3338 &bonded_pool.roles.depositor,
3339 reward_pool_remaining,
3340 Preservation::Expendable,
3341 );
3342
3343 defensive_assert!(
3344 T::Currency::total_balance(&reward_account) == Zero::zero(),
3345 "could not transfer all amount to depositor while dissolving pool"
3346 );
3347 T::Currency::set_balance(&reward_account, Zero::zero());
3349
3350 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3352
3353 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3354 Metadata::<T>::remove(bonded_pool.id);
3356
3357 bonded_pool.remove();
3358 }
3359
3360 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3362 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3363 }
3364
3365 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3366 T::StakeAdapter::migrate_nominator_to_agent(
3367 Pool::from(Self::generate_bonded_account(id)),
3368 &Self::generate_reward_account(id),
3369 )
3370 }
3371
3372 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3374 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3377 }
3378
3379 fn get_member_with_pools(
3381 who: &T::AccountId,
3382 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3383 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3384 let bonded_pool =
3385 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3386 let reward_pool =
3387 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3388 Ok((member, bonded_pool, reward_pool))
3389 }
3390
3391 fn put_member_with_pools(
3394 member_account: &T::AccountId,
3395 member: PoolMember<T>,
3396 bonded_pool: BondedPool<T>,
3397 reward_pool: RewardPool<T>,
3398 ) {
3399 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3402 debug_assert_eq!(member.pool_id, bonded_pool.id);
3403
3404 bonded_pool.put();
3405 RewardPools::insert(member.pool_id, reward_pool);
3406 PoolMembers::<T>::insert(member_account, member);
3407 }
3408
3409 fn balance_to_point(
3412 current_balance: BalanceOf<T>,
3413 current_points: BalanceOf<T>,
3414 new_funds: BalanceOf<T>,
3415 ) -> BalanceOf<T> {
3416 let u256 = T::BalanceToU256::convert;
3417 let balance = T::U256ToBalance::convert;
3418 match (current_balance.is_zero(), current_points.is_zero()) {
3419 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3420 (true, false) => {
3421 new_funds.saturating_mul(current_points)
3424 },
3425 (false, false) => {
3426 balance(
3428 u256(current_points)
3429 .saturating_mul(u256(new_funds))
3430 .div(u256(current_balance)),
3432 )
3433 },
3434 }
3435 }
3436
3437 fn point_to_balance(
3440 current_balance: BalanceOf<T>,
3441 current_points: BalanceOf<T>,
3442 points: BalanceOf<T>,
3443 ) -> BalanceOf<T> {
3444 let u256 = T::BalanceToU256::convert;
3445 let balance = T::U256ToBalance::convert;
3446 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3447 return Zero::zero()
3449 }
3450
3451 balance(
3453 u256(current_balance)
3454 .saturating_mul(u256(points))
3455 .div(u256(current_points)),
3457 )
3458 }
3459
3460 fn do_reward_payout(
3464 member_account: &T::AccountId,
3465 member: &mut PoolMember<T>,
3466 bonded_pool: &mut BondedPool<T>,
3467 reward_pool: &mut RewardPool<T>,
3468 ) -> Result<BalanceOf<T>, DispatchError> {
3469 debug_assert_eq!(member.pool_id, bonded_pool.id);
3470 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3471
3472 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3474
3475 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3476 bonded_pool.id,
3477 bonded_pool.points,
3478 bonded_pool.commission.current(),
3479 )?;
3480
3481 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3484 if pending_rewards.is_zero() {
3485 return Ok(pending_rewards)
3486 }
3487
3488 member.last_recorded_reward_counter = current_reward_counter;
3490 reward_pool.register_claimed_reward(pending_rewards);
3491
3492 T::Currency::transfer(
3493 &bonded_pool.reward_account(),
3494 member_account,
3495 pending_rewards,
3496 Preservation::Preserve,
3499 )?;
3500
3501 Self::deposit_event(Event::<T>::PaidOut {
3502 member: member_account.clone(),
3503 pool_id: member.pool_id,
3504 payout: pending_rewards,
3505 });
3506 Ok(pending_rewards)
3507 }
3508
3509 fn do_create(
3510 who: T::AccountId,
3511 amount: BalanceOf<T>,
3512 root: AccountIdLookupOf<T>,
3513 nominator: AccountIdLookupOf<T>,
3514 bouncer: AccountIdLookupOf<T>,
3515 pool_id: PoolId,
3516 ) -> DispatchResult {
3517 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3519
3520 let root = T::Lookup::lookup(root)?;
3521 let nominator = T::Lookup::lookup(nominator)?;
3522 let bouncer = T::Lookup::lookup(bouncer)?;
3523
3524 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3525 ensure!(
3526 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3527 Error::<T>::MaxPools
3528 );
3529 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3530 let mut bonded_pool = BondedPool::<T>::new(
3531 pool_id,
3532 PoolRoles {
3533 root: Some(root),
3534 nominator: Some(nominator),
3535 bouncer: Some(bouncer),
3536 depositor: who.clone(),
3537 },
3538 );
3539
3540 bonded_pool.try_inc_members()?;
3541 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3542
3543 T::Currency::transfer(
3545 &who,
3546 &bonded_pool.reward_account(),
3547 T::Currency::minimum_balance(),
3548 Preservation::Expendable,
3549 )?;
3550
3551 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3553
3554 PoolMembers::<T>::insert(
3555 who.clone(),
3556 PoolMember::<T> {
3557 pool_id,
3558 points,
3559 last_recorded_reward_counter: Zero::zero(),
3560 unbonding_eras: Default::default(),
3561 },
3562 );
3563 RewardPools::<T>::insert(
3564 pool_id,
3565 RewardPool::<T> {
3566 last_recorded_reward_counter: Zero::zero(),
3567 last_recorded_total_payouts: Zero::zero(),
3568 total_rewards_claimed: Zero::zero(),
3569 total_commission_pending: Zero::zero(),
3570 total_commission_claimed: Zero::zero(),
3571 },
3572 );
3573 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3574
3575 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3576
3577 Self::deposit_event(Event::<T>::Bonded {
3578 member: who,
3579 pool_id,
3580 bonded: amount,
3581 joined: true,
3582 });
3583 bonded_pool.put();
3584
3585 Ok(())
3586 }
3587
3588 fn do_bond_extra(
3589 signer: T::AccountId,
3590 member_account: T::AccountId,
3591 extra: BondExtra<BalanceOf<T>>,
3592 ) -> DispatchResult {
3593 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3595
3596 if signer != member_account {
3597 ensure!(
3598 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3599 Error::<T>::DoesNotHavePermission
3600 );
3601 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3602 }
3603
3604 let (mut member, mut bonded_pool, mut reward_pool) =
3605 Self::get_member_with_pools(&member_account)?;
3606
3607 reward_pool.update_records(
3610 bonded_pool.id,
3611 bonded_pool.points,
3612 bonded_pool.commission.current(),
3613 )?;
3614 let claimed = Self::do_reward_payout(
3615 &member_account,
3616 &mut member,
3617 &mut bonded_pool,
3618 &mut reward_pool,
3619 )?;
3620
3621 let (points_issued, bonded) = match extra {
3622 BondExtra::FreeBalance(amount) =>
3623 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
3624 BondExtra::Rewards =>
3625 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
3626 };
3627
3628 bonded_pool.ok_to_be_open()?;
3629 member.points =
3630 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3631
3632 Self::deposit_event(Event::<T>::Bonded {
3633 member: member_account.clone(),
3634 pool_id: member.pool_id,
3635 bonded,
3636 joined: false,
3637 });
3638 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3639
3640 Ok(())
3641 }
3642
3643 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3644 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3645 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3646
3647 let mut reward_pool = RewardPools::<T>::get(pool_id)
3648 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3649
3650 reward_pool.update_records(
3653 pool_id,
3654 bonded_pool.points,
3655 bonded_pool.commission.current(),
3656 )?;
3657
3658 let commission = reward_pool.total_commission_pending;
3659 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3660
3661 let payee = bonded_pool
3662 .commission
3663 .current
3664 .as_ref()
3665 .map(|(_, p)| p.clone())
3666 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3667
3668 T::Currency::transfer(
3670 &bonded_pool.reward_account(),
3671 &payee,
3672 commission,
3673 Preservation::Preserve,
3674 )?;
3675
3676 reward_pool.total_commission_claimed =
3678 reward_pool.total_commission_claimed.saturating_add(commission);
3679 reward_pool.total_commission_pending = Zero::zero();
3681 RewardPools::<T>::insert(pool_id, reward_pool);
3682
3683 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3684 Ok(())
3685 }
3686
3687 pub(crate) fn do_claim_payout(
3688 signer: T::AccountId,
3689 member_account: T::AccountId,
3690 ) -> DispatchResult {
3691 if signer != member_account {
3692 ensure!(
3693 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3694 Error::<T>::DoesNotHavePermission
3695 );
3696 }
3697 let (mut member, mut bonded_pool, mut reward_pool) =
3698 Self::get_member_with_pools(&member_account)?;
3699
3700 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3701
3702 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3703 Ok(())
3704 }
3705
3706 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3707 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3708
3709 let reward_acc = &bonded_pool.reward_account();
3710 let pre_frozen_balance =
3711 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3712 let min_balance = T::Currency::minimum_balance();
3713
3714 if pre_frozen_balance == min_balance {
3715 return Err(Error::<T>::NothingToAdjust.into())
3716 }
3717
3718 Self::freeze_pool_deposit(reward_acc)?;
3720
3721 if pre_frozen_balance > min_balance {
3722 let excess = pre_frozen_balance.saturating_sub(min_balance);
3724 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3725 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3726 pool_id: pool,
3727 amount: excess,
3728 });
3729 } else {
3730 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3732 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3733 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3734 pool_id: pool,
3735 amount: deficit,
3736 });
3737 }
3738
3739 Ok(())
3740 }
3741
3742 fn do_apply_slash(
3744 member_account: &T::AccountId,
3745 reporter: Option<T::AccountId>,
3746 enforce_min_slash: bool,
3747 ) -> DispatchResult {
3748 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3749
3750 let pending_slash =
3751 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3752
3753 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3755
3756 if enforce_min_slash {
3757 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3759 }
3760
3761 T::StakeAdapter::member_slash(
3762 Member::from(member_account.clone()),
3763 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3764 pending_slash,
3765 reporter,
3766 )
3767 }
3768
3769 fn member_pending_slash(
3773 member_account: Member<T::AccountId>,
3774 pool_member: PoolMember<T>,
3775 ) -> Result<BalanceOf<T>, DispatchError> {
3776 debug_assert!(
3778 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3779 pool_member
3780 );
3781
3782 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3783 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3786 return Ok(Zero::zero())
3787 }
3788
3789 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3791 .ok_or(Error::<T>::NotMigrated)?;
3793
3794 let expected_balance = pool_member.total_balance();
3796
3797 Ok(actual_balance.saturating_sub(expected_balance))
3799 }
3800
3801 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3803 T::Currency::set_freeze(
3804 &FreezeReason::PoolMinBalance.into(),
3805 reward_acc,
3806 T::Currency::minimum_balance(),
3807 )
3808 }
3809
3810 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3812 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3813 }
3814
3815 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3852 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3853 if level.is_zero() {
3854 return Ok(())
3855 }
3856 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3859 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3860 ensure!(
3861 bonded_pools == reward_pools,
3862 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3863 );
3864
3865 ensure!(
3866 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3867 "`SubPoolsStorage` must be a subset of the above superset."
3868 );
3869 ensure!(
3870 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3871 "`Metadata` keys must be a subset of the above superset."
3872 );
3873
3874 ensure!(
3875 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3876 Error::<T>::MaxPools
3877 );
3878
3879 for id in reward_pools {
3880 let account = Self::generate_reward_account(id);
3881 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3882 T::Currency::minimum_balance()
3883 {
3884 log!(
3885 warn,
3886 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3887 changed recently. Pool operators should be notified to top up the reward \
3888 account",
3889 id,
3890 T::Currency::reducible_balance(
3891 &account,
3892 Preservation::Expendable,
3893 Fortitude::Polite
3894 ),
3895 T::Currency::minimum_balance(),
3896 )
3897 }
3898 }
3899
3900 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3901 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3902 let mut all_members = 0u32;
3903 let mut total_balance_members = Default::default();
3904 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3905 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3906 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3907 *pools_members.entry(d.pool_id).or_default() += 1;
3908 all_members += 1;
3909
3910 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3911 if !bonded_pool.points.is_zero() {
3912 let commission = bonded_pool.commission.current();
3913 let (current_rc, _) = reward_pool
3914 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3915 .unwrap();
3916 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3917 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3918 } total_balance_members += d.total_balance();
3920
3921 Ok(())
3922 })?;
3923
3924 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3925 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3928 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3929
3930 if !pending_rewards_lt_leftover_bal {
3933 log!(
3934 warn,
3935 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
3936 id,
3937 pools_members_pending_rewards.get(&id),
3938 RewardPool::<T>::current_balance(id)
3939 );
3940 }
3941 Ok(())
3942 })?;
3943
3944 let mut expected_tvl: BalanceOf<T> = Default::default();
3945 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
3946 let bonded_pool = BondedPool { id, inner };
3947 ensure!(
3948 pools_members.get(&id).copied().unwrap_or_default() ==
3949 bonded_pool.member_counter,
3950 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
3951 );
3952 ensure!(
3953 MaxPoolMembersPerPool::<T>::get()
3954 .map_or(true, |max| bonded_pool.member_counter <= max),
3955 Error::<T>::MaxPoolMembers
3956 );
3957
3958 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
3959 let depositor_has_enough_stake = bonded_pool
3960 .is_destroying_and_only_depositor(depositor.active_points()) ||
3961 depositor.active_points() >= MinCreateBond::<T>::get();
3962 if !depositor_has_enough_stake {
3963 log!(
3964 warn,
3965 "pool {:?} has depositor {:?} with insufficient stake {:?}, minimum required is {:?}",
3966 id,
3967 bonded_pool.roles.depositor,
3968 depositor.active_points(),
3969 MinCreateBond::<T>::get()
3970 );
3971 }
3972
3973 ensure!(
3974 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
3975 "Each `BondedPool.points` must never be lower than the pool's balance"
3976 );
3977
3978 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
3979
3980 Ok(())
3981 })?;
3982
3983 ensure!(
3984 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
3985 Error::<T>::MaxPoolMembers
3986 );
3987
3988 ensure!(
3989 TotalValueLocked::<T>::get() == expected_tvl,
3990 "TVL deviates from the actual sum of funds of all Pools."
3991 );
3992
3993 ensure!(
3994 TotalValueLocked::<T>::get() <= total_balance_members,
3995 "TVL must be equal to or less than the total balance of all PoolMembers."
3996 );
3997
3998 if level <= 1 {
3999 return Ok(())
4000 }
4001
4002 for (pool_id, _pool) in BondedPools::<T>::iter() {
4003 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
4004 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
4005
4006 let sum_unbonding_balance = subs.sum_unbonding_balance();
4007 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
4008 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
4010 .unwrap_or(T::Currency::total_balance(&pool_account));
4013
4014 if total_balance < bonded_balance + sum_unbonding_balance {
4015 log!(
4016 warn,
4017 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4018 pool_id,
4019 _pool,
4020 total_balance,
4021 bonded_balance,
4022 sum_unbonding_balance
4023 )
4024 };
4025 }
4026
4027 let _needs_adjust = Self::check_ed_imbalance()?;
4030
4031 Ok(())
4032 }
4033
4034 #[cfg(any(
4038 feature = "try-runtime",
4039 feature = "runtime-benchmarks",
4040 feature = "fuzzing",
4041 test,
4042 debug_assertions
4043 ))]
4044 pub fn check_ed_imbalance() -> Result<u32, DispatchError> {
4045 let mut needs_adjust = 0;
4046 BondedPools::<T>::iter_keys().for_each(|id| {
4047 let reward_acc = Self::generate_reward_account(id);
4048 let frozen_balance =
4049 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4050
4051 let expected_frozen_balance = T::Currency::minimum_balance();
4052 if frozen_balance != expected_frozen_balance {
4053 needs_adjust += 1;
4054 log!(
4055 warn,
4056 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}. Use `adjust_pool_deposit` to fix it",
4057 id,
4058 expected_frozen_balance,
4059 frozen_balance,
4060 );
4061 }
4062 });
4063
4064 Ok(needs_adjust)
4065 }
4066 #[cfg(any(feature = "runtime-benchmarks", test))]
4071 pub fn fully_unbond(
4072 origin: frame_system::pallet_prelude::OriginFor<T>,
4073 member: T::AccountId,
4074 ) -> DispatchResult {
4075 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4076 let member_lookup = T::Lookup::unlookup(member);
4077 Self::unbond(origin, member_lookup, points)
4078 }
4079}
4080
4081impl<T: Config> Pallet<T> {
4082 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4086 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4087 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4088 .zip(BondedPools::<T>::get(pool_member.pool_id))
4089 {
4090 let commission = bonded_pool.commission.current();
4091 let (current_reward_counter, _) = reward_pool
4092 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4093 .ok()?;
4094 return pool_member.pending_rewards(current_reward_counter).ok()
4095 }
4096 }
4097
4098 None
4099 }
4100
4101 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4105 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4106 pool.points_to_balance(points)
4107 } else {
4108 Zero::zero()
4109 }
4110 }
4111
4112 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4116 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4117 let bonded_balance =
4118 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4119 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4120 } else {
4121 Zero::zero()
4122 }
4123 }
4124
4125 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4129 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4130 }
4131
4132 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4139 PoolMembers::<T>::get(who.clone())
4140 .map(|pool_member| {
4141 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4142 })
4143 .unwrap_or_default()
4144 }
4145
4146 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4151 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4153 return false
4154 }
4155
4156 if !BondedPools::<T>::contains_key(pool_id) {
4158 return false
4159 }
4160
4161 let pool_account = Self::generate_bonded_account(pool_id);
4162
4163 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4165 adapter::StakeStrategyType::Delegate
4166 }
4167
4168 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4174 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4176 return false
4177 }
4178
4179 PoolMembers::<T>::get(who.clone())
4180 .map(|pool_member| {
4181 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4182 return false
4184 }
4185
4186 let member_balance = pool_member.total_balance();
4187 let delegated_balance =
4188 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4189
4190 delegated_balance.is_none() && !member_balance.is_zero()
4193 })
4194 .unwrap_or_default()
4195 }
4196
4197 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4202 PoolMembers::<T>::get(who.clone())
4203 .map(|m| m.total_balance())
4204 .unwrap_or_default()
4205 }
4206
4207 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4209 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4210 .unwrap_or_default()
4211 }
4212
4213 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4215 let bonded_account = Self::generate_bonded_account(pool_id);
4216 let reward_account = Self::generate_reward_account(pool_id);
4217 (bonded_account, reward_account)
4218 }
4219}
4220
4221impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4222 fn on_slash(
4228 pool_account: &T::AccountId,
4229 slashed_bonded: BalanceOf<T>,
4232 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4233 total_slashed: BalanceOf<T>,
4234 ) {
4235 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4236 TotalValueLocked::<T>::mutate(|tvl| {
4239 tvl.defensive_saturating_reduce(total_slashed);
4240 });
4241
4242 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4243 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4245 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4246 pool.balance = *slashed_balance;
4247 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4248 era: *era,
4249 pool_id,
4250 balance: *slashed_balance,
4251 });
4252 }
4253 });
4254 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4255 } else if !slashed_unlocking.is_empty() {
4256 defensive!("Expected SubPools were not found");
4257 }
4258 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4259 }
4260
4261 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4264 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4265 TotalValueLocked::<T>::mutate(|tvl| {
4266 tvl.saturating_reduce(amount);
4267 });
4268 }
4269 }
4270}
4271
4272pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4274impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4275 fn contains(t: &T::AccountId) -> bool {
4276 PoolMembers::<T>::contains_key(t)
4277 }
4278}