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
use crate::xcm_mock::{parachain::PolkadotXcm, *};
18
use crate::xcm_testing::{add_supported_asset, currency_to_asset, helpers::*};
19
use frame_support::assert_ok;
20
use moonbase_runtime::xcm_config::AssetType;
21
use sp_weights::Weight;
22
use xcm::{
23
	latest::prelude::{
24
		AccountKey20, All, BuyExecution, ClearOrigin, DepositAsset, Limited, Location,
25
		PalletInstance, Parachain, WeightLimit, WithdrawAsset, Xcm,
26
	},
27
	VersionedAssets, VersionedLocation,
28
};
29
use xcm_primitives::{split_location_into_chain_part_and_beneficiary, DEFAULT_PROOF_SIZE};
30
use xcm_simulator::TestExt;
31

            
32
#[test]
33
1
fn send_para_a_asset_to_para_b() {
34
1
	MockNet::reset();
35
1

            
36
1
	// this represents the asset in paraA
37
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
38
1
	let source_location: AssetType = para_a_balances
39
1
		.try_into()
40
1
		.expect("Location convertion to AssetType should succeed");
41
1
	let source_id: parachain::AssetId = source_location.clone().into();
42
1

            
43
1
	let asset_metadata = parachain::AssetMetadata {
44
1
		name: b"ParaAToken".to_vec(),
45
1
		symbol: b"ParaA".to_vec(),
46
1
		decimals: 18,
47
1
	};
48
1

            
49
1
	// Register asset in paraB. Free execution
50
1
	ParaB::execute_with(|| {
51
1
		assert_ok!(AssetManager::register_foreign_asset(
52
1
			parachain::RuntimeOrigin::root(),
53
1
			source_location.clone(),
54
1
			asset_metadata,
55
1
			1u128,
56
1
			true
57
1
		));
58
1
		assert_ok!(add_supported_asset(source_location, 0));
59
1
	});
60
1

            
61
1
	// Send para A asset from para A to para B using helper
62
1
	let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
63
1
	execute_transfer_to_para(
64
1
		PARAALICE,
65
1
		VersionedAssets::from(vec![asset]),
66
1
		2,
67
1
		PARAALICE,
68
1
		Some(WeightLimit::Limited(Weight::from_parts(
69
1
			800000u64,
70
1
			DEFAULT_PROOF_SIZE,
71
1
		))),
72
1
	);
73
1

            
74
1
	// Native token is substracted in paraA
75
1
	assert_native_balance_decreased_by(&PARAALICE, INITIAL_BALANCE, 100);
76
1

            
77
1
	// Asset is minted in paraB
78
1
	ParaB::execute_with(|| {
79
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 100);
80
1
	});
81
1
}
82

            
83
#[test]
84
1
fn send_para_a_asset_from_para_b_to_para_c() {
85
1
	MockNet::reset();
86
1

            
87
1
	// Represents para A asset
88
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
89
1
	let source_location: AssetType = para_a_balances
90
1
		.try_into()
91
1
		.expect("Location convertion to AssetType should succeed");
92
1
	let source_id: parachain::AssetId = source_location.clone().into();
93
1

            
94
1
	let asset_metadata = parachain::AssetMetadata {
95
1
		name: b"ParaAToken".to_vec(),
96
1
		symbol: b"ParaA".to_vec(),
97
1
		decimals: 18,
98
1
	};
99
1

            
100
1
	// Register para A asset in parachain B. Free execution
101
1
	ParaB::execute_with(|| {
102
1
		assert_ok!(AssetManager::register_foreign_asset(
103
1
			parachain::RuntimeOrigin::root(),
104
1
			source_location.clone(),
105
1
			asset_metadata.clone(),
106
1
			1u128,
107
1
			true
108
1
		));
109
1
		assert_ok!(add_supported_asset(source_location.clone(), 0));
110
1
	});
111
1

            
112
1
	// Register para A asset in parachain C. Free execution
113
1
	ParaC::execute_with(|| {
114
1
		assert_ok!(AssetManager::register_foreign_asset(
115
1
			parachain::RuntimeOrigin::root(),
116
1
			source_location.clone(),
117
1
			asset_metadata,
118
1
			1u128,
119
1
			true
120
1
		));
121
1
		assert_ok!(add_supported_asset(source_location, 0));
122
1
	});
123
1

            
124
1
	// Send para A asset to para B using helper
125
1
	let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
126
1
	execute_transfer_to_para(
127
1
		PARAALICE,
128
1
		VersionedAssets::from(vec![asset]),
129
1
		2,
130
1
		PARAALICE,
131
1
		Some(standard_transfer_weight()),
132
1
	);
133
1

            
134
1
	// Para A balances have been substracted
135
1
	assert_native_balance_decreased_by(&PARAALICE, INITIAL_BALANCE, 100);
136
1

            
137
1
	// Para B balances have been credited
138
1
	ParaB::execute_with(|| {
139
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 100);
140
1
	});
