referrerpolicy=no-referrer-when-downgrade

bridge_hub_test_utils/test_cases/
helpers.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Module contains tests code, that is shared by all types of bridges
18
19use crate::test_cases::{bridges_prelude::*, run_test, RuntimeHelper};
20
21use asset_test_utils::BasicParachainRuntime;
22use bp_messages::MessageNonce;
23use bp_polkadot_core::parachains::{ParaHash, ParaId};
24use bp_runtime::Chain;
25use codec::Decode;
26use core::marker::PhantomData;
27use frame_support::{
28	assert_ok,
29	dispatch::GetDispatchInfo,
30	traits::{fungible::Mutate, Contains, OnFinalize, OnInitialize, PalletInfoAccess},
31};
32use frame_system::pallet_prelude::BlockNumberFor;
33use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader};
34use pallet_bridge_messages::{BridgedChainOf, LaneIdOf};
35use parachains_common::AccountId;
36use parachains_runtimes_test_utils::{
37	mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations,
38};
39use sp_core::Get;
40use sp_keyring::Sr25519Keyring::*;
41use sp_runtime::{traits::TrailingZeroInput, AccountId32};
42use xcm::latest::prelude::*;
43use xcm_executor::traits::ConvertLocation;
44
45/// Verify that the transaction has succeeded.
46#[impl_trait_for_tuples::impl_for_tuples(30)]
47pub trait VerifyTransactionOutcome {
48	fn verify_outcome(&self);
49}
50
51impl VerifyTransactionOutcome for Box<dyn VerifyTransactionOutcome> {
52	fn verify_outcome(&self) {
53		VerifyTransactionOutcome::verify_outcome(&**self)
54	}
55}
56
57/// Checks that the best finalized header hash in the bridge GRANDPA pallet equals to given one.
58pub struct VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
59where
60	Runtime: BridgeGrandpaConfig<GPI>,
61	GPI: 'static,
62{
63	expected_best_hash: BridgedBlockHash<Runtime, GPI>,
64}
65
66impl<Runtime, GPI> VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
67where
68	Runtime: BridgeGrandpaConfig<GPI>,
69	GPI: 'static,
70{
71	/// Expect given header hash to be the best after transaction.
72	pub fn expect_best_header_hash(
73		expected_best_hash: BridgedBlockHash<Runtime, GPI>,
74	) -> Box<dyn VerifyTransactionOutcome> {
75		Box::new(Self { expected_best_hash })
76	}
77}
78
79impl<Runtime, GPI> VerifyTransactionOutcome
80	for VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
81where
82	Runtime: BridgeGrandpaConfig<GPI>,
83	GPI: 'static,
84{
85	fn verify_outcome(&self) {
86		assert_eq!(
87			pallet_bridge_grandpa::BestFinalized::<Runtime, GPI>::get().unwrap().1,
88			self.expected_best_hash
89		);
90		assert!(pallet_bridge_grandpa::ImportedHeaders::<Runtime, GPI>::contains_key(
91			self.expected_best_hash
92		));
93	}
94}
95
96/// Checks that the best parachain header hash in the bridge parachains pallet equals to given one.
97pub struct VerifySubmitParachainHeaderProofOutcome<Runtime, PPI> {
98	bridged_para_id: u32,
99	expected_best_hash: ParaHash,
100	_marker: PhantomData<(Runtime, PPI)>,
101}
102
103impl<Runtime, PPI> VerifySubmitParachainHeaderProofOutcome<Runtime, PPI>
104where
105	Runtime: BridgeParachainsConfig<PPI>,
106	PPI: 'static,
107{
108	/// Expect given header hash to be the best after transaction.
109	pub fn expect_best_header_hash(
110		bridged_para_id: u32,
111		expected_best_hash: ParaHash,
112	) -> Box<dyn VerifyTransactionOutcome> {
113		Box::new(Self { bridged_para_id, expected_best_hash, _marker: PhantomData })
114	}
115}
116
117impl<Runtime, PPI> VerifyTransactionOutcome
118	for VerifySubmitParachainHeaderProofOutcome<Runtime, PPI>
119where
120	Runtime: BridgeParachainsConfig<PPI>,
121	PPI: 'static,
122{
123	fn verify_outcome(&self) {
124		assert_eq!(
125			pallet_bridge_parachains::ParasInfo::<Runtime, PPI>::get(ParaId(self.bridged_para_id))
126				.map(|info| info.best_head_hash.head_hash),
127			Some(self.expected_best_hash),
128		);
129	}
130}
131
132/// Checks that the latest delivered nonce in the bridge messages pallet equals to given one.
133pub struct VerifySubmitMessagesProofOutcome<Runtime: BridgeMessagesConfig<MPI>, MPI: 'static> {
134	lane: LaneIdOf<Runtime, MPI>,
135	expected_nonce: MessageNonce,
136	_marker: PhantomData<(Runtime, MPI)>,
137}
138
139impl<Runtime, MPI> VerifySubmitMessagesProofOutcome<Runtime, MPI>
140where
141	Runtime: BridgeMessagesConfig<MPI>,
142	MPI: 'static,
143{
144	/// Expect given delivered nonce to be the latest after transaction.
145	pub fn expect_last_delivered_nonce(
146		lane: LaneIdOf<Runtime, MPI>,
147		expected_nonce: MessageNonce,
148	) -> Box<dyn VerifyTransactionOutcome> {
149		Box::new(Self { lane, expected_nonce, _marker: PhantomData })
150	}
151}
152
153impl<Runtime, MPI> VerifyTransactionOutcome for VerifySubmitMessagesProofOutcome<Runtime, MPI>
154where
155	Runtime: BridgeMessagesConfig<MPI>,
156	MPI: 'static,
157{
158	fn verify_outcome(&self) {
159		assert_eq!(
160			pallet_bridge_messages::InboundLanes::<Runtime, MPI>::get(self.lane)
161				.map(|d| d.last_delivered_nonce()),
162			Some(self.expected_nonce),
163		);
164	}
165}
166
167/// Verifies that relayer is rewarded at this chain.
168pub struct VerifyRelayerRewarded<Runtime: pallet_bridge_relayers::Config<RPI>, RPI: 'static> {
169	relayer: Runtime::AccountId,
170	reward_params: Runtime::Reward,
171}
172
173impl<Runtime, RPI> VerifyRelayerRewarded<Runtime, RPI>
174where
175	Runtime: pallet_bridge_relayers::Config<RPI>,
176	RPI: 'static,
177{
178	/// Expect given delivered nonce to be the latest after transaction.
179	pub fn expect_relayer_reward(
180		relayer: Runtime::AccountId,
181		reward_params: impl Into<Runtime::Reward>,
182	) -> Box<dyn VerifyTransactionOutcome> {
183		Box::new(Self { relayer, reward_params: reward_params.into() })
184	}
185}
186
187impl<Runtime, RPI> VerifyTransactionOutcome for VerifyRelayerRewarded<Runtime, RPI>
188where
189	Runtime: pallet_bridge_relayers::Config<RPI>,
190	RPI: 'static,
191{
192	fn verify_outcome(&self) {
193		assert!(pallet_bridge_relayers::RelayerRewards::<Runtime, RPI>::get(
194			&self.relayer,
195			&self.reward_params,
196		)
197		.is_some());
198	}
199}
200
201/// Verifies that relayer balance is equal to given value.
202pub struct VerifyRelayerBalance<Runtime: pallet_balances::Config> {
203	relayer: Runtime::AccountId,
204	balance: Runtime::Balance,
205}
206
207impl<Runtime> VerifyRelayerBalance<Runtime>
208where
209	Runtime: pallet_balances::Config,
210{
211	/// Expect given relayer balance after transaction.
212	pub fn expect_relayer_balance(
213		relayer: Runtime::AccountId,
214		balance: Runtime::Balance,
215	) -> Box<dyn VerifyTransactionOutcome> {
216		Box::new(Self { relayer, balance })
217	}
218}
219
220impl<Runtime> VerifyTransactionOutcome for VerifyRelayerBalance<Runtime>
221where
222	Runtime: pallet_balances::Config,
223{
224	fn verify_outcome(&self) {
225		assert_eq!(pallet_balances::Pallet::<Runtime>::free_balance(&self.relayer), self.balance,);
226	}
227}
228
229/// Initialize bridge GRANDPA pallet.
230pub(crate) fn initialize_bridge_grandpa_pallet<Runtime, GPI>(
231	init_data: bp_header_chain::InitializationData<BridgedHeader<Runtime, GPI>>,
232) where
233	Runtime: BridgeGrandpaConfig<GPI>
234		+ cumulus_pallet_parachain_system::Config
235		+ pallet_timestamp::Config,
236{
237	pallet_bridge_grandpa::Pallet::<Runtime, GPI>::initialize(
238		RuntimeHelper::<Runtime>::root_origin(),
239		init_data,
240	)
241	.unwrap();
242}
243
244/// Runtime calls and their verifiers.
245pub type CallsAndVerifiers<Runtime> =
246	Vec<(RuntimeCallOf<Runtime>, Box<dyn VerifyTransactionOutcome>)>;
247
248pub type InboundRelayerId<Runtime, MPI> = bp_runtime::AccountIdOf<BridgedChainOf<Runtime, MPI>>;
249
250/// Returns relayer id at the bridged chain.
251pub fn relayer_id_at_bridged_chain<Runtime: pallet_bridge_messages::Config<MPI>, MPI>(
252) -> InboundRelayerId<Runtime, MPI> {
253	Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
254}
255
256/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer,
257/// with proofs (finality, message) independently submitted.
258pub fn relayed_incoming_message_works<Runtime, AllPalletsWithoutSystem, MPI>(
259	collator_session_key: CollatorSessionKeys<Runtime>,
260	slot_durations: SlotDurations,
261	runtime_para_id: u32,
262	sibling_parachain_id: u32,
263	local_relay_chain_id: NetworkId,
264	construct_and_apply_extrinsic: fn(
265		sp_keyring::Sr25519Keyring,
266		RuntimeCallOf<Runtime>,
267	) -> sp_runtime::DispatchOutcome,
268	prepare_message_proof_import: impl FnOnce(
269		Runtime::AccountId,
270		InboundRelayerId<Runtime, MPI>,
271		InteriorLocation,
272		MessageNonce,
273		Xcm<()>,
274		bp_runtime::ChainId,
275	) -> CallsAndVerifiers<Runtime>,
276) where
277	Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config + BridgeMessagesConfig<MPI>,
278	AllPalletsWithoutSystem:
279		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
280	MPI: 'static,
281	AccountIdOf<Runtime>: From<AccountId32>,
282{
283	let relayer_at_target = Bob;
284	let relayer_id_on_target: AccountId32 = relayer_at_target.public().into();
285	let relayer_id_on_source = relayer_id_at_bridged_chain::<Runtime, MPI>();
286	let bridged_chain_id = Runtime::BridgedChain::ID;
287
288	assert_ne!(runtime_para_id, sibling_parachain_id);
289
290	run_test::<Runtime, _>(
291		collator_session_key,
292		runtime_para_id,
293		vec![(
294			relayer_id_on_target.clone().into(),
295			// this value should be enough to cover all transaction costs, but computing the actual
296			// value here is tricky - there are several transaction payment pallets and we don't
297			// want to introduce additional bounds and traits here just for that, so let's just
298			// select some presumably large value
299			core::cmp::max::<Runtime::Balance>(Runtime::ExistentialDeposit::get(), 1u32.into()) *
300				100_000_000u32.into(),
301		)],
302		|| {
303			let mut alice = [0u8; 32];
304			alice[0] = 1;
305
306			let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
307				2,
308				AccountId::from(alice).into(),
309			);
310			mock_open_hrmp_channel::<Runtime, cumulus_pallet_parachain_system::Pallet<Runtime>>(
311				runtime_para_id.into(),
312				sibling_parachain_id.into(),
313				included_head,
314				&alice,
315				&slot_durations,
316			);
317
318			// set up relayer details and proofs
319
320			let message_destination: InteriorLocation =
321				[GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)].into();
322			// some random numbers (checked by test)
323			let message_nonce = 1;
324
325			let xcm = vec![Instruction::<()>::ClearOrigin; 42];
326			let expected_dispatch = xcm::latest::Xcm::<()>({
327				let mut expected_instructions = xcm.clone();
328				// dispatch prepends bridge pallet instance
329				expected_instructions.insert(
330					0,
331					DescendOrigin([PalletInstance(
332						<pallet_bridge_messages::Pallet<Runtime, MPI> as PalletInfoAccess>::index()
333							as u8,
334					)].into()),
335				);
336				expected_instructions
337			});
338
339			execute_and_verify_calls::<Runtime>(
340				relayer_at_target,
341				construct_and_apply_extrinsic,
342				prepare_message_proof_import(
343					relayer_id_on_target.clone().into(),
344					relayer_id_on_source.clone().into(),
345					message_destination,
346					message_nonce,
347					xcm.clone().into(),
348					bridged_chain_id,
349				),
350			);
351
352			// verify that imported XCM contains original message
353			let imported_xcm =
354				RuntimeHelper::<cumulus_pallet_xcmp_queue::Pallet<Runtime>>::take_xcm(
355					sibling_parachain_id.into(),
356				)
357				.unwrap();
358			let dispatched = xcm::latest::Xcm::<()>::try_from(imported_xcm).unwrap();
359			let mut dispatched_clone = dispatched.clone();
360			for (idx, expected_instr) in expected_dispatch.0.iter().enumerate() {
361				assert_eq!(expected_instr, &dispatched.0[idx]);
362				assert_eq!(expected_instr, &dispatched_clone.0.remove(0));
363			}
364			match dispatched_clone.0.len() {
365				0 => (),
366				1 => assert!(matches!(dispatched_clone.0[0], SetTopic(_))),
367				count => assert!(false, "Unexpected messages count: {:?}", count),
368			}
369		},
370	)
371}
372
373/// Execute every call and verify its outcome.
374fn execute_and_verify_calls<Runtime: frame_system::Config>(
375	submitter: sp_keyring::Sr25519Keyring,
376	construct_and_apply_extrinsic: fn(
377		sp_keyring::Sr25519Keyring,
378		RuntimeCallOf<Runtime>,
379	) -> sp_runtime::DispatchOutcome,
380	calls_and_verifiers: CallsAndVerifiers<Runtime>,
381) {
382	for (call, verifier) in calls_and_verifiers {
383		let dispatch_outcome = construct_and_apply_extrinsic(submitter, call);
384		assert_ok!(dispatch_outcome);
385		verifier.verify_outcome();
386	}
387}
388
389pub(crate) mod for_pallet_xcm_bridge_hub {
390	use super::{super::for_pallet_xcm_bridge_hub::*, *};
391
392	/// Helper function to open the bridge/lane for `source` and `destination` while ensuring all
393	/// required balances are placed into the SA of the source.
394	pub fn ensure_opened_bridge<
395		Runtime,
396		XcmOverBridgePalletInstance,
397		LocationToAccountId,
398		TokenLocation>
399	(source: Location, destination: InteriorLocation, is_paid_xcm_execution: bool, bridge_opener: impl Fn(pallet_xcm_bridge_hub::BridgeLocations, Option<Asset>)) -> (pallet_xcm_bridge_hub::BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>)
400	where
401		Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>,
402		XcmOverBridgePalletInstance: 'static,
403		<Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
404		<Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>,
405		<Runtime as pallet_balances::Config>::Balance: From<u128>,
406		LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
407		TokenLocation: Get<Location>
408	{
409		// construct expected bridge configuration
410		let locations =
411			pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations(
412				source.clone().into(),
413				destination.clone().into(),
414			)
415				.expect("valid bridge locations");
416		assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
417			locations.bridge_id()
418		)
419		.is_none());
420
421		// SA of source location needs to have some required balance
422		if !<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::AllowWithoutBridgeDeposit::contains(&source) {
423			// required balance: ED + fee + BridgeDeposit
424			let bridge_deposit =
425				<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeDeposit::get();
426			let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + bridge_deposit.into();
427
428			let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location");
429			let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
430				.expect("mint_into passes");
431		};
432
433		let maybe_paid_execution = if is_paid_xcm_execution {
434			// random high enough value for `BuyExecution` fees
435			let buy_execution_fee_amount = 5_000_000_000_000_u128;
436			let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into();
437
438			let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() +
439				buy_execution_fee_amount.into();
440			let source_account_id =
441				LocationToAccountId::convert_location(&source).expect("valid location");
442			let _ =
443				<pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
444					.expect("mint_into passes");
445			Some(buy_execution_fee)
446		} else {
447			None
448		};
449
450		// call the bridge opener
451		bridge_opener(*locations.clone(), maybe_paid_execution);
452
453		// check opened bridge
454		let bridge = pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
455			locations.bridge_id(),
456		)
457		.expect("opened bridge");
458
459		// check state
460		assert_ok!(
461			pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state()
462		);
463
464		// return locations
465		(*locations, bridge.lane_id)
466	}
467
468	/// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic.
469	pub fn open_bridge_with_extrinsic<Runtime, XcmOverBridgePalletInstance>(
470		(origin, origin_kind): (Location, OriginKind),
471		bridge_destination_universal_location: InteriorLocation,
472		maybe_paid_execution: Option<Asset>,
473	) where
474		Runtime: frame_system::Config
475			+ pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>
476			+ cumulus_pallet_parachain_system::Config
477			+ pallet_xcm::Config,
478		XcmOverBridgePalletInstance: 'static,
479		<Runtime as frame_system::Config>::RuntimeCall:
480			GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
481	{
482		// open bridge with `Transact` call
483		let open_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::<
484			Runtime,
485			XcmOverBridgePalletInstance,
486		>::open_bridge {
487			bridge_destination_universal_location: Box::new(
488				bridge_destination_universal_location.clone().into(),
489			),
490		});
491
492		// execute XCM as source origin would do with `Transact -> Origin::Xcm`
493		assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin(
494			(origin, origin_kind),
495			open_bridge_call,
496			maybe_paid_execution
497		)
498		.ensure_complete());
499	}
500
501	/// Utility for opening bridge directly inserting data to the `pallet_xcm_bridge_hub`'s storage
502	/// (used only for legacy purposes).
503	pub fn open_bridge_with_storage<Runtime, XcmOverBridgePalletInstance>(
504		locations: pallet_xcm_bridge_hub::BridgeLocations,
505		lane_id: pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>,
506	) where
507		Runtime: pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>,
508		XcmOverBridgePalletInstance: 'static,
509	{
510		// insert bridge data directly to the storage
511		assert_ok!(
512			pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_open_bridge(
513				Box::new(locations),
514				lane_id,
515				true
516			)
517		);
518	}
519
520	/// Helper function to close the bridge/lane for `source` and `destination`.
521	pub fn close_bridge<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>(
522		expected_source: Location,
523		bridge_destination_universal_location: InteriorLocation,
524		(origin, origin_kind): (Location, OriginKind),
525		is_paid_xcm_execution: bool
526	) where
527		Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>,
528		XcmOverBridgePalletInstance: 'static,
529		<Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
530		<Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>,
531		<Runtime as pallet_balances::Config>::Balance: From<u128>,
532		LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
533		TokenLocation: Get<Location>
534	{
535		// construct expected bridge configuration
536		let locations =
537			pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations(
538				expected_source.clone().into(),
539				bridge_destination_universal_location.clone().into(),
540			)
541				.expect("valid bridge locations");
542		assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
543			locations.bridge_id()
544		)
545		.is_some());
546
547		// required balance: ED + fee + BridgeDeposit
548		let maybe_paid_execution = if is_paid_xcm_execution {
549			// random high enough value for `BuyExecution` fees
550			let buy_execution_fee_amount = 2_500_000_000_000_u128;
551			let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into();
552
553			let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() +
554				buy_execution_fee_amount.into();
555			let source_account_id =
556				LocationToAccountId::convert_location(&expected_source).expect("valid location");
557			let _ =
558				<pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
559					.expect("mint_into passes");
560			Some(buy_execution_fee)
561		} else {
562			None
563		};
564
565		// close bridge with `Transact` call
566		let close_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::<
567			Runtime,
568			XcmOverBridgePalletInstance,
569		>::close_bridge {
570			bridge_destination_universal_location: Box::new(
571				bridge_destination_universal_location.into(),
572			),
573			may_prune_messages: 16,
574		});
575
576		// execute XCM as source origin would do with `Transact -> Origin::Xcm`
577		assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin(
578			(origin, origin_kind),
579			close_bridge_call,
580			maybe_paid_execution
581		)
582		.ensure_complete());
583
584		// bridge is closed
585		assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
586			locations.bridge_id()
587		)
588		.is_none());
589
590		// check state
591		assert_ok!(
592			pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state()
593		);
594	}
595}