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 return Err(Error::<T>::DoesNotHavePermission.into());
1278 },
1279 };
1280
1281 Ok(())
1282 }
1283
1284 fn ok_to_withdraw_unbonded_with(
1288 &self,
1289 caller: &T::AccountId,
1290 target_account: &T::AccountId,
1291 ) -> Result<(), DispatchError> {
1292 let is_permissioned = caller == target_account;
1294 ensure!(
1295 is_permissioned || self.can_kick(caller) || self.is_destroying(),
1296 Error::<T>::NotKickerOrDestroying
1297 );
1298 Ok(())
1299 }
1300
1301 fn try_bond_funds(
1309 &mut self,
1310 who: &T::AccountId,
1311 amount: BalanceOf<T>,
1312 ty: BondType,
1313 ) -> Result<BalanceOf<T>, DispatchError> {
1314 let points_issued = self.issue(amount);
1317
1318 T::StakeAdapter::pledge_bond(
1319 Member::from(who.clone()),
1320 Pool::from(self.bonded_account()),
1321 &self.reward_account(),
1322 amount,
1323 ty,
1324 )?;
1325 TotalValueLocked::<T>::mutate(|tvl| {
1326 tvl.saturating_accrue(amount);
1327 });
1328
1329 Ok(points_issued)
1330 }
1331
1332 fn set_state(&mut self, state: PoolState) {
1335 if self.state != state {
1336 self.state = state;
1337 Pallet::<T>::deposit_event(Event::<T>::StateChanged {
1338 pool_id: self.id,
1339 new_state: state,
1340 });
1341 };
1342 }
1343}
1344
1345#[derive(
1351 Encode,
1352 Decode,
1353 MaxEncodedLen,
1354 DecodeWithMemTracking,
1355 TypeInfo,
1356 CloneNoBound,
1357 PartialEqNoBound,
1358 EqNoBound,
1359 DebugNoBound,
1360)]
1361#[cfg_attr(feature = "std", derive(DefaultNoBound))]
1362#[codec(mel_bound(T: Config))]
1363#[scale_info(skip_type_params(T))]
1364pub struct RewardPool<T: Config> {
1365 pub last_recorded_reward_counter: T::RewardCounter,
1370 pub last_recorded_total_payouts: BalanceOf<T>,
1376 pub total_rewards_claimed: BalanceOf<T>,
1378 pub total_commission_pending: BalanceOf<T>,
1380 pub total_commission_claimed: BalanceOf<T>,
1382}
1383
1384impl<T: Config> RewardPool<T> {
1385 pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter {
1387 self.last_recorded_reward_counter
1388 }
1389
1390 fn register_claimed_reward(&mut self, reward: BalanceOf<T>) {
1392 self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward);
1393 }
1394
1395 fn update_records(
1402 &mut self,
1403 id: PoolId,
1404 bonded_points: BalanceOf<T>,
1405 commission: Perbill,
1406 ) -> Result<(), Error<T>> {
1407 let balance = Self::current_balance(id);
1408
1409 let (current_reward_counter, new_pending_commission) =
1410 self.current_reward_counter(id, bonded_points, commission)?;
1411
1412 self.last_recorded_reward_counter = current_reward_counter;
1416
1417 self.total_commission_pending =
1420 self.total_commission_pending.saturating_add(new_pending_commission);
1421
1422 let last_recorded_total_payouts = balance
1426 .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed))
1427 .ok_or(Error::<T>::OverflowRisk)?;
1428
1429 self.last_recorded_total_payouts =
1436 self.last_recorded_total_payouts.max(last_recorded_total_payouts);
1437
1438 Ok(())
1439 }
1440
1441 fn current_reward_counter(
1444 &self,
1445 id: PoolId,
1446 bonded_points: BalanceOf<T>,
1447 commission: Perbill,
1448 ) -> Result<(T::RewardCounter, BalanceOf<T>), Error<T>> {
1449 let balance = Self::current_balance(id);
1450
1451 let current_payout_balance = balance
1456 .saturating_add(self.total_rewards_claimed)
1457 .saturating_add(self.total_commission_claimed)
1458 .saturating_sub(self.last_recorded_total_payouts);
1459
1460 let new_pending_commission = commission * current_payout_balance;
1463 let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission);
1464
1465 let current_reward_counter =
1500 T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points)
1501 .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r))
1502 .ok_or(Error::<T>::OverflowRisk)?;
1503
1504 Ok((current_reward_counter, new_pending_commission))
1505 }
1506
1507 fn current_balance(id: PoolId) -> BalanceOf<T> {
1511 T::Currency::reducible_balance(
1512 &Pallet::<T>::generate_reward_account(id),
1513 Preservation::Expendable,
1514 Fortitude::Polite,
1515 )
1516 }
1517}
1518
1519#[derive(
1521 Encode,
1522 Decode,
1523 MaxEncodedLen,
1524 DecodeWithMemTracking,
1525 TypeInfo,
1526 DefaultNoBound,
1527 DebugNoBound,
1528 CloneNoBound,
1529 PartialEqNoBound,
1530 EqNoBound,
1531)]
1532#[codec(mel_bound(T: Config))]
1533#[scale_info(skip_type_params(T))]
1534pub struct UnbondPool<T: Config> {
1535 pub points: BalanceOf<T>,
1537 pub balance: BalanceOf<T>,
1539}
1540
1541impl<T: Config> UnbondPool<T> {
1542 fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1543 Pallet::<T>::balance_to_point(self.balance, self.points, new_funds)
1544 }
1545
1546 fn point_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
1547 Pallet::<T>::point_to_balance(self.balance, self.points, points)
1548 }
1549
1550 fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
1554 let new_points = self.balance_to_point(new_funds);
1555 self.points = self.points.saturating_add(new_points);
1556 self.balance = self.balance.saturating_add(new_funds);
1557 new_points
1558 }
1559
1560 fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
1565 let balance_to_unbond = self.point_to_balance(points);
1566 self.points = self.points.saturating_sub(points);
1567 self.balance = self.balance.saturating_sub(balance_to_unbond);
1568
1569 balance_to_unbond
1570 }
1571}
1572
1573#[derive(
1574 Encode,
1575 Decode,
1576 MaxEncodedLen,
1577 DecodeWithMemTracking,
1578 TypeInfo,
1579 DefaultNoBound,
1580 DebugNoBound,
1581 CloneNoBound,
1582 PartialEqNoBound,
1583 EqNoBound,
1584)]
1585#[codec(mel_bound(T: Config))]
1586#[scale_info(skip_type_params(T))]
1587pub struct SubPools<T: Config> {
1588 pub no_era: UnbondPool<T>,
1592 pub with_era: BoundedBTreeMap<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>,
1594}
1595
1596impl<T: Config> SubPools<T> {
1597 fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self {
1602 if let Some(newest_era_to_remove) =
1606 current_era.checked_sub(T::PostUnbondingPoolsWindow::get())
1607 {
1608 self.with_era.retain(|k, v| {
1609 if *k > newest_era_to_remove {
1610 true
1612 } else {
1613 self.no_era.points = self.no_era.points.saturating_add(v.points);
1615 self.no_era.balance = self.no_era.balance.saturating_add(v.balance);
1616 false
1617 }
1618 });
1619 }
1620
1621 self
1622 }
1623
1624 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
1626 fn sum_unbonding_balance(&self) -> BalanceOf<T> {
1627 self.no_era.balance.saturating_add(
1628 self.with_era
1629 .values()
1630 .fold(BalanceOf::<T>::zero(), |acc, pool| acc.saturating_add(pool.balance)),
1631 )
1632 }
1633}
1634
1635pub struct TotalUnbondingPools<T: Config>(PhantomData<T>);
1639
1640impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
1641 fn get() -> u32 {
1642 T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
1646 }
1647}
1648
1649#[frame_support::pallet]
1650pub mod pallet {
1651 use super::*;
1652 use frame_support::traits::StorageVersion;
1653 use frame_system::pallet_prelude::{
1654 ensure_root, ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
1655 };
1656 use sp_runtime::Perbill;
1657
1658 const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
1660
1661 #[pallet::pallet]
1662 #[pallet::storage_version(STORAGE_VERSION)]
1663 pub struct Pallet<T>(_);
1664
1665 #[pallet::config]
1666 pub trait Config: frame_system::Config {
1667 #[allow(deprecated)]
1669 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
1670
1671 type WeightInfo: weights::WeightInfo;
1673
1674 type Currency: Mutate<Self::AccountId>
1676 + MutateFreeze<Self::AccountId, Id = Self::RuntimeFreezeReason>;
1677
1678 type RuntimeFreezeReason: From<FreezeReason>;
1680
1681 type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec;
1694
1695 #[pallet::constant]
1697 type PalletId: Get<frame_support::PalletId>;
1698
1699 #[pallet::constant]
1712 type MaxPointsToBalance: Get<u8>;
1713
1714 #[pallet::constant]
1716 type MaxUnbonding: Get<u32>;
1717
1718 type BalanceToU256: Convert<BalanceOf<Self>, U256>;
1720
1721 type U256ToBalance: Convert<U256, BalanceOf<Self>>;
1723
1724 type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
1728
1729 type PostUnbondingPoolsWindow: Get<u32>;
1735
1736 type MaxMetadataLen: Get<u32>;
1738
1739 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
1741
1742 type BlockNumberProvider: BlockNumberProvider;
1744
1745 type Filter: Contains<Self::AccountId>;
1747 }
1748
1749 #[pallet::storage]
1755 pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1756
1757 #[pallet::storage]
1759 pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1760
1761 #[pallet::storage]
1769 pub type MinCreateBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
1770
1771 #[pallet::storage]
1774 pub type MaxPools<T: Config> = StorageValue<_, u32, OptionQuery>;
1775
1776 #[pallet::storage]
1779 pub type MaxPoolMembers<T: Config> = StorageValue<_, u32, OptionQuery>;
1780
1781 #[pallet::storage]
1784 pub type MaxPoolMembersPerPool<T: Config> = StorageValue<_, u32, OptionQuery>;
1785
1786 #[pallet::storage]
1790 pub type GlobalMaxCommission<T: Config> = StorageValue<_, Perbill, OptionQuery>;
1791
1792 #[pallet::storage]
1796 pub type PoolMembers<T: Config> =
1797 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember<T>>;
1798
1799 #[pallet::storage]
1802 pub type BondedPools<T: Config> =
1803 CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner<T>>;
1804
1805 #[pallet::storage]
1808 pub type RewardPools<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool<T>>;
1809
1810 #[pallet::storage]
1813 pub type SubPoolsStorage<T: Config> = CountedStorageMap<_, Twox64Concat, PoolId, SubPools<T>>;
1814
1815 #[pallet::storage]
1817 pub type Metadata<T: Config> =
1818 CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec<u8, T::MaxMetadataLen>, ValueQuery>;
1819
1820 #[pallet::storage]
1822 pub type LastPoolId<T: Config> = StorageValue<_, u32, ValueQuery>;
1823
1824 #[pallet::storage]
1829 pub type ReversePoolIdLookup<T: Config> =
1830 CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>;
1831
1832 #[pallet::storage]
1834 pub type ClaimPermissions<T: Config> =
1835 StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>;
1836
1837 #[pallet::genesis_config]
1838 pub struct GenesisConfig<T: Config> {
1839 pub min_join_bond: BalanceOf<T>,
1840 pub min_create_bond: BalanceOf<T>,
1841 pub max_pools: Option<u32>,
1842 pub max_members_per_pool: Option<u32>,
1843 pub max_members: Option<u32>,
1844 pub global_max_commission: Option<Perbill>,
1845 }
1846
1847 impl<T: Config> Default for GenesisConfig<T> {
1848 fn default() -> Self {
1849 Self {
1850 min_join_bond: Zero::zero(),
1851 min_create_bond: Zero::zero(),
1852 max_pools: Some(16),
1853 max_members_per_pool: Some(32),
1854 max_members: Some(16 * 32),
1855 global_max_commission: None,
1856 }
1857 }
1858 }
1859
1860 #[pallet::genesis_build]
1861 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1862 fn build(&self) {
1863 MinJoinBond::<T>::put(self.min_join_bond);
1864 MinCreateBond::<T>::put(self.min_create_bond);
1865
1866 if let Some(max_pools) = self.max_pools {
1867 MaxPools::<T>::put(max_pools);
1868 }
1869 if let Some(max_members_per_pool) = self.max_members_per_pool {
1870 MaxPoolMembersPerPool::<T>::put(max_members_per_pool);
1871 }
1872 if let Some(max_members) = self.max_members {
1873 MaxPoolMembers::<T>::put(max_members);
1874 }
1875 if let Some(global_max_commission) = self.global_max_commission {
1876 GlobalMaxCommission::<T>::put(global_max_commission);
1877 }
1878 }
1879 }
1880
1881 #[pallet::event]
1883 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
1884 pub enum Event<T: Config> {
1885 Created { depositor: T::AccountId, pool_id: PoolId },
1887 Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf<T>, joined: bool },
1889 PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf<T> },
1891 Unbonded {
1903 member: T::AccountId,
1904 pool_id: PoolId,
1905 balance: BalanceOf<T>,
1906 points: BalanceOf<T>,
1907 era: EraIndex,
1908 },
1909 Withdrawn {
1916 member: T::AccountId,
1917 pool_id: PoolId,
1918 balance: BalanceOf<T>,
1919 points: BalanceOf<T>,
1920 },
1921 Destroyed { pool_id: PoolId },
1923 StateChanged { pool_id: PoolId, new_state: PoolState },
1925 MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf<T> },
1931 RolesUpdated {
1934 root: Option<T::AccountId>,
1935 bouncer: Option<T::AccountId>,
1936 nominator: Option<T::AccountId>,
1937 },
1938 PoolSlashed { pool_id: PoolId, balance: BalanceOf<T> },
1940 UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf<T> },
1942 PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> },
1944 PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill },
1946 PoolCommissionChangeRateUpdated {
1948 pool_id: PoolId,
1949 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
1950 },
1951 PoolCommissionClaimPermissionUpdated {
1953 pool_id: PoolId,
1954 permission: Option<CommissionClaimPermission<T::AccountId>>,
1955 },
1956 PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf<T> },
1958 MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1960 MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> },
1962 MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission },
1964 MetadataUpdated { pool_id: PoolId, caller: T::AccountId },
1966 PoolNominationMade { pool_id: PoolId, caller: T::AccountId },
1969 PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId },
1971 GlobalParamsUpdated {
1973 min_join_bond: BalanceOf<T>,
1974 min_create_bond: BalanceOf<T>,
1975 max_pools: Option<u32>,
1976 max_members: Option<u32>,
1977 max_members_per_pool: Option<u32>,
1978 global_max_commission: Option<Perbill>,
1979 },
1980 }
1981
1982 #[pallet::error]
1983 #[cfg_attr(test, derive(PartialEq))]
1984 pub enum Error<T> {
1985 PoolNotFound,
1987 PoolMemberNotFound,
1989 RewardPoolNotFound,
1991 SubPoolsNotFound,
1993 AccountBelongsToOtherPool,
1996 FullyUnbonding,
1999 MaxUnbondingLimit,
2001 CannotWithdrawAny,
2003 MinimumBondNotMet,
2009 OverflowRisk,
2011 NotDestroying,
2014 NotNominator,
2016 NotKickerOrDestroying,
2018 NotOpen,
2020 MaxPools,
2022 MaxPoolMembers,
2024 CanNotChangeState,
2026 DoesNotHavePermission,
2028 MetadataExceedsMaxLen,
2030 Defensive(DefensiveError),
2033 PartialUnbondNotAllowedPermissionlessly,
2035 MaxCommissionRestricted,
2037 CommissionExceedsMaximum,
2039 CommissionExceedsGlobalMaximum,
2041 CommissionChangeThrottled,
2043 CommissionChangeRateNotAllowed,
2045 NoPendingCommission,
2047 NoCommissionCurrentSet,
2049 PoolIdInUse,
2051 InvalidPoolId,
2053 BondExtraRestricted,
2055 NothingToAdjust,
2057 NothingToSlash,
2059 SlashTooLow,
2061 AlreadyMigrated,
2063 NotMigrated,
2065 NotSupported,
2067 Restricted,
2070 }
2071
2072 #[derive(Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, PalletError, Debug)]
2073 pub enum DefensiveError {
2074 NotEnoughSpaceInUnbondPool,
2076 PoolNotFound,
2078 RewardPoolNotFound,
2080 SubPoolsNotFound,
2082 BondedStashKilledPrematurely,
2085 DelegationUnsupported,
2087 SlashNotApplied,
2089 }
2090
2091 impl<T> From<DefensiveError> for Error<T> {
2092 fn from(e: DefensiveError) -> Error<T> {
2093 Error::<T>::Defensive(e)
2094 }
2095 }
2096
2097 #[pallet::composite_enum]
2099 pub enum FreezeReason {
2100 #[codec(index = 0)]
2102 PoolMinBalance,
2103 }
2104
2105 #[pallet::call]
2106 impl<T: Config> Pallet<T> {
2107 #[pallet::call_index(0)]
2124 #[pallet::weight(T::WeightInfo::join())]
2125 pub fn join(
2126 origin: OriginFor<T>,
2127 #[pallet::compact] amount: BalanceOf<T>,
2128 pool_id: PoolId,
2129 ) -> DispatchResult {
2130 let who = ensure_signed(origin)?;
2131 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2133
2134 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
2136
2137 ensure!(amount >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
2138 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
2140
2141 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2142 bonded_pool.ok_to_join()?;
2143
2144 let mut reward_pool = RewardPools::<T>::get(pool_id)
2145 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2146 reward_pool.update_records(
2148 pool_id,
2149 bonded_pool.points,
2150 bonded_pool.commission.current(),
2151 )?;
2152
2153 bonded_pool.try_inc_members()?;
2154 let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
2155
2156 PoolMembers::insert(
2157 who.clone(),
2158 PoolMember::<T> {
2159 pool_id,
2160 points: points_issued,
2161 last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(),
2164 unbonding_eras: Default::default(),
2165 },
2166 );
2167
2168 Self::deposit_event(Event::<T>::Bonded {
2169 member: who,
2170 pool_id,
2171 bonded: amount,
2172 joined: true,
2173 });
2174
2175 bonded_pool.put();
2176 RewardPools::<T>::insert(pool_id, reward_pool);
2177
2178 Ok(())
2179 }
2180
2181 #[pallet::call_index(1)]
2192 #[pallet::weight(
2193 T::WeightInfo::bond_extra_transfer()
2194 .max(T::WeightInfo::bond_extra_other())
2195 )]
2196 pub fn bond_extra(origin: OriginFor<T>, extra: BondExtra<BalanceOf<T>>) -> DispatchResult {
2197 let who = ensure_signed(origin)?;
2198
2199 ensure!(
2201 !Self::api_member_needs_delegate_migration(who.clone()),
2202 Error::<T>::NotMigrated
2203 );
2204
2205 Self::do_bond_extra(who.clone(), who, extra)
2206 }
2207
2208 #[pallet::call_index(2)]
2217 #[pallet::weight(T::WeightInfo::claim_payout())]
2218 pub fn claim_payout(origin: OriginFor<T>) -> DispatchResult {
2219 let signer = ensure_signed(origin)?;
2220 ensure!(
2222 !Self::api_member_needs_delegate_migration(signer.clone()),
2223 Error::<T>::NotMigrated
2224 );
2225
2226 Self::do_claim_payout(signer.clone(), signer)
2227 }
2228
2229 #[pallet::call_index(3)]
2261 #[pallet::weight(T::WeightInfo::unbond())]
2262 pub fn unbond(
2263 origin: OriginFor<T>,
2264 member_account: AccountIdLookupOf<T>,
2265 #[pallet::compact] unbonding_points: BalanceOf<T>,
2266 ) -> DispatchResult {
2267 let who = ensure_signed(origin)?;
2268 let member_account = T::Lookup::lookup(member_account)?;
2269 ensure!(
2271 !Self::api_member_needs_delegate_migration(member_account.clone()),
2272 Error::<T>::NotMigrated
2273 );
2274
2275 let (mut member, mut bonded_pool, mut reward_pool) =
2276 Self::get_member_with_pools(&member_account)?;
2277
2278 bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?;
2279
2280 reward_pool.update_records(
2284 bonded_pool.id,
2285 bonded_pool.points,
2286 bonded_pool.commission.current(),
2287 )?;
2288 Self::do_reward_payout(
2289 &member_account,
2290 &mut member,
2291 &mut bonded_pool,
2292 &mut reward_pool,
2293 )?;
2294
2295 let active_era = T::StakeAdapter::current_era();
2296 let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(active_era);
2297
2298 let unbonding_balance = bonded_pool.dissolve(unbonding_points);
2300 T::StakeAdapter::unbond(Pool::from(bonded_pool.bonded_account()), unbonding_balance)?;
2301
2302 let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
2304 .unwrap_or_default()
2305 .maybe_merge_pools(active_era);
2306
2307 if !sub_pools.with_era.contains_key(&unbond_era) {
2310 sub_pools
2311 .with_era
2312 .try_insert(unbond_era, UnbondPool::default())
2313 .defensive_map_err::<Error<T>, _>(|_| {
2316 DefensiveError::NotEnoughSpaceInUnbondPool.into()
2317 })?;
2318 }
2319
2320 let points_unbonded = sub_pools
2321 .with_era
2322 .get_mut(&unbond_era)
2323 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?
2325 .issue(unbonding_balance);
2326
2327 member.try_unbond(unbonding_points, points_unbonded, unbond_era)?;
2329
2330 Self::deposit_event(Event::<T>::Unbonded {
2331 member: member_account.clone(),
2332 pool_id: member.pool_id,
2333 points: points_unbonded,
2334 balance: unbonding_balance,
2335 era: unbond_era,
2336 });
2337
2338 SubPoolsStorage::insert(member.pool_id, sub_pools);
2340 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
2341 Ok(())
2342 }
2343
2344 #[pallet::call_index(4)]
2351 #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))]
2352 pub fn pool_withdraw_unbonded(
2353 origin: OriginFor<T>,
2354 pool_id: PoolId,
2355 num_slashing_spans: u32,
2356 ) -> DispatchResult {
2357 ensure_signed(origin)?;
2358 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2360
2361 let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2362
2363 ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
2366 T::StakeAdapter::withdraw_unbonded(
2367 Pool::from(pool.bonded_account()),
2368 num_slashing_spans,
2369 )?;
2370
2371 Ok(())
2372 }
2373
2374 #[pallet::call_index(5)]
2397 #[pallet::weight(
2398 T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
2399 )]
2400 pub fn withdraw_unbonded(
2401 origin: OriginFor<T>,
2402 member_account: AccountIdLookupOf<T>,
2403 num_slashing_spans: u32,
2404 ) -> DispatchResultWithPostInfo {
2405 let caller = ensure_signed(origin)?;
2406 let member_account = T::Lookup::lookup(member_account)?;
2407 ensure!(
2409 !Self::api_member_needs_delegate_migration(member_account.clone()),
2410 Error::<T>::NotMigrated
2411 );
2412
2413 let mut member =
2414 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
2415 let active_era = T::StakeAdapter::current_era();
2416
2417 let bonded_pool = BondedPool::<T>::get(member.pool_id)
2418 .defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
2419 let mut sub_pools =
2420 SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
2421
2422 let slash_weight =
2423 match Self::do_apply_slash(&member_account, None, false) {
2425 Ok(_) => T::WeightInfo::apply_slash(),
2426 Err(e) => {
2427 let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
2428 if Err(e) == no_pending_slash {
2430 T::WeightInfo::apply_slash_fail()
2431 } else {
2432 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
2434 }
2435 }
2436
2437 };
2438
2439 bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
2440 let pool_account = bonded_pool.bonded_account();
2441
2442 let withdrawn_points = member.withdraw_unlocked(active_era);
2444 ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
2445
2446 let stash_killed = T::StakeAdapter::withdraw_unbonded(
2449 Pool::from(bonded_pool.bonded_account()),
2450 num_slashing_spans,
2451 )?;
2452
2453 ensure!(
2456 !stash_killed || caller == bonded_pool.roles.depositor,
2457 Error::<T>::Defensive(DefensiveError::BondedStashKilledPrematurely)
2458 );
2459
2460 if stash_killed {
2461 if frame_system::Pallet::<T>::consumers(&pool_account) == 1 {
2463 frame_system::Pallet::<T>::dec_consumers(&pool_account);
2464 }
2465
2466 }
2473
2474 let mut sum_unlocked_points: BalanceOf<T> = Zero::zero();
2475 let balance_to_unbond = withdrawn_points
2476 .iter()
2477 .fold(BalanceOf::<T>::zero(), |accumulator, (era, unlocked_points)| {
2478 sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points);
2479 if let Some(era_pool) = sub_pools.with_era.get_mut(era) {
2480 let balance_to_unbond = era_pool.dissolve(*unlocked_points);
2481 if era_pool.points.is_zero() {
2482 sub_pools.with_era.remove(era);
2483 }
2484 accumulator.saturating_add(balance_to_unbond)
2485 } else {
2486 accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points))
2489 }
2490 })
2491 .min(T::StakeAdapter::transferable_balance(
2499 Pool::from(bonded_pool.bonded_account()),
2500 Member::from(member_account.clone()),
2501 ));
2502
2503 T::StakeAdapter::member_withdraw(
2506 Member::from(member_account.clone()),
2507 Pool::from(bonded_pool.bonded_account()),
2508 balance_to_unbond,
2509 num_slashing_spans,
2510 )?;
2511
2512 Self::deposit_event(Event::<T>::Withdrawn {
2513 member: member_account.clone(),
2514 pool_id: member.pool_id,
2515 points: sum_unlocked_points,
2516 balance: balance_to_unbond,
2517 });
2518
2519 let post_info_weight = if member.total_points().is_zero() {
2520 ClaimPermissions::<T>::remove(&member_account);
2522
2523 PoolMembers::<T>::remove(&member_account);
2525
2526 let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(
2528 Member::from(member_account.clone()),
2529 ) {
2530 Some(dangling_delegation) => {
2531 T::StakeAdapter::member_withdraw(
2532 Member::from(member_account.clone()),
2533 Pool::from(bonded_pool.bonded_account()),
2534 dangling_delegation,
2535 num_slashing_spans,
2536 )?;
2537 dangling_delegation
2538 },
2539 None => Zero::zero(),
2540 };
2541
2542 Self::deposit_event(Event::<T>::MemberRemoved {
2543 pool_id: member.pool_id,
2544 member: member_account.clone(),
2545 released_balance: dangling_withdrawal,
2546 });
2547
2548 if member_account == bonded_pool.roles.depositor {
2549 Pallet::<T>::dissolve_pool(bonded_pool);
2550 Weight::default()
2551 } else {
2552 bonded_pool.dec_members().put();
2553 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2554 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2555 }
2556 } else {
2557 SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
2559 PoolMembers::<T>::insert(&member_account, member);
2560 T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
2561 };
2562
2563 Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
2564 }
2565
2566 #[pallet::call_index(6)]
2584 #[pallet::weight(T::WeightInfo::create())]
2585 pub fn create(
2586 origin: OriginFor<T>,
2587 #[pallet::compact] amount: BalanceOf<T>,
2588 root: AccountIdLookupOf<T>,
2589 nominator: AccountIdLookupOf<T>,
2590 bouncer: AccountIdLookupOf<T>,
2591 ) -> DispatchResult {
2592 let depositor = ensure_signed(origin)?;
2593
2594 let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
2595 *id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
2596 Ok(*id)
2597 })?;
2598
2599 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2600 }
2601
2602 #[pallet::call_index(7)]
2609 #[pallet::weight(T::WeightInfo::create())]
2610 pub fn create_with_pool_id(
2611 origin: OriginFor<T>,
2612 #[pallet::compact] amount: BalanceOf<T>,
2613 root: AccountIdLookupOf<T>,
2614 nominator: AccountIdLookupOf<T>,
2615 bouncer: AccountIdLookupOf<T>,
2616 pool_id: PoolId,
2617 ) -> DispatchResult {
2618 let depositor = ensure_signed(origin)?;
2619
2620 ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
2621 ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);
2622
2623 Self::do_create(depositor, amount, root, nominator, bouncer, pool_id)
2624 }
2625
2626 #[pallet::call_index(8)]
2639 #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))]
2640 pub fn nominate(
2641 origin: OriginFor<T>,
2642 pool_id: PoolId,
2643 validators: Vec<T::AccountId>,
2644 ) -> DispatchResult {
2645 let who = ensure_signed(origin)?;
2646 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2647 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2649 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2650
2651 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2652 .ok_or(Error::<T>::PoolMemberNotFound)?
2653 .active_points();
2654
2655 ensure!(
2656 bonded_pool.points_to_balance(depositor_points) >= Self::depositor_min_bond(),
2657 Error::<T>::MinimumBondNotMet
2658 );
2659
2660 T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map(
2661 |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }),
2662 )
2663 }
2664
2665 #[pallet::call_index(9)]
2676 #[pallet::weight(T::WeightInfo::set_state())]
2677 pub fn set_state(
2678 origin: OriginFor<T>,
2679 pool_id: PoolId,
2680 state: PoolState,
2681 ) -> DispatchResult {
2682 let who = ensure_signed(origin)?;
2683 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2684 ensure!(bonded_pool.state != PoolState::Destroying, Error::<T>::CanNotChangeState);
2685 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2687
2688 if bonded_pool.can_toggle_state(&who) {
2689 bonded_pool.set_state(state);
2690 } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying {
2691 bonded_pool.set_state(PoolState::Destroying);
2693 } else {
2694 Err(Error::<T>::CanNotChangeState)?;
2695 }
2696
2697 bonded_pool.put();
2698
2699 Ok(())
2700 }
2701
2702 #[pallet::call_index(10)]
2707 #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))]
2708 pub fn set_metadata(
2709 origin: OriginFor<T>,
2710 pool_id: PoolId,
2711 metadata: Vec<u8>,
2712 ) -> DispatchResult {
2713 let who = ensure_signed(origin)?;
2714 let metadata: BoundedVec<_, _> =
2715 metadata.try_into().map_err(|_| Error::<T>::MetadataExceedsMaxLen)?;
2716 ensure!(
2717 BondedPool::<T>::get(pool_id)
2718 .ok_or(Error::<T>::PoolNotFound)?
2719 .can_set_metadata(&who),
2720 Error::<T>::DoesNotHavePermission
2721 );
2722 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2724
2725 Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata);
2726
2727 Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who });
2728
2729 Ok(())
2730 }
2731
2732 #[pallet::call_index(11)]
2744 #[pallet::weight(T::WeightInfo::set_configs())]
2745 pub fn set_configs(
2746 origin: OriginFor<T>,
2747 min_join_bond: ConfigOp<BalanceOf<T>>,
2748 min_create_bond: ConfigOp<BalanceOf<T>>,
2749 max_pools: ConfigOp<u32>,
2750 max_members: ConfigOp<u32>,
2751 max_members_per_pool: ConfigOp<u32>,
2752 global_max_commission: ConfigOp<Perbill>,
2753 ) -> DispatchResult {
2754 T::AdminOrigin::ensure_origin(origin)?;
2755
2756 macro_rules! config_op_exp {
2757 ($storage:ty, $op:ident) => {
2758 match $op {
2759 ConfigOp::Noop => (),
2760 ConfigOp::Set(v) => <$storage>::put(v),
2761 ConfigOp::Remove => <$storage>::kill(),
2762 }
2763 };
2764 }
2765
2766 config_op_exp!(MinJoinBond::<T>, min_join_bond);
2767 config_op_exp!(MinCreateBond::<T>, min_create_bond);
2768 config_op_exp!(MaxPools::<T>, max_pools);
2769 config_op_exp!(MaxPoolMembers::<T>, max_members);
2770 config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool);
2771 config_op_exp!(GlobalMaxCommission::<T>, global_max_commission);
2772
2773 Self::deposit_event(Event::<T>::GlobalParamsUpdated {
2774 min_join_bond: MinJoinBond::<T>::get(),
2775 min_create_bond: MinCreateBond::<T>::get(),
2776 max_pools: MaxPools::<T>::get(),
2777 max_members: MaxPoolMembers::<T>::get(),
2778 max_members_per_pool: MaxPoolMembersPerPool::<T>::get(),
2779 global_max_commission: GlobalMaxCommission::<T>::get(),
2780 });
2781
2782 Ok(())
2783 }
2784
2785 #[pallet::call_index(12)]
2793 #[pallet::weight(T::WeightInfo::update_roles())]
2794 pub fn update_roles(
2795 origin: OriginFor<T>,
2796 pool_id: PoolId,
2797 new_root: ConfigOp<T::AccountId>,
2798 new_nominator: ConfigOp<T::AccountId>,
2799 new_bouncer: ConfigOp<T::AccountId>,
2800 ) -> DispatchResult {
2801 let mut bonded_pool = match ensure_root(origin.clone()) {
2802 Ok(()) => BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?,
2803 Err(sp_runtime::traits::BadOrigin) => {
2804 let who = ensure_signed(origin)?;
2805 let bonded_pool =
2806 BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2807 ensure!(bonded_pool.can_update_roles(&who), Error::<T>::DoesNotHavePermission);
2808 bonded_pool
2809 },
2810 };
2811
2812 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2814
2815 match new_root {
2816 ConfigOp::Noop => (),
2817 ConfigOp::Remove => bonded_pool.roles.root = None,
2818 ConfigOp::Set(v) => bonded_pool.roles.root = Some(v),
2819 };
2820 match new_nominator {
2821 ConfigOp::Noop => (),
2822 ConfigOp::Remove => bonded_pool.roles.nominator = None,
2823 ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v),
2824 };
2825 match new_bouncer {
2826 ConfigOp::Noop => (),
2827 ConfigOp::Remove => bonded_pool.roles.bouncer = None,
2828 ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v),
2829 };
2830
2831 Self::deposit_event(Event::<T>::RolesUpdated {
2832 root: bonded_pool.roles.root.clone(),
2833 nominator: bonded_pool.roles.nominator.clone(),
2834 bouncer: bonded_pool.roles.bouncer.clone(),
2835 });
2836
2837 bonded_pool.put();
2838 Ok(())
2839 }
2840
2841 #[pallet::call_index(13)]
2859 #[pallet::weight(T::WeightInfo::chill())]
2860 pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
2861 let who = ensure_signed(origin)?;
2862 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2863 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2865
2866 let depositor_points = PoolMembers::<T>::get(&bonded_pool.roles.depositor)
2867 .ok_or(Error::<T>::PoolMemberNotFound)?
2868 .active_points();
2869
2870 if bonded_pool.points_to_balance(depositor_points) >=
2871 T::StakeAdapter::minimum_nominator_bond()
2872 {
2873 ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
2874 }
2875
2876 T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| {
2877 Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who })
2878 })
2879 }
2880
2881 #[pallet::call_index(14)]
2891 #[pallet::weight(
2892 T::WeightInfo::bond_extra_transfer()
2893 .max(T::WeightInfo::bond_extra_other())
2894 )]
2895 pub fn bond_extra_other(
2896 origin: OriginFor<T>,
2897 member: AccountIdLookupOf<T>,
2898 extra: BondExtra<BalanceOf<T>>,
2899 ) -> DispatchResult {
2900 let who = ensure_signed(origin)?;
2901 let member_account = T::Lookup::lookup(member)?;
2902 ensure!(
2904 !Self::api_member_needs_delegate_migration(member_account.clone()),
2905 Error::<T>::NotMigrated
2906 );
2907
2908 Self::do_bond_extra(who, member_account, extra)
2909 }
2910
2911 #[pallet::call_index(15)]
2919 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
2920 pub fn set_claim_permission(
2921 origin: OriginFor<T>,
2922 permission: ClaimPermission,
2923 ) -> DispatchResult {
2924 let who = ensure_signed(origin)?;
2925 ensure!(PoolMembers::<T>::contains_key(&who), Error::<T>::PoolMemberNotFound);
2926
2927 ensure!(
2929 !Self::api_member_needs_delegate_migration(who.clone()),
2930 Error::<T>::NotMigrated
2931 );
2932
2933 ClaimPermissions::<T>::mutate(who.clone(), |source| {
2934 *source = permission;
2935 });
2936
2937 Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated {
2938 member: who,
2939 permission,
2940 });
2941
2942 Ok(())
2943 }
2944
2945 #[pallet::call_index(16)]
2950 #[pallet::weight(T::WeightInfo::claim_payout())]
2951 pub fn claim_payout_other(origin: OriginFor<T>, other: T::AccountId) -> DispatchResult {
2952 let signer = ensure_signed(origin)?;
2953 ensure!(
2955 !Self::api_member_needs_delegate_migration(other.clone()),
2956 Error::<T>::NotMigrated
2957 );
2958
2959 Self::do_claim_payout(signer, other)
2960 }
2961
2962 #[pallet::call_index(17)]
2969 #[pallet::weight(T::WeightInfo::set_commission())]
2970 pub fn set_commission(
2971 origin: OriginFor<T>,
2972 pool_id: PoolId,
2973 new_commission: Option<(Perbill, T::AccountId)>,
2974 ) -> DispatchResult {
2975 let who = ensure_signed(origin)?;
2976 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
2977 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
2979
2980 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
2981
2982 let mut reward_pool = RewardPools::<T>::get(pool_id)
2983 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
2984 reward_pool.update_records(
2987 pool_id,
2988 bonded_pool.points,
2989 bonded_pool.commission.current(),
2990 )?;
2991 RewardPools::insert(pool_id, reward_pool);
2992
2993 bonded_pool.commission.try_update_current(&new_commission)?;
2994 bonded_pool.put();
2995 Self::deposit_event(Event::<T>::PoolCommissionUpdated {
2996 pool_id,
2997 current: new_commission,
2998 });
2999 Ok(())
3000 }
3001
3002 #[pallet::call_index(18)]
3008 #[pallet::weight(T::WeightInfo::set_commission_max())]
3009 pub fn set_commission_max(
3010 origin: OriginFor<T>,
3011 pool_id: PoolId,
3012 max_commission: Perbill,
3013 ) -> DispatchResult {
3014 let who = ensure_signed(origin)?;
3015 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3016 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3018
3019 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3020
3021 bonded_pool.commission.try_update_max(pool_id, max_commission)?;
3022 bonded_pool.put();
3023
3024 Self::deposit_event(Event::<T>::PoolMaxCommissionUpdated { pool_id, max_commission });
3025 Ok(())
3026 }
3027
3028 #[pallet::call_index(19)]
3033 #[pallet::weight(T::WeightInfo::set_commission_change_rate())]
3034 pub fn set_commission_change_rate(
3035 origin: OriginFor<T>,
3036 pool_id: PoolId,
3037 change_rate: CommissionChangeRate<BlockNumberFor<T>>,
3038 ) -> DispatchResult {
3039 let who = ensure_signed(origin)?;
3040 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3041 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3043 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3044
3045 bonded_pool.commission.try_update_change_rate(change_rate)?;
3046 bonded_pool.put();
3047
3048 Self::deposit_event(Event::<T>::PoolCommissionChangeRateUpdated {
3049 pool_id,
3050 change_rate,
3051 });
3052 Ok(())
3053 }
3054
3055 #[pallet::call_index(20)]
3072 #[pallet::weight(T::WeightInfo::claim_commission())]
3073 pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3074 let who = ensure_signed(origin)?;
3075 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3077
3078 Self::do_claim_commission(who, pool_id)
3079 }
3080
3081 #[pallet::call_index(21)]
3089 #[pallet::weight(T::WeightInfo::adjust_pool_deposit())]
3090 pub fn adjust_pool_deposit(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
3091 let who = ensure_signed(origin)?;
3092 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3094
3095 Self::do_adjust_pool_deposit(who, pool_id)
3096 }
3097
3098 #[pallet::call_index(22)]
3103 #[pallet::weight(T::WeightInfo::set_commission_claim_permission())]
3104 pub fn set_commission_claim_permission(
3105 origin: OriginFor<T>,
3106 pool_id: PoolId,
3107 permission: Option<CommissionClaimPermission<T::AccountId>>,
3108 ) -> DispatchResult {
3109 let who = ensure_signed(origin)?;
3110 let mut bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3111 ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::<T>::NotMigrated);
3113 ensure!(bonded_pool.can_manage_commission(&who), Error::<T>::DoesNotHavePermission);
3114
3115 bonded_pool.commission.claim_permission = permission.clone();
3116 bonded_pool.put();
3117
3118 Self::deposit_event(Event::<T>::PoolCommissionClaimPermissionUpdated {
3119 pool_id,
3120 permission,
3121 });
3122
3123 Ok(())
3124 }
3125
3126 #[pallet::call_index(23)]
3136 #[pallet::weight(T::WeightInfo::apply_slash())]
3137 pub fn apply_slash(
3138 origin: OriginFor<T>,
3139 member_account: AccountIdLookupOf<T>,
3140 ) -> DispatchResultWithPostInfo {
3141 ensure!(
3142 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3143 Error::<T>::NotSupported
3144 );
3145
3146 let who = ensure_signed(origin)?;
3147 let member_account = T::Lookup::lookup(member_account)?;
3148 Self::do_apply_slash(&member_account, Some(who), true)?;
3149
3150 Ok(Pays::No.into())
3152 }
3153
3154 #[pallet::call_index(24)]
3164 #[pallet::weight(T::WeightInfo::migrate_delegation())]
3165 pub fn migrate_delegation(
3166 origin: OriginFor<T>,
3167 member_account: AccountIdLookupOf<T>,
3168 ) -> DispatchResultWithPostInfo {
3169 let _caller = ensure_signed(origin)?;
3170
3171 ensure!(
3173 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3174 Error::<T>::NotSupported
3175 );
3176
3177 let member_account = T::Lookup::lookup(member_account)?;
3179 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3180
3181 let member =
3182 PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3183
3184 ensure!(
3186 T::StakeAdapter::pool_strategy(Pool::from(Self::generate_bonded_account(
3187 member.pool_id
3188 ))) == adapter::StakeStrategyType::Delegate,
3189 Error::<T>::NotMigrated
3190 );
3191
3192 let pool_contribution = member.total_balance();
3193 ensure!(
3196 pool_contribution >= T::Currency::minimum_balance(),
3197 Error::<T>::MinimumBondNotMet
3198 );
3199
3200 let delegation =
3201 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()));
3202 ensure!(delegation.is_none(), Error::<T>::AlreadyMigrated);
3204
3205 T::StakeAdapter::migrate_delegation(
3206 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3207 Member::from(member_account),
3208 pool_contribution,
3209 )?;
3210
3211 Ok(Pays::No.into())
3213 }
3214
3215 #[pallet::call_index(25)]
3225 #[pallet::weight(T::WeightInfo::pool_migrate())]
3226 pub fn migrate_pool_to_delegate_stake(
3227 origin: OriginFor<T>,
3228 pool_id: PoolId,
3229 ) -> DispatchResultWithPostInfo {
3230 ensure!(
3232 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3233 Error::<T>::NotSupported
3234 );
3235
3236 let _caller = ensure_signed(origin)?;
3237 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3239 ensure!(
3240 T::StakeAdapter::pool_strategy(Pool::from(bonded_pool.bonded_account())) ==
3241 adapter::StakeStrategyType::Transfer,
3242 Error::<T>::AlreadyMigrated
3243 );
3244
3245 Self::migrate_to_delegate_stake(pool_id)?;
3246 Ok(Pays::No.into())
3247 }
3248 }
3249
3250 #[pallet::hooks]
3251 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
3252 #[cfg(feature = "try-runtime")]
3253 fn try_state(_n: SystemBlockNumberFor<T>) -> Result<(), TryRuntimeError> {
3254 Self::do_try_state(u8::MAX)
3255 }
3256
3257 fn integrity_test() {
3258 assert!(
3259 T::MaxPointsToBalance::get() > 0,
3260 "Minimum points to balance ratio must be greater than 0"
3261 );
3262 assert!(
3263 T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
3264 "There must be more unbonding pools then the bonding duration /
3265 so a slash can be applied to relevant unbonding pools. (We assume /
3266 the bonding duration > slash deffer duration.",
3267 );
3268 }
3269 }
3270}
3271
3272impl<T: Config> Pallet<T> {
3273 pub fn depositor_min_bond() -> BalanceOf<T> {
3281 T::StakeAdapter::minimum_nominator_bond()
3282 .max(MinCreateBond::<T>::get())
3283 .max(MinJoinBond::<T>::get())
3284 .max(T::Currency::minimum_balance())
3285 }
3286
3287 pub fn do_claim_trapped_balance(member_account: &T::AccountId) -> DispatchResult {
3299 ensure!(
3300 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
3301 Error::<T>::NotSupported
3302 );
3303
3304 match Self::do_apply_slash(member_account, None, false) {
3307 Ok(_) => {},
3308 Err(e)
3309 if e == Error::<T>::NothingToSlash.into() ||
3310 e == Error::<T>::PoolMemberNotFound.into() => {},
3311 Err(_) => {
3312 return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
3313 },
3314 };
3315
3316 let member = match PoolMembers::<T>::get(member_account) {
3317 Some(m) => m,
3318 None => return Ok(()),
3319 };
3320
3321 let expected_balance = member.total_balance();
3322 let actual_balance =
3323 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()))
3324 .unwrap_or_default();
3325
3326 let trapped_amount = actual_balance.saturating_sub(expected_balance);
3327
3328 if trapped_amount.is_zero() {
3329 return Ok(());
3330 }
3331
3332 T::StakeAdapter::member_withdraw(
3333 Member::from(member_account.clone()),
3334 Pool::from(Self::generate_bonded_account(member.pool_id)),
3335 trapped_amount,
3336 0,
3337 )?;
3338
3339 log!(
3340 info,
3341 "Claimed trapped balance for member {:?}, pool {:?}, amount {:?}",
3342 member_account,
3343 member.pool_id,
3344 trapped_amount
3345 );
3346
3347 Ok(())
3348 }
3349
3350 pub fn dissolve_pool(bonded_pool: BondedPool<T>) {
3355 let reward_account = bonded_pool.reward_account();
3356 let bonded_account = bonded_pool.bonded_account();
3357
3358 ReversePoolIdLookup::<T>::remove(&bonded_account);
3359 RewardPools::<T>::remove(bonded_pool.id);
3360 SubPoolsStorage::<T>::remove(bonded_pool.id);
3361
3362 let _ = Self::unfreeze_pool_deposit(&bonded_pool.reward_account()).defensive();
3364
3365 defensive_assert!(
3373 frame_system::Pallet::<T>::consumers(&reward_account) == 0,
3374 "reward account of dissolving pool should have no consumers"
3375 );
3376 defensive_assert!(
3377 frame_system::Pallet::<T>::consumers(&bonded_account) == 0,
3378 "bonded account of dissolving pool should have no consumers"
3379 );
3380 defensive_assert!(
3381 T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account())) == Zero::zero(),
3382 "dissolving pool should not have any stake in the staking pallet"
3383 );
3384
3385 let reward_pool_remaining = T::Currency::reducible_balance(
3388 &reward_account,
3389 Preservation::Expendable,
3390 Fortitude::Polite,
3391 );
3392 let _ = T::Currency::transfer(
3393 &reward_account,
3394 &bonded_pool.roles.depositor,
3395 reward_pool_remaining,
3396 Preservation::Expendable,
3397 );
3398
3399 defensive_assert!(
3400 T::Currency::total_balance(&reward_account) == Zero::zero(),
3401 "could not transfer all amount to depositor while dissolving pool"
3402 );
3403 T::Currency::set_balance(&reward_account, Zero::zero());
3405
3406 let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive();
3408
3409 Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
3410 Metadata::<T>::remove(bonded_pool.id);
3412
3413 bonded_pool.remove();
3414 }
3415
3416 pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
3418 T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
3419 }
3420
3421 fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
3422 T::StakeAdapter::migrate_nominator_to_agent(
3423 Pool::from(Self::generate_bonded_account(id)),
3424 &Self::generate_reward_account(id),
3425 )
3426 }
3427
3428 pub fn generate_reward_account(id: PoolId) -> T::AccountId {
3430 T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
3433 }
3434
3435 fn get_member_with_pools(
3437 who: &T::AccountId,
3438 ) -> Result<(PoolMember<T>, BondedPool<T>, RewardPool<T>), Error<T>> {
3439 let member = PoolMembers::<T>::get(who).ok_or(Error::<T>::PoolMemberNotFound)?;
3440 let bonded_pool =
3441 BondedPool::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3442 let reward_pool =
3443 RewardPools::<T>::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?;
3444 Ok((member, bonded_pool, reward_pool))
3445 }
3446
3447 fn put_member_with_pools(
3450 member_account: &T::AccountId,
3451 member: PoolMember<T>,
3452 bonded_pool: BondedPool<T>,
3453 reward_pool: RewardPool<T>,
3454 ) {
3455 debug_assert_eq!(PoolMembers::<T>::get(member_account).unwrap().pool_id, member.pool_id);
3458 debug_assert_eq!(member.pool_id, bonded_pool.id);
3459
3460 bonded_pool.put();
3461 RewardPools::insert(member.pool_id, reward_pool);
3462 PoolMembers::<T>::insert(member_account, member);
3463 }
3464
3465 fn balance_to_point(
3468 current_balance: BalanceOf<T>,
3469 current_points: BalanceOf<T>,
3470 new_funds: BalanceOf<T>,
3471 ) -> BalanceOf<T> {
3472 let u256 = T::BalanceToU256::convert;
3473 let balance = T::U256ToBalance::convert;
3474 match (current_balance.is_zero(), current_points.is_zero()) {
3475 (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()),
3476 (true, false) => {
3477 new_funds.saturating_mul(current_points)
3480 },
3481 (false, false) => {
3482 balance(
3484 u256(current_points)
3485 .saturating_mul(u256(new_funds))
3486 .div(u256(current_balance)),
3488 )
3489 },
3490 }
3491 }
3492
3493 fn point_to_balance(
3496 current_balance: BalanceOf<T>,
3497 current_points: BalanceOf<T>,
3498 points: BalanceOf<T>,
3499 ) -> BalanceOf<T> {
3500 let u256 = T::BalanceToU256::convert;
3501 let balance = T::U256ToBalance::convert;
3502 if current_balance.is_zero() || current_points.is_zero() || points.is_zero() {
3503 return Zero::zero();
3505 }
3506
3507 balance(
3509 u256(current_balance)
3510 .saturating_mul(u256(points))
3511 .div(u256(current_points)),
3513 )
3514 }
3515
3516 fn do_reward_payout(
3520 member_account: &T::AccountId,
3521 member: &mut PoolMember<T>,
3522 bonded_pool: &mut BondedPool<T>,
3523 reward_pool: &mut RewardPool<T>,
3524 ) -> Result<BalanceOf<T>, DispatchError> {
3525 debug_assert_eq!(member.pool_id, bonded_pool.id);
3526 debug_assert_eq!(&mut PoolMembers::<T>::get(member_account).unwrap(), member);
3527
3528 ensure!(!member.active_points().is_zero(), Error::<T>::FullyUnbonding);
3530
3531 let (current_reward_counter, _) = reward_pool.current_reward_counter(
3532 bonded_pool.id,
3533 bonded_pool.points,
3534 bonded_pool.commission.current(),
3535 )?;
3536
3537 let pending_rewards = member.pending_rewards(current_reward_counter)?;
3540 if pending_rewards.is_zero() {
3541 return Ok(pending_rewards);
3542 }
3543
3544 member.last_recorded_reward_counter = current_reward_counter;
3546 reward_pool.register_claimed_reward(pending_rewards);
3547
3548 T::Currency::transfer(
3549 &bonded_pool.reward_account(),
3550 member_account,
3551 pending_rewards,
3552 Preservation::Preserve,
3555 )?;
3556
3557 Self::deposit_event(Event::<T>::PaidOut {
3558 member: member_account.clone(),
3559 pool_id: member.pool_id,
3560 payout: pending_rewards,
3561 });
3562 Ok(pending_rewards)
3563 }
3564
3565 fn do_create(
3566 who: T::AccountId,
3567 amount: BalanceOf<T>,
3568 root: AccountIdLookupOf<T>,
3569 nominator: AccountIdLookupOf<T>,
3570 bouncer: AccountIdLookupOf<T>,
3571 pool_id: PoolId,
3572 ) -> DispatchResult {
3573 ensure!(!T::Filter::contains(&who), Error::<T>::Restricted);
3575
3576 let root = T::Lookup::lookup(root)?;
3577 let nominator = T::Lookup::lookup(nominator)?;
3578 let bouncer = T::Lookup::lookup(bouncer)?;
3579
3580 ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
3581 ensure!(
3582 MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
3583 Error::<T>::MaxPools
3584 );
3585 ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
3586 let mut bonded_pool = BondedPool::<T>::new(
3587 pool_id,
3588 PoolRoles {
3589 root: Some(root),
3590 nominator: Some(nominator),
3591 bouncer: Some(bouncer),
3592 depositor: who.clone(),
3593 },
3594 );
3595
3596 bonded_pool.try_inc_members()?;
3597 let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;
3598
3599 T::Currency::transfer(
3601 &who,
3602 &bonded_pool.reward_account(),
3603 T::Currency::minimum_balance(),
3604 Preservation::Expendable,
3605 )?;
3606
3607 Self::freeze_pool_deposit(&bonded_pool.reward_account())?;
3609
3610 PoolMembers::<T>::insert(
3611 who.clone(),
3612 PoolMember::<T> {
3613 pool_id,
3614 points,
3615 last_recorded_reward_counter: Zero::zero(),
3616 unbonding_eras: Default::default(),
3617 },
3618 );
3619 RewardPools::<T>::insert(
3620 pool_id,
3621 RewardPool::<T> {
3622 last_recorded_reward_counter: Zero::zero(),
3623 last_recorded_total_payouts: Zero::zero(),
3624 total_rewards_claimed: Zero::zero(),
3625 total_commission_pending: Zero::zero(),
3626 total_commission_claimed: Zero::zero(),
3627 },
3628 );
3629 ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
3630
3631 Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
3632
3633 Self::deposit_event(Event::<T>::Bonded {
3634 member: who,
3635 pool_id,
3636 bonded: amount,
3637 joined: true,
3638 });
3639 bonded_pool.put();
3640
3641 Ok(())
3642 }
3643
3644 fn do_bond_extra(
3645 signer: T::AccountId,
3646 member_account: T::AccountId,
3647 extra: BondExtra<BalanceOf<T>>,
3648 ) -> DispatchResult {
3649 ensure!(!T::Filter::contains(&member_account), Error::<T>::Restricted);
3651
3652 if signer != member_account {
3653 ensure!(
3654 ClaimPermissions::<T>::get(&member_account).can_bond_extra(),
3655 Error::<T>::DoesNotHavePermission
3656 );
3657 ensure!(extra == BondExtra::Rewards, Error::<T>::BondExtraRestricted);
3658 }
3659
3660 let (mut member, mut bonded_pool, mut reward_pool) =
3661 Self::get_member_with_pools(&member_account)?;
3662
3663 reward_pool.update_records(
3666 bonded_pool.id,
3667 bonded_pool.points,
3668 bonded_pool.commission.current(),
3669 )?;
3670 let claimed = Self::do_reward_payout(
3671 &member_account,
3672 &mut member,
3673 &mut bonded_pool,
3674 &mut reward_pool,
3675 )?;
3676
3677 let (points_issued, bonded) = match extra {
3678 BondExtra::FreeBalance(amount) => {
3679 (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount)
3680 },
3681 BondExtra::Rewards => {
3682 (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed)
3683 },
3684 };
3685
3686 bonded_pool.ok_to_be_open()?;
3687 member.points =
3688 member.points.checked_add(&points_issued).ok_or(Error::<T>::OverflowRisk)?;
3689
3690 Self::deposit_event(Event::<T>::Bonded {
3691 member: member_account.clone(),
3692 pool_id: member.pool_id,
3693 bonded,
3694 joined: false,
3695 });
3696 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3697
3698 Ok(())
3699 }
3700
3701 fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
3702 let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
3703 ensure!(bonded_pool.can_claim_commission(&who), Error::<T>::DoesNotHavePermission);
3704
3705 let mut reward_pool = RewardPools::<T>::get(pool_id)
3706 .defensive_ok_or::<Error<T>>(DefensiveError::RewardPoolNotFound.into())?;
3707
3708 reward_pool.update_records(
3711 pool_id,
3712 bonded_pool.points,
3713 bonded_pool.commission.current(),
3714 )?;
3715
3716 let commission = reward_pool.total_commission_pending;
3717 ensure!(!commission.is_zero(), Error::<T>::NoPendingCommission);
3718
3719 let payee = bonded_pool
3720 .commission
3721 .current
3722 .as_ref()
3723 .map(|(_, p)| p.clone())
3724 .ok_or(Error::<T>::NoCommissionCurrentSet)?;
3725
3726 T::Currency::transfer(
3728 &bonded_pool.reward_account(),
3729 &payee,
3730 commission,
3731 Preservation::Preserve,
3732 )?;
3733
3734 reward_pool.total_commission_claimed =
3736 reward_pool.total_commission_claimed.saturating_add(commission);
3737 reward_pool.total_commission_pending = Zero::zero();
3739 RewardPools::<T>::insert(pool_id, reward_pool);
3740
3741 Self::deposit_event(Event::<T>::PoolCommissionClaimed { pool_id, commission });
3742 Ok(())
3743 }
3744
3745 pub(crate) fn do_claim_payout(
3746 signer: T::AccountId,
3747 member_account: T::AccountId,
3748 ) -> DispatchResult {
3749 if signer != member_account {
3750 ensure!(
3751 ClaimPermissions::<T>::get(&member_account).can_claim_payout(),
3752 Error::<T>::DoesNotHavePermission
3753 );
3754 }
3755 let (mut member, mut bonded_pool, mut reward_pool) =
3756 Self::get_member_with_pools(&member_account)?;
3757
3758 Self::do_reward_payout(&member_account, &mut member, &mut bonded_pool, &mut reward_pool)?;
3759
3760 Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool);
3761 Ok(())
3762 }
3763
3764 fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult {
3765 let bonded_pool = BondedPool::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
3766
3767 let reward_acc = &bonded_pool.reward_account();
3768 let pre_frozen_balance =
3769 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc);
3770 let min_balance = T::Currency::minimum_balance();
3771
3772 if pre_frozen_balance == min_balance {
3773 return Err(Error::<T>::NothingToAdjust.into());
3774 }
3775
3776 Self::freeze_pool_deposit(reward_acc)?;
3778
3779 if pre_frozen_balance > min_balance {
3780 ensure!(
3782 who == bonded_pool.roles.depositor ||
3783 bonded_pool.roles.root.as_ref().map_or(false, |root| &who == root),
3784 Error::<T>::DoesNotHavePermission
3785 );
3786
3787 let excess = pre_frozen_balance.saturating_sub(min_balance);
3789
3790 T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
3791 Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
3792 pool_id: pool,
3793 amount: excess,
3794 });
3795 } else {
3796 let deficit = min_balance.saturating_sub(pre_frozen_balance);
3798 T::Currency::transfer(&who, reward_acc, deficit, Preservation::Expendable)?;
3799 Self::deposit_event(Event::<T>::MinBalanceDeficitAdjusted {
3800 pool_id: pool,
3801 amount: deficit,
3802 });
3803 }
3804
3805 Ok(())
3806 }
3807
3808 fn do_apply_slash(
3810 member_account: &T::AccountId,
3811 reporter: Option<T::AccountId>,
3812 enforce_min_slash: bool,
3813 ) -> DispatchResult {
3814 let member = PoolMembers::<T>::get(member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
3815
3816 let pending_slash =
3817 Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?;
3818
3819 ensure!(!pending_slash.is_zero(), Error::<T>::NothingToSlash);
3821
3822 if enforce_min_slash {
3823 ensure!(pending_slash >= T::Currency::minimum_balance(), Error::<T>::SlashTooLow);
3825 }
3826
3827 T::StakeAdapter::member_slash(
3828 Member::from(member_account.clone()),
3829 Pool::from(Pallet::<T>::generate_bonded_account(member.pool_id)),
3830 pending_slash,
3831 reporter,
3832 )
3833 }
3834
3835 fn member_pending_slash(
3839 member_account: Member<T::AccountId>,
3840 pool_member: PoolMember<T>,
3841 ) -> Result<BalanceOf<T>, DispatchError> {
3842 debug_assert!(
3844 PoolMembers::<T>::get(member_account.clone().get()).expect("member must exist") ==
3845 pool_member
3846 );
3847
3848 let pool_account = Pallet::<T>::generate_bonded_account(pool_member.pool_id);
3849 if T::StakeAdapter::pending_slash(Pool::from(pool_account.clone())).is_zero() {
3852 return Ok(Zero::zero());
3853 }
3854
3855 let actual_balance = T::StakeAdapter::member_delegation_balance(member_account)
3857 .ok_or(Error::<T>::NotMigrated)?;
3859
3860 let expected_balance = pool_member.total_balance();
3862
3863 Ok(actual_balance.saturating_sub(expected_balance))
3865 }
3866
3867 pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3869 T::Currency::set_freeze(
3870 &FreezeReason::PoolMinBalance.into(),
3871 reward_acc,
3872 T::Currency::minimum_balance(),
3873 )
3874 }
3875
3876 pub fn unfreeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
3878 T::Currency::thaw(&FreezeReason::PoolMinBalance.into(), reward_acc)
3879 }
3880
3881 #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
3918 pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> {
3919 if level.is_zero() {
3920 return Ok(());
3921 }
3922 let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
3925 let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
3926 ensure!(
3927 bonded_pools == reward_pools,
3928 "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set."
3929 );
3930
3931 ensure!(
3932 SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3933 "`SubPoolsStorage` must be a subset of the above superset."
3934 );
3935 ensure!(
3936 Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)),
3937 "`Metadata` keys must be a subset of the above superset."
3938 );
3939
3940 ensure!(
3941 MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)),
3942 Error::<T>::MaxPools
3943 );
3944
3945 for id in reward_pools {
3946 let account = Self::generate_reward_account(id);
3947 if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
3948 T::Currency::minimum_balance()
3949 {
3950 log!(
3951 warn,
3952 "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \
3953 changed recently. Pool operators should be notified to top up the reward \
3954 account",
3955 id,
3956 T::Currency::reducible_balance(
3957 &account,
3958 Preservation::Expendable,
3959 Fortitude::Polite
3960 ),
3961 T::Currency::minimum_balance(),
3962 )
3963 }
3964 }
3965
3966 let mut pools_members = BTreeMap::<PoolId, u32>::new();
3967 let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
3968 let mut all_members = 0u32;
3969 let mut total_balance_members = Default::default();
3970 PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
3971 let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
3972 ensure!(!d.total_points().is_zero(), "No member should have zero points");
3973 *pools_members.entry(d.pool_id).or_default() += 1;
3974 all_members += 1;
3975
3976 let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
3977 if !bonded_pool.points.is_zero() {
3978 let commission = bonded_pool.commission.current();
3979 let (current_rc, _) = reward_pool
3980 .current_reward_counter(d.pool_id, bonded_pool.points, commission)
3981 .unwrap();
3982 let pending_rewards = d.pending_rewards(current_rc).unwrap();
3983 *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
3984 } total_balance_members += d.total_balance();
3986
3987 Ok(())
3988 })?;
3989
3990 RewardPools::<T>::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> {
3991 let pending_rewards_lt_leftover_bal = RewardPool::<T>::current_balance(id) >=
3994 pools_members_pending_rewards.get(&id).copied().unwrap_or_default();
3995
3996 if !pending_rewards_lt_leftover_bal {
3999 log!(
4000 warn,
4001 "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}",
4002 id,
4003 pools_members_pending_rewards.get(&id),
4004 RewardPool::<T>::current_balance(id)
4005 );
4006 }
4007 Ok(())
4008 })?;
4009
4010 let mut expected_tvl: BalanceOf<T> = Default::default();
4011 let mut depositor_undermin: Vec<(PoolId, T::AccountId)> = Vec::new();
4012 let mut depositor_undermin_total: u32 = 0;
4013 let mut total_pools: u32 = 0;
4014 const MAX_EXAMPLES: usize = 10;
4015
4016 BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
4017 total_pools += 1;
4018 let bonded_pool = BondedPool { id, inner };
4019 ensure!(
4020 pools_members.get(&id).copied().unwrap_or_default() ==
4021 bonded_pool.member_counter,
4022 "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool"
4023 );
4024 ensure!(
4025 MaxPoolMembersPerPool::<T>::get()
4026 .map_or(true, |max| bonded_pool.member_counter <= max),
4027 Error::<T>::MaxPoolMembers
4028 );
4029
4030 let depositor = PoolMembers::<T>::get(&bonded_pool.roles.depositor).unwrap();
4031 let depositor_has_enough_stake = bonded_pool
4032 .is_destroying_and_only_depositor(depositor.active_points()) ||
4033 depositor.active_points() >= MinCreateBond::<T>::get();
4034 if !depositor_has_enough_stake {
4035 depositor_undermin_total += 1;
4036 if depositor_undermin.len() < MAX_EXAMPLES {
4037 depositor_undermin.push((id, bonded_pool.roles.depositor.clone()));
4038 }
4039 log!(
4040 trace,
4041 "pool {:?} has depositor {:?} with insufficient stake {:?}, minimum required is {:?}",
4042 id,
4043 bonded_pool.roles.depositor,
4044 depositor.active_points(),
4045 MinCreateBond::<T>::get()
4046 );
4047 }
4048
4049 ensure!(
4050 bonded_pool.points >= bonded_pool.points_to_balance(bonded_pool.points),
4051 "Each `BondedPool.points` must never be lower than the pool's balance"
4052 );
4053
4054 expected_tvl += T::StakeAdapter::total_stake(Pool::from(bonded_pool.bonded_account()));
4055
4056 Ok(())
4057 })?;
4058
4059 if depositor_undermin_total > 0 {
4060 log!(
4061 warn,
4062 "{}/{} pools have depositor with insufficient stake, minimum required is {:?}. Examples: {:?}",
4063 depositor_undermin_total,
4064 total_pools,
4065 MinCreateBond::<T>::get(),
4066 depositor_undermin,
4067 );
4068 }
4069
4070 ensure!(
4071 MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
4072 Error::<T>::MaxPoolMembers
4073 );
4074
4075 ensure!(
4076 TotalValueLocked::<T>::get() == expected_tvl,
4077 "TVL deviates from the actual sum of funds of all Pools."
4078 );
4079
4080 ensure!(
4081 TotalValueLocked::<T>::get() <= total_balance_members,
4082 "TVL must be equal to or less than the total balance of all PoolMembers."
4083 );
4084
4085 if level <= 1 {
4086 return Ok(());
4087 }
4088
4089 for (pool_id, _pool) in BondedPools::<T>::iter() {
4090 let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
4091 let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
4092
4093 let sum_unbonding_balance = subs.sum_unbonding_balance();
4094 let bonded_balance = T::StakeAdapter::active_stake(Pool::from(pool_account.clone()));
4095 let total_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
4097 .unwrap_or(T::Currency::total_balance(&pool_account));
4100
4101 if total_balance < bonded_balance + sum_unbonding_balance {
4102 log!(
4103 warn,
4104 "possibly faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
4105 pool_id,
4106 _pool,
4107 total_balance,
4108 bonded_balance,
4109 sum_unbonding_balance
4110 )
4111 };
4112 }
4113
4114 let _needs_adjust = Self::check_ed_imbalance()?;
4117
4118 Ok(())
4119 }
4120
4121 #[cfg(any(
4125 feature = "try-runtime",
4126 feature = "runtime-benchmarks",
4127 feature = "fuzzing",
4128 test,
4129 debug_assertions
4130 ))]
4131 pub fn check_ed_imbalance() -> Result<u32, DispatchError> {
4132 let mut needs_adjust: u32 = 0;
4133 let mut total_pools: u32 = 0;
4134 let mut ed_examples: Vec<PoolId> = Vec::new();
4135 const MAX_EXAMPLES: usize = 10;
4136
4137 BondedPools::<T>::iter_keys().for_each(|id| {
4138 total_pools += 1;
4139 let reward_acc = Self::generate_reward_account(id);
4140 let frozen_balance =
4141 T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
4142
4143 let expected_frozen_balance = T::Currency::minimum_balance();
4144 if frozen_balance != expected_frozen_balance {
4145 needs_adjust += 1;
4146 if ed_examples.len() < MAX_EXAMPLES {
4147 ed_examples.push(id);
4148 }
4149 log!(
4150 trace,
4151 "pool {:?} has incorrect ED frozen that can result from change in ED. Expected = {:?}, Actual = {:?}. Use `adjust_pool_deposit` to fix it",
4152 id,
4153 expected_frozen_balance,
4154 frozen_balance,
4155 );
4156 }
4157 });
4158
4159 if needs_adjust > 0 {
4160 log!(
4161 warn,
4162 "{}/{} pools have incorrect ED frozen (expected {:?}). Use `adjust_pool_deposit` to fix. Examples: {:?}",
4163 needs_adjust,
4164 total_pools,
4165 T::Currency::minimum_balance(),
4166 ed_examples,
4167 );
4168 }
4169
4170 Ok(needs_adjust)
4171 }
4172 #[cfg(any(feature = "runtime-benchmarks", test))]
4177 pub fn fully_unbond(
4178 origin: frame_system::pallet_prelude::OriginFor<T>,
4179 member: T::AccountId,
4180 ) -> DispatchResult {
4181 let points = PoolMembers::<T>::get(&member).map(|d| d.active_points()).unwrap_or_default();
4182 let member_lookup = T::Lookup::unlookup(member);
4183 Self::unbond(origin, member_lookup, points)
4184 }
4185}
4186
4187impl<T: Config> Pallet<T> {
4188 pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> {
4192 if let Some(pool_member) = PoolMembers::<T>::get(who) {
4193 if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id)
4194 .zip(BondedPools::<T>::get(pool_member.pool_id))
4195 {
4196 let commission = bonded_pool.commission.current();
4197 let (current_reward_counter, _) = reward_pool
4198 .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission)
4199 .ok()?;
4200 return pool_member.pending_rewards(current_reward_counter).ok();
4201 }
4202 }
4203
4204 None
4205 }
4206
4207 pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> {
4211 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4212 pool.points_to_balance(points)
4213 } else {
4214 Zero::zero()
4215 }
4216 }
4217
4218 pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
4222 if let Some(pool) = BondedPool::<T>::get(pool_id) {
4223 let bonded_balance =
4224 T::StakeAdapter::active_stake(Pool::from(Self::generate_bonded_account(pool_id)));
4225 Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
4226 } else {
4227 Zero::zero()
4228 }
4229 }
4230
4231 pub fn api_pool_pending_slash(pool_id: PoolId) -> BalanceOf<T> {
4235 T::StakeAdapter::pending_slash(Pool::from(Self::generate_bonded_account(pool_id)))
4236 }
4237
4238 pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf<T> {
4245 PoolMembers::<T>::get(who.clone())
4246 .map(|pool_member| {
4247 Self::member_pending_slash(Member::from(who), pool_member).unwrap_or_default()
4248 })
4249 .unwrap_or_default()
4250 }
4251
4252 pub fn api_pool_needs_delegate_migration(pool_id: PoolId) -> bool {
4257 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4259 return false;
4260 }
4261
4262 if !BondedPools::<T>::contains_key(pool_id) {
4264 return false;
4265 }
4266
4267 let pool_account = Self::generate_bonded_account(pool_id);
4268
4269 T::StakeAdapter::pool_strategy(Pool::from(pool_account)) !=
4271 adapter::StakeStrategyType::Delegate
4272 }
4273
4274 pub fn api_member_needs_delegate_migration(who: T::AccountId) -> bool {
4280 if T::StakeAdapter::strategy_type() != adapter::StakeStrategyType::Delegate {
4282 return false;
4283 }
4284
4285 PoolMembers::<T>::get(who.clone())
4286 .map(|pool_member| {
4287 if Self::api_pool_needs_delegate_migration(pool_member.pool_id) {
4288 return false;
4290 }
4291
4292 let member_balance = pool_member.total_balance();
4293 let delegated_balance =
4294 T::StakeAdapter::member_delegation_balance(Member::from(who.clone()));
4295
4296 delegated_balance.is_none() && !member_balance.is_zero()
4299 })
4300 .unwrap_or_default()
4301 }
4302
4303 pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf<T> {
4308 PoolMembers::<T>::get(who.clone())
4309 .map(|m| m.total_balance())
4310 .unwrap_or_default()
4311 }
4312
4313 pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf<T> {
4315 T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id)))
4316 .unwrap_or_default()
4317 }
4318
4319 pub fn api_pool_accounts(pool_id: PoolId) -> (T::AccountId, T::AccountId) {
4321 let bonded_account = Self::generate_bonded_account(pool_id);
4322 let reward_account = Self::generate_reward_account(pool_id);
4323 (bonded_account, reward_account)
4324 }
4325}
4326
4327impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
4328 fn on_slash(
4334 pool_account: &T::AccountId,
4335 slashed_bonded: BalanceOf<T>,
4338 slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
4339 total_slashed: BalanceOf<T>,
4340 ) {
4341 let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
4342 TotalValueLocked::<T>::mutate(|tvl| {
4345 tvl.defensive_saturating_reduce(total_slashed);
4346 });
4347
4348 if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
4349 slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
4351 if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
4352 pool.balance = *slashed_balance;
4353 Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
4354 era: *era,
4355 pool_id,
4356 balance: *slashed_balance,
4357 });
4358 }
4359 });
4360 SubPoolsStorage::<T>::insert(pool_id, sub_pools);
4361 } else if !slashed_unlocking.is_empty() {
4362 defensive!("Expected SubPools were not found");
4363 }
4364 Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
4365 }
4366
4367 fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf<T>) {
4370 if ReversePoolIdLookup::<T>::get(pool_account).is_some() {
4371 TotalValueLocked::<T>::mutate(|tvl| {
4372 tvl.saturating_reduce(amount);
4373 });
4374 }
4375 }
4376}
4377
4378pub struct AllPoolMembers<T: Config>(PhantomData<T>);
4380impl<T: Config> Contains<T::AccountId> for AllPoolMembers<T> {
4381 fn contains(t: &T::AccountId) -> bool {
4382 PoolMembers::<T>::contains_key(t)
4383 }
4384}