141
1

            
142
1
	// Send para A asset from para B to para C
143
1
	let dest = Location {
144
1
		parents: 1,
145
1
		interior: [
146
1
			Parachain(3),
147
1
			AccountKey20 {
148
1
				network: None,
149
1
				key: PARAALICE.into(),
150
1
			},
151
1
		]
152
1
		.into(),
153
1
	};
154
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
155
1

            
156
1
	ParaB::execute_with(|| {
157
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
158
1
		assert_ok!(PolkadotXcm::transfer_assets(
159
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
160
1
			Box::new(VersionedLocation::from(chain_part)),
161
1
			Box::new(VersionedLocation::from(beneficiary)),
162
1
			Box::new(VersionedAssets::from(vec![asset])),
163
1
			0,
164
1
			WeightLimit::Limited(Weight::from_parts(80u64, DEFAULT_PROOF_SIZE))
165
1
		));
166
1
	});
167
1

            
168
1
	// The message passed through parachainA so we needed to pay since its the native token
169
1
	// The message passed through parachainA so we needed to pay since its the native token
170
1
	ParaC::execute_with(|| {
171
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 96);
172
1
	});
173
1
}
174

            
175
#[test]
176
1
fn send_para_a_asset_to_para_b_and_back_to_para_a() {
177
1
	MockNet::reset();
178
1

            
179
1
	// Para A asset
180
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
181
1
	let source_location: AssetType = para_a_balances
182
1
		.try_into()
183
1
		.expect("Location convertion to AssetType should succeed");
184
1
	let source_id: parachain::AssetId = source_location.clone().into();
185
1

            
186
1
	let asset_metadata = parachain::AssetMetadata {
187
1
		name: b"ParaAToken".to_vec(),
188
1
		symbol: b"ParaA".to_vec(),
189
1
		decimals: 18,
190
1
	};
191
1

            
192
1
	// Register para A asset in para B
193
1
	ParaB::execute_with(|| {
194
1
		assert_ok!(AssetManager::register_foreign_asset(
195
1
			parachain::RuntimeOrigin::root(),
196
1
			source_location.clone(),
197
1
			asset_metadata,
198
1
			1u128,
199
1
			true
200
1
		));
201
1
		assert_ok!(add_supported_asset(source_location, 0));
202
1
	});
203
1

            
204
1
	// Send para A asset to para B
205
1
	let dest = Location {
206
1
		parents: 1,
207
1
		interior: [
208
1
			Parachain(2),
209
1
			AccountKey20 {
210
1
				network: None,
211
1
				key: PARAALICE.into(),
212
1
			},
213
1
		]
214
1
		.into(),
215
1
	};
216
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
217
1

            
218
1
	ParaA::execute_with(|| {
219
1
		let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
220
1
		assert_ok!(PolkadotXcm::transfer_assets(
221
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
222
1
			Box::new(VersionedLocation::from(chain_part)),
223
1
			Box::new(VersionedLocation::from(beneficiary)),
224
1
			Box::new(VersionedAssets::from(vec![asset])),
225
1
			0,
226
1
			WeightLimit::Limited(Weight::from_parts(80u64, DEFAULT_PROOF_SIZE))
227
1
		));
228
1
	});
229
1

            
230
1
	// Balances have been subtracted
231
1
	ParaA::execute_with(|| {
232
1
		assert_eq!(
233
1
			ParaBalances::free_balance(&PARAALICE.into()),
234
1
			INITIAL_BALANCE - 100
235
1
		);
236
1
	});
237
1

            
238
1
	// Para B balances have been credited
239
1
	ParaB::execute_with(|| {
240
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 100);
241
1
	});
