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_mock::*;
19
use crate::xcm_testing::{
20
	add_supported_asset, currency_to_asset, derivative_account_id, helpers::*,
21
};
22
use frame_support::{
23
	assert_ok, traits::PalletInfo, weights::constants::WEIGHT_REF_TIME_PER_SECOND, weights::Weight,
24
};
25
use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights};
26
use sp_std::boxed::Box;
27
use xcm::VersionedLocation;
28
use xcm::{
29
	latest::prelude::{AccountId32, Limited, Location, OriginKind, Parachain, WeightLimit},
30
	VersionedAssets,
31
};
32
use xcm_primitives::{
33
	split_location_into_chain_part_and_beneficiary, UtilityEncodeCall, DEFAULT_PROOF_SIZE,
34
};
35
use xcm_simulator::{Encode, TestExt};
36

            
37
#[test]
38
1
fn transact_through_sovereign() {
39
1
	MockNet::reset();
40
1

            
41
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
42
1
	let source_id: parachain::AssetId = source_location.clone().into();
43
1

            
44
1
	let asset_metadata = parachain::AssetMetadata {
45
1
		name: b"RelayToken".to_vec(),
46
1
		symbol: b"Relay".to_vec(),
47
1
		decimals: 12,
48
1
	};
49
1

            
50
1
	ParaA::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, 1));
59

            
60
		// Root can set transact info
61
1
		assert_ok!(XcmTransactor::set_transact_info(
62
1
			parachain::RuntimeOrigin::root(),
63
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
64
1
			// Relay charges 1000 for every instruction, and we have 3, so 3000
65
1
			3000.into(),
66
1
			20000000000.into(),
67
1
			None
68
1
		));
69
		// Root can set transact info
70
1
		assert_ok!(XcmTransactor::set_fee_per_second(
71
1
			parachain::RuntimeOrigin::root(),
72
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
73
1
			WEIGHT_REF_TIME_PER_SECOND as u128,
74
1
		));
75
1
	});
76
1

            
77
1
	let dest = account_key20_location(PARAALICE);
78
1
	Relay::execute_with(|| {
79
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
80
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
81
1
			Box::new(Parachain(1).into()),
82
1
			Box::new(VersionedLocation::from(dest).clone()),
83
1
			Box::new(([], 4000003100u128).into()),
84
1
			0,
85
1
			WeightLimit::Unlimited
86
1
		));
87
1
	});
88
1

            
89
1
	ParaA::execute_with(|| {
90
1
		// free execution, full amount received
91
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003100);
92
1
	});
93
1

            
94
1
	// Register address
95
1
	ParaA::execute_with(|| {
96
1
		assert_ok!(XcmTransactor::register(
97
1
			parachain::RuntimeOrigin::root(),
98
1
			PARAALICE.into(),
99
1
			0,
100
1
		));
101
1
	});
102
1

            
103
1
	// Send to registered address
104
1
	let registered_address = derivative_account_id(para_a_account(), 0);
105
1
	let dest = Location {
106
1
		parents: 1,
107
1
		interior: [AccountId32 {
108
1
			network: None,
109
1
			id: registered_address.clone().into(),
110
1
		}]
111
1
		.into(),
112
1
	};
113
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
114
1

            
115
1
	ParaA::execute_with(|| {
116
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
117
1
		// free execution, full amount received
118
1
		assert_ok!(PolkadotXcm::transfer_assets(
119
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
120
1
			Box::new(VersionedLocation::from(chain_part)),
121
1
			Box::new(VersionedLocation::from(beneficiary)),
122
1
			Box::new(VersionedAssets::from(vec![asset])),
123
1
			0,
124
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
125
1
		));
126
1
	});
127
1

            
128
1
	ParaA::execute_with(|| {
129
1
		// free execution, full amount received
130
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003000);
131
1
	});
132
1

            
133
1
	// What we will do now is transfer this relay tokens from the derived account to the sovereign
134
1
	// again
