referrerpolicy=no-referrer-when-downgrade

asset_test_utils/
test_cases.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Module contains predefined test-case scenarios for `Runtime` with various assets.
17
18use super::xcm_helpers;
19use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees};
20use assets_common::local_and_foreign_assets::ForeignAssetReserveData;
21use codec::Encode;
22use core::ops::Mul;
23use cumulus_primitives_core::{UpwardMessageSender, XcmpMessageSource};
24use frame_support::{
25	assert_noop, assert_ok,
26	traits::{
27		fungible::Mutate, fungibles::InspectEnumerable, Currency, Get, OnFinalize, OnInitialize,
28		OriginTrait,
29	},
30	weights::Weight,
31};
32use frame_system::pallet_prelude::BlockNumberFor;
33use parachains_common::{AccountId, Balance};
34use parachains_runtimes_test_utils::{
35	assert_metadata, assert_total, mock_open_hrmp_channel, AccountIdOf, BalanceOf,
36	CollatorSessionKeys, ExtBuilder, SlotDurations, ValidatorIdOf, XcmReceivedFrom,
37};
38use sp_runtime::{
39	traits::{Block as BlockT, MaybeEquivalence, StaticLookup, Zero},
40	DispatchError, SaturatedConversion, Saturating,
41};
42use xcm::{latest::prelude::*, VersionedAssetId, VersionedAssets, VersionedXcm};
43use xcm_executor::{
44	traits::{ConvertLocation, TransferType},
45	XcmExecutor,
46};
47use xcm_runtime_apis::fees::{
48	runtime_decl_for_xcm_payment_api::XcmPaymentApiV2, Error as XcmPaymentApiError,
49};
50
51type RuntimeHelper<Runtime, AllPalletsWithoutSystem = ()> =
52	parachains_runtimes_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>;
53
54// Re-export test_case from `parachains-runtimes-test-utils`
55pub use parachains_runtimes_test_utils::test_cases::change_storage_constant_by_governance_works;
56
57/// Test-case makes sure that `Runtime` can receive native asset from relay chain and can teleport
58/// it back
59pub fn teleports_for_native_asset_works<
60	Runtime,
61	AllPalletsWithoutSystem,
62	XcmConfig,
63	CheckingAccount,
64	WeightToFee,
65	HrmpChannelOpener,
66>(
67	collator_session_keys: CollatorSessionKeys<Runtime>,
68	slot_durations: SlotDurations,
69	existential_deposit: BalanceOf<Runtime>,
70	target_account: AccountIdOf<Runtime>,
71	unwrap_pallet_xcm_event: Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
72	runtime_para_id: u32,
73) where
74	Runtime: frame_system::Config
75		+ pallet_balances::Config
76		+ pallet_session::Config
77		+ pallet_xcm::Config
78		+ parachain_info::Config
79		+ pallet_collator_selection::Config
80		+ cumulus_pallet_parachain_system::Config
81		+ cumulus_pallet_xcmp_queue::Config
82		+ pallet_timestamp::Config,
83	AllPalletsWithoutSystem:
84		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
85	AccountIdOf<Runtime>: Into<[u8; 32]>,
86	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
87	BalanceOf<Runtime>: From<Balance> + Into<u128>,
88	WeightToFee: frame_support::weights::WeightToFee<Balance = Balance>,
89	<WeightToFee as frame_support::weights::WeightToFee>::Balance: From<u128> + Into<u128>,
90	<Runtime as frame_system::Config>::AccountId:
91		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
92	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
93		From<<Runtime as frame_system::Config>::AccountId>,
94	<Runtime as frame_system::Config>::AccountId: From<AccountId>,
95	XcmConfig: xcm_executor::Config,
96	CheckingAccount: Get<Option<AccountIdOf<Runtime>>>,
97	HrmpChannelOpener: frame_support::inherent::ProvideInherent<
98		Call = cumulus_pallet_parachain_system::Call<Runtime>,
99	>,
100{
101	let buy_execution_fee_amount_eta =
102		WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 1024));
103	let native_asset_amount_unit = existential_deposit;
104	let native_asset_amount_received =
105		native_asset_amount_unit * 10.into() + buy_execution_fee_amount_eta.into();
106
107	let checking_account = if let Some(checking_account) = CheckingAccount::get() {
108		Some((checking_account, native_asset_amount_received))
109	} else {
110		None
111	};
112
113	let mut builder = ExtBuilder::<Runtime>::default()
114		.with_collators(collator_session_keys.collators())
115		.with_session_keys(collator_session_keys.session_keys())
116		.with_safe_xcm_version(XCM_VERSION)
117		.with_para_id(runtime_para_id.into())
118		.with_tracing();
119
120	if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() {
121		builder =
122			builder.with_balances(vec![(checking_account.clone(), *initial_checking_account)]);
123	};
124
125	builder
126		.build()
127		.execute_with(|| {
128			let mut alice = [0u8; 32];
129			alice[0] = 1;
130
131			let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
132				2,
133				AccountId::from(alice).into(),
134			);
135			// check Balances before
136			assert_eq!(<pallet_balances::Pallet<Runtime>>::free_balance(&target_account), 0.into());
137			if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() {
138				assert_eq!(
139					<pallet_balances::Pallet<Runtime>>::free_balance(checking_account),
140					*initial_checking_account
141				);
142			};
143
144			let native_asset_id = Location::parent();
145
146			// 1. process received teleported assets from relaychain
147			let xcm = Xcm(vec![
148				ReceiveTeleportedAsset(Assets::from(vec![Asset {
149					id: AssetId(native_asset_id.clone()),
150					fun: Fungible(native_asset_amount_received.into()),
151				}])),
152				ClearOrigin,
153				BuyExecution {
154					fees: Asset {
155						id: AssetId(native_asset_id.clone()),
156						fun: Fungible(buy_execution_fee_amount_eta),
157					},
158					weight_limit: Limited(Weight::from_parts(3035310000, 65536)),
159				},
160				DepositAsset {
161					assets: Wild(AllCounted(1)),
162					beneficiary: Location {
163						parents: 0,
164						interior: [AccountId32 {
165							network: None,
166							id: target_account.clone().into(),
167						}]
168						.into(),
169					},
170				},
171				ExpectTransactStatus(MaybeErrorCode::Success),
172			]);
173
174			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
175
176			let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
177				Parent,
178				xcm,
179				&mut hash,
180				RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Parent),
181				Weight::zero(),
182			);
183			assert_ok!(outcome.ensure_complete());
184
185			// check Balances after
186			assert_ne!(<pallet_balances::Pallet<Runtime>>::free_balance(&target_account), 0.into());
187			if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() {
188				assert_eq!(
189					<pallet_balances::Pallet<Runtime>>::free_balance(checking_account),
190					*initial_checking_account - native_asset_amount_received
191				);
192			}
193
194			let native_asset_to_teleport_away = native_asset_amount_unit * 3.into();
195			// 2. try to teleport asset back to the relaychain
196			{
197				<cumulus_pallet_parachain_system::Pallet<Runtime> as UpwardMessageSender>::ensure_successful_delivery();
198
199				let dest = Location::parent();
200				let mut dest_beneficiary = Location::parent()
201					.appended_with(AccountId32 {
202						network: None,
203						id: sp_runtime::AccountId32::new([3; 32]).into(),
204					})
205					.unwrap();
206				dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap();
207
208				let target_account_balance_before_teleport =
209					<pallet_balances::Pallet<Runtime>>::free_balance(&target_account);
210				assert!(
211					native_asset_to_teleport_away <
212						target_account_balance_before_teleport - existential_deposit
213				);
214
215				// Mint funds into account to ensure it has enough balance to pay delivery fees
216				let delivery_fees =
217					xcm_helpers::teleport_assets_delivery_fees::<XcmConfig::XcmSender>(
218						(native_asset_id.clone(), native_asset_to_teleport_away.into()).into(),
219						0,
220						Unlimited,
221						dest_beneficiary.clone(),
222						dest.clone(),
223					);
224				<pallet_balances::Pallet<Runtime>>::mint_into(
225					&target_account,
226					delivery_fees.into(),
227				)
228				.unwrap();
229
230				assert_ok!(RuntimeHelper::<Runtime>::do_teleport_assets::<HrmpChannelOpener>(
231					RuntimeHelper::<Runtime>::origin_of(target_account.clone()),
232					dest,
233					dest_beneficiary,
234					(native_asset_id.clone(), native_asset_to_teleport_away.into()),
235					None,
236					included_head.clone(),
237					&alice,
238					&slot_durations,
239				));
240
241				// check balances
242				assert_eq!(
243					<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
244					target_account_balance_before_teleport - native_asset_to_teleport_away
245				);
246				if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() {
247					assert_eq!(
248						<pallet_balances::Pallet<Runtime>>::free_balance(checking_account),
249						*initial_checking_account - native_asset_amount_received + native_asset_to_teleport_away
250					);
251				}
252
253				// check events
254				RuntimeHelper::<Runtime>::assert_pallet_xcm_event_outcome(
255					&unwrap_pallet_xcm_event,
256					|outcome| {
257						assert_ok!(outcome.ensure_complete());
258					},
259				);
260			}
261
262			// 3. try to teleport assets away to other parachain (2345): should not work as we don't
263			//    trust `IsTeleporter` for `(relay-native-asset, para(2345))` pair
264			{
265				let other_para_id = 2345;
266				let dest = Location::new(1, [Parachain(other_para_id)]);
267				let mut dest_beneficiary = Location::new(1, [Parachain(other_para_id)])
268					.appended_with(AccountId32 {
269						network: None,
270						id: sp_runtime::AccountId32::new([3; 32]).into(),
271					})
272					.unwrap();
273				dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap();
274
275				let target_account_balance_before_teleport =
276					<pallet_balances::Pallet<Runtime>>::free_balance(&target_account);
277
278				let native_asset_to_teleport_away = native_asset_amount_unit * 3.into();
279				assert!(
280					native_asset_to_teleport_away <
281						target_account_balance_before_teleport - existential_deposit
282				);
283
284				assert_eq!(
285					RuntimeHelper::<Runtime>::do_teleport_assets::<HrmpChannelOpener>(
286						RuntimeHelper::<Runtime>::origin_of(target_account.clone()),
287						dest,
288						dest_beneficiary,
289						(native_asset_id, native_asset_to_teleport_away.into()),
290						Some((runtime_para_id, other_para_id)),
291						included_head,
292						&alice,
293						&slot_durations,
294					),
295					Err(DispatchError::Module(sp_runtime::ModuleError {
296						index: 31,
297						error: [2, 0, 0, 0,],
298						message: Some("Filtered",),
299					},),)
300				);
301
302				// check balances
303				assert_eq!(
304					<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
305					target_account_balance_before_teleport
306				);
307				if let Some((checking_account, initial_checking_account)) = checking_account.as_ref() {
308					assert_eq!(
309						<pallet_balances::Pallet<Runtime>>::free_balance(checking_account),
310						*initial_checking_account - native_asset_amount_received + native_asset_to_teleport_away
311					);
312				}
313			}
314		})
315}
316
317#[macro_export]
318macro_rules! include_teleports_for_native_asset_works(
319	(
320		$runtime:path,
321		$all_pallets_without_system:path,
322		$xcm_config:path,
323		$checking_account:ty,
324		$weight_to_fee:path,
325		$hrmp_channel_opener:path,
326		$collator_session_key:expr,
327		$slot_durations:expr,
328		$existential_deposit:expr,
329		$unwrap_pallet_xcm_event:expr,
330		$runtime_para_id:expr
331	) => {
332		#[test]
333		fn teleports_for_native_asset_works() {
334			const BOB: [u8; 32] = [2u8; 32];
335			let target_account = parachains_common::AccountId::from(BOB);
336
337			$crate::test_cases::teleports_for_native_asset_works::<
338				$runtime,
339				$all_pallets_without_system,
340				$xcm_config,
341				$checking_account,
342				$weight_to_fee,
343				$hrmp_channel_opener
344			>(
345				$collator_session_key,
346				$slot_durations,
347				$existential_deposit,
348				target_account,
349				$unwrap_pallet_xcm_event,
350				$runtime_para_id
351			)
352		}
353	}
354);
355
356/// Test-case makes sure that `Runtime` can receive teleported assets from sibling parachain, and
357/// can teleport it back
358pub fn teleports_for_foreign_assets_works<
359	Runtime,
360	AllPalletsWithoutSystem,
361	XcmConfig,
362	CheckingAccount,
363	WeightToFee,
364	HrmpChannelOpener,
365	SovereignAccountOf,
366	ForeignAssetsPalletInstance,
367>(
368	collator_session_keys: CollatorSessionKeys<Runtime>,
369	slot_durations: SlotDurations,
370	target_account: AccountIdOf<Runtime>,
371	existential_deposit: BalanceOf<Runtime>,
372	asset_owner: AccountIdOf<Runtime>,
373	unwrap_pallet_xcm_event: Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
374	unwrap_xcmp_queue_event: Box<
375		dyn Fn(Vec<u8>) -> Option<cumulus_pallet_xcmp_queue::Event<Runtime>>,
376	>,
377) where
378	Runtime: frame_system::Config
379		+ pallet_balances::Config
380		+ pallet_session::Config
381		+ pallet_xcm::Config
382		+ parachain_info::Config
383		+ pallet_collator_selection::Config
384		+ cumulus_pallet_parachain_system::Config
385		+ cumulus_pallet_xcmp_queue::Config
386		+ pallet_assets::Config<ForeignAssetsPalletInstance, ReserveData = ForeignAssetReserveData>
387		+ pallet_timestamp::Config,
388	AllPalletsWithoutSystem:
389		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
390	AccountIdOf<Runtime>: Into<[u8; 32]>,
391	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
392	BalanceOf<Runtime>: From<Balance>,
393	XcmConfig: xcm_executor::Config,
394	CheckingAccount: Get<AccountIdOf<Runtime>>,
395	HrmpChannelOpener: frame_support::inherent::ProvideInherent<
396		Call = cumulus_pallet_parachain_system::Call<Runtime>,
397	>,
398	WeightToFee: frame_support::weights::WeightToFee<Balance = Balance>,
399	<WeightToFee as frame_support::weights::WeightToFee>::Balance: From<u128> + Into<u128>,
400	SovereignAccountOf: ConvertLocation<AccountIdOf<Runtime>>,
401	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
402		From<xcm::v5::Location> + Into<xcm::v5::Location>,
403	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetIdParameter:
404		From<xcm::v5::Location> + Into<xcm::v5::Location>,
405	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::Balance:
406		From<Balance> + Into<u128>,
407	<Runtime as frame_system::Config>::AccountId:
408		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
409	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
410		From<<Runtime as frame_system::Config>::AccountId>,
411	<Runtime as frame_system::Config>::AccountId: From<AccountId>,
412	ForeignAssetsPalletInstance: 'static,
413{
414	// foreign parachain with the same consensus currency as asset
415	let foreign_para_id = 2222;
416	let foreign_asset_id_location = xcm::v5::Location {
417		parents: 1,
418		interior: [
419			xcm::v5::Junction::Parachain(foreign_para_id),
420			xcm::v5::Junction::GeneralIndex(1234567),
421		]
422		.into(),
423	};
424	let foreign_asset_reserve_data = ForeignAssetReserveData {
425		reserve: xcm::v5::Location {
426			parents: 1,
427			interior: [xcm::v5::Junction::Parachain(foreign_para_id)].into(),
428		},
429		teleportable: true,
430	};
431
432	// foreign creator, which can be sibling parachain to match ForeignCreators
433	let foreign_creator = Location { parents: 1, interior: [Parachain(foreign_para_id)].into() };
434	let foreign_creator_as_account_id =
435		SovereignAccountOf::convert_location(&foreign_creator).expect("");
436
437	// we want to buy execution with local relay chain currency
438	let buy_execution_fee_amount =
439		WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0));
440	let buy_execution_fee =
441		Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) };
442
443	let teleported_foreign_asset_amount = 10_000_000_000_000;
444	let runtime_para_id = 1000;
445	ExtBuilder::<Runtime>::default()
446		.with_collators(collator_session_keys.collators())
447		.with_session_keys(collator_session_keys.session_keys())
448		.with_balances(vec![
449			(
450				foreign_creator_as_account_id,
451				existential_deposit + (buy_execution_fee_amount * 2).into(),
452			),
453			(target_account.clone(), existential_deposit),
454			(CheckingAccount::get(), existential_deposit),
455		])
456		.with_safe_xcm_version(XCM_VERSION)
457		.with_para_id(runtime_para_id.into())
458		.with_tracing()
459		.build()
460		.execute_with(|| {
461			let mut alice = [0u8; 32];
462			alice[0] = 1;
463
464			let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
465				2,
466				AccountId::from(alice).into(),
467			);
468			// checks target_account before
469			assert_eq!(
470				<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
471				existential_deposit
472			);
473			// check `CheckingAccount` before
474			assert_eq!(
475				<pallet_balances::Pallet<Runtime>>::free_balance(&CheckingAccount::get()),
476				existential_deposit
477			);
478			assert_eq!(
479				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
480					foreign_asset_id_location.clone().into(),
481					&target_account
482				),
483				0.into()
484			);
485			assert_eq!(
486				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
487					foreign_asset_id_location.clone().into(),
488					&CheckingAccount::get()
489				),
490				0.into()
491			);
492			// check totals before
493			assert_total::<
494				pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>,
495				AccountIdOf<Runtime>,
496			>(foreign_asset_id_location.clone(), 0, 0);
497
498			// create foreign asset (0 total issuance)
499			let asset_minimum_asset_balance = 3333333_u128;
500			assert_ok!(
501				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::force_create(
502					RuntimeHelper::<Runtime>::root_origin(),
503					foreign_asset_id_location.clone().into(),
504					asset_owner.clone().into(),
505					false,
506					asset_minimum_asset_balance.into()
507				)
508			);
509			assert_total::<
510				pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>,
511				AccountIdOf<Runtime>,
512			>(foreign_asset_id_location.clone(), 0, 0);
513			assert!(teleported_foreign_asset_amount > asset_minimum_asset_balance);
514			// mark the foreign asset as teleportable
515			assert_ok!(
516				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::set_reserves(
517					RuntimeHelper::<Runtime>::origin_of(asset_owner.into()),
518					foreign_asset_id_location.clone().into(),
519					vec![foreign_asset_reserve_data],
520				)
521			);
522
523			// 1. process received teleported assets from sibling parachain (foreign_para_id)
524			let xcm = Xcm(vec![
525				// BuyExecution with relaychain native token
526				WithdrawAsset(buy_execution_fee.clone().into()),
527				BuyExecution {
528					fees: Asset {
529						id: AssetId(Location::parent()),
530						fun: Fungible(buy_execution_fee_amount),
531					},
532					weight_limit: Unlimited,
533				},
534				// Process teleported asset
535				ReceiveTeleportedAsset(Assets::from(vec![Asset {
536					id: AssetId(foreign_asset_id_location.clone()),
537					fun: Fungible(teleported_foreign_asset_amount),
538				}])),
539				DepositAsset {
540					assets: Wild(AllOf {
541						id: AssetId(foreign_asset_id_location.clone()),
542						fun: WildFungibility::Fungible,
543					}),
544					beneficiary: Location {
545						parents: 0,
546						interior: [AccountId32 {
547							network: None,
548							id: target_account.clone().into(),
549						}]
550						.into(),
551					},
552				},
553				ExpectTransactStatus(MaybeErrorCode::Success),
554			]);
555			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
556
557			let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
558				foreign_creator,
559				xcm,
560				&mut hash,
561				RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Sibling),
562				Weight::zero(),
563			);
564			assert_ok!(outcome.ensure_complete());
565
566			// checks target_account after
567			assert_eq!(
568				<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
569				existential_deposit
570			);
571			assert_eq!(
572				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
573					foreign_asset_id_location.clone().into(),
574					&target_account
575				),
576				teleported_foreign_asset_amount.into()
577			);
578			// checks `CheckingAccount` after
579			assert_eq!(
580				<pallet_balances::Pallet<Runtime>>::free_balance(&CheckingAccount::get()),
581				existential_deposit
582			);
583			assert_eq!(
584				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
585					foreign_asset_id_location.clone().into(),
586					&CheckingAccount::get()
587				),
588				0.into()
589			);
590			// check total after (twice: target_account + CheckingAccount)
591			assert_total::<
592				pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>,
593				AccountIdOf<Runtime>,
594			>(
595				foreign_asset_id_location.clone(),
596				teleported_foreign_asset_amount,
597				teleported_foreign_asset_amount,
598			);
599
600			// 2. try to teleport asset back to source parachain (foreign_para_id)
601			{
602				let dest = Location::new(1, [Parachain(foreign_para_id)]);
603				let mut dest_beneficiary = Location::new(1, [Parachain(foreign_para_id)])
604					.appended_with(AccountId32 {
605						network: None,
606						id: sp_runtime::AccountId32::new([3; 32]).into(),
607					})
608					.unwrap();
609				dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap();
610
611				let target_account_balance_before_teleport =
612					<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
613						foreign_asset_id_location.clone().into(),
614						&target_account,
615					);
616				let asset_to_teleport_away = asset_minimum_asset_balance * 3;
617				assert!(
618					asset_to_teleport_away <
619						(target_account_balance_before_teleport -
620							asset_minimum_asset_balance.into())
621						.into()
622				);
623
624				// Make sure the target account has enough native asset to pay for delivery fees
625				let delivery_fees =
626					xcm_helpers::teleport_assets_delivery_fees::<XcmConfig::XcmSender>(
627						(foreign_asset_id_location.clone(), asset_to_teleport_away).into(),
628						0,
629						Unlimited,
630						dest_beneficiary.clone(),
631						dest.clone(),
632					);
633				<pallet_balances::Pallet<Runtime>>::mint_into(
634					&target_account,
635					delivery_fees.into(),
636				)
637				.unwrap();
638
639				assert_ok!(RuntimeHelper::<Runtime>::do_teleport_assets::<HrmpChannelOpener>(
640					RuntimeHelper::<Runtime>::origin_of(target_account.clone()),
641					dest,
642					dest_beneficiary,
643					(foreign_asset_id_location.clone(), asset_to_teleport_away),
644					Some((runtime_para_id, foreign_para_id)),
645					included_head,
646					&alice,
647					&slot_durations,
648				));
649
650				// check balances
651				assert_eq!(
652					<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
653						foreign_asset_id_location.clone().into(),
654						&target_account
655					),
656					(target_account_balance_before_teleport - asset_to_teleport_away.into())
657				);
658				assert_eq!(
659					<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
660						foreign_asset_id_location.clone().into(),
661						&CheckingAccount::get()
662					),
663					0.into()
664				);
665				// check total after (twice: target_account + CheckingAccount)
666				assert_total::<
667					pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>,
668					AccountIdOf<Runtime>,
669				>(
670					foreign_asset_id_location.clone(),
671					teleported_foreign_asset_amount - asset_to_teleport_away,
672					teleported_foreign_asset_amount - asset_to_teleport_away,
673				);
674
675				// check events
676				RuntimeHelper::<Runtime>::assert_pallet_xcm_event_outcome(
677					&unwrap_pallet_xcm_event,
678					|outcome| {
679						assert_ok!(outcome.ensure_complete());
680					},
681				);
682				assert!(RuntimeHelper::<Runtime>::xcmp_queue_message_sent(unwrap_xcmp_queue_event)
683					.is_some());
684			}
685		})
686}
687
688#[macro_export]
689macro_rules! include_teleports_for_foreign_assets_works(
690	(
691		$runtime:path,
692		$all_pallets_without_system:path,
693		$xcm_config:path,
694		$checking_account:path,
695		$weight_to_fee:path,
696		$hrmp_channel_opener:path,
697		$sovereign_account_of:path,
698		$assets_pallet_instance:path,
699		$collator_session_key:expr,
700		$slot_durations:expr,
701		$existential_deposit:expr,
702		$unwrap_pallet_xcm_event:expr,
703		$unwrap_xcmp_queue_event:expr
704	) => {
705		#[test]
706		fn teleports_for_foreign_assets_works() {
707			const BOB: [u8; 32] = [2u8; 32];
708			let target_account = parachains_common::AccountId::from(BOB);
709			const SOME_ASSET_OWNER: [u8; 32] = [5u8; 32];
710			let asset_owner = parachains_common::AccountId::from(SOME_ASSET_OWNER);
711
712			$crate::test_cases::teleports_for_foreign_assets_works::<
713				$runtime,
714				$all_pallets_without_system,
715				$xcm_config,
716				$checking_account,
717				$weight_to_fee,
718				$hrmp_channel_opener,
719				$sovereign_account_of,
720				$assets_pallet_instance
721			>(
722				$collator_session_key,
723				$slot_durations,
724				target_account,
725				$existential_deposit,
726				asset_owner,
727				$unwrap_pallet_xcm_event,
728				$unwrap_xcmp_queue_event
729			)
730		}
731	}
732);
733
734/// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain
735/// currency
736pub fn asset_transactor_transfer_with_local_consensus_currency_works<Runtime, XcmConfig>(
737	collator_session_keys: CollatorSessionKeys<Runtime>,
738	source_account: AccountIdOf<Runtime>,
739	target_account: AccountIdOf<Runtime>,
740	existential_deposit: BalanceOf<Runtime>,
741	additional_checks_before: Box<dyn Fn()>,
742	additional_checks_after: Box<dyn Fn()>,
743) where
744	Runtime: frame_system::Config
745		+ pallet_balances::Config
746		+ pallet_session::Config
747		+ pallet_xcm::Config
748		+ parachain_info::Config
749		+ pallet_collator_selection::Config
750		+ cumulus_pallet_parachain_system::Config
751		+ pallet_timestamp::Config,
752	AccountIdOf<Runtime>: Into<[u8; 32]>,
753	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
754	BalanceOf<Runtime>: From<Balance>,
755	XcmConfig: xcm_executor::Config,
756	<Runtime as pallet_balances::Config>::Balance: From<Balance> + Into<u128>,
757	<Runtime as frame_system::Config>::AccountId:
758		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
759	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
760		From<<Runtime as frame_system::Config>::AccountId>,
761{
762	let unit = existential_deposit;
763
764	ExtBuilder::<Runtime>::default()
765		.with_collators(collator_session_keys.collators())
766		.with_session_keys(collator_session_keys.session_keys())
767		.with_balances(vec![(source_account.clone(), (BalanceOf::<Runtime>::from(10_u128) * unit))])
768		.with_tracing()
769		.build()
770		.execute_with(|| {
771			// check Balances before
772			assert_eq!(
773				<pallet_balances::Pallet<Runtime>>::free_balance(&source_account),
774				(BalanceOf::<Runtime>::from(10_u128) * unit)
775			);
776			assert_eq!(
777				<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
778				(BalanceOf::<Runtime>::zero() * unit)
779			);
780
781			// additional check before
782			additional_checks_before();
783
784			// transfer_asset (deposit/withdraw) ALICE -> BOB
785			let _ = RuntimeHelper::<XcmConfig>::do_transfer(
786				Location {
787					parents: 0,
788					interior: [AccountId32 { network: None, id: source_account.clone().into() }]
789						.into(),
790				},
791				Location {
792					parents: 0,
793					interior: [AccountId32 { network: None, id: target_account.clone().into() }]
794						.into(),
795				},
796				// local_consensus_currency_asset, e.g.: relaychain token (KSM, DOT, ...)
797				(
798					Location { parents: 1, interior: Here },
799					(BalanceOf::<Runtime>::from(1_u128) * unit).into(),
800				),
801			)
802			.expect("no error");
803
804			// check Balances after
805			assert_eq!(
806				<pallet_balances::Pallet<Runtime>>::free_balance(source_account),
807				(BalanceOf::<Runtime>::from(9_u128) * unit)
808			);
809			assert_eq!(
810				<pallet_balances::Pallet<Runtime>>::free_balance(target_account),
811				(BalanceOf::<Runtime>::from(1_u128) * unit)
812			);
813
814			additional_checks_after();
815		})
816}
817
818#[macro_export]
819macro_rules! include_asset_transactor_transfer_with_local_consensus_currency_works(
820	(
821		$runtime:path,
822		$xcm_config:path,
823		$collator_session_key:expr,
824		$existential_deposit:expr,
825		$additional_checks_before:expr,
826		$additional_checks_after:expr
827	) => {
828		#[test]
829		fn asset_transactor_transfer_with_local_consensus_currency_works() {
830			const ALICE: [u8; 32] = [1u8; 32];
831			let source_account = parachains_common::AccountId::from(ALICE);
832			const BOB: [u8; 32] = [2u8; 32];
833			let target_account = parachains_common::AccountId::from(BOB);
834
835			$crate::test_cases::asset_transactor_transfer_with_local_consensus_currency_works::<
836				$runtime,
837				$xcm_config
838			>(
839				$collator_session_key,
840				source_account,
841				target_account,
842				$existential_deposit,
843				$additional_checks_before,
844				$additional_checks_after
845			)
846		}
847	}
848);
849
850/// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain
851/// currency
852pub fn asset_transactor_transfer_with_pallet_assets_instance_works<
853	Runtime,
854	XcmConfig,
855	AssetsPalletInstance,
856	AssetId,
857	AssetIdConverter,
858>(
859	collator_session_keys: CollatorSessionKeys<Runtime>,
860	existential_deposit: BalanceOf<Runtime>,
861	asset_id: AssetId,
862	asset_owner: AccountIdOf<Runtime>,
863	alice_account: AccountIdOf<Runtime>,
864	bob_account: AccountIdOf<Runtime>,
865	charlie_account: AccountIdOf<Runtime>,
866	additional_checks_before: Box<dyn Fn()>,
867	additional_checks_after: Box<dyn Fn()>,
868) where
869	Runtime: frame_system::Config
870		+ pallet_balances::Config
871		+ pallet_session::Config
872		+ pallet_xcm::Config
873		+ parachain_info::Config
874		+ pallet_collator_selection::Config
875		+ cumulus_pallet_parachain_system::Config
876		+ pallet_assets::Config<AssetsPalletInstance>
877		+ pallet_timestamp::Config,
878	AccountIdOf<Runtime>: Into<[u8; 32]>,
879	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
880	BalanceOf<Runtime>: From<Balance>,
881	XcmConfig: xcm_executor::Config,
882	<Runtime as pallet_assets::Config<AssetsPalletInstance>>::AssetId:
883		From<AssetId> + Into<AssetId>,
884	<Runtime as pallet_assets::Config<AssetsPalletInstance>>::AssetIdParameter:
885		From<AssetId> + Into<AssetId>,
886	<Runtime as pallet_assets::Config<AssetsPalletInstance>>::Balance: From<Balance> + Into<u128>,
887	<Runtime as frame_system::Config>::AccountId:
888		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
889	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
890		From<<Runtime as frame_system::Config>::AccountId>,
891	AssetsPalletInstance: 'static,
892	AssetId: Clone,
893	AssetIdConverter: MaybeEquivalence<Location, AssetId>,
894{
895	ExtBuilder::<Runtime>::default()
896		.with_collators(collator_session_keys.collators())
897		.with_session_keys(collator_session_keys.session_keys())
898		.with_balances(vec![
899			(asset_owner.clone(), existential_deposit),
900			(alice_account.clone(), existential_deposit),
901			(bob_account.clone(), existential_deposit),
902		])
903		.with_tracing()
904		.build()
905		.execute_with(|| {
906			// create  some asset class
907			let asset_minimum_asset_balance = 3333333_u128;
908			let asset_id_as_location = AssetIdConverter::convert_back(&asset_id).unwrap();
909			assert_ok!(<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::force_create(
910				RuntimeHelper::<Runtime>::root_origin(),
911				asset_id.clone().into(),
912				asset_owner.clone().into(),
913				false,
914				asset_minimum_asset_balance.into()
915			));
916
917			// We first mint enough asset for the account to exist for assets
918			assert_ok!(<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::mint(
919				RuntimeHelper::<Runtime>::origin_of(asset_owner.clone()),
920				asset_id.clone().into(),
921				alice_account.clone().into(),
922				(6 * asset_minimum_asset_balance).into()
923			));
924
925			// check Assets before
926			assert_eq!(
927				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
928					asset_id.clone().into(),
929					&alice_account
930				),
931				(6 * asset_minimum_asset_balance).into()
932			);
933			assert_eq!(
934				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
935					asset_id.clone().into(),
936					&bob_account
937				),
938				0.into()
939			);
940			assert_eq!(
941				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
942					asset_id.clone().into(),
943					&charlie_account
944				),
945				0.into()
946			);
947			assert_eq!(
948				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
949					asset_id.clone().into(),
950					&asset_owner
951				),
952				0.into()
953			);
954			assert_eq!(
955				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account),
956				existential_deposit
957			);
958			assert_eq!(
959				<pallet_balances::Pallet<Runtime>>::free_balance(&bob_account),
960				existential_deposit
961			);
962			assert_eq!(
963				<pallet_balances::Pallet<Runtime>>::free_balance(&charlie_account),
964				0.into()
965			);
966			assert_eq!(
967				<pallet_balances::Pallet<Runtime>>::free_balance(&asset_owner),
968				existential_deposit
969			);
970			additional_checks_before();
971
972			// transfer_asset (deposit/withdraw) ALICE -> CHARLIE (not ok - Charlie does not have
973			// ExistentialDeposit)
974			assert_noop!(
975				RuntimeHelper::<XcmConfig>::do_transfer(
976					Location {
977						parents: 0,
978						interior: [AccountId32 { network: None, id: alice_account.clone().into() }]
979							.into(),
980					},
981					Location {
982						parents: 0,
983						interior: [AccountId32 {
984							network: None,
985							id: charlie_account.clone().into()
986						}]
987						.into(),
988					},
989					(asset_id_as_location.clone(), asset_minimum_asset_balance),
990				),
991				XcmError::FailedToTransactAsset(Into::<&str>::into(
992					sp_runtime::TokenError::CannotCreate
993				))
994			);
995
996			// transfer_asset (deposit/withdraw) ALICE -> BOB (ok - has ExistentialDeposit)
997			assert!(matches!(
998				RuntimeHelper::<XcmConfig>::do_transfer(
999					Location {
1000						parents: 0,
1001						interior: [AccountId32 { network: None, id: alice_account.clone().into() }]
1002							.into(),
1003					},
1004					Location {
1005						parents: 0,
1006						interior: [AccountId32 { network: None, id: bob_account.clone().into() }]
1007							.into(),
1008					},
1009					(asset_id_as_location, asset_minimum_asset_balance),
1010				),
1011				Ok(_)
1012			));
1013
1014			// check Assets after
1015			assert_eq!(
1016				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
1017					asset_id.clone().into(),
1018					&alice_account
1019				),
1020				(5 * asset_minimum_asset_balance).into()
1021			);
1022			assert_eq!(
1023				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
1024					asset_id.clone().into(),
1025					&bob_account
1026				),
1027				asset_minimum_asset_balance.into()
1028			);
1029			assert_eq!(
1030				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
1031					asset_id.clone().into(),
1032					&charlie_account
1033				),
1034				0.into()
1035			);
1036			assert_eq!(
1037				<pallet_assets::Pallet<Runtime, AssetsPalletInstance>>::balance(
1038					asset_id.into(),
1039					&asset_owner
1040				),
1041				0.into()
1042			);
1043			assert_eq!(
1044				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account),
1045				existential_deposit
1046			);
1047			assert_eq!(
1048				<pallet_balances::Pallet<Runtime>>::free_balance(&bob_account),
1049				existential_deposit
1050			);
1051			assert_eq!(
1052				<pallet_balances::Pallet<Runtime>>::free_balance(&charlie_account),
1053				0.into()
1054			);
1055			assert_eq!(
1056				<pallet_balances::Pallet<Runtime>>::free_balance(&asset_owner),
1057				existential_deposit
1058			);
1059
1060			additional_checks_after();
1061		})
1062}
1063
1064#[macro_export]
1065macro_rules! include_asset_transactor_transfer_with_pallet_assets_instance_works(
1066	(
1067		$test_name:tt,
1068		$runtime:path,
1069		$xcm_config:path,
1070		$assets_pallet_instance:path,
1071		$asset_id:path,
1072		$asset_id_converter:path,
1073		$collator_session_key:expr,
1074		$existential_deposit:expr,
1075		$tested_asset_id:expr,
1076		$additional_checks_before:expr,
1077		$additional_checks_after:expr
1078	) => {
1079		#[test]
1080		fn $test_name() {
1081			const SOME_ASSET_OWNER: [u8; 32] = [5u8; 32];
1082			let asset_owner = parachains_common::AccountId::from(SOME_ASSET_OWNER);
1083			const ALICE: [u8; 32] = [1u8; 32];
1084			let alice_account = parachains_common::AccountId::from(ALICE);
1085			const BOB: [u8; 32] = [2u8; 32];
1086			let bob_account = parachains_common::AccountId::from(BOB);
1087			const CHARLIE: [u8; 32] = [3u8; 32];
1088			let charlie_account = parachains_common::AccountId::from(CHARLIE);
1089
1090			$crate::test_cases::asset_transactor_transfer_with_pallet_assets_instance_works::<
1091				$runtime,
1092				$xcm_config,
1093				$assets_pallet_instance,
1094				$asset_id,
1095				$asset_id_converter
1096			>(
1097				$collator_session_key,
1098				$existential_deposit,
1099				$tested_asset_id,
1100				asset_owner,
1101				alice_account,
1102				bob_account,
1103				charlie_account,
1104				$additional_checks_before,
1105				$additional_checks_after
1106			)
1107		}
1108	}
1109);
1110
1111/// Test-case makes sure that `Runtime` can create and manage `ForeignAssets`
1112pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works<
1113	Runtime,
1114	XcmConfig,
1115	WeightToFee,
1116	SovereignAccountOf,
1117	ForeignAssetsPalletInstance,
1118	AssetId,
1119	AssetIdConverter,
1120>(
1121	collator_session_keys: CollatorSessionKeys<Runtime>,
1122	existential_deposit: BalanceOf<Runtime>,
1123	asset_deposit: BalanceOf<Runtime>,
1124	metadata_deposit_base: BalanceOf<Runtime>,
1125	metadata_deposit_per_byte: BalanceOf<Runtime>,
1126	alice_account: AccountIdOf<Runtime>,
1127	bob_account: AccountIdOf<Runtime>,
1128	runtime_call_encode: Box<
1129		dyn Fn(pallet_assets::Call<Runtime, ForeignAssetsPalletInstance>) -> Vec<u8>,
1130	>,
1131	unwrap_pallet_assets_event: Box<
1132		dyn Fn(Vec<u8>) -> Option<pallet_assets::Event<Runtime, ForeignAssetsPalletInstance>>,
1133	>,
1134	additional_checks_before: Box<dyn Fn()>,
1135	additional_checks_after: Box<dyn Fn()>,
1136) where
1137	Runtime: frame_system::Config
1138		+ pallet_balances::Config
1139		+ pallet_session::Config
1140		+ pallet_xcm::Config
1141		+ parachain_info::Config
1142		+ pallet_collator_selection::Config
1143		+ cumulus_pallet_parachain_system::Config
1144		+ pallet_assets::Config<ForeignAssetsPalletInstance>
1145		+ pallet_timestamp::Config,
1146	AccountIdOf<Runtime>: Into<[u8; 32]>,
1147	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
1148	BalanceOf<Runtime>: From<Balance>,
1149	XcmConfig: xcm_executor::Config,
1150	WeightToFee: frame_support::weights::WeightToFee<Balance = Balance>,
1151	<WeightToFee as frame_support::weights::WeightToFee>::Balance: From<u128> + Into<u128>,
1152	SovereignAccountOf: ConvertLocation<AccountIdOf<Runtime>>,
1153	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
1154		From<AssetId> + Into<AssetId>,
1155	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetIdParameter:
1156		From<AssetId> + Into<AssetId>,
1157	<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::Balance:
1158		From<Balance> + Into<u128>,
1159	<Runtime as frame_system::Config>::AccountId:
1160		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
1161	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
1162		From<<Runtime as frame_system::Config>::AccountId>,
1163	ForeignAssetsPalletInstance: 'static,
1164	AssetId: Clone,
1165	AssetIdConverter: MaybeEquivalence<Location, AssetId>,
1166{
1167	// foreign parachain with the same consensus currency as asset
1168	let foreign_asset_id_location = Location::new(1, [Parachain(2222), GeneralIndex(1234567)]);
1169	let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap();
1170
1171	// foreign creator, which can be sibling parachain to match ForeignCreators
1172	let foreign_creator = Location { parents: 1, interior: [Parachain(2222)].into() };
1173	let foreign_creator_as_account_id =
1174		SovereignAccountOf::convert_location(&foreign_creator).expect("");
1175
1176	// we want to buy execution with local relay chain currency
1177	let buy_execution_fee_amount =
1178		WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0));
1179	let buy_execution_fee =
1180		Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) };
1181
1182	const ASSET_NAME: &str = "My super coin";
1183	const ASSET_SYMBOL: &str = "MY_S_COIN";
1184	let metadata_deposit_per_byte_eta = metadata_deposit_per_byte
1185		.saturating_mul(((ASSET_NAME.len() + ASSET_SYMBOL.len()) as u128).into());
1186
1187	ExtBuilder::<Runtime>::default()
1188		.with_collators(collator_session_keys.collators())
1189		.with_session_keys(collator_session_keys.session_keys())
1190		.with_balances(vec![(
1191			foreign_creator_as_account_id.clone(),
1192			existential_deposit +
1193				asset_deposit +
1194				metadata_deposit_base +
1195				metadata_deposit_per_byte_eta +
1196				buy_execution_fee_amount.into() +
1197				buy_execution_fee_amount.into(),
1198		)])
1199		.with_tracing()
1200		.build()
1201		.execute_with(|| {
1202			assert!(<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::asset_ids()
1203				.collect::<Vec<_>>()
1204				.is_empty());
1205			assert_eq!(
1206				<pallet_balances::Pallet<Runtime>>::free_balance(&foreign_creator_as_account_id),
1207				existential_deposit +
1208					asset_deposit + metadata_deposit_base +
1209					metadata_deposit_per_byte_eta +
1210					buy_execution_fee_amount.into() +
1211					buy_execution_fee_amount.into()
1212			);
1213			additional_checks_before();
1214
1215			// execute XCM with Transacts to create/manage foreign assets by foreign governance
1216			// prepare data for xcm::Transact(create)
1217			let foreign_asset_create = runtime_call_encode(pallet_assets::Call::<
1218				Runtime,
1219				ForeignAssetsPalletInstance,
1220			>::create {
1221				id: asset_id.clone().into(),
1222				// admin as sovereign_account
1223				admin: foreign_creator_as_account_id.clone().into(),
1224				min_balance: 1.into(),
1225			});
1226			// prepare data for xcm::Transact(set_metadata)
1227			let foreign_asset_set_metadata = runtime_call_encode(pallet_assets::Call::<
1228				Runtime,
1229				ForeignAssetsPalletInstance,
1230			>::set_metadata {
1231				id: asset_id.clone().into(),
1232				name: Vec::from(ASSET_NAME),
1233				symbol: Vec::from(ASSET_SYMBOL),
1234				decimals: 12,
1235			});
1236			// prepare data for xcm::Transact(set_team - change just freezer to Bob)
1237			let foreign_asset_set_team = runtime_call_encode(pallet_assets::Call::<
1238				Runtime,
1239				ForeignAssetsPalletInstance,
1240			>::set_team {
1241				id: asset_id.clone().into(),
1242				issuer: foreign_creator_as_account_id.clone().into(),
1243				admin: foreign_creator_as_account_id.clone().into(),
1244				freezer: bob_account.clone().into(),
1245			});
1246
1247			// lets simulate this was triggered by relay chain from local consensus sibling
1248			// parachain
1249			let xcm = Xcm(vec![
1250				WithdrawAsset(buy_execution_fee.clone().into()),
1251				BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
1252				Transact {
1253					origin_kind: OriginKind::Xcm,
1254					call: foreign_asset_create.into(),
1255					fallback_max_weight: None,
1256				},
1257				Transact {
1258					origin_kind: OriginKind::SovereignAccount,
1259					call: foreign_asset_set_metadata.into(),
1260					fallback_max_weight: None,
1261				},
1262				Transact {
1263					origin_kind: OriginKind::SovereignAccount,
1264					call: foreign_asset_set_team.into(),
1265					fallback_max_weight: None,
1266				},
1267				ExpectTransactStatus(MaybeErrorCode::Success),
1268			]);
1269
1270			// messages with different consensus should go through the local bridge-hub
1271			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
1272
1273			// execute xcm as XcmpQueue would do
1274			let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
1275				foreign_creator.clone(),
1276				xcm,
1277				&mut hash,
1278				RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Sibling),
1279				Weight::zero(),
1280			);
1281			assert_ok!(outcome.ensure_complete());
1282
1283			// check events
1284			let mut events = <frame_system::Pallet<Runtime>>::events()
1285				.into_iter()
1286				.filter_map(|e| unwrap_pallet_assets_event(e.event.encode()));
1287			assert!(events.any(|e| matches!(e, pallet_assets::Event::Created { .. })));
1288			assert!(events.any(|e| matches!(e, pallet_assets::Event::MetadataSet { .. })));
1289			assert!(events.any(|e| matches!(e, pallet_assets::Event::TeamChanged { .. })));
1290
1291			// check assets after
1292			assert!(!<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::asset_ids()
1293				.collect::<Vec<_>>()
1294				.is_empty());
1295
1296			// check update metadata
1297			use frame_support::traits::fungibles::roles::Inspect as InspectRoles;
1298			assert_eq!(
1299				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::owner(
1300					asset_id.clone().into()
1301				),
1302				Some(foreign_creator_as_account_id.clone())
1303			);
1304			assert_eq!(
1305				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::admin(
1306					asset_id.clone().into()
1307				),
1308				Some(foreign_creator_as_account_id.clone())
1309			);
1310			assert_eq!(
1311				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::issuer(
1312					asset_id.clone().into()
1313				),
1314				Some(foreign_creator_as_account_id.clone())
1315			);
1316			assert_eq!(
1317				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::freezer(
1318					asset_id.clone().into()
1319				),
1320				Some(bob_account.clone())
1321			);
1322			assert!(
1323				<pallet_balances::Pallet<Runtime>>::free_balance(&foreign_creator_as_account_id) >=
1324					existential_deposit + buy_execution_fee_amount.into(),
1325				"Free balance: {:?} should be ge {:?}",
1326				<pallet_balances::Pallet<Runtime>>::free_balance(&foreign_creator_as_account_id),
1327				existential_deposit + buy_execution_fee_amount.into()
1328			);
1329			assert_metadata::<
1330				pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>,
1331				AccountIdOf<Runtime>,
1332			>(asset_id.clone(), ASSET_NAME, ASSET_SYMBOL, 12);
1333
1334			// check if changed freezer, can freeze
1335			assert_noop!(
1336				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::freeze(
1337					RuntimeHelper::<Runtime>::origin_of(bob_account),
1338					asset_id.clone().into(),
1339					alice_account.clone().into()
1340				),
1341				pallet_assets::Error::<Runtime, ForeignAssetsPalletInstance>::NoAccount
1342			);
1343			assert_noop!(
1344				<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::freeze(
1345					RuntimeHelper::<Runtime>::origin_of(foreign_creator_as_account_id.clone()),
1346					asset_id.into(),
1347					alice_account.into()
1348				),
1349				pallet_assets::Error::<Runtime, ForeignAssetsPalletInstance>::NoPermission
1350			);
1351
1352			// lets try create asset for different parachain(3333) (foreign_creator(2222) can create
1353			// just his assets)
1354			let foreign_asset_id_location =
1355				Location { parents: 1, interior: [Parachain(3333), GeneralIndex(1234567)].into() };
1356			let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap();
1357
1358			// prepare data for xcm::Transact(create)
1359			let foreign_asset_create = runtime_call_encode(pallet_assets::Call::<
1360				Runtime,
1361				ForeignAssetsPalletInstance,
1362			>::create {
1363				id: asset_id.into(),
1364				// admin as sovereign_account
1365				admin: foreign_creator_as_account_id.clone().into(),
1366				min_balance: 1.into(),
1367			});
1368			let xcm = Xcm(vec![
1369				WithdrawAsset(buy_execution_fee.clone().into()),
1370				BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
1371				Transact {
1372					origin_kind: OriginKind::Xcm,
1373					call: foreign_asset_create.into(),
1374					fallback_max_weight: None,
1375				},
1376				ExpectTransactStatus(MaybeErrorCode::from(DispatchError::BadOrigin.encode())),
1377			]);
1378
1379			// messages with different consensus should go through the local bridge-hub
1380			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
1381
1382			// execute xcm as XcmpQueue would do
1383			let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
1384				foreign_creator,
1385				xcm,
1386				&mut hash,
1387				RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Sibling),
1388				Weight::zero(),
1389			);
1390			assert_ok!(outcome.ensure_complete());
1391
1392			additional_checks_after();
1393		})
1394}
1395
1396#[macro_export]
1397macro_rules! include_create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works(
1398	(
1399		$runtime:path,
1400		$xcm_config:path,
1401		$weight_to_fee:path,
1402		$sovereign_account_of:path,
1403		$assets_pallet_instance:path,
1404		$asset_id:path,
1405		$asset_id_converter:path,
1406		$collator_session_key:expr,
1407		$existential_deposit:expr,
1408		$asset_deposit:expr,
1409		$metadata_deposit_base:expr,
1410		$metadata_deposit_per_byte:expr,
1411		$runtime_call_encode:expr,
1412		$unwrap_pallet_assets_event:expr,
1413		$additional_checks_before:expr,
1414		$additional_checks_after:expr
1415	) => {
1416		#[test]
1417		fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works() {
1418			const ALICE: [u8; 32] = [1u8; 32];
1419			let alice_account = parachains_common::AccountId::from(ALICE);
1420			const BOB: [u8; 32] = [2u8; 32];
1421			let bob_account = parachains_common::AccountId::from(BOB);
1422
1423			$crate::test_cases::create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works::<
1424				$runtime,
1425				$xcm_config,
1426				$weight_to_fee,
1427				$sovereign_account_of,
1428				$assets_pallet_instance,
1429				$asset_id,
1430				$asset_id_converter
1431			>(
1432				$collator_session_key,
1433				$existential_deposit,
1434				$asset_deposit,
1435				$metadata_deposit_base,
1436				$metadata_deposit_per_byte,
1437				alice_account,
1438				bob_account,
1439				$runtime_call_encode,
1440				$unwrap_pallet_assets_event,
1441				$additional_checks_before,
1442				$additional_checks_after
1443			)
1444		}
1445	}
1446);
1447
1448/// Test-case makes sure that `Runtime` can reserve-transfer asset to other parachains (where
1449/// teleport is not trusted)
1450pub fn reserve_transfer_native_asset_to_non_teleport_para_works<
1451	Runtime,
1452	AllPalletsWithoutSystem,
1453	XcmConfig,
1454	HrmpChannelOpener,
1455	HrmpChannelSource,
1456	LocationToAccountId,
1457>(
1458	collator_session_keys: CollatorSessionKeys<Runtime>,
1459	slot_durations: SlotDurations,
1460	existential_deposit: BalanceOf<Runtime>,
1461	alice_account: AccountIdOf<Runtime>,
1462	unwrap_pallet_xcm_event: Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
1463	unwrap_xcmp_queue_event: Box<
1464		dyn Fn(Vec<u8>) -> Option<cumulus_pallet_xcmp_queue::Event<Runtime>>,
1465	>,
1466	weight_limit: WeightLimit,
1467) where
1468	Runtime: frame_system::Config
1469		+ pallet_balances::Config
1470		+ pallet_session::Config
1471		+ pallet_xcm::Config
1472		+ parachain_info::Config
1473		+ pallet_collator_selection::Config
1474		+ cumulus_pallet_parachain_system::Config
1475		+ cumulus_pallet_xcmp_queue::Config
1476		+ pallet_timestamp::Config,
1477	AllPalletsWithoutSystem:
1478		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
1479	AccountIdOf<Runtime>: Into<[u8; 32]>,
1480	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
1481	BalanceOf<Runtime>: From<Balance>,
1482	<Runtime as pallet_balances::Config>::Balance: From<Balance> + Into<u128>,
1483	XcmConfig: xcm_executor::Config,
1484	LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
1485	<Runtime as frame_system::Config>::AccountId:
1486		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
1487	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
1488		From<<Runtime as frame_system::Config>::AccountId>,
1489	<Runtime as frame_system::Config>::AccountId: From<AccountId>,
1490	HrmpChannelOpener: frame_support::inherent::ProvideInherent<
1491		Call = cumulus_pallet_parachain_system::Call<Runtime>,
1492	>,
1493	HrmpChannelSource: XcmpMessageSource,
1494{
1495	let runtime_para_id = 1000;
1496	ExtBuilder::<Runtime>::default()
1497		.with_collators(collator_session_keys.collators())
1498		.with_session_keys(collator_session_keys.session_keys())
1499		.with_tracing()
1500		.with_safe_xcm_version(3)
1501		.with_para_id(runtime_para_id.into())
1502		.build()
1503		.execute_with(|| {
1504			let mut alice = [0u8; 32];
1505			alice[0] = 1;
1506			let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
1507				2,
1508				AccountId::from(alice).into(),
1509			);
1510
1511			// reserve-transfer native asset with local reserve to remote parachain (2345)
1512
1513			let other_para_id = 2345;
1514			let native_asset = Location::parent();
1515			let dest = Location::new(1, [Parachain(other_para_id)]);
1516			let mut dest_beneficiary = Location::new(1, [Parachain(other_para_id)])
1517				.appended_with(AccountId32 {
1518					network: None,
1519					id: sp_runtime::AccountId32::new([3; 32]).into(),
1520				})
1521				.unwrap();
1522			dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap();
1523
1524			let reserve_account = LocationToAccountId::convert_location(&dest)
1525				.expect("Sovereign account for reserves");
1526			let balance_to_transfer = 1_000_000_000_000_u128;
1527
1528			// open HRMP to other parachain
1529			mock_open_hrmp_channel::<Runtime, HrmpChannelOpener>(
1530				runtime_para_id.into(),
1531				other_para_id.into(),
1532				included_head,
1533				&alice,
1534				&slot_durations,
1535			);
1536
1537			// we calculate exact delivery fees _after_ sending the message by weighing the sent
1538			// xcm, and this delivery fee varies for different runtimes, so just add enough buffer,
1539			// then verify the arithmetics check out on final balance.
1540			let delivery_fees_buffer = 40_000_000_000u128;
1541			// drip 2xED + transfer_amount + delivery_fees_buffer to Alice account
1542			let alice_account_init_balance = existential_deposit.saturating_mul(2.into()) +
1543				balance_to_transfer.into() +
1544				delivery_fees_buffer.into();
1545			let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
1546				&alice_account,
1547				alice_account_init_balance,
1548			);
1549			// SA of target location needs to have at least ED, otherwise making reserve fails
1550			let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
1551				&reserve_account,
1552				existential_deposit,
1553			);
1554
1555			// we just check here, that user retains enough balance after withdrawal
1556			// and also we check if `balance_to_transfer` is more than `existential_deposit`,
1557			assert!(
1558				(<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account) -
1559					balance_to_transfer.into()) >=
1560					existential_deposit
1561			);
1562			// SA has just ED
1563			assert_eq!(
1564				<pallet_balances::Pallet<Runtime>>::free_balance(&reserve_account),
1565				existential_deposit
1566			);
1567
1568			// local native asset (pallet_balances)
1569			let asset_to_transfer =
1570				Asset { fun: Fungible(balance_to_transfer.into()), id: AssetId(native_asset) };
1571
1572			// pallet_xcm call reserve transfer
1573			assert_ok!(<pallet_xcm::Pallet<Runtime>>::transfer_assets_using_type_and_then(
1574				RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(alice_account.clone()),
1575				Box::new(dest.clone().into_versioned()),
1576				Box::new(VersionedAssets::from(Assets::from(asset_to_transfer))),
1577				Box::new(TransferType::LocalReserve),
1578				Box::new(VersionedAssetId::from(AssetId(Location::parent()))),
1579				Box::new(TransferType::LocalReserve),
1580				Box::new(VersionedXcm::from(
1581					Xcm::<()>::builder_unsafe()
1582						.deposit_asset(AllCounted(1), dest_beneficiary.clone())
1583						.build()
1584				)),
1585				weight_limit,
1586			));
1587
1588			// check events
1589			// check pallet_xcm attempted
1590			RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::assert_pallet_xcm_event_outcome(
1591				&unwrap_pallet_xcm_event,
1592				|outcome| {
1593					assert_ok!(outcome.ensure_complete());
1594				},
1595			);
1596
1597			// check that xcm was sent
1598			let xcm_sent_message_hash = <frame_system::Pallet<Runtime>>::events()
1599				.into_iter()
1600				.filter_map(|e| unwrap_xcmp_queue_event(e.event.encode()))
1601				.find_map(|e| match e {
1602					cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } =>
1603						Some(message_hash),
1604					_ => None,
1605				});
1606
1607			// read xcm
1608			let xcm_sent = RuntimeHelper::<HrmpChannelSource, AllPalletsWithoutSystem>::take_xcm(
1609				other_para_id.into(),
1610			)
1611			.unwrap();
1612
1613			let delivery_fees = get_fungible_delivery_fees::<
1614				<XcmConfig as xcm_executor::Config>::XcmSender,
1615			>(dest.clone(), Xcm::try_from(xcm_sent.clone()).unwrap());
1616
1617			assert_eq!(
1618				xcm_sent_message_hash,
1619				Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256))
1620			);
1621			let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm");
1622
1623			// check sent XCM Program to other parachain
1624			println!("reserve_transfer_native_asset_works sent xcm: {:?}", xcm_sent);
1625			let reserve_assets_deposited = Assets::from(vec![Asset {
1626				id: AssetId(Location { parents: 1, interior: Here }),
1627				fun: Fungible(1000000000000),
1628			}]);
1629
1630			assert_matches_reserve_asset_deposited_instructions(
1631				&mut xcm_sent,
1632				&reserve_assets_deposited,
1633				&dest_beneficiary,
1634			);
1635
1636			// check alice account decreased by balance_to_transfer ( + delivery_fees)
1637			assert_eq!(
1638				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account),
1639				alice_account_init_balance - balance_to_transfer.into() - delivery_fees.into()
1640			);
1641
1642			// check reserve account
1643			// check reserve account increased by balance_to_transfer
1644			assert_eq!(
1645				<pallet_balances::Pallet<Runtime>>::free_balance(&reserve_account),
1646				existential_deposit + balance_to_transfer.into()
1647			);
1648		})
1649}
1650
1651pub fn xcm_payment_api_with_pools_works<Runtime, RuntimeCall, RuntimeOrigin, Block, WeightToFee>()
1652where
1653	Runtime: XcmPaymentApiV2<Block>
1654		+ frame_system::Config<RuntimeOrigin = RuntimeOrigin, AccountId = AccountId>
1655		+ pallet_balances::Config<Balance = u128>
1656		+ pallet_session::Config
1657		+ pallet_xcm::Config
1658		+ parachain_info::Config
1659		+ pallet_collator_selection::Config
1660		+ cumulus_pallet_parachain_system::Config
1661		+ cumulus_pallet_xcmp_queue::Config
1662		+ pallet_timestamp::Config
1663		+ pallet_assets::Config<
1664			pallet_assets::Instance1,
1665			AssetId = u32,
1666			Balance = <Runtime as pallet_balances::Config>::Balance,
1667		> + pallet_asset_conversion::Config<
1668			AssetKind = xcm::v5::Location,
1669			Balance = <Runtime as pallet_balances::Config>::Balance,
1670		>,
1671	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
1672	RuntimeOrigin: OriginTrait<AccountId = <Runtime as frame_system::Config>::AccountId>,
1673	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
1674		From<<Runtime as frame_system::Config>::AccountId>,
1675	Block: BlockT,
1676	WeightToFee: frame_support::weights::WeightToFee,
1677{
1678	use xcm::prelude::*;
1679
1680	ExtBuilder::<Runtime>::default().build().execute_with(|| {
1681		let test_account = AccountId::from([0u8; 32]);
1682		let transfer_amount = 100u128;
1683		let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe()
1684			.withdraw_asset((Here, transfer_amount))
1685			.buy_execution((Here, transfer_amount), Unlimited)
1686			.deposit_asset(AllCounted(1), [1u8; 32])
1687			.build();
1688		let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into());
1689
1690		let xcm_weight =
1691			Runtime::query_xcm_weight(versioned_xcm_to_weigh).expect("xcm weight must be computed");
1692
1693		let expected_weight_native_fee: u128 =
1694			WeightToFee::weight_to_fee(&xcm_weight).saturated_into();
1695
1696		let native_token: Location = Parent.into();
1697		let native_token_versioned = VersionedAssetId::from(AssetId(native_token.clone()));
1698		let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, native_token_versioned)
1699			.expect("weight must be converted to native fee");
1700
1701		assert_eq!(execution_fees, expected_weight_native_fee);
1702
1703		// We need some balance to create an asset.
1704		assert_ok!(
1705			pallet_balances::Pallet::<Runtime>::mint_into(&test_account, 3_000_000_000_000,)
1706		);
1707
1708		// Now we try to use an asset that's not in a pool.
1709		let asset_id = 1984u32; // USDT.
1710		let usdt_token: Location = (PalletInstance(50), GeneralIndex(asset_id.into())).into();
1711		assert_ok!(pallet_assets::Pallet::<Runtime, pallet_assets::Instance1>::create(
1712			RuntimeOrigin::signed(test_account.clone()),
1713			asset_id.into(),
1714			test_account.clone().into(),
1715			1000
1716		));
1717		let execution_fees =
1718			Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.clone().into());
1719		assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound));
1720
1721		// We add it to a pool with native.
1722		assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::create_pool(
1723			RuntimeOrigin::signed(test_account.clone()),
1724			native_token.clone().try_into().unwrap(),
1725			usdt_token.clone().try_into().unwrap()
1726		));
1727		let execution_fees =
1728			Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.clone().into());
1729		// Still not enough because it doesn't have any liquidity.
1730		assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound));
1731
1732		// We mint some of the asset...
1733		assert_ok!(pallet_assets::Pallet::<Runtime, pallet_assets::Instance1>::mint(
1734			RuntimeOrigin::signed(test_account.clone()),
1735			asset_id.into(),
1736			test_account.clone().into(),
1737			3_000_000_000_000,
1738		));
1739		// ...so we can add liquidity to the pool.
1740		assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::add_liquidity(
1741			RuntimeOrigin::signed(test_account.clone()),
1742			native_token.clone().try_into().unwrap(),
1743			usdt_token.clone().try_into().unwrap(),
1744			1_000_000_000_000,
1745			2_000_000_000_000,
1746			0,
1747			0,
1748			test_account
1749		));
1750
1751		let expected_weight_usdt_fee: u128 =
1752			pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
1753				usdt_token.clone(),
1754				native_token,
1755				expected_weight_native_fee,
1756				true,
1757			)
1758			.expect("the quote price must work")
1759			.saturated_into();
1760
1761		assert_ne!(expected_weight_usdt_fee, expected_weight_native_fee);
1762
1763		let execution_fees = Runtime::query_weight_to_asset_fee(xcm_weight, usdt_token.into())
1764			.expect("weight must be converted to native fee");
1765
1766		assert_eq!(execution_fees, expected_weight_usdt_fee);
1767	});
1768}
1769
1770pub fn setup_pool_for_paying_fees_with_foreign_assets<Runtime, RuntimeOrigin>(
1771	existential_deposit: Balance,
1772	(foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): (
1773		AccountId,
1774		Location,
1775		Balance,
1776	),
1777) where
1778	Runtime: frame_system::Config<RuntimeOrigin = RuntimeOrigin, AccountId = AccountId>
1779		+ pallet_balances::Config<Balance = u128>
1780		+ pallet_assets::Config<
1781			pallet_assets::Instance2,
1782			AssetId = xcm::v5::Location,
1783			Balance = <Runtime as pallet_balances::Config>::Balance,
1784		> + pallet_asset_conversion::Config<
1785			AssetKind = xcm::v5::Location,
1786			Balance = <Runtime as pallet_balances::Config>::Balance,
1787		>,
1788	RuntimeOrigin: OriginTrait<AccountId = <Runtime as frame_system::Config>::AccountId>,
1789	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
1790		From<<Runtime as frame_system::Config>::AccountId>,
1791{
1792	// setup a pool to pay fees with `foreign_asset_id_location` tokens
1793	let pool_owner: AccountId = [14u8; 32].into();
1794	let native_asset = Location::parent();
1795	let pool_liquidity: Balance =
1796		existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000);
1797
1798	let _ = pallet_balances::Pallet::<Runtime>::force_set_balance(
1799		RuntimeOrigin::root(),
1800		pool_owner.clone().into(),
1801		(existential_deposit + pool_liquidity).mul(2).into(),
1802	);
1803
1804	assert_ok!(pallet_assets::Pallet::<Runtime, pallet_assets::Instance2>::mint(
1805		RuntimeOrigin::signed(foreign_asset_owner),
1806		foreign_asset_id_location.clone().into(),
1807		pool_owner.clone().into(),
1808		(foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(),
1809	));
1810
1811	assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::create_pool(
1812		RuntimeOrigin::signed(pool_owner.clone()),
1813		Box::new(native_asset.clone().into()),
1814		Box::new(foreign_asset_id_location.clone().into())
1815	));
1816
1817	assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::add_liquidity(
1818		RuntimeOrigin::signed(pool_owner.clone()),
1819		Box::new(native_asset.into()),
1820		Box::new(foreign_asset_id_location.into()),
1821		pool_liquidity,
1822		pool_liquidity,
1823		1,
1824		1,
1825		pool_owner,
1826	));
1827}
1828
1829pub fn xcm_payment_api_foreign_asset_pool_works<
1830	Runtime,
1831	RuntimeCall,
1832	RuntimeOrigin,
1833	LocationToAccountId,
1834	Block,
1835	WeightToFee,
1836>(
1837	existential_deposit: Balance,
1838	another_network_genesis_hash: [u8; 32],
1839) where
1840	Runtime: XcmPaymentApiV2<Block>
1841		+ frame_system::Config<RuntimeOrigin = RuntimeOrigin, AccountId = AccountId>
1842		+ pallet_balances::Config<Balance = u128>
1843		+ pallet_session::Config
1844		+ pallet_xcm::Config
1845		+ parachain_info::Config
1846		+ pallet_collator_selection::Config
1847		+ cumulus_pallet_parachain_system::Config
1848		+ cumulus_pallet_xcmp_queue::Config
1849		+ pallet_timestamp::Config
1850		+ pallet_assets::Config<
1851			pallet_assets::Instance2,
1852			AssetId = xcm::v5::Location,
1853			Balance = <Runtime as pallet_balances::Config>::Balance,
1854		> + pallet_asset_conversion::Config<
1855			AssetKind = xcm::v5::Location,
1856			Balance = <Runtime as pallet_balances::Config>::Balance,
1857		>,
1858	RuntimeOrigin: OriginTrait<AccountId = <Runtime as frame_system::Config>::AccountId>,
1859	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
1860	<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
1861		From<<Runtime as frame_system::Config>::AccountId>,
1862	LocationToAccountId: ConvertLocation<AccountId>,
1863	Block: BlockT,
1864	WeightToFee: frame_support::weights::WeightToFee,
1865{
1866	use xcm::prelude::*;
1867
1868	ExtBuilder::<Runtime>::default().build().execute_with(|| {
1869		let foreign_asset_owner =
1870			LocationToAccountId::convert_location(&Location::parent()).unwrap();
1871		let foreign_asset_id_location = Location::new(
1872			2,
1873			[Junction::GlobalConsensus(NetworkId::ByGenesis(another_network_genesis_hash))],
1874		);
1875		let native_asset_location = Location::parent();
1876		let foreign_asset_id_minimum_balance = 1_000_000_000;
1877
1878		pallet_assets::Pallet::<Runtime, pallet_assets::Instance2>::force_create(
1879			RuntimeHelper::<Runtime>::root_origin(),
1880			foreign_asset_id_location.clone().into(),
1881			foreign_asset_owner.clone().into(),
1882			true, // is_sufficient=true
1883			foreign_asset_id_minimum_balance.into(),
1884		)
1885		.unwrap();
1886
1887		setup_pool_for_paying_fees_with_foreign_assets::<Runtime, RuntimeOrigin>(
1888			existential_deposit,
1889			(
1890				foreign_asset_owner,
1891				foreign_asset_id_location.clone(),
1892				foreign_asset_id_minimum_balance,
1893			),
1894		);
1895
1896		let transfer_amount = 100u128;
1897		let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe()
1898			.withdraw_asset((Here, transfer_amount))
1899			.buy_execution((Here, transfer_amount), Unlimited)
1900			.deposit_asset(AllCounted(1), [1u8; 32])
1901			.build();
1902		let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.into());
1903
1904		let xcm_weight =
1905			Runtime::query_xcm_weight(versioned_xcm_to_weigh).expect("xcm weight must be computed");
1906
1907		let weight_native_fee: u128 = WeightToFee::weight_to_fee(&xcm_weight).saturated_into();
1908
1909		let expected_weight_foreign_asset_fee: u128 =
1910			pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
1911				foreign_asset_id_location.clone(),
1912				native_asset_location,
1913				weight_native_fee,
1914				true,
1915			)
1916			.expect("the quote price must work")
1917			.saturated_into();
1918
1919		assert_ne!(expected_weight_foreign_asset_fee, weight_native_fee);
1920
1921		let execution_fees = Runtime::query_weight_to_asset_fee(
1922			xcm_weight,
1923			foreign_asset_id_location.clone().into(),
1924		)
1925		.expect("weight must be converted to foreign asset fee");
1926
1927		assert_eq!(execution_fees, expected_weight_foreign_asset_fee);
1928	});
1929}