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