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
//! The XCM primitive trait implementations
18

            
19
#![cfg_attr(not(feature = "std"), no_std)]
20

            
21
mod asset_id_conversions;
22
pub use asset_id_conversions::*;
23

            
24
mod constants;
25
pub use constants::*;
26

            
27
mod ethereum_xcm;
28
pub use ethereum_xcm::*;
29

            
30
mod filter_asset_max_fee;
31
pub use filter_asset_max_fee::*;
32

            
33
mod origin_conversion;
34
pub use origin_conversion::*;
35

            
36
mod transactor_traits;
37
pub use transactor_traits::*;
38

            
39
mod fee_trader;
40
pub use fee_trader::*;
41

            
42
use sp_std::sync::Arc;
43
use sp_std::vec::Vec;
44
use xcm::latest::{Junction, Junctions, Location};
45

            
46
/// Build Junctions from a slice of junctions
47
2613
fn junctions_from_slice(junctions: &[Junction]) -> Option<Junctions> {
48
2613
	match junctions.len() {
49
1
		0 => Some(Junctions::Here),
50
2609
		1 => Some(Junctions::X1(Arc::new([junctions[0].clone()]))),
51
1
		2 => Some(Junctions::X2(Arc::new([
52
1
			junctions[0].clone(),
53
1
			junctions[1].clone(),
54
1
		]))),
55
2
		3 => Some(Junctions::X3(Arc::new([
56
2
			junctions[0].clone(),
57
2
			junctions[1].clone(),
58
2
			junctions[2].clone(),
59
2
		]))),
60
		4 => Some(Junctions::X4(Arc::new([
61
			junctions[0].clone(),
62
			junctions[1].clone(),
63
			junctions[2].clone(),
64
			junctions[3].clone(),
65
		]))),
66
		5 => Some(Junctions::X5(Arc::new([
67
			junctions[0].clone(),
68
			junctions[1].clone(),
69
			junctions[2].clone(),
70
			junctions[3].clone(),
71
			junctions[4].clone(),
72
		]))),
73
		6 => Some(Junctions::X6(Arc::new([
74
			junctions[0].clone(),
75
			junctions[1].clone(),
76
			junctions[2].clone(),
77
			junctions[3].clone(),
78
			junctions[4].clone(),
79
			junctions[5].clone(),
80
		]))),
81
		7 => Some(Junctions::X7(Arc::new([
82
			junctions[0].clone(),
83
			junctions[1].clone(),
84
			junctions[2].clone(),
85
			junctions[3].clone(),
86
			junctions[4].clone(),
87
			junctions[5].clone(),
88
			junctions[6].clone(),
89
		]))),
90
		8 => Some(Junctions::X8(Arc::new([
91
			junctions[0].clone(),
92
			junctions[1].clone(),
93
			junctions[2].clone(),
94
			junctions[3].clone(),
95
			junctions[4].clone(),
96
			junctions[5].clone(),
97
			junctions[6].clone(),
98
			junctions[7].clone(),
99
		]))),
100
		_ => None,
101
	}
102
2613
}
103

            
104
2613
pub fn split_location_into_chain_part_and_beneficiary(
105
2613
	mut location: Location,
106
2613
) -> Option<(Location, Location)> {
107
2613
	let mut beneficiary_junctions_vec = Vec::new();
108

            
109
	// start popping junctions until we reach chain identifier
110
5230
	while let Some(j) = location.last() {
111
4080
		if matches!(j, Junction::Parachain(_) | Junction::GlobalConsensus(_)) {
112
			// return chain subsection
113
			// Reverse the vec to restore original order, then build Junctions
114
1463
			beneficiary_junctions_vec.reverse();
115
1463
			let beneficiary_junctions = junctions_from_slice(&beneficiary_junctions_vec)?;
116
1463
			return Some((location, beneficiary_junctions.into_location()));
117
		} else {
118
2617
			let (location_prefix, maybe_last_junction) = location.split_last_interior();
119
2617
			location = location_prefix;
120
2617
			if let Some(junction) = maybe_last_junction {
121
2617
				beneficiary_junctions_vec.push(junction);
122
2617
			}
123
		}
124
	}
125

            
126
	// Reverse the vec to restore original order, then build Junctions
127
1150
	beneficiary_junctions_vec.reverse();
128
1150
	let beneficiary_junctions = junctions_from_slice(&beneficiary_junctions_vec)?;
129

            
130
1150
	if location.parent_count() == 1 {
131
1148
		Some((Location::parent(), beneficiary_junctions.into_location()))
132
	} else {
133
2
		None
134
	}
135
2613
}
136

            
137
#[cfg(test)]
138
mod tests {
139
	use super::*;
140
	use xcm::latest::prelude::*;
141

            
142
	#[test]
143
1
	fn test_split_location_single_beneficiary_junction() {
144
		// Test: Parachain(2) + AccountKey20
145
1
		let location = Location {
146
1
			parents: 1,
147
1
			interior: [
148
1
				Parachain(2),
149
1
				AccountKey20 {
150
1
					network: None,
151
1
					key: [1u8; 20],
152
1
				},
153
1
			]
154
1
			.into(),
155
1
		};
156

            
157
1
		let (chain_part, beneficiary) =
158
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
159

            
160
		// Chain part should be Parachain(2)
161
1
		assert_eq!(
162
			chain_part,
163
1
			Location {
164
1
				parents: 1,
165
1
				interior: [Parachain(2)].into()
166
1
			}
167
		);
168

            
169
		// Beneficiary should be AccountKey20
170
1
		assert_eq!(
171
			beneficiary,
172
1
			Location {
173
1
				parents: 0,
174
1
				interior: [AccountKey20 {
175
1
					network: None,
176
1
					key: [1u8; 20]
177
1
				}]
178
1
				.into()
179
1
			}
180
		);
181
1
	}
182

            
183
	#[test]
184
1
	fn test_split_location_multiple_beneficiary_junctions_order_preserved() {
185
		// Test: Parachain(100) + AccountId32 + GeneralIndex(42)
186
		// This test verifies that the order is preserved (AccountId32 comes before GeneralIndex)
187
1
		let account_id = AccountId32 {
188
1
			network: None,
189
1
			id: [2u8; 32],
190
1
		};
191
1
		let general_index = GeneralIndex(42);
192

            
193
1
		let location = Location {
194
1
			parents: 1,
195
1
			interior: [Parachain(100), account_id, general_index].into(),
196
1
		};
197

            
198
1
		let (chain_part, beneficiary) =
199
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
200

            
201
		// Chain part should be Parachain(100)
202
1
		assert_eq!(
203
			chain_part,
204
1
			Location {
205
1
				parents: 1,
206
1
				interior: [Parachain(100)].into()
207
1
			}
208
		);
209

            
210
		// Beneficiary should preserve order: AccountId32, then GeneralIndex
211
1
		assert_eq!(
212
			beneficiary,
213
1
			Location {
214
1
				parents: 0,
215
1
				interior: [account_id, general_index].into()
216
1
			}
217
		);
218
1
	}
219

            
220
	#[test]
221
1
	fn test_split_location_three_beneficiary_junctions_order_preserved() {
222
		// Test: Parachain(200) + PalletInstance(5) + AccountId32 + GeneralIndex(10)
223
1
		let pallet = PalletInstance(5);
224
1
		let account_id = AccountId32 {
225
1
			network: None,
226
1
			id: [3u8; 32],
227
1
		};
228
1
		let general_index = GeneralIndex(10);
229

            
230
1
		let location = Location {
231
1
			parents: 1,
232
1
			interior: [Parachain(200), pallet, account_id, general_index].into(),
233
1
		};
234

            
235
1
		let (chain_part, beneficiary) =
236
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
237

            
238
		// Chain part should be Parachain(200)
239
1
		assert_eq!(
240
			chain_part,
241
1
			Location {
242
1
				parents: 1,
243
1
				interior: [Parachain(200)].into()
244
1
			}
245
		);
246

            
247
		// Beneficiary should preserve order: PalletInstance, AccountId32, GeneralIndex
248
1
		assert_eq!(
249
			beneficiary,
250
1
			Location {
251
1
				parents: 0,
252
1
				interior: [pallet, account_id, general_index].into()
253
1
			}
254
		);
255
1
	}
256

            
257
	#[test]
258
1
	fn test_split_location_with_global_consensus() {
259
		// Test: GlobalConsensus(Polkadot) + Parachain(1) + AccountId32
260
1
		let account_id = AccountId32 {
261
1
			network: None,
262
1
			id: [4u8; 32],
263
1
		};
264

            
265
1
		let location = Location {
266
1
			parents: 1,
267
1
			interior: [
268
1
				GlobalConsensus(NetworkId::Polkadot),
269
1
				Parachain(1),
270
1
				account_id,
271
1
			]
272
1
			.into(),
273
1
		};
274

            
275
1
		let (chain_part, beneficiary) =
276
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
277

            
278
		// Chain part should stop at Parachain(1) (last chain identifier when processing from end)
279
		// Since Parachain(1) is encountered first when processing from the end, chain_part includes
280
		// both GlobalConsensus and Parachain(1)
281
1
		assert_eq!(
282
			chain_part,
283
1
			Location {
284
1
				parents: 1,
285
1
				interior: [GlobalConsensus(NetworkId::Polkadot), Parachain(1)].into()
286
1
			}
287
		);
288

            
289
		// Beneficiary should be AccountId32
290
1
		assert_eq!(
291
			beneficiary,
292
1
			Location {
293
1
				parents: 0,
294
1
				interior: [account_id].into()
295
1
			}
296
		);
297
1
	}
298

            
299
	#[test]
300
1
	fn test_split_location_parent_only() {
301
		// Test: Parent + AccountId32
302
1
		let account_id = AccountId32 {
303
1
			network: None,
304
1
			id: [5u8; 32],
305
1
		};
306

            
307
1
		let location = Location {
308
1
			parents: 1,
309
1
			interior: [account_id].into(),
310
1
		};
311

            
312
1
		let (chain_part, beneficiary) =
313
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
314

            
315
		// Chain part should be parent
316
1
		assert_eq!(chain_part, Location::parent());
317

            
318
		// Beneficiary should be AccountId32
319
1
		assert_eq!(
320
			beneficiary,
321
1
			Location {
322
1
				parents: 0,
323
1
				interior: [account_id].into()
324
1
			}
325
		);
326
1
	}
327

            
328
	#[test]
329
1
	fn test_split_location_multiple_junctions_order_verification() {
330
		// Test with multiple junctions to verify order is NOT reversed
331
		// Original: [Parachain(300), JunctionA, JunctionB, JunctionC]
332
		// Expected beneficiary: [JunctionA, JunctionB, JunctionC] (same order)
333
1
		let junction_a = AccountKey20 {
334
1
			network: None,
335
1
			key: [10u8; 20],
336
1
		};
337
1
		let junction_b = AccountId32 {
338
1
			network: None,
339
1
			id: [20u8; 32],
340
1
		};
341
1
		let junction_c = GeneralIndex(30);
342

            
343
1
		let location = Location {
344
1
			parents: 1,
345
1
			interior: [Parachain(300), junction_a, junction_b, junction_c].into(),
346
1
		};
347

            
348
1
		let (chain_part, beneficiary) =
349
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
350

            
351
		// Verify chain part
352
1
		assert_eq!(
353
			chain_part,
354
1
			Location {
355
1
				parents: 1,
356
1
				interior: [Parachain(300)].into()
357
1
			}
358
		);
359

            
360
		// Verify beneficiary order is preserved (A, B, C - not reversed)
361
1
		let beneficiary_interior = beneficiary.interior;
362
1
		match beneficiary_interior {
363
1
			Junctions::X3(junctions) => {
364
1
				assert_eq!(
365
1
					junctions[0],
366
					Junction::AccountKey20 {
367
						network: None,
368
						key: [10u8; 20]
369
					}
370
				);
371
1
				assert_eq!(
372
1
					junctions[1],
373
					Junction::AccountId32 {
374
						network: None,
375
						id: [20u8; 32]
376
					}
377
				);
378
1
				assert_eq!(junctions[2], Junction::GeneralIndex(30));
379
			}
380
			_ => panic!("Expected X3 junctions"),
381
		}
382
1
	}
383

            
384
	#[test]
385
1
	fn test_split_location_no_beneficiary() {
386
		// Test: Only Parachain (no beneficiary junctions)
387
1
		let location = Location {
388
1
			parents: 1,
389
1
			interior: [Parachain(400)].into(),
390
1
		};
391

            
392
1
		let (chain_part, beneficiary) =
393
1
			split_location_into_chain_part_and_beneficiary(location).unwrap();
394

            
395
		// Chain part should be Parachain(400)
396
1
		assert_eq!(
397
			chain_part,
398
1
			Location {
399
1
				parents: 1,
400
1
				interior: [Parachain(400)].into()
401
1
			}
402
		);
403

            
404
		// Beneficiary should be Here (empty)
405
1
		assert_eq!(
406
			beneficiary,
407
			Location {
408
				parents: 0,
409
				interior: Junctions::Here
410
			}
411
		);
412
1
	}
413

            
414
	#[test]
415
1
	fn test_split_location_invalid_no_chain_identifier() {
416
		// Test: Only beneficiary junctions, no chain identifier (should return None)
417
1
		let location = Location {
418
1
			parents: 0,
419
1
			interior: [AccountId32 {
420
1
				network: None,
421
1
				id: [6u8; 32],
422
1
			}]
423
1
			.into(),
424
1
		};
425

            
426
1
		let result = split_location_into_chain_part_and_beneficiary(location);
427
1
		assert!(result.is_none());
428
1
	}
429

            
430
	#[test]
431
1
	fn test_split_location_invalid_wrong_parent_count() {
432
		// Test: Wrong parent count (not 1)
433
1
		let location = Location {
434
1
			parents: 2,
435
1
			interior: [AccountId32 {
436
1
				network: None,
437
1
				id: [7u8; 32],
438
1
			}]
439
1
			.into(),
440
1
		};
441

            
442
1
		let result = split_location_into_chain_part_and_beneficiary(location);
443
1
		assert!(result.is_none());
444
1
	}
445
}