diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs
index 0f5b8e0123ab6ee51b7ff1e3dee46bcdcbb5cf55..3672056534b75d6010893463a9216f35a022ba1d 100644
--- a/substrate/frame/staking/src/lib.rs
+++ b/substrate/frame/staking/src/lib.rs
@@ -881,31 +881,6 @@ impl Default for Forcing {
 	}
 }
 
-// A value placed in storage that represents the current version of the Staking storage. This value
-// is used by the `on_runtime_upgrade` logic to determine whether we run storage migration logic.
-// This should match directly with the semantic versions of the Rust crate.
-#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
-enum Releases {
-	V1_0_0Ancient,
-	V2_0_0,
-	V3_0_0,
-	V4_0_0,
-	V5_0_0,  // blockable validators.
-	V6_0_0,  // removal of all storage associated with offchain phragmen.
-	V7_0_0,  // keep track of number of nominators / validators in map
-	V8_0_0,  // populate `VoterList`.
-	V9_0_0,  // inject validators into `VoterList` as well.
-	V10_0_0, // remove `EarliestUnappliedSlash`.
-	V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList
-	V12_0_0, // remove `HistoryDepth`.
-}
-
-impl Default for Releases {
-	fn default() -> Self {
-		Releases::V11_0_0
-	}
-}
-
 /// A `Convert` implementation that finds the stash of the given controller account,
 /// if any.
 pub struct StashOf<T>(sp_std::marker::PhantomData<T>);
diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs
index f2ccb4f8b096f14096b8c5461a415ec2589d2ab7..6253c3feed17d6460f4ba67d8b0edfa89cd27604 100644
--- a/substrate/frame/staking/src/migrations.rs
+++ b/substrate/frame/staking/src/migrations.rs
@@ -18,7 +18,87 @@
 
 use super::*;
 use frame_election_provider_support::SortedListProvider;