242
1

            
243
1
	// Send back para A asset to para A
244
1
	let dest = Location {
245
1
		parents: 1,
246
1
		interior: [
247
1
			Parachain(1),
248
1
			AccountKey20 {
249
1
				network: None,
250
1
				key: PARAALICE.into(),
251
1
			},
252
1
		]
253
1
		.into(),
254
1
	};
255
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
256
1

            
257
1
	ParaB::execute_with(|| {
258
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
259
1
		assert_ok!(PolkadotXcm::transfer_assets(
260
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
261
1
			Box::new(VersionedLocation::from(chain_part)),
262
1
			Box::new(VersionedLocation::from(beneficiary)),
263
1
			Box::new(VersionedAssets::from(vec![asset])),
264
1
			0,
265
1
			WeightLimit::Limited(Weight::from_parts(80u64, DEFAULT_PROOF_SIZE))
266
1
		));
267
1
	});
268
1

            
269
1
	ParaA::execute_with(|| {
270
1
		// Weight used is 4
271
1
		assert_eq!(
272
1
			ParaBalances::free_balance(&PARAALICE.into()),
273
1
			INITIAL_BALANCE - 4
274
1
		);
275
1
	});
276
1
}
277

            
278
#[test]
279
1
fn send_para_a_asset_to_para_b_and_back_to_para_a_with_new_reanchoring() {
280
1
	MockNet::reset();
281
1

            
282
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
283
1
	let source_location: AssetType = para_a_balances
284
1
		.try_into()
285
1
		.expect("Location convertion to AssetType should succeed");
286
1
	let source_id: parachain::AssetId = source_location.clone().into();
287
1

            
288
1
	let asset_metadata = parachain::AssetMetadata {
289
1
		name: b"ParaAToken".to_vec(),
290
1
		symbol: b"ParaA".to_vec(),
291
1
		decimals: 18,
292
1
	};
293
1

            
294
1
	ParaB::execute_with(|| {
295
1
		assert_ok!(AssetManager::register_foreign_asset(
296
1
			parachain::RuntimeOrigin::root(),
297
1
			source_location.clone(),
298
1
			asset_metadata,
299
1
			1u128,
300
1
			true
301
1
		));
302
1
		assert_ok!(add_supported_asset(source_location, 0));
303
1
	});
304
1

            
305
1
	let dest = Location {
306
1
		parents: 1,
307
1
		interior: [
308
1
			Parachain(2),
309
1
			AccountKey20 {
310
1
				network: None,
311
1
				key: PARAALICE.into(),
312
1
			},
313
1
		]
314
1
		.into(),
315
1
	};
316
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
317
1

            
318
1
	ParaA::execute_with(|| {
319
1
		let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
320
1
		assert_ok!(PolkadotXcm::transfer_assets(
321
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
322
1
			Box::new(VersionedLocation::from(chain_part)),
323
1
			Box::new(VersionedLocation::from(beneficiary)),
324
1
			Box::new(VersionedAssets::from(vec![asset])),
325
1
			0,
326
1
			WeightLimit::Limited(Weight::from_parts(80u64, DEFAULT_PROOF_SIZE))
327
1
		));
328
1
	});
329
1

            
330
1
	// Para A asset has been credited
331
1
	ParaA::execute_with(|| {
332
1
		assert_eq!(
333
1
			ParaBalances::free_balance(&PARAALICE.into()),
334
1
			INITIAL_BALANCE - 100
335
1
		);
336
1
	});
337
1

            
338
1
	ParaB::execute_with(|| {
339
1
		// free execution, full amount received
340
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 100);
341
1
	});