135
1
	Relay::execute_with(|| {
136
1
		// free execution,x	 full amount received
137
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 4000003000);
138
1
		0
139
1
	});
140
1

            
141
1
	// We send the xcm transact operation to parent
142
1
	let dest = Location {
143
1
		parents: 1,
144
1
		interior: [].into(),
145
1
	};
146
1

            
147
1
	// Encode the call. Balances transact to para_a_account
148
1
	// First index
149
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
150
1

            
151
1
	let utility_bytes = parachain::MockTransactors::Relay.encode_call(
152
1
		xcm_primitives::UtilityAvailableCalls::AsDerivative(0, encoded),
153
1
	);
154
1

            
155
1
	// Root can directly pass the execution byes to the sovereign
156
1
	ParaA::execute_with(|| {
157
1
		assert_ok!(XcmTransactor::transact_through_sovereign(
158
1
			parachain::RuntimeOrigin::root(),
159
1
			Box::new(xcm::VersionedLocation::from(dest)),
160
1
			Some(PARAALICE.into()),
161
1
			CurrencyPayment {
162
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
163
1
					Location::parent()
164
1
				))),
165
1
				fee_amount: None
166
1
			},
167
1
			utility_bytes,
168
1
			OriginKind::SovereignAccount,
169
1
			TransactWeights {
170
1
				transact_required_weight_at_most: 4000000000.into(),
171
1
				overall_weight: None
172
1
			},
173
1
			false
174
1
		));
175
1
	});
176
1

            
177
1
	Relay::execute_with(|| {
178
1
		// free execution,x	 full amount received
179
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 100);
180

            
181
1
		assert!(RelayBalances::free_balance(&registered_address) == 0);
182
1
	});
183
1
}
184

            
185
#[test]
186
1
fn transact_through_sovereign_fee_payer_none() {
187
1
	MockNet::reset();
188
1

            
189
1
	ParaA::execute_with(|| {
190
1
		// Root can set transact info
191
1
		assert_ok!(XcmTransactor::set_transact_info(
192
1
			parachain::RuntimeOrigin::root(),
193
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
194
1
			// Relay charges 1000 for every instruction, and we have 3, so 3000
195
1
			3000.into(),
196
1
			20000000000.into(),
197
1
			None
198
1
		));
199
		// Root can set transact info
200
1
		assert_ok!(XcmTransactor::set_fee_per_second(
201
1
			parachain::RuntimeOrigin::root(),
202
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
203
1
			WEIGHT_REF_TIME_PER_SECOND as u128,
204
1
		));
205
1
	});
206
1

            
207
1
	let derivative_address = derivative_account_id(para_a_account(), 0);
208
1

            
209
1
	Relay::execute_with(|| {
210
1
		// Transfer 100 tokens to derivative_address on the relay
211
1
		assert_ok!(RelayBalances::transfer_keep_alive(
212
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
213
1
			derivative_address.clone(),
214
1
			100u128
215
1
		));
216

            
217
		// Transfer the XCM execution fee amount to ParaA's sovereign account
218
1
		assert_ok!(RelayBalances::transfer_keep_alive(
219
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
220
1
			para_a_account(),
221
1
			4000003000u128
222
1
		));
223
1
	});
224
1

            
225
1
	// Check balances before the transact call
226
1
	Relay::execute_with(|| {
227
1
		assert_eq!(RelayBalances::free_balance(&para_a_account()), 4000003000);
228
1
		assert_eq!(RelayBalances::free_balance(&derivative_address), 100);
229
1
		assert_eq!(RelayBalances::free_balance(&RELAYBOB), 0);
230
1
	});
231
1

            
232
1
	// Encode the call. Balances transfer of 100 relay tokens to RELAYBOB
233
1
	let mut encoded: Vec<u8> = Vec::new();
234
1
	let index = <relay_chain::Runtime as frame_system::Config>::PalletInfo::index::<
235
1
		relay_chain::Balances,
236
1
	>()
237
1
	.unwrap() as u8;
238
1

            
239
1
	encoded.push(index);
240
1

            
241
1
	let mut call_bytes = pallet_balances::Call::<relay_chain::Runtime>::transfer_allow_death {
242
1
		dest: RELAYBOB,
243
1
		value: 100u32.into(),
244
1
	}
245
1
	.encode();
246
1
	encoded.append(&mut call_bytes);
247
1

            
248
1
	// The final call will be an AsDerivative using index 0
249
1
	let utility_bytes = parachain::MockTransactors::Relay.encode_call(
250
1
		xcm_primitives::UtilityAvailableCalls::AsDerivative(0, encoded),
251
1
	);
252
1

            
253
1
	// We send the xcm transact operation to parent
254
1
	let dest = Location {
255
1
		parents: 1,
256
1
		interior: /* Here */ [].into(),
257
1
	};
258
1

            
259
1
	// Root can directly pass the execution byes to the sovereign
260
1
	ParaA::execute_with(|| {
261
1
		assert_ok!(XcmTransactor::transact_through_sovereign(
262
1
			parachain::RuntimeOrigin::root(),
263
1
			Box::new(xcm::VersionedLocation::from(dest)),
264
1
			// No fee_payer here. The sovereign account will pay the fees on destination.
265
1
			None,
266
1
			CurrencyPayment {
267
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
268
1
					Location::parent()
269
1
				))),
270
1
				fee_amount: None
271
1
			},
272
1
			utility_bytes,
273
1
			OriginKind::SovereignAccount,
274
1
			TransactWeights {
275
1
				transact_required_weight_at_most: 4000000000.into(),
276
1
				overall_weight: None
277
1
			},
278
1
			false
279
1
		));
