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