pallet_staking_async/
migrations.rs1use crate::{
21 log, reward::EraRewardManager, Config, DisableMintingGuard, RewardKind, RewardPot,
22 WeightedPointsFormulaStartEra,
23};
24use frame_support::{
25 migrations::VersionedMigration,
26 pallet_prelude::*,
27 traits::{
28 fungible::{Inspect, Mutate},
29 tokens::Preservation,
30 Get, OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade,
31 },
32 PalletId,
33};
34use sp_runtime::{traits::AccountIdConversion, Saturating};
35use sp_staking::EraIndex;
36
37pub struct MigrateEraPotsToPool<T, S, K>(core::marker::PhantomData<(T, S, K)>);
51
52impl<T: Config, S: Get<PalletId>, K: Get<RewardKind>> MigrateEraPotsToPool<T, S, K> {
53 fn old_pot_account(era: EraIndex) -> T::AccountId {
56 S::get().into_sub_account_truncating(RewardPot::Era(era, K::get()))
57 }
58}
59
60impl<T: Config, S: Get<PalletId>, K: Get<RewardKind>> OnRuntimeUpgrade
61 for MigrateEraPotsToPool<T, S, K>
62{
63 fn on_runtime_upgrade() -> Weight {
64 let mut weight = T::DbWeight::get().reads(2);
65
66 let Some(guard_era) = DisableMintingGuard::<T>::get() else {
67 log!(info, "EraPotsToPool: guard unset, nothing to migrate");
68 return weight;
69 };
70
71 let active_era_idx = crate::session_rotation::Rotator::<T>::active_era();
72 debug_assert!(
73 active_era_idx >= guard_era,
74 "active_era should always be past DisableMintingGuard once set"
75 );
76 if active_era_idx <= guard_era {
77 return weight;
78 }
79
80 let oldest = active_era_idx.saturating_sub(T::HistoryDepth::get()).max(guard_era);
83
84 let kind = K::get();
85 let mut migrated = 0u32;
86 for era in oldest..active_era_idx {
87 let old = Self::old_pot_account(era);
88 weight.saturating_accrue(T::DbWeight::get().reads(1));
89 if frame_system::Pallet::<T>::providers(&old) == 0 {
90 continue;
91 }
92
93 let new = EraRewardManager::<T>::create(era, kind);
96 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
97
98 let balance = T::Currency::balance(&old);
99 weight.saturating_accrue(T::DbWeight::get().reads(1));
100 if !balance.is_zero() {
101 if let Err(e) = T::Currency::transfer(&old, &new, balance, Preservation::Expendable)
102 {
103 log!(
104 error,
105 "EraPotsToPool: era {} kind {:?}: transfer failed: {:?}",
106 era,
107 kind,
108 e,
109 );
110 continue;
113 }
114 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
115 }
116
117 let _ = frame_system::Pallet::<T>::dec_providers(&old);
119 weight.saturating_accrue(T::DbWeight::get().writes(1));
120 migrated.saturating_accrue(1);
121 }
122
123 log!(
124 info,
125 "EraPotsToPool: migrated {} eras of kind {:?} from guard {} to active {}",
126 migrated,
127 kind,
128 guard_era,
129 active_era_idx,
130 );
131 weight
132 }
133
134 #[cfg(feature = "try-runtime")]
135 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
136 use crate::{BalanceOf, PotAccountProvider};
137 use codec::Encode;
138 use sp_runtime::traits::Zero;
139
140 let kind = K::get();
141 let mut total_old: BalanceOf<T> = Zero::zero();
142 let mut total_new_pre: BalanceOf<T> = Zero::zero();
143 for era in Self::migrated_eras() {
144 let old = Self::old_pot_account(era);
145 total_old.saturating_accrue(T::Currency::balance(&old));
146 let new = T::RewardPots::pot_account(RewardPot::Era(era, kind));
147 total_new_pre.saturating_accrue(T::Currency::balance(&new));
148 }
149 Ok((total_old, total_new_pre).encode())
150 }
151
152 #[cfg(feature = "try-runtime")]
153 fn post_upgrade(state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
154 use crate::{BalanceOf, PotAccountProvider};
155 use codec::Decode;
156 use sp_runtime::traits::Zero;
157
158 let (total_old, total_new_pre): (BalanceOf<T>, BalanceOf<T>) =
159 Decode::decode(&mut &state[..]).map_err(|_| "decode pre_upgrade state")?;
160
161 let kind = K::get();
162 let mut remaining_old: BalanceOf<T> = Zero::zero();
163 let mut total_new_post: BalanceOf<T> = Zero::zero();
164 for era in Self::migrated_eras() {
165 let old = Self::old_pot_account(era);
166 remaining_old.saturating_accrue(T::Currency::balance(&old));
167 let new = T::RewardPots::pot_account(RewardPot::Era(era, kind));
168 total_new_post.saturating_accrue(T::Currency::balance(&new));
169 }
170
171 frame_support::ensure!(
172 remaining_old.is_zero(),
173 "old pot accounts still hold balance after migration"
174 );
175 frame_support::ensure!(
178 total_new_post.saturating_sub(total_new_pre) == total_old,
179 "new pot balances did not increase by total_old after migration"
180 );
181 Ok(())
182 }
183}
184
185#[cfg(feature = "try-runtime")]
186impl<T: Config, S: Get<PalletId>, K: Get<RewardKind>> MigrateEraPotsToPool<T, S, K> {
187 fn migrated_eras() -> core::ops::Range<EraIndex> {
189 let active = crate::session_rotation::Rotator::<T>::active_era();
190 match DisableMintingGuard::<T>::get() {
191 Some(guard) if active > guard => {
192 let oldest = active.saturating_sub(T::HistoryDepth::get()).max(guard);
193 oldest..active
194 },
195 _ => 0..0,
196 }
197 }
198}
199
200pub type SetWeightedPointsFormulaStartEra<T> = VersionedMigration<
202 17,
203 18,
204 VersionUncheckedSetWeightedPointsFormulaStartEra<T>,
205 crate::Pallet<T>,
206 <T as frame_system::Config>::DbWeight,
207>;
208
209pub struct VersionUncheckedSetWeightedPointsFormulaStartEra<T>(core::marker::PhantomData<T>);
230
231impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedSetWeightedPointsFormulaStartEra<T> {
232 fn on_runtime_upgrade() -> Weight {
233 let active_era = crate::session_rotation::Rotator::<T>::active_era();
234 let cutoff = active_era.saturating_add(1);
238 WeightedPointsFormulaStartEra::<T>::put(cutoff);
239
240 log!(
241 info,
242 "WeightedPointsFormulaStartEra set to {} (active_era {} uses legacy formula)",
243 cutoff,
244 active_era,
245 );
246
247 T::DbWeight::get().reads_writes(1, 1)
248 }
249
250 #[cfg(feature = "try-runtime")]
251 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
252 use codec::Encode;
253 Ok(crate::session_rotation::Rotator::<T>::active_era().encode())
257 }
258
259 #[cfg(feature = "try-runtime")]
260 fn post_upgrade(state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
261 use codec::Decode;
262
263 let pre_active_era = EraIndex::decode(&mut &state[..]).map_err(|_| "decode active_era")?;
266 frame_support::ensure!(
267 WeightedPointsFormulaStartEra::<T>::get() == Some(pre_active_era.saturating_add(1)),
268 "cutoff must be active_era + 1 after the migration"
269 );
270
271 Ok(())
272 }
273}