280
1
	});
281
1

            
282
1
	// Check balances after the transact call are correct
283
1
	Relay::execute_with(|| {
284
1
		assert_eq!(RelayBalances::free_balance(&para_a_account()), 0);
285
1
		assert_eq!(RelayBalances::free_balance(&derivative_address), 0);
286
1
		assert_eq!(RelayBalances::free_balance(&RELAYBOB), 100);
287
1
	});
288
1
}
289

            
290
#[test]
291
1
fn transact_through_sovereign_with_custom_fee_weight() {
292
1
	MockNet::reset();
293
1

            
294
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
295
1
	let source_id: parachain::AssetId = source_location.clone().into();
296
1

            
297
1
	let asset_metadata = parachain::AssetMetadata {
298
1
		name: b"RelayToken".to_vec(),
299
1
		symbol: b"Relay".to_vec(),
300
1
		decimals: 12,
301
1
	};
302
1

            
303
1
	ParaA::execute_with(|| {
304
1
		assert_ok!(AssetManager::register_foreign_asset(
305
1
			parachain::RuntimeOrigin::root(),
306
1
			source_location.clone(),
307
1
			asset_metadata,
308
1
			1u128,
309
1
			true
310
1
		));
311
1
		assert_ok!(add_supported_asset(source_location, 1));
312
1
	});
313
1

            
314
1
	let dest = account_key20_location(PARAALICE);
315
1
	Relay::execute_with(|| {
316
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
317
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
318
1
			Box::new(Parachain(1).into()),
319
1
			Box::new(VersionedLocation::from(dest).clone()),
320
1
			Box::new(([], 4000003100u128).into()),
321
1
			0,
322
1
			WeightLimit::Unlimited
323
1
		));
324
1
	});
325
1

            
326
1
	ParaA::execute_with(|| {
327
1
		// free execution, full amount received
328
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003100);
329
1
	});
330
1

            
331
1
	// Register address
332
1
	ParaA::execute_with(|| {
333
1
		assert_ok!(XcmTransactor::register(
334
1
			parachain::RuntimeOrigin::root(),
335
1
			PARAALICE.into(),
336
1
			0,
337
1
		));
338
1
	});
