1pub mod v09;
61pub mod v10;
62pub mod v11;
63pub mod v12;
64pub mod v13;
65pub mod v14;
66pub mod v15;
67pub mod v16;
68include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs"));
69
70use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET};
71use codec::{Codec, Decode};
72use core::marker::PhantomData;
73use frame_support::{
74 pallet_prelude::*,
75 traits::{ConstU32, OnRuntimeUpgrade},
76 weights::WeightMeter,
77};
78use sp_runtime::Saturating;
79
80#[cfg(feature = "try-runtime")]
81use alloc::vec::Vec;
82#[cfg(feature = "try-runtime")]
83use sp_runtime::TryRuntimeError;
84
85const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed";
86const PROOF_DECODE: &str =
87 "We encode to the same type in this trait only. No other code touches this item; qed";
88
89fn invalid_version(version: StorageVersion) -> ! {
90 panic!("Required migration {version:?} not supported by this runtime. This is a bug.");
91}
92
93pub type Cursor = BoundedVec<u8, ConstU32<1024>>;
96
97pub enum IsFinished {
99 Yes,
100 No,
101}
102
103pub trait MigrationStep: Codec + MaxEncodedLen + Default {
108 const VERSION: u16;
110
111 fn max_step_weight() -> Weight;
113
114 fn step(&mut self, meter: &mut WeightMeter) -> IsFinished;
118
119 fn integrity_test(max_block_weight: Weight) {
122 if Self::max_step_weight().any_gt(max_block_weight) {
123 panic!(
124 "Invalid max_step_weight for Migration {}. Value should be lower than {}",
125 Self::VERSION,
126 max_block_weight
127 );
128 }
129
130 let len = <Self as MaxEncodedLen>::max_encoded_len();
131 let max = Cursor::bound();
132 if len > max {
133 panic!(
134 "Migration {} has size {} which is bigger than the maximum of {}",
135 Self::VERSION,
136 len,
137 max,
138 );
139 }
140 }
141
142 #[cfg(feature = "try-runtime")]
144 fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
145 Ok(Vec::new())
146 }
147
148 #[cfg(feature = "try-runtime")]
150 fn post_upgrade_step(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
151 Ok(())
152 }
153}
154
155#[doc(hidden)]
157#[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)]
158pub struct NoopMigration<const N: u16>;
159
160impl<const N: u16> MigrationStep for NoopMigration<N> {
161 const VERSION: u16 = N;
162 fn max_step_weight() -> Weight {
163 Weight::zero()
164 }
165 fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished {
166 log::debug!(target: LOG_TARGET, "Noop migration for version {}", N);
167 IsFinished::Yes
168 }
169}
170
171mod private {
172 use crate::migration::MigrationStep;
173 pub trait Sealed {}
174 #[impl_trait_for_tuples::impl_for_tuples(10)]
175 #[tuple_types_custom_trait_bound(MigrationStep)]
176 impl Sealed for Tuple {}
177}
178
179pub trait MigrateSequence: private::Sealed {
184 const VERSION_RANGE: (u16, u16);
199
200 fn new(version: StorageVersion) -> Cursor;
202
203 #[cfg(feature = "try-runtime")]
204 fn pre_upgrade_step(_version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
205 Ok(Vec::new())
206 }
207
208 #[cfg(feature = "try-runtime")]
209 fn post_upgrade_step(_version: StorageVersion, _state: Vec<u8>) -> Result<(), TryRuntimeError> {
210 Ok(())
211 }
212
213 fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult;
215
216 fn integrity_test(max_block_weight: Weight);
219
220 fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool {
224 let (low, high) = Self::VERSION_RANGE;
225 target == high && in_storage + 1 == low
226 }
227}
228
229pub struct Migration<T: Config, const TEST_ALL_STEPS: bool = true>(PhantomData<T>);
235
236#[cfg(feature = "try-runtime")]
237impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
238 fn run_all_steps() -> Result<(), TryRuntimeError> {
239 let mut meter = &mut WeightMeter::new();
240 let name = <Pallet<T>>::name();
241 loop {
242 let in_progress_version = <Pallet<T>>::on_chain_storage_version() + 1;
243 let state = T::Migrations::pre_upgrade_step(in_progress_version)?;
244 let before = meter.consumed();
245 let status = Self::migrate(&mut meter);
246 log::info!(
247 target: LOG_TARGET,
248 "{name}: Migration step {:?} weight = {}",
249 in_progress_version,
250 meter.consumed() - before
251 );
252 T::Migrations::post_upgrade_step(in_progress_version, state)?;
253 if matches!(status, MigrateResult::Completed) {
254 break
255 }
256 }
257
258 let name = <Pallet<T>>::name();
259 log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed());
260 Ok(())
261 }
262}
263
264impl<T: Config, const TEST_ALL_STEPS: bool> OnRuntimeUpgrade for Migration<T, TEST_ALL_STEPS> {
265 fn on_runtime_upgrade() -> Weight {
266 let name = <Pallet<T>>::name();
267 let in_code_version = <Pallet<T>>::in_code_storage_version();
268 let on_chain_version = <Pallet<T>>::on_chain_storage_version();
269
270 if on_chain_version == in_code_version {
271 log::warn!(
272 target: LOG_TARGET,
273 "{name}: No Migration performed storage_version = latest_version = {:?}",
274 &on_chain_version
275 );
276 return T::WeightInfo::on_runtime_upgrade_noop()
277 }
278
279 if Self::in_progress() {
282 log::warn!(
283 target: LOG_TARGET,
284 "{name}: Migration already in progress {:?}",
285 &on_chain_version
286 );
287
288 return T::WeightInfo::on_runtime_upgrade_in_progress()
289 }
290
291 log::info!(
292 target: LOG_TARGET,
293 "{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.",
294 );
295
296 let cursor = T::Migrations::new(on_chain_version + 1);
297 MigrationInProgress::<T>::set(Some(cursor));
298
299 #[cfg(feature = "try-runtime")]
300 if TEST_ALL_STEPS {
301 Self::run_all_steps().unwrap();
302 }
303
304 T::WeightInfo::on_runtime_upgrade()
305 }
306
307 #[cfg(feature = "try-runtime")]
308 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
309 let on_chain_version = <Pallet<T>>::on_chain_storage_version();
313 let in_code_version = <Pallet<T>>::in_code_storage_version();
314
315 if on_chain_version == in_code_version {
316 return Ok(Default::default())
317 }
318
319 log::debug!(
320 target: LOG_TARGET,
321 "Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)",
322 <Pallet<T>>::name(), on_chain_version, in_code_version
323 );
324
325 ensure!(
326 T::Migrations::is_upgrade_supported(on_chain_version, in_code_version),
327 "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)"
328 );
329
330 Ok(Default::default())
331 }
332
333 #[cfg(feature = "try-runtime")]
334 fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
335 if !TEST_ALL_STEPS {
336 return Ok(())
337 }
338
339 log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ===");
340
341 if let Some(hash) = crate::CodeInfoOf::<T>::iter_keys().next() {
343 crate::CodeInfoOf::<T>::get(hash).expect("CodeInfo exists for hash; qed");
344 }
345 if let Some(hash) = crate::PristineCode::<T>::iter_keys().next() {
346 crate::PristineCode::<T>::get(hash).expect("PristineCode exists for hash; qed");
347 }
348 if let Some(account_id) = crate::ContractInfoOf::<T>::iter_keys().next() {
349 crate::ContractInfoOf::<T>::get(account_id)
350 .expect("ContractInfo exists for account_id; qed");
351 }
352 if let Some(nonce) = crate::DeletionQueue::<T>::iter_keys().next() {
353 crate::DeletionQueue::<T>::get(nonce).expect("DeletionQueue exists for nonce; qed");
354 }
355
356 Ok(())
357 }
358}
359
360#[derive(Debug, PartialEq)]
362pub enum MigrateResult {
363 NoMigrationPerformed,
365 NoMigrationInProgress,
367 InProgress { steps_done: u32 },
369 Completed,
371}
372
373#[derive(Debug, PartialEq)]
375pub enum StepResult {
376 InProgress { cursor: Cursor, steps_done: u32 },
377 Completed { steps_done: u32 },
378}
379
380impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
381 pub(crate) fn integrity_test() {
384 let max_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
385 T::Migrations::integrity_test(max_weight)
386 }
387
388 pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult {
391 let name = <Pallet<T>>::name();
392
393 if meter.try_consume(T::WeightInfo::migrate()).is_err() {
394 return MigrateResult::NoMigrationPerformed
395 }
396
397 MigrationInProgress::<T>::mutate_exists(|progress| {
398 let Some(cursor_before) = progress.as_mut() else {
399 meter.consume(T::WeightInfo::migration_noop());
400 return MigrateResult::NoMigrationInProgress
401 };
402
403 let storage_version = <Pallet<T>>::on_chain_storage_version();
405 let in_progress_version = storage_version + 1;
406
407 log::info!(
408 target: LOG_TARGET,
409 "{name}: Migrating from {:?} to {:?},",
410 storage_version,
411 in_progress_version,
412 );
413
414 let result =
415 match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter)
416 {
417 StepResult::InProgress { cursor, steps_done } => {
418 *progress = Some(cursor);
419 MigrateResult::InProgress { steps_done }
420 },
421 StepResult::Completed { steps_done } => {
422 in_progress_version.put::<Pallet<T>>();
423 if <Pallet<T>>::in_code_storage_version() != in_progress_version {
424 log::info!(
425 target: LOG_TARGET,
426 "{name}: Next migration is {:?},",
427 in_progress_version + 1
428 );
429 *progress = Some(T::Migrations::new(in_progress_version + 1));
430 MigrateResult::InProgress { steps_done }
431 } else {
432 log::info!(
433 target: LOG_TARGET,
434 "{name}: All migrations done. At version {:?},",
435 in_progress_version
436 );
437 *progress = None;
438 MigrateResult::Completed
439 }
440 },
441 };
442
443 result
444 })
445 }
446
447 pub(crate) fn ensure_migrated() -> DispatchResult {
448 if Self::in_progress() {
449 Err(Error::<T>::MigrationInProgress.into())
450 } else {
451 Ok(())
452 }
453 }
454
455 pub(crate) fn in_progress() -> bool {
456 MigrationInProgress::<T>::exists()
457 }
458}
459
460#[impl_trait_for_tuples::impl_for_tuples(10)]
461#[tuple_types_custom_trait_bound(MigrationStep)]
462impl MigrateSequence for Tuple {
463 const VERSION_RANGE: (u16, u16) = {
464 let mut versions: (u16, u16) = (0, 0);
465 for_tuples!(
466 #(
467 match versions {
468 (0, 0) => {
469 versions = (Tuple::VERSION, Tuple::VERSION);
470 },
471 (min_version, last_version) if Tuple::VERSION == last_version + 1 => {
472 versions = (min_version, Tuple::VERSION);
473 },
474 _ => panic!("Migrations must be ordered by their versions with no gaps.")
475 }
476 )*
477 );
478 versions
479 };
480
481 fn new(version: StorageVersion) -> Cursor {
482 for_tuples!(
483 #(
484 if version == Tuple::VERSION {
485 return Tuple::default().encode().try_into().expect(PROOF_ENCODE)
486 }
487 )*
488 );
489 invalid_version(version)
490 }
491
492 #[cfg(feature = "try-runtime")]
493 fn pre_upgrade_step(version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
495 for_tuples!(
496 #(
497 if version == Tuple::VERSION {
498 return Tuple::pre_upgrade_step()
499 }
500 )*
501 );
502 invalid_version(version)
503 }
504
505 #[cfg(feature = "try-runtime")]
506 fn post_upgrade_step(version: StorageVersion, state: Vec<u8>) -> Result<(), TryRuntimeError> {
508 for_tuples!(
509 #(
510 if version == Tuple::VERSION {
511 return Tuple::post_upgrade_step(state)
512 }
513 )*
514 );
515 invalid_version(version)
516 }
517
518 fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult {
519 for_tuples!(
520 #(
521 if version == Tuple::VERSION {
522 let mut migration = <Tuple as Decode>::decode(&mut cursor)
523 .expect(PROOF_DECODE);
524 let max_weight = Tuple::max_step_weight();
525 let mut steps_done = 0;
526 while meter.can_consume(max_weight) {
527 steps_done.saturating_accrue(1);
528 if matches!(migration.step(meter), IsFinished::Yes) {
529 return StepResult::Completed{ steps_done }
530 }
531 }
532 return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done }
533 }
534 )*
535 );
536 invalid_version(version)
537 }
538
539 fn integrity_test(max_block_weight: Weight) {
540 for_tuples!(
541 #(
542 Tuple::integrity_test(max_block_weight);
543 )*
544 );
545 }
546}
547
548#[cfg(test)]
549mod test {
550 use super::*;
551 use crate::{
552 migration::codegen::LATEST_MIGRATION_VERSION,
553 tests::{ExtBuilder, Test},
554 };
555
556 #[derive(Default, Encode, Decode, MaxEncodedLen)]
557 struct MockMigration<const N: u16> {
558 count: u16,
560 }
561
562 impl<const N: u16> MigrationStep for MockMigration<N> {
563 const VERSION: u16 = N;
564 fn max_step_weight() -> Weight {
565 Weight::from_all(1)
566 }
567 fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
568 assert!(self.count != N);
569 self.count += 1;
570 meter.consume(Weight::from_all(1));
571 if self.count == N {
572 IsFinished::Yes
573 } else {
574 IsFinished::No
575 }
576 }
577 }
578
579 #[test]
580 fn test_storage_version_matches_last_migration_file() {
581 assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION);
582 }
583
584 #[test]
585 fn version_range_works() {
586 let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE;
587 assert_eq!(range, (1, 2));
588 }
589
590 #[test]
591 fn is_upgrade_supported_works() {
592 type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>);
593 assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11)));
594 assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11)));
595 assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12)));
596 }
597
598 #[test]
599 fn steps_works() {
600 type Migrations = (MockMigration<2>, MockMigration<3>);
601 let version = StorageVersion::new(2);
602 let mut cursor = Migrations::new(version);
603
604 let mut meter = WeightMeter::with_limit(Weight::from_all(1));
605 let result = Migrations::steps(version, &cursor, &mut meter);
606 cursor = alloc::vec![1u8, 0].try_into().unwrap();
607 assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 });
608 assert_eq!(meter.consumed(), Weight::from_all(1));
609
610 let mut meter = WeightMeter::with_limit(Weight::from_all(1));
611 assert_eq!(
612 Migrations::steps(version, &cursor, &mut meter),
613 StepResult::Completed { steps_done: 1 }
614 );
615 }
616
617 #[test]
618 fn no_migration_in_progress_works() {
619 type TestMigration = Migration<Test>;
620
621 ExtBuilder::default().build().execute_with(|| {
622 assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
623 assert_eq!(
624 TestMigration::migrate(&mut WeightMeter::new()),
625 MigrateResult::NoMigrationInProgress
626 )
627 });
628 }
629
630 #[test]
631 fn migration_works() {
632 type TestMigration = Migration<Test, false>;
633
634 ExtBuilder::default()
635 .set_storage_version(LATEST_MIGRATION_VERSION - 2)
636 .build()
637 .execute_with(|| {
638 assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION - 2);
639 TestMigration::on_runtime_upgrade();
640 for (version, status) in [
641 (LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }),
642 (LATEST_MIGRATION_VERSION, MigrateResult::Completed),
643 ] {
644 assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status);
645 assert_eq!(
646 <Pallet<Test>>::on_chain_storage_version(),
647 StorageVersion::new(version)
648 );
649 }
650
651 assert_eq!(
652 TestMigration::migrate(&mut WeightMeter::new()),
653 MigrateResult::NoMigrationInProgress
654 );
655 assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
656 });
657 }
658}