1use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees};
20use assets_common::local_and_foreign_assets::ForeignAssetReserveData;
21use codec::Encode;
22use cumulus_primitives_core::XcmpMessageSource;
23use frame_support::{
24 assert_ok,
25 traits::{Currency, Get, OnFinalize, OnInitialize, OriginTrait, ProcessMessageError},
26};
27use frame_system::pallet_prelude::BlockNumberFor;
28use parachains_common::{AccountId, Balance};
29use parachains_runtimes_test_utils::{
30 mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, RuntimeHelper,
31 SlotDurations, ValidatorIdOf, XcmReceivedFrom,
32};
33use sp_runtime::{traits::StaticLookup, Saturating};
34use xcm::{latest::prelude::*, VersionedAssetId, VersionedAssets, VersionedXcm};
35use xcm_builder::{CreateMatcher, MatchXcm};
36use xcm_executor::{
37 traits::{ConvertLocation, TransferType},
38 XcmExecutor,
39};
40
41pub struct TestBridgingConfig {
42 pub bridged_network: NetworkId,
43 pub local_bridge_hub_para_id: u32,
44 pub local_bridge_hub_location: Location,
45 pub bridged_target_location: Location,
46}
47
48pub fn limited_reserve_transfer_assets_for_native_asset_works<
50 Runtime,
51 AllPalletsWithoutSystem,
52 XcmConfig,
53 HrmpChannelOpener,
54 HrmpChannelSource,
55 LocationToAccountId,
56>(
57 collator_session_keys: CollatorSessionKeys<Runtime>,
58 slot_durations: SlotDurations,
59 existential_deposit: BalanceOf<Runtime>,
60 alice_account: AccountIdOf<Runtime>,
61 unwrap_pallet_xcm_event: Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
62 unwrap_xcmp_queue_event: Box<
63 dyn Fn(Vec<u8>) -> Option<cumulus_pallet_xcmp_queue::Event<Runtime>>,
64 >,
65 prepare_configuration: fn() -> TestBridgingConfig,
66 weight_limit: WeightLimit,
67 maybe_paid_export_message: Option<AssetId>,
68 delivery_fees_account: Option<AccountIdOf<Runtime>>,
69) where
70 Runtime: frame_system::Config
71 + pallet_balances::Config
72 + pallet_session::Config
73 + pallet_xcm::Config
74 + parachain_info::Config
75 + pallet_collator_selection::Config
76 + cumulus_pallet_parachain_system::Config
77 + cumulus_pallet_xcmp_queue::Config
78 + pallet_timestamp::Config,
79 AllPalletsWithoutSystem:
80 OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
81 AccountIdOf<Runtime>: Into<[u8; 32]>,
82 ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
83 BalanceOf<Runtime>: From<Balance>,
84 <Runtime as pallet_balances::Config>::Balance: From<Balance> + Into<u128>,
85 XcmConfig: xcm_executor::Config,
86 LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
87 <Runtime as frame_system::Config>::AccountId:
88 Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
89 <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
90 From<<Runtime as frame_system::Config>::AccountId>,
91 <Runtime as frame_system::Config>::AccountId: From<AccountId>,
92 HrmpChannelOpener: frame_support::inherent::ProvideInherent<
93 Call = cumulus_pallet_parachain_system::Call<Runtime>,
94 >,
95 HrmpChannelSource: XcmpMessageSource,
96{
97 let runtime_para_id = 1000;
98 ExtBuilder::<Runtime>::default()
99 .with_collators(collator_session_keys.collators())
100 .with_session_keys(collator_session_keys.session_keys())
101 .with_tracing()
102 .with_safe_xcm_version(3)
103 .with_para_id(runtime_para_id.into())
104 .build()
105 .execute_with(|| {
106 let mut alice = [0u8; 32];
107 alice[0] = 1;
108 let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
109 2,
110 AccountId::from(alice).into(),
111 );
112
113 let TestBridgingConfig {
115 bridged_network,
116 local_bridge_hub_para_id,
117 bridged_target_location: target_location_from_different_consensus,
118 ..
119 } = prepare_configuration();
120
121 let reserve_account =
122 LocationToAccountId::convert_location(&target_location_from_different_consensus)
123 .expect("Sovereign account for reserves");
124 let balance_to_transfer = 1_000_000_000_000_u128;
125 let native_asset = Location::parent();
126
127 mock_open_hrmp_channel::<Runtime, HrmpChannelOpener>(
129 runtime_para_id.into(),
130 local_bridge_hub_para_id.into(),
131 included_head,
132 &alice,
133 &slot_durations,
134 );
135
136 let delivery_fees_buffer = 8_000_000_000_000u128;
140 let alice_account_init_balance =
142 existential_deposit + balance_to_transfer.into() + delivery_fees_buffer.into();
143 let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
144 &alice_account,
145 alice_account_init_balance,
146 );
147 let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
149 &reserve_account,
150 existential_deposit,
151 );
152
153 assert!(
156 (<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account) -
157 balance_to_transfer.into()) >=
158 existential_deposit
159 );
160 assert_eq!(
162 <pallet_balances::Pallet<Runtime>>::free_balance(&reserve_account),
163 existential_deposit
164 );
165
166 let delivery_fees_account_balance_before = delivery_fees_account
167 .as_ref()
168 .map(|dfa| <pallet_balances::Pallet<Runtime>>::free_balance(dfa))
169 .unwrap_or(0.into());
170
171 let asset_to_transfer =
173 Asset { fun: Fungible(balance_to_transfer.into()), id: native_asset.into() };
174
175 let target_destination_account = Location::new(
177 0,
178 [AccountId32 {
179 network: Some(bridged_network),
180 id: sp_runtime::AccountId32::new([3; 32]).into(),
181 }],
182 );
183
184 let assets_to_transfer = Assets::from(asset_to_transfer);
185 let mut expected_assets = assets_to_transfer.clone();
186 let context = XcmConfig::UniversalLocation::get();
187 expected_assets
188 .reanchor(&target_location_from_different_consensus, &context)
189 .unwrap();
190
191 let expected_beneficiary = target_destination_account.clone();
192
193 assert_ok!(<pallet_xcm::Pallet<Runtime>>::transfer_assets_using_type_and_then(
195 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(alice_account.clone()),
196 Box::new(target_location_from_different_consensus.clone().into_versioned()),
197 Box::new(VersionedAssets::from(assets_to_transfer)),
198 Box::new(TransferType::LocalReserve),
199 Box::new(VersionedAssetId::from(AssetId(Location::parent()))),
200 Box::new(TransferType::LocalReserve),
201 Box::new(VersionedXcm::from(
202 Xcm::<()>::builder_unsafe()
203 .deposit_asset(AllCounted(1), target_destination_account)
204 .build()
205 )),
206 weight_limit,
207 ));
208
209 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::assert_pallet_xcm_event_outcome(
212 &unwrap_pallet_xcm_event,
213 |outcome| {
214 assert_ok!(outcome.ensure_complete());
215 },
216 );
217
218 let xcm_sent_message_hash = <frame_system::Pallet<Runtime>>::events()
220 .into_iter()
221 .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode()))
222 .find_map(|e| match e {
223 cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } =>
224 Some(message_hash),
225 _ => None,
226 });
227
228 let xcm_sent = RuntimeHelper::<HrmpChannelSource, AllPalletsWithoutSystem>::take_xcm(
230 local_bridge_hub_para_id.into(),
231 )
232 .unwrap();
233 assert_eq!(
234 xcm_sent_message_hash,
235 Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256))
236 );
237 let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm");
238
239 let mut delivery_fees = 0;
242 if let Some(expected_fee_asset_id) = maybe_paid_export_message {
244 xcm_sent
245 .0
246 .matcher()
247 .match_next_inst(|instr| match instr {
248 WithdrawAsset(_) => Ok(()),
249 _ => Err(ProcessMessageError::BadFormat),
250 })
251 .expect("contains WithdrawAsset")
252 .match_next_inst(|instr| match instr {
253 BuyExecution { fees, .. } if fees.id.eq(&expected_fee_asset_id) => Ok(()),
254 _ => Err(ProcessMessageError::BadFormat),
255 })
256 .expect("contains BuyExecution")
257 .match_next_inst(|instr| match instr {
258 SetAppendix(_) => Ok(()),
259 _ => Err(ProcessMessageError::BadFormat),
260 })
261 .expect("contains SetAppendix")
262 } else {
263 xcm_sent
264 .0
265 .matcher()
266 .match_next_inst(|instr| match instr {
267 UnpaidExecution { weight_limit, check_origin }
270 if weight_limit == &Unlimited && check_origin.is_none() =>
271 Ok(()),
272 _ => Err(ProcessMessageError::BadFormat),
273 })
274 .expect("contains UnpaidExecution")
275 }
276 .match_next_inst(|instr| match instr {
278 ExportMessage { network, destination, xcm: inner_xcm } => {
280 assert_eq!(network, &bridged_network);
281 let (_, target_location_junctions_without_global_consensus) =
282 target_location_from_different_consensus
283 .interior
284 .clone()
285 .split_global()
286 .expect("split works");
287 assert_eq!(destination, &target_location_junctions_without_global_consensus);
288 delivery_fees = get_fungible_delivery_fees::<
290 <XcmConfig as xcm_executor::Config>::XcmSender,
291 >(
292 target_location_from_different_consensus.clone(),
293 inner_xcm.clone(),
294 );
295 assert_matches_reserve_asset_deposited_instructions(
296 inner_xcm,
297 &expected_assets,
298 &expected_beneficiary,
299 );
300 Ok(())
301 },
302 _ => Err(ProcessMessageError::BadFormat),
303 })
304 .expect("contains ExportMessage");
305
306 assert_eq!(
308 <pallet_balances::Pallet<Runtime>>::free_balance(&alice_account),
309 alice_account_init_balance
310 .saturating_sub(balance_to_transfer.into())
311 .saturating_sub(delivery_fees.into())
312 );
313
314 assert_eq!(
316 <pallet_balances::Pallet<Runtime>>::free_balance(&reserve_account),
317 existential_deposit + balance_to_transfer.into()
318 );
319
320 if let Some(delivery_fees_account) = delivery_fees_account {
322 let delivery_fees_account_balance_after =
323 <pallet_balances::Pallet<Runtime>>::free_balance(&delivery_fees_account);
324 assert!(
325 delivery_fees_account_balance_after - delivery_fees.into() >=
326 delivery_fees_account_balance_before
327 );
328 }
329 })
330}
331
332pub fn receive_reserve_asset_deposited_from_different_consensus_works<
333 Runtime,
334 AllPalletsWithoutSystem,
335 XcmConfig,
336 ForeignAssetsPalletInstance,
337>(
338 collator_session_keys: CollatorSessionKeys<Runtime>,
339 existential_deposit: BalanceOf<Runtime>,
340 target_account: AccountIdOf<Runtime>,
341 block_author_account: AccountIdOf<Runtime>,
342 (
343 foreign_asset_owner,
344 foreign_asset_id_location,
345 foreign_asset_reserve_data,
346 foreign_asset_id_minimum_balance,
347 ): (AccountIdOf<Runtime>, xcm::v5::Location, ForeignAssetReserveData, u128),
348 foreign_asset_id_amount_to_transfer: u128,
349 prepare_configuration: impl FnOnce() -> TestBridgingConfig,
350 (bridge_instance, universal_origin, descend_origin): (Junctions, Junction, Junctions), additional_checks_before: impl FnOnce(),
352 additional_checks_after: impl FnOnce(),
353) where
354 Runtime: frame_system::Config
355 + pallet_balances::Config
356 + pallet_session::Config
357 + pallet_xcm::Config
358 + parachain_info::Config
359 + pallet_collator_selection::Config
360 + cumulus_pallet_parachain_system::Config
361 + cumulus_pallet_xcmp_queue::Config
362 + pallet_assets::Config<ForeignAssetsPalletInstance, ReserveData = ForeignAssetReserveData>
363 + pallet_timestamp::Config,
364 AllPalletsWithoutSystem:
365 OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
366 AccountIdOf<Runtime>: Into<[u8; 32]> + From<[u8; 32]>,
367 ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
368 BalanceOf<Runtime>: From<Balance> + Into<Balance>,
369 XcmConfig: xcm_executor::Config,
370 <Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
371 From<xcm::v5::Location> + Into<xcm::v5::Location>,
372 <Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetIdParameter:
373 From<xcm::v5::Location> + Into<xcm::v5::Location>,
374 <Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::Balance:
375 From<Balance> + Into<u128> + From<u128>,
376 <Runtime as frame_system::Config>::AccountId: Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>
377 + Into<AccountId>,
378 <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
379 From<<Runtime as frame_system::Config>::AccountId>,
380 ForeignAssetsPalletInstance: 'static,
381{
382 ExtBuilder::<Runtime>::default()
383 .with_collators(collator_session_keys.collators())
384 .with_session_keys(collator_session_keys.session_keys())
385 .with_tracing()
386 .build()
387 .execute_with(|| {
388 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
390 2,
391 block_author_account.clone().into(),
392 );
393
394 let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
396 &target_account,
397 existential_deposit,
398 );
399
400 assert_ok!(
402 <pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::force_create(
403 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::root_origin(),
404 foreign_asset_id_location.clone().into(),
405 foreign_asset_owner.clone().into(),
406 true, foreign_asset_id_minimum_balance.into()
408 )
409 );
410 assert_ok!(
412 <pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::set_reserves(
413 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(
414 foreign_asset_owner
415 ),
416 foreign_asset_id_location.clone().into(),
417 vec![foreign_asset_reserve_data],
418 )
419 );
420
421 let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration();
423
424 assert_eq!(
426 <pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
427 existential_deposit.clone()
428 );
429
430 assert_eq!(
432 <pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
433 foreign_asset_id_location.clone().into(),
434 &target_account
435 ),
436 0.into()
437 );
438
439 additional_checks_before();
441
442 let expected_assets = Assets::from(vec![Asset {
443 id: AssetId(foreign_asset_id_location.clone()),
444 fun: Fungible(foreign_asset_id_amount_to_transfer),
445 }]);
446 let expected_beneficiary = Location::new(
447 0,
448 [AccountId32 { network: None, id: target_account.clone().into() }],
449 );
450
451 let xcm = Xcm(vec![
453 DescendOrigin(bridge_instance),
454 UniversalOrigin(universal_origin),
455 DescendOrigin(descend_origin),
456 ReserveAssetDeposited(expected_assets.clone()),
457 ClearOrigin,
458 BuyExecution {
459 fees: Asset {
460 id: AssetId(foreign_asset_id_location.clone()),
461 fun: Fungible(foreign_asset_id_amount_to_transfer),
462 },
463 weight_limit: Unlimited,
464 },
465 DepositAsset {
466 assets: Wild(AllCounted(1)),
467 beneficiary: expected_beneficiary.clone(),
468 },
469 SetTopic([
470 220, 188, 144, 32, 213, 83, 111, 175, 44, 210, 111, 19, 90, 165, 191, 112, 140,
471 247, 192, 124, 42, 17, 153, 141, 114, 34, 189, 20, 83, 69, 237, 173,
472 ]),
473 ]);
474 assert_matches_reserve_asset_deposited_instructions(
475 &mut xcm.clone(),
476 &expected_assets,
477 &expected_beneficiary,
478 );
479
480 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
481
482 let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
484 local_bridge_hub_location,
485 xcm,
486 &mut hash,
487 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::xcm_max_weight(
488 XcmReceivedFrom::Sibling,
489 ),
490 Weight::zero(),
491 );
492 assert_ok!(outcome.ensure_complete());
493
494 assert_eq!(
496 <pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
497 existential_deposit.clone()
498 );
499
500 assert!(
502 <pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
503 foreign_asset_id_location.into(),
504 &target_account
505 ) > 0.into()
506 );
507
508 additional_checks_after();
510 })
511}
512
513pub fn report_bridge_status_from_xcm_bridge_router_works<
514 Runtime,
515 AllPalletsWithoutSystem,
516 XcmConfig,
517 LocationToAccountId,
518 XcmBridgeHubRouterInstance,
519>(
520 collator_session_keys: CollatorSessionKeys<Runtime>,
521 prepare_configuration: fn() -> TestBridgingConfig,
522 congested_message: fn() -> Xcm<XcmConfig::RuntimeCall>,
523 uncongested_message: fn() -> Xcm<XcmConfig::RuntimeCall>,
524) where
525 Runtime: frame_system::Config
526 + pallet_balances::Config
527 + pallet_session::Config
528 + pallet_xcm::Config
529 + parachain_info::Config
530 + pallet_collator_selection::Config
531 + cumulus_pallet_parachain_system::Config
532 + cumulus_pallet_xcmp_queue::Config
533 + pallet_xcm_bridge_hub_router::Config<XcmBridgeHubRouterInstance>
534 + pallet_timestamp::Config,
535 AllPalletsWithoutSystem:
536 OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
537 AccountIdOf<Runtime>: Into<[u8; 32]>,
538 ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
539 BalanceOf<Runtime>: From<Balance>,
540 <Runtime as pallet_balances::Config>::Balance: From<Balance> + Into<u128>,
541 XcmConfig: xcm_executor::Config,
542 LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
543 <Runtime as frame_system::Config>::AccountId:
544 Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
545 <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
546 From<<Runtime as frame_system::Config>::AccountId>,
547 <Runtime as frame_system::Config>::AccountId: From<AccountId>,
548 XcmBridgeHubRouterInstance: 'static,
549{
550 ExtBuilder::<Runtime>::default()
551 .with_collators(collator_session_keys.collators())
552 .with_session_keys(collator_session_keys.session_keys())
553 .with_tracing()
554 .build()
555 .execute_with(|| {
556 let report_bridge_status = |is_congested: bool| {
557 let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration();
559
560 let xcm = if is_congested { congested_message() } else { uncongested_message() };
562 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
563
564 let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
566 local_bridge_hub_location.clone(),
567 xcm,
568 &mut hash,
569 RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::xcm_max_weight(
570 XcmReceivedFrom::Sibling,
571 ),
572 Weight::zero(),
573 );
574 assert_ok!(outcome.ensure_complete());
575 assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::<Runtime, XcmBridgeHubRouterInstance>::bridge().is_congested);
576 };
577
578 report_bridge_status(true);
579 report_bridge_status(false);
580 })
581}