339
1

            
340
1
	// Send to registered address
341
1
	let registered_address = derivative_account_id(para_a_account(), 0);
342
1
	let dest = Location {
343
1
		parents: 1,
344
1
		interior: [AccountId32 {
345
1
			network: None,
346
1
			id: registered_address.clone().into(),
347
1
		}]
348
1
		.into(),
349
1
	};
350
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
351
1

            
352
1
	ParaA::execute_with(|| {
353
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
354
1
		// free execution, full amount received
355
1
		assert_ok!(PolkadotXcm::transfer_assets(
356
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
357
1
			Box::new(VersionedLocation::from(chain_part)),
358
1
			Box::new(VersionedLocation::from(beneficiary)),
359
1
			Box::new(VersionedAssets::from(vec![asset])),
360
1
			0,
361
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
362
1
		));
363
1
	});
364
1

            
365
1
	ParaA::execute_with(|| {
366
1
		// free execution, full amount received
367
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003000);
368
1
	});
369
1

            
370
1
	// What we will do now is transfer this relay tokens from the derived account to the sovereign
371
1
	// again
372
1
	Relay::execute_with(|| {
373
1
		// free execution,x	 full amount received
374
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 4000003000);
375
1
		0
376
1
	});
377
1

            
378
1
	// We send the xcm transact operation to parent
379
1
	let dest = Location {
380
1
		parents: 1,
381
1
		interior: [].into(),
382
1
	};
383
1

            
384
1
	// Encode the call. Balances transact to para_a_account
385
1
	// First index
386
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
387
1

            
388
1
	let utility_bytes = parachain::MockTransactors::Relay.encode_call(
389
1
		xcm_primitives::UtilityAvailableCalls::AsDerivative(0, encoded),
390
1
	);
391
1

            
392
1
	let total_weight = 4000003000u64;
393
1
	// Root can directly pass the execution byes to the sovereign
394
1
	ParaA::execute_with(|| {
395
1
		assert_ok!(XcmTransactor::transact_through_sovereign(
396
1
			parachain::RuntimeOrigin::root(),
397
1
			Box::new(xcm::VersionedLocation::from(dest)),
398
1
			Some(PARAALICE.into()),
399
1
			CurrencyPayment {
400
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
401
1
					Location::parent()
402
1
				))),
403
1
				// 1-1 fee-weight mapping
404
1
				fee_amount: Some(total_weight as u128)
405
1
			},
406
1
			utility_bytes,
407
1
			OriginKind::SovereignAccount,
408
1
			TransactWeights {
409
1
				transact_required_weight_at_most: 4000000000.into(),
410
1
				overall_weight: Some(Limited(total_weight.into()))
411
1
			},
412
1
			false
413
1
		));
414
1
	});
415
1

            
416
1
	Relay::execute_with(|| {
417
1
		// free execution,x	 full amount received
418
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 100);
419

            
420
1
		assert!(RelayBalances::free_balance(&registered_address) == 0);
421
1
	});
422
1
}
423

            
424
#[test]
425
1
fn transact_through_sovereign_with_custom_fee_weight_refund() {
426
1
	MockNet::reset();
427
1

            
428
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
429
1
	let source_id: parachain::AssetId = source_location.clone().into();
430
1

            
431
1
	let asset_metadata = parachain::AssetMetadata {
432
1
		name: b"RelayToken".to_vec(),
433
1
		symbol: b"Relay".to_vec(),
434
1
		decimals: 12,
435
1
	};
436
1

            
437
1
	ParaA::execute_with(|| {
438
1
		assert_ok!(AssetManager::register_foreign_asset(
439
1
			parachain::RuntimeOrigin::root(),
440
1
			source_location.clone(),
441
1
			asset_metadata,
442
1
			1u128,
443
1
			true
444
1
		));
445
1
		assert_ok!(add_supported_asset(source_location, 1));
446
1
	});
447
1

            
448
1
	let dest = account_key20_location(PARAALICE);
449
1
	Relay::execute_with(|| {
450
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
451
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
452
1
			Box::new(Parachain(1).into()),
453
1
			Box::new(VersionedLocation::from(dest).clone()),
454
1
			Box::new(([], 4000009100u128).into()),
455
1
			0,
456
1
			WeightLimit::Unlimited
457
1
		));
458
1
	});
