1use crate::{
19 defensive,
20 storage::{storage_prefix, transactional::with_transaction_opaque_err},
21 traits::{
22 Defensive, GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, SafeMode,
23 StorageVersion,
24 },
25 weights::{RuntimeDbWeight, Weight, WeightMeter},
26};
27use alloc::vec::Vec;
28use codec::{Decode, Encode, MaxEncodedLen};
29use core::marker::PhantomData;
30use impl_trait_for_tuples::impl_for_tuples;
31use sp_arithmetic::traits::Bounded;
32use sp_core::Get;
33use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult};
34use sp_runtime::traits::Zero;
35
36pub struct VersionedMigration<const FROM: u16, const TO: u16, Inner, Pallet, Weight> {
98 _marker: PhantomData<(Inner, Pallet, Weight)>,
99}
100
101#[derive(Encode, Decode)]
104pub enum VersionedPostUpgradeData {
105 MigrationExecuted(alloc::vec::Vec<u8>),
107 Noop,
109}
110
111impl<
118 const FROM: u16,
119 const TO: u16,
120 Inner: crate::traits::UncheckedOnRuntimeUpgrade,
121 Pallet: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess,
122 DbWeight: Get<RuntimeDbWeight>,
123 > crate::traits::OnRuntimeUpgrade for VersionedMigration<FROM, TO, Inner, Pallet, DbWeight>
124{
125 #[cfg(feature = "try-runtime")]
129 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
130 let on_chain_version = Pallet::on_chain_storage_version();
131 if on_chain_version == FROM {
132 Ok(VersionedPostUpgradeData::MigrationExecuted(Inner::pre_upgrade()?).encode())
133 } else {
134 Ok(VersionedPostUpgradeData::Noop.encode())
135 }
136 }
137
138 fn on_runtime_upgrade() -> Weight {
145 let on_chain_version = Pallet::on_chain_storage_version();
146 if on_chain_version == FROM {
147 log::info!(
148 "๐ Pallet {:?} VersionedMigration migrating storage version from {:?} to {:?}.",
149 Pallet::name(),
150 FROM,
151 TO
152 );
153
154 let weight = Inner::on_runtime_upgrade();
156
157 StorageVersion::new(TO).put::<Pallet>();
159
160 weight.saturating_add(DbWeight::get().reads_writes(1, 1))
161 } else {
162 log::warn!(
163 "๐ Pallet {:?} VersionedMigration migration {}->{} can be removed; on-chain is already at {:?}.",
164 Pallet::name(),
165 FROM,
166 TO,
167 on_chain_version
168 );
169 DbWeight::get().reads(1)
170 }
171 }
172
173 #[cfg(feature = "try-runtime")]
178 fn post_upgrade(
179 versioned_post_upgrade_data_bytes: alloc::vec::Vec<u8>,
180 ) -> Result<(), sp_runtime::TryRuntimeError> {
181 use codec::DecodeAll;
182 match <VersionedPostUpgradeData>::decode_all(&mut &versioned_post_upgrade_data_bytes[..])
183 .map_err(|_| "VersionedMigration post_upgrade failed to decode PreUpgradeData")?
184 {
185 VersionedPostUpgradeData::MigrationExecuted(inner_bytes) =>
186 Inner::post_upgrade(inner_bytes),
187 VersionedPostUpgradeData::Noop => Ok(()),
188 }
189 }
190}
191
192pub trait StoreInCodeStorageVersion<T: GetStorageVersion + PalletInfoAccess> {
194 fn store_in_code_storage_version();
196}
197
198impl<T: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess>
199 StoreInCodeStorageVersion<T> for StorageVersion
200{
201 fn store_in_code_storage_version() {
202 let version = <T as GetStorageVersion>::in_code_storage_version();
203 version.put::<T>();
204 }
205}
206
207impl<T: GetStorageVersion<InCodeStorageVersion = NoStorageVersionSet> + PalletInfoAccess>
208 StoreInCodeStorageVersion<T> for NoStorageVersionSet
209{
210 fn store_in_code_storage_version() {
211 StorageVersion::default().put::<T>();
212 }
213}
214
215pub trait PalletVersionToStorageVersionHelper {
217 fn migrate(db_weight: &RuntimeDbWeight) -> Weight;
218}
219
220impl<T: GetStorageVersion + PalletInfoAccess> PalletVersionToStorageVersionHelper for T
221where
222 T::InCodeStorageVersion: StoreInCodeStorageVersion<T>,
223{
224 fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
225 const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:";
226
227 fn pallet_version_key(name: &str) -> [u8; 32] {
228 crate::storage::storage_prefix(name.as_bytes(), PALLET_VERSION_STORAGE_KEY_POSTFIX)
229 }
230
231 sp_io::storage::clear(&pallet_version_key(<T as PalletInfoAccess>::name()));
232
233 <T::InCodeStorageVersion as StoreInCodeStorageVersion<T>>::store_in_code_storage_version();
234
235 db_weight.writes(2)
236 }
237}
238
239#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
240#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
241#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
242impl PalletVersionToStorageVersionHelper for T {
243 fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
244 let mut weight = Weight::zero();
245
246 for_tuples!( #( weight = weight.saturating_add(T::migrate(db_weight)); )* );
247
248 weight
249 }
250}
251
252pub fn migrate_from_pallet_version_to_storage_version<
256 Pallets: PalletVersionToStorageVersionHelper,
257>(
258 db_weight: &RuntimeDbWeight,
259) -> Weight {
260 Pallets::migrate(db_weight)
261}
262
263pub struct RemovePallet<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
315 PhantomData<(P, DbWeight)>,
316);
317impl<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>> frame_support::traits::OnRuntimeUpgrade
318 for RemovePallet<P, DbWeight>
319{
320 fn on_runtime_upgrade() -> frame_support::weights::Weight {
321 let hashed_prefix = twox_128(P::get().as_bytes());
322 let keys_removed = match clear_prefix(&hashed_prefix, None) {
323 KillStorageResult::AllRemoved(value) => value,
324 KillStorageResult::SomeRemaining(value) => {
325 log::error!(
326 "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! ๐จ",
327 P::get()
328 );
329 value
330 },
331 } as u64;
332
333 log::info!("Removed {} {} keys ๐งน", keys_removed, P::get());
334
335 DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
336 }
337
338 #[cfg(feature = "try-runtime")]
339 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
340 use crate::storage::unhashed::contains_prefixed_key;
341
342 let hashed_prefix = twox_128(P::get().as_bytes());
343 match contains_prefixed_key(&hashed_prefix) {
344 true => log::info!("Found {} keys pre-removal ๐", P::get()),
345 false => log::warn!(
346 "Migration RemovePallet<{}> can be removed (no keys found pre-removal).",
347 P::get()
348 ),
349 };
350 Ok(alloc::vec::Vec::new())
351 }
352
353 #[cfg(feature = "try-runtime")]
354 fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
355 use crate::storage::unhashed::contains_prefixed_key;
356
357 let hashed_prefix = twox_128(P::get().as_bytes());
358 match contains_prefixed_key(&hashed_prefix) {
359 true => {
360 log::error!("{} has keys remaining post-removal โ", P::get());
361 return Err("Keys remaining post-removal, this should never happen ๐จ".into())
362 },
363 false => log::info!("No {} keys found post-removal ๐", P::get()),
364 };
365 Ok(())
366 }
367}
368
369pub struct RemoveStorage<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
422 PhantomData<(P, S, DbWeight)>,
423);
424impl<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>
425 frame_support::traits::OnRuntimeUpgrade for RemoveStorage<P, S, DbWeight>
426{
427 fn on_runtime_upgrade() -> frame_support::weights::Weight {
428 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
429 let keys_removed = match clear_prefix(&hashed_prefix, None) {
430 KillStorageResult::AllRemoved(value) => value,
431 KillStorageResult::SomeRemaining(value) => {
432 log::error!(
433 "`clear_prefix` failed to remove all keys for storage `{}` from pallet `{}`. THIS SHOULD NEVER HAPPEN! ๐จ",
434 S::get(), P::get()
435 );
436 value
437 },
438 } as u64;
439
440 log::info!("Removed `{}` `{}` `{}` keys ๐งน", keys_removed, P::get(), S::get());
441
442 DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
443 }
444
445 #[cfg(feature = "try-runtime")]
446 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
447 use crate::storage::unhashed::contains_prefixed_key;
448
449 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
450 match contains_prefixed_key(&hashed_prefix) {
451 true => log::info!("Found `{}` `{}` keys pre-removal ๐", P::get(), S::get()),
452 false => log::warn!(
453 "Migration RemoveStorage<{}, {}> can be removed (no keys found pre-removal).",
454 P::get(),
455 S::get()
456 ),
457 };
458 Ok(Default::default())
459 }
460
461 #[cfg(feature = "try-runtime")]
462 fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
463 use crate::storage::unhashed::contains_prefixed_key;
464
465 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
466 match contains_prefixed_key(&hashed_prefix) {
467 true => {
468 log::error!("`{}` `{}` has keys remaining post-removal โ", P::get(), S::get());
469 return Err("Keys remaining post-removal, this should never happen ๐จ".into())
470 },
471 false => log::info!("No `{}` `{}` keys found post-removal ๐", P::get(), S::get()),
472 };
473 Ok(())
474 }
475}
476
477pub trait SteppedMigration {
479 type Cursor: codec::FullCodec + codec::MaxEncodedLen;
481
482 type Identifier: codec::FullCodec + codec::MaxEncodedLen;
484
485 fn id() -> Self::Identifier;
489
490 fn max_steps() -> Option<u32> {
496 None
497 }
498
499 fn step(
506 cursor: Option<Self::Cursor>,
507 meter: &mut WeightMeter,
508 ) -> Result<Option<Self::Cursor>, SteppedMigrationError>;
509
510 fn transactional_step(
512 mut cursor: Option<Self::Cursor>,
513 meter: &mut WeightMeter,
514 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
515 with_transaction_opaque_err(move || match Self::step(cursor, meter) {
516 Ok(new_cursor) => {
517 cursor = new_cursor;
518 sp_runtime::TransactionOutcome::Commit(Ok(cursor))
519 },
520 Err(err) => sp_runtime::TransactionOutcome::Rollback(Err(err)),
521 })
522 .map_err(|()| SteppedMigrationError::Failed)?
523 }
524
525 #[cfg(feature = "try-runtime")]
530 fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
531 Ok(Vec::new())
532 }
533
534 #[cfg(feature = "try-runtime")]
540 fn post_upgrade(_state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
541 Ok(())
542 }
543}
544
545#[derive(Debug, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
547pub enum SteppedMigrationError {
548 InsufficientWeight {
554 required: Weight,
556 },
557 InvalidCursor,
564 Failed,
566}
567
568#[derive(MaxEncodedLen, Encode, Decode)]
572pub struct MigrationId<const N: usize> {
573 pub pallet_id: [u8; N],
574 pub version_from: u8,
575 pub version_to: u8,
576}
577
578#[impl_trait_for_tuples::impl_for_tuples(8)]
580pub trait MigrationStatusHandler {
581 fn started() {}
583
584 fn completed() {}
586}
587
588pub trait FailedMigrationHandler {
592 fn failed(migration: Option<u32>) -> FailedMigrationHandling;
598}
599
600pub struct FreezeChainOnFailedMigration;
604
605impl FailedMigrationHandler for FreezeChainOnFailedMigration {
606 fn failed(_migration: Option<u32>) -> FailedMigrationHandling {
607 FailedMigrationHandling::KeepStuck
608 }
609}
610
611pub struct EnterSafeModeOnFailedMigration<SM, Else: FailedMigrationHandler>(
616 PhantomData<(SM, Else)>,
617);
618
619impl<Else: FailedMigrationHandler, SM: SafeMode> FailedMigrationHandler
620 for EnterSafeModeOnFailedMigration<SM, Else>
621where
622 <SM as SafeMode>::BlockNumber: Bounded,
623{
624 fn failed(migration: Option<u32>) -> FailedMigrationHandling {
625 let entered = if SM::is_entered() {
626 SM::extend(Bounded::max_value())
627 } else {
628 SM::enter(Bounded::max_value())
629 };
630
631 if entered.is_err() {
633 Else::failed(migration)
634 } else {
635 FailedMigrationHandling::KeepStuck
636 }
637 }
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq)]
644pub enum FailedMigrationHandling {
645 ForceUnstuck,
650 KeepStuck,
652 Ignore,
657}
658
659pub trait MultiStepMigrator {
661 fn ongoing() -> bool;
663
664 fn step() -> Weight;
668}
669
670impl MultiStepMigrator for () {
671 fn ongoing() -> bool {
672 false
673 }
674
675 fn step() -> Weight {
676 Weight::zero()
677 }
678}
679
680pub trait SteppedMigrations {
682 fn len() -> u32;
684
685 fn nth_id(n: u32) -> Option<Vec<u8>>;
690
691 fn nth_max_steps(n: u32) -> Option<Option<u32>>;
695
696 fn nth_step(
700 n: u32,
701 cursor: Option<Vec<u8>>,
702 meter: &mut WeightMeter,
703 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
704
705 fn nth_transactional_step(
709 n: u32,
710 cursor: Option<Vec<u8>>,
711 meter: &mut WeightMeter,
712 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
713
714 #[cfg(feature = "try-runtime")]
718 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>>;
719
720 #[cfg(feature = "try-runtime")]
724 fn nth_post_upgrade(n: u32, _state: Vec<u8>)
725 -> Option<Result<(), sp_runtime::TryRuntimeError>>;
726
727 fn cursor_max_encoded_len() -> usize;
729
730 fn identifier_max_encoded_len() -> usize;
732
733 #[cfg(feature = "std")]
738 fn integrity_test() -> Result<(), &'static str> {
739 use crate::ensure;
740 let l = Self::len();
741
742 for n in 0..l {
743 ensure!(Self::nth_id(n).is_some(), "id is None");
744 ensure!(Self::nth_max_steps(n).is_some(), "steps is None");
745
746 ensure!(
748 Self::nth_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
749 "steps is None"
750 );
751 ensure!(
752 Self::nth_transactional_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
753 "steps is None"
754 );
755 }
756
757 Ok(())
758 }
759}
760
761impl SteppedMigrations for () {
762 fn len() -> u32 {
763 0
764 }
765
766 fn nth_id(_n: u32) -> Option<Vec<u8>> {
767 None
768 }
769
770 fn nth_max_steps(_n: u32) -> Option<Option<u32>> {
771 None
772 }
773
774 fn nth_step(
775 _n: u32,
776 _cursor: Option<Vec<u8>>,
777 _meter: &mut WeightMeter,
778 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
779 None
780 }
781
782 fn nth_transactional_step(
783 _n: u32,
784 _cursor: Option<Vec<u8>>,
785 _meter: &mut WeightMeter,
786 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
787 None
788 }
789
790 #[cfg(feature = "try-runtime")]
791 fn nth_pre_upgrade(_n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
792 Some(Ok(Vec::new()))
793 }
794
795 #[cfg(feature = "try-runtime")]
796 fn nth_post_upgrade(
797 _n: u32,
798 _state: Vec<u8>,
799 ) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
800 Some(Ok(()))
801 }
802
803 fn cursor_max_encoded_len() -> usize {
804 0
805 }
806
807 fn identifier_max_encoded_len() -> usize {
808 0
809 }
810}
811
812impl<T: SteppedMigration> SteppedMigrations for T {
814 fn len() -> u32 {
815 1
816 }
817
818 fn nth_id(n: u32) -> Option<Vec<u8>> {
819 n.is_zero()
820 .then(|| T::id().encode())
821 .defensive_proof("nth_id should only be called with n==0")
822 }
823
824 fn nth_max_steps(n: u32) -> Option<Option<u32>> {
825 n.is_zero()
827 .then(|| T::max_steps())
828 .defensive_proof("nth_max_steps should only be called with n==0")
829 }
830
831 fn nth_step(
832 n: u32,
833 cursor: Option<Vec<u8>>,
834 meter: &mut WeightMeter,
835 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
836 if !n.is_zero() {
837 defensive!("nth_step should only be called with n==0");
838 return None
839 }
840
841 let cursor = match cursor {
842 Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
843 Ok(cursor) => Some(cursor),
844 Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
845 },
846 None => None,
847 };
848
849 Some(T::step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())))
850 }
851
852 fn nth_transactional_step(
853 n: u32,
854 cursor: Option<Vec<u8>>,
855 meter: &mut WeightMeter,
856 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
857 if n != 0 {
858 defensive!("nth_transactional_step should only be called with n==0");
859 return None
860 }
861
862 let cursor = match cursor {
863 Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
864 Ok(cursor) => Some(cursor),
865 Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
866 },
867 None => None,
868 };
869
870 Some(
871 T::transactional_step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())),
872 )
873 }
874
875 #[cfg(feature = "try-runtime")]
876 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
877 if n != 0 {
878 defensive!("nth_pre_upgrade should only be called with n==0");
879 }
880
881 Some(T::pre_upgrade())
882 }
883
884 #[cfg(feature = "try-runtime")]
885 fn nth_post_upgrade(n: u32, state: Vec<u8>) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
886 if n != 0 {
887 defensive!("nth_post_upgrade should only be called with n==0");
888 }
889 Some(T::post_upgrade(state))
890 }
891
892 fn cursor_max_encoded_len() -> usize {
893 T::Cursor::max_encoded_len()
894 }
895
896 fn identifier_max_encoded_len() -> usize {
897 T::Identifier::max_encoded_len()
898 }
899}
900
901#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
902impl SteppedMigrations for Tuple {
903 fn len() -> u32 {
904 for_tuples!( #( Tuple::len() )+* )
905 }
906
907 fn nth_id(n: u32) -> Option<Vec<u8>> {
908 let mut i = 0;
909
910 for_tuples!( #(
911 if (i + Tuple::len()) > n {
912 return Tuple::nth_id(n - i)
913 }
914
915 i += Tuple::len();
916 )* );
917
918 None
919 }
920
921 fn nth_step(
922 n: u32,
923 cursor: Option<Vec<u8>>,
924 meter: &mut WeightMeter,
925 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
926 let mut i = 0;
927
928 for_tuples!( #(
929 if (i + Tuple::len()) > n {
930 return Tuple::nth_step(n - i, cursor, meter)
931 }
932
933 i += Tuple::len();
934 )* );
935
936 None
937 }
938
939 fn nth_transactional_step(
940 n: u32,
941 cursor: Option<Vec<u8>>,
942 meter: &mut WeightMeter,
943 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
944 let mut i = 0;
945
946 for_tuples! ( #(
947 if (i + Tuple::len()) > n {
948 return Tuple::nth_transactional_step(n - i, cursor, meter)
949 }
950
951 i += Tuple::len();
952 )* );
953
954 None
955 }
956
957 #[cfg(feature = "try-runtime")]
958 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, sp_runtime::TryRuntimeError>> {
959 let mut i = 0;
960
961 for_tuples! ( #(
962 if (i + Tuple::len()) > n {
963 return Tuple::nth_pre_upgrade(n - i)
964 }
965
966 i += Tuple::len();
967 )* );
968
969 None
970 }
971
972 #[cfg(feature = "try-runtime")]
973 fn nth_post_upgrade(n: u32, state: Vec<u8>) -> Option<Result<(), sp_runtime::TryRuntimeError>> {
974 let mut i = 0;
975
976 for_tuples! ( #(
977 if (i + Tuple::len()) > n {
978 return Tuple::nth_post_upgrade(n - i, state)
979 }
980
981 i += Tuple::len();
982 )* );
983
984 None
985 }
986
987 fn nth_max_steps(n: u32) -> Option<Option<u32>> {
988 let mut i = 0;
989
990 for_tuples!( #(
991 if (i + Tuple::len()) > n {
992 return Tuple::nth_max_steps(n - i)
993 }
994
995 i += Tuple::len();
996 )* );
997
998 None
999 }
1000
1001 fn cursor_max_encoded_len() -> usize {
1002 let mut max_len = 0;
1003
1004 for_tuples!( #(
1005 max_len = max_len.max(Tuple::cursor_max_encoded_len());
1006 )* );
1007
1008 max_len
1009 }
1010
1011 fn identifier_max_encoded_len() -> usize {
1012 let mut max_len = 0;
1013
1014 for_tuples!( #(
1015 max_len = max_len.max(Tuple::identifier_max_encoded_len());
1016 )* );
1017
1018 max_len
1019 }
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024 use super::*;
1025 use crate::{assert_ok, storage::unhashed};
1026
1027 #[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)]
1028 pub enum Either<L, R> {
1029 Left(L),
1030 Right(R),
1031 }
1032
1033 pub struct M0;
1034 impl SteppedMigration for M0 {
1035 type Cursor = ();
1036 type Identifier = u8;
1037
1038 fn id() -> Self::Identifier {
1039 0
1040 }
1041
1042 fn step(
1043 _cursor: Option<Self::Cursor>,
1044 _meter: &mut WeightMeter,
1045 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1046 log::info!("M0");
1047 unhashed::put(&[0], &());
1048 Ok(None)
1049 }
1050 }
1051
1052 pub struct M1;
1053 impl SteppedMigration for M1 {
1054 type Cursor = ();
1055 type Identifier = u8;
1056
1057 fn id() -> Self::Identifier {
1058 1
1059 }
1060
1061 fn step(
1062 _cursor: Option<Self::Cursor>,
1063 _meter: &mut WeightMeter,
1064 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1065 log::info!("M1");
1066 unhashed::put(&[1], &());
1067 Ok(None)
1068 }
1069
1070 fn max_steps() -> Option<u32> {
1071 Some(1)
1072 }
1073 }
1074
1075 pub struct M2;
1076 impl SteppedMigration for M2 {
1077 type Cursor = ();
1078 type Identifier = u8;
1079
1080 fn id() -> Self::Identifier {
1081 2
1082 }
1083
1084 fn step(
1085 _cursor: Option<Self::Cursor>,
1086 _meter: &mut WeightMeter,
1087 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1088 log::info!("M2");
1089 unhashed::put(&[2], &());
1090 Ok(None)
1091 }
1092
1093 fn max_steps() -> Option<u32> {
1094 Some(2)
1095 }
1096 }
1097
1098 pub struct F0;
1099 impl SteppedMigration for F0 {
1100 type Cursor = ();
1101 type Identifier = u8;
1102
1103 fn id() -> Self::Identifier {
1104 3
1105 }
1106
1107 fn step(
1108 _cursor: Option<Self::Cursor>,
1109 _meter: &mut WeightMeter,
1110 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1111 log::info!("F0");
1112 unhashed::put(&[3], &());
1113 Err(SteppedMigrationError::Failed)
1114 }
1115 }
1116
1117 type Triple = (M0, (M1, M2));
1119 type Hextuple = (Triple, Triple);
1121
1122 #[test]
1123 fn singular_migrations_work() {
1124 assert_eq!(M0::max_steps(), None);
1125 assert_eq!(M1::max_steps(), Some(1));
1126 assert_eq!(M2::max_steps(), Some(2));
1127
1128 assert_eq!(<(M0, M1)>::nth_max_steps(0), Some(None));
1129 assert_eq!(<(M0, M1)>::nth_max_steps(1), Some(Some(1)));
1130 assert_eq!(<(M0, M1, M2)>::nth_max_steps(2), Some(Some(2)));
1131
1132 assert_eq!(<(M0, M1)>::nth_max_steps(2), None);
1133 }
1134
1135 #[test]
1136 fn tuple_migrations_work() {
1137 assert_eq!(<() as SteppedMigrations>::len(), 0);
1138 assert_eq!(<((), ((), ())) as SteppedMigrations>::len(), 0);
1139 assert_eq!(<Triple as SteppedMigrations>::len(), 3);
1140 assert_eq!(<Hextuple as SteppedMigrations>::len(), 6);
1141
1142 assert_eq!(<Triple as SteppedMigrations>::nth_id(0), Some(0u8.encode()));
1145 assert_eq!(<Triple as SteppedMigrations>::nth_id(1), Some(1u8.encode()));
1146 assert_eq!(<Triple as SteppedMigrations>::nth_id(2), Some(2u8.encode()));
1147
1148 sp_io::TestExternalities::default().execute_with(|| {
1149 for n in 0..3 {
1150 <Triple as SteppedMigrations>::nth_step(
1151 n,
1152 Default::default(),
1153 &mut WeightMeter::new(),
1154 );
1155 }
1156 });
1157 }
1158
1159 #[test]
1160 fn integrity_test_works() {
1161 sp_io::TestExternalities::default().execute_with(|| {
1162 assert_ok!(<() as SteppedMigrations>::integrity_test());
1163 assert_ok!(<M0 as SteppedMigrations>::integrity_test());
1164 assert_ok!(<M1 as SteppedMigrations>::integrity_test());
1165 assert_ok!(<M2 as SteppedMigrations>::integrity_test());
1166 assert_ok!(<Triple as SteppedMigrations>::integrity_test());
1167 assert_ok!(<Hextuple as SteppedMigrations>::integrity_test());
1168 });
1169 }
1170
1171 #[test]
1172 fn transactional_rollback_works() {
1173 sp_io::TestExternalities::default().execute_with(|| {
1174 assert_ok!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1175 0,
1176 Default::default(),
1177 &mut WeightMeter::new()
1178 )
1179 .unwrap());
1180 assert!(unhashed::exists(&[0]));
1181
1182 let _g = crate::StorageNoopGuard::new();
1183 assert!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1184 1,
1185 Default::default(),
1186 &mut WeightMeter::new()
1187 )
1188 .unwrap()
1189 .is_err());
1190 assert!(<(F0, M1) as SteppedMigrations>::nth_transactional_step(
1191 0,
1192 Default::default(),
1193 &mut WeightMeter::new()
1194 )
1195 .unwrap()
1196 .is_err());
1197 });
1198 }
1199}