1#![cfg_attr(not(feature = "std"), no_std)]
354
355extern crate alloc;
356
357use adapter::{Member, Pool, StakeStrategy};
358use alloc::{collections::btree_map::BTreeMap, vec::Vec};
359use codec::{Codec, DecodeWithMemTracking};
360use core::{fmt::Debug, ops::Div};
361use frame_support::{
362 defensive, defensive_assert, ensure,
363 pallet_prelude::{MaxEncodedLen, *},
364 storage::bounded_btree_map::BoundedBTreeMap,
365 traits::{
366 fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze},
367 tokens::{Fortitude, Preservation},
368 Contains, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, Get,
369 },
370 DefaultNoBound, PalletError,
371};
372use scale_info::TypeInfo;
373use sp_core::U256;
374use sp_runtime::{
375 traits::{
376 AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup,
377 Zero,
378 },
379 FixedPointNumber, Perbill,
380};
381use sp_staking::{EraIndex, StakingInterface};
382
383#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
384use sp_runtime::TryRuntimeError;
385
386pub const LOG_TARGET: &str = "runtime::nomination-pools";
388#[macro_export]
390macro_rules! log {
391 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
392 log::$level!(
393 target: $crate::LOG_TARGET,
394 concat!("[{:?}] 🏊♂️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
395 )
396 };
397}
398
399#[cfg(any(test, feature = "fuzzing"))]
400pub mod mock;
401#[cfg(test)]
402mod tests;
403
404pub mod adapter;
405pub mod migration;
406pub mod weights;
407
408pub use pallet::*;
409use sp_runtime::traits::BlockNumberProvider;
410pub use weights::WeightInfo;
411
412pub type BalanceOf<T> =
414 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
415pub type PoolId = u32;
417
418type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
419
420pub type BlockNumberFor<T> =
421 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
422
423pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1;
424
425#[derive(
427 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
428)]
429pub enum ConfigOp<T: Codec + Debug> {
430 Noop,
432 Set(T),
434 Remove,
436}
437
438pub enum BondType {
440 Create,
442 Extra,
444}
445
446#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
448pub enum BondExtra<Balance> {
449 FreeBalance(Balance),
451 Rewards,
453}
454
455#[derive(Encode, Decode)]
457enum AccountType {
458 Bonded,
459 Reward,
460}
461
462#[derive(
464 Encode,
465 Decode,
466 DecodeWithMemTracking,
467 MaxEncodedLen,
468 Clone,
469 Copy,
470 Debug,
471 PartialEq,
472 Eq,
473 TypeInfo,
474)]
475pub enum ClaimPermission {
476 Permissioned,
478 PermissionlessCompound,
480 PermissionlessWithdraw,
482 PermissionlessAll,
484}
485
486impl Default for ClaimPermission {
487 fn default() -> Self {
488 Self::PermissionlessWithdraw
489 }
490}
491
492impl ClaimPermission {
493 fn can_bond_extra(&self) -> bool {
496 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
497 }
498
499 fn can_claim_payout(&self) -> bool {
502 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
503 }
504}
505
506#[derive(
508 Encode,
509 Decode,
510 DecodeWithMemTracking,
511 MaxEncodedLen,
512 TypeInfo,
513 DebugNoBound,
514 CloneNoBound,
515 PartialEqNoBound,
516 EqNoBound,
517)]
518#[cfg_attr(feature = "std", derive(DefaultNoBound))]
519#[scale_info(skip_type_params(T))]
520pub struct PoolMember<T: Config> {
521 pub pool_id: PoolId,
523 pub points: BalanceOf<T>,
526 pub last_recorded_reward_counter: T::RewardCounter,
528 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
531}
532
533impl<T: Config> PoolMember<T> {
534 fn pending_rewards(
536 &self,
537 current_reward_counter: T::RewardCounter,
538 ) -> Result<BalanceOf<T>, Error<T>> {
539 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
557 .checked_mul_int(self.active_points())
558 .ok_or(Error::<T>::OverflowRisk)
559 }
560
561 fn active_balance(&self) -> BalanceOf<T> {
566 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
567 pool.points_to_balance(self.points)
568 } else {
569 Zero::zero()
570 }
571 }
572
573 pub fn total_balance(&self) -> BalanceOf<T> {
579 let pool = match BondedPool::<T>::get(self.pool_id) {
580 Some(pool) => pool,
581 None => {
582 defensive!("pool should exist; qed");
584 return Zero::zero();
585 },
586 };
587
588 let active_balance = pool.points_to_balance(self.active_points());
589
590 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
591 Some(sub_pools) => sub_pools,
592 None => return active_balance,
593 };
594
595 let unbonding_balance = self.unbonding_eras.iter().fold(
596 BalanceOf::<T>::zero(),
597 |accumulator, (era, unlocked_points)| {
598 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
601 accumulator + (era_pool.point_to_balance(*unlocked_points))
602 },
603 );
604
605 active_balance + unbonding_balance
606 }
607
608 fn total_points(&self) -> BalanceOf<T> {
610 self.active_points().saturating_add(self.unbonding_points())
611 }
612
613 fn active_points(&self) -> BalanceOf<T> {
615 self.points
616 }
617
618 fn unbonding_points(&self) -> BalanceOf<T> {
620 self.unbonding_eras
621 .as_ref()
622 .iter()
623 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
624 }
625
626 fn try_unbond(
634 &mut self,
635 points_dissolved: BalanceOf<T>,
636 points_issued: BalanceOf<T>,
637 unbonding_era: EraIndex,
638 ) -> Result<(), Error<T>> {
639 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
640 match self.unbonding_eras.get_mut(&unbonding_era) {
641 Some(already_unbonding_points) => {
642 *already_unbonding_points =
643 already_unbonding_points.saturating_add(points_issued)
644 },
645 None => self
646 .unbonding_eras
647 .try_insert(unbonding_era, points_issued)
648 .map(|old| {
649 if old.is_some() {
650 defensive!("value checked to not exist in the map; qed");
651 }
652 })
653 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
654 }
655 self.points = new_points;
656 Ok(())
657 } else {
658 Err(Error::<T>::MinimumBondNotMet)
659 }
660 }
661
662 fn withdraw_unlocked(
669 &mut self,
670 active_era: EraIndex,
671 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
672 let mut removed_points =
674 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
675 self.unbonding_eras.retain(|e, p| {
676 if *e > active_era {
677 true
678 } else {
679 removed_points
680 .try_insert(*e, *p)
681 .expect("source map is bounded, this is a subset, will be bounded; qed");
682 false
683 }
684 });
685 removed_points
686 }
687}
688
689#[derive(
691 Encode,
692 Decode,
693 DecodeWithMemTracking,
694 MaxEncodedLen,
695 TypeInfo,
696 PartialEq,
697 DebugNoBound,
698 Clone,
699 Copy,
700)]
701pub enum PoolState {
702 Open,
704 Blocked,
706 Destroying,
711}
712
713#[derive(
719 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone,
720)]
721pub struct PoolRoles<AccountId> {
722 pub depositor: AccountId,
725 pub root: Option<AccountId>,
728 pub nominator: Option<AccountId>,
730 pub bouncer: Option<AccountId>,
732}
733
734#[derive(
736 PartialEq,
737 Eq,
738 Copy,
739 Clone,
740 Encode,
741 Decode,
742 DecodeWithMemTracking,
743 Debug,
744 TypeInfo,
745 MaxEncodedLen,
746)]
747pub enum CommissionClaimPermission<AccountId> {
748 Permissionless,
749 Account(AccountId),
750}
751
752#[derive(
765 Encode,
766 Decode,
767 DecodeWithMemTracking,
768 DefaultNoBound,
769 MaxEncodedLen,
770 TypeInfo,
771 DebugNoBound,
772 PartialEq,
773 Copy,
774 Clone,
775)]
776#[codec(mel_bound(T: Config))]
777#[scale_info(skip_type_params(T))]
778pub struct Commission<T: Config> {
779 pub current: Option<(Perbill, T::AccountId)>,
781 pub max: Option<Perbill>,
784 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
787 pub throttle_from: Option<BlockNumberFor<T>>,
790 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
793}
794
795impl<T: Config> Commission<T> {
796 fn throttling(&self, to: &Perbill) -> bool {
803 if let Some(t) = self.change_rate.as_ref() {
804 let commission_as_percent =
805 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
806
807 if *to <= commission_as_percent {
809 return false;
810 }
811 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
815 return true;
816 }
817
818 return self.throttle_from.map_or_else(
823 || {
824 defensive!("throttle_from should exist if change_rate is set");
825 true
826 },
827 |f| {
828 if t.min_delay == Zero::zero() {
830 false
831 } else {
832 let blocks_surpassed =
834 T::BlockNumberProvider::current_block_number().saturating_sub(f);
835 blocks_surpassed < t.min_delay
836 }
837 },
838 );
839 }
840 false
841 }
842
843 fn current(&self) -> Perbill {
846 self.current
847 .as_ref()
848 .map_or(Perbill::zero(), |(c, _)| *c)
849 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
850 }
851
852 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
858 self.current = match current {
859 None => None,
860 Some((commission, payee)) => {
861 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
862 ensure!(
863 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
864 Error::<T>::CommissionExceedsGlobalMaximum
865 );
866 ensure!(
867 self.max.map_or(true, |m| commission <= &m),
868 Error::<T>::CommissionExceedsMaximum
869 );
870 if commission.is_zero() {
871 None
872 } else {
873 Some((*commission, payee.clone()))
874 }
875 },
876 };
877 self.register_update();
878 Ok(())
879 }
880
881 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
890 ensure!(
891 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
892 Error::<T>::CommissionExceedsGlobalMaximum
893 );
894 if let Some(old) = self.max.as_mut() {
895 if new_max > *old {
896 return Err(Error::<T>::MaxCommissionRestricted.into());
897 }
898 *old = new_max;
899 } else {
900 self.max = Some(new_max)
901 };
902 let updated_current = self
903 .current
904 .as_mut()
905 .map(|(c, _)| {
906 let u = *c > new_max;
907 *c = (*c).min(new_max);
908 u
909 })
910 .unwrap_or(false);
911
912 if updated_current {
913 if let Some((_, payee)) = self.current.as_ref() {
914 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
915 pool_id,
916 current: Some((new_max, payee.clone())),
917 });
918 }
919 self.register_update();
920 }
921 Ok(())
922 }
923
924 fn try_update_change_rate(
933 &mut self,
934 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
935 ) -> DispatchResult {
936 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
937
938 if self.change_rate.is_none() {
939 self.register_update();
940 }
941 self.change_rate = Some(change_rate);
942 Ok(())
943 }
944
945 fn register_update(&mut self) {
947 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
948 }
949
950 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
955 self.change_rate
956 .as_ref()
957 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
958 .unwrap_or(false)
959 }
960}
961
962#[derive(
970 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
971)]
972pub struct CommissionChangeRate<BlockNumber> {
973 pub max_increase: Perbill,
975 pub min_delay: BlockNumber,
977}
978
979#[derive(
981 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
982)]
983#[codec(mel_bound(T: Config))]
984#[scale_info(skip_type_params(T))]
985pub struct BondedPoolInner<T: Config> {
986 pub commission: Commission<T>,
988 pub member_counter: u32,
990 pub points: BalanceOf<T>,
992 pub roles: PoolRoles<T::AccountId>,
994 pub state: PoolState,
996}
997
998#[derive(DebugNoBound)]
1003#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1004pub struct BondedPool<T: Config> {
1005 id: PoolId,
1007 inner: BondedPoolInner<T>,
1009}
1010
1011impl<T: Config> core::ops::Deref for BondedPool<T> {
1012 type Target = BondedPoolInner<T>;
1013 fn deref(&self) -> &Self::Target {
1014 &self.inner
1015 }
1016}
1017
1018impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1019 fn deref_mut(&mut self) -> &mut Self::Target {
1020 &mut self.inner
1021 }
1022}
1023
1024impl<T: Config> BondedPool<T> {
1025 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1027 Self {
1028 id,
1029 inner: BondedPoolInner {
1030 commission: Commission::default(),
1031 member_counter: Zero::zero(),
1032 points: Zero::zero(),
1033 roles,
1034 state: PoolState::Open,
1035 },
1036 }
1037 }
1038
1039 pub fn get(id: PoolId) -> Option<Self> {
1041 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1042 }
1043
1044 fn bonded_account(&self) -> T::AccountId {
1046 Pallet::<T>::generate_bonded_account(self.id)
1047 }
1048
1049 fn reward_account(&self) -> T::AccountId {
1051 Pallet::<T>::generate_reward_account(self.id)
1052 }
1053
1054 fn put(self) {
1056 BondedPools::<T>::insert(self.id, self.inner);
1057 }
1058
1059 fn remove(self) {
1061 BondedPools::<T>::remove(self.id);
1062 }
1063
1064 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1068 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1069 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1070 }
1071
1072 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1076 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1077 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1078 }
1079
1080 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1082 let points_to_issue = self.balance_to_point(new_funds);
1083 self.points = self.points.saturating_add(points_to_issue);
1084 points_to_issue
1085 }
1086
1087 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1094 let balance = self.points_to_balance(points);
1097 self.points = self.points.saturating_sub(points);
1098 balance
1099 }
1100
1101 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1104 ensure!(
1105 MaxPoolMembersPerPool::<T>::get()
1106 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1107 Error::<T>::MaxPoolMembers
1108 );
1109 ensure!(
1110 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1111 Error::<T>::MaxPoolMembers
1112 );
1113 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1114 Ok(())
1115 }
1116
1117 fn dec_members(mut self) -> Self {
1119 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1120 self
1121 }
1122
1123 fn is_root(&self, who: &T::AccountId) -> bool {
1124 self.roles.root.as_ref().map_or(false, |root| root == who)
1125 }
1126
1127 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1128 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1129 }
1130
1131 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1132 self.is_root(who)
1133 }
1134
1135 fn can_nominate(&self, who: &T::AccountId) -> bool {
1136 self.is_root(who) ||
1137 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1138 }
1139
1140 fn can_kick(&self, who: &T::AccountId) -> bool {
1141 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1142 }
1143
1144 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1145 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1146 }
1147
1148 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1149 self.is_root(who) || self.is_bouncer(who)
1150 }
1151
1152 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1153 self.is_root(who)
1154 }
1155
1156 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1157 if let Some(permission) = self.commission.claim_permission.as_ref() {
1158 match permission {
1159 CommissionClaimPermission::Permissionless => true,
1160 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1161 }
1162 } else {
1163 self.is_root(who)
1164 }
1165 }
1166
1167 fn is_destroying(&self) -> bool {
1168 matches!(self.state, PoolState::Destroying)
1169 }
1170
1171 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1172 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1179 }
1180
1181 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1184 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1185
1186 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1187 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1188
1189 let points_to_balance_ratio_floor = self
1190 .points
1191 .div(bonded_balance);
1193
1194 let max_points_to_balance = T::MaxPointsToBalance::get();
1195
1196 ensure!(
1200 points_to_balance_ratio_floor < max_points_to_balance.into(),
1201 Error::<T>::OverflowRisk
1202 );
1203
1204 Ok(())
1208 }
1209
1210 fn ok_to_join(&self) -> Result<(), DispatchError> {
1212 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1213 self.ok_to_be_open()?;
1214 Ok(())
1215 }
1216
1217 fn ok_to_unbond_with(
1218 &self,
1219 caller: &T::AccountId,
1220 target_account: &T::AccountId,
1221 target_member: &PoolMember<T>,
1222 unbonding_points: BalanceOf<T>,
1223 ) -> Result<(), DispatchError> {
1224 let is_permissioned = caller == target_account;
1225 let is_depositor = *target_account == self.roles.depositor;
1226 let is_full_unbond = unbonding_points == target_member.active_points();
1227
1228 let balance_after_unbond = {
1229 let new_depositor_points =
1230 target_member.active_points().saturating_sub(unbonding_points);
1231 let mut target_member_after_unbond = (*target_member).clone();
1232 target_member_after_unbond.points = new_depositor_points;
1233 target_member_after_unbond.active_balance()
1234 };
1235
1236 ensure!(
1238 is_permissioned || is_full_unbond,
1239 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1240 );
1241
1242 ensure!(
1244 is_full_unbond ||
1245 balance_after_unbond >=
1246 if is_depositor {
1247 Pallet::<T>::depositor_min_bond()
1248 } else {
1249 MinJoinBond::<T>::get()
1250 },
1251 Error::<T>::MinimumBondNotMet
1252 );
1253
1254 match (is_permissioned, is_depositor) {
1256 (true, false) => (),
1257 (true, true) => {
1258 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1261 } else {
1263 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1265 }
1266 },
1267 (false, false) => {
1268 debug_assert!(is_full_unbond);
1271 ensure!(
1272 self.can_kick(caller) || self.is_destroying(),
1273 Error::<T>::NotKickerOrDestroying
1274 )
1275 },
1276 (false, true) => {
1277 debug_assert!(is_full_unbond);
1281 ensure!(
1282 self.is_destroying_and_only_depositor(target_member.active_points()),
1283 Error::<T>::DoesNotHavePermission
1284 );
1285 },
1286 };
1287
1288 Ok(())
1289 }
1290
1291 fn ok_to_withdraw_unbonded_with(
1295 &self,
1296 caller: &T::AccountId,
1297 target_account: &T::AccountId,
1298 ) -> Result<(), DispatchError> {
1299 let is_permissioned = caller == target_account;
1301 ensure!(
1302 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1303 Error::<T>::NotKickerOrDestroying
1304 );
1305 Ok(())
1306 }
1307
1308 fn try_bond_funds(
1316 &mut self,
1317 who: &T::AccountId,
1318 amount: BalanceOf<T>,
1319 ty: BondType,
1320 ) -> Result<BalanceOf<T>, DispatchError> {
1321 let points_issued = self.issue(amount);
1324
1325 T::StakeAdapter::pledge_bond(
1326 Member::from(who.clone()),
1327 Pool::from(self.bonded_account()),
1328 &self.reward_account(),
1329 amount,
1330 ty,
1331 )?;
1332 TotalValueLocked::<T>::mutate(|tvl| {
1333 tvl.saturating_accrue(amount);
1334 });
1335
1336 Ok(points_issued)
1337 }
1338
1339 fn set_state(&mut self, state: PoolState) {
1342 if self.state != state {
1343 self.state = state;
1344 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1345 pool_id: self.id,
1346 new_state: state,
1347 });
1348 };
1349 }
1350}
1351
1352#[derive(
1358 Encode,
1359 Decode,
1360 MaxEncodedLen,
1361 DecodeWithMemTracking,
1362 TypeInfo,
1363 CloneNoBound,
1364 PartialEqNoBound,
1365 EqNoBound,
1366 DebugNoBound,
1367)]
1368#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1369#[codec(mel_bound(T: Config))]
1370#[scale_info(skip_type_params(T))]
1371pub struct RewardPool<T: Config> {
1372 pub last_recorded_reward_counter: T::RewardCounter,
1377 pub last_recorded_total_payouts: BalanceOf<T>,
1383 pub total_rewards_claimed: BalanceOf<T>,
1385 pub total_commission_pending: BalanceOf<T>,
1387 pub total_commission_claimed: BalanceOf<T>,
1389}
1390
1391impl<T: Config> RewardPool<T> {
1392 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1394 self.last_recorded_reward_counter
1395 }
1396
1397 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1399 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1400 }
1401
1402 fn update_records(
1409 &mut self,
1410 id: PoolId,
1411 bonded_points: BalanceOf<T>,
1412 commission: Perbill,
1413 ) -> Result<(), Error<T>> {
1414 let balance = Self::current_balance(id);
1415
1416 let (current_reward_counter, new_pending_commission) =
1417 self.current_reward_counter(id, bonded_points, commission)?;
1418
1419 self.last_recorded_reward_counter = current_reward_counter;
1423
1424 self.total_commission_pending =
1427 self.total_commission_pending.saturating_add(new_pending_commission);
1428
1429 let last_recorded_total_payouts = balance
1433 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1434 .ok_or(Error::<T>::OverflowRisk)?;
1435
1436 self.last_recorded_total_payouts =
1443 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1444
1445 Ok(())
1446 }
1447
1448 fn current_reward_counter(
1451 &self,
1452 id: PoolId,
1453 bonded_points: BalanceOf<T>,
1454 commission: Perbill,
1455 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1456 let balance = Self::current_balance(id);
1457
1458 let current_payout_balance = balance
1463 .saturating_add(self.total_rewards_claimed)
1464 .saturating_add(self.total_commission_claimed)
1465 .saturating_sub(self.last_recorded_total_payouts);
1466
1467 let new_pending_commission = commission * current_payout_balance;
1470 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1471
1472 let current_reward_counter =
1507 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1508 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1509 .ok_or(Error::<T>::OverflowRisk)?;
1510
1511 Ok((current_reward_counter, new_pending_commission))
1512 }
1513
1514 fn current_balance(id: PoolId) -> BalanceOf<T> {
1518 T::Currency::reducible_balance(
1519 &Pallet::<T>::generate_reward_account(id),
1520 Preservation::Expendable,
1521 Fortitude::Polite,
1522 )
1523 }
1524}
1525
1526#[derive(
1528 Encode,
1529 Decode,
1530 MaxEncodedLen,
1531 DecodeWithMemTracking,
1532 TypeInfo,
1533 DefaultNoBound,
1534 DebugNoBound,
1535 CloneNoBound,
1536 PartialEqNoBound,
1537 EqNoBound,
1538)]
1539#[codec(mel_bound(T: Config))]
1540#[scale_info(skip_type_params(T))]
1541pub struct UnbondPool<T: Config> {
1542 pub points: BalanceOf<T>,
1544 pub balance: BalanceOf<T>,
1546}
1547
1548impl<T: Config> UnbondPool<T> {
1549 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1550 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1551 }
1552
1553 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1554 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1555 }
1556
1557 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1561 let new_points = self.balance_to_point(new_funds);
1562 self.points = self.points.saturating_add(new_points);
1563 self.balance = self.balance.saturating_add(new_funds);
1564 new_points
1565 }
1566
1567 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1572 let balance_to_unbond = self.point_to_balance(points);
1573 self.points = self.points.saturating_sub(points);
1574 self.balance = self.balance.saturating_sub(balance_to_unbond);
1575
1576 balance_to_unbond
1577 }
1578}
1579
1580#[derive(
1581 Encode,
1582 Decode,
1583 MaxEncodedLen,
1584 DecodeWithMemTracking,
1585 TypeInfo,
1586 DefaultNoBound,
1587 DebugNoBound,
1588 CloneNoBound,
1589 PartialEqNoBound,
1590 EqNoBound,
1591)]
1592#[codec(mel_bound(T: Config))]
1593#[scale_info(skip_type_params(T))]
1594pub struct SubPools<T: Config> {
1595 pub no_era: UnbondPool<T>,
1599 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, T::MaxUnbondingPools>,
1601}
1602
1603impl<T: Config> SubPools<T> {
1604 fn maybe_merge_pools(mut self, active_era: EraIndex) -> Self {
1609 let effective_post_unbonding_window =
1612 T::MaxUnbondingPools::get().saturating_sub(T::StakeAdapter::bonding_duration());
1613 if let Some(newest_era_to_remove) = active_era.checked_sub(effective_post_unbonding_window)
1614 {
1615 self.with_era.retain(|k, v| {
1616 if *k > newest_era_to_remove {
1617 true
1619 } else {
1620 self.no_era.points = self.no_era.points.saturating_add(v.points);
1622 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1623 false
1624 }
1625 });
1626 }
1627
1628 self
1629 }
1630
1631 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1633 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1634 self.no_era.balance.saturating_add(
1635 self.with_era
1636 .values()
1637 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1638 )
1639 }
1640}
1641
1642#[frame_support::pallet]
1643pub mod pallet {
1644 use super::*;
1645 use frame_support::traits::StorageVersion;
1646 use frame_system::pallet_prelude::{
1647 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1648 };
1649 use sp_runtime::Perbill;
1650
1651 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1653
1654 #[pallet::pallet]
1655 #[pallet::storage_version(STORAGE_VERSION)]
1656 pub struct Pallet<T>(_);
1657
1658 #[pallet::config]
1659 pub trait Config: frame_system::Config {
1660 #[allow(deprecated)]
1662 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1663
1664 type WeightInfo: weights::WeightInfo;
1666
1667 type Currency: Mutate<Self::AccountId>
1669 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1670
1671 type RuntimeFreezeReason: From<FreezeReason>;
1673
1674 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1687
1688 #[pallet::constant]
1690 type PalletId: Get<frame_support::PalletId>;
1691
1692 #[pallet::constant]
1705 type MaxPointsToBalance: Get<u8>;
1706
1707 #[pallet::constant]
1709 type MaxUnbonding: Get<u32>;
1710
1711 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1713
1714 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1716
1717 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1721
1722 #[pallet::constant]
1729 type MaxUnbondingPools: Get<u32>;
1730
1731 type MaxMetadataLen: Get<u32>;
1733
1734 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1736
1737 type BlockNumberProvider: BlockNumberProvider;
1739
1740 type Filter: Contains<Self::AccountId>;
1742 }
1743
1744 #[pallet::storage]
1750 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1751
1752 #[pallet::storage]
1754 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1755
1756 #[pallet::storage]
1764 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1765
1766 #[pallet::storage]
1769 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1770
1771 #[pallet::storage]
1774 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1775
1776 #[pallet::storage]
1779 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1780
1781 #[pallet::storage]
1785 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1786
1787 #[pallet::storage]
1791 pub type PoolMembers<T: Config> =
1792 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1793
1794 #[pallet::storage]
1797 pub type BondedPools<T: Config> =
1798 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1799
1800 #[pallet::storage]
1803 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1804
1805 #[pallet::storage]
1808 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1809
1810 #[pallet::storage]
1812 pub type Metadata<T: Config> =
1813 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1814
1815 #[pallet::storage]
1817 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1818
1819 #[pallet::storage]
1824 pub type ReversePoolIdLookup<T: Config> =
1825 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1826
1827 #[pallet::storage]
1829 pub type ClaimPermissions<T: Config> =
1830 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1831
1832 #[pallet::genesis_config]
1833 pub struct GenesisConfig<T: Config> {
1834 pub min_join_bond: BalanceOf<T>,
1835 pub min_create_bond: BalanceOf<T>,
1836 pub max_pools: Option<u32>,
1837 pub max_members_per_pool: Option<u32>,
1838 pub max_members: Option<u32>,
1839 pub global_max_commission: Option<Perbill>,
1840 }
1841
1842 impl<T: Config> Default for GenesisConfig<T> {
1843 fn default() -> Self {
1844 Self {
1845 min_join_bond: Zero::zero(),
1846 min_create_bond: Zero::zero(),
1847 max_pools: Some(16),
1848 max_members_per_pool: Some(32),
1849 max_members: Some(16 * 32),
1850 global_max_commission: None,
1851 }
1852 }
1853 }
1854
1855 #[pallet::genesis_build]
1856 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1857 fn build(&self) {
1858 MinJoinBond::<T>::put(self.min_join_bond);
1859 MinCreateBond::<T>::put(self.min_create_bond);
1860
1861 if let Some(max_pools) = self.max_pools {
1862 MaxPools::<T>::put(max_pools);
1863 }
1864 if let Some(max_members_per_pool) = self.max_members_per_pool {
1865 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1866 }
1867 if let Some(max_members) = self.max_members {
1868 MaxPoolMembers::<T>::put(max_members);
1869 }
1870 if let Some(global_max_commission) = self.global_max_commission {
1871 GlobalMaxCommission::<T>::put(global_max_commission);
1872 }
1873 }
1874 }
1875
1876 #[pallet::event]
1878 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1879 pub enum Event<T: Config> {
1880 Created { depositor: T::AccountId, pool_id: PoolId },
1882 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1884 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1886 Unbonded {
1898 member: T::AccountId,
1899 pool_id: PoolId,
1900 balance: BalanceOf<T>,
1901 points: BalanceOf<T>,
1902 era: EraIndex,
1903 },
1904 Withdrawn {
1911 member: T::AccountId,
1912 pool_id: PoolId,
1913 balance: BalanceOf<T>,
1914 points: BalanceOf<T>,
1915 },
1916 Destroyed { pool_id: PoolId },
1918 StateChanged { pool_id: PoolId, new_state: PoolState },
1920 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1926 RolesUpdated {
1929 root: Option<T::AccountId>,
1930 bouncer: Option<T::AccountId>,
1931 nominator: Option<T::AccountId>,
1932 },
1933 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1935 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1937 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1939 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1941 PoolCommissionChangeRateUpdated {
1943 pool_id: PoolId,
1944 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1945 },
1946 PoolCommissionClaimPermissionUpdated {
1948 pool_id: PoolId,
1949 permission: Option<CommissionClaimPermission<T::AccountId>>,
1950 },
1951 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1953 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1955 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1957 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1959 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1961 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1964 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1966 GlobalParamsUpdated {
1968 min_join_bond: BalanceOf<T>,
1969 min_create_bond: BalanceOf<T>,
1970 max_pools: Option<u32>,
1971 max_members: Option<u32>,
1972 max_members_per_pool: Option<u32>,
1973 global_max_commission: Option<Perbill>,
1974 },
1975 }
1976
1977 #[pallet::error]
1978 #[cfg_attr(test, derive(PartialEq))]
1979 pub enum Error<T> {
1980 PoolNotFound,
1982 PoolMemberNotFound,
1984 RewardPoolNotFound,
1986 SubPoolsNotFound,
1988 AccountBelongsToOtherPool,
1991 FullyUnbonding,
1994 MaxUnbondingLimit,
1996 CannotWithdrawAny,
1998 MinimumBondNotMet,
2004 OverflowRisk,
2006 NotDestroying,
2009 NotNominator,
2011 NotKickerOrDestroying,
2013 NotOpen,
2015 MaxPools,
2017 MaxPoolMembers,
2019 CanNotChangeState,
2021 DoesNotHavePermission,
2023 MetadataExceedsMaxLen,
2025 Defensive(DefensiveError),
2028 PartialUnbondNotAllowedPermissionlessly,
2030 MaxCommissionRestricted,
2032 CommissionExceedsMaximum,
2034 CommissionExceedsGlobalMaximum,
2036 CommissionChangeThrottled,
2038 CommissionChangeRateNotAllowed,
2040 NoPendingCommission,
2042 NoCommissionCurrentSet,
2044 PoolIdInUse,
2046 InvalidPoolId,
2048 BondExtraRestricted,
2050 NothingToAdjust,
2052 NothingToSlash,
2054 SlashTooLow,
2056 AlreadyMigrated,
2058 NotMigrated,
2060 NotSupported,
2062 Restricted,
2065 }
2066
2067 #[derive(Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, Debug)]
2068 pub enum DefensiveError {
2069 NotEnoughSpaceInUnbondPool,
2071 PoolNotFound,
2073 RewardPoolNotFound,
2075 SubPoolsNotFound,
2077 BondedStashKilledPrematurely,
2080 DelegationUnsupported,
2082 SlashNotApplied,
2084 }
2085
2086 impl<T> From<DefensiveError> for Error<T> {
2087 fn from(e: DefensiveError) -> Error<T> {
2088 Error::<T>::Defensive(e)
2089 }
2090 }
2091
2092 #[pallet::composite_enum]
2094 pub enum FreezeReason {
2095 #[codec(index = 0)]
2097 PoolMinBalance,
2098 }
2099
2100 #[pallet::call]
2101 impl<T: Config> Pallet<T> {
2102 #[pallet::call_index(0)]
2119 #[pallet::weight(T::WeightInfo::join())]
2120 pub fn join(
2121 origin: OriginFor<T>,
2122 #[pallet::compact] amount: BalanceOf<T>,
2123 pool_id: PoolId,
2124 ) -> DispatchResult {
2125 let who = ensure_signed(origin)?;
2126 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2128
2129 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2131
2132 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2133 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2135
2136 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2137 bonded_pool.ok_to_join()?;
2138
2139 let mut reward_pool = RewardPools::<T>::get(pool_id)
2140 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2141 reward_pool.update_records(
2143 pool_id,
2144 bonded_pool.points,
2145 bonded_pool.commission.current(),
2146 )?;
2147
2148 bonded_pool.try_inc_members()?;
2149 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2150
2151 PoolMembers::insert(
2152 who.clone(),
2153 PoolMember::<T> {
2154 pool_id,
2155 points: points_issued,
2156 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2159 unbonding_eras: Default::default(),
2160 },
2161 );
2162
2163 Self::deposit_event(Event::<T>::Bonded {
2164 member: who,
2165 pool_id,
2166 bonded: amount,
2167 joined: true,
2168 });
2169
2170 bonded_pool.put();
2171 RewardPools::<T>::insert(pool_id, reward_pool);
2172
2173 Ok(())
2174 }
2175
2176 #[pallet::call_index(1)]
2187 #[pallet::weight(
2188 T::WeightInfo::bond_extra_transfer()
2189 .max(T::WeightInfo::bond_extra_other())
2190 )]
2191 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2192 let who = ensure_signed(origin)?;
2193
2194 ensure!(
2196 !Self::api_member_needs_delegate_migration(who.clone()),
2197 Error::<T>::NotMigrated
2198 );
2199
2200 Self::do_bond_extra(who.clone(), who, extra)
2201 }
2202
2203 #[pallet::call_index(2)]
2212 #[pallet::weight(T::WeightInfo::claim_payout())]
2213 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2214 let signer = ensure_signed(origin)?;
2215 ensure!(
2217 !Self::api_member_needs_delegate_migration(signer.clone()),
2218 Error::<T>::NotMigrated
2219 );
2220
2221 Self::do_claim_payout(signer.clone(), signer)
2222 }
2223
2224 #[pallet::call_index(3)]
2256 #[pallet::weight(T::WeightInfo::unbond())]
2257 pub fn unbond(
2258 origin: OriginFor<T>,
2259 member_account: AccountIdLookupOf<T>,
2260 #[pallet::compact] unbonding_points: BalanceOf<T>,
2261 ) -> DispatchResult {
2262 let who = ensure_signed(origin)?;
2263 let member_account = T::Lookup::lookup(member_account)?;
2264 ensure!(
2266 !Self::api_member_needs_delegate_migration(member_account.clone()),
2267 Error::<T>::NotMigrated
2268 );
2269
2270 let (mut member, mut bonded_pool, mut reward_pool) =
2271 Self::get_member_with_pools(&member_account)?;
2272
2273 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2274
2275 reward_pool.update_records(
2279 bonded_pool.id,
2280 bonded_pool.points,
2281 bonded_pool.commission.current(),
2282 )?;
2283 Self::do_reward_payout(
2284 &member_account,
2285 &mut member,
2286 &mut bonded_pool,
2287 &mut reward_pool,
2288 )?;
2289
2290 let active_era = T::StakeAdapter::current_era();
2291 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(active_era);
2292
2293 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2295 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2296
2297 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2299 .unwrap_or_default()
2300 .maybe_merge_pools(active_era);
2301
2302 if !sub_pools.with_era.contains_key(&unbond_era) {
2305 sub_pools
2306 .with_era
2307 .try_insert(unbond_era, UnbondPool::default())
2308 .defensive_map_err::<Error<T>, _>(|_| {
2311 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2312 })?;
2313 }
2314
2315 let points_unbonded = sub_pools
2316 .with_era
2317 .get_mut(&unbond_era)
2318 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2320 .issue(unbonding_balance);
2321
2322 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2324
2325 Self::deposit_event(Event::<T>::Unbonded {
2326 member: member_account.clone(),
2327 pool_id: member.pool_id,
2328 points: points_unbonded,
2329 balance: unbonding_balance,
2330 era: unbond_era,
2331 });
2332
2333 SubPoolsStorage::insert(member.pool_id, sub_pools);
2335 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2336 Ok(())
2337 }
2338
2339 #[pallet::call_index(4)]
2346 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2347 pub fn pool_withdraw_unbonded(
2348 origin: OriginFor<T>,
2349 pool_id: PoolId,
2350 num_slashing_spans: u32,
2351 ) -> DispatchResult {
2352 ensure_signed(origin)?;
2353 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2355
2356 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2357
2358 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2361 T::StakeAdapter::withdraw_unbonded(
2362 Pool::from(pool.bonded_account()),
2363 num_slashing_spans,
2364 )?;
2365
2366 Ok(())
2367 }
2368
2369 #[pallet::call_index(5)]
2392 #[pallet::weight(
2393 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2394 )]
2395 pub fn withdraw_unbonded(
2396 origin: OriginFor<T>,
2397 member_account: AccountIdLookupOf<T>,
2398 num_slashing_spans: u32,
2399 ) -> DispatchResultWithPostInfo {
2400 let caller = ensure_signed(origin)?;
2401 let member_account = T::Lookup::lookup(member_account)?;
2402 ensure!(
2404 !Self::api_member_needs_delegate_migration(member_account.clone()),
2405 Error::<T>::NotMigrated
2406 );
2407
2408 let mut member =
2409 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2410 let active_era = T::StakeAdapter::current_era();
2411
2412 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2413 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2414 let mut sub_pools =
2415 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2416
2417 let slash_weight =
2418 match Self::do_apply_slash(&member_account, None, false) {
2420 Ok(_) => T::WeightInfo::apply_slash(),
2421 Err(e) => {
2422 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2423 if Err(e) == no_pending_slash {
2425 T::WeightInfo::apply_slash_fail()
2426 } else {
2427 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2429 }
2430 }
2431
2432 };
2433
2434 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2435 let pool_account = bonded_pool.bonded_account();
2436
2437 let withdrawn_points = member.withdraw_unlocked(active_era);
2439 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2440
2441 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2444 Pool::from(bonded_pool.bonded_account()),
2445 num_slashing_spans,
2446 )?;
2447
2448 ensure!(
2451 !stash_killed || caller == bonded_pool.roles.depositor,
2452 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2453 );
2454
2455 if stash_killed {
2456 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2458 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2459 }
2460
2461 }
2468
2469 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2470 let balance_to_unbond = withdrawn_points
2471 .iter()
2472 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2473 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2474 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2475 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2476 if era_pool.points.is_zero() {
2477 sub_pools.with_era.remove(era);
2478 }
2479 accumulator.saturating_add(balance_to_unbond)
2480 } else {
2481 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2484 }
2485 })
2486 .min(T::StakeAdapter::transferable_balance(
2494 Pool::from(bonded_pool.bonded_account()),
2495 Member::from(member_account.clone()),
2496 ));
2497
2498 T::StakeAdapter::member_withdraw(
2501 Member::from(member_account.clone()),
2502 Pool::from(bonded_pool.bonded_account()),
2503 balance_to_unbond,
2504 num_slashing_spans,
2505 )?;
2506
2507 Self::deposit_event(Event::<T>::Withdrawn {
2508 member: member_account.clone(),
2509 pool_id: member.pool_id,
2510 points: sum_unlocked_points,
2511 balance: balance_to_unbond,
2512 });
2513
2514 let post_info_weight = if member.total_points().is_zero() {
2515 ClaimPermissions::<T>::remove(&member_account);
2517
2518 PoolMembers::<T>::remove(&member_account);
2520
2521 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2523 Member::from(member_account.clone()),
2524 ) {
2525 Some(dangling_delegation) => {
2526 T::StakeAdapter::member_withdraw(
2527 Member::from(member_account.clone()),
2528 Pool::from(bonded_pool.bonded_account()),
2529 dangling_delegation,
2530 num_slashing_spans,
2531 )?;
2532 dangling_delegation
2533 },
2534 None => Zero::zero(),
2535 };
2536
2537 Self::deposit_event(Event::<T>::MemberRemoved {
2538 pool_id: member.pool_id,
2539 member: member_account.clone(),
2540 released_balance: dangling_withdrawal,
2541 });
2542
2543 if member_account == bonded_pool.roles.depositor {
2544 Pallet::<T>::dissolve_pool(bonded_pool);
2545 Weight::default()
2546 } else {
2547 bonded_pool.dec_members().put();
2548 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2549 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2550 }
2551 } else {
2552 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2554 PoolMembers::<T>::insert(&member_account, member);
2555 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2556 };
2557
2558 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2559 }
2560
2561 #[pallet::call_index(6)]
2579 #[pallet::weight(T::WeightInfo::create())]
2580 pub fn create(
2581 origin: OriginFor<T>,
2582 #[pallet::compact] amount: BalanceOf<T>,
2583 root: AccountIdLookupOf<T>,
2584 nominator: AccountIdLookupOf<T>,
2585 bouncer: AccountIdLookupOf<T>,
2586 ) -> DispatchResult {
2587 let depositor = ensure_signed(origin)?;
2588
2589 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2590 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2591 Ok(*id)
2592 })?;
2593
2594 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2595 }
2596
2597 #[pallet::call_index(7)]
2604 #[pallet::weight(T::WeightInfo::create())]
2605 pub fn create_with_pool_id(
2606 origin: OriginFor<T>,
2607 #[pallet::compact] amount: BalanceOf<T>,
2608 root: AccountIdLookupOf<T>,
2609 nominator: AccountIdLookupOf<T>,
2610 bouncer: AccountIdLookupOf<T>,
2611 pool_id: PoolId,
2612 ) -> DispatchResult {
2613 let depositor = ensure_signed(origin)?;
2614
2615 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2616 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2617
2618 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2619 }
2620
2621 #[pallet::call_index(8)]
2634 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2635 pub fn nominate(
2636 origin: OriginFor<T>,
2637 pool_id: PoolId,
2638 validators: Vec<T::AccountId>,
2639 ) -> DispatchResult {
2640 let who = ensure_signed(origin)?;
2641 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2642 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2644 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2645
2646 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2647 .ok_or(Error::<T>::PoolMemberNotFound)?
2648 .active_points();
2649
2650 ensure!(
2651 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2652 Error::<T>::MinimumBondNotMet
2653 );
2654
2655 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2656 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2657 )
2658 }
2659
2660 #[pallet::call_index(9)]
2671 #[pallet::weight(T::WeightInfo::set_state())]
2672 pub fn set_state(
2673 origin: OriginFor<T>,
2674 pool_id: PoolId,
2675 state: PoolState,
2676 ) -> DispatchResult {
2677 let who = ensure_signed(origin)?;
2678 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2679 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2680 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2682
2683 if bonded_pool.can_toggle_state(&who) {
2684 bonded_pool.set_state(state);
2685 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2686 bonded_pool.set_state(PoolState::Destroying);
2688 } else {
2689 Err(Error::<T>::CanNotChangeState)?;
2690 }
2691
2692 bonded_pool.put();
2693
2694 Ok(())
2695 }
2696
2697 #[pallet::call_index(10)]
2702 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2703 pub fn set_metadata(
2704 origin: OriginFor<T>,
2705 pool_id: PoolId,
2706 metadata: Vec<u8>,
2707 ) -> DispatchResult {
2708 let who = ensure_signed(origin)?;
2709 let metadata: BoundedVec<_, _> =
2710 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2711 ensure!(
2712 BondedPool::<T>::get(pool_id)
2713 .ok_or(Error::<T>::PoolNotFound)?
2714 .can_set_metadata(&who),
2715 Error::<T>::DoesNotHavePermission
2716 );
2717 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2719
2720 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2721
2722 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2723
2724 Ok(())
2725 }
2726
2727 #[pallet::call_index(11)]
2739 #[pallet::weight(T::WeightInfo::set_configs())]
2740 pub fn set_configs(
2741 origin: OriginFor<T>,
2742 min_join_bond: ConfigOp<BalanceOf<T>>,
2743 min_create_bond: ConfigOp<BalanceOf<T>>,
2744 max_pools: ConfigOp<u32>,
2745 max_members: ConfigOp<u32>,
2746 max_members_per_pool: ConfigOp<u32>,
2747 global_max_commission: ConfigOp<Perbill>,
2748 ) -> DispatchResult {
2749 T::AdminOrigin::ensure_origin(origin)?;
2750
2751 macro_rules! config_op_exp {
2752 ($storage:ty, $op:ident) => {
2753 match $op {
2754 ConfigOp::Noop => (),
2755 ConfigOp::Set(v) => <$storage>::put(v),
2756 ConfigOp::Remove => <$storage>::kill(),
2757 }
2758 };
2759 }
2760
2761 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2762 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2763 config_op_exp!(MaxPools::<T>, max_pools);
2764 config_op_exp!(MaxPoolMembers::<T>, max_members);
2765 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2766 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2767
2768 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2769 min_join_bond: MinJoinBond::<T>::get(),
2770 min_create_bond: MinCreateBond::<T>::get(),
2771 max_pools: MaxPools::<T>::get(),
2772 max_members: MaxPoolMembers::<T>::get(),
2773 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2774 global_max_commission: GlobalMaxCommission::<T>::get(),
2775 });
2776
2777 Ok(())
2778 }
2779
2780 #[pallet::call_index(12)]
2788 #[pallet::weight(T::WeightInfo::update_roles())]
2789 pub fn update_roles(
2790 origin: OriginFor<T>,
2791 pool_id: PoolId,
2792 new_root: ConfigOp<T::AccountId>,
2793 new_nominator: ConfigOp<T::AccountId>,
2794 new_bouncer: ConfigOp<T::AccountId>,
2795 ) -> DispatchResult {
2796 let mut bonded_pool = match ensure_root(origin.clone()) {
2797 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2798 Err(sp_runtime::traits::BadOrigin) => {
2799 let who = ensure_signed(origin)?;
2800 let bonded_pool =
2801 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2802 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2803 bonded_pool
2804 },
2805 };
2806
2807 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2809
2810 match new_root {
2811 ConfigOp::Noop => (),
2812 ConfigOp::Remove => bonded_pool.roles.root = None,
2813 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2814 };
2815 match new_nominator {
2816 ConfigOp::Noop => (),
2817 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2818 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2819 };
2820 match new_bouncer {
2821 ConfigOp::Noop => (),
2822 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2823 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2824 };
2825
2826 Self::deposit_event(Event::<T>::RolesUpdated {
2827 root: bonded_pool.roles.root.clone(),
2828 nominator: bonded_pool.roles.nominator.clone(),
2829 bouncer: bonded_pool.roles.bouncer.clone(),
2830 });
2831
2832 bonded_pool.put();
2833 Ok(())
2834 }
2835
2836 #[pallet::call_index(13)]
2854 #[pallet::weight(T::WeightInfo::chill())]
2855 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2856 let who = ensure_signed(origin)?;
2857 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2858 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2860
2861 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2862 .ok_or(Error::<T>::PoolMemberNotFound)?
2863 .active_points();
2864
2865 if bonded_pool.points_to_balance(depositor_points) >=
2866 T::StakeAdapter::minimum_nominator_bond()
2867 {
2868 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2869 }
2870
2871 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2872 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2873 })
2874 }
2875
2876 #[pallet::call_index(14)]
2886 #[pallet::weight(
2887 T::WeightInfo::bond_extra_transfer()
2888 .max(T::WeightInfo::bond_extra_other())
2889 )]
2890 pub fn bond_extra_other(
2891 origin: OriginFor<T>,
2892 member: AccountIdLookupOf<T>,
2893 extra: BondExtra<BalanceOf<T>>,
2894 ) -> DispatchResult {
2895 let who = ensure_signed(origin)?;
2896 let member_account = T::Lookup::lookup(member)?;
2897 ensure!(
2899 !Self::api_member_needs_delegate_migration(member_account.clone()),
2900 Error::<T>::NotMigrated
2901 );
2902
2903 Self::do_bond_extra(who, member_account, extra)
2904 }
2905
2906 #[pallet::call_index(15)]
2914 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2915 pub fn set_claim_permission(
2916 origin: OriginFor<T>,
2917 permission: ClaimPermission,
2918 ) -> DispatchResult {
2919 let who = ensure_signed(origin)?;
2920 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2921
2922 ensure!(
2924 !Self::api_member_needs_delegate_migration(who.clone()),
2925 Error::<T>::NotMigrated
2926 );
2927
2928 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2929 *source = permission;
2930 });
2931
2932 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2933 member: who,
2934 permission,
2935 });
2936
2937 Ok(())
2938 }
2939
2940 #[pallet::call_index(16)]
2945 #[pallet::weight(T::WeightInfo::claim_payout())]
2946 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2947 let signer = ensure_signed(origin)?;
2948 ensure!(
2950 !Self::api_member_needs_delegate_migration(other.clone()),
2951 Error::<T>::NotMigrated
2952 );
2953
2954 Self::do_claim_payout(signer, other)
2955 }
2956
2957 #[pallet::call_index(17)]
2964 #[pallet::weight(T::WeightInfo::set_commission())]
2965 pub fn set_commission(
2966 origin: OriginFor<T>,
2967 pool_id: PoolId,
2968 new_commission: Option<(Perbill, T::AccountId)>,
2969 ) -> DispatchResult {
2970 let who = ensure_signed(origin)?;
2971 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2972 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2974
2975 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2976
2977 let mut reward_pool = RewardPools::<T>::get(pool_id)
2978 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2979 reward_pool.update_records(
2982 pool_id,
2983 bonded_pool.points,
2984 bonded_pool.commission.current(),
2985 )?;
2986 RewardPools::insert(pool_id, reward_pool);
2987
2988 bonded_pool.commission.try_update_current(&new_commission)?;
2989 bonded_pool.put();
2990 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
2991 pool_id,
2992 current: new_commission,
2993 });
2994 Ok(())
2995 }
2996
2997 #[pallet::call_index(18)]
3003 #[pallet::weight(T::WeightInfo::set_commission_max())]
3004 pub fn set_commission_max(
3005 origin: OriginFor<T>,
3006 pool_id: PoolId,
3007 max_commission: Perbill,
3008 ) -> DispatchResult {
3009 let who = ensure_signed(origin)?;
3010 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3011 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3013
3014 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3015
3016 let mut reward_pool = RewardPools::<T>::get(pool_id)
3017 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3018 reward_pool.update_records(
3023 pool_id,
3024 bonded_pool.points,
3025 bonded_pool.commission.current(),
3026 )?;
3027 RewardPools::insert(pool_id, reward_pool);
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() < T::MaxUnbondingPools::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
3295 pub fn do_claim_trapped_balance(member_account: &T::AccountId) -> DispatchResult {
3307 ensure!(
3308 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3309 Error::<T>::NotSupported
3310 );
3311
3312 match Self::do_apply_slash(member_account, None, false) {
3315 Ok(_) => {},
3316 Err(e)
3317 if e == Error::<T>::NothingToSlash.into() ||
3318 e == Error::<T>::PoolMemberNotFound.into() => {},
3319 Err(_) => {
3320 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
3321 },
3322 };
3323
3324 let member = match PoolMembers::<T>::get(member_account) {
3325 Some(m) => m,
3326 None => return Ok(()),
3327 };
3328
3329 let expected_balance = member.total_balance();
3330 let actual_balance =
3331 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()))
3332 .unwrap_or_default();
3333
3334 let trapped_amount = actual_balance.saturating_sub(expected_balance);
3335
3336 if trapped_amount.is_zero() {
3337 return Ok(());
3338 }
3339
3340 T::StakeAdapter::member_withdraw(
3341 Member::from(member_account.clone()),
3342 Pool::from(Self::generate_bonded_account(member.pool_id)),
3343 trapped_amount,
3344 0,
3345 )?;
3346
3347 log!(
3348 info,
3349 "Claimed trapped balance for member {:?}, pool {:?}, amount {:?}",
3350 member_account,
3351 member.pool_id,
3352 trapped_amount
3353 );
3354
3355 Ok(())
3356 }
3357
3358 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3363 let reward_account = bonded_pool.reward_account();
3364 let bonded_account = bonded_pool.bonded_account();
3365
3366 ReversePoolIdLookup::<T>::remove(&bonded_account);
3367 RewardPools::<T>::remove(bonded_pool.id);
3368 SubPoolsStorage::<T>::remove(bonded_pool.id);
3369
3370 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3372
3373 defensive_assert!(
3381 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3382 "reward account of dissolving pool should have no consumers"
3383 );
3384 defensive_assert!(
3385 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3386 "bonded account of dissolving pool should have no consumers"
3387 );
3388 defensive_assert!(
3389 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3390 "dissolving pool should not have any stake in the staking pallet"
3391 );
3392
3393 let reward_pool_remaining = T::Currency::reducible_balance(
3396 &reward_account,
3397 Preservation::Expendable,
3398 Fortitude::Polite,
3399 );
3400 let _ = T::Currency::transfer(
3401 &reward_account,
3402 &bonded_pool.roles.depositor,
3403 reward_pool_remaining,
3404 Preservation::Expendable,
3405 );
3406
3407 defensive_assert!(
3408 T::Currency::total_balance(&reward_account) == Zero::zero(),
3409 "could not transfer all amount to depositor while dissolving pool"
3410 );
3411 T::Currency::set_balance(&reward_account, Zero::zero());
3413
3414 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3416
3417 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3418 Metadata::<T>::remove(bonded_pool.id);
3420
3421 bonded_pool.remove();
3422 }
3423
3424 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3426 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3427 }
3428
3429 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3430 T::StakeAdapter::migrate_nominator_to_agent(
3431 Pool::from(Self::generate_bonded_account(id)),
3432 &Self::generate_reward_account(id),
3433 )
3434 }
3435
3436 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3438 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3441 }
3442
3443 fn get_member_with_pools(
3445 who: &T::AccountId,
3446 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3447 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3448 let bonded_pool =
3449 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3450 let reward_pool =
3451 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3452 Ok((member, bonded_pool, reward_pool))
3453 }
3454
3455 fn put_member_with_pools(
3458 member_account: &T::AccountId,
3459 member: PoolMember<T>,
3460 bonded_pool: BondedPool<T>,
3461 reward_pool: RewardPool<T>,
3462 ) {
3463 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3466 debug_assert_eq!(member.pool_id, bonded_pool.id);
3467
3468 bonded_pool.put();
3469 RewardPools::insert(member.pool_id, reward_pool);
3470 PoolMembers::<T>::insert(member_account, member);
3471 }
3472
3473 fn balance_to_point(
3476 current_balance: BalanceOf<T>,
3477 current_points: BalanceOf<T>,
3478 new_funds: BalanceOf<T>,
3479 ) -> BalanceOf<T> {
3480 let u256 = T::BalanceToU256::convert;
3481 let balance = T::U256ToBalance::convert;
3482 match (current_balance.is_zero(), current_points.is_zero()) {
3483 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3484 (true, false) => {
3485 new_funds.saturating_mul(current_points)
3488 },
3489 (false, false) => {
3490 balance(
3492 u256(current_points)
3493 .saturating_mul(u256(new_funds))
3494 .div(u256(current_balance)),
3496 )
3497 },
3498 }
3499 }
3500
3501 fn point_to_balance(
3504 current_balance: BalanceOf<T>,
3505 current_points: BalanceOf<T>,
3506 points: BalanceOf<T>,
3507 ) -> BalanceOf<T> {
3508 let u256 = T::BalanceToU256::convert;
3509 let balance = T::U256ToBalance::convert;
3510 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3511 return Zero::zero();
3513 }
3514
3515 balance(
3517 u256(current_balance)
3518 .saturating_mul(u256(points))
3519 .div(u256(current_points)),
3521 )
3522 }
3523
3524 fn do_reward_payout(
3528 member_account: &T::AccountId,
3529 member: &mut PoolMember<T>,
3530 bonded_pool: &mut BondedPool<T>,
3531 reward_pool: &mut RewardPool<T>,
3532 ) -> Result<BalanceOf<T>, DispatchError> {
3533 debug_assert_eq!(member.pool_id, bonded_pool.id);
3534 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3535
3536 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3538
3539 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3540 bonded_pool.id,
3541 bonded_pool.points,
3542 bonded_pool.commission.current(),
3543 )?;
3544
3545 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3548 if pending_rewards.is_zero() {
3549 return Ok(pending_rewards);
3550 }
3551
3552 member.last_recorded_reward_counter = current_reward_counter;
3554 reward_pool.register_claimed_reward(pending_rewards);
3555
3556 T::Currency::transfer(
3557 &bonded_pool.reward_account(),
3558 member_account,
3559 pending_rewards,
3560 Preservation::Preserve,
3563 )?;
3564
3565 Self::deposit_event(Event::<T>::PaidOut {
3566 member: member_account.clone(),
3567 pool_id: member.pool_id,
3568 payout: pending_rewards,
3569 });
3570 Ok(pending_rewards)
3571 }
3572
3573 fn do_create(
3574 who: T::AccountId,
3575 amount: BalanceOf<T>,
3576 root: AccountIdLookupOf<T>,
3577 nominator: AccountIdLookupOf<T>,
3578 bouncer: AccountIdLookupOf<T>,
3579 pool_id: PoolId,
3580 ) -> DispatchResult {
3581 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3583
3584 let root = T::Lookup::lookup(root)?;
3585 let nominator = T::Lookup::lookup(nominator)?;
3586 let bouncer = T::Lookup::lookup(bouncer)?;
3587
3588 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3589 ensure!(
3590 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3591 Error::<T>::MaxPools
3592 );
3593 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3594 let mut bonded_pool = BondedPool::<T>::new(
3595 pool_id,
3596 PoolRoles {
3597 root: Some(root),
3598 nominator: Some(nominator),
3599 bouncer: Some(bouncer),
3600 depositor: who.clone(),
3601 },
3602 );
3603
3604 bonded_pool.try_inc_members()?;
3605 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3606
3607 T::Currency::transfer(
3609 &who,
3610 &bonded_pool.reward_account(),
3611 T::Currency::minimum_balance(),
3612 Preservation::Expendable,
3613 )?;
3614
3615 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3617
3618 PoolMembers::<T>::insert(
3619 who.clone(),
3620 PoolMember::<T> {
3621 pool_id,
3622 points,
3623 last_recorded_reward_counter: Zero::zero(),
3624 unbonding_eras: Default::default(),
3625 },
3626 );
3627 RewardPools::<T>::insert(
3628 pool_id,
3629 RewardPool::<T> {
3630 last_recorded_reward_counter: Zero::zero(),
3631 last_recorded_total_payouts: Zero::zero(),
3632 total_rewards_claimed: Zero::zero(),
3633 total_commission_pending: Zero::zero(),
3634 total_commission_claimed: Zero::zero(),
3635 },
3636 );
3637 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3638
3639 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3640
3641 Self::deposit_event(Event::<T>::Bonded {
3642 member: who,
3643 pool_id,
3644 bonded: amount,
3645 joined: true,
3646 });
3647 bonded_pool.put();
3648
3649 Ok(())
3650 }
3651
3652 fn do_bond_extra(
3653 signer: T::AccountId,
3654 member_account: T::AccountId,
3655 extra: BondExtra<BalanceOf<T>>,
3656 ) -> DispatchResult {
3657 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3659
3660 if signer != member_account {
3661 ensure!(
3662 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3663 Error::<T>::DoesNotHavePermission
3664 );
3665 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3666 }
3667
3668 let (mut member, mut bonded_pool, mut reward_pool) =
3669 Self::get_member_with_pools(&member_account)?;
3670
3671 reward_pool.update_records(
3674 bonded_pool.id,
3675 bonded_pool.points,
3676 bonded_pool.commission.current(),
3677 )?;
3678 let claimed = Self::do_reward_payout(
3679 &member_account,
3680 &mut member,
3681 &mut bonded_pool,
3682 &mut reward_pool,
3683 )?;
3684
3685 let (points_issued, bonded) = match extra {
3686 BondExtra::FreeBalance(amount) => {
3687 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount)
3688 },
3689 BondExtra::Rewards => {
3690 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed)
3691 },
3692 };
3693
3694 bonded_pool.ok_to_be_open()?;
3695 member.points =
3696 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3697
3698 Self::deposit_event(Event::<T>::Bonded {
3699 member: member_account.clone(),
3700 pool_id: member.pool_id,
3701 bonded,
3702 joined: false,
3703 });
3704 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3705
3706 Ok(())
3707 }
3708
3709 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3710 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3711 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3712
3713 let mut reward_pool = RewardPools::<T>::get(pool_id)
3714 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3715
3716 reward_pool.update_records(
3719 pool_id,
3720 bonded_pool.points,
3721 bonded_pool.commission.current(),
3722 )?;
3723
3724 let commission = reward_pool.total_commission_pending;
3725 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3726
3727 let payee = bonded_pool
3728 .commission
3729 .current
3730 .as_ref()
3731 .map(|(_, p)| p.clone())
3732 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3733
3734 T::Currency::transfer(
3736 &bonded_pool.reward_account(),
3737 &payee,
3738 commission,
3739 Preservation::Preserve,
3740 )?;
3741
3742 reward_pool.total_commission_claimed =
3744 reward_pool.total_commission_claimed.saturating_add(commission);
3745 reward_pool.total_commission_pending = Zero::zero();
3747 RewardPools::<T>::insert(pool_id, reward_pool);
3748
3749 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3750 Ok(())
3751 }
3752
3753 pub(crate) fn do_claim_payout(
3754 signer: T::AccountId,
3755 member_account: T::AccountId,
3756 ) -> DispatchResult {
3757 if signer != member_account {
3758 ensure!(
3759 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3760 Error::<T>::DoesNotHavePermission
3761 );
3762 }
3763 let (mut member, mut bonded_pool, mut reward_pool) =
3764 Self::get_member_with_pools(&member_account)?;
3765
3766 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3767
3768 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3769 Ok(())
3770 }
3771
3772 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3773 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3774
3775 let reward_acc = &bonded_pool.reward_account();
3776 let pre_frozen_balance =
3777 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3778 let min_balance = T::Currency::minimum_balance();
3779
3780 if pre_frozen_balance == min_balance {
3781 return Err(Error::<T>::NothingToAdjust.into());
3782 }
3783
3784 Self::freeze_pool_deposit(reward_acc)?;
3786
3787 if pre_frozen_balance > min_balance {
3788 ensure!(
3790 who == bonded_pool.roles.depositor ||
3791 bonded_pool.roles.root.as_ref().map_or(false, |root| &who == root),
3792 Error::<T>::DoesNotHavePermission
3793 );
3794
3795 let excess = pre_frozen_balance.saturating_sub(min_balance);
3797
3798 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3799 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3800 pool_id: pool,
3801 amount: excess,
3802 });
3803 } else {
3804 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3806 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3807 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3808 pool_id: pool,
3809 amount: deficit,
3810 });
3811 }
3812
3813 Ok(())
3814 }
3815
3816 fn do_apply_slash(
3818 member_account: &T::AccountId,
3819 reporter: Option<T::AccountId>,
3820 enforce_min_slash: bool,
3821 ) -> DispatchResult {
3822 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3823
3824 let pending_slash =
3825 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3826
3827 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3829
3830 if enforce_min_slash {
3831 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3833 }
3834
3835 T::StakeAdapter::member_slash(
3836 Member::from(member_account.clone()),
3837 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3838 pending_slash,
3839 reporter,
3840 )
3841 }
3842
3843 fn member_pending_slash(
3847 member_account: Member<T::AccountId>,
3848 pool_member: PoolMember<T>,
3849 ) -> Result<BalanceOf<T>, DispatchError> {
3850 debug_assert!(
3852 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3853 pool_member
3854 );
3855
3856 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3857 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3860 return Ok(Zero::zero());
3861 }
3862
3863 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3865 .ok_or(Error::<T>::NotMigrated)?;
3867
3868 let expected_balance = pool_member.total_balance();
3870
3871 Ok(actual_balance.saturating_sub(expected_balance))
3873 }
3874
3875 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3877 T::Currency::set_freeze(
3878 &FreezeReason::PoolMinBalance.into(),
3879 reward_acc,
3880 T::Currency::minimum_balance(),
3881 )
3882 }
3883
3884 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3886 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3887 }
3888
3889 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3926 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3927 if level.is_zero() {
3928 return Ok(());
3929 }
3930 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3933 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3934 ensure!(
3935 bonded_pools == reward_pools,
3936 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3937 );
3938
3939 ensure!(
3940 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3941 "`SubPoolsStorage` must be a subset of the above superset."
3942 );
3943 ensure!(
3944 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3945 "`Metadata` keys must be a subset of the above superset."
3946 );
3947
3948 ensure!(
3949 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3950 Error::<T>::MaxPools
3951 );
3952
3953 for id in reward_pools {
3954 let account = Self::generate_reward_account(id);
3955 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3956 T::Currency::minimum_balance()
3957 {
3958 log!(
3959 warn,
3960 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3961 changed recently. Pool operators should be notified to top up the reward \
3962 account",
3963 id,
3964 T::Currency::reducible_balance(
3965 &account,
3966 Preservation::Expendable,
3967 Fortitude::Polite
3968 ),
3969 T::Currency::minimum_balance(),
3970 )
3971 }
3972 }
3973
3974 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3975 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3976 let mut all_members = 0u32;
3977 let mut total_balance_members = Default::default();
3978 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3979 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3980 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3981 *pools_members.entry(d.pool_id).or_default() += 1;
3982 all_members += 1;
3983
3984 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3985 if !bonded_pool.points.is_zero() {
3986 let commission = bonded_pool.commission.current();
3987 let (current_rc, _) = reward_pool
3988 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3989 .unwrap();
3990 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3991 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3992 } total_balance_members += d.total_balance();
3994
3995 Ok(())
3996 })?;
3997
3998 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3999 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
4002 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
4003
4004 if !pending_rewards_lt_leftover_bal {
4007 log!(
4008 warn,
4009 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
4010 id,
4011 pools_members_pending_rewards.get(&id),
4012 RewardPool::<T>::current_balance(id)
4013 );
4014 }
4015 Ok(())
4016 })?;
4017
4018 let mut expected_tvl: BalanceOf<T> = Default::default();
4019 let mut depositor_undermin: Vec<(PoolId, T::AccountId)> = Vec::new();
4020 let mut depositor_undermin_total: u32 = 0;
4021 let mut total_pools: u32 = 0;
4022 const MAX_EXAMPLES: usize = 10;
4023
4024 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
4025 total_pools += 1;
4026 let bonded_pool = BondedPool { id, inner };
4027 ensure!(
4028 pools_members.get(&id).copied().unwrap_or_default() ==
4029 bonded_pool.member_counter,
4030 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
4031 );
4032 ensure!(
4033 MaxPoolMembersPerPool::<T>::get()
4034 .map_or(true, |max| bonded_pool.member_counter <= max),
4035 Error::<T>::MaxPoolMembers
4036 );
4037
4038 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
4039 let depositor_has_enough_stake = bonded_pool
4040 .is_destroying_and_only_depositor(depositor.active_points()) ||
4041 depositor.active_points() >= MinCreateBond::<T>::get();
4042 if !depositor_has_enough_stake {
4043 depositor_undermin_total += 1;
4044 if depositor_undermin.len() < MAX_EXAMPLES {
4045 depositor_undermin.push((id, bonded_pool.roles.depositor.clone()));
4046 }
4047 log!(
4048 trace,
4049 "pool {:?} has depositor {:?} with insufficient stake {:?}, minimum required is {:?}",
4050 id,
4051 bonded_pool.roles.depositor,
4052 depositor.active_points(),
4053 MinCreateBond::<T>::get()
4054 );
4055 }
4056
4057 ensure!(
4058 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
4059 "Each `BondedPool.points` must never be lower than the pool's balance"
4060 );
4061
4062 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
4063
4064 Ok(())
4065 })?;
4066
4067 if depositor_undermin_total > 0 {
4068 log!(
4069 warn,
4070 "{}/{} pools have depositor with insufficient stake, minimum required is {:?}. Examples: {:?}",
4071 depositor_undermin_total,
4072 total_pools,
4073 MinCreateBond::<T>::get(),
4074 depositor_undermin,
4075 );
4076 }
4077
4078 ensure!(
4079 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
4080 Error::<T>::MaxPoolMembers
4081 );
4082
4083 ensure!(
4084 TotalValueLocked::<T>::get() == expected_tvl,
4085 "TVL deviates from the actual sum of funds of all Pools."
4086 );
4087
4088 ensure!(
4089 TotalValueLocked::<T>::get() <= total_balance_members,
4090 "TVL must be equal to or less than the total balance of all PoolMembers."
4091 );
4092
4093 if level <= 1 {
4094 return Ok(());
4095 }
4096
4097 for (pool_id, _pool) in BondedPools::<T>::iter() {
4098 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
4099 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
4100
4101 let sum_unbonding_balance = subs.sum_unbonding_balance();
4102 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
4103 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
4105 .unwrap_or(T::Currency::total_balance(&pool_account));
4108
4109 if total_balance < bonded_balance + sum_unbonding_balance {
4110 log!(
4111 warn,
4112 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4113 pool_id,
4114 _pool,
4115 total_balance,
4116 bonded_balance,
4117 sum_unbonding_balance
4118 )
4119 };
4120 }
4121
4122 let _needs_adjust = Self::check_ed_imbalance()?;
4125
4126 Ok(())
4127 }
4128
4129 #[cfg(any(
4133 feature = "try-runtime",
4134 feature = "runtime-benchmarks",
4135 feature = "fuzzing",
4136 test,
4137 debug_assertions
4138 ))]
4139 pub fn check_ed_imbalance() -> Result<u32, DispatchError> {
4140 let mut needs_adjust: u32 = 0;
4141 let mut total_pools: u32 = 0;
4142 let mut ed_examples: Vec<PoolId> = Vec::new();
4143 const MAX_EXAMPLES: usize = 10;
4144
4145 BondedPools::<T>::iter_keys().for_each(|id| {
4146 total_pools += 1;
4147 let reward_acc = Self::generate_reward_account(id);
4148 let frozen_balance =
4149 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4150
4151 let expected_frozen_balance = T::Currency::minimum_balance();
4152 if frozen_balance != expected_frozen_balance {
4153 needs_adjust += 1;
4154 if ed_examples.len() < MAX_EXAMPLES {
4155 ed_examples.push(id);
4156 }
4157 log!(
4158 trace,
4159 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}. Use `adjust_pool_deposit` to fix it",
4160 id,
4161 expected_frozen_balance,
4162 frozen_balance,
4163 );
4164 }
4165 });
4166
4167 if needs_adjust > 0 {
4168 log!(
4169 warn,
4170 "{}/{} pools have incorrect ED frozen (expected {:?}). Use `adjust_pool_deposit` to fix. Examples: {:?}",
4171 needs_adjust,
4172 total_pools,
4173 T::Currency::minimum_balance(),
4174 ed_examples,
4175 );
4176 }
4177
4178 Ok(needs_adjust)
4179 }
4180 #[cfg(any(feature = "runtime-benchmarks", test))]
4185 pub fn fully_unbond(
4186 origin: frame_system::pallet_prelude::OriginFor<T>,
4187 member: T::AccountId,
4188 ) -> DispatchResult {
4189 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4190 let member_lookup = T::Lookup::unlookup(member);
4191 Self::unbond(origin, member_lookup, points)
4192 }
4193}
4194
4195impl<T: Config> Pallet<T> {
4196 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4200 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4201 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4202 .zip(BondedPools::<T>::get(pool_member.pool_id))
4203 {
4204 let commission = bonded_pool.commission.current();
4205 let (current_reward_counter, _) = reward_pool
4206 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4207 .ok()?;
4208 return pool_member.pending_rewards(current_reward_counter).ok();
4209 }
4210 }
4211
4212 None
4213 }
4214
4215 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4219 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4220 pool.points_to_balance(points)
4221 } else {
4222 Zero::zero()
4223 }
4224 }
4225
4226 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4230 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4231 let bonded_balance =
4232 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4233 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4234 } else {
4235 Zero::zero()
4236 }
4237 }
4238
4239 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4243 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4244 }
4245
4246 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4253 PoolMembers::<T>::get(who.clone())
4254 .map(|pool_member| {
4255 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4256 })
4257 .unwrap_or_default()
4258 }
4259
4260 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4265 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4267 return false;
4268 }
4269
4270 if !BondedPools::<T>::contains_key(pool_id) {
4272 return false;
4273 }
4274
4275 let pool_account = Self::generate_bonded_account(pool_id);
4276
4277 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4279 adapter::StakeStrategyType::Delegate
4280 }
4281
4282 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4288 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4290 return false;
4291 }
4292
4293 PoolMembers::<T>::get(who.clone())
4294 .map(|pool_member| {
4295 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4296 return false;
4298 }
4299
4300 let member_balance = pool_member.total_balance();
4301 let delegated_balance =
4302 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4303
4304 delegated_balance.is_none() && !member_balance.is_zero()
4307 })
4308 .unwrap_or_default()
4309 }
4310
4311 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4316 PoolMembers::<T>::get(who.clone())
4317 .map(|m| m.total_balance())
4318 .unwrap_or_default()
4319 }
4320
4321 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4323 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4324 .unwrap_or_default()
4325 }
4326
4327 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4329 let bonded_account = Self::generate_bonded_account(pool_id);
4330 let reward_account = Self::generate_reward_account(pool_id);
4331 (bonded_account, reward_account)
4332 }
4333}
4334
4335impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4336 fn on_slash(
4342 pool_account: &T::AccountId,
4343 slashed_bonded: BalanceOf<T>,
4346 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4347 total_slashed: BalanceOf<T>,
4348 ) {
4349 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4350 TotalValueLocked::<T>::mutate(|tvl| {
4353 tvl.defensive_saturating_reduce(total_slashed);
4354 });
4355
4356 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4357 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4359 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4360 pool.balance = *slashed_balance;
4361 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4362 era: *era,
4363 pool_id,
4364 balance: *slashed_balance,
4365 });
4366 }
4367 });
4368 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4369 } else if !slashed_unlocking.is_empty() {
4370 defensive!("Expected SubPools were not found");
4371 }
4372 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4373 }
4374
4375 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4378 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4379 TotalValueLocked::<T>::mutate(|tvl| {
4380 tvl.saturating_reduce(amount);
4381 });
4382 }
4383 }
4384}
4385
4386pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4388impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4389 fn contains(t: &T::AccountId) -> bool {
4390 PoolMembers::<T>::contains_key(t)
4391 }
4392}