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::{assert_ok, weights::Weight};
23
use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights};
24
use sp_std::boxed::Box;
25
use xcm::VersionedLocation;
26
use xcm::{
27
	latest::prelude::{AccountId32, Limited, Location, Parachain, WeightLimit},
28
	VersionedAssets,
29
};
30
use xcm_primitives::{split_location_into_chain_part_and_beneficiary, DEFAULT_PROOF_SIZE};
31
use xcm_simulator::TestExt;
32

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

            
37
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
38
1
	let source_id: parachain::AssetId = source_location.clone().into();
39
1

            
40
1
	let asset_metadata = parachain::AssetMetadata {
41
1
		name: b"RelayToken".to_vec(),
42
1
		symbol: b"Relay".to_vec(),
43
1
		decimals: 12,
44
1
	};
45
1

            
46
1
	ParaA::execute_with(|| {
47
1
		assert_ok!(AssetManager::register_foreign_asset(
48
1
			parachain::RuntimeOrigin::root(),
49
1
			source_location.clone(),
50
1
			asset_metadata,
51
1
			1u128,
52
1
			true
53
1
		));
54
1
		assert_ok!(add_supported_asset(source_location, 1));
55
1
	});
56
1

            
57
1
	// Setup relay transactor configuration
58
1
	ParaA::execute_with(|| {
59
1
		// Root can set transact info
60
1
		assert_ok!(XcmTransactor::set_transact_info(
61
1
			parachain::RuntimeOrigin::root(),
62
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
63
1
			// Relay charges 1000 for every instruction, and we have 3, so 3000
64
1
			3000.into(),
65
1
			20000000000.into(),
66
1
			None
67
1
		));
68
		// Root can set fee per second
69
1
		assert_ok!(XcmTransactor::set_fee_per_second(
70
1
			parachain::RuntimeOrigin::root(),
71
1
			Box::new(xcm::VersionedLocation::from(Location::parent())),
72
1
			frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as u128,
73
1
		));
74
1
	});
75
1

            
76
1
	// Let's construct the call to know how much weight it is going to require
77
1

            
78
1
	let dest = account_key20_location(PARAALICE);
79
1
	Relay::execute_with(|| {
80
1
		// 4000000000 transact + 3000 correspond to 4000003000 tokens. 100 more for the transfer call
81
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
82
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
83
1
			Box::new(Parachain(1).into()),
84
1
			Box::new(VersionedLocation::from(dest).clone()),
85
1
			Box::new(([], 4000003100u128).into()),
86
1
			0,
87
1
			WeightLimit::Unlimited
88
1
		));
89
1
	});
90
1

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

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

            
105
1
	// Send to registered address
106
1

            
107
1
	let registered_address = derivative_account_id(para_a_account(), 0);
108
1
	let dest = Location {
109
1
		parents: 1,
110
1
		interior: [AccountId32 {
111
1
			network: None,
112
1
			id: registered_address.clone().into(),
113
1
		}]
114
1
		.into(),
115
1
	};
116
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
117
1

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

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

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

            
143
1
	// Encode the call. Balances transact to para_a_account
144
1
	// First index
145
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
146
1

            
147
1
	ParaA::execute_with(|| {
148
1
		assert_ok!(XcmTransactor::transact_through_derivative(
149
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
150
1
			parachain::MockTransactors::Relay,
151
1
			0,
152
1
			CurrencyPayment {
153
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
154
1
					Location::parent()
155
1
				))),
156
1
				fee_amount: None
157
1
			},
158
1
			// 4000000000 + 3000 we should have taken out 4000003000 tokens from the caller
159
1
			encoded,
160
1
			TransactWeights {
161
1
				transact_required_weight_at_most: 4000000000.into(),
162
1
				overall_weight: None
163
1
			},
164
1
			false
165
1
		));
166
1
		let event_found: Option<parachain::RuntimeEvent> = parachain::para_events()
167
1
			.iter()
168
14
			.find_map(|event| match event.clone() {
169
				parachain::RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped {
170
					..
171
				}) => Some(event.clone()),
172
14
				_ => None,
173
14
			});
174
1
		// Assert that the events do not contain the assets being trapped
175
1
		assert!(event_found.is_none());
176
1
	});
177
1

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

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

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

            
190
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
191
1
	let source_id: parachain::AssetId = source_location.clone().into();
192
1

            
193
1
	let asset_metadata = parachain::AssetMetadata {
194
1
		name: b"RelayToken".to_vec(),
195
1
		symbol: b"Relay".to_vec(),
196
1
		decimals: 12,
197
1
	};
