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