1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! XCM Transactor pallet migrations
18

            
19
use crate::{
20
	chain_indices::{AssetHubIndices, ChainIndices},
21
	pallet::Config,
22
	ChainIndicesMap, RelayIndices,
23
};
24
use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade, weights::Weight};
25
use sp_std::marker::PhantomData;
26

            
27
/// Migration from RelayIndices (single StorageValue) to ChainIndicesMap (StorageMap)
28
///
29
/// This migration:
30
/// 1. Migrates existing RelayIndices to ChainIndicesMap under the Relay transactor key
31
/// 2. Initializes AssetHub indices with network-specific values
32
/// 3. Keeps the old RelayIndices storage for backwards compatibility (deprecated)
33
///
34
/// Note: For fresh chains, ChainIndicesMap should be initialized via genesis config,
35
/// not via this migration.
36
///
37
/// # Type Parameters
38
/// - `RelayIndicesValue`: A `Get<RelayChainIndices>` that provides the network-specific
39
///   Relay chain indices (e.g., Polkadot, Kusama, or Westend relay)
40
/// - `AssetHubIndicesValue`: A `Get<AssetHubIndices>` that provides the network-specific
41
///   AssetHub indices (e.g., Polkadot, Kusama, or Westend AssetHub)
42
pub mod v1 {
43
	use super::*;
44
	use crate::chain_indices::RelayChainIndices;
45
	#[cfg(feature = "try-runtime")]
46
	use sp_std::vec::Vec;
47

            
48
	pub struct MigrateToChainIndicesMap<
49
		T,
50
		RelayTransactor,
51
		AssetHubTransactor,
52
		RelayIndicesValue,
53
		AssetHubIndicesValue,
54
	>(
55
		PhantomData<(
56
			T,
57
			RelayTransactor,
58
			AssetHubTransactor,
59
			RelayIndicesValue,
60
			AssetHubIndicesValue,
61
		)>,
62
	);
63

            
64
	impl<T, RelayTransactor, AssetHubTransactor, RelayIndicesValue, AssetHubIndicesValue>
65
		OnRuntimeUpgrade
66
		for MigrateToChainIndicesMap<
67
			T,
68
			RelayTransactor,
69
			AssetHubTransactor,
70
			RelayIndicesValue,
71
			AssetHubIndicesValue,
72
		>
73
	where
74
		T: Config,
75
		RelayTransactor: Get<T::Transactor>,
76
		AssetHubTransactor: Get<T::Transactor>,
77
		RelayIndicesValue: Get<RelayChainIndices>,
78
		AssetHubIndicesValue: Get<AssetHubIndices>,
79
	{
80
		fn on_runtime_upgrade() -> Weight {
81
			let mut weight = T::DbWeight::get().reads(1);
82

            
83
			// Check if migration is needed by seeing if ChainIndicesMap is empty
84
			let relay_key = RelayTransactor::get();
85
			if ChainIndicesMap::<T>::contains_key(&relay_key) {
86
				// Migration already ran or genesis initialized ChainIndicesMap
87
				// Ensure RelayIndices is also populated for backwards compatibility
88
				if let Some(ChainIndices::Relay(relay_indices)) =
89
					ChainIndicesMap::<T>::get(&relay_key)
90
				{
91
					RelayIndices::<T>::put(relay_indices);
92
					weight = weight.saturating_add(T::DbWeight::get().writes(1));
93
				}
94
				return weight;
95
			}
96

            
97
			// Step 1: Migrate existing RelayIndices to ChainIndicesMap
98
			let old_relay_indices = RelayIndices::<T>::get();
99

            
100
			// If RelayIndices is default, this is likely a fresh chain that should have
101
			// been initialized via genesis config. Use the network-specific hardcoded values
102
			// as a fallback to ensure the chain can function.
103
			let relay_indices_to_use = if old_relay_indices == Default::default() {
104
				RelayIndicesValue::get()
105
			} else {
106
				old_relay_indices
107
			};
108

            
109
			// Populate both new and old storage
110
			ChainIndicesMap::<T>::insert(&relay_key, ChainIndices::Relay(relay_indices_to_use));
111
			RelayIndices::<T>::put(relay_indices_to_use);
112
			weight = weight.saturating_add(T::DbWeight::get().writes(2));
113

            
114
			// Step 2: Initialize AssetHub indices
115
			let assethub_key = AssetHubTransactor::get();
116
			let assethub_indices = AssetHubIndicesValue::get();
117
			ChainIndicesMap::<T>::insert(&assethub_key, ChainIndices::AssetHub(assethub_indices));
118
			weight = weight.saturating_add(T::DbWeight::get().writes(1));
119

            
120
			// Note: We keep RelayIndices storage for backwards compatibility
121
			// It will be removed in a future version
122

            
123
			weight
124
		}
125

            
126
		#[cfg(feature = "try-runtime")]
127
		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
128
			use parity_scale_codec::Encode;
129

            
130
			// Store the current RelayIndices for verification
131
			let old_indices = RelayIndices::<T>::get();
132

            
133
			// Encode state for post-upgrade verification
134
			Ok(old_indices.encode())
135
		}
136

            
137
		#[cfg(feature = "try-runtime")]
138
		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
139
			use crate::chain_indices::RelayChainIndices;
140
			use parity_scale_codec::Decode;
141

            
142
			// Decode pre-upgrade state
143
			let old_relay_indices: RelayChainIndices = Decode::decode(&mut &state[..])
144
				.map_err(|_| "Failed to decode pre-upgrade state")?;
145

            
146
			let relay_key = RelayTransactor::get();
147
			let assethub_key = AssetHubTransactor::get();
148

            
149
			// Verify Relay indices were migrated correctly (always present now)
150
			let migrated_relay = ChainIndicesMap::<T>::get(relay_key)
151
				.ok_or("Relay indices not found in ChainIndicesMap")?;
152

            
153
			match migrated_relay {
154
				ChainIndices::Relay(indices) => {
155
					if indices != old_relay_indices {
156
						return Err("Migrated Relay indices don't match original".into());
157
					}
158
				}
159
				_ => {
160
					return Err("Expected ChainIndices::Relay variant".into());
161
				}
162
			}
163

            
164
			// Verify old storage still exists (backwards compat)
165
			let current_relay_indices = RelayIndices::<T>::get();
166
			if current_relay_indices != old_relay_indices {
167
				return Err("RelayIndices storage should remain unchanged".into());
168
			}
169

            
170
			// Verify AssetHub indices were initialized
171
			let assethub_indices = ChainIndicesMap::<T>::get(assethub_key)
172
				.ok_or("AssetHub indices not found in ChainIndicesMap")?;
173

            
174
			match assethub_indices {
175
				ChainIndices::AssetHub(indices) => {
176
					// Verify key indices are set (not all zeros)
177
					if indices.utility == 0 && indices.proxy == 0 && indices.staking == 0 {
178
						return Err("AssetHub indices are all zero".into());
179
					}
180
				}
181
				_ => {
182
					return Err("Expected ChainIndices::AssetHub variant".into());
183
				}
184
			}
185

            
186
			Ok(())
187
		}
188
	}
189
}