1#![cfg_attr(not(feature = "std"), no_std)]
353
354extern crate alloc;
355
356use adapter::{Member, Pool, StakeStrategy};
357use alloc::{collections::btree_map::BTreeMap, vec::Vec};
358use codec::{Codec, DecodeWithMemTracking};
359use core::{fmt::Debug, ops::Div};
360use frame_support::{
361 defensive, defensive_assert, ensure,
362 pallet_prelude::{MaxEncodedLen, *},
363 storage::bounded_btree_map::BoundedBTreeMap,
364 traits::{
365 fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze},
366 tokens::{Fortitude, Preservation},
367 Contains, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, Get,
368 },
369 DefaultNoBound, PalletError,
370};
371use scale_info::TypeInfo;
372use sp_core::U256;
373use sp_runtime::{
374 traits::{
375 AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup,
376 Zero,
377 },
378 FixedPointNumber, Perbill,
379};
380use sp_staking::{EraIndex, StakingInterface};
381
382#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
383use sp_runtime::TryRuntimeError;
384
385pub const LOG_TARGET: &str = "runtime::nomination-pools";
387#[macro_export]
389macro_rules! log {
390 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
391 log::$level!(
392 target: $crate::LOG_TARGET,
393 concat!("[{:?}] 🏊♂️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
394 )
395 };
396}
397
398#[cfg(any(test, feature = "fuzzing"))]
399pub mod mock;
400#[cfg(test)]
401mod tests;
402
403pub mod adapter;
404pub mod migration;
405pub mod weights;
406
407pub use pallet::*;
408use sp_runtime::traits::BlockNumberProvider;
409pub use weights::WeightInfo;
410
411pub type BalanceOf<T> =
413 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
414pub type PoolId = u32;
416
417type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
418
419pub type BlockNumberFor<T> =
420 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
421
422pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1;
423
424#[derive(
426 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
427)]
428pub enum ConfigOp<T: Codec + Debug> {
429 Noop,
431 Set(T),
433 Remove,
435}
436
437pub enum BondType {
439 Create,
441 Extra,
443}
444
445#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
447pub enum BondExtra<Balance> {
448 FreeBalance(Balance),
450 Rewards,
452}
453
454#[derive(Encode, Decode)]
456enum AccountType {
457 Bonded,
458 Reward,
459}
460
461#[derive(
463 Encode,
464 Decode,
465 DecodeWithMemTracking,
466 MaxEncodedLen,
467 Clone,
468 Copy,
469 Debug,
470 PartialEq,
471 Eq,
472 TypeInfo,
473)]
474pub enum ClaimPermission {
475 Permissioned,
477 PermissionlessCompound,
479 PermissionlessWithdraw,
481 PermissionlessAll,
483}
484
485impl Default for ClaimPermission {
486 fn default() -> Self {
487 Self::PermissionlessWithdraw
488 }
489}
490
491impl ClaimPermission {
492 fn can_bond_extra(&self) -> bool {
495 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
496 }
497
498 fn can_claim_payout(&self) -> bool {
501 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
502 }
503}
504
505#[derive(
507 Encode,
508 Decode,
509 DecodeWithMemTracking,
510 MaxEncodedLen,
511 TypeInfo,
512 DebugNoBound,
513 CloneNoBound,
514 PartialEqNoBound,
515 EqNoBound,
516)]
517#[cfg_attr(feature = "std", derive(DefaultNoBound))]
518#[scale_info(skip_type_params(T))]
519pub struct PoolMember<T: Config> {
520 pub pool_id: PoolId,
522 pub points: BalanceOf<T>,
525 pub last_recorded_reward_counter: T::RewardCounter,
527 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
530}
531
532impl<T: Config> PoolMember<T> {
533 fn pending_rewards(
535 &self,
536 current_reward_counter: T::RewardCounter,
537 ) -> Result<BalanceOf<T>, Error<T>> {
538 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
556 .checked_mul_int(self.active_points())
557 .ok_or(Error::<T>::OverflowRisk)
558 }
559
560 fn active_balance(&self) -> BalanceOf<T> {
565 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
566 pool.points_to_balance(self.points)
567 } else {
568 Zero::zero()
569 }
570 }
571
572 pub fn total_balance(&self) -> BalanceOf<T> {
578 let pool = match BondedPool::<T>::get(self.pool_id) {
579 Some(pool) => pool,
580 None => {
581 defensive!("pool should exist; qed");
583 return Zero::zero();
584 },
585 };
586
587 let active_balance = pool.points_to_balance(self.active_points());
588
589 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
590 Some(sub_pools) => sub_pools,
591 None => return active_balance,
592 };
593
594 let unbonding_balance = self.unbonding_eras.iter().fold(
595 BalanceOf::<T>::zero(),
596 |accumulator, (era, unlocked_points)| {
597 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
600 accumulator + (era_pool.point_to_balance(*unlocked_points))
601 },
602 );
603
604 active_balance + unbonding_balance
605 }
606
607 fn total_points(&self) -> BalanceOf<T> {
609 self.active_points().saturating_add(self.unbonding_points())
610 }
611
612 fn active_points(&self) -> BalanceOf<T> {
614 self.points
615 }
616
617 fn unbonding_points(&self) -> BalanceOf<T> {
619 self.unbonding_eras
620 .as_ref()
621 .iter()
622 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
623 }
624
625 fn try_unbond(
633 &mut self,
634 points_dissolved: BalanceOf<T>,
635 points_issued: BalanceOf<T>,
636 unbonding_era: EraIndex,
637 ) -> Result<(), Error<T>> {
638 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
639 match self.unbonding_eras.get_mut(&unbonding_era) {
640 Some(already_unbonding_points) => {
641 *already_unbonding_points =
642 already_unbonding_points.saturating_add(points_issued)
643 },
644 None => self
645 .unbonding_eras
646 .try_insert(unbonding_era, points_issued)
647 .map(|old| {
648 if old.is_some() {
649 defensive!("value checked to not exist in the map; qed");
650 }
651 })
652 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
653 }
654 self.points = new_points;
655 Ok(())
656 } else {
657 Err(Error::<T>::MinimumBondNotMet)
658 }
659 }
660
661 fn withdraw_unlocked(
668 &mut self,
669 current_era: EraIndex,
670 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
671 let mut removed_points =
673 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
674 self.unbonding_eras.retain(|e, p| {
675 if *e > current_era {
676 true
677 } else {
678 removed_points
679 .try_insert(*e, *p)
680 .expect("source map is bounded, this is a subset, will be bounded; qed");
681 false
682 }
683 });
684 removed_points
685 }
686}
687
688#[derive(
690 Encode,
691 Decode,
692 DecodeWithMemTracking,
693 MaxEncodedLen,
694 TypeInfo,
695 PartialEq,
696 DebugNoBound,
697 Clone,
698 Copy,
699)]
700pub enum PoolState {
701 Open,
703 Blocked,
705 Destroying,
710}
711
712#[derive(
718 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone,
719)]
720pub struct PoolRoles<AccountId> {
721 pub depositor: AccountId,
724 pub root: Option<AccountId>,
727 pub nominator: Option<AccountId>,
729 pub bouncer: Option<AccountId>,
731}
732
733#[derive(
735 PartialEq,
736 Eq,
737 Copy,
738 Clone,
739 Encode,
740 Decode,
741 DecodeWithMemTracking,
742 Debug,
743 TypeInfo,
744 MaxEncodedLen,
745)]
746pub enum CommissionClaimPermission<AccountId> {
747 Permissionless,
748 Account(AccountId),
749}
750
751#[derive(
764 Encode,
765 Decode,
766 DecodeWithMemTracking,
767 DefaultNoBound,
768 MaxEncodedLen,
769 TypeInfo,
770 DebugNoBound,
771 PartialEq,
772 Copy,
773 Clone,
774)]
775#[codec(mel_bound(T: Config))]
776#[scale_info(skip_type_params(T))]
777pub struct Commission<T: Config> {
778 pub current: Option<(Perbill, T::AccountId)>,
780 pub max: Option<Perbill>,
783 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
786 pub throttle_from: Option<BlockNumberFor<T>>,
789 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
792}
793
794impl<T: Config> Commission<T> {
795 fn throttling(&self, to: &Perbill) -> bool {
802 if let Some(t) = self.change_rate.as_ref() {
803 let commission_as_percent =
804 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
805
806 if *to <= commission_as_percent {
808 return false;
809 }
810 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
814 return true;
815 }
816
817 return self.throttle_from.map_or_else(
822 || {
823 defensive!("throttle_from should exist if change_rate is set");
824 true
825 },
826 |f| {
827 if t.min_delay == Zero::zero() {
829 false
830 } else {
831 let blocks_surpassed =
833 T::BlockNumberProvider::current_block_number().saturating_sub(f);
834 blocks_surpassed < t.min_delay
835 }
836 },
837 );
838 }
839 false
840 }
841
842 fn current(&self) -> Perbill {
845 self.current
846 .as_ref()
847 .map_or(Perbill::zero(), |(c, _)| *c)
848 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
849 }
850
851 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
857 self.current = match current {
858 None => None,
859 Some((commission, payee)) => {
860 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
861 ensure!(
862 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
863 Error::<T>::CommissionExceedsGlobalMaximum
864 );
865 ensure!(
866 self.max.map_or(true, |m| commission <= &m),
867 Error::<T>::CommissionExceedsMaximum
868 );
869 if commission.is_zero() {
870 None
871 } else {
872 Some((*commission, payee.clone()))
873 }
874 },
875 };
876 self.register_update();
877 Ok(())
878 }
879
880 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
889 ensure!(
890 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
891 Error::<T>::CommissionExceedsGlobalMaximum
892 );
893 if let Some(old) = self.max.as_mut() {
894 if new_max > *old {
895 return Err(Error::<T>::MaxCommissionRestricted.into());
896 }
897 *old = new_max;
898 } else {
899 self.max = Some(new_max)
900 };
901 let updated_current = self
902 .current
903 .as_mut()
904 .map(|(c, _)| {
905 let u = *c > new_max;
906 *c = (*c).min(new_max);
907 u
908 })
909 .unwrap_or(false);
910
911 if updated_current {
912 if let Some((_, payee)) = self.current.as_ref() {
913 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
914 pool_id,
915 current: Some((new_max, payee.clone())),
916 });
917 }
918 self.register_update();
919 }
920 Ok(())
921 }
922
923 fn try_update_change_rate(
932 &mut self,
933 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
934 ) -> DispatchResult {
935 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
936
937 if self.change_rate.is_none() {
938 self.register_update();
939 }
940 self.change_rate = Some(change_rate);
941 Ok(())
942 }
943
944 fn register_update(&mut self) {
946 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
947 }
948
949 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
954 self.change_rate
955 .as_ref()
956 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
957 .unwrap_or(false)
958 }
959}
960
961#[derive(
969 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
970)]
971pub struct CommissionChangeRate<BlockNumber> {
972 pub max_increase: Perbill,
974 pub min_delay: BlockNumber,
976}
977
978#[derive(
980 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
981)]
982#[codec(mel_bound(T: Config))]
983#[scale_info(skip_type_params(T))]
984pub struct BondedPoolInner<T: Config> {
985 pub commission: Commission<T>,
987 pub member_counter: u32,
989 pub points: BalanceOf<T>,
991 pub roles: PoolRoles<T::AccountId>,
993 pub state: PoolState,
995}
996
997#[derive(DebugNoBound)]
1002#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1003pub struct BondedPool<T: Config> {
1004 id: PoolId,
1006 inner: BondedPoolInner<T>,
1008}
1009
1010impl<T: Config> core::ops::Deref for BondedPool<T> {
1011 type Target = BondedPoolInner<T>;
1012 fn deref(&self) -> &Self::Target {
1013 &self.inner
1014 }
1015}
1016
1017impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1018 fn deref_mut(&mut self) -> &mut Self::Target {
1019 &mut self.inner
1020 }
1021}
1022
1023impl<T: Config> BondedPool<T> {
1024 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1026 Self {
1027 id,
1028 inner: BondedPoolInner {
1029 commission: Commission::default(),
1030 member_counter: Zero::zero(),
1031 points: Zero::zero(),
1032 roles,
1033 state: PoolState::Open,
1034 },
1035 }
1036 }
1037
1038 pub fn get(id: PoolId) -> Option<Self> {
1040 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1041 }
1042
1043 fn bonded_account(&self) -> T::AccountId {
1045 Pallet::<T>::generate_bonded_account(self.id)
1046 }
1047
1048 fn reward_account(&self) -> T::AccountId {
1050 Pallet::<T>::generate_reward_account(self.id)
1051 }
1052
1053 fn put(self) {
1055 BondedPools::<T>::insert(self.id, self.inner);
1056 }
1057
1058 fn remove(self) {
1060 BondedPools::<T>::remove(self.id);
1061 }
1062
1063 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1067 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1068 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1069 }
1070
1071 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1075 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1076 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1077 }
1078
1079 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1081 let points_to_issue = self.balance_to_point(new_funds);
1082 self.points = self.points.saturating_add(points_to_issue);
1083 points_to_issue
1084 }
1085
1086 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1093 let balance = self.points_to_balance(points);
1096 self.points = self.points.saturating_sub(points);
1097 balance
1098 }
1099
1100 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1103 ensure!(
1104 MaxPoolMembersPerPool::<T>::get()
1105 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1106 Error::<T>::MaxPoolMembers
1107 );
1108 ensure!(
1109 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1110 Error::<T>::MaxPoolMembers
1111 );
1112 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1113 Ok(())
1114 }
1115
1116 fn dec_members(mut self) -> Self {
1118 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1119 self
1120 }
1121
1122 fn is_root(&self, who: &T::AccountId) -> bool {
1123 self.roles.root.as_ref().map_or(false, |root| root == who)
1124 }
1125
1126 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1127 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1128 }
1129
1130 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1131 self.is_root(who)
1132 }
1133
1134 fn can_nominate(&self, who: &T::AccountId) -> bool {
1135 self.is_root(who) ||
1136 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1137 }
1138
1139 fn can_kick(&self, who: &T::AccountId) -> bool {
1140 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1141 }
1142
1143 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1144 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1145 }
1146
1147 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1148 self.is_root(who) || self.is_bouncer(who)
1149 }
1150
1151 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1152 self.is_root(who)
1153 }
1154
1155 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1156 if let Some(permission) = self.commission.claim_permission.as_ref() {
1157 match permission {
1158 CommissionClaimPermission::Permissionless => true,
1159 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1160 }
1161 } else {
1162 self.is_root(who)
1163 }
1164 }
1165
1166 fn is_destroying(&self) -> bool {
1167 matches!(self.state, PoolState::Destroying)
1168 }
1169
1170 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1171 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1178 }
1179
1180 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1183 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1184
1185 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1186 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1187
1188 let points_to_balance_ratio_floor = self
1189 .points
1190 .div(bonded_balance);
1192
1193 let max_points_to_balance = T::MaxPointsToBalance::get();
1194
1195 ensure!(
1199 points_to_balance_ratio_floor < max_points_to_balance.into(),
1200 Error::<T>::OverflowRisk
1201 );
1202
1203 Ok(())
1207 }
1208
1209 fn ok_to_join(&self) -> Result<(), DispatchError> {
1211 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1212 self.ok_to_be_open()?;
1213 Ok(())
1214 }
1215
1216 fn ok_to_unbond_with(
1217 &self,
1218 caller: &T::AccountId,
1219 target_account: &T::AccountId,
1220 target_member: &PoolMember<T>,
1221 unbonding_points: BalanceOf<T>,
1222 ) -> Result<(), DispatchError> {
1223 let is_permissioned = caller == target_account;
1224 let is_depositor = *target_account == self.roles.depositor;
1225 let is_full_unbond = unbonding_points == target_member.active_points();
1226
1227 let balance_after_unbond = {
1228 let new_depositor_points =
1229 target_member.active_points().saturating_sub(unbonding_points);
1230 let mut target_member_after_unbond = (*target_member).clone();
1231 target_member_after_unbond.points = new_depositor_points;
1232 target_member_after_unbond.active_balance()
1233 };
1234
1235 ensure!(
1237 is_permissioned || is_full_unbond,
1238 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1239 );
1240
1241 ensure!(
1243 is_full_unbond ||
1244 balance_after_unbond >=
1245 if is_depositor {
1246 Pallet::<T>::depositor_min_bond()
1247 } else {
1248 MinJoinBond::<T>::get()
1249 },
1250 Error::<T>::MinimumBondNotMet
1251 );
1252
1253 match (is_permissioned, is_depositor) {
1255 (true, false) => (),
1256 (true, true) => {
1257 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1260 } else {
1262 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1264 }
1265 },
1266 (false, false) => {
1267 debug_assert!(is_full_unbond);
1270 ensure!(
1271 self.can_kick(caller) || self.is_destroying(),
1272 Error::<T>::NotKickerOrDestroying
1273 )
1274 },
1275 (false, true) => {
1276 debug_assert!(is_full_unbond);
1280 ensure!(
1281 self.is_destroying_and_only_depositor(target_member.active_points()),
1282 Error::<T>::DoesNotHavePermission
1283 );
1284 },
1285 };
1286
1287 Ok(())
1288 }
1289
1290 fn ok_to_withdraw_unbonded_with(
1294 &self,
1295 caller: &T::AccountId,
1296 target_account: &T::AccountId,
1297 ) -> Result<(), DispatchError> {
1298 let is_permissioned = caller == target_account;
1300 ensure!(
1301 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1302 Error::<T>::NotKickerOrDestroying
1303 );
1304 Ok(())
1305 }
1306
1307 fn try_bond_funds(
1315 &mut self,
1316 who: &T::AccountId,
1317 amount: BalanceOf<T>,
1318 ty: BondType,
1319 ) -> Result<BalanceOf<T>, DispatchError> {
1320 let points_issued = self.issue(amount);
1323
1324 T::StakeAdapter::pledge_bond(
1325 Member::from(who.clone()),
1326 Pool::from(self.bonded_account()),
1327 &self.reward_account(),
1328 amount,
1329 ty,
1330 )?;
1331 TotalValueLocked::<T>::mutate(|tvl| {
1332 tvl.saturating_accrue(amount);
1333 });
1334
1335 Ok(points_issued)
1336 }
1337
1338 fn set_state(&mut self, state: PoolState) {
1341 if self.state != state {
1342 self.state = state;
1343 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1344 pool_id: self.id,
1345 new_state: state,
1346 });
1347 };
1348 }
1349}
1350
1351#[derive(
1357 Encode,
1358 Decode,
1359 MaxEncodedLen,
1360 DecodeWithMemTracking,
1361 TypeInfo,
1362 CloneNoBound,
1363 PartialEqNoBound,
1364 EqNoBound,
1365 DebugNoBound,
1366)]
1367#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1368#[codec(mel_bound(T: Config))]
1369#[scale_info(skip_type_params(T))]
1370pub struct RewardPool<T: Config> {
1371 pub last_recorded_reward_counter: T::RewardCounter,
1376 pub last_recorded_total_payouts: BalanceOf<T>,
1382 pub total_rewards_claimed: BalanceOf<T>,
1384 pub total_commission_pending: BalanceOf<T>,
1386 pub total_commission_claimed: BalanceOf<T>,
1388}
1389
1390impl<T: Config> RewardPool<T> {
1391 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1393 self.last_recorded_reward_counter
1394 }
1395
1396 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1398 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1399 }
1400
1401 fn update_records(
1408 &mut self,
1409 id: PoolId,
1410 bonded_points: BalanceOf<T>,
1411 commission: Perbill,
1412 ) -> Result<(), Error<T>> {
1413 let balance = Self::current_balance(id);
1414
1415 let (current_reward_counter, new_pending_commission) =
1416 self.current_reward_counter(id, bonded_points, commission)?;
1417
1418 self.last_recorded_reward_counter = current_reward_counter;
1422
1423 self.total_commission_pending =
1426 self.total_commission_pending.saturating_add(new_pending_commission);
1427
1428 let last_recorded_total_payouts = balance
1432 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1433 .ok_or(Error::<T>::OverflowRisk)?;
1434
1435 self.last_recorded_total_payouts =
1442 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1443
1444 Ok(())
1445 }
1446
1447 fn current_reward_counter(
1450 &self,
1451 id: PoolId,
1452 bonded_points: BalanceOf<T>,
1453 commission: Perbill,
1454 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1455 let balance = Self::current_balance(id);
1456
1457 let current_payout_balance = balance
1462 .saturating_add(self.total_rewards_claimed)
1463 .saturating_add(self.total_commission_claimed)
1464 .saturating_sub(self.last_recorded_total_payouts);
1465
1466 let new_pending_commission = commission * current_payout_balance;
1469 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1470
1471 let current_reward_counter =
1506 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1507 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1508 .ok_or(Error::<T>::OverflowRisk)?;
1509
1510 Ok((current_reward_counter, new_pending_commission))
1511 }
1512
1513 fn current_balance(id: PoolId) -> BalanceOf<T> {
1517 T::Currency::reducible_balance(
1518 &Pallet::<T>::generate_reward_account(id),
1519 Preservation::Expendable,
1520 Fortitude::Polite,
1521 )
1522 }
1523}
1524
1525#[derive(
1527 Encode,
1528 Decode,
1529 MaxEncodedLen,
1530 DecodeWithMemTracking,
1531 TypeInfo,
1532 DefaultNoBound,
1533 DebugNoBound,
1534 CloneNoBound,
1535 PartialEqNoBound,
1536 EqNoBound,
1537)]
1538#[codec(mel_bound(T: Config))]
1539#[scale_info(skip_type_params(T))]
1540pub struct UnbondPool<T: Config> {
1541 pub points: BalanceOf<T>,
1543 pub balance: BalanceOf<T>,
1545}
1546
1547impl<T: Config> UnbondPool<T> {
1548 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1549 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1550 }
1551
1552 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1553 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1554 }
1555
1556 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1560 let new_points = self.balance_to_point(new_funds);
1561 self.points = self.points.saturating_add(new_points);
1562 self.balance = self.balance.saturating_add(new_funds);
1563 new_points
1564 }
1565
1566 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1571 let balance_to_unbond = self.point_to_balance(points);
1572 self.points = self.points.saturating_sub(points);
1573 self.balance = self.balance.saturating_sub(balance_to_unbond);
1574
1575 balance_to_unbond
1576 }
1577}
1578
1579#[derive(
1580 Encode,
1581 Decode,
1582 MaxEncodedLen,
1583 DecodeWithMemTracking,
1584 TypeInfo,
1585 DefaultNoBound,
1586 DebugNoBound,
1587 CloneNoBound,
1588 PartialEqNoBound,
1589 EqNoBound,
1590)]
1591#[codec(mel_bound(T: Config))]
1592#[scale_info(skip_type_params(T))]
1593pub struct SubPools<T: Config> {
1594 pub no_era: UnbondPool<T>,
1598 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1600}
1601
1602impl<T: Config> SubPools<T> {
1603 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1608 if let Some(newest_era_to_remove) =
1612 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1613 {
1614 self.with_era.retain(|k, v| {
1615 if *k > newest_era_to_remove {
1616 true
1618 } else {
1619 self.no_era.points = self.no_era.points.saturating_add(v.points);
1621 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1622 false
1623 }
1624 });
1625 }
1626
1627 self
1628 }
1629
1630 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1632 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1633 self.no_era.balance.saturating_add(
1634 self.with_era
1635 .values()
1636 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1637 )
1638 }
1639}
1640
1641pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1645
1646impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1647 fn get() -> u32 {
1648 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1652 }
1653}
1654
1655#[frame_support::pallet]
1656pub mod pallet {
1657 use super::*;
1658 use frame_support::traits::StorageVersion;
1659 use frame_system::pallet_prelude::{
1660 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1661 };
1662 use sp_runtime::Perbill;
1663
1664 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1666
1667 #[pallet::pallet]
1668 #[pallet::storage_version(STORAGE_VERSION)]
1669 pub struct Pallet<T>(_);
1670
1671 #[pallet::config]
1672 pub trait Config: frame_system::Config {
1673 #[allow(deprecated)]
1675 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1676
1677 type WeightInfo: weights::WeightInfo;
1679
1680 type Currency: Mutate<Self::AccountId>
1682 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1683
1684 type RuntimeFreezeReason: From<FreezeReason>;
1686
1687 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1700
1701 #[pallet::constant]
1703 type PalletId: Get<frame_support::PalletId>;
1704
1705 #[pallet::constant]
1718 type MaxPointsToBalance: Get<u8>;
1719
1720 #[pallet::constant]
1722 type MaxUnbonding: Get<u32>;
1723
1724 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1726
1727 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1729
1730 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1734
1735 type PostUnbondingPoolsWindow: Get<u32>;
1741
1742 type MaxMetadataLen: Get<u32>;
1744
1745 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1747
1748 type BlockNumberProvider: BlockNumberProvider;
1750
1751 type Filter: Contains<Self::AccountId>;
1753 }
1754
1755 #[pallet::storage]
1761 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1762
1763 #[pallet::storage]
1765 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1766
1767 #[pallet::storage]
1775 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1776
1777 #[pallet::storage]
1780 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1781
1782 #[pallet::storage]
1785 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1786
1787 #[pallet::storage]
1790 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1791
1792 #[pallet::storage]
1796 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1797
1798 #[pallet::storage]
1802 pub type PoolMembers<T: Config> =
1803 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1804
1805 #[pallet::storage]
1808 pub type BondedPools<T: Config> =
1809 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1810
1811 #[pallet::storage]
1814 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1815
1816 #[pallet::storage]
1819 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1820
1821 #[pallet::storage]
1823 pub type Metadata<T: Config> =
1824 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1825
1826 #[pallet::storage]
1828 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1829
1830 #[pallet::storage]
1835 pub type ReversePoolIdLookup<T: Config> =
1836 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1837
1838 #[pallet::storage]
1840 pub type ClaimPermissions<T: Config> =
1841 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1842
1843 #[pallet::genesis_config]
1844 pub struct GenesisConfig<T: Config> {
1845 pub min_join_bond: BalanceOf<T>,
1846 pub min_create_bond: BalanceOf<T>,
1847 pub max_pools: Option<u32>,
1848 pub max_members_per_pool: Option<u32>,
1849 pub max_members: Option<u32>,
1850 pub global_max_commission: Option<Perbill>,
1851 }
1852
1853 impl<T: Config> Default for GenesisConfig<T> {
1854 fn default() -> Self {
1855 Self {
1856 min_join_bond: Zero::zero(),
1857 min_create_bond: Zero::zero(),
1858 max_pools: Some(16),
1859 max_members_per_pool: Some(32),
1860 max_members: Some(16 * 32),
1861 global_max_commission: None,
1862 }
1863 }
1864 }
1865
1866 #[pallet::genesis_build]
1867 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1868 fn build(&self) {
1869 MinJoinBond::<T>::put(self.min_join_bond);
1870 MinCreateBond::<T>::put(self.min_create_bond);
1871
1872 if let Some(max_pools) = self.max_pools {
1873 MaxPools::<T>::put(max_pools);
1874 }
1875 if let Some(max_members_per_pool) = self.max_members_per_pool {
1876 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1877 }
1878 if let Some(max_members) = self.max_members {
1879 MaxPoolMembers::<T>::put(max_members);
1880 }
1881 if let Some(global_max_commission) = self.global_max_commission {
1882 GlobalMaxCommission::<T>::put(global_max_commission);
1883 }
1884 }
1885 }
1886
1887 #[pallet::event]
1889 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1890 pub enum Event<T: Config> {
1891 Created { depositor: T::AccountId, pool_id: PoolId },
1893 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1895 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1897 Unbonded {
1909 member: T::AccountId,
1910 pool_id: PoolId,
1911 balance: BalanceOf<T>,
1912 points: BalanceOf<T>,
1913 era: EraIndex,
1914 },
1915 Withdrawn {
1922 member: T::AccountId,
1923 pool_id: PoolId,
1924 balance: BalanceOf<T>,
1925 points: BalanceOf<T>,
1926 },
1927 Destroyed { pool_id: PoolId },
1929 StateChanged { pool_id: PoolId, new_state: PoolState },
1931 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1937 RolesUpdated {
1940 root: Option<T::AccountId>,
1941 bouncer: Option<T::AccountId>,
1942 nominator: Option<T::AccountId>,
1943 },
1944 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1946 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1948 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1950 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1952 PoolCommissionChangeRateUpdated {
1954 pool_id: PoolId,
1955 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1956 },
1957 PoolCommissionClaimPermissionUpdated {
1959 pool_id: PoolId,
1960 permission: Option<CommissionClaimPermission<T::AccountId>>,
1961 },
1962 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1964 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1966 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1968 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1970 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1972 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1975 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1977 GlobalParamsUpdated {
1979 min_join_bond: BalanceOf<T>,
1980 min_create_bond: BalanceOf<T>,
1981 max_pools: Option<u32>,
1982 max_members: Option<u32>,
1983 max_members_per_pool: Option<u32>,
1984 global_max_commission: Option<Perbill>,
1985 },
1986 }
1987
1988 #[pallet::error]
1989 #[cfg_attr(test, derive(PartialEq))]
1990 pub enum Error<T> {
1991 PoolNotFound,
1993 PoolMemberNotFound,
1995 RewardPoolNotFound,
1997 SubPoolsNotFound,
1999 AccountBelongsToOtherPool,
2002 FullyUnbonding,
2005 MaxUnbondingLimit,
2007 CannotWithdrawAny,
2009 MinimumBondNotMet,
2015 OverflowRisk,
2017 NotDestroying,
2020 NotNominator,
2022 NotKickerOrDestroying,
2024 NotOpen,
2026 MaxPools,
2028 MaxPoolMembers,
2030 CanNotChangeState,
2032 DoesNotHavePermission,
2034 MetadataExceedsMaxLen,
2036 Defensive(DefensiveError),
2039 PartialUnbondNotAllowedPermissionlessly,
2041 MaxCommissionRestricted,
2043 CommissionExceedsMaximum,
2045 CommissionExceedsGlobalMaximum,
2047 CommissionChangeThrottled,
2049 CommissionChangeRateNotAllowed,
2051 NoPendingCommission,
2053 NoCommissionCurrentSet,
2055 PoolIdInUse,
2057 InvalidPoolId,
2059 BondExtraRestricted,
2061 NothingToAdjust,
2063 NothingToSlash,
2065 SlashTooLow,
2067 AlreadyMigrated,
2069 NotMigrated,
2071 NotSupported,
2073 Restricted,
2076 }
2077
2078 #[derive(Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, Debug)]
2079 pub enum DefensiveError {
2080 NotEnoughSpaceInUnbondPool,
2082 PoolNotFound,
2084 RewardPoolNotFound,
2086 SubPoolsNotFound,
2088 BondedStashKilledPrematurely,
2091 DelegationUnsupported,
2093 SlashNotApplied,
2095 }
2096
2097 impl<T> From<DefensiveError> for Error<T> {
2098 fn from(e: DefensiveError) -> Error<T> {
2099 Error::<T>::Defensive(e)
2100 }
2101 }
2102
2103 #[pallet::composite_enum]
2105 pub enum FreezeReason {
2106 #[codec(index = 0)]
2108 PoolMinBalance,
2109 }
2110
2111 #[pallet::call]
2112 impl<T: Config> Pallet<T> {
2113 #[pallet::call_index(0)]
2130 #[pallet::weight(T::WeightInfo::join())]
2131 pub fn join(
2132 origin: OriginFor<T>,
2133 #[pallet::compact] amount: BalanceOf<T>,
2134 pool_id: PoolId,
2135 ) -> DispatchResult {
2136 let who = ensure_signed(origin)?;
2137 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2139
2140 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2142
2143 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2144 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2146
2147 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2148 bonded_pool.ok_to_join()?;
2149
2150 let mut reward_pool = RewardPools::<T>::get(pool_id)
2151 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2152 reward_pool.update_records(
2154 pool_id,
2155 bonded_pool.points,
2156 bonded_pool.commission.current(),
2157 )?;
2158
2159 bonded_pool.try_inc_members()?;
2160 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2161
2162 PoolMembers::insert(
2163 who.clone(),
2164 PoolMember::<T> {
2165 pool_id,
2166 points: points_issued,
2167 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2170 unbonding_eras: Default::default(),
2171 },
2172 );
2173
2174 Self::deposit_event(Event::<T>::Bonded {
2175 member: who,
2176 pool_id,
2177 bonded: amount,
2178 joined: true,
2179 });
2180
2181 bonded_pool.put();
2182 RewardPools::<T>::insert(pool_id, reward_pool);
2183
2184 Ok(())
2185 }
2186
2187 #[pallet::call_index(1)]
2198 #[pallet::weight(
2199 T::WeightInfo::bond_extra_transfer()
2200 .max(T::WeightInfo::bond_extra_other())
2201 )]
2202 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2203 let who = ensure_signed(origin)?;
2204
2205 ensure!(
2207 !Self::api_member_needs_delegate_migration(who.clone()),
2208 Error::<T>::NotMigrated
2209 );
2210
2211 Self::do_bond_extra(who.clone(), who, extra)
2212 }
2213
2214 #[pallet::call_index(2)]
2223 #[pallet::weight(T::WeightInfo::claim_payout())]
2224 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2225 let signer = ensure_signed(origin)?;
2226 ensure!(
2228 !Self::api_member_needs_delegate_migration(signer.clone()),
2229 Error::<T>::NotMigrated
2230 );
2231
2232 Self::do_claim_payout(signer.clone(), signer)
2233 }
2234
2235 #[pallet::call_index(3)]
2267 #[pallet::weight(T::WeightInfo::unbond())]
2268 pub fn unbond(
2269 origin: OriginFor<T>,
2270 member_account: AccountIdLookupOf<T>,
2271 #[pallet::compact] unbonding_points: BalanceOf<T>,
2272 ) -> DispatchResult {
2273 let who = ensure_signed(origin)?;
2274 let member_account = T::Lookup::lookup(member_account)?;
2275 ensure!(
2277 !Self::api_member_needs_delegate_migration(member_account.clone()),
2278 Error::<T>::NotMigrated
2279 );
2280
2281 let (mut member, mut bonded_pool, mut reward_pool) =
2282 Self::get_member_with_pools(&member_account)?;
2283
2284 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2285
2286 reward_pool.update_records(
2290 bonded_pool.id,
2291 bonded_pool.points,
2292 bonded_pool.commission.current(),
2293 )?;
2294 Self::do_reward_payout(
2295 &member_account,
2296 &mut member,
2297 &mut bonded_pool,
2298 &mut reward_pool,
2299 )?;
2300
2301 let active_era = T::StakeAdapter::current_era();
2302 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(active_era);
2303
2304 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2306 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2307
2308 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2310 .unwrap_or_default()
2311 .maybe_merge_pools(active_era);
2312
2313 if !sub_pools.with_era.contains_key(&unbond_era) {
2316 sub_pools
2317 .with_era
2318 .try_insert(unbond_era, UnbondPool::default())
2319 .defensive_map_err::<Error<T>, _>(|_| {
2322 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2323 })?;
2324 }
2325
2326 let points_unbonded = sub_pools
2327 .with_era
2328 .get_mut(&unbond_era)
2329 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2331 .issue(unbonding_balance);
2332
2333 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2335
2336 Self::deposit_event(Event::<T>::Unbonded {
2337 member: member_account.clone(),
2338 pool_id: member.pool_id,
2339 points: points_unbonded,
2340 balance: unbonding_balance,
2341 era: unbond_era,
2342 });
2343
2344 SubPoolsStorage::insert(member.pool_id, sub_pools);
2346 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2347 Ok(())
2348 }
2349
2350 #[pallet::call_index(4)]
2357 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2358 pub fn pool_withdraw_unbonded(
2359 origin: OriginFor<T>,
2360 pool_id: PoolId,
2361 num_slashing_spans: u32,
2362 ) -> DispatchResult {
2363 ensure_signed(origin)?;
2364 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2366
2367 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2368
2369 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2372 T::StakeAdapter::withdraw_unbonded(
2373 Pool::from(pool.bonded_account()),
2374 num_slashing_spans,
2375 )?;
2376
2377 Ok(())
2378 }
2379
2380 #[pallet::call_index(5)]
2403 #[pallet::weight(
2404 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2405 )]
2406 pub fn withdraw_unbonded(
2407 origin: OriginFor<T>,
2408 member_account: AccountIdLookupOf<T>,
2409 num_slashing_spans: u32,
2410 ) -> DispatchResultWithPostInfo {
2411 let caller = ensure_signed(origin)?;
2412 let member_account = T::Lookup::lookup(member_account)?;
2413 ensure!(
2415 !Self::api_member_needs_delegate_migration(member_account.clone()),
2416 Error::<T>::NotMigrated
2417 );
2418
2419 let mut member =
2420 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2421 let active_era = T::StakeAdapter::current_era();
2422
2423 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2424 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2425 let mut sub_pools =
2426 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2427
2428 let slash_weight =
2429 match Self::do_apply_slash(&member_account, None, false) {
2431 Ok(_) => T::WeightInfo::apply_slash(),
2432 Err(e) => {
2433 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2434 if Err(e) == no_pending_slash {
2436 T::WeightInfo::apply_slash_fail()
2437 } else {
2438 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2440 }
2441 }
2442
2443 };
2444
2445 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2446 let pool_account = bonded_pool.bonded_account();
2447
2448 let withdrawn_points = member.withdraw_unlocked(active_era);
2450 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2451
2452 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2455 Pool::from(bonded_pool.bonded_account()),
2456 num_slashing_spans,
2457 )?;
2458
2459 ensure!(
2462 !stash_killed || caller == bonded_pool.roles.depositor,
2463 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2464 );
2465
2466 if stash_killed {
2467 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2469 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2470 }
2471
2472 }
2479
2480 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2481 let balance_to_unbond = withdrawn_points
2482 .iter()
2483 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2484 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2485 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2486 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2487 if era_pool.points.is_zero() {
2488 sub_pools.with_era.remove(era);
2489 }
2490 accumulator.saturating_add(balance_to_unbond)
2491 } else {
2492 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2495 }
2496 })
2497 .min(T::StakeAdapter::transferable_balance(
2505 Pool::from(bonded_pool.bonded_account()),
2506 Member::from(member_account.clone()),
2507 ));
2508
2509 T::StakeAdapter::member_withdraw(
2512 Member::from(member_account.clone()),
2513 Pool::from(bonded_pool.bonded_account()),
2514 balance_to_unbond,
2515 num_slashing_spans,
2516 )?;
2517
2518 Self::deposit_event(Event::<T>::Withdrawn {
2519 member: member_account.clone(),
2520 pool_id: member.pool_id,
2521 points: sum_unlocked_points,
2522 balance: balance_to_unbond,
2523 });
2524
2525 let post_info_weight = if member.total_points().is_zero() {
2526 ClaimPermissions::<T>::remove(&member_account);
2528
2529 PoolMembers::<T>::remove(&member_account);
2531
2532 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2534 Member::from(member_account.clone()),
2535 ) {
2536 Some(dangling_delegation) => {
2537 T::StakeAdapter::member_withdraw(
2538 Member::from(member_account.clone()),
2539 Pool::from(bonded_pool.bonded_account()),
2540 dangling_delegation,
2541 num_slashing_spans,
2542 )?;
2543 dangling_delegation
2544 },
2545 None => Zero::zero(),
2546 };
2547
2548 Self::deposit_event(Event::<T>::MemberRemoved {
2549 pool_id: member.pool_id,
2550 member: member_account.clone(),
2551 released_balance: dangling_withdrawal,
2552 });
2553
2554 if member_account == bonded_pool.roles.depositor {
2555 Pallet::<T>::dissolve_pool(bonded_pool);
2556 Weight::default()
2557 } else {
2558 bonded_pool.dec_members().put();
2559 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2560 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2561 }
2562 } else {
2563 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2565 PoolMembers::<T>::insert(&member_account, member);
2566 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2567 };
2568
2569 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2570 }
2571
2572 #[pallet::call_index(6)]
2590 #[pallet::weight(T::WeightInfo::create())]
2591 pub fn create(
2592 origin: OriginFor<T>,
2593 #[pallet::compact] amount: BalanceOf<T>,
2594 root: AccountIdLookupOf<T>,
2595 nominator: AccountIdLookupOf<T>,
2596 bouncer: AccountIdLookupOf<T>,
2597 ) -> DispatchResult {
2598 let depositor = ensure_signed(origin)?;
2599
2600 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2601 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2602 Ok(*id)
2603 })?;
2604
2605 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2606 }
2607
2608 #[pallet::call_index(7)]
2615 #[pallet::weight(T::WeightInfo::create())]
2616 pub fn create_with_pool_id(
2617 origin: OriginFor<T>,
2618 #[pallet::compact] amount: BalanceOf<T>,
2619 root: AccountIdLookupOf<T>,
2620 nominator: AccountIdLookupOf<T>,
2621 bouncer: AccountIdLookupOf<T>,
2622 pool_id: PoolId,
2623 ) -> DispatchResult {
2624 let depositor = ensure_signed(origin)?;
2625
2626 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2627 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2628
2629 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2630 }
2631
2632 #[pallet::call_index(8)]
2645 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2646 pub fn nominate(
2647 origin: OriginFor<T>,
2648 pool_id: PoolId,
2649 validators: Vec<T::AccountId>,
2650 ) -> DispatchResult {
2651 let who = ensure_signed(origin)?;
2652 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2653 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2655 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2656
2657 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2658 .ok_or(Error::<T>::PoolMemberNotFound)?
2659 .active_points();
2660
2661 ensure!(
2662 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2663 Error::<T>::MinimumBondNotMet
2664 );
2665
2666 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2667 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2668 )
2669 }
2670
2671 #[pallet::call_index(9)]
2682 #[pallet::weight(T::WeightInfo::set_state())]
2683 pub fn set_state(
2684 origin: OriginFor<T>,
2685 pool_id: PoolId,
2686 state: PoolState,
2687 ) -> DispatchResult {
2688 let who = ensure_signed(origin)?;
2689 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2690 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2691 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2693
2694 if bonded_pool.can_toggle_state(&who) {
2695 bonded_pool.set_state(state);
2696 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2697 bonded_pool.set_state(PoolState::Destroying);
2699 } else {
2700 Err(Error::<T>::CanNotChangeState)?;
2701 }
2702
2703 bonded_pool.put();
2704
2705 Ok(())
2706 }
2707
2708 #[pallet::call_index(10)]
2713 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2714 pub fn set_metadata(
2715 origin: OriginFor<T>,
2716 pool_id: PoolId,
2717 metadata: Vec<u8>,
2718 ) -> DispatchResult {
2719 let who = ensure_signed(origin)?;
2720 let metadata: BoundedVec<_, _> =
2721 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2722 ensure!(
2723 BondedPool::<T>::get(pool_id)
2724 .ok_or(Error::<T>::PoolNotFound)?
2725 .can_set_metadata(&who),
2726 Error::<T>::DoesNotHavePermission
2727 );
2728 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2730
2731 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2732
2733 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2734
2735 Ok(())
2736 }
2737
2738 #[pallet::call_index(11)]
2750 #[pallet::weight(T::WeightInfo::set_configs())]
2751 pub fn set_configs(
2752 origin: OriginFor<T>,
2753 min_join_bond: ConfigOp<BalanceOf<T>>,
2754 min_create_bond: ConfigOp<BalanceOf<T>>,
2755 max_pools: ConfigOp<u32>,
2756 max_members: ConfigOp<u32>,
2757 max_members_per_pool: ConfigOp<u32>,
2758 global_max_commission: ConfigOp<Perbill>,
2759 ) -> DispatchResult {
2760 T::AdminOrigin::ensure_origin(origin)?;
2761
2762 macro_rules! config_op_exp {
2763 ($storage:ty, $op:ident) => {
2764 match $op {
2765 ConfigOp::Noop => (),
2766 ConfigOp::Set(v) => <$storage>::put(v),
2767 ConfigOp::Remove => <$storage>::kill(),
2768 }
2769 };
2770 }
2771
2772 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2773 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2774 config_op_exp!(MaxPools::<T>, max_pools);
2775 config_op_exp!(MaxPoolMembers::<T>, max_members);
2776 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2777 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2778
2779 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2780 min_join_bond: MinJoinBond::<T>::get(),
2781 min_create_bond: MinCreateBond::<T>::get(),
2782 max_pools: MaxPools::<T>::get(),
2783 max_members: MaxPoolMembers::<T>::get(),
2784 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2785 global_max_commission: GlobalMaxCommission::<T>::get(),
2786 });
2787
2788 Ok(())
2789 }
2790
2791 #[pallet::call_index(12)]
2799 #[pallet::weight(T::WeightInfo::update_roles())]
2800 pub fn update_roles(
2801 origin: OriginFor<T>,
2802 pool_id: PoolId,
2803 new_root: ConfigOp<T::AccountId>,
2804 new_nominator: ConfigOp<T::AccountId>,
2805 new_bouncer: ConfigOp<T::AccountId>,
2806 ) -> DispatchResult {
2807 let mut bonded_pool = match ensure_root(origin.clone()) {
2808 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2809 Err(sp_runtime::traits::BadOrigin) => {
2810 let who = ensure_signed(origin)?;
2811 let bonded_pool =
2812 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2813 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2814 bonded_pool
2815 },
2816 };
2817
2818 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2820
2821 match new_root {
2822 ConfigOp::Noop => (),
2823 ConfigOp::Remove => bonded_pool.roles.root = None,
2824 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2825 };
2826 match new_nominator {
2827 ConfigOp::Noop => (),
2828 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2829 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2830 };
2831 match new_bouncer {
2832 ConfigOp::Noop => (),
2833 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2834 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2835 };
2836
2837 Self::deposit_event(Event::<T>::RolesUpdated {
2838 root: bonded_pool.roles.root.clone(),
2839 nominator: bonded_pool.roles.nominator.clone(),
2840 bouncer: bonded_pool.roles.bouncer.clone(),
2841 });
2842
2843 bonded_pool.put();
2844 Ok(())
2845 }
2846
2847 #[pallet::call_index(13)]
2865 #[pallet::weight(T::WeightInfo::chill())]
2866 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2867 let who = ensure_signed(origin)?;
2868 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2869 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2871
2872 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2873 .ok_or(Error::<T>::PoolMemberNotFound)?
2874 .active_points();
2875
2876 if bonded_pool.points_to_balance(depositor_points) >=
2877 T::StakeAdapter::minimum_nominator_bond()
2878 {
2879 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2880 }
2881
2882 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2883 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2884 })
2885 }
2886
2887 #[pallet::call_index(14)]
2897 #[pallet::weight(
2898 T::WeightInfo::bond_extra_transfer()
2899 .max(T::WeightInfo::bond_extra_other())
2900 )]
2901 pub fn bond_extra_other(
2902 origin: OriginFor<T>,
2903 member: AccountIdLookupOf<T>,
2904 extra: BondExtra<BalanceOf<T>>,
2905 ) -> DispatchResult {
2906 let who = ensure_signed(origin)?;
2907 let member_account = T::Lookup::lookup(member)?;
2908 ensure!(
2910 !Self::api_member_needs_delegate_migration(member_account.clone()),
2911 Error::<T>::NotMigrated
2912 );
2913
2914 Self::do_bond_extra(who, member_account, extra)
2915 }
2916
2917 #[pallet::call_index(15)]
2925 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2926 pub fn set_claim_permission(
2927 origin: OriginFor<T>,
2928 permission: ClaimPermission,
2929 ) -> DispatchResult {
2930 let who = ensure_signed(origin)?;
2931 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2932
2933 ensure!(
2935 !Self::api_member_needs_delegate_migration(who.clone()),
2936 Error::<T>::NotMigrated
2937 );
2938
2939 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2940 *source = permission;
2941 });
2942
2943 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2944 member: who,
2945 permission,
2946 });
2947
2948 Ok(())
2949 }
2950
2951 #[pallet::call_index(16)]
2956 #[pallet::weight(T::WeightInfo::claim_payout())]
2957 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2958 let signer = ensure_signed(origin)?;
2959 ensure!(
2961 !Self::api_member_needs_delegate_migration(other.clone()),
2962 Error::<T>::NotMigrated
2963 );
2964
2965 Self::do_claim_payout(signer, other)
2966 }
2967
2968 #[pallet::call_index(17)]
2975 #[pallet::weight(T::WeightInfo::set_commission())]
2976 pub fn set_commission(
2977 origin: OriginFor<T>,
2978 pool_id: PoolId,
2979 new_commission: Option<(Perbill, T::AccountId)>,
2980 ) -> DispatchResult {
2981 let who = ensure_signed(origin)?;
2982 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2983 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2985
2986 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2987
2988 let mut reward_pool = RewardPools::<T>::get(pool_id)
2989 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2990 reward_pool.update_records(
2993 pool_id,
2994 bonded_pool.points,
2995 bonded_pool.commission.current(),
2996 )?;
2997 RewardPools::insert(pool_id, reward_pool);
2998
2999 bonded_pool.commission.try_update_current(&new_commission)?;
3000 bonded_pool.put();
3001 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
3002 pool_id,
3003 current: new_commission,
3004 });
3005 Ok(())
3006 }
3007
3008 #[pallet::call_index(18)]
3014 #[pallet::weight(T::WeightInfo::set_commission_max())]
3015 pub fn set_commission_max(
3016 origin: OriginFor<T>,
3017 pool_id: PoolId,
3018 max_commission: Perbill,
3019 ) -> DispatchResult {
3020 let who = ensure_signed(origin)?;
3021 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3022 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3024
3025 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3026
3027 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3028 bonded_pool.put();
3029
3030 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3031 Ok(())
3032 }
3033
3034 #[pallet::call_index(19)]
3039 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3040 pub fn set_commission_change_rate(
3041 origin: OriginFor<T>,
3042 pool_id: PoolId,
3043 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3044 ) -> DispatchResult {
3045 let who = ensure_signed(origin)?;
3046 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3047 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3049 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3050
3051 bonded_pool.commission.try_update_change_rate(change_rate)?;
3052 bonded_pool.put();
3053
3054 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3055 pool_id,
3056 change_rate,
3057 });
3058 Ok(())
3059 }
3060
3061 #[pallet::call_index(20)]
3078 #[pallet::weight(T::WeightInfo::claim_commission())]
3079 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3080 let who = ensure_signed(origin)?;
3081 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3083
3084 Self::do_claim_commission(who, pool_id)
3085 }
3086
3087 #[pallet::call_index(21)]
3095 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3096 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3097 let who = ensure_signed(origin)?;
3098 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3100
3101 Self::do_adjust_pool_deposit(who, pool_id)
3102 }
3103
3104 #[pallet::call_index(22)]
3109 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3110 pub fn set_commission_claim_permission(
3111 origin: OriginFor<T>,
3112 pool_id: PoolId,
3113 permission: Option<CommissionClaimPermission<T::AccountId>>,
3114 ) -> DispatchResult {
3115 let who = ensure_signed(origin)?;
3116 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3117 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3119 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3120
3121 bonded_pool.commission.claim_permission = permission.clone();
3122 bonded_pool.put();
3123
3124 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3125 pool_id,
3126 permission,
3127 });
3128
3129 Ok(())
3130 }
3131
3132 #[pallet::call_index(23)]
3142 #[pallet::weight(T::WeightInfo::apply_slash())]
3143 pub fn apply_slash(
3144 origin: OriginFor<T>,
3145 member_account: AccountIdLookupOf<T>,
3146 ) -> DispatchResultWithPostInfo {
3147 ensure!(
3148 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3149 Error::<T>::NotSupported
3150 );
3151
3152 let who = ensure_signed(origin)?;
3153 let member_account = T::Lookup::lookup(member_account)?;
3154 Self::do_apply_slash(&member_account, Some(who), true)?;
3155
3156 Ok(Pays::No.into())
3158 }
3159
3160 #[pallet::call_index(24)]
3170 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3171 pub fn migrate_delegation(
3172 origin: OriginFor<T>,
3173 member_account: AccountIdLookupOf<T>,
3174 ) -> DispatchResultWithPostInfo {
3175 let _caller = ensure_signed(origin)?;
3176
3177 ensure!(
3179 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3180 Error::<T>::NotSupported
3181 );
3182
3183 let member_account = T::Lookup::lookup(member_account)?;
3185 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3186
3187 let member =
3188 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3189
3190 ensure!(
3192 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3193 member.pool_id
3194 ))) == adapter::StakeStrategyType::Delegate,
3195 Error::<T>::NotMigrated
3196 );
3197
3198 let pool_contribution = member.total_balance();
3199 ensure!(
3202 pool_contribution >= T::Currency::minimum_balance(),
3203 Error::<T>::MinimumBondNotMet
3204 );
3205
3206 let delegation =
3207 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3208 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3210
3211 T::StakeAdapter::migrate_delegation(
3212 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3213 Member::from(member_account),
3214 pool_contribution,
3215 )?;
3216
3217 Ok(Pays::No.into())
3219 }
3220
3221 #[pallet::call_index(25)]
3231 #[pallet::weight(T::WeightInfo::pool_migrate())]
3232 pub fn migrate_pool_to_delegate_stake(
3233 origin: OriginFor<T>,
3234 pool_id: PoolId,
3235 ) -> DispatchResultWithPostInfo {
3236 ensure!(
3238 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3239 Error::<T>::NotSupported
3240 );
3241
3242 let _caller = ensure_signed(origin)?;
3243 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3245 ensure!(
3246 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3247 adapter::StakeStrategyType::Transfer,
3248 Error::<T>::AlreadyMigrated
3249 );
3250
3251 Self::migrate_to_delegate_stake(pool_id)?;
3252 Ok(Pays::No.into())
3253 }
3254 }
3255
3256 #[pallet::hooks]
3257 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3258 #[cfg(feature = "try-runtime")]
3259 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3260 Self::do_try_state(u8::MAX)
3261 }
3262
3263 fn integrity_test() {
3264 assert!(
3265 T::MaxPointsToBalance::get() > 0,
3266 "Minimum points to balance ratio must be greater than 0"
3267 );
3268 assert!(
3269 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3270 "There must be more unbonding pools then the bonding duration /
3271 so a slash can be applied to relevant unbonding pools. (We assume /
3272 the bonding duration > slash deffer duration.",
3273 );
3274 }
3275 }
3276}
3277
3278impl<T: Config> Pallet<T> {
3279 pub fn depositor_min_bond() -> BalanceOf<T> {
3287 T::StakeAdapter::minimum_nominator_bond()
3288 .max(MinCreateBond::<T>::get())
3289 .max(MinJoinBond::<T>::get())
3290 .max(T::Currency::minimum_balance())
3291 }
3292
3293 pub fn do_claim_trapped_balance(member_account: &T::AccountId) -> DispatchResult {
3305 ensure!(
3306 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3307 Error::<T>::NotSupported
3308 );
3309
3310 match Self::do_apply_slash(member_account, None, false) {
3313 Ok(_) => {},
3314 Err(e)
3315 if e == Error::<T>::NothingToSlash.into() ||
3316 e == Error::<T>::PoolMemberNotFound.into() => {},
3317 Err(_) => {
3318 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
3319 },
3320 };
3321
3322 let member = match PoolMembers::<T>::get(member_account) {
3323 Some(m) => m,
3324 None => return Ok(()),
3325 };
3326
3327 let expected_balance = member.total_balance();
3328 let actual_balance =
3329 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()))
3330 .unwrap_or_default();
3331
3332 let trapped_amount = actual_balance.saturating_sub(expected_balance);
3333
3334 if trapped_amount.is_zero() {
3335 return Ok(());
3336 }
3337
3338 T::StakeAdapter::member_withdraw(
3339 Member::from(member_account.clone()),
3340 Pool::from(Self::generate_bonded_account(member.pool_id)),
3341 trapped_amount,
3342 0,
3343 )?;
3344
3345 log!(
3346 info,
3347 "Claimed trapped balance for member {:?}, pool {:?}, amount {:?}",
3348 member_account,
3349 member.pool_id,
3350 trapped_amount
3351 );
3352
3353 Ok(())
3354 }
3355
3356 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3361 let reward_account = bonded_pool.reward_account();
3362 let bonded_account = bonded_pool.bonded_account();
3363
3364 ReversePoolIdLookup::<T>::remove(&bonded_account);
3365 RewardPools::<T>::remove(bonded_pool.id);
3366 SubPoolsStorage::<T>::remove(bonded_pool.id);
3367
3368 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3370
3371 defensive_assert!(
3379 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3380 "reward account of dissolving pool should have no consumers"
3381 );
3382 defensive_assert!(
3383 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3384 "bonded account of dissolving pool should have no consumers"
3385 );
3386 defensive_assert!(
3387 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3388 "dissolving pool should not have any stake in the staking pallet"
3389 );
3390
3391 let reward_pool_remaining = T::Currency::reducible_balance(
3394 &reward_account,
3395 Preservation::Expendable,
3396 Fortitude::Polite,
3397 );
3398 let _ = T::Currency::transfer(
3399 &reward_account,
3400 &bonded_pool.roles.depositor,
3401 reward_pool_remaining,
3402 Preservation::Expendable,
3403 );
3404
3405 defensive_assert!(
3406 T::Currency::total_balance(&reward_account) == Zero::zero(),
3407 "could not transfer all amount to depositor while dissolving pool"
3408 );
3409 T::Currency::set_balance(&reward_account, Zero::zero());
3411
3412 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3414
3415 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3416 Metadata::<T>::remove(bonded_pool.id);
3418
3419 bonded_pool.remove();
3420 }
3421
3422 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3424 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3425 }
3426
3427 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3428 T::StakeAdapter::migrate_nominator_to_agent(
3429 Pool::from(Self::generate_bonded_account(id)),
3430 &Self::generate_reward_account(id),
3431 )
3432 }
3433
3434 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3436 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3439 }
3440
3441 fn get_member_with_pools(
3443 who: &T::AccountId,
3444 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3445 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3446 let bonded_pool =
3447 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3448 let reward_pool =
3449 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3450 Ok((member, bonded_pool, reward_pool))
3451 }
3452
3453 fn put_member_with_pools(
3456 member_account: &T::AccountId,
3457 member: PoolMember<T>,
3458 bonded_pool: BondedPool<T>,
3459 reward_pool: RewardPool<T>,
3460 ) {
3461 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3464 debug_assert_eq!(member.pool_id, bonded_pool.id);
3465
3466 bonded_pool.put();
3467 RewardPools::insert(member.pool_id, reward_pool);
3468 PoolMembers::<T>::insert(member_account, member);
3469 }
3470
3471 fn balance_to_point(
3474 current_balance: BalanceOf<T>,
3475 current_points: BalanceOf<T>,
3476 new_funds: BalanceOf<T>,
3477 ) -> BalanceOf<T> {
3478 let u256 = T::BalanceToU256::convert;
3479 let balance = T::U256ToBalance::convert;
3480 match (current_balance.is_zero(), current_points.is_zero()) {
3481 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3482 (true, false) => {
3483 new_funds.saturating_mul(current_points)
3486 },
3487 (false, false) => {
3488 balance(
3490 u256(current_points)
3491 .saturating_mul(u256(new_funds))
3492 .div(u256(current_balance)),
3494 )
3495 },
3496 }
3497 }
3498
3499 fn point_to_balance(
3502 current_balance: BalanceOf<T>,
3503 current_points: BalanceOf<T>,
3504 points: BalanceOf<T>,
3505 ) -> BalanceOf<T> {
3506 let u256 = T::BalanceToU256::convert;
3507 let balance = T::U256ToBalance::convert;
3508 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3509 return Zero::zero();
3511 }
3512
3513 balance(
3515 u256(current_balance)
3516 .saturating_mul(u256(points))
3517 .div(u256(current_points)),
3519 )
3520 }
3521
3522 fn do_reward_payout(
3526 member_account: &T::AccountId,
3527 member: &mut PoolMember<T>,
3528 bonded_pool: &mut BondedPool<T>,
3529 reward_pool: &mut RewardPool<T>,
3530 ) -> Result<BalanceOf<T>, DispatchError> {
3531 debug_assert_eq!(member.pool_id, bonded_pool.id);
3532 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3533
3534 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3536
3537 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3538 bonded_pool.id,
3539 bonded_pool.points,
3540 bonded_pool.commission.current(),
3541 )?;
3542
3543 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3546 if pending_rewards.is_zero() {
3547 return Ok(pending_rewards);
3548 }
3549
3550 member.last_recorded_reward_counter = current_reward_counter;
3552 reward_pool.register_claimed_reward(pending_rewards);
3553
3554 T::Currency::transfer(
3555 &bonded_pool.reward_account(),
3556 member_account,
3557 pending_rewards,
3558 Preservation::Preserve,
3561 )?;
3562
3563 Self::deposit_event(Event::<T>::PaidOut {
3564 member: member_account.clone(),
3565 pool_id: member.pool_id,
3566 payout: pending_rewards,
3567 });
3568 Ok(pending_rewards)
3569 }
3570
3571 fn do_create(
3572 who: T::AccountId,
3573 amount: BalanceOf<T>,
3574 root: AccountIdLookupOf<T>,
3575 nominator: AccountIdLookupOf<T>,
3576 bouncer: AccountIdLookupOf<T>,
3577 pool_id: PoolId,
3578 ) -> DispatchResult {
3579 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3581
3582 let root = T::Lookup::lookup(root)?;
3583 let nominator = T::Lookup::lookup(nominator)?;
3584 let bouncer = T::Lookup::lookup(bouncer)?;
3585
3586 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3587 ensure!(
3588 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3589 Error::<T>::MaxPools
3590 );
3591 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3592 let mut bonded_pool = BondedPool::<T>::new(
3593 pool_id,
3594 PoolRoles {
3595 root: Some(root),
3596 nominator: Some(nominator),
3597 bouncer: Some(bouncer),
3598 depositor: who.clone(),
3599 },
3600 );
3601
3602 bonded_pool.try_inc_members()?;
3603 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3604
3605 T::Currency::transfer(
3607 &who,
3608 &bonded_pool.reward_account(),
3609 T::Currency::minimum_balance(),
3610 Preservation::Expendable,
3611 )?;
3612
3613 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3615
3616 PoolMembers::<T>::insert(
3617 who.clone(),
3618 PoolMember::<T> {
3619 pool_id,
3620 points,
3621 last_recorded_reward_counter: Zero::zero(),
3622 unbonding_eras: Default::default(),
3623 },
3624 );
3625 RewardPools::<T>::insert(
3626 pool_id,
3627 RewardPool::<T> {
3628 last_recorded_reward_counter: Zero::zero(),
3629 last_recorded_total_payouts: Zero::zero(),
3630 total_rewards_claimed: Zero::zero(),
3631 total_commission_pending: Zero::zero(),
3632 total_commission_claimed: Zero::zero(),
3633 },
3634 );
3635 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3636
3637 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3638
3639 Self::deposit_event(Event::<T>::Bonded {
3640 member: who,
3641 pool_id,
3642 bonded: amount,
3643 joined: true,
3644 });
3645 bonded_pool.put();
3646
3647 Ok(())
3648 }
3649
3650 fn do_bond_extra(
3651 signer: T::AccountId,
3652 member_account: T::AccountId,
3653 extra: BondExtra<BalanceOf<T>>,
3654 ) -> DispatchResult {
3655 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3657
3658 if signer != member_account {
3659 ensure!(
3660 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3661 Error::<T>::DoesNotHavePermission
3662 );
3663 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3664 }
3665
3666 let (mut member, mut bonded_pool, mut reward_pool) =
3667 Self::get_member_with_pools(&member_account)?;
3668
3669 reward_pool.update_records(
3672 bonded_pool.id,
3673 bonded_pool.points,
3674 bonded_pool.commission.current(),
3675 )?;
3676 let claimed = Self::do_reward_payout(
3677 &member_account,
3678 &mut member,
3679 &mut bonded_pool,
3680 &mut reward_pool,
3681 )?;
3682
3683 let (points_issued, bonded) = match extra {
3684 BondExtra::FreeBalance(amount) => {
3685 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount)
3686 },
3687 BondExtra::Rewards => {
3688 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed)
3689 },
3690 };
3691
3692 bonded_pool.ok_to_be_open()?;
3693 member.points =
3694 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3695
3696 Self::deposit_event(Event::<T>::Bonded {
3697 member: member_account.clone(),
3698 pool_id: member.pool_id,
3699 bonded,
3700 joined: false,
3701 });
3702 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3703
3704 Ok(())
3705 }
3706
3707 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3708 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3709 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3710
3711 let mut reward_pool = RewardPools::<T>::get(pool_id)
3712 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3713
3714 reward_pool.update_records(
3717 pool_id,
3718 bonded_pool.points,
3719 bonded_pool.commission.current(),
3720 )?;
3721
3722 let commission = reward_pool.total_commission_pending;
3723 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3724
3725 let payee = bonded_pool
3726 .commission
3727 .current
3728 .as_ref()
3729 .map(|(_, p)| p.clone())
3730 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3731
3732 T::Currency::transfer(
3734 &bonded_pool.reward_account(),
3735 &payee,
3736 commission,
3737 Preservation::Preserve,
3738 )?;
3739
3740 reward_pool.total_commission_claimed =
3742 reward_pool.total_commission_claimed.saturating_add(commission);
3743 reward_pool.total_commission_pending = Zero::zero();
3745 RewardPools::<T>::insert(pool_id, reward_pool);
3746
3747 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3748 Ok(())
3749 }
3750
3751 pub(crate) fn do_claim_payout(
3752 signer: T::AccountId,
3753 member_account: T::AccountId,
3754 ) -> DispatchResult {
3755 if signer != member_account {
3756 ensure!(
3757 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3758 Error::<T>::DoesNotHavePermission
3759 );
3760 }
3761 let (mut member, mut bonded_pool, mut reward_pool) =
3762 Self::get_member_with_pools(&member_account)?;
3763
3764 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3765
3766 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3767 Ok(())
3768 }
3769
3770 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3771 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3772
3773 let reward_acc = &bonded_pool.reward_account();
3774 let pre_frozen_balance =
3775 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3776 let min_balance = T::Currency::minimum_balance();
3777
3778 if pre_frozen_balance == min_balance {
3779 return Err(Error::<T>::NothingToAdjust.into());
3780 }
3781
3782 Self::freeze_pool_deposit(reward_acc)?;
3784
3785 if pre_frozen_balance > min_balance {
3786 ensure!(
3788 who == bonded_pool.roles.depositor ||
3789 bonded_pool.roles.root.as_ref().map_or(false, |root| &who == root),
3790 Error::<T>::DoesNotHavePermission
3791 );
3792
3793 let excess = pre_frozen_balance.saturating_sub(min_balance);
3795
3796 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3797 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3798 pool_id: pool,
3799 amount: excess,
3800 });
3801 } else {
3802 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3804 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3805 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3806 pool_id: pool,
3807 amount: deficit,
3808 });
3809 }
3810
3811 Ok(())
3812 }
3813
3814 fn do_apply_slash(
3816 member_account: &T::AccountId,
3817 reporter: Option<T::AccountId>,
3818 enforce_min_slash: bool,
3819 ) -> DispatchResult {
3820 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3821
3822 let pending_slash =
3823 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3824
3825 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3827
3828 if enforce_min_slash {
3829 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3831 }
3832
3833 T::StakeAdapter::member_slash(
3834 Member::from(member_account.clone()),
3835 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3836 pending_slash,
3837 reporter,
3838 )
3839 }
3840
3841 fn member_pending_slash(
3845 member_account: Member<T::AccountId>,
3846 pool_member: PoolMember<T>,
3847 ) -> Result<BalanceOf<T>, DispatchError> {
3848 debug_assert!(
3850 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3851 pool_member
3852 );
3853
3854 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3855 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3858 return Ok(Zero::zero());
3859 }
3860
3861 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3863 .ok_or(Error::<T>::NotMigrated)?;
3865
3866 let expected_balance = pool_member.total_balance();
3868
3869 Ok(actual_balance.saturating_sub(expected_balance))
3871 }
3872
3873 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3875 T::Currency::set_freeze(
3876 &FreezeReason::PoolMinBalance.into(),
3877 reward_acc,
3878 T::Currency::minimum_balance(),
3879 )
3880 }
3881
3882 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3884 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3885 }
3886
3887 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3924 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3925 if level.is_zero() {
3926 return Ok(());
3927 }
3928 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3931 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3932 ensure!(
3933 bonded_pools == reward_pools,
3934 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3935 );
3936
3937 ensure!(
3938 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3939 "`SubPoolsStorage` must be a subset of the above superset."
3940 );
3941 ensure!(
3942 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3943 "`Metadata` keys must be a subset of the above superset."
3944 );
3945
3946 ensure!(
3947 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3948 Error::<T>::MaxPools
3949 );
3950
3951 for id in reward_pools {
3952 let account = Self::generate_reward_account(id);
3953 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3954 T::Currency::minimum_balance()
3955 {
3956 log!(
3957 warn,
3958 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3959 changed recently. Pool operators should be notified to top up the reward \
3960 account",
3961 id,
3962 T::Currency::reducible_balance(
3963 &account,
3964 Preservation::Expendable,
3965 Fortitude::Polite
3966 ),
3967 T::Currency::minimum_balance(),
3968 )
3969 }
3970 }
3971
3972 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3973 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3974 let mut all_members = 0u32;
3975 let mut total_balance_members = Default::default();
3976 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3977 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3978 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3979 *pools_members.entry(d.pool_id).or_default() += 1;
3980 all_members += 1;
3981
3982 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3983 if !bonded_pool.points.is_zero() {
3984 let commission = bonded_pool.commission.current();
3985 let (current_rc, _) = reward_pool
3986 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3987 .unwrap();
3988 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3989 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3990 } total_balance_members += d.total_balance();
3992
3993 Ok(())
3994 })?;
3995
3996 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3997 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
4000 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
4001
4002 if !pending_rewards_lt_leftover_bal {
4005 log!(
4006 warn,
4007 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
4008 id,
4009 pools_members_pending_rewards.get(&id),
4010 RewardPool::<T>::current_balance(id)
4011 );
4012 }
4013 Ok(())
4014 })?;
4015
4016 let mut expected_tvl: BalanceOf<T> = Default::default();
4017 let mut depositor_undermin: Vec<(PoolId, T::AccountId)> = Vec::new();
4018 let mut depositor_undermin_total: u32 = 0;
4019 let mut total_pools: u32 = 0;
4020 const MAX_EXAMPLES: usize = 10;
4021
4022 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
4023 total_pools += 1;
4024 let bonded_pool = BondedPool { id, inner };
4025 ensure!(
4026 pools_members.get(&id).copied().unwrap_or_default() ==
4027 bonded_pool.member_counter,
4028 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
4029 );
4030 ensure!(
4031 MaxPoolMembersPerPool::<T>::get()
4032 .map_or(true, |max| bonded_pool.member_counter <= max),
4033 Error::<T>::MaxPoolMembers
4034 );
4035
4036 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
4037 let depositor_has_enough_stake = bonded_pool
4038 .is_destroying_and_only_depositor(depositor.active_points()) ||
4039 depositor.active_points() >= MinCreateBond::<T>::get();
4040 if !depositor_has_enough_stake {
4041 depositor_undermin_total += 1;
4042 if depositor_undermin.len() < MAX_EXAMPLES {
4043 depositor_undermin.push((id, bonded_pool.roles.depositor.clone()));
4044 }
4045 log!(
4046 trace,
4047 "pool {:?} has depositor {:?} with insufficient stake {:?}, minimum required is {:?}",
4048 id,
4049 bonded_pool.roles.depositor,
4050 depositor.active_points(),
4051 MinCreateBond::<T>::get()
4052 );
4053 }
4054
4055 ensure!(
4056 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
4057 "Each `BondedPool.points` must never be lower than the pool's balance"
4058 );
4059
4060 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
4061
4062 Ok(())
4063 })?;
4064
4065 if depositor_undermin_total > 0 {
4066 log!(
4067 warn,
4068 "{}/{} pools have depositor with insufficient stake, minimum required is {:?}. Examples: {:?}",
4069 depositor_undermin_total,
4070 total_pools,
4071 MinCreateBond::<T>::get(),
4072 depositor_undermin,
4073 );
4074 }
4075
4076 ensure!(
4077 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
4078 Error::<T>::MaxPoolMembers
4079 );
4080
4081 ensure!(
4082 TotalValueLocked::<T>::get() == expected_tvl,
4083 "TVL deviates from the actual sum of funds of all Pools."
4084 );
4085
4086 ensure!(
4087 TotalValueLocked::<T>::get() <= total_balance_members,
4088 "TVL must be equal to or less than the total balance of all PoolMembers."
4089 );
4090
4091 if level <= 1 {
4092 return Ok(());
4093 }
4094
4095 for (pool_id, _pool) in BondedPools::<T>::iter() {
4096 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
4097 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
4098
4099 let sum_unbonding_balance = subs.sum_unbonding_balance();
4100 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
4101 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
4103 .unwrap_or(T::Currency::total_balance(&pool_account));
4106
4107 if total_balance < bonded_balance + sum_unbonding_balance {
4108 log!(
4109 warn,
4110 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4111 pool_id,
4112 _pool,
4113 total_balance,
4114 bonded_balance,
4115 sum_unbonding_balance
4116 )
4117 };
4118 }
4119
4120 let _needs_adjust = Self::check_ed_imbalance()?;
4123
4124 Ok(())
4125 }
4126
4127 #[cfg(any(
4131 feature = "try-runtime",
4132 feature = "runtime-benchmarks",
4133 feature = "fuzzing",
4134 test,
4135 debug_assertions
4136 ))]
4137 pub fn check_ed_imbalance() -> Result<u32, DispatchError> {
4138 let mut needs_adjust: u32 = 0;
4139 let mut total_pools: u32 = 0;
4140 let mut ed_examples: Vec<PoolId> = Vec::new();
4141 const MAX_EXAMPLES: usize = 10;
4142
4143 BondedPools::<T>::iter_keys().for_each(|id| {
4144 total_pools += 1;
4145 let reward_acc = Self::generate_reward_account(id);
4146 let frozen_balance =
4147 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4148
4149 let expected_frozen_balance = T::Currency::minimum_balance();
4150 if frozen_balance != expected_frozen_balance {
4151 needs_adjust += 1;
4152 if ed_examples.len() < MAX_EXAMPLES {
4153 ed_examples.push(id);
4154 }
4155 log!(
4156 trace,
4157 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}. Use `adjust_pool_deposit` to fix it",
4158 id,
4159 expected_frozen_balance,
4160 frozen_balance,
4161 );
4162 }
4163 });
4164
4165 if needs_adjust > 0 {
4166 log!(
4167 warn,
4168 "{}/{} pools have incorrect ED frozen (expected {:?}). Use `adjust_pool_deposit` to fix. Examples: {:?}",
4169 needs_adjust,
4170 total_pools,
4171 T::Currency::minimum_balance(),
4172 ed_examples,
4173 );
4174 }
4175
4176 Ok(needs_adjust)
4177 }
4178 #[cfg(any(feature = "runtime-benchmarks", test))]
4183 pub fn fully_unbond(
4184 origin: frame_system::pallet_prelude::OriginFor<T>,
4185 member: T::AccountId,
4186 ) -> DispatchResult {
4187 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4188 let member_lookup = T::Lookup::unlookup(member);
4189 Self::unbond(origin, member_lookup, points)
4190 }
4191}
4192
4193impl<T: Config> Pallet<T> {
4194 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4198 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4199 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4200 .zip(BondedPools::<T>::get(pool_member.pool_id))
4201 {
4202 let commission = bonded_pool.commission.current();
4203 let (current_reward_counter, _) = reward_pool
4204 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4205 .ok()?;
4206 return pool_member.pending_rewards(current_reward_counter).ok();
4207 }
4208 }
4209
4210 None
4211 }
4212
4213 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4217 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4218 pool.points_to_balance(points)
4219 } else {
4220 Zero::zero()
4221 }
4222 }
4223
4224 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4228 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4229 let bonded_balance =
4230 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4231 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4232 } else {
4233 Zero::zero()
4234 }
4235 }
4236
4237 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4241 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4242 }
4243
4244 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4251 PoolMembers::<T>::get(who.clone())
4252 .map(|pool_member| {
4253 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4254 })
4255 .unwrap_or_default()
4256 }
4257
4258 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4263 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4265 return false;
4266 }
4267
4268 if !BondedPools::<T>::contains_key(pool_id) {
4270 return false;
4271 }
4272
4273 let pool_account = Self::generate_bonded_account(pool_id);
4274
4275 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4277 adapter::StakeStrategyType::Delegate
4278 }
4279
4280 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4286 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4288 return false;
4289 }
4290
4291 PoolMembers::<T>::get(who.clone())
4292 .map(|pool_member| {
4293 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4294 return false;
4296 }
4297
4298 let member_balance = pool_member.total_balance();
4299 let delegated_balance =
4300 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4301
4302 delegated_balance.is_none() && !member_balance.is_zero()
4305 })
4306 .unwrap_or_default()
4307 }
4308
4309 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4314 PoolMembers::<T>::get(who.clone())
4315 .map(|m| m.total_balance())
4316 .unwrap_or_default()
4317 }
4318
4319 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4321 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4322 .unwrap_or_default()
4323 }
4324
4325 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4327 let bonded_account = Self::generate_bonded_account(pool_id);
4328 let reward_account = Self::generate_reward_account(pool_id);
4329 (bonded_account, reward_account)
4330 }
4331}
4332
4333impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4334 fn on_slash(
4340 pool_account: &T::AccountId,
4341 slashed_bonded: BalanceOf<T>,
4344 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4345 total_slashed: BalanceOf<T>,
4346 ) {
4347 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4348 TotalValueLocked::<T>::mutate(|tvl| {
4351 tvl.defensive_saturating_reduce(total_slashed);
4352 });
4353
4354 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4355 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4357 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4358 pool.balance = *slashed_balance;
4359 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4360 era: *era,
4361 pool_id,
4362 balance: *slashed_balance,
4363 });
4364 }
4365 });
4366 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4367 } else if !slashed_unlocking.is_empty() {
4368 defensive!("Expected SubPools were not found");
4369 }
4370 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4371 }
4372
4373 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4376 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4377 TotalValueLocked::<T>::mutate(|tvl| {
4378 tvl.saturating_reduce(amount);
4379 });
4380 }
4381 }
4382}
4383
4384pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4386impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4387 fn contains(t: &T::AccountId) -> bool {
4388 PoolMembers::<T>::contains_key(t)
4389 }
4390}