1use super::*;
19use alloc::{collections::btree_map::BTreeMap, vec::Vec};
20use frame_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade};
21
22#[cfg(feature = "try-runtime")]
23use sp_runtime::TryRuntimeError;
24
25pub mod versioned {
27 use super::*;
28
29 pub type V7ToV8<T> = frame_support::migrations::VersionedMigration<
31 7,
32 8,
33 v8::VersionUncheckedMigrateV7ToV8<T>,
34 crate::pallet::Pallet<T>,
35 <T as frame_system::Config>::DbWeight,
36 >;
37
38 pub type V6ToV7<T> = frame_support::migrations::VersionedMigration<
41 6,
42 7,
43 v7::VersionUncheckedMigrateV6ToV7<T>,
44 crate::pallet::Pallet<T>,
45 <T as frame_system::Config>::DbWeight,
46 >;
47
48 pub type V5toV6<T> = frame_support::migrations::VersionedMigration<
50 5,
51 6,
52 v6::MigrateToV6<T>,
53 crate::pallet::Pallet<T>,
54 <T as frame_system::Config>::DbWeight,
55 >;
56}
57
58pub mod unversioned {
59 use super::*;
60
61 pub struct TotalValueLockedSync<T>(core::marker::PhantomData<T>);
63 impl<T: Config> OnRuntimeUpgrade for TotalValueLockedSync<T> {
64 #[cfg(feature = "try-runtime")]
65 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
66 Ok(Vec::new())
67 }
68
69 fn on_runtime_upgrade() -> Weight {
70 let migrated = BondedPools::<T>::count();
71
72 let tvl: BalanceOf<T> = helpers::calculate_tvl_by_total_stake::<T>();
75 let onchain_tvl = TotalValueLocked::<T>::get();
76
77 let writes = if tvl != onchain_tvl {
78 TotalValueLocked::<T>::set(tvl);
79
80 log!(
81 info,
82 "on-chain TVL was out of sync, update. Old: {:?}, new: {:?}",
83 onchain_tvl,
84 tvl
85 );
86
87 2
89 } else {
90 log!(info, "on-chain TVL was OK: {:?}", tvl);
91
92 1
94 };
95
96 T::DbWeight::get()
101 .reads_writes(migrated.saturating_mul(2).saturating_add(2).into(), writes)
102 }
103
104 #[cfg(feature = "try-runtime")]
105 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
106 Ok(())
107 }
108 }
109
110 pub struct DelegationStakeMigration<T, MaxPools>(core::marker::PhantomData<(T, MaxPools)>);
128
129 impl<T: Config, MaxPools: Get<u32>> OnRuntimeUpgrade for DelegationStakeMigration<T, MaxPools> {
130 fn on_runtime_upgrade() -> Weight {
131 let mut count: u32 = 0;
132
133 BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).for_each(|id| {
134 let pool_acc = Pallet::<T>::generate_bonded_account(id);
135
136 if T::StakeAdapter::pool_strategy(Pool::from(pool_acc)) ==
138 adapter::StakeStrategyType::Transfer
139 {
140 let _ = Pallet::<T>::migrate_to_delegate_stake(id).map_err(|err| {
141 log!(
142 warn,
143 "failed to migrate pool {:?} to delegate stake strategy with err: {:?}",
144 id,
145 err
146 )
147 });
148 count.saturating_inc();
149 }
150 });
151
152 log!(info, "migrated {:?} pools to delegate stake strategy", count);
153
154 T::DbWeight::get()
156 .reads_writes(2, 0)
157 .saturating_mul(MaxPools::get() as u64)
158 .saturating_add(T::WeightInfo::pool_migrate().saturating_mul(count.into()))
160 }
161
162 #[cfg(feature = "try-runtime")]
163 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
164 ensure!(
166 T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
167 "Current strategy is not `Delegate"
168 );
169
170 if BondedPools::<T>::count() > MaxPools::get() {
171 log!(
173 warn,
174 "Number of pools {} exceeds the maximum bound {}. This would leave some pools unmigrated.", BondedPools::<T>::count(), MaxPools::get()
175 );
176 }
177
178 let mut pool_balances: Vec<BalanceOf<T>> = Vec::new();
179 BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).for_each(|id| {
180 let pool_account = Pallet::<T>::generate_bonded_account(id);
181
182 let pool_balance = T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
184 .unwrap_or(T::Currency::total_balance(&pool_account));
186
187 pool_balances.push(pool_balance);
188 });
189
190 Ok(pool_balances.encode())
191 }
192
193 #[cfg(feature = "try-runtime")]
194 fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
195 let expected_pool_balances: Vec<BalanceOf<T>> = Decode::decode(&mut &data[..]).unwrap();
196
197 for (index, id) in
198 BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).enumerate()
199 {
200 let pool_account = Pallet::<T>::generate_bonded_account(id);
201 if T::StakeAdapter::pool_strategy(Pool::from(pool_account.clone())) ==
202 adapter::StakeStrategyType::Transfer
203 {
204 log!(error, "Pool {} failed to migrate", id,);
205 return Err(TryRuntimeError::Other("Pool failed to migrate"));
206 }
207
208 let actual_balance =
209 T::StakeAdapter::total_balance(Pool::from(pool_account.clone()))
210 .expect("after migration, this should return a value");
211 let expected_balance = expected_pool_balances.get(index).unwrap();
212
213 if actual_balance != *expected_balance {
214 log!(
215 error,
216 "Pool {} balance mismatch. Expected: {:?}, Actual: {:?}",
217 id,
218 expected_balance,
219 actual_balance
220 );
221 return Err(TryRuntimeError::Other("Pool balance mismatch"));
222 }
223
224 let pool_account_balance = T::Currency::total_balance(&pool_account);
226 if pool_account_balance != Zero::zero() {
227 log!(
228 error,
229 "Pool account balance was expected to be zero. Pool: {}, Balance: {:?}",
230 id,
231 pool_account_balance
232 );
233 return Err(TryRuntimeError::Other("Pool account balance not migrated"));
234 }
235 }
236
237 Ok(())
238 }
239 }
240
241 pub struct ClaimTrappedBalance<T, A>(core::marker::PhantomData<(T, A)>);
246
247 impl<T: Config, A: Get<T::AccountId>> OnRuntimeUpgrade for ClaimTrappedBalance<T, A> {
248 fn on_runtime_upgrade() -> Weight {
249 let member_account = A::get();
250 match Pallet::<T>::do_claim_trapped_balance(&member_account) {
251 Ok(()) => {
252 log!(info, "Successfully claimed trapped balance for {:?}", member_account);
253 },
254 Err(e) => {
255 log!(info, "No trapped balance to claim for {:?}: {:?}", member_account, e);
256 },
257 }
258
259 T::WeightInfo::apply_slash()
261 .saturating_add(T::WeightInfo::withdraw_unbonded_update(T::MaxUnbonding::get()))
262 }
263
264 #[cfg(feature = "try-runtime")]
265 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
266 let member_account = A::get();
267 let expected = PoolMembers::<T>::get(&member_account)
268 .map(|m| m.total_balance())
269 .unwrap_or_default();
270 let actual =
271 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()))
272 .unwrap_or_default();
273
274 log!(
275 info,
276 "pre_upgrade: member {:?}, expected_balance: {:?}, actual_balance: {:?}, \
277 trapped: {:?}",
278 member_account,
279 expected,
280 actual,
281 actual.saturating_sub(expected)
282 );
283
284 Ok((expected, actual).encode())
285 }
286
287 #[cfg(feature = "try-runtime")]
288 fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
289 let member_account = A::get();
290 let (pre_expected, pre_actual): (BalanceOf<T>, BalanceOf<T>) =
291 Decode::decode(&mut &data[..])
292 .map_err(|_| TryRuntimeError::Other("Failed to decode pre_upgrade data"))?;
293
294 let post_actual =
295 T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone()))
296 .unwrap_or_default();
297
298 let post_expected = PoolMembers::<T>::get(&member_account)
299 .map(|m| m.total_balance())
300 .unwrap_or_default();
301
302 log!(
303 info,
304 "post_upgrade: member {:?}, pre_expected: {:?}, pre_actual: {:?}, \
305 post_expected: {:?}, post_actual: {:?}",
306 member_account,
307 pre_expected,
308 pre_actual,
309 post_expected,
310 post_actual
311 );
312
313 if pre_actual > pre_expected {
315 ensure!(
316 post_actual == post_expected,
317 TryRuntimeError::Other("Trapped balance was not fully claimed after migration")
318 );
319 }
320
321 Ok(())
322 }
323 }
324}
325
326pub mod v8 {
327 use super::{v7::V7BondedPoolInner, *};
328
329 impl<T: Config> V7BondedPoolInner<T> {
330 fn migrate_to_v8(self) -> BondedPoolInner<T> {
331 BondedPoolInner {
332 commission: Commission {
333 current: self.commission.current,
334 max: self.commission.max,
335 change_rate: self.commission.change_rate,
336 throttle_from: self.commission.throttle_from,
337 claim_permission: None,
339 },
340 member_counter: self.member_counter,
341 points: self.points,
342 roles: self.roles,
343 state: self.state,
344 }
345 }
346 }
347
348 pub struct VersionUncheckedMigrateV7ToV8<T>(core::marker::PhantomData<T>);
349 impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV7ToV8<T> {
350 #[cfg(feature = "try-runtime")]
351 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
352 Ok(Vec::new())
353 }
354
355 fn on_runtime_upgrade() -> Weight {
356 let mut translated = 0u64;
357 BondedPools::<T>::translate::<V7BondedPoolInner<T>, _>(|_key, old_value| {
358 translated.saturating_inc();
359 Some(old_value.migrate_to_v8())
360 });
361 T::DbWeight::get().reads_writes(translated, translated + 1)
362 }
363
364 #[cfg(feature = "try-runtime")]
365 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
366 ensure!(
368 BondedPools::<T>::iter()
369 .all(|(_, inner)| inner.commission.claim_permission.is_none()),
370 "`claim_permission` value has not been set correctly."
371 );
372 Ok(())
373 }
374 }
375}
376
377pub(crate) mod v7 {
382 use super::*;
383
384 #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)]
385 #[codec(mel_bound(T: Config))]
386 #[scale_info(skip_type_params(T))]
387 pub struct V7Commission<T: Config> {
388 pub current: Option<(Perbill, T::AccountId)>,
389 pub max: Option<Perbill>,
390 pub change_rate: Option<CommissionChangeRate<BlockNumberFor<T>>>,
391 pub throttle_from: Option<BlockNumberFor<T>>,
392 }
393
394 #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)]
395 #[codec(mel_bound(T: Config))]
396 #[scale_info(skip_type_params(T))]
397 pub struct V7BondedPoolInner<T: Config> {
398 pub commission: V7Commission<T>,
399 pub member_counter: u32,
400 pub points: BalanceOf<T>,
401 pub roles: PoolRoles<T::AccountId>,
402 pub state: PoolState,
403 }
404
405 #[allow(dead_code)]
406 #[derive(DebugNoBound)]
407 #[cfg_attr(feature = "std", derive(Clone, PartialEq))]
408 pub struct V7BondedPool<T: Config> {
409 id: PoolId,
411 inner: V7BondedPoolInner<T>,
413 }
414
415 impl<T: Config> V7BondedPool<T> {
416 #[allow(dead_code)]
417 fn bonded_account(&self) -> T::AccountId {
418 Pallet::<T>::generate_bonded_account(self.id)
419 }
420 }
421
422 #[frame_support::storage_alias]
424 pub type BondedPools<T: Config> =
425 CountedStorageMap<Pallet<T>, Twox64Concat, PoolId, V7BondedPoolInner<T>>;
426
427 pub struct VersionUncheckedMigrateV6ToV7<T>(core::marker::PhantomData<T>);
428 impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7<T> {
429 fn on_runtime_upgrade() -> Weight {
430 let migrated = BondedPools::<T>::count();
431 let tvl: BalanceOf<T> = helpers::calculate_tvl_by_total_stake::<T>();
434
435 TotalValueLocked::<T>::set(tvl);
436
437 log!(info, "Upgraded {} pools with a TVL of {:?}", migrated, tvl);
438
439 T::DbWeight::get().reads_writes(migrated.saturating_mul(2).saturating_add(2).into(), 2)
444 }
445
446 #[cfg(feature = "try-runtime")]
447 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
448 Ok(Vec::new())
449 }
450
451 #[cfg(feature = "try-runtime")]
452 fn post_upgrade(_data: Vec<u8>) -> Result<(), TryRuntimeError> {
453 let tvl: BalanceOf<T> = helpers::calculate_tvl_by_total_stake::<T>();
456 ensure!(
457 TotalValueLocked::<T>::get() == tvl,
458 "TVL written is not equal to `Staking::total_stake` of all `BondedPools`."
459 );
460
461 let total_balance_members: BalanceOf<T> = PoolMembers::<T>::iter()
464 .map(|(_, member)| member.total_balance())
465 .reduce(|acc, total_balance| acc + total_balance)
466 .unwrap_or_default();
467
468 ensure!(
469 TotalValueLocked::<T>::get() <= total_balance_members,
470 "TVL is greater than the balance of all PoolMembers."
471 );
472
473 ensure!(
474 Pallet::<T>::on_chain_storage_version() >= 7,
475 "nomination-pools::migration::v7: wrong storage version"
476 );
477
478 Ok(())
479 }
480 }
481}
482
483mod v6 {
484 use super::*;
485
486 pub struct MigrateToV6<T>(core::marker::PhantomData<T>);
489
490 impl<T: Config> MigrateToV6<T> {
491 fn freeze_ed(pool_id: PoolId) -> Result<(), ()> {
492 let reward_acc = Pallet::<T>::generate_reward_account(pool_id);
493 Pallet::<T>::freeze_pool_deposit(&reward_acc).map_err(|e| {
494 log!(error, "Failed to freeze ED for pool {} with error: {:?}", pool_id, e);
495 ()
496 })
497 }
498 }
499 impl<T: Config> UncheckedOnRuntimeUpgrade for MigrateToV6<T> {
500 fn on_runtime_upgrade() -> Weight {
501 let mut success = 0u64;
502 let mut fail = 0u64;
503
504 BondedPools::<T>::iter_keys().for_each(|p| {
505 if Self::freeze_ed(p).is_ok() {
506 success.saturating_inc();
507 } else {
508 fail.saturating_inc();
509 }
510 });
511
512 if fail > 0 {
513 log!(error, "Failed to freeze ED for {} pools", fail);
514 } else {
515 log!(info, "Freezing ED succeeded for {} pools", success);
516 }
517
518 let total = success.saturating_add(fail);
519 T::DbWeight::get().reads_writes(3u64.saturating_mul(total), 2u64.saturating_mul(total))
523 }
524
525 #[cfg(feature = "try-runtime")]
526 fn post_upgrade(_data: Vec<u8>) -> Result<(), TryRuntimeError> {
527 Pallet::<T>::check_ed_imbalance().map(|_| ())
529 }
530 }
531}
532pub mod v5 {
533 use super::*;
534
535 #[derive(Decode)]
536 pub struct OldRewardPool<T: Config> {
537 last_recorded_reward_counter: T::RewardCounter,
538 last_recorded_total_payouts: BalanceOf<T>,
539 total_rewards_claimed: BalanceOf<T>,
540 }
541
542 impl<T: Config> OldRewardPool<T> {
543 fn migrate_to_v5(self) -> RewardPool<T> {
544 RewardPool {
545 last_recorded_reward_counter: self.last_recorded_reward_counter,
546 last_recorded_total_payouts: self.last_recorded_total_payouts,
547 total_rewards_claimed: self.total_rewards_claimed,
548 total_commission_pending: Zero::zero(),
549 total_commission_claimed: Zero::zero(),
550 }
551 }
552 }
553
554 pub struct MigrateToV5<T>(core::marker::PhantomData<T>);
557 impl<T: Config> OnRuntimeUpgrade for MigrateToV5<T> {
558 fn on_runtime_upgrade() -> Weight {
559 let in_code = Pallet::<T>::in_code_storage_version();
560 let onchain = Pallet::<T>::on_chain_storage_version();
561
562 log!(
563 info,
564 "Running migration with in-code storage version {:?} / onchain {:?}",
565 in_code,
566 onchain
567 );
568
569 if in_code == 5 && onchain == 4 {
570 let mut translated = 0u64;
571 RewardPools::<T>::translate::<OldRewardPool<T>, _>(|_id, old_value| {
572 translated.saturating_inc();
573 Some(old_value.migrate_to_v5())
574 });
575
576 in_code.put::<Pallet<T>>();
577 log!(info, "Upgraded {} pools, storage to version {:?}", translated, in_code);
578
579 T::DbWeight::get().reads_writes(translated + 1, translated + 1)
582 } else {
583 log!(info, "Migration did not execute. This probably should be removed");
584 T::DbWeight::get().reads(1)
585 }
586 }
587
588 #[cfg(feature = "try-runtime")]
589 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
590 let rpool_keys = RewardPools::<T>::iter_keys().count();
591 let rpool_values = RewardPools::<T>::iter_values().count();
592 if rpool_keys != rpool_values {
593 log!(info, "🔥 There are {} undecodable RewardPools in storage. This migration will try to correct them. keys: {}, values: {}", rpool_keys.saturating_sub(rpool_values), rpool_keys, rpool_values);
594 }
595
596 ensure!(
597 PoolMembers::<T>::iter_keys().count() == PoolMembers::<T>::iter_values().count(),
598 "There are undecodable PoolMembers in storage. This migration will not fix that."
599 );
600 ensure!(
601 BondedPools::<T>::iter_keys().count() == BondedPools::<T>::iter_values().count(),
602 "There are undecodable BondedPools in storage. This migration will not fix that."
603 );
604 ensure!(
605 SubPoolsStorage::<T>::iter_keys().count() ==
606 SubPoolsStorage::<T>::iter_values().count(),
607 "There are undecodable SubPools in storage. This migration will not fix that."
608 );
609 ensure!(
610 Metadata::<T>::iter_keys().count() == Metadata::<T>::iter_values().count(),
611 "There are undecodable Metadata in storage. This migration will not fix that."
612 );
613
614 Ok((rpool_values as u64).encode())
615 }
616
617 #[cfg(feature = "try-runtime")]
618 fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
619 let old_rpool_values: u64 = Decode::decode(&mut &data[..]).unwrap();
620 let rpool_keys = RewardPools::<T>::iter_keys().count() as u64;
621 let rpool_values = RewardPools::<T>::iter_values().count() as u64;
622 ensure!(
623 rpool_keys == rpool_values,
624 "There are STILL undecodable RewardPools - migration failed"
625 );
626
627 if old_rpool_values != rpool_values {
628 log!(
629 info,
630 "🎉 Fixed {} undecodable RewardPools.",
631 rpool_values.saturating_sub(old_rpool_values)
632 );
633 }
634
635 ensure!(
638 RewardPools::<T>::iter().all(|(_, reward_pool)| reward_pool
639 .total_commission_pending >=
640 Zero::zero() && reward_pool
641 .total_commission_claimed >=
642 Zero::zero()),
643 "a commission value has been incorrectly set"
644 );
645 ensure!(
646 Pallet::<T>::on_chain_storage_version() >= 5,
647 "nomination-pools::migration::v5: wrong storage version"
648 );
649
650 ensure!(
652 PoolMembers::<T>::iter_keys().count() == PoolMembers::<T>::iter_values().count(),
653 "There are undecodable PoolMembers in storage."
654 );
655 ensure!(
656 BondedPools::<T>::iter_keys().count() == BondedPools::<T>::iter_values().count(),
657 "There are undecodable BondedPools in storage."
658 );
659 ensure!(
660 SubPoolsStorage::<T>::iter_keys().count() ==
661 SubPoolsStorage::<T>::iter_values().count(),
662 "There are undecodable SubPools in storage."
663 );
664 ensure!(
665 Metadata::<T>::iter_keys().count() == Metadata::<T>::iter_values().count(),
666 "There are undecodable Metadata in storage."
667 );
668
669 Ok(())
670 }
671 }
672}
673
674pub mod v4 {
675 use super::*;
676
677 #[derive(Decode)]
678 pub struct OldBondedPoolInner<T: Config> {
679 pub points: BalanceOf<T>,
680 pub state: PoolState,
681 pub member_counter: u32,
682 pub roles: PoolRoles<T::AccountId>,
683 }
684
685 impl<T: Config> OldBondedPoolInner<T> {
686 fn migrate_to_v4(self) -> BondedPoolInner<T> {
687 BondedPoolInner {
688 commission: Commission::default(),
689 member_counter: self.member_counter,
690 points: self.points,
691 state: self.state,
692 roles: self.roles,
693 }
694 }
695 }
696
697 #[allow(deprecated)]
699 pub type MigrateV3ToV5<T, U> = (v4::MigrateToV4<T, U>, v5::MigrateToV5<T>);
700
701 #[deprecated(
709 note = "To avoid mangled storage please use `MigrateV3ToV5` instead. See: github.com/paritytech/substrate/pull/13715"
710 )]
711 pub struct MigrateToV4<T, U>(core::marker::PhantomData<(T, U)>);
712 #[allow(deprecated)]
713 impl<T: Config, U: Get<Perbill>> OnRuntimeUpgrade for MigrateToV4<T, U> {
714 fn on_runtime_upgrade() -> Weight {
715 let current = Pallet::<T>::in_code_storage_version();
716 let onchain = Pallet::<T>::on_chain_storage_version();
717
718 log!(
719 info,
720 "Running migration with in-code storage version {:?} / onchain {:?}",
721 current,
722 onchain
723 );
724
725 if onchain == 3 {
726 log!(warn, "Please run MigrateToV5 immediately after this migration. See github.com/paritytech/substrate/pull/13715");
727 let initial_global_max_commission = U::get();
728 GlobalMaxCommission::<T>::set(Some(initial_global_max_commission));
729 log!(
730 info,
731 "Set initial global max commission to {:?}.",
732 initial_global_max_commission
733 );
734
735 let mut translated = 0u64;
736 BondedPools::<T>::translate::<OldBondedPoolInner<T>, _>(|_key, old_value| {
737 translated.saturating_inc();
738 Some(old_value.migrate_to_v4())
739 });
740
741 StorageVersion::new(4).put::<Pallet<T>>();
742 log!(info, "Upgraded {} pools, storage to version {:?}", translated, current);
743
744 T::DbWeight::get().reads_writes(translated + 1, translated + 2)
747 } else {
748 log!(info, "Migration did not execute. This probably should be removed");
749 T::DbWeight::get().reads(1)
750 }
751 }
752
753 #[cfg(feature = "try-runtime")]
754 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
755 Ok(Vec::new())
756 }
757
758 #[cfg(feature = "try-runtime")]
759 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
760 ensure!(
762 BondedPools::<T>::iter().all(|(_, inner)|
763 (inner.commission.current.is_none() ||
765 inner.commission.current.is_some()) &&
766 (inner.commission.max.is_none() || inner.commission.max.is_some()) &&
768 (inner.commission.change_rate.is_none() ||
770 inner.commission.change_rate.is_some()) &&
771 (inner.commission.throttle_from.is_none() ||
773 inner.commission.throttle_from.is_some())),
774 "a commission value has not been set correctly"
775 );
776 ensure!(
777 GlobalMaxCommission::<T>::get() == Some(U::get()),
778 "global maximum commission error"
779 );
780 ensure!(
781 Pallet::<T>::on_chain_storage_version() >= 4,
782 "nomination-pools::migration::v4: wrong storage version"
783 );
784 Ok(())
785 }
786 }
787}
788
789pub mod v3 {
790 use super::*;
791
792 pub struct MigrateToV3<T>(core::marker::PhantomData<T>);
794 impl<T: Config> OnRuntimeUpgrade for MigrateToV3<T> {
795 fn on_runtime_upgrade() -> Weight {
796 let current = Pallet::<T>::in_code_storage_version();
797 let onchain = Pallet::<T>::on_chain_storage_version();
798
799 if onchain == 2 {
800 log!(
801 info,
802 "Running migration with in-code storage version {:?} / onchain {:?}",
803 current,
804 onchain
805 );
806
807 let mut metadata_iterated = 0u64;
808 let mut metadata_removed = 0u64;
809 Metadata::<T>::iter_keys()
810 .filter(|id| {
811 metadata_iterated += 1;
812 !BondedPools::<T>::contains_key(&id)
813 })
814 .collect::<Vec<_>>()
815 .into_iter()
816 .for_each(|id| {
817 metadata_removed += 1;
818 Metadata::<T>::remove(&id);
819 });
820 StorageVersion::new(3).put::<Pallet<T>>();
821 let total_reads = metadata_iterated * 2 + 1;
823 let total_writes = metadata_removed + 1;
825 T::DbWeight::get().reads_writes(total_reads, total_writes)
826 } else {
827 log!(info, "MigrateToV3 should be removed");
828 T::DbWeight::get().reads(1)
829 }
830 }
831
832 #[cfg(feature = "try-runtime")]
833 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
834 Ok(Vec::new())
835 }
836
837 #[cfg(feature = "try-runtime")]
838 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
839 ensure!(
840 Metadata::<T>::iter_keys().all(|id| BondedPools::<T>::contains_key(&id)),
841 "not all of the stale metadata has been removed"
842 );
843 ensure!(
844 Pallet::<T>::on_chain_storage_version() >= 3,
845 "nomination-pools::migration::v3: wrong storage version"
846 );
847 Ok(())
848 }
849 }
850}
851
852pub mod v2 {
853 use super::*;
854 use sp_runtime::Perbill;
855
856 #[test]
857 fn migration_assumption_is_correct() {
858 use crate::mock::*;
862 ExtBuilder::default().build_and_execute(|| {
863 let join = |x| {
864 Currency::set_balance(&x, Balances::minimum_balance() + 10);
865 frame_support::assert_ok!(Pools::join(RuntimeOrigin::signed(x), 10, 1));
866 };
867
868 assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 10);
869 assert_eq!(
870 RewardPools::<Runtime>::get(1).unwrap(),
871 RewardPool { ..Default::default() }
872 );
873 assert_eq!(
874 PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
875 Zero::zero()
876 );
877
878 join(20);
879 assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 20);
880 assert_eq!(
881 RewardPools::<Runtime>::get(1).unwrap(),
882 RewardPool { ..Default::default() }
883 );
884 assert_eq!(
885 PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
886 Zero::zero()
887 );
888 assert_eq!(
889 PoolMembers::<Runtime>::get(20).unwrap().last_recorded_reward_counter,
890 Zero::zero()
891 );
892
893 join(30);
894 assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 30);
895 assert_eq!(
896 RewardPools::<Runtime>::get(1).unwrap(),
897 RewardPool { ..Default::default() }
898 );
899 assert_eq!(
900 PoolMembers::<Runtime>::get(10).unwrap().last_recorded_reward_counter,
901 Zero::zero()
902 );
903 assert_eq!(
904 PoolMembers::<Runtime>::get(20).unwrap().last_recorded_reward_counter,
905 Zero::zero()
906 );
907 assert_eq!(
908 PoolMembers::<Runtime>::get(30).unwrap().last_recorded_reward_counter,
909 Zero::zero()
910 );
911 });
912 }
913
914 #[derive(Decode)]
915 pub struct OldRewardPool<B> {
916 pub balance: B,
917 pub total_earnings: B,
918 pub points: U256,
919 }
920
921 #[derive(Decode)]
922 pub struct OldPoolMember<T: Config> {
923 pub pool_id: PoolId,
924 pub points: BalanceOf<T>,
925 pub reward_pool_total_earnings: BalanceOf<T>,
926 pub unbonding_eras: BoundedBTreeMap<EraIndex, BalanceOf<T>, T::MaxUnbonding>,
927 }
928
929 pub struct MigrateToV2<T>(core::marker::PhantomData<T>);
932 impl<T: Config> MigrateToV2<T> {
933 fn run(current: StorageVersion) -> Weight {
934 let mut reward_pools_translated = 0u64;
935 let mut members_translated = 0u64;
936 let mut total_value_locked = BalanceOf::<T>::zero();
938 let mut total_points_locked = BalanceOf::<T>::zero();
939
940 let mut temp_members = BTreeMap::<PoolId, Vec<(T::AccountId, BalanceOf<T>)>>::new();
943 PoolMembers::<T>::translate::<OldPoolMember<T>, _>(|key, old_member| {
944 let id = old_member.pool_id;
945 temp_members.entry(id).or_default().push((key, old_member.points));
946
947 total_points_locked += old_member.points;
948 members_translated += 1;
949 Some(PoolMember::<T> {
950 last_recorded_reward_counter: Zero::zero(),
951 pool_id: old_member.pool_id,
952 points: old_member.points,
953 unbonding_eras: old_member.unbonding_eras,
954 })
955 });
956
957 RewardPools::<T>::translate::<OldRewardPool<BalanceOf<T>>, _>(
959 |id, _old_reward_pool| {
960 let members = match temp_members.get(&id) {
962 Some(x) => x,
963 None => {
964 log!(error, "pool {} has no member! deleting it..", id);
965 return None;
966 },
967 };
968 let bonded_pool = match BondedPools::<T>::get(id) {
969 Some(x) => x,
970 None => {
971 log!(error, "pool {} has no bonded pool! deleting it..", id);
972 return None;
973 },
974 };
975
976 let accumulated_reward = RewardPool::<T>::current_balance(id);
977 let reward_account = Pallet::<T>::generate_reward_account(id);
978 let mut sum_paid_out = BalanceOf::<T>::zero();
979
980 members
981 .into_iter()
982 .filter_map(|(who, points)| {
983 let bonded_pool = match BondedPool::<T>::get(id) {
984 Some(x) => x,
985 None => {
986 log!(error, "pool {} for member {:?} does not exist!", id, who);
987 return None;
988 },
989 };
990
991 total_value_locked += bonded_pool.points_to_balance(*points);
992 let portion = Perbill::from_rational(*points, bonded_pool.points);
993 let last_claim = portion * accumulated_reward;
994
995 log!(
996 debug,
997 "{:?} has {:?} ({:?}) of pool {} with total reward of {:?}",
998 who,
999 portion,
1000 last_claim,
1001 id,
1002 accumulated_reward
1003 );
1004
1005 if last_claim.is_zero() {
1006 None
1007 } else {
1008 Some((who, last_claim))
1009 }
1010 })
1011 .for_each(|(who, last_claim)| {
1012 let outcome = T::Currency::transfer(
1013 &reward_account,
1014 &who,
1015 last_claim,
1016 Preservation::Preserve,
1017 );
1018
1019 if let Err(reason) = outcome {
1020 log!(warn, "last reward claim failed due to {:?}", reason,);
1021 } else {
1022 sum_paid_out = sum_paid_out.saturating_add(last_claim);
1023 }
1024
1025 Pallet::<T>::deposit_event(Event::<T>::PaidOut {
1026 member: who.clone(),
1027 pool_id: id,
1028 payout: last_claim,
1029 });
1030 });
1031
1032 let leftover = accumulated_reward.saturating_sub(sum_paid_out);
1035 if !leftover.is_zero() {
1036 let o = T::Currency::transfer(
1038 &reward_account,
1039 &bonded_pool.roles.depositor,
1040 leftover,
1041 Preservation::Preserve,
1042 );
1043 log!(warn, "paying {:?} leftover to the depositor: {:?}", leftover, o);
1044 }
1045
1046 reward_pools_translated += 1;
1048
1049 Some(RewardPool {
1050 last_recorded_reward_counter: Zero::zero(),
1051 last_recorded_total_payouts: Zero::zero(),
1052 total_rewards_claimed: Zero::zero(),
1053 total_commission_claimed: Zero::zero(),
1054 total_commission_pending: Zero::zero(),
1055 })
1056 },
1057 );
1058
1059 log!(
1060 info,
1061 "Upgraded {} members, {} reward pools, TVL {:?} TPL {:?}, storage to version {:?}",
1062 members_translated,
1063 reward_pools_translated,
1064 total_value_locked,
1065 total_points_locked,
1066 current
1067 );
1068 current.put::<Pallet<T>>();
1069
1070 T::DbWeight::get().reads_writes(members_translated + 1, reward_pools_translated + 1)
1071 }
1072 }
1073
1074 impl<T: Config> OnRuntimeUpgrade for MigrateToV2<T> {
1075 fn on_runtime_upgrade() -> Weight {
1076 let current = Pallet::<T>::in_code_storage_version();
1077 let onchain = Pallet::<T>::on_chain_storage_version();
1078
1079 log!(
1080 info,
1081 "Running migration with in-code storage version {:?} / onchain {:?}",
1082 current,
1083 onchain
1084 );
1085
1086 if current == 2 && onchain == 1 {
1087 Self::run(current)
1088 } else {
1089 log!(info, "MigrateToV2 did not executed. This probably should be removed");
1090 T::DbWeight::get().reads(1)
1091 }
1092 }
1093
1094 #[cfg(feature = "try-runtime")]
1095 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
1096 RewardPools::<T>::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> {
1098 ensure!(
1099 <T::Currency as frame_support::traits::fungible::Inspect<T::AccountId>>::balance(
1100 &Pallet::<T>::generate_reward_account(id)
1101 ) >= T::Currency::minimum_balance(),
1102 "Reward accounts must have greater balance than ED."
1103 );
1104 Ok(())
1105 })?;
1106
1107 Ok(Vec::new())
1108 }
1109
1110 #[cfg(feature = "try-runtime")]
1111 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
1112 ensure!(
1114 Pallet::<T>::on_chain_storage_version() == 2,
1115 "The onchain version must be updated after the migration."
1116 );
1117
1118 ensure!(
1120 RewardPools::<T>::iter().count() as u32 == RewardPools::<T>::count(),
1121 "The count of reward pools must remain the same after the migration."
1122 );
1123 ensure!(
1124 BondedPools::<T>::iter().count() as u32 == BondedPools::<T>::count(),
1125 "The count of reward pools must remain the same after the migration."
1126 );
1127
1128 RewardPools::<T>::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> {
1131 ensure!(
1132 RewardPool::<T>::current_balance(id) == Zero::zero(),
1133 "Reward pool balance must be zero.",
1134 );
1135 Ok(())
1136 })?;
1137
1138 log!(info, "post upgrade hook for MigrateToV2 executed.");
1139 Ok(())
1140 }
1141 }
1142}
1143
1144pub mod v1 {
1145 use super::*;
1146
1147 #[derive(Decode)]
1148 pub struct OldPoolRoles<AccountId> {
1149 pub depositor: AccountId,
1150 pub root: AccountId,
1151 pub nominator: AccountId,
1152 pub bouncer: AccountId,
1153 }
1154
1155 impl<AccountId> OldPoolRoles<AccountId> {
1156 fn migrate_to_v1(self) -> PoolRoles<AccountId> {
1157 PoolRoles {
1158 depositor: self.depositor,
1159 root: Some(self.root),
1160 nominator: Some(self.nominator),
1161 bouncer: Some(self.bouncer),
1162 }
1163 }
1164 }
1165
1166 #[derive(Decode)]
1167 pub struct OldBondedPoolInner<T: Config> {
1168 pub points: BalanceOf<T>,
1169 pub state: PoolState,
1170 pub member_counter: u32,
1171 pub roles: OldPoolRoles<T::AccountId>,
1172 }
1173
1174 impl<T: Config> OldBondedPoolInner<T> {
1175 fn migrate_to_v1(self) -> BondedPoolInner<T> {
1176 BondedPoolInner {
1179 points: self.points,
1180 commission: Commission::default(),
1181 member_counter: self.member_counter,
1182 state: self.state,
1183 roles: self.roles.migrate_to_v1(),
1184 }
1185 }
1186 }
1187
1188 pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
1192 impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
1193 fn on_runtime_upgrade() -> Weight {
1194 let current = Pallet::<T>::in_code_storage_version();
1195 let onchain = Pallet::<T>::on_chain_storage_version();
1196
1197 log!(
1198 info,
1199 "Running migration with in-code storage version {:?} / onchain {:?}",
1200 current,
1201 onchain
1202 );
1203
1204 if current == 1 && onchain == 0 {
1205 let mut translated = 0u64;
1207 BondedPools::<T>::translate::<OldBondedPoolInner<T>, _>(|_key, old_value| {
1208 translated.saturating_inc();
1209 Some(old_value.migrate_to_v1())
1210 });
1211
1212 current.put::<Pallet<T>>();
1213
1214 log!(info, "Upgraded {} pools, storage to version {:?}", translated, current);
1215
1216 T::DbWeight::get().reads_writes(translated + 1, translated + 1)
1217 } else {
1218 log!(info, "Migration did not executed. This probably should be removed");
1219 T::DbWeight::get().reads(1)
1220 }
1221 }
1222
1223 #[cfg(feature = "try-runtime")]
1224 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
1225 ensure!(
1227 Pallet::<T>::on_chain_storage_version() == 1,
1228 "The onchain version must be updated after the migration."
1229 );
1230 Pallet::<T>::try_state(frame_system::Pallet::<T>::block_number())?;
1231 Ok(())
1232 }
1233 }
1234}
1235
1236mod helpers {
1237 use super::*;
1238
1239 pub(crate) fn calculate_tvl_by_total_stake<T: Config>() -> BalanceOf<T> {
1240 BondedPools::<T>::iter_keys()
1241 .map(|id| {
1242 T::StakeAdapter::total_stake(Pool::from(Pallet::<T>::generate_bonded_account(id)))
1243 })
1244 .reduce(|acc, total_balance| acc + total_balance)
1245 .unwrap_or_default()
1246 }
1247}