1use crate::{
8 defensive,
9 storage::{storage_prefix, transactional::with_transaction_opaque_err},
10 traits::{
11 Defensive, GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, SafeMode,
12 StorageVersion,
13 },
14 weights::{RuntimeDbWeight, Weight, WeightMeter},
15};
16use alloc::vec::Vec;
17use codec::{Decode, Encode, MaxEncodedLen};
18use core::marker::PhantomData;
19use impl_trait_for_tuples::impl_for_tuples;
20use subsoil::arithmetic::traits::Bounded;
21use subsoil::core::Get;
22use subsoil::io::{hashing::twox_128, storage::clear_prefix, KillStorageResult};
23use subsoil::runtime::traits::Zero;
24
25pub struct VersionedMigration<const FROM: u16, const TO: u16, Inner, Pallet, Weight> {
87 _marker: PhantomData<(Inner, Pallet, Weight)>,
88}
89
90#[derive(Encode, Decode)]
93pub enum VersionedPostUpgradeData {
94 MigrationExecuted(alloc::vec::Vec<u8>),
96 Noop,
98}
99
100impl<
107 const FROM: u16,
108 const TO: u16,
109 Inner: crate::traits::UncheckedOnRuntimeUpgrade,
110 Pallet: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess,
111 DbWeight: Get<RuntimeDbWeight>,
112 > crate::traits::OnRuntimeUpgrade for VersionedMigration<FROM, TO, Inner, Pallet, DbWeight>
113{
114 #[cfg(feature = "try-runtime")]
118 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, subsoil::runtime::TryRuntimeError> {
119 let on_chain_version = Pallet::on_chain_storage_version();
120 if on_chain_version == FROM {
121 Ok(VersionedPostUpgradeData::MigrationExecuted(Inner::pre_upgrade()?).encode())
122 } else {
123 Ok(VersionedPostUpgradeData::Noop.encode())
124 }
125 }
126
127 fn on_runtime_upgrade() -> Weight {
134 let on_chain_version = Pallet::on_chain_storage_version();
135 if on_chain_version == FROM {
136 log::info!(
137 "๐ Pallet {:?} VersionedMigration migrating storage version from {:?} to {:?}.",
138 Pallet::name(),
139 FROM,
140 TO
141 );
142
143 let weight = Inner::on_runtime_upgrade();
145
146 StorageVersion::new(TO).put::<Pallet>();
148
149 weight.saturating_add(DbWeight::get().reads_writes(1, 1))
150 } else {
151 log::warn!(
152 "๐ Pallet {:?} VersionedMigration migration {}->{} can be removed; on-chain is already at {:?}.",
153 Pallet::name(),
154 FROM,
155 TO,
156 on_chain_version
157 );
158 DbWeight::get().reads(1)
159 }
160 }
161
162 #[cfg(feature = "try-runtime")]
167 fn post_upgrade(
168 versioned_post_upgrade_data_bytes: alloc::vec::Vec<u8>,
169 ) -> Result<(), subsoil::runtime::TryRuntimeError> {
170 use codec::DecodeAll;
171 match <VersionedPostUpgradeData>::decode_all(&mut &versioned_post_upgrade_data_bytes[..])
172 .map_err(|_| "VersionedMigration post_upgrade failed to decode PreUpgradeData")?
173 {
174 VersionedPostUpgradeData::MigrationExecuted(inner_bytes) => {
175 Inner::post_upgrade(inner_bytes)
176 },
177 VersionedPostUpgradeData::Noop => Ok(()),
178 }
179 }
180}
181
182pub trait StoreInCodeStorageVersion<T: GetStorageVersion + PalletInfoAccess> {
184 fn store_in_code_storage_version();
186}
187
188impl<T: GetStorageVersion<InCodeStorageVersion = StorageVersion> + PalletInfoAccess>
189 StoreInCodeStorageVersion<T> for StorageVersion
190{
191 fn store_in_code_storage_version() {
192 let version = <T as GetStorageVersion>::in_code_storage_version();
193 version.put::<T>();
194 }
195}
196
197impl<T: GetStorageVersion<InCodeStorageVersion = NoStorageVersionSet> + PalletInfoAccess>
198 StoreInCodeStorageVersion<T> for NoStorageVersionSet
199{
200 fn store_in_code_storage_version() {
201 StorageVersion::default().put::<T>();
202 }
203}
204
205pub trait PalletVersionToStorageVersionHelper {
207 fn migrate(db_weight: &RuntimeDbWeight) -> Weight;
208}
209
210impl<T: GetStorageVersion + PalletInfoAccess> PalletVersionToStorageVersionHelper for T
211where
212 T::InCodeStorageVersion: StoreInCodeStorageVersion<T>,
213{
214 fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
215 const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:";
216
217 fn pallet_version_key(name: &str) -> [u8; 32] {
218 crate::storage::storage_prefix(name.as_bytes(), PALLET_VERSION_STORAGE_KEY_POSTFIX)
219 }
220
221 subsoil::io::storage::clear(&pallet_version_key(<T as PalletInfoAccess>::name()));
222
223 <T::InCodeStorageVersion as StoreInCodeStorageVersion<T>>::store_in_code_storage_version();
224
225 db_weight.writes(2)
226 }
227}
228
229#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
230#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
231#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
232impl PalletVersionToStorageVersionHelper for T {
233 fn migrate(db_weight: &RuntimeDbWeight) -> Weight {
234 let mut weight = Weight::zero();
235
236 for_tuples!( #( weight = weight.saturating_add(T::migrate(db_weight)); )* );
237
238 weight
239 }
240}
241
242pub fn migrate_from_pallet_version_to_storage_version<
246 Pallets: PalletVersionToStorageVersionHelper,
247>(
248 db_weight: &RuntimeDbWeight,
249) -> Weight {
250 Pallets::migrate(db_weight)
251}
252
253pub struct RemovePallet<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
305 PhantomData<(P, DbWeight)>,
306);
307impl<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>> topsoil_core::traits::OnRuntimeUpgrade
308 for RemovePallet<P, DbWeight>
309{
310 fn on_runtime_upgrade() -> topsoil_core::weights::Weight {
311 let hashed_prefix = twox_128(P::get().as_bytes());
312 let keys_removed = match clear_prefix(&hashed_prefix, None) {
313 KillStorageResult::AllRemoved(value) => value,
314 KillStorageResult::SomeRemaining(value) => {
315 log::error!(
316 "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! ๐จ",
317 P::get()
318 );
319 value
320 },
321 } as u64;
322
323 log::info!("Removed {} {} keys ๐งน", keys_removed, P::get());
324
325 DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
326 }
327
328 #[cfg(feature = "try-runtime")]
329 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, subsoil::runtime::TryRuntimeError> {
330 use crate::storage::unhashed::contains_prefixed_key;
331
332 let hashed_prefix = twox_128(P::get().as_bytes());
333 match contains_prefixed_key(&hashed_prefix) {
334 true => log::info!("Found {} keys pre-removal ๐", P::get()),
335 false => log::warn!(
336 "Migration RemovePallet<{}> can be removed (no keys found pre-removal).",
337 P::get()
338 ),
339 };
340 Ok(alloc::vec::Vec::new())
341 }
342
343 #[cfg(feature = "try-runtime")]
344 fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), subsoil::runtime::TryRuntimeError> {
345 use crate::storage::unhashed::contains_prefixed_key;
346
347 let hashed_prefix = twox_128(P::get().as_bytes());
348 match contains_prefixed_key(&hashed_prefix) {
349 true => {
350 log::error!("{} has keys remaining post-removal โ", P::get());
351 return Err("Keys remaining post-removal, this should never happen ๐จ".into());
352 },
353 false => log::info!("No {} keys found post-removal ๐", P::get()),
354 };
355 Ok(())
356 }
357}
358
359pub struct RemoveStorage<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>(
412 PhantomData<(P, S, DbWeight)>,
413);
414impl<P: Get<&'static str>, S: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>>
415 topsoil_core::traits::OnRuntimeUpgrade for RemoveStorage<P, S, DbWeight>
416{
417 fn on_runtime_upgrade() -> topsoil_core::weights::Weight {
418 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
419 let keys_removed = match clear_prefix(&hashed_prefix, None) {
420 KillStorageResult::AllRemoved(value) => value,
421 KillStorageResult::SomeRemaining(value) => {
422 log::error!(
423 "`clear_prefix` failed to remove all keys for storage `{}` from pallet `{}`. THIS SHOULD NEVER HAPPEN! ๐จ",
424 S::get(), P::get()
425 );
426 value
427 },
428 } as u64;
429
430 log::info!("Removed `{}` `{}` `{}` keys ๐งน", keys_removed, P::get(), S::get());
431
432 DbWeight::get().reads_writes(keys_removed + 1, keys_removed)
433 }
434
435 #[cfg(feature = "try-runtime")]
436 fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, subsoil::runtime::TryRuntimeError> {
437 use crate::storage::unhashed::contains_prefixed_key;
438
439 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
440 match contains_prefixed_key(&hashed_prefix) {
441 true => log::info!("Found `{}` `{}` keys pre-removal ๐", P::get(), S::get()),
442 false => log::warn!(
443 "Migration RemoveStorage<{}, {}> can be removed (no keys found pre-removal).",
444 P::get(),
445 S::get()
446 ),
447 };
448 Ok(Default::default())
449 }
450
451 #[cfg(feature = "try-runtime")]
452 fn post_upgrade(_state: alloc::vec::Vec<u8>) -> Result<(), subsoil::runtime::TryRuntimeError> {
453 use crate::storage::unhashed::contains_prefixed_key;
454
455 let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes());
456 match contains_prefixed_key(&hashed_prefix) {
457 true => {
458 log::error!("`{}` `{}` has keys remaining post-removal โ", P::get(), S::get());
459 return Err("Keys remaining post-removal, this should never happen ๐จ".into());
460 },
461 false => log::info!("No `{}` `{}` keys found post-removal ๐", P::get(), S::get()),
462 };
463 Ok(())
464 }
465}
466
467pub trait SteppedMigration {
469 type Cursor: codec::FullCodec + codec::MaxEncodedLen;
471
472 type Identifier: codec::FullCodec + codec::MaxEncodedLen;
474
475 fn id() -> Self::Identifier;
479
480 fn max_steps() -> Option<u32> {
486 None
487 }
488
489 fn migrating_prefixes() -> Option<impl IntoIterator<Item = Vec<u8>>> {
496 None::<core::iter::Empty<_>>
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 subsoil::runtime::TransactionOutcome::Commit(Ok(cursor))
519 },
520 Err(err) => subsoil::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>, subsoil::runtime::TryRuntimeError> {
531 Ok(Vec::new())
532 }
533
534 #[cfg(feature = "try-runtime")]
540 fn post_upgrade(_state: Vec<u8>) -> Result<(), subsoil::runtime::TryRuntimeError> {
541 Ok(())
542 }
543}
544
545#[derive(Debug, Encode, Decode, MaxEncodedLen, PartialEq, Eq, 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 fn nth_migrating_prefixes(n: u32) -> Option<Option<Vec<Vec<u8>>>>;
718
719 #[cfg(feature = "try-runtime")]
723 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, subsoil::runtime::TryRuntimeError>>;
724
725 #[cfg(feature = "try-runtime")]
729 fn nth_post_upgrade(
730 n: u32,
731 _state: Vec<u8>,
732 ) -> Option<Result<(), subsoil::runtime::TryRuntimeError>>;
733
734 fn cursor_max_encoded_len() -> usize;
736
737 fn identifier_max_encoded_len() -> usize;
739
740 #[cfg(feature = "std")]
745 fn integrity_test() -> Result<(), &'static str> {
746 use crate::ensure;
747 let l = Self::len();
748
749 for n in 0..l {
750 ensure!(Self::nth_id(n).is_some(), "id is None");
751 ensure!(Self::nth_max_steps(n).is_some(), "steps is None");
752
753 ensure!(
755 Self::nth_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
756 "steps is None"
757 );
758 ensure!(
759 Self::nth_transactional_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
760 "steps is None"
761 );
762 }
763
764 Ok(())
765 }
766}
767
768impl SteppedMigrations for () {
769 fn len() -> u32 {
770 0
771 }
772
773 fn nth_id(_n: u32) -> Option<Vec<u8>> {
774 None
775 }
776
777 fn nth_max_steps(_n: u32) -> Option<Option<u32>> {
778 None
779 }
780
781 fn nth_step(
782 _n: u32,
783 _cursor: Option<Vec<u8>>,
784 _meter: &mut WeightMeter,
785 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
786 None
787 }
788
789 fn nth_transactional_step(
790 _n: u32,
791 _cursor: Option<Vec<u8>>,
792 _meter: &mut WeightMeter,
793 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
794 None
795 }
796
797 fn nth_migrating_prefixes(_n: u32) -> Option<Option<Vec<Vec<u8>>>> {
798 None
799 }
800
801 #[cfg(feature = "try-runtime")]
802 fn nth_pre_upgrade(_n: u32) -> Option<Result<Vec<u8>, subsoil::runtime::TryRuntimeError>> {
803 Some(Ok(Vec::new()))
804 }
805
806 #[cfg(feature = "try-runtime")]
807 fn nth_post_upgrade(
808 _n: u32,
809 _state: Vec<u8>,
810 ) -> Option<Result<(), subsoil::runtime::TryRuntimeError>> {
811 Some(Ok(()))
812 }
813
814 fn cursor_max_encoded_len() -> usize {
815 0
816 }
817
818 fn identifier_max_encoded_len() -> usize {
819 0
820 }
821}
822
823impl<T: SteppedMigration> SteppedMigrations for T {
825 fn len() -> u32 {
826 1
827 }
828 fn nth_id(n: u32) -> Option<Vec<u8>> {
830 n.is_zero()
831 .then(|| T::id().encode())
832 .defensive_proof("nth_id should only be called with n==0")
833 }
834
835 fn nth_max_steps(n: u32) -> Option<Option<u32>> {
836 n.is_zero()
837 .then(|| T::max_steps())
838 .defensive_proof("nth_max_steps should only be called with n==0")
839 }
840
841 fn nth_step(
842 n: u32,
843 cursor: Option<Vec<u8>>,
844 meter: &mut WeightMeter,
845 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
846 if !n.is_zero() {
847 defensive!("nth_step should only be called with n==0");
848 return None;
849 }
850
851 let cursor = match cursor {
852 Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
853 Ok(cursor) => Some(cursor),
854 Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
855 },
856 None => None,
857 };
858
859 Some(T::step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())))
860 }
861
862 fn nth_transactional_step(
863 n: u32,
864 cursor: Option<Vec<u8>>,
865 meter: &mut WeightMeter,
866 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
867 if n != 0 {
868 defensive!("nth_transactional_step should only be called with n==0");
869 return None;
870 }
871
872 let cursor = match cursor {
873 Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
874 Ok(cursor) => Some(cursor),
875 Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
876 },
877 None => None,
878 };
879
880 Some(
881 T::transactional_step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())),
882 )
883 }
884
885 fn nth_migrating_prefixes(n: u32) -> Option<Option<Vec<Vec<u8>>>> {
886 n.is_zero()
887 .then(|| T::migrating_prefixes().map(|p| p.into_iter().collect()))
888 .defensive_proof("nth_migrating_prefixes should only be called with n==0")
889 }
890
891 #[cfg(feature = "try-runtime")]
892 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, subsoil::runtime::TryRuntimeError>> {
893 if n != 0 {
894 defensive!("nth_pre_upgrade should only be called with n==0");
895 }
896
897 Some(T::pre_upgrade())
898 }
899
900 #[cfg(feature = "try-runtime")]
901 fn nth_post_upgrade(
902 n: u32,
903 state: Vec<u8>,
904 ) -> Option<Result<(), subsoil::runtime::TryRuntimeError>> {
905 if n != 0 {
906 defensive!("nth_post_upgrade should only be called with n==0");
907 }
908 Some(T::post_upgrade(state))
909 }
910
911 fn cursor_max_encoded_len() -> usize {
912 T::Cursor::max_encoded_len()
913 }
914
915 fn identifier_max_encoded_len() -> usize {
916 T::Identifier::max_encoded_len()
917 }
918}
919
920#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
921impl SteppedMigrations for Tuple {
922 fn len() -> u32 {
923 for_tuples!( #( Tuple::len() )+* )
924 }
925
926 fn nth_id(n: u32) -> Option<Vec<u8>> {
927 let mut i = 0;
928
929 for_tuples!( #(
930 if (i + Tuple::len()) > n {
931 return Tuple::nth_id(n - i)
932 }
933
934 i += Tuple::len();
935 )* );
936
937 None
938 }
939
940 fn nth_step(
941 n: u32,
942 cursor: Option<Vec<u8>>,
943 meter: &mut WeightMeter,
944 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
945 let mut i = 0;
946
947 for_tuples!( #(
948 if (i + Tuple::len()) > n {
949 return Tuple::nth_step(n - i, cursor, meter)
950 }
951
952 i += Tuple::len();
953 )* );
954
955 None
956 }
957
958 fn nth_transactional_step(
959 n: u32,
960 cursor: Option<Vec<u8>>,
961 meter: &mut WeightMeter,
962 ) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
963 let mut i = 0;
964
965 for_tuples! ( #(
966 if (i + Tuple::len()) > n {
967 return Tuple::nth_transactional_step(n - i, cursor, meter)
968 }
969
970 i += Tuple::len();
971 )* );
972
973 None
974 }
975
976 fn nth_migrating_prefixes(n: u32) -> Option<Option<Vec<Vec<u8>>>> {
977 let mut i = 0;
978 for_tuples!( #(
979 let len = Tuple::len() as u32;
980 if (i + len) > n {
981 return Tuple::nth_migrating_prefixes(n - i);
982 }
983 i += len;
984 )* );
985 None
986 }
987
988 #[cfg(feature = "try-runtime")]
989 fn nth_pre_upgrade(n: u32) -> Option<Result<Vec<u8>, subsoil::runtime::TryRuntimeError>> {
990 let mut i = 0;
991
992 for_tuples! ( #(
993 if (i + Tuple::len()) > n {
994 return Tuple::nth_pre_upgrade(n - i)
995 }
996
997 i += Tuple::len();
998 )* );
999
1000 None
1001 }
1002
1003 #[cfg(feature = "try-runtime")]
1004 fn nth_post_upgrade(
1005 n: u32,
1006 state: Vec<u8>,
1007 ) -> Option<Result<(), subsoil::runtime::TryRuntimeError>> {
1008 let mut i = 0;
1009
1010 for_tuples! ( #(
1011 if (i + Tuple::len()) > n {
1012 return Tuple::nth_post_upgrade(n - i, state)
1013 }
1014
1015 i += Tuple::len();
1016 )* );
1017
1018 None
1019 }
1020
1021 fn nth_max_steps(n: u32) -> Option<Option<u32>> {
1022 let mut i = 0;
1023
1024 for_tuples!( #(
1025 if (i + Tuple::len()) > n {
1026 return Tuple::nth_max_steps(n - i)
1027 }
1028
1029 i += Tuple::len();
1030 )* );
1031
1032 None
1033 }
1034
1035 fn cursor_max_encoded_len() -> usize {
1036 let mut max_len = 0;
1037
1038 for_tuples!( #(
1039 max_len = max_len.max(Tuple::cursor_max_encoded_len());
1040 )* );
1041
1042 max_len
1043 }
1044
1045 fn identifier_max_encoded_len() -> usize {
1046 let mut max_len = 0;
1047
1048 for_tuples!( #(
1049 max_len = max_len.max(Tuple::identifier_max_encoded_len());
1050 )* );
1051
1052 max_len
1053 }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059 use crate::{assert_ok, storage::unhashed};
1060
1061 #[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)]
1062 #[allow(dead_code)]
1063 pub enum Either<L, R> {
1064 Left(L),
1065 Right(R),
1066 }
1067
1068 pub struct M0;
1069 impl SteppedMigration for M0 {
1070 type Cursor = ();
1071 type Identifier = u8;
1072
1073 fn id() -> Self::Identifier {
1074 0
1075 }
1076
1077 fn step(
1078 _cursor: Option<Self::Cursor>,
1079 _meter: &mut WeightMeter,
1080 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1081 log::info!("M0");
1082 unhashed::put(&[0], &());
1083 Ok(None)
1084 }
1085
1086 fn migrating_prefixes() -> Option<impl IntoIterator<Item = Vec<u8>>> {
1087 Some(vec![b"M0_prefix1".to_vec(), b"M0_prefix2".to_vec()])
1088 }
1089 }
1090
1091 pub struct M1;
1092 impl SteppedMigration for M1 {
1093 type Cursor = ();
1094 type Identifier = u8;
1095
1096 fn id() -> Self::Identifier {
1097 1
1098 }
1099
1100 fn step(
1101 _cursor: Option<Self::Cursor>,
1102 _meter: &mut WeightMeter,
1103 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1104 log::info!("M1");
1105 unhashed::put(&[1], &());
1106 Ok(None)
1107 }
1108
1109 fn max_steps() -> Option<u32> {
1110 Some(1)
1111 }
1112
1113 fn migrating_prefixes() -> Option<impl IntoIterator<Item = Vec<u8>>> {
1114 Some(vec![b"M1_prefix".to_vec()])
1115 }
1116 }
1117
1118 pub struct M2;
1119 impl SteppedMigration for M2 {
1120 type Cursor = ();
1121 type Identifier = u8;
1122
1123 fn id() -> Self::Identifier {
1124 2
1125 }
1126
1127 fn step(
1128 _cursor: Option<Self::Cursor>,
1129 _meter: &mut WeightMeter,
1130 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1131 log::info!("M2");
1132 unhashed::put(&[2], &());
1133 Ok(None)
1134 }
1135
1136 fn max_steps() -> Option<u32> {
1137 Some(2)
1138 }
1139
1140 fn migrating_prefixes() -> Option<impl IntoIterator<Item = Vec<u8>>> {
1141 Some(vec![b"M2_prefix1".to_vec(), b"M2_prefix2".to_vec(), b"M2_prefix3".to_vec()])
1142 }
1143 }
1144
1145 pub struct M3;
1146 impl SteppedMigration for M3 {
1147 type Cursor = ();
1148 type Identifier = u8;
1149
1150 fn id() -> Self::Identifier {
1151 3
1152 }
1153
1154 fn step(
1155 _cursor: Option<Self::Cursor>,
1156 _meter: &mut WeightMeter,
1157 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1158 log::info!("M3");
1159 Ok(None)
1160 }
1161 }
1162
1163 pub struct F0;
1164 impl SteppedMigration for F0 {
1165 type Cursor = ();
1166 type Identifier = u8;
1167
1168 fn id() -> Self::Identifier {
1169 4
1170 }
1171
1172 fn step(
1173 _cursor: Option<Self::Cursor>,
1174 _meter: &mut WeightMeter,
1175 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
1176 log::info!("F0");
1177 unhashed::put(&[3], &());
1178 Err(SteppedMigrationError::Failed)
1179 }
1180 }
1181
1182 type Triple = (M0, (M1, M2));
1184 type Hextuple = (Triple, Triple);
1186
1187 #[test]
1188 fn singular_migrations_work() {
1189 assert_eq!(M0::max_steps(), None);
1190 assert_eq!(M1::max_steps(), Some(1));
1191 assert_eq!(M2::max_steps(), Some(2));
1192
1193 assert_eq!(<(M0, M1)>::nth_max_steps(0), Some(None));
1194 assert_eq!(<(M0, M1)>::nth_max_steps(1), Some(Some(1)));
1195 assert_eq!(<(M0, M1, M2)>::nth_max_steps(2), Some(Some(2)));
1196
1197 assert_eq!(<(M0, M1)>::nth_max_steps(2), None);
1198 }
1199
1200 #[test]
1201 fn tuple_migrations_work() {
1202 assert_eq!(<() as SteppedMigrations>::len(), 0);
1203 assert_eq!(<((), ((), ())) as SteppedMigrations>::len(), 0);
1204 assert_eq!(<Triple as SteppedMigrations>::len(), 3);
1205 assert_eq!(<Hextuple as SteppedMigrations>::len(), 6);
1206
1207 assert_eq!(<Triple as SteppedMigrations>::nth_id(0), Some(0u8.encode()));
1210 assert_eq!(<Triple as SteppedMigrations>::nth_id(1), Some(1u8.encode()));
1211 assert_eq!(<Triple as SteppedMigrations>::nth_id(2), Some(2u8.encode()));
1212
1213 subsoil::io::TestExternalities::default().execute_with(|| {
1214 for n in 0..3 {
1215 <Triple as SteppedMigrations>::nth_step(
1216 n,
1217 Default::default(),
1218 &mut WeightMeter::new(),
1219 );
1220 }
1221 });
1222 }
1223
1224 #[test]
1225 fn integrity_test_works() {
1226 subsoil::io::TestExternalities::default().execute_with(|| {
1227 assert_ok!(<() as SteppedMigrations>::integrity_test());
1228 assert_ok!(<M0 as SteppedMigrations>::integrity_test());
1229 assert_ok!(<M1 as SteppedMigrations>::integrity_test());
1230 assert_ok!(<M2 as SteppedMigrations>::integrity_test());
1231 assert_ok!(<Triple as SteppedMigrations>::integrity_test());
1232 assert_ok!(<Hextuple as SteppedMigrations>::integrity_test());
1233 });
1234 }
1235
1236 #[test]
1237 fn transactional_rollback_works() {
1238 subsoil::io::TestExternalities::default().execute_with(|| {
1239 assert_ok!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1240 0,
1241 Default::default(),
1242 &mut WeightMeter::new()
1243 )
1244 .unwrap());
1245 assert!(unhashed::exists(&[0]));
1246
1247 let _g = crate::StorageNoopGuard::new();
1248 assert!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
1249 1,
1250 Default::default(),
1251 &mut WeightMeter::new()
1252 )
1253 .unwrap()
1254 .is_err());
1255 assert!(<(F0, M1) as SteppedMigrations>::nth_transactional_step(
1256 0,
1257 Default::default(),
1258 &mut WeightMeter::new()
1259 )
1260 .unwrap()
1261 .is_err());
1262 });
1263 }
1264
1265 #[test]
1266 fn nth_migrating_prefixes_works() {
1267 assert_eq!(
1269 M0::nth_migrating_prefixes(0),
1270 Some(Some(vec![b"M0_prefix1".to_vec(), b"M0_prefix2".to_vec()]))
1271 );
1272
1273 assert_eq!(M3::nth_migrating_prefixes(0), Some(None));
1275
1276 type Pair = (M0, M1);
1278 assert_eq!(Pair::len(), 2);
1279
1280 assert_eq!(
1282 Pair::nth_migrating_prefixes(0),
1283 Some(Some(vec![b"M0_prefix1".to_vec(), b"M0_prefix2".to_vec()]))
1284 );
1285
1286 assert_eq!(Pair::nth_migrating_prefixes(1), Some(Some(vec![b"M1_prefix".to_vec()])));
1288
1289 assert_eq!(Pair::nth_migrating_prefixes(2), None);
1291
1292 type Nested = (M0, (M1, M2));
1294 assert_eq!(Nested::len(), 3);
1295
1296 assert_eq!(
1298 Nested::nth_migrating_prefixes(0),
1299 Some(Some(vec![b"M0_prefix1".to_vec(), b"M0_prefix2".to_vec()]))
1300 );
1301
1302 assert_eq!(Nested::nth_migrating_prefixes(1), Some(Some(vec![b"M1_prefix".to_vec()])));
1304
1305 assert_eq!(
1307 Nested::nth_migrating_prefixes(2),
1308 Some(Some(vec![
1309 b"M2_prefix1".to_vec(),
1310 b"M2_prefix2".to_vec(),
1311 b"M2_prefix3".to_vec()
1312 ]))
1313 );
1314
1315 assert_eq!(Nested::nth_migrating_prefixes(3), None);
1316 }
1317}