1#![cfg_attr(not(feature = "std"), no_std)]
353
354extern crate alloc;
355
356use adapter::{Member, Pool, StakeStrategy};
357use alloc::{collections::btree_map::BTreeMap, vec::Vec};
358use codec::{Codec, DecodeWithMemTracking};
359use core::{fmt::Debug, ops::Div};
360use frame_support::{
361 defensive, defensive_assert, ensure,
362 pallet_prelude::{MaxEncodedLen, *},
363 storage::bounded_btree_map::BoundedBTreeMap,
364 traits::{
365 fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze},
366 tokens::{Fortitude, Preservation},
367 Contains, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, Get,
368 },
369 DefaultNoBound, PalletError,
370};
371use scale_info::TypeInfo;
372use sp_core::U256;
373use sp_runtime::{
374 traits::{
375 AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup,
376 Zero,
377 },
378 FixedPointNumber, Perbill,
379};
380use sp_staking::{EraIndex, StakingInterface};
381
382#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
383use sp_runtime::TryRuntimeError;
384
385pub const LOG_TARGET: &str = "runtime::nomination-pools";
387#[macro_export]
389macro_rules! log {
390 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
391 log::$level!(
392 target: $crate::LOG_TARGET,
393 concat!("[{:?}] 🏊♂️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
394 )
395 };
396}
397
398#[cfg(any(test, feature = "fuzzing"))]
399pub mod mock;
400#[cfg(test)]
401mod tests;
402
403pub mod adapter;
404pub mod migration;
405pub mod weights;
406
407pub use pallet::*;
408use sp_runtime::traits::BlockNumberProvider;
409pub use weights::WeightInfo;
410
411pub type BalanceOf<T> =
413 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
414pub type PoolId = u32;
416
417type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
418
419pub type BlockNumberFor<T> =
420 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
421
422pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1;
423
424#[derive(
426 Encode,
427 Decode,
428 DecodeWithMemTracking,
429 MaxEncodedLen,
430 TypeInfo,
431 RuntimeDebugNoBound,
432 PartialEq,
433 Clone,
434)]
435pub enum ConfigOp<T: Codec + Debug> {
436 Noop,
438 Set(T),
440 Remove,
442}
443
444pub enum BondType {
446 Create,
448 Extra,
450}
451
452#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
454pub enum BondExtra<Balance> {
455 FreeBalance(Balance),
457 Rewards,
459}
460
461#[derive(Encode, Decode)]
463enum AccountType {
464 Bonded,
465 Reward,
466}
467
468#[derive(
470 Encode,
471 Decode,
472 DecodeWithMemTracking,
473 MaxEncodedLen,
474 Clone,
475 Copy,
476 Debug,
477 PartialEq,
478 Eq,
479 TypeInfo,
480)]
481pub enum ClaimPermission {
482 Permissioned,
484 PermissionlessCompound,
486 PermissionlessWithdraw,
488 PermissionlessAll,
490}
491
492impl Default for ClaimPermission {
493 fn default() -> Self {
494 Self::PermissionlessWithdraw
495 }
496}
497
498impl ClaimPermission {
499 fn can_bond_extra(&self) -> bool {
502 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound)
503 }
504
505 fn can_claim_payout(&self) -> bool {
508 matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw)
509 }
510}
511
512#[derive(
514 Encode,
515 Decode,
516 DecodeWithMemTracking,
517 MaxEncodedLen,
518 TypeInfo,
519 RuntimeDebugNoBound,
520 CloneNoBound,
521 PartialEqNoBound,
522 EqNoBound,
523)]
524#[cfg_attr(feature = "std", derive(DefaultNoBound))]
525#[scale_info(skip_type_params(T))]
526pub struct PoolMember<T: Config> {
527 pub pool_id: PoolId,
529 pub points: BalanceOf<T>,
532 pub last_recorded_reward_counter: T::RewardCounter,
534 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
537}
538
539impl<T: Config> PoolMember<T> {
540 fn pending_rewards(
542 &self,
543 current_reward_counter: T::RewardCounter,
544 ) -> Result<BalanceOf<T>, Error<T>> {
545 (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter))
563 .checked_mul_int(self.active_points())
564 .ok_or(Error::<T>::OverflowRisk)
565 }
566
567 fn active_balance(&self) -> BalanceOf<T> {
572 if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() {
573 pool.points_to_balance(self.points)
574 } else {
575 Zero::zero()
576 }
577 }
578
579 pub fn total_balance(&self) -> BalanceOf<T> {
585 let pool = match BondedPool::<T>::get(self.pool_id) {
586 Some(pool) => pool,
587 None => {
588 defensive!("pool should exist; qed");
590 return Zero::zero();
591 },
592 };
593
594 let active_balance = pool.points_to_balance(self.active_points());
595
596 let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
597 Some(sub_pools) => sub_pools,
598 None => return active_balance,
599 };
600
601 let unbonding_balance = self.unbonding_eras.iter().fold(
602 BalanceOf::<T>::zero(),
603 |accumulator, (era, unlocked_points)| {
604 let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
607 accumulator + (era_pool.point_to_balance(*unlocked_points))
608 },
609 );
610
611 active_balance + unbonding_balance
612 }
613
614 fn total_points(&self) -> BalanceOf<T> {
616 self.active_points().saturating_add(self.unbonding_points())
617 }
618
619 fn active_points(&self) -> BalanceOf<T> {
621 self.points
622 }
623
624 fn unbonding_points(&self) -> BalanceOf<T> {
626 self.unbonding_eras
627 .as_ref()
628 .iter()
629 .fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v))
630 }
631
632 fn try_unbond(
640 &mut self,
641 points_dissolved: BalanceOf<T>,
642 points_issued: BalanceOf<T>,
643 unbonding_era: EraIndex,
644 ) -> Result<(), Error<T>> {
645 if let Some(new_points) = self.points.checked_sub(&points_dissolved) {
646 match self.unbonding_eras.get_mut(&unbonding_era) {
647 Some(already_unbonding_points) =>
648 *already_unbonding_points =
649 already_unbonding_points.saturating_add(points_issued),
650 None => self
651 .unbonding_eras
652 .try_insert(unbonding_era, points_issued)
653 .map(|old| {
654 if old.is_some() {
655 defensive!("value checked to not exist in the map; qed");
656 }
657 })
658 .map_err(|_| Error::<T>::MaxUnbondingLimit)?,
659 }
660 self.points = new_points;
661 Ok(())
662 } else {
663 Err(Error::<T>::MinimumBondNotMet)
664 }
665 }
666
667 fn withdraw_unlocked(
674 &mut self,
675 current_era: EraIndex,
676 ) -> BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding> {
677 let mut removed_points =
679 BoundedBTreeMap::<EraIndex, BalanceOf<T>, T::MaxUnbonding>::default();
680 self.unbonding_eras.retain(|e, p| {
681 if *e > current_era {
682 true
683 } else {
684 removed_points
685 .try_insert(*e, *p)
686 .expect("source map is bounded, this is a subset, will be bounded; qed");
687 false
688 }
689 });
690 removed_points
691 }
692}
693
694#[derive(
696 Encode,
697 Decode,
698 DecodeWithMemTracking,
699 MaxEncodedLen,
700 TypeInfo,
701 PartialEq,
702 RuntimeDebugNoBound,
703 Clone,
704 Copy,
705)]
706pub enum PoolState {
707 Open,
709 Blocked,
711 Destroying,
716}
717
718#[derive(
724 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone,
725)]
726pub struct PoolRoles<AccountId> {
727 pub depositor: AccountId,
730 pub root: Option<AccountId>,
733 pub nominator: Option<AccountId>,
735 pub bouncer: Option<AccountId>,
737}
738
739#[derive(
741 PartialEq,
742 Eq,
743 Copy,
744 Clone,
745 Encode,
746 Decode,
747 DecodeWithMemTracking,
748 RuntimeDebug,
749 TypeInfo,
750 MaxEncodedLen,
751)]
752pub enum CommissionClaimPermission<AccountId> {
753 Permissionless,
754 Account(AccountId),
755}
756
757#[derive(
770 Encode,
771 Decode,
772 DecodeWithMemTracking,
773 DefaultNoBound,
774 MaxEncodedLen,
775 TypeInfo,
776 DebugNoBound,
777 PartialEq,
778 Copy,
779 Clone,
780)]
781#[codec(mel_bound(T: Config))]
782#[scale_info(skip_type_params(T))]
783pub struct Commission<T: Config> {
784 pub current: Option<(Perbill, T::AccountId)>,
786 pub max: Option<Perbill>,
789 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
792 pub throttle_from: Option<BlockNumberFor<T>>,
795 pub claim_permission: Option<CommissionClaimPermission<T::AccountId>>,
798}
799
800impl<T: Config> Commission<T> {
801 fn throttling(&self, to: &Perbill) -> bool {
808 if let Some(t) = self.change_rate.as_ref() {
809 let commission_as_percent =
810 self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero());
811
812 if *to <= commission_as_percent {
814 return false
815 }
816 if (*to).saturating_sub(commission_as_percent) > t.max_increase {
820 return true
821 }
822
823 return self.throttle_from.map_or_else(
828 || {
829 defensive!("throttle_from should exist if change_rate is set");
830 true
831 },
832 |f| {
833 if t.min_delay == Zero::zero() {
835 false
836 } else {
837 let blocks_surpassed =
839 T::BlockNumberProvider::current_block_number().saturating_sub(f);
840 blocks_surpassed < t.min_delay
841 }
842 },
843 )
844 }
845 false
846 }
847
848 fn current(&self) -> Perbill {
851 self.current
852 .as_ref()
853 .map_or(Perbill::zero(), |(c, _)| *c)
854 .min(GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()))
855 }
856
857 fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult {
863 self.current = match current {
864 None => None,
865 Some((commission, payee)) => {
866 ensure!(!self.throttling(commission), Error::<T>::CommissionChangeThrottled);
867 ensure!(
868 commission <= &GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
869 Error::<T>::CommissionExceedsGlobalMaximum
870 );
871 ensure!(
872 self.max.map_or(true, |m| commission <= &m),
873 Error::<T>::CommissionExceedsMaximum
874 );
875 if commission.is_zero() {
876 None
877 } else {
878 Some((*commission, payee.clone()))
879 }
880 },
881 };
882 self.register_update();
883 Ok(())
884 }
885
886 fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult {
895 ensure!(
896 new_max <= GlobalMaxCommission::<T>::get().unwrap_or(Bounded::max_value()),
897 Error::<T>::CommissionExceedsGlobalMaximum
898 );
899 if let Some(old) = self.max.as_mut() {
900 if new_max > *old {
901 return Err(Error::<T>::MaxCommissionRestricted.into())
902 }
903 *old = new_max;
904 } else {
905 self.max = Some(new_max)
906 };
907 let updated_current = self
908 .current
909 .as_mut()
910 .map(|(c, _)| {
911 let u = *c > new_max;
912 *c = (*c).min(new_max);
913 u
914 })
915 .unwrap_or(false);
916
917 if updated_current {
918 if let Some((_, payee)) = self.current.as_ref() {
919 Pallet::<T>::deposit_event(Event::<T>::PoolCommissionUpdated {
920 pool_id,
921 current: Some((new_max, payee.clone())),
922 });
923 }
924 self.register_update();
925 }
926 Ok(())
927 }
928
929 fn try_update_change_rate(
938 &mut self,
939 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
940 ) -> DispatchResult {
941 ensure!(!&self.less_restrictive(&change_rate), Error::<T>::CommissionChangeRateNotAllowed);
942
943 if self.change_rate.is_none() {
944 self.register_update();
945 }
946 self.change_rate = Some(change_rate);
947 Ok(())
948 }
949
950 fn register_update(&mut self) {
952 self.throttle_from = Some(T::BlockNumberProvider::current_block_number());
953 }
954
955 fn less_restrictive(&self, new: &CommissionChangeRate<BlockNumberFor<T>>) -> bool {
960 self.change_rate
961 .as_ref()
962 .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay)
963 .unwrap_or(false)
964 }
965}
966
967#[derive(
975 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone,
976)]
977pub struct CommissionChangeRate<BlockNumber> {
978 pub max_increase: Perbill,
980 pub min_delay: BlockNumber,
982}
983
984#[derive(
986 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone,
987)]
988#[codec(mel_bound(T: Config))]
989#[scale_info(skip_type_params(T))]
990pub struct BondedPoolInner<T: Config> {
991 pub commission: Commission<T>,
993 pub member_counter: u32,
995 pub points: BalanceOf<T>,
997 pub roles: PoolRoles<T::AccountId>,
999 pub state: PoolState,
1001}
1002
1003#[derive(RuntimeDebugNoBound)]
1008#[cfg_attr(feature = "std", derive(Clone, PartialEq))]
1009pub struct BondedPool<T: Config> {
1010 id: PoolId,
1012 inner: BondedPoolInner<T>,
1014}
1015
1016impl<T: Config> core::ops::Deref for BondedPool<T> {
1017 type Target = BondedPoolInner<T>;
1018 fn deref(&self) -> &Self::Target {
1019 &self.inner
1020 }
1021}
1022
1023impl<T: Config> core::ops::DerefMut for BondedPool<T> {
1024 fn deref_mut(&mut self) -> &mut Self::Target {
1025 &mut self.inner
1026 }
1027}
1028
1029impl<T: Config> BondedPool<T> {
1030 fn new(id: PoolId, roles: PoolRoles<T::AccountId>) -> Self {
1032 Self {
1033 id,
1034 inner: BondedPoolInner {
1035 commission: Commission::default(),
1036 member_counter: Zero::zero(),
1037 points: Zero::zero(),
1038 roles,
1039 state: PoolState::Open,
1040 },
1041 }
1042 }
1043
1044 pub fn get(id: PoolId) -> Option<Self> {
1046 BondedPools::<T>::try_get(id).ok().map(|inner| Self { id, inner })
1047 }
1048
1049 fn bonded_account(&self) -> T::AccountId {
1051 Pallet::<T>::generate_bonded_account(self.id)
1052 }
1053
1054 fn reward_account(&self) -> T::AccountId {
1056 Pallet::<T>::generate_reward_account(self.id)
1057 }
1058
1059 fn put(self) {
1061 BondedPools::<T>::insert(self.id, self.inner);
1062 }
1063
1064 fn remove(self) {
1066 BondedPools::<T>::remove(self.id);
1067 }
1068
1069 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1073 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1074 Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
1075 }
1076
1077 fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1081 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1082 Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
1083 }
1084
1085 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1087 let points_to_issue = self.balance_to_point(new_funds);
1088 self.points = self.points.saturating_add(points_to_issue);
1089 points_to_issue
1090 }
1091
1092 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1099 let balance = self.points_to_balance(points);
1102 self.points = self.points.saturating_sub(points);
1103 balance
1104 }
1105
1106 fn try_inc_members(&mut self) -> Result<(), DispatchError> {
1109 ensure!(
1110 MaxPoolMembersPerPool::<T>::get()
1111 .map_or(true, |max_per_pool| self.member_counter < max_per_pool),
1112 Error::<T>::MaxPoolMembers
1113 );
1114 ensure!(
1115 MaxPoolMembers::<T>::get().map_or(true, |max| PoolMembers::<T>::count() < max),
1116 Error::<T>::MaxPoolMembers
1117 );
1118 self.member_counter = self.member_counter.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
1119 Ok(())
1120 }
1121
1122 fn dec_members(mut self) -> Self {
1124 self.member_counter = self.member_counter.defensive_saturating_sub(1);
1125 self
1126 }
1127
1128 fn is_root(&self, who: &T::AccountId) -> bool {
1129 self.roles.root.as_ref().map_or(false, |root| root == who)
1130 }
1131
1132 fn is_bouncer(&self, who: &T::AccountId) -> bool {
1133 self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who)
1134 }
1135
1136 fn can_update_roles(&self, who: &T::AccountId) -> bool {
1137 self.is_root(who)
1138 }
1139
1140 fn can_nominate(&self, who: &T::AccountId) -> bool {
1141 self.is_root(who) ||
1142 self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who)
1143 }
1144
1145 fn can_kick(&self, who: &T::AccountId) -> bool {
1146 self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who))
1147 }
1148
1149 fn can_toggle_state(&self, who: &T::AccountId) -> bool {
1150 (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying()
1151 }
1152
1153 fn can_set_metadata(&self, who: &T::AccountId) -> bool {
1154 self.is_root(who) || self.is_bouncer(who)
1155 }
1156
1157 fn can_manage_commission(&self, who: &T::AccountId) -> bool {
1158 self.is_root(who)
1159 }
1160
1161 fn can_claim_commission(&self, who: &T::AccountId) -> bool {
1162 if let Some(permission) = self.commission.claim_permission.as_ref() {
1163 match permission {
1164 CommissionClaimPermission::Permissionless => true,
1165 CommissionClaimPermission::Account(account) => account == who || self.is_root(who),
1166 }
1167 } else {
1168 self.is_root(who)
1169 }
1170 }
1171
1172 fn is_destroying(&self) -> bool {
1173 matches!(self.state, PoolState::Destroying)
1174 }
1175
1176 fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf<T>) -> bool {
1177 self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1
1184 }
1185
1186 fn ok_to_be_open(&self) -> Result<(), DispatchError> {
1189 ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
1190
1191 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(self.bonded_account()));
1192 ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
1193
1194 let points_to_balance_ratio_floor = self
1195 .points
1196 .div(bonded_balance);
1198
1199 let max_points_to_balance = T::MaxPointsToBalance::get();
1200
1201 ensure!(
1205 points_to_balance_ratio_floor < max_points_to_balance.into(),
1206 Error::<T>::OverflowRisk
1207 );
1208
1209 Ok(())
1213 }
1214
1215 fn ok_to_join(&self) -> Result<(), DispatchError> {
1217 ensure!(self.state == PoolState::Open, Error::<T>::NotOpen);
1218 self.ok_to_be_open()?;
1219 Ok(())
1220 }
1221
1222 fn ok_to_unbond_with(
1223 &self,
1224 caller: &T::AccountId,
1225 target_account: &T::AccountId,
1226 target_member: &PoolMember<T>,
1227 unbonding_points: BalanceOf<T>,
1228 ) -> Result<(), DispatchError> {
1229 let is_permissioned = caller == target_account;
1230 let is_depositor = *target_account == self.roles.depositor;
1231 let is_full_unbond = unbonding_points == target_member.active_points();
1232
1233 let balance_after_unbond = {
1234 let new_depositor_points =
1235 target_member.active_points().saturating_sub(unbonding_points);
1236 let mut target_member_after_unbond = (*target_member).clone();
1237 target_member_after_unbond.points = new_depositor_points;
1238 target_member_after_unbond.active_balance()
1239 };
1240
1241 ensure!(
1243 is_permissioned || is_full_unbond,
1244 Error::<T>::PartialUnbondNotAllowedPermissionlessly
1245 );
1246
1247 ensure!(
1249 is_full_unbond ||
1250 balance_after_unbond >=
1251 if is_depositor {
1252 Pallet::<T>::depositor_min_bond()
1253 } else {
1254 MinJoinBond::<T>::get()
1255 },
1256 Error::<T>::MinimumBondNotMet
1257 );
1258
1259 match (is_permissioned, is_depositor) {
1261 (true, false) => (),
1262 (true, true) => {
1263 if self.is_destroying_and_only_depositor(target_member.active_points()) {
1266 } else {
1268 ensure!(!is_full_unbond, Error::<T>::MinimumBondNotMet);
1270 }
1271 },
1272 (false, false) => {
1273 debug_assert!(is_full_unbond);
1276 ensure!(
1277 self.can_kick(caller) || self.is_destroying(),
1278 Error::<T>::NotKickerOrDestroying
1279 )
1280 },
1281 (false, true) => {
1282 return Err(Error::<T>::DoesNotHavePermission.into())
1284 },
1285 };
1286
1287 Ok(())
1288 }
1289
1290 fn ok_to_withdraw_unbonded_with(
1294 &self,
1295 caller: &T::AccountId,
1296 target_account: &T::AccountId,
1297 ) -> Result<(), DispatchError> {
1298 let is_permissioned = caller == target_account;
1300 ensure!(
1301 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1302 Error::<T>::NotKickerOrDestroying
1303 );
1304 Ok(())
1305 }
1306
1307 fn try_bond_funds(
1315 &mut self,
1316 who: &T::AccountId,
1317 amount: BalanceOf<T>,
1318 ty: BondType,
1319 ) -> Result<BalanceOf<T>, DispatchError> {
1320 let points_issued = self.issue(amount);
1323
1324 T::StakeAdapter::pledge_bond(
1325 Member::from(who.clone()),
1326 Pool::from(self.bonded_account()),
1327 &self.reward_account(),
1328 amount,
1329 ty,
1330 )?;
1331 TotalValueLocked::<T>::mutate(|tvl| {
1332 tvl.saturating_accrue(amount);
1333 });
1334
1335 Ok(points_issued)
1336 }
1337
1338 fn set_state(&mut self, state: PoolState) {
1341 if self.state != state {
1342 self.state = state;
1343 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1344 pool_id: self.id,
1345 new_state: state,
1346 });
1347 };
1348 }
1349}
1350
1351#[derive(
1357 Encode,
1358 Decode,
1359 MaxEncodedLen,
1360 TypeInfo,
1361 CloneNoBound,
1362 PartialEqNoBound,
1363 EqNoBound,
1364 RuntimeDebugNoBound,
1365)]
1366#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1367#[codec(mel_bound(T: Config))]
1368#[scale_info(skip_type_params(T))]
1369pub struct RewardPool<T: Config> {
1370 pub last_recorded_reward_counter: T::RewardCounter,
1375 pub last_recorded_total_payouts: BalanceOf<T>,
1381 pub total_rewards_claimed: BalanceOf<T>,
1383 pub total_commission_pending: BalanceOf<T>,
1385 pub total_commission_claimed: BalanceOf<T>,
1387}
1388
1389impl<T: Config> RewardPool<T> {
1390 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1392 self.last_recorded_reward_counter
1393 }
1394
1395 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1397 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1398 }
1399
1400 fn update_records(
1407 &mut self,
1408 id: PoolId,
1409 bonded_points: BalanceOf<T>,
1410 commission: Perbill,
1411 ) -> Result<(), Error<T>> {
1412 let balance = Self::current_balance(id);
1413
1414 let (current_reward_counter, new_pending_commission) =
1415 self.current_reward_counter(id, bonded_points, commission)?;
1416
1417 self.last_recorded_reward_counter = current_reward_counter;
1421
1422 self.total_commission_pending =
1425 self.total_commission_pending.saturating_add(new_pending_commission);
1426
1427 let last_recorded_total_payouts = balance
1431 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1432 .ok_or(Error::<T>::OverflowRisk)?;
1433
1434 self.last_recorded_total_payouts =
1441 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1442
1443 Ok(())
1444 }
1445
1446 fn current_reward_counter(
1449 &self,
1450 id: PoolId,
1451 bonded_points: BalanceOf<T>,
1452 commission: Perbill,
1453 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1454 let balance = Self::current_balance(id);
1455
1456 let current_payout_balance = balance
1461 .saturating_add(self.total_rewards_claimed)
1462 .saturating_add(self.total_commission_claimed)
1463 .saturating_sub(self.last_recorded_total_payouts);
1464
1465 let new_pending_commission = commission * current_payout_balance;
1468 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1469
1470 let current_reward_counter =
1505 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1506 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1507 .ok_or(Error::<T>::OverflowRisk)?;
1508
1509 Ok((current_reward_counter, new_pending_commission))
1510 }
1511
1512 fn current_balance(id: PoolId) -> BalanceOf<T> {
1516 T::Currency::reducible_balance(
1517 &Pallet::<T>::generate_reward_account(id),
1518 Preservation::Expendable,
1519 Fortitude::Polite,
1520 )
1521 }
1522}
1523
1524#[derive(
1526 Encode,
1527 Decode,
1528 MaxEncodedLen,
1529 TypeInfo,
1530 DefaultNoBound,
1531 RuntimeDebugNoBound,
1532 CloneNoBound,
1533 PartialEqNoBound,
1534 EqNoBound,
1535)]
1536#[codec(mel_bound(T: Config))]
1537#[scale_info(skip_type_params(T))]
1538pub struct UnbondPool<T: Config> {
1539 pub points: BalanceOf<T>,
1541 pub balance: BalanceOf<T>,
1543}
1544
1545impl<T: Config> UnbondPool<T> {
1546 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1547 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1548 }
1549
1550 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1551 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1552 }
1553
1554 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1558 let new_points = self.balance_to_point(new_funds);
1559 self.points = self.points.saturating_add(new_points);
1560 self.balance = self.balance.saturating_add(new_funds);
1561 new_points
1562 }
1563
1564 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1569 let balance_to_unbond = self.point_to_balance(points);
1570 self.points = self.points.saturating_sub(points);
1571 self.balance = self.balance.saturating_sub(balance_to_unbond);
1572
1573 balance_to_unbond
1574 }
1575}
1576
1577#[derive(
1578 Encode,
1579 Decode,
1580 MaxEncodedLen,
1581 TypeInfo,
1582 DefaultNoBound,
1583 RuntimeDebugNoBound,
1584 CloneNoBound,
1585 PartialEqNoBound,
1586 EqNoBound,
1587)]
1588#[codec(mel_bound(T: Config))]
1589#[scale_info(skip_type_params(T))]
1590pub struct SubPools<T: Config> {
1591 pub no_era: UnbondPool<T>,
1595 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1597}
1598
1599impl<T: Config> SubPools<T> {
1600 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1605 if let Some(newest_era_to_remove) =
1609 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1610 {
1611 self.with_era.retain(|k, v| {
1612 if *k > newest_era_to_remove {
1613 true
1615 } else {
1616 self.no_era.points = self.no_era.points.saturating_add(v.points);
1618 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1619 false
1620 }
1621 });
1622 }
1623
1624 self
1625 }
1626
1627 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1629 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1630 self.no_era.balance.saturating_add(
1631 self.with_era
1632 .values()
1633 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1634 )
1635 }
1636}
1637
1638pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1642
1643impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1644 fn get() -> u32 {
1645 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1649 }
1650}
1651
1652#[frame_support::pallet]
1653pub mod pallet {
1654 use super::*;
1655 use frame_support::traits::StorageVersion;
1656 use frame_system::pallet_prelude::{
1657 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1658 };
1659 use sp_runtime::Perbill;
1660
1661 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1663
1664 #[pallet::pallet]
1665 #[pallet::storage_version(STORAGE_VERSION)]
1666 pub struct Pallet<T>(_);
1667
1668 #[pallet::config]
1669 pub trait Config: frame_system::Config {
1670 #[allow(deprecated)]
1672 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1673
1674 type WeightInfo: weights::WeightInfo;
1676
1677 type Currency: Mutate<Self::AccountId>
1679 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1680
1681 type RuntimeFreezeReason: From<FreezeReason>;
1683
1684 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1697
1698 #[pallet::constant]
1700 type PalletId: Get<frame_support::PalletId>;
1701
1702 #[pallet::constant]
1715 type MaxPointsToBalance: Get<u8>;
1716
1717 #[pallet::constant]
1719 type MaxUnbonding: Get<u32>;
1720
1721 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1723
1724 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1726
1727 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1731
1732 type PostUnbondingPoolsWindow: Get<u32>;
1738
1739 type MaxMetadataLen: Get<u32>;
1741
1742 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1744
1745 type BlockNumberProvider: BlockNumberProvider;
1747
1748 type Filter: Contains<Self::AccountId>;
1750 }
1751
1752 #[pallet::storage]
1758 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1759
1760 #[pallet::storage]
1762 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1763
1764 #[pallet::storage]
1772 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1773
1774 #[pallet::storage]
1777 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1778
1779 #[pallet::storage]
1782 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1783
1784 #[pallet::storage]
1787 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1788
1789 #[pallet::storage]
1793 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1794
1795 #[pallet::storage]
1799 pub type PoolMembers<T: Config> =
1800 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1801
1802 #[pallet::storage]
1805 pub type BondedPools<T: Config> =
1806 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1807
1808 #[pallet::storage]
1811 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1812
1813 #[pallet::storage]
1816 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1817
1818 #[pallet::storage]
1820 pub type Metadata<T: Config> =
1821 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1822
1823 #[pallet::storage]
1825 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1826
1827 #[pallet::storage]
1832 pub type ReversePoolIdLookup<T: Config> =
1833 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1834
1835 #[pallet::storage]
1837 pub type ClaimPermissions<T: Config> =
1838 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1839
1840 #[pallet::genesis_config]
1841 pub struct GenesisConfig<T: Config> {
1842 pub min_join_bond: BalanceOf<T>,
1843 pub min_create_bond: BalanceOf<T>,
1844 pub max_pools: Option<u32>,
1845 pub max_members_per_pool: Option<u32>,
1846 pub max_members: Option<u32>,
1847 pub global_max_commission: Option<Perbill>,
1848 }
1849
1850 impl<T: Config> Default for GenesisConfig<T> {
1851 fn default() -> Self {
1852 Self {
1853 min_join_bond: Zero::zero(),
1854 min_create_bond: Zero::zero(),
1855 max_pools: Some(16),
1856 max_members_per_pool: Some(32),
1857 max_members: Some(16 * 32),
1858 global_max_commission: None,
1859 }
1860 }
1861 }
1862
1863 #[pallet::genesis_build]
1864 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1865 fn build(&self) {
1866 MinJoinBond::<T>::put(self.min_join_bond);
1867 MinCreateBond::<T>::put(self.min_create_bond);
1868
1869 if let Some(max_pools) = self.max_pools {
1870 MaxPools::<T>::put(max_pools);
1871 }
1872 if let Some(max_members_per_pool) = self.max_members_per_pool {
1873 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1874 }
1875 if let Some(max_members) = self.max_members {
1876 MaxPoolMembers::<T>::put(max_members);
1877 }
1878 if let Some(global_max_commission) = self.global_max_commission {
1879 GlobalMaxCommission::<T>::put(global_max_commission);
1880 }
1881 }
1882 }
1883
1884 #[pallet::event]
1886 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1887 pub enum Event<T: Config> {
1888 Created { depositor: T::AccountId, pool_id: PoolId },
1890 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1892 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1894 Unbonded {
1906 member: T::AccountId,
1907 pool_id: PoolId,
1908 balance: BalanceOf<T>,
1909 points: BalanceOf<T>,
1910 era: EraIndex,
1911 },
1912 Withdrawn {
1919 member: T::AccountId,
1920 pool_id: PoolId,
1921 balance: BalanceOf<T>,
1922 points: BalanceOf<T>,
1923 },
1924 Destroyed { pool_id: PoolId },
1926 StateChanged { pool_id: PoolId, new_state: PoolState },
1928 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1934 RolesUpdated {
1937 root: Option<T::AccountId>,
1938 bouncer: Option<T::AccountId>,
1939 nominator: Option<T::AccountId>,
1940 },
1941 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1943 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1945 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1947 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1949 PoolCommissionChangeRateUpdated {
1951 pool_id: PoolId,
1952 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1953 },
1954 PoolCommissionClaimPermissionUpdated {
1956 pool_id: PoolId,
1957 permission: Option<CommissionClaimPermission<T::AccountId>>,
1958 },
1959 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1961 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1963 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1965 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1967 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1969 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1972 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1974 GlobalParamsUpdated {
1976 min_join_bond: BalanceOf<T>,
1977 min_create_bond: BalanceOf<T>,
1978 max_pools: Option<u32>,
1979 max_members: Option<u32>,
1980 max_members_per_pool: Option<u32>,
1981 global_max_commission: Option<Perbill>,
1982 },
1983 }
1984
1985 #[pallet::error]
1986 #[cfg_attr(test, derive(PartialEq))]
1987 pub enum Error<T> {
1988 PoolNotFound,
1990 PoolMemberNotFound,
1992 RewardPoolNotFound,
1994 SubPoolsNotFound,
1996 AccountBelongsToOtherPool,
1999 FullyUnbonding,
2002 MaxUnbondingLimit,
2004 CannotWithdrawAny,
2006 MinimumBondNotMet,
2012 OverflowRisk,
2014 NotDestroying,
2017 NotNominator,
2019 NotKickerOrDestroying,
2021 NotOpen,
2023 MaxPools,
2025 MaxPoolMembers,
2027 CanNotChangeState,
2029 DoesNotHavePermission,
2031 MetadataExceedsMaxLen,
2033 Defensive(DefensiveError),
2036 PartialUnbondNotAllowedPermissionlessly,
2038 MaxCommissionRestricted,
2040 CommissionExceedsMaximum,
2042 CommissionExceedsGlobalMaximum,
2044 CommissionChangeThrottled,
2046 CommissionChangeRateNotAllowed,
2048 NoPendingCommission,
2050 NoCommissionCurrentSet,
2052 PoolIdInUse,
2054 InvalidPoolId,
2056 BondExtraRestricted,
2058 NothingToAdjust,
2060 NothingToSlash,
2062 SlashTooLow,
2064 AlreadyMigrated,
2066 NotMigrated,
2068 NotSupported,
2070 Restricted,
2073 }
2074
2075 #[derive(
2076 Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, RuntimeDebug,
2077 )]
2078 pub enum DefensiveError {
2079 NotEnoughSpaceInUnbondPool,
2081 PoolNotFound,
2083 RewardPoolNotFound,
2085 SubPoolsNotFound,
2087 BondedStashKilledPrematurely,
2090 DelegationUnsupported,
2092 SlashNotApplied,
2094 }
2095
2096 impl<T> From<DefensiveError> for Error<T> {
2097 fn from(e: DefensiveError) -> Error<T> {
2098 Error::<T>::Defensive(e)
2099 }
2100 }
2101
2102 #[pallet::composite_enum]
2104 pub enum FreezeReason {
2105 #[codec(index = 0)]
2107 PoolMinBalance,
2108 }
2109
2110 #[pallet::call]
2111 impl<T: Config> Pallet<T> {
2112 #[pallet::call_index(0)]
2129 #[pallet::weight(T::WeightInfo::join())]
2130 pub fn join(
2131 origin: OriginFor<T>,
2132 #[pallet::compact] amount: BalanceOf<T>,
2133 pool_id: PoolId,
2134 ) -> DispatchResult {
2135 let who = ensure_signed(origin)?;
2136 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2138
2139 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2141
2142 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2143 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2145
2146 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2147 bonded_pool.ok_to_join()?;
2148
2149 let mut reward_pool = RewardPools::<T>::get(pool_id)
2150 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2151 reward_pool.update_records(
2153 pool_id,
2154 bonded_pool.points,
2155 bonded_pool.commission.current(),
2156 )?;
2157
2158 bonded_pool.try_inc_members()?;
2159 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2160
2161 PoolMembers::insert(
2162 who.clone(),
2163 PoolMember::<T> {
2164 pool_id,
2165 points: points_issued,
2166 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2169 unbonding_eras: Default::default(),
2170 },
2171 );
2172
2173 Self::deposit_event(Event::<T>::Bonded {
2174 member: who,
2175 pool_id,
2176 bonded: amount,
2177 joined: true,
2178 });
2179
2180 bonded_pool.put();
2181 RewardPools::<T>::insert(pool_id, reward_pool);
2182
2183 Ok(())
2184 }
2185
2186 #[pallet::call_index(1)]
2197 #[pallet::weight(
2198 T::WeightInfo::bond_extra_transfer()
2199 .max(T::WeightInfo::bond_extra_other())
2200 )]
2201 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2202 let who = ensure_signed(origin)?;
2203
2204 ensure!(
2206 !Self::api_member_needs_delegate_migration(who.clone()),
2207 Error::<T>::NotMigrated
2208 );
2209
2210 Self::do_bond_extra(who.clone(), who, extra)
2211 }
2212
2213 #[pallet::call_index(2)]
2222 #[pallet::weight(T::WeightInfo::claim_payout())]
2223 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2224 let signer = ensure_signed(origin)?;
2225 ensure!(
2227 !Self::api_member_needs_delegate_migration(signer.clone()),
2228 Error::<T>::NotMigrated
2229 );
2230
2231 Self::do_claim_payout(signer.clone(), signer)
2232 }
2233
2234 #[pallet::call_index(3)]
2266 #[pallet::weight(T::WeightInfo::unbond())]
2267 pub fn unbond(
2268 origin: OriginFor<T>,
2269 member_account: AccountIdLookupOf<T>,
2270 #[pallet::compact] unbonding_points: BalanceOf<T>,
2271 ) -> DispatchResult {
2272 let who = ensure_signed(origin)?;
2273 let member_account = T::Lookup::lookup(member_account)?;
2274 ensure!(
2276 !Self::api_member_needs_delegate_migration(member_account.clone()),
2277 Error::<T>::NotMigrated
2278 );
2279
2280 let (mut member, mut bonded_pool, mut reward_pool) =
2281 Self::get_member_with_pools(&member_account)?;
2282
2283 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2284
2285 reward_pool.update_records(
2289 bonded_pool.id,
2290 bonded_pool.points,
2291 bonded_pool.commission.current(),
2292 )?;
2293 Self::do_reward_payout(
2294 &member_account,
2295 &mut member,
2296 &mut bonded_pool,
2297 &mut reward_pool,
2298 )?;
2299
2300 let current_era = T::StakeAdapter::current_era();
2301 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
2302
2303 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2305 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2306
2307 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2309 .unwrap_or_default()
2310 .maybe_merge_pools(current_era);
2311
2312 if !sub_pools.with_era.contains_key(&unbond_era) {
2315 sub_pools
2316 .with_era
2317 .try_insert(unbond_era, UnbondPool::default())
2318 .defensive_map_err::<Error<T>, _>(|_| {
2321 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2322 })?;
2323 }
2324
2325 let points_unbonded = sub_pools
2326 .with_era
2327 .get_mut(&unbond_era)
2328 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2330 .issue(unbonding_balance);
2331
2332 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2334
2335 Self::deposit_event(Event::<T>::Unbonded {
2336 member: member_account.clone(),
2337 pool_id: member.pool_id,
2338 points: points_unbonded,
2339 balance: unbonding_balance,
2340 era: unbond_era,
2341 });
2342
2343 SubPoolsStorage::insert(member.pool_id, sub_pools);
2345 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2346 Ok(())
2347 }
2348
2349 #[pallet::call_index(4)]
2356 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2357 pub fn pool_withdraw_unbonded(
2358 origin: OriginFor<T>,
2359 pool_id: PoolId,
2360 num_slashing_spans: u32,
2361 ) -> DispatchResult {
2362 ensure_signed(origin)?;
2363 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2365
2366 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2367
2368 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2371 T::StakeAdapter::withdraw_unbonded(
2372 Pool::from(pool.bonded_account()),
2373 num_slashing_spans,
2374 )?;
2375
2376 Ok(())
2377 }
2378
2379 #[pallet::call_index(5)]
2402 #[pallet::weight(
2403 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2404 )]
2405 pub fn withdraw_unbonded(
2406 origin: OriginFor<T>,
2407 member_account: AccountIdLookupOf<T>,
2408 num_slashing_spans: u32,
2409 ) -> DispatchResultWithPostInfo {
2410 let caller = ensure_signed(origin)?;
2411 let member_account = T::Lookup::lookup(member_account)?;
2412 ensure!(
2414 !Self::api_member_needs_delegate_migration(member_account.clone()),
2415 Error::<T>::NotMigrated
2416 );
2417
2418 let mut member =
2419 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2420 let current_era = T::StakeAdapter::current_era();
2421
2422 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2423 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2424 let mut sub_pools =
2425 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2426
2427 let slash_weight =
2428 match Self::do_apply_slash(&member_account, None, false) {
2430 Ok(_) => T::WeightInfo::apply_slash(),
2431 Err(e) => {
2432 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2433 if Err(e) == no_pending_slash {
2435 T::WeightInfo::apply_slash_fail()
2436 } else {
2437 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2439 }
2440 }
2441
2442 };
2443
2444 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2445 let pool_account = bonded_pool.bonded_account();
2446
2447 let withdrawn_points = member.withdraw_unlocked(current_era);
2449 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2450
2451 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2454 Pool::from(bonded_pool.bonded_account()),
2455 num_slashing_spans,
2456 )?;
2457
2458 ensure!(
2461 !stash_killed || caller == bonded_pool.roles.depositor,
2462 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2463 );
2464
2465 if stash_killed {
2466 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2468 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2469 }
2470
2471 }
2478
2479 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2480 let balance_to_unbond = withdrawn_points
2481 .iter()
2482 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2483 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2484 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2485 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2486 if era_pool.points.is_zero() {
2487 sub_pools.with_era.remove(era);
2488 }
2489 accumulator.saturating_add(balance_to_unbond)
2490 } else {
2491 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2494 }
2495 })
2496 .min(T::StakeAdapter::transferable_balance(
2504 Pool::from(bonded_pool.bonded_account()),
2505 Member::from(member_account.clone()),
2506 ));
2507
2508 T::StakeAdapter::member_withdraw(
2511 Member::from(member_account.clone()),
2512 Pool::from(bonded_pool.bonded_account()),
2513 balance_to_unbond,
2514 num_slashing_spans,
2515 )?;
2516
2517 Self::deposit_event(Event::<T>::Withdrawn {
2518 member: member_account.clone(),
2519 pool_id: member.pool_id,
2520 points: sum_unlocked_points,
2521 balance: balance_to_unbond,
2522 });
2523
2524 let post_info_weight = if member.total_points().is_zero() {
2525 ClaimPermissions::<T>::remove(&member_account);
2527
2528 PoolMembers::<T>::remove(&member_account);
2530
2531 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2533 Member::from(member_account.clone()),
2534 ) {
2535 Some(dangling_delegation) => {
2536 T::StakeAdapter::member_withdraw(
2537 Member::from(member_account.clone()),
2538 Pool::from(bonded_pool.bonded_account()),
2539 dangling_delegation,
2540 num_slashing_spans,
2541 )?;
2542 dangling_delegation
2543 },
2544 None => Zero::zero(),
2545 };
2546
2547 Self::deposit_event(Event::<T>::MemberRemoved {
2548 pool_id: member.pool_id,
2549 member: member_account.clone(),
2550 released_balance: dangling_withdrawal,
2551 });
2552
2553 if member_account == bonded_pool.roles.depositor {
2554 Pallet::<T>::dissolve_pool(bonded_pool);
2555 Weight::default()
2556 } else {
2557 bonded_pool.dec_members().put();
2558 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2559 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2560 }
2561 } else {
2562 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2564 PoolMembers::<T>::insert(&member_account, member);
2565 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2566 };
2567
2568 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2569 }
2570
2571 #[pallet::call_index(6)]
2589 #[pallet::weight(T::WeightInfo::create())]
2590 pub fn create(
2591 origin: OriginFor<T>,
2592 #[pallet::compact] amount: BalanceOf<T>,
2593 root: AccountIdLookupOf<T>,
2594 nominator: AccountIdLookupOf<T>,
2595 bouncer: AccountIdLookupOf<T>,
2596 ) -> DispatchResult {
2597 let depositor = ensure_signed(origin)?;
2598
2599 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2600 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2601 Ok(*id)
2602 })?;
2603
2604 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2605 }
2606
2607 #[pallet::call_index(7)]
2614 #[pallet::weight(T::WeightInfo::create())]
2615 pub fn create_with_pool_id(
2616 origin: OriginFor<T>,
2617 #[pallet::compact] amount: BalanceOf<T>,
2618 root: AccountIdLookupOf<T>,
2619 nominator: AccountIdLookupOf<T>,
2620 bouncer: AccountIdLookupOf<T>,
2621 pool_id: PoolId,
2622 ) -> DispatchResult {
2623 let depositor = ensure_signed(origin)?;
2624
2625 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2626 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2627
2628 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2629 }
2630
2631 #[pallet::call_index(8)]
2644 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2645 pub fn nominate(
2646 origin: OriginFor<T>,
2647 pool_id: PoolId,
2648 validators: Vec<T::AccountId>,
2649 ) -> DispatchResult {
2650 let who = ensure_signed(origin)?;
2651 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2652 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2654 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2655
2656 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2657 .ok_or(Error::<T>::PoolMemberNotFound)?
2658 .active_points();
2659
2660 ensure!(
2661 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2662 Error::<T>::MinimumBondNotMet
2663 );
2664
2665 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2666 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2667 )
2668 }
2669
2670 #[pallet::call_index(9)]
2681 #[pallet::weight(T::WeightInfo::set_state())]
2682 pub fn set_state(
2683 origin: OriginFor<T>,
2684 pool_id: PoolId,
2685 state: PoolState,
2686 ) -> DispatchResult {
2687 let who = ensure_signed(origin)?;
2688 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2689 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2690 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2692
2693 if bonded_pool.can_toggle_state(&who) {
2694 bonded_pool.set_state(state);
2695 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2696 bonded_pool.set_state(PoolState::Destroying);
2698 } else {
2699 Err(Error::<T>::CanNotChangeState)?;
2700 }
2701
2702 bonded_pool.put();
2703
2704 Ok(())
2705 }
2706
2707 #[pallet::call_index(10)]
2712 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2713 pub fn set_metadata(
2714 origin: OriginFor<T>,
2715 pool_id: PoolId,
2716 metadata: Vec<u8>,
2717 ) -> DispatchResult {
2718 let who = ensure_signed(origin)?;
2719 let metadata: BoundedVec<_, _> =
2720 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2721 ensure!(
2722 BondedPool::<T>::get(pool_id)
2723 .ok_or(Error::<T>::PoolNotFound)?
2724 .can_set_metadata(&who),
2725 Error::<T>::DoesNotHavePermission
2726 );
2727 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2729
2730 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2731
2732 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2733
2734 Ok(())
2735 }
2736
2737 #[pallet::call_index(11)]
2749 #[pallet::weight(T::WeightInfo::set_configs())]
2750 pub fn set_configs(
2751 origin: OriginFor<T>,
2752 min_join_bond: ConfigOp<BalanceOf<T>>,
2753 min_create_bond: ConfigOp<BalanceOf<T>>,
2754 max_pools: ConfigOp<u32>,
2755 max_members: ConfigOp<u32>,
2756 max_members_per_pool: ConfigOp<u32>,
2757 global_max_commission: ConfigOp<Perbill>,
2758 ) -> DispatchResult {
2759 T::AdminOrigin::ensure_origin(origin)?;
2760
2761 macro_rules! config_op_exp {
2762 ($storage:ty, $op:ident) => {
2763 match $op {
2764 ConfigOp::Noop => (),
2765 ConfigOp::Set(v) => <$storage>::put(v),
2766 ConfigOp::Remove => <$storage>::kill(),
2767 }
2768 };
2769 }
2770
2771 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2772 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2773 config_op_exp!(MaxPools::<T>, max_pools);
2774 config_op_exp!(MaxPoolMembers::<T>, max_members);
2775 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2776 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2777
2778 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2779 min_join_bond: MinJoinBond::<T>::get(),
2780 min_create_bond: MinCreateBond::<T>::get(),
2781 max_pools: MaxPools::<T>::get(),
2782 max_members: MaxPoolMembers::<T>::get(),
2783 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2784 global_max_commission: GlobalMaxCommission::<T>::get(),
2785 });
2786
2787 Ok(())
2788 }
2789
2790 #[pallet::call_index(12)]
2798 #[pallet::weight(T::WeightInfo::update_roles())]
2799 pub fn update_roles(
2800 origin: OriginFor<T>,
2801 pool_id: PoolId,
2802 new_root: ConfigOp<T::AccountId>,
2803 new_nominator: ConfigOp<T::AccountId>,
2804 new_bouncer: ConfigOp<T::AccountId>,
2805 ) -> DispatchResult {
2806 let mut bonded_pool = match ensure_root(origin.clone()) {
2807 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2808 Err(sp_runtime::traits::BadOrigin) => {
2809 let who = ensure_signed(origin)?;
2810 let bonded_pool =
2811 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2812 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2813 bonded_pool
2814 },
2815 };
2816
2817 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2819
2820 match new_root {
2821 ConfigOp::Noop => (),
2822 ConfigOp::Remove => bonded_pool.roles.root = None,
2823 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2824 };
2825 match new_nominator {
2826 ConfigOp::Noop => (),
2827 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2828 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2829 };
2830 match new_bouncer {
2831 ConfigOp::Noop => (),
2832 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2833 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2834 };
2835
2836 Self::deposit_event(Event::<T>::RolesUpdated {
2837 root: bonded_pool.roles.root.clone(),
2838 nominator: bonded_pool.roles.nominator.clone(),
2839 bouncer: bonded_pool.roles.bouncer.clone(),
2840 });
2841
2842 bonded_pool.put();
2843 Ok(())
2844 }
2845
2846 #[pallet::call_index(13)]
2864 #[pallet::weight(T::WeightInfo::chill())]
2865 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2866 let who = ensure_signed(origin)?;
2867 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2868 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2870
2871 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2872 .ok_or(Error::<T>::PoolMemberNotFound)?
2873 .active_points();
2874
2875 if bonded_pool.points_to_balance(depositor_points) >=
2876 T::StakeAdapter::minimum_nominator_bond()
2877 {
2878 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2879 }
2880
2881 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2882 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2883 })
2884 }
2885
2886 #[pallet::call_index(14)]
2896 #[pallet::weight(
2897 T::WeightInfo::bond_extra_transfer()
2898 .max(T::WeightInfo::bond_extra_other())
2899 )]
2900 pub fn bond_extra_other(
2901 origin: OriginFor<T>,
2902 member: AccountIdLookupOf<T>,
2903 extra: BondExtra<BalanceOf<T>>,
2904 ) -> DispatchResult {
2905 let who = ensure_signed(origin)?;
2906 let member_account = T::Lookup::lookup(member)?;
2907 ensure!(
2909 !Self::api_member_needs_delegate_migration(member_account.clone()),
2910 Error::<T>::NotMigrated
2911 );
2912
2913 Self::do_bond_extra(who, member_account, extra)
2914 }
2915
2916 #[pallet::call_index(15)]
2924 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2925 pub fn set_claim_permission(
2926 origin: OriginFor<T>,
2927 permission: ClaimPermission,
2928 ) -> DispatchResult {
2929 let who = ensure_signed(origin)?;
2930 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2931
2932 ensure!(
2934 !Self::api_member_needs_delegate_migration(who.clone()),
2935 Error::<T>::NotMigrated
2936 );
2937
2938 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2939 *source = permission;
2940 });
2941
2942 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2943 member: who,
2944 permission,
2945 });
2946
2947 Ok(())
2948 }
2949
2950 #[pallet::call_index(16)]
2955 #[pallet::weight(T::WeightInfo::claim_payout())]
2956 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2957 let signer = ensure_signed(origin)?;
2958 ensure!(
2960 !Self::api_member_needs_delegate_migration(other.clone()),
2961 Error::<T>::NotMigrated
2962 );
2963
2964 Self::do_claim_payout(signer, other)
2965 }
2966
2967 #[pallet::call_index(17)]
2974 #[pallet::weight(T::WeightInfo::set_commission())]
2975 pub fn set_commission(
2976 origin: OriginFor<T>,
2977 pool_id: PoolId,
2978 new_commission: Option<(Perbill, T::AccountId)>,
2979 ) -> DispatchResult {
2980 let who = ensure_signed(origin)?;
2981 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2982 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2984
2985 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2986
2987 let mut reward_pool = RewardPools::<T>::get(pool_id)
2988 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2989 reward_pool.update_records(
2992 pool_id,
2993 bonded_pool.points,
2994 bonded_pool.commission.current(),
2995 )?;
2996 RewardPools::insert(pool_id, reward_pool);
2997
2998 bonded_pool.commission.try_update_current(&new_commission)?;
2999 bonded_pool.put();
3000 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
3001 pool_id,
3002 current: new_commission,
3003 });
3004 Ok(())
3005 }
3006
3007 #[pallet::call_index(18)]
3013 #[pallet::weight(T::WeightInfo::set_commission_max())]
3014 pub fn set_commission_max(
3015 origin: OriginFor<T>,
3016 pool_id: PoolId,
3017 max_commission: Perbill,
3018 ) -> DispatchResult {
3019 let who = ensure_signed(origin)?;
3020 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3021 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3023
3024 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3025
3026 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3027 bonded_pool.put();
3028
3029 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3030 Ok(())
3031 }
3032
3033 #[pallet::call_index(19)]
3038 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3039 pub fn set_commission_change_rate(
3040 origin: OriginFor<T>,
3041 pool_id: PoolId,
3042 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3043 ) -> DispatchResult {
3044 let who = ensure_signed(origin)?;
3045 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3046 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3048 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3049
3050 bonded_pool.commission.try_update_change_rate(change_rate)?;
3051 bonded_pool.put();
3052
3053 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3054 pool_id,
3055 change_rate,
3056 });
3057 Ok(())
3058 }
3059
3060 #[pallet::call_index(20)]
3077 #[pallet::weight(T::WeightInfo::claim_commission())]
3078 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3079 let who = ensure_signed(origin)?;
3080 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3082
3083 Self::do_claim_commission(who, pool_id)
3084 }
3085
3086 #[pallet::call_index(21)]
3094 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3095 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3096 let who = ensure_signed(origin)?;
3097 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3099
3100 Self::do_adjust_pool_deposit(who, pool_id)
3101 }
3102
3103 #[pallet::call_index(22)]
3108 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3109 pub fn set_commission_claim_permission(
3110 origin: OriginFor<T>,
3111 pool_id: PoolId,
3112 permission: Option<CommissionClaimPermission<T::AccountId>>,
3113 ) -> DispatchResult {
3114 let who = ensure_signed(origin)?;
3115 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3116 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3118 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3119
3120 bonded_pool.commission.claim_permission = permission.clone();
3121 bonded_pool.put();
3122
3123 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3124 pool_id,
3125 permission,
3126 });
3127
3128 Ok(())
3129 }
3130
3131 #[pallet::call_index(23)]
3141 #[pallet::weight(T::WeightInfo::apply_slash())]
3142 pub fn apply_slash(
3143 origin: OriginFor<T>,
3144 member_account: AccountIdLookupOf<T>,
3145 ) -> DispatchResultWithPostInfo {
3146 ensure!(
3147 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3148 Error::<T>::NotSupported
3149 );
3150
3151 let who = ensure_signed(origin)?;
3152 let member_account = T::Lookup::lookup(member_account)?;
3153 Self::do_apply_slash(&member_account, Some(who), true)?;
3154
3155 Ok(Pays::No.into())
3157 }
3158
3159 #[pallet::call_index(24)]
3169 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3170 pub fn migrate_delegation(
3171 origin: OriginFor<T>,
3172 member_account: AccountIdLookupOf<T>,
3173 ) -> DispatchResultWithPostInfo {
3174 let _caller = ensure_signed(origin)?;
3175
3176 ensure!(
3178 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3179 Error::<T>::NotSupported
3180 );
3181
3182 let member_account = T::Lookup::lookup(member_account)?;
3184 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3185
3186 let member =
3187 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3188
3189 ensure!(
3191 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3192 member.pool_id
3193 ))) == adapter::StakeStrategyType::Delegate,
3194 Error::<T>::NotMigrated
3195 );
3196
3197 let pool_contribution = member.total_balance();
3198 ensure!(
3201 pool_contribution >= T::Currency::minimum_balance(),
3202 Error::<T>::MinimumBondNotMet
3203 );
3204
3205 let delegation =
3206 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3207 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3209
3210 T::StakeAdapter::migrate_delegation(
3211 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3212 Member::from(member_account),
3213 pool_contribution,
3214 )?;
3215
3216 Ok(Pays::No.into())
3218 }
3219
3220 #[pallet::call_index(25)]
3230 #[pallet::weight(T::WeightInfo::pool_migrate())]
3231 pub fn migrate_pool_to_delegate_stake(
3232 origin: OriginFor<T>,
3233 pool_id: PoolId,
3234 ) -> DispatchResultWithPostInfo {
3235 ensure!(
3237 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3238 Error::<T>::NotSupported
3239 );
3240
3241 let _caller = ensure_signed(origin)?;
3242 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3244 ensure!(
3245 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3246 adapter::StakeStrategyType::Transfer,
3247 Error::<T>::AlreadyMigrated
3248 );
3249
3250 Self::migrate_to_delegate_stake(pool_id)?;
3251 Ok(Pays::No.into())
3252 }
3253 }
3254
3255 #[pallet::hooks]
3256 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3257 #[cfg(feature = "try-runtime")]
3258 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3259 Self::do_try_state(u8::MAX)
3260 }
3261
3262 fn integrity_test() {
3263 assert!(
3264 T::MaxPointsToBalance::get() > 0,
3265 "Minimum points to balance ratio must be greater than 0"
3266 );
3267 assert!(
3268 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3269 "There must be more unbonding pools then the bonding duration /
3270 so a slash can be applied to relevant unbonding pools. (We assume /
3271 the bonding duration > slash deffer duration.",
3272 );
3273 }
3274 }
3275}
3276
3277impl<T: Config> Pallet<T> {
3278 pub fn depositor_min_bond() -> BalanceOf<T> {
3286 T::StakeAdapter::minimum_nominator_bond()
3287 .max(MinCreateBond::<T>::get())
3288 .max(MinJoinBond::<T>::get())
3289 .max(T::Currency::minimum_balance())
3290 }
3291 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3296 let reward_account = bonded_pool.reward_account();
3297 let bonded_account = bonded_pool.bonded_account();
3298
3299 ReversePoolIdLookup::<T>::remove(&bonded_account);
3300 RewardPools::<T>::remove(bonded_pool.id);
3301 SubPoolsStorage::<T>::remove(bonded_pool.id);
3302
3303 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3305
3306 defensive_assert!(
3314 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3315 "reward account of dissolving pool should have no consumers"
3316 );
3317 defensive_assert!(
3318 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3319 "bonded account of dissolving pool should have no consumers"
3320 );
3321 defensive_assert!(
3322 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3323 "dissolving pool should not have any stake in the staking pallet"
3324 );
3325
3326 let reward_pool_remaining = T::Currency::reducible_balance(
3329 &reward_account,
3330 Preservation::Expendable,
3331 Fortitude::Polite,
3332 );
3333 let _ = T::Currency::transfer(
3334 &reward_account,
3335 &bonded_pool.roles.depositor,
3336 reward_pool_remaining,
3337 Preservation::Expendable,
3338 );
3339
3340 defensive_assert!(
3341 T::Currency::total_balance(&reward_account) == Zero::zero(),
3342 "could not transfer all amount to depositor while dissolving pool"
3343 );
3344 T::Currency::set_balance(&reward_account, Zero::zero());
3346
3347 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3349
3350 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3351 Metadata::<T>::remove(bonded_pool.id);
3353
3354 bonded_pool.remove();
3355 }
3356
3357 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3359 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3360 }
3361
3362 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3363 T::StakeAdapter::migrate_nominator_to_agent(
3364 Pool::from(Self::generate_bonded_account(id)),
3365 &Self::generate_reward_account(id),
3366 )
3367 }
3368
3369 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3371 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3374 }
3375
3376 fn get_member_with_pools(
3378 who: &T::AccountId,
3379 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3380 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3381 let bonded_pool =
3382 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3383 let reward_pool =
3384 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3385 Ok((member, bonded_pool, reward_pool))
3386 }
3387
3388 fn put_member_with_pools(
3391 member_account: &T::AccountId,
3392 member: PoolMember<T>,
3393 bonded_pool: BondedPool<T>,
3394 reward_pool: RewardPool<T>,
3395 ) {
3396 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3399 debug_assert_eq!(member.pool_id, bonded_pool.id);
3400
3401 bonded_pool.put();
3402 RewardPools::insert(member.pool_id, reward_pool);
3403 PoolMembers::<T>::insert(member_account, member);
3404 }
3405
3406 fn balance_to_point(
3409 current_balance: BalanceOf<T>,
3410 current_points: BalanceOf<T>,
3411 new_funds: BalanceOf<T>,
3412 ) -> BalanceOf<T> {
3413 let u256 = T::BalanceToU256::convert;
3414 let balance = T::U256ToBalance::convert;
3415 match (current_balance.is_zero(), current_points.is_zero()) {
3416 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3417 (true, false) => {
3418 new_funds.saturating_mul(current_points)
3421 },
3422 (false, false) => {
3423 balance(
3425 u256(current_points)
3426 .saturating_mul(u256(new_funds))
3427 .div(u256(current_balance)),
3429 )
3430 },
3431 }
3432 }
3433
3434 fn point_to_balance(
3437 current_balance: BalanceOf<T>,
3438 current_points: BalanceOf<T>,
3439 points: BalanceOf<T>,
3440 ) -> BalanceOf<T> {
3441 let u256 = T::BalanceToU256::convert;
3442 let balance = T::U256ToBalance::convert;
3443 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3444 return Zero::zero()
3446 }
3447
3448 balance(
3450 u256(current_balance)
3451 .saturating_mul(u256(points))
3452 .div(u256(current_points)),
3454 )
3455 }
3456
3457 fn do_reward_payout(
3461 member_account: &T::AccountId,
3462 member: &mut PoolMember<T>,
3463 bonded_pool: &mut BondedPool<T>,
3464 reward_pool: &mut RewardPool<T>,
3465 ) -> Result<BalanceOf<T>, DispatchError> {
3466 debug_assert_eq!(member.pool_id, bonded_pool.id);
3467 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3468
3469 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3471
3472 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3473 bonded_pool.id,
3474 bonded_pool.points,
3475 bonded_pool.commission.current(),
3476 )?;
3477
3478 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3481 if pending_rewards.is_zero() {
3482 return Ok(pending_rewards)
3483 }
3484
3485 member.last_recorded_reward_counter = current_reward_counter;
3487 reward_pool.register_claimed_reward(pending_rewards);
3488
3489 T::Currency::transfer(
3490 &bonded_pool.reward_account(),
3491 member_account,
3492 pending_rewards,
3493 Preservation::Preserve,
3496 )?;
3497
3498 Self::deposit_event(Event::<T>::PaidOut {
3499 member: member_account.clone(),
3500 pool_id: member.pool_id,
3501 payout: pending_rewards,
3502 });
3503 Ok(pending_rewards)
3504 }
3505
3506 fn do_create(
3507 who: T::AccountId,
3508 amount: BalanceOf<T>,
3509 root: AccountIdLookupOf<T>,
3510 nominator: AccountIdLookupOf<T>,
3511 bouncer: AccountIdLookupOf<T>,
3512 pool_id: PoolId,
3513 ) -> DispatchResult {
3514 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3516
3517 let root = T::Lookup::lookup(root)?;
3518 let nominator = T::Lookup::lookup(nominator)?;
3519 let bouncer = T::Lookup::lookup(bouncer)?;
3520
3521 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3522 ensure!(
3523 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3524 Error::<T>::MaxPools
3525 );
3526 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3527 let mut bonded_pool = BondedPool::<T>::new(
3528 pool_id,
3529 PoolRoles {
3530 root: Some(root),
3531 nominator: Some(nominator),
3532 bouncer: Some(bouncer),
3533 depositor: who.clone(),
3534 },
3535 );
3536
3537 bonded_pool.try_inc_members()?;
3538 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3539
3540 T::Currency::transfer(
3542 &who,
3543 &bonded_pool.reward_account(),
3544 T::Currency::minimum_balance(),
3545 Preservation::Expendable,
3546 )?;
3547
3548 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3550
3551 PoolMembers::<T>::insert(
3552 who.clone(),
3553 PoolMember::<T> {
3554 pool_id,
3555 points,
3556 last_recorded_reward_counter: Zero::zero(),
3557 unbonding_eras: Default::default(),
3558 },
3559 );
3560 RewardPools::<T>::insert(
3561 pool_id,
3562 RewardPool::<T> {
3563 last_recorded_reward_counter: Zero::zero(),
3564 last_recorded_total_payouts: Zero::zero(),
3565 total_rewards_claimed: Zero::zero(),
3566 total_commission_pending: Zero::zero(),
3567 total_commission_claimed: Zero::zero(),
3568 },
3569 );
3570 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3571
3572 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3573
3574 Self::deposit_event(Event::<T>::Bonded {
3575 member: who,
3576 pool_id,
3577 bonded: amount,
3578 joined: true,
3579 });
3580 bonded_pool.put();
3581
3582 Ok(())
3583 }
3584
3585 fn do_bond_extra(
3586 signer: T::AccountId,
3587 member_account: T::AccountId,
3588 extra: BondExtra<BalanceOf<T>>,
3589 ) -> DispatchResult {
3590 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3592
3593 if signer != member_account {
3594 ensure!(
3595 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3596 Error::<T>::DoesNotHavePermission
3597 );
3598 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3599 }
3600
3601 let (mut member, mut bonded_pool, mut reward_pool) =
3602 Self::get_member_with_pools(&member_account)?;
3603
3604 reward_pool.update_records(
3607 bonded_pool.id,
3608 bonded_pool.points,
3609 bonded_pool.commission.current(),
3610 )?;
3611 let claimed = Self::do_reward_payout(
3612 &member_account,
3613 &mut member,
3614 &mut bonded_pool,
3615 &mut reward_pool,
3616 )?;
3617
3618 let (points_issued, bonded) = match extra {
3619 BondExtra::FreeBalance(amount) =>
3620 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
3621 BondExtra::Rewards =>
3622 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
3623 };
3624
3625 bonded_pool.ok_to_be_open()?;
3626 member.points =
3627 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3628
3629 Self::deposit_event(Event::<T>::Bonded {
3630 member: member_account.clone(),
3631 pool_id: member.pool_id,
3632 bonded,
3633 joined: false,
3634 });
3635 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3636
3637 Ok(())
3638 }
3639
3640 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3641 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3642 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3643
3644 let mut reward_pool = RewardPools::<T>::get(pool_id)
3645 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3646
3647 reward_pool.update_records(
3650 pool_id,
3651 bonded_pool.points,
3652 bonded_pool.commission.current(),
3653 )?;
3654
3655 let commission = reward_pool.total_commission_pending;
3656 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3657
3658 let payee = bonded_pool
3659 .commission
3660 .current
3661 .as_ref()
3662 .map(|(_, p)| p.clone())
3663 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3664
3665 T::Currency::transfer(
3667 &bonded_pool.reward_account(),
3668 &payee,
3669 commission,
3670 Preservation::Preserve,
3671 )?;
3672
3673 reward_pool.total_commission_claimed =
3675 reward_pool.total_commission_claimed.saturating_add(commission);
3676 reward_pool.total_commission_pending = Zero::zero();
3678 RewardPools::<T>::insert(pool_id, reward_pool);
3679
3680 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3681 Ok(())
3682 }
3683
3684 pub(crate) fn do_claim_payout(
3685 signer: T::AccountId,
3686 member_account: T::AccountId,
3687 ) -> DispatchResult {
3688 if signer != member_account {
3689 ensure!(
3690 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3691 Error::<T>::DoesNotHavePermission
3692 );
3693 }
3694 let (mut member, mut bonded_pool, mut reward_pool) =
3695 Self::get_member_with_pools(&member_account)?;
3696
3697 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3698
3699 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3700 Ok(())
3701 }
3702
3703 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3704 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3705
3706 let reward_acc = &bonded_pool.reward_account();
3707 let pre_frozen_balance =
3708 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3709 let min_balance = T::Currency::minimum_balance();
3710
3711 if pre_frozen_balance == min_balance {
3712 return Err(Error::<T>::NothingToAdjust.into())
3713 }
3714
3715 Self::freeze_pool_deposit(reward_acc)?;
3717
3718 if pre_frozen_balance > min_balance {
3719 let excess = pre_frozen_balance.saturating_sub(min_balance);
3721 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3722 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3723 pool_id: pool,
3724 amount: excess,
3725 });
3726 } else {
3727 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3729 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3730 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3731 pool_id: pool,
3732 amount: deficit,
3733 });
3734 }
3735
3736 Ok(())
3737 }
3738
3739 fn do_apply_slash(
3741 member_account: &T::AccountId,
3742 reporter: Option<T::AccountId>,
3743 enforce_min_slash: bool,
3744 ) -> DispatchResult {
3745 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3746
3747 let pending_slash =
3748 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3749
3750 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3752
3753 if enforce_min_slash {
3754 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3756 }
3757
3758 T::StakeAdapter::member_slash(
3759 Member::from(member_account.clone()),
3760 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3761 pending_slash,
3762 reporter,
3763 )
3764 }
3765
3766 fn member_pending_slash(
3770 member_account: Member<T::AccountId>,
3771 pool_member: PoolMember<T>,
3772 ) -> Result<BalanceOf<T>, DispatchError> {
3773 debug_assert!(
3775 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3776 pool_member
3777 );
3778
3779 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3780 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3783 return Ok(Zero::zero())
3784 }
3785
3786 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3788 .ok_or(Error::<T>::NotMigrated)?;
3790
3791 let expected_balance = pool_member.total_balance();
3793
3794 Ok(actual_balance.saturating_sub(expected_balance))
3796 }
3797
3798 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3800 T::Currency::set_freeze(
3801 &FreezeReason::PoolMinBalance.into(),
3802 reward_acc,
3803 T::Currency::minimum_balance(),
3804 )
3805 }
3806
3807 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3809 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3810 }
3811
3812 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3849 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3850 if level.is_zero() {
3851 return Ok(())
3852 }
3853 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3856 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3857 ensure!(
3858 bonded_pools == reward_pools,
3859 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3860 );
3861
3862 ensure!(
3863 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3864 "`SubPoolsStorage` must be a subset of the above superset."
3865 );
3866 ensure!(
3867 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3868 "`Metadata` keys must be a subset of the above superset."
3869 );
3870
3871 ensure!(
3872 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3873 Error::<T>::MaxPools
3874 );
3875
3876 for id in reward_pools {
3877 let account = Self::generate_reward_account(id);
3878 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3879 T::Currency::minimum_balance()
3880 {
3881 log!(
3882 warn,
3883 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3884 changed recently. Pool operators should be notified to top up the reward \
3885 account",
3886 id,
3887 T::Currency::reducible_balance(
3888 &account,
3889 Preservation::Expendable,
3890 Fortitude::Polite
3891 ),
3892 T::Currency::minimum_balance(),
3893 )
3894 }
3895 }
3896
3897 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3898 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3899 let mut all_members = 0u32;
3900 let mut total_balance_members = Default::default();
3901 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3902 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3903 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3904 *pools_members.entry(d.pool_id).or_default() += 1;
3905 all_members += 1;
3906
3907 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3908 if !bonded_pool.points.is_zero() {
3909 let commission = bonded_pool.commission.current();
3910 let (current_rc, _) = reward_pool
3911 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3912 .unwrap();
3913 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3914 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3915 } total_balance_members += d.total_balance();
3917
3918 Ok(())
3919 })?;
3920
3921 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3922 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3925 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3926
3927 if !pending_rewards_lt_leftover_bal {
3930 log::warn!(
3931 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
3932 id,
3933 pools_members_pending_rewards.get(&id),
3934 RewardPool::<T>::current_balance(id)
3935 );
3936 }
3937 Ok(())
3938 })?;
3939
3940 let mut expected_tvl: BalanceOf<T> = Default::default();
3941 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
3942 let bonded_pool = BondedPool { id, inner };
3943 ensure!(
3944 pools_members.get(&id).copied().unwrap_or_default() ==
3945 bonded_pool.member_counter,
3946 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
3947 );
3948 ensure!(
3949 MaxPoolMembersPerPool::<T>::get()
3950 .map_or(true, |max| bonded_pool.member_counter <= max),
3951 Error::<T>::MaxPoolMembers
3952 );
3953
3954 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
3955 ensure!(
3956 bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) ||
3957 depositor.active_points() >= MinCreateBond::<T>::get(),
3958 "depositor must always have MinCreateBond stake in the pool, except for when the \
3959 pool is being destroyed and the depositor is the last member",
3960 );
3961
3962 ensure!(
3963 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
3964 "Each `BondedPool.points` must never be lower than the pool's balance"
3965 );
3966
3967 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
3968
3969 Ok(())
3970 })?;
3971
3972 ensure!(
3973 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
3974 Error::<T>::MaxPoolMembers
3975 );
3976
3977 ensure!(
3978 TotalValueLocked::<T>::get() == expected_tvl,
3979 "TVL deviates from the actual sum of funds of all Pools."
3980 );
3981
3982 ensure!(
3983 TotalValueLocked::<T>::get() <= total_balance_members,
3984 "TVL must be equal to or less than the total balance of all PoolMembers."
3985 );
3986
3987 if level <= 1 {
3988 return Ok(())
3989 }
3990
3991 for (pool_id, _pool) in BondedPools::<T>::iter() {
3992 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
3993 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
3994
3995 let sum_unbonding_balance = subs.sum_unbonding_balance();
3996 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
3997 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
3999 .unwrap_or(T::Currency::total_balance(&pool_account));
4002
4003 if total_balance < bonded_balance + sum_unbonding_balance {
4004 log!(
4005 warn,
4006 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4007 pool_id,
4008 _pool,
4009 total_balance,
4010 bonded_balance,
4011 sum_unbonding_balance
4012 )
4013 };
4014 }
4015
4016 Self::check_ed_imbalance()?;
4019
4020 Ok(())
4021 }
4022
4023 #[cfg(any(
4027 feature = "try-runtime",
4028 feature = "runtime-benchmarks",
4029 feature = "fuzzing",
4030 test,
4031 debug_assertions
4032 ))]
4033 pub fn check_ed_imbalance() -> Result<(), DispatchError> {
4034 let mut failed: u32 = 0;
4035 BondedPools::<T>::iter_keys().for_each(|id| {
4036 let reward_acc = Self::generate_reward_account(id);
4037 let frozen_balance =
4038 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4039
4040 let expected_frozen_balance = T::Currency::minimum_balance();
4041 if frozen_balance != expected_frozen_balance {
4042 failed += 1;
4043 log::warn!(
4044 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}",
4045 id,
4046 expected_frozen_balance,
4047 frozen_balance,
4048 );
4049 }
4050 });
4051
4052 ensure!(failed == 0, "Some pools do not have correct ED frozen");
4053 Ok(())
4054 }
4055 #[cfg(any(feature = "runtime-benchmarks", test))]
4060 pub fn fully_unbond(
4061 origin: frame_system::pallet_prelude::OriginFor<T>,
4062 member: T::AccountId,
4063 ) -> DispatchResult {
4064 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4065 let member_lookup = T::Lookup::unlookup(member);
4066 Self::unbond(origin, member_lookup, points)
4067 }
4068}
4069
4070impl<T: Config> Pallet<T> {
4071 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4075 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4076 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4077 .zip(BondedPools::<T>::get(pool_member.pool_id))
4078 {
4079 let commission = bonded_pool.commission.current();
4080 let (current_reward_counter, _) = reward_pool
4081 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4082 .ok()?;
4083 return pool_member.pending_rewards(current_reward_counter).ok()
4084 }
4085 }
4086
4087 None
4088 }
4089
4090 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4094 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4095 pool.points_to_balance(points)
4096 } else {
4097 Zero::zero()
4098 }
4099 }
4100
4101 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4105 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4106 let bonded_balance =
4107 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4108 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4109 } else {
4110 Zero::zero()
4111 }
4112 }
4113
4114 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4118 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4119 }
4120
4121 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4128 PoolMembers::<T>::get(who.clone())
4129 .map(|pool_member| {
4130 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4131 })
4132 .unwrap_or_default()
4133 }
4134
4135 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4140 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4142 return false
4143 }
4144
4145 if !BondedPools::<T>::contains_key(pool_id) {
4147 return false
4148 }
4149
4150 let pool_account = Self::generate_bonded_account(pool_id);
4151
4152 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4154 adapter::StakeStrategyType::Delegate
4155 }
4156
4157 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4163 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4165 return false
4166 }
4167
4168 PoolMembers::<T>::get(who.clone())
4169 .map(|pool_member| {
4170 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4171 return false
4173 }
4174
4175 let member_balance = pool_member.total_balance();
4176 let delegated_balance =
4177 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4178
4179 delegated_balance.is_none() && !member_balance.is_zero()
4182 })
4183 .unwrap_or_default()
4184 }
4185
4186 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4191 PoolMembers::<T>::get(who.clone())
4192 .map(|m| m.total_balance())
4193 .unwrap_or_default()
4194 }
4195
4196 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4198 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4199 .unwrap_or_default()
4200 }
4201
4202 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4204 let bonded_account = Self::generate_bonded_account(pool_id);
4205 let reward_account = Self::generate_reward_account(pool_id);
4206 (bonded_account, reward_account)
4207 }
4208}
4209
4210impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4211 fn on_slash(
4217 pool_account: &T::AccountId,
4218 slashed_bonded: BalanceOf<T>,
4221 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4222 total_slashed: BalanceOf<T>,
4223 ) {
4224 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4225 TotalValueLocked::<T>::mutate(|tvl| {
4228 tvl.defensive_saturating_reduce(total_slashed);
4229 });
4230
4231 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4232 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4234 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4235 pool.balance = *slashed_balance;
4236 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4237 era: *era,
4238 pool_id,
4239 balance: *slashed_balance,
4240 });
4241 }
4242 });
4243 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4244 } else if !slashed_unlocking.is_empty() {
4245 defensive!("Expected SubPools were not found");
4246 }
4247 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4248 }
4249
4250 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4253 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4254 TotalValueLocked::<T>::mutate(|tvl| {
4255 tvl.saturating_reduce(amount);
4256 });
4257 }
4258 }
4259}
4260
4261pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4263impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4264 fn contains(t: &T::AccountId) -> bool {
4265 PoolMembers::<T>::contains_key(t)
4266 }
4267}