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