198
1

            
199
1
	ParaA::execute_with(|| {
200
1
		assert_ok!(AssetManager::register_foreign_asset(
201
1
			parachain::RuntimeOrigin::root(),
202
1
			source_location.clone(),
203
1
			asset_metadata,
204
1
			1u128,
205
1
			true
206
1
		));
207
1
		assert_ok!(add_supported_asset(source_location, 1));
208
1
	});
209
1

            
210
1
	// Let's construct the call to know how much weight it is going to require
211
1

            
212
1
	let dest = account_key20_location(PARAALICE);
213
1
	Relay::execute_with(|| {
214
1
		// 4000000000 transact + 3000 correspond to 4000003000 tokens. 100 more for the transfer call
215
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
216
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
217
1
			Box::new(Parachain(1).into()),
218
1
			Box::new(VersionedLocation::from(dest).clone()),
219
1
			Box::new(([], 4000003100u128).into()),
220
1
			0,
221
1
			WeightLimit::Unlimited
222
1
		));
223
1
	});
224
1

            
225
1
	ParaA::execute_with(|| {
226
1
		// free execution, full amount received
227
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003100);
228
1
	});
229
1

            
230
1
	// Register address
231
1
	ParaA::execute_with(|| {
232
1
		assert_ok!(XcmTransactor::register(
233
1
			parachain::RuntimeOrigin::root(),
234
1
			PARAALICE.into(),
235
1
			0,
236
1
		));
237
1
	});
238
1

            
239
1
	// Send to registered address
240
1

            
241
1
	let registered_address = derivative_account_id(para_a_account(), 0);
242
1
	let dest = Location {
243
1
		parents: 1,
244
1
		interior: [AccountId32 {
245
1
			network: None,
246
1
			id: registered_address.clone().into(),
247
1
		}]
248
1
		.into(),
249
1
	};
250
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
251
1

            
252
1
	ParaA::execute_with(|| {
253
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
254
1
		// free execution, full amount received
255
1
		assert_ok!(PolkadotXcm::transfer_assets(
256
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
257
1
			Box::new(VersionedLocation::from(chain_part)),
258
1
			Box::new(VersionedLocation::from(beneficiary)),
259
1
			Box::new(VersionedAssets::from(vec![asset])),
260
1
			0,
261
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
262
1
		));
263
1
	});
264
1

            
265
1
	ParaA::execute_with(|| {
266
1
		// free execution, full amount received
267
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000003000);
268
1
	});
269
1

            
270
1
	// What we will do now is transfer this relay tokens from the derived account to the sovereign
271
1
	// again
272
1
	Relay::execute_with(|| {
273
1
		// free execution,x	 full amount received
274
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 4000003000);
275
1
	});
276
1

            
277
1
	// Encode the call. Balances transact to para_a_account
278
1
	// First index
279
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
280
1

            
281
1
	let overall_weight = 4000003000u64;
282
1
	ParaA::execute_with(|| {
283
1
		assert_ok!(XcmTransactor::transact_through_derivative(
284
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
285
1
			parachain::MockTransactors::Relay,
286
1
			0,
287
1
			CurrencyPayment {
288
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
289
1
					Location::parent()
290
1
				))),
291
1
				// 1-1 fee weight mapping
292
1
				fee_amount: Some(overall_weight as u128)
293
1
			},
294
1
			// 4000000000 + 3000 we should have taken out 4000003000 tokens from the caller
295
1
			encoded,
296
1
			TransactWeights {
297
1
				transact_required_weight_at_most: 4000000000.into(),
298
1
				overall_weight: Some(Limited(overall_weight.into()))
299
1
			},
300
1
			false
301
1
		));
302
1
		let event_found: Option<parachain::RuntimeEvent> = parachain::para_events()
303
1
			.iter()
304
12
			.find_map(|event| match event.clone() {
305
				parachain::RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped {
306
					..
307
				}) => Some(event.clone()),
308
12
				_ => None,
309
12
			});
310
1
		// Assert that the events do not contain the assets being trapped
311
1
		assert!(event_found.is_none());
312
1
	});
313
1

            
314
1
	Relay::execute_with(|| {
315
1
		// free execution,x	 full amount received
316
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 100);
317

            
318
1
		assert!(RelayBalances::free_balance(&registered_address) == 0);
319
1
	});