-use frame_support::traits::OnRuntimeUpgrade;
+use frame_support::{
+	dispatch::GetStorageVersion, pallet_prelude::ValueQuery, storage_alias,
+	traits::OnRuntimeUpgrade,
+};
+
+/// Used for release versioning upto v12.
+///
+/// Obsolete from v13. Keeping around to make encoding/decoding of old migration code easier.
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+enum ObsoleteReleases {
+	V1_0_0Ancient,
+	V2_0_0,
+	V3_0_0,
+	V4_0_0,
+	V5_0_0,  // blockable validators.
+	V6_0_0,  // removal of all storage associated with offchain phragmen.
+	V7_0_0,  // keep track of number of nominators / validators in map
+	V8_0_0,  // populate `VoterList`.
+	V9_0_0,  // inject validators into `VoterList` as well.
+	V10_0_0, // remove `EarliestUnappliedSlash`.
+	V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList
+	V12_0_0, // remove `HistoryDepth`.
+}
+
+impl Default for ObsoleteReleases {
+	fn default() -> Self {
+		ObsoleteReleases::V12_0_0
+	}
+}
+
+/// Alias to the old storage item used for release versioning. Obsolete since v13.
+#[storage_alias]
+type StorageVersion<T: Config> = StorageValue<Pallet<T>, ObsoleteReleases, ValueQuery>;
+
+pub mod v13 {
+	use super::*;
+
+	pub struct MigrateToV13<T>(sp_std::marker::PhantomData<T>);
+	impl<T: Config> OnRuntimeUpgrade for MigrateToV13<T> {
+		#[cfg(feature = "try-runtime")]
+		fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
+			frame_support::ensure!(
+				StorageVersion::<T>::get() == ObsoleteReleases::V12_0_0,
+				"Required v12 before upgrading to v13"
+			);
+
+			Ok(Default::default())
+		}
+
+		fn on_runtime_upgrade() -> Weight {
+			let current = Pallet::<T>::current_storage_version();
+			let onchain = StorageVersion::<T>::get();
+
+			if current == 13 && onchain == ObsoleteReleases::V12_0_0 {
+				StorageVersion::<T>::kill();
+				current.put::<Pallet<T>>();
+
+				log!(info, "v13 applied successfully");
+				T::DbWeight::get().reads_writes(1, 2)
+			} else {
+				log!(warn, "Skipping v13, should be removed");
+				T::DbWeight::get().reads(1)
+			}
+		}
+
+		#[cfg(feature = "try-runtime")]
+		fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
+			frame_support::ensure!(
+				Pallet::<T>::on_chain_storage_version() == 13,
+				"v13 not applied"
+			);
+
+			frame_support::ensure!(
+				!StorageVersion::<T>::exists(),
+				"Storage version not migrated correctly"
+			);
+
+			Ok(())
+		}
+	}
+}
 
 pub mod v12 {
 	use super::*;
@@ -36,7 +116,7 @@ pub mod v12 {
 		#[cfg(feature = "try-runtime")]
 		fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == Releases::V11_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V11_0_0,
 				"Expected v11 before upgrading to v12"
 			);
 
@@ -53,9 +133,9 @@ pub mod v12 {
 		}
 
 		fn on_runtime_upgrade() -> frame_support::weights::Weight {
-			if StorageVersion::<T>::get() == Releases::V11_0_0 {
+			if StorageVersion::<T>::get() == ObsoleteReleases::V11_0_0 {
 				HistoryDepth::<T>::kill();
-				StorageVersion::<T>::put(Releases::V12_0_0);
+				StorageVersion::<T>::put(ObsoleteReleases::V12_0_0);
 
 				log!(info, "v12 applied successfully");
 				T::DbWeight::get().reads_writes(1, 2)
@@ -68,7 +148,7 @@ pub mod v12 {
 		#[cfg(feature = "try-runtime")]
 		fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == crate::Releases::V12_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V12_0_0,
 				"v12 not applied"
 			);
 			Ok(())
@@ -92,7 +172,7 @@ pub mod v11 {
 		#[cfg(feature = "try-runtime")]
 		fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == crate::Releases::V10_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V10_0_0,
 				"must upgrade linearly"
 			);
 			let old_pallet_prefix = twox_128(N::get().as_bytes());
@@ -117,9 +197,9 @@ pub mod v11 {
 			let old_pallet_name = N::get();
 			let new_pallet_name = <P as PalletInfoAccess>::name();
 
-			if StorageVersion::<T>::get() == Releases::V10_0_0 {
+			if StorageVersion::<T>::get() == ObsoleteReleases::V10_0_0 {
 				// bump version anyway, even if we don't need to move the prefix
-				StorageVersion::<T>::put(Releases::V11_0_0);
+				StorageVersion::<T>::put(ObsoleteReleases::V11_0_0);
 				if new_pallet_name == old_pallet_name {
 					log!(
 						warn,
@@ -139,7 +219,7 @@ pub mod v11 {
 		#[cfg(feature = "try-runtime")]
 		fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == crate::Releases::V11_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V11_0_0,
 				"wrong version after the upgrade"
 			);
 
@@ -184,7 +264,7 @@ pub mod v10 {
 	pub struct MigrateToV10<T>(sp_std::marker::PhantomData<T>);
 	impl<T: Config> OnRuntimeUpgrade for MigrateToV10<T> {
 		fn on_runtime_upgrade() -> frame_support::weights::Weight {
-			if StorageVersion::<T>::get() == Releases::V9_0_0 {
+			if StorageVersion::<T>::get() == ObsoleteReleases::V9_0_0 {
 				let pending_slashes = <Pallet<T> as Store>::UnappliedSlashes::iter().take(512);
 				for (era, slashes) in pending_slashes {
 					for slash in slashes {
@@ -196,7 +276,7 @@ pub mod v10 {
 				}
 
 				EarliestUnappliedSlash::<T>::kill();
-				StorageVersion::<T>::put(Releases::V10_0_0);
+				StorageVersion::<T>::put(ObsoleteReleases::V10_0_0);
 
 				log!(info, "MigrateToV10 executed successfully");
 				T::DbWeight::get().reads_writes(1, 1)
@@ -221,7 +301,7 @@ pub mod v9 {
 	pub struct InjectValidatorsIntoVoterList<T>(sp_std::marker::PhantomData<T>);
 	impl<T: Config> OnRuntimeUpgrade for InjectValidatorsIntoVoterList<T> {
 		fn on_runtime_upgrade() -> Weight {
-			if StorageVersion::<T>::get() == Releases::V8_0_0 {
+			if StorageVersion::<T>::get() == ObsoleteReleases::V8_0_0 {
 				let prev_count = T::VoterList::count();
 				let weight_of_cached = Pallet::<T>::weight_of_fn();
 				for (v, _) in Validators::<T>::iter() {
@@ -239,13 +319,13 @@ pub mod v9 {
 					T::VoterList::count(),
 				);
 
-				StorageVersion::<T>::put(crate::Releases::V9_0_0);
+				StorageVersion::<T>::put(ObsoleteReleases::V9_0_0);
 				T::BlockWeights::get().max_block
 			} else {
 				log!(
 					warn,
 					"InjectValidatorsIntoVoterList being executed on the wrong storage \
-				version, expected Releases::V8_0_0"
+				version, expected ObsoleteReleases::V8_0_0"
 				);
 				T::DbWeight::get().reads(1)
 			}
@@ -254,7 +334,7 @@ pub mod v9 {
 		#[cfg(feature = "try-runtime")]
 		fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == crate::Releases::V8_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V8_0_0,
 				"must upgrade linearly"
 			);
 
@@ -272,7 +352,7 @@ pub mod v9 {
 			assert!(post_count == prev_count + validators);
 
 			frame_support::ensure!(
-				StorageVersion::<T>::get() == crate::Releases::V9_0_0,
+				StorageVersion::<T>::get() == ObsoleteReleases::V9_0_0,
 				"must upgrade "
 			);
 			Ok(())
@@ -281,14 +361,15 @@ pub mod v9 {
 }
 
 pub mod v8 {
-	use crate::{Config, Nominators, Pallet, StorageVersion, Weight};
+	use super::*;
+	use crate::{Config, Nominators, Pallet, Weight};
 	use frame_election_provider_support::SortedListProvider;
 	use frame_support::traits::Get;
 
 	#[cfg(feature = "try-runtime")]
 	pub fn pre_migrate<T: Config>() -> Result<(), &'static str> {
 		frame_support::ensure!(
-			StorageVersion::<T>::get() == crate::Releases::V7_0_0,
+			StorageVersion::<T>::get() == ObsoleteReleases::V7_0_0,
 			"must upgrade linearly"
 		);
 
@@ -298,8 +379,8 @@ pub mod v8 {
 
 	/// Migration to sorted `VoterList`.
 	pub fn migrate<T: Config>() -> Weight {
-		if StorageVersion::<T>::get() == crate::Releases::V7_0_0 {
-			crate::log!(info, "migrating staking to Releases::V8_0_0");
+		if StorageVersion::<T>::get() == ObsoleteReleases::V7_0_0 {
+			crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0");
 
 			let migrated = T::VoterList::unsafe_regenerate(
 				Nominators::<T>::iter().map(|(id, _)| id),
@@ -307,10 +388,10 @@ pub mod v8 {
 			);
 			debug_assert_eq!(T::VoterList::try_state(), Ok(()));
 
-			StorageVersion::<T>::put(crate::Releases::V8_0_0);
+			StorageVersion::<T>::put(ObsoleteReleases::V8_0_0);
 			crate::log!(
 				info,
-				"👜 completed staking migration to Releases::V8_0_0 with {} voters migrated",
+				"👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated",
 				migrated,
 			);
 
@@ -348,20 +429,20 @@ pub mod v7 {
 		);
 		assert!(Validators::<T>::count().is_zero(), "Validators already set.");
 		assert!(Nominators::<T>::count().is_zero(), "Nominators already set.");
-		assert!(StorageVersion::<T>::get() == Releases::V6_0_0);
+		assert!(StorageVersion::<T>::get() == ObsoleteReleases::V6_0_0);
 		Ok(())
 	}
 
 	pub fn migrate<T: Config>() -> Weight {
-		log!(info, "Migrating staking to Releases::V7_0_0");
+		log!(info, "Migrating staking to ObsoleteReleases::V7_0_0");
 		let validator_count = Validators::<T>::iter().count() as u32;
 		let nominator_count = Nominators::<T>::iter().count() as u32;
 
 		CounterForValidators::<T>::put(validator_count);
 		CounterForNominators::<T>::put(nominator_count);
 
-		StorageVersion::<T>::put(Releases::V7_0_0);
-		log!(info, "Completed staking migration to Releases::V7_0_0");
+		StorageVersion::<T>::put(ObsoleteReleases::V7_0_0);
+		log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0");
 
 		T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2)
 	}
@@ -403,7 +484,7 @@ pub mod v6 {
 
 	/// Migrate storage to v6.
 	pub fn migrate<T: Config>() -> Weight {
-		log!(info, "Migrating staking to Releases::V6_0_0");
+		log!(info, "Migrating staking to ObsoleteReleases::V6_0_0");
 
 		SnapshotValidators::<T>::kill();
 		SnapshotNominators::<T>::kill();
@@ -412,7 +493,8 @@ pub mod v6 {
 		EraElectionStatus::<T>::kill();
 		IsCurrentSessionFinal::<T>::kill();
 
-		StorageVersion::<T>::put(Releases::V6_0_0);
+		StorageVersion::<T>::put(ObsoleteReleases::V6_0_0);
+
 		log!(info, "Done.");
 		T::DbWeight::get().writes(6 + 1)
 	}
diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs
index 8e8a8d9c7f60024035a8a6d7fc84f69369e8ed8f..92502949ef1a0591ca920ef0ea241827c5bd2d2d 100644
--- a/substrate/frame/staking/src/pallet/mod.rs
+++ b/substrate/frame/staking/src/pallet/mod.rs
@@ -46,7 +46,7 @@ pub use impls::*;
 use crate::{
 	slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout,
 	EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf,
-	Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
+	RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
 	ValidatorPrefs,
 };
 
@@ -64,8 +64,12 @@ pub mod pallet {
 
 	use super::*;
 
+	/// The current storage version.
+	const STORAGE_VERSION: StorageVersion = StorageVersion::new(13);
+
 	#[pallet::pallet]
 	#[pallet::generate_store(pub(crate) trait Store)]
+	#[pallet::storage_version(STORAGE_VERSION)]
 	pub struct Pallet<T>(_);
 
 	/// Possible operations on the configuration values of this pallet.
@@ -561,13 +565,6 @@ pub mod pallet {
 	#[pallet::getter(fn offending_validators)]
 	pub type OffendingValidators<T: Config> = StorageValue<_, Vec<(u32, bool)>, ValueQuery>;
 
-	/// True if network has been upgraded to this version.
-	/// Storage version of the pallet.
-	///
-	/// This is set to v7.0.0 for new networks.
-	#[pallet::storage]
-	pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
-
 	/// The threshold for when users can start calling `chill_other` for other validators /
 	/// nominators. The threshold is compared to the actual number of validators / nominators
 	/// (`CountFor*`) in the system compared to the configured max (`Max*Count`).
@@ -618,7 +615,6 @@ pub mod pallet {
 			ForceEra::<T>::put(self.force_era);
 			CanceledSlashPayout::<T>::put(self.canceled_payout);
 			SlashRewardFraction::<T>::put(self.slash_reward_fraction);
-			StorageVersion::<T>::put(Releases::V7_0_0);
 			MinNominatorBond::<T>::put(self.min_nominator_bond);
 			MinValidatorBond::<T>::put(self.min_validator_bond);
 			if let Some(x) = self.max_validator_count {