459
1

            
460
1
	ParaA::execute_with(|| {
461
1
		// free execution, full amount received
462
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000009100);
463
1
	});
464
1

            
465
1
	// Register address
466
1
	ParaA::execute_with(|| {
467
1
		assert_ok!(XcmTransactor::register(
468
1
			parachain::RuntimeOrigin::root(),
469
1
			PARAALICE.into(),
470
1
			0,
471
1
		));
472
1
	});
473
1

            
474
1
	// Send to registered address
475
1
	let registered_address = derivative_account_id(para_a_account(), 0);
476
1
	let dest = Location {
477
1
		parents: 1,
478
1
		interior: [AccountId32 {
479
1
			network: None,
480
1
			id: registered_address.clone().into(),
481
1
		}]
482
1
		.into(),
483
1
	};
484
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
485
1

            
486
1
	ParaA::execute_with(|| {
487
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
488
1
		// free execution, full amount received
489
1
		assert_ok!(PolkadotXcm::transfer_assets(
490
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
491
1
			Box::new(VersionedLocation::from(chain_part)),
492
1
			Box::new(VersionedLocation::from(beneficiary)),
493
1
			Box::new(VersionedAssets::from(vec![asset])),
494
1
			0,
495
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
496
1
		));
497
1
	});
498
1

            
499
1
	ParaA::execute_with(|| {
500
1
		// free execution, full amount received
501
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000009000);
502
1
	});
503
1

            
504
1
	// What we will do now is transfer this relay tokens from the derived account to the sovereign
505
1
	// again
506
1
	Relay::execute_with(|| {
507
1
		// free execution,x	 full amount received
508
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 4000009000);
509
1
		0
510
1
	});
511
1

            
512
1
	// We send the xcm transact operation to parent
513
1
	let dest = Location {
514
1
		parents: 1,
515
1
		interior: [].into(),
516
1
	};
517
1

            
518
1
	// Encode the call. Balances transact to para_a_account
519
1
	// First index
520
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
521
1

            
522
1
	let utility_bytes = parachain::MockTransactors::Relay.encode_call(
523
1
		xcm_primitives::UtilityAvailableCalls::AsDerivative(0, encoded),
524
1
	);
525
1

            
526
1
	let total_weight = 4000009000u64;
527
1
	// Root can directly pass the execution byes to the sovereign
528
1
	ParaA::execute_with(|| {
529
1
		assert_ok!(XcmTransactor::transact_through_sovereign(
530
1
			parachain::RuntimeOrigin::root(),
531
1
			Box::new(xcm::VersionedLocation::from(dest)),
532
1
			Some(PARAALICE.into()),
533
1
			CurrencyPayment {
534
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
535
1
					Location::parent()
536
1
				))),
537
1
				// 1-1 fee-weight mapping
538
1
				fee_amount: Some(total_weight as u128)
539
1
			},
540
1
			utility_bytes,
541
1
			OriginKind::SovereignAccount,
542
1
			TransactWeights {
543
1
				transact_required_weight_at_most: 4000000000.into(),
544
1
				overall_weight: Some(Limited(total_weight.into()))
545
1
			},
546
1
			true
547
1
		));
548
1
	});
549
1

            
550
1
	Relay::execute_with(|| {
551
1
		// free execution, full amount received
552
1
		// 4000009000 refunded + 100 transferred = 4000009100
553
1
		assert_eq!(RelayBalances::free_balance(&para_a_account()), 4000009100);
554

            
555
1
		assert_eq!(RelayBalances::free_balance(&registered_address), 0);
556
1
	});
557
1
}