320
1
}
321

            
322
#[test]
323
1
fn transact_through_derivative_with_custom_fee_weight_refund() {
324
1
	MockNet::reset();
325
1

            
326
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
327
1
	let source_id: parachain::AssetId = source_location.clone().into();
328
1

            
329
1
	let asset_metadata = parachain::AssetMetadata {
330
1
		name: b"RelayToken".to_vec(),
331
1
		symbol: b"Relay".to_vec(),
332
1
		decimals: 12,
333
1
	};
334
1

            
335
1
	ParaA::execute_with(|| {
336
1
		assert_ok!(AssetManager::register_foreign_asset(
337
1
			parachain::RuntimeOrigin::root(),
338
1
			source_location.clone(),
339
1
			asset_metadata,
340
1
			1u128,
341
1
			true
342
1
		));
343
1
		assert_ok!(add_supported_asset(source_location, 1));
344
1
	});
345
1

            
346
1
	// Let's construct the call to know how much weight it is going to require
347
1

            
348
1
	let dest = account_key20_location(PARAALICE);
349
1
	Relay::execute_with(|| {
350
1
		// 4000000000 transact + 9000 correspond to 4000009000 tokens. 100 more for the transfer call
351
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
352
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
353
1
			Box::new(Parachain(1).into()),
354
1
			Box::new(VersionedLocation::from(dest).clone()),
355
1
			Box::new(([], 4000009100u128).into()),
356
1
			0,
357
1
			WeightLimit::Unlimited
358
1
		));
359
1
	});
360
1

            
361
1
	ParaA::execute_with(|| {
362
1
		// free execution, full amount received
363
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000009100);
364
1
	});
365
1

            
366
1
	// Register address
367
1
	ParaA::execute_with(|| {
368
1
		assert_ok!(XcmTransactor::register(
369
1
			parachain::RuntimeOrigin::root(),
370
1
			PARAALICE.into(),
371
1
			0,
372
1
		));
373
1
	});
374
1

            
375
1
	// Send to registered address
376
1

            
377
1
	let registered_address = derivative_account_id(para_a_account(), 0);
378
1
	let dest = Location {
379
1
		parents: 1,
380
1
		interior: [AccountId32 {
381
1
			network: None,
382
1
			id: registered_address.clone().into(),
383
1
		}]
384
1
		.into(),
385
1
	};
386
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
387
1

            
388
1
	ParaA::execute_with(|| {
389
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(source_id), 100);
390
1
		// free execution, full amount received
391
1
		assert_ok!(PolkadotXcm::transfer_assets(
392
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
393
1
			Box::new(VersionedLocation::from(chain_part)),
394
1
			Box::new(VersionedLocation::from(beneficiary)),
395
1
			Box::new(VersionedAssets::from(vec![asset])),
396
1
			0,
397
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
398
1
		));
399
1
	});
400
1

            
401
1
	ParaA::execute_with(|| {
402
1
		// free execution, full amount received
403
1
		assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 4000009000);
404
1
	});
405
1

            
406
1
	// What we will do now is transfer this relay tokens from the derived account to the sovereign
407
1
	// again
408
1
	Relay::execute_with(|| {
409
1
		// free execution,x	 full amount received
410
1
		assert!(RelayBalances::free_balance(&para_a_account()) == 4000009000);
411
1
	});
412
1

            
413
1
	// Encode the call. Balances transact to para_a_account
414
1
	// First index
415
1
	let encoded = encode_relay_balance_transfer_call(para_a_account(), 100u128);
416
1

            
417
1
	let overall_weight = 4000009000u64;
418
1
	ParaA::execute_with(|| {
419
1
		assert_ok!(XcmTransactor::transact_through_derivative(
420
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
421
1
			parachain::MockTransactors::Relay,
422
1
			0,
423
1
			CurrencyPayment {
424
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
425
1
					Location::parent()
426
1
				))),
427
1
				// 1-1 fee weight mapping
428
1
				fee_amount: Some(overall_weight as u128)
429
1
			},
430
1
			encoded,
431
1
			TransactWeights {
432
1
				transact_required_weight_at_most: 4000000000.into(),
433
1
				overall_weight: Some(Limited(overall_weight.into()))
434
1
			},
435
1
			true
436
1
		));
437
1
		let event_found: Option<parachain::RuntimeEvent> = parachain::para_events()
438
1
			.iter()
439
12
			.find_map(|event| match event.clone() {
440
				parachain::RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped {
441
					..
442
				}) => Some(event.clone()),
443
12
				_ => None,
444
12
			});
445
1
		// Assert that the events do not contain the assets being trapped
446
1
		assert!(event_found.is_none());
447
1
	});
448
1

            
449
1
	Relay::execute_with(|| {
450
1
		// free execution, full amount received
451
1
		// 4000009000 refunded + 100 transferred = 4000009100
452
1
		assert_eq!(RelayBalances::free_balance(&para_a_account()), 4000009100);
453
1
		assert_eq!(RelayBalances::free_balance(&registered_address), 0);
454
1
	});
455
1
}