342
1

            
343
1
	// This time we will force the new reanchoring by manually sending the
344
1
	// Message through polkadotXCM pallet
345
1

            
346
1
	let dest = Location {
347
1
		parents: 1,
348
1
		interior: [Parachain(1)].into(),
349
1
	};
350
1

            
351
1
	let reanchored_para_a_balances = Location::new(0, [PalletInstance(1u8)]);
352
1

            
353
1
	let message = xcm::VersionedXcm::<()>::V5(Xcm(vec![
354
1
		WithdrawAsset((reanchored_para_a_balances.clone(), 100).into()),
355
1
		ClearOrigin,
356
1
		BuyExecution {
357
1
			fees: (reanchored_para_a_balances, 100).into(),
358
1
			weight_limit: Limited(80.into()),
359
1
		},
360
1
		DepositAsset {
361
1
			assets: All.into(),
362
1
			beneficiary: Location::new(
363
1
				0,
364
1
				[AccountKey20 {
365
1
					network: None,
366
1
					key: PARAALICE,
367
1
				}],
368
1
			),
369
1
		},
370
1
	]));
371
1
	ParaB::execute_with(|| {
372
1
		// Send a message to the sovereign account in ParaA to withdraw
373
1
		// and deposit asset
374
1
		assert_ok!(ParachainPalletXcm::send(
375
1
			parachain::RuntimeOrigin::root(),
376
1
			Box::new(dest.into()),
377
1
			Box::new(message),
378
1
		));
379
1
	});
380
1

            
381
1
	ParaA::execute_with(|| {
382
1
		// Weight used is 4
383
1
		assert_eq!(
384
1
			ParaBalances::free_balance(&PARAALICE.into()),
385
1
			INITIAL_BALANCE - 4
386
1
		);
387
1
	});
388
1
}
389

            
390
#[test]
391
1
fn send_para_a_asset_to_para_b_with_trader() {
392
1
	MockNet::reset();
393
1

            
394
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
395
1
	let source_location: AssetType = para_a_balances
396
1
		.try_into()
397
1
		.expect("Location convertion to AssetType should succeed");
398
1
	let source_id: parachain::AssetId = source_location.clone().into();
399
1

            
400
1
	let asset_metadata = parachain::AssetMetadata {
401
1
		name: b"ParaAToken".to_vec(),
402
1
		symbol: b"ParaA".to_vec(),
403
1
		decimals: 18,
404
1
	};
405
1

            
406
1
	ParaB::execute_with(|| {
407
1
		assert_ok!(AssetManager::register_foreign_asset(
408
1
			parachain::RuntimeOrigin::root(),
409
1
			source_location.clone(),
410
1
			asset_metadata,
411
1
			1u128,
412
1
			true
413
1
		));
414
1
		assert_ok!(add_supported_asset(source_location, 2500000000000));
415
1
	});
416
1

            
417
1
	let dest = Location {
418
1
		parents: 1,
419
1
		interior: [
420
1
			Parachain(2),
421
1
			AccountKey20 {
422
1
				network: None,
423
1
				key: PARAALICE.into(),
424
1
			},
425
1
		]
426
1
		.into(),
427
1
	};
428
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
429
1

            
430
1
	// In destination chain, we only need 4 weight
431
1
	// We put 10 weight, 6 of which should be refunded and 4 of which should go to treasury
432
1
	ParaA::execute_with(|| {
433
1
		let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
434
1
		assert_ok!(PolkadotXcm::transfer_assets(
435
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
436
1
			Box::new(VersionedLocation::from(chain_part)),
437
1
			Box::new(VersionedLocation::from(beneficiary)),
438
1
			Box::new(VersionedAssets::from(vec![asset])),
439
1
			0,
440
1
			WeightLimit::Limited(Weight::from_parts(10u64, DEFAULT_PROOF_SIZE))
441
1
		));
442
1
	});
443
1
	ParaA::execute_with(|| {
444
1
		// free execution, full amount received
445
1
		assert_eq!(
446
1
			ParaBalances::free_balance(&PARAALICE.into()),
447
1
			INITIAL_BALANCE - 100
448
1
		);
449
1
	});
450
1

            
451
1
	// We are sending 100 tokens from para A.
452
1
	// Amount spent in fees is Units per second * weight / 1_000_000_000_000 (weight per second)
453
1
	// weight is 4 since we are executing 4 instructions with a unitweightcost of 1.
454
1
	// Units per second should be 2_500_000_000_000_000
455
1
	// Since we set 10 weight in destination chain, 25 will be charged upfront
456
1
	// 15 of those will be refunded, while 10 will go to treasury as the true weight used
457
1
	// will be 4
458
1
	ParaB::execute_with(|| {
459
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 90);
460
		// Fee should have been received by treasury
461
1
		assert_eq!(Assets::balance(source_id, &Treasury::account_id()), 10);
462
1
	});
463
1
}
464

            
465
#[test]
466
1
fn send_para_a_asset_to_para_b_with_trader_and_fee() {
467
1
	MockNet::reset();
468
1

            
469
1
	let para_a_balances = Location::new(1, [Parachain(1), PalletInstance(1u8)]);
470
1
	let source_location: AssetType = para_a_balances
471
1
		.try_into()
472
1
		.expect("Location convertion to AssetType should succeed");
473
1
	let source_id: parachain::AssetId = source_location.clone().into();
474
1

            
475
1
	let asset_metadata = parachain::AssetMetadata {
476
1
		name: b"ParaAToken".to_vec(),
477
1
		symbol: b"ParaA".to_vec(),
478
1
		decimals: 18,
479
1
	};
480
1

            
481
1
	ParaB::execute_with(|| {
482
1
		assert_ok!(AssetManager::register_foreign_asset(
483
1
			parachain::RuntimeOrigin::root(),
484
1
			source_location.clone(),
485
1
			asset_metadata,
486
1
			1u128,
487
1
			true
488
1
		));
489
		// With these units per second, 80K weight convrets to 1 asset unit
490
1
		assert_ok!(add_supported_asset(source_location, 12500000));
491
1
	});
492
1

            
493
1
	let dest = Location {
494
1
		parents: 1,
495
1
		interior: [
496
1
			Parachain(2),
497
1
			AccountKey20 {
498
1
				network: None,
499
1
				key: PARAALICE.into(),
500
1
			},
501
1
		]
502
1
		.into(),
503
1
	};
504
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
505
1
	// we use transfer_with_fee
506
1
	ParaA::execute_with(|| {
507
1
		let asset = currency_to_asset(parachain::CurrencyId::SelfReserve, 100);
508
1
		let asset_fee = currency_to_asset(parachain::CurrencyId::SelfReserve, 1);
509
1
		assert_ok!(PolkadotXcm::transfer_assets(
510
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
511
1
			Box::new(VersionedLocation::from(chain_part)),
512
1
			Box::new(VersionedLocation::from(beneficiary)),
513
1
			Box::new(VersionedAssets::from(vec![asset_fee, asset])),
514
1
			0,
515
1
			WeightLimit::Limited(Weight::from_parts(800000u64, DEFAULT_PROOF_SIZE))
516
1
		));
517
1
	});
518
1
	ParaA::execute_with(|| {
519
1
		// 100 tokens transferred plus 1 taken from fees
520
1
		assert_eq!(
521
1
			ParaBalances::free_balance(&PARAALICE.into()),
522
1
			INITIAL_BALANCE - 100 - 1
523
1
		);
524
1
	});
525
1

            
526
1
	ParaB::execute_with(|| {
527
1
		// free execution, full amount received because the xcm instruction does not cost
528
1
		// what it is specified
529
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 101);
530
1
	});
531
1
}