Skip to main content

parachains_runtimes_test_utils/
lib.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
16use core::marker::PhantomData;
17
18use codec::{Decode, DecodeLimit};
19use cumulus_pallet_parachain_system::parachain_inherent::{
20	deconstruct_parachain_inherent_data, InboundMessagesData,
21};
22use cumulus_primitives_core::{
23	relay_chain::Slot, AbridgedHrmpChannel, ParaId, PersistedValidationData,
24};
25use cumulus_primitives_parachain_inherent::ParachainInherentData;
26use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
27use frame_support::{
28	dispatch::{DispatchResult, GetDispatchInfo, RawOrigin},
29	inherent::{InherentData, ProvideInherent},
30	pallet_prelude::Get,
31	traits::{OnFinalize, OnInitialize, OriginTrait, UnfilteredDispatchable},
32	weights::Weight,
33};
34use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
35use polkadot_parachain_primitives::primitives::{
36	HeadData, HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat,
37};
38use sp_consensus_aura::{SlotDuration, AURA_ENGINE_ID};
39use sp_core::{Encode, U256};
40use sp_runtime::{
41	traits::{Dispatchable, Header},
42	BuildStorage, Digest, DigestItem, DispatchError, Either, SaturatedConversion,
43};
44use xcm::{
45	latest::{Asset, Location, XcmContext, XcmHash},
46	prelude::*,
47	VersionedXcm, MAX_XCM_DECODE_DEPTH,
48};
49use xcm_executor::traits::TransactAsset;
50
51pub mod test_cases;
52
53pub type BalanceOf<Runtime> = <Runtime as pallet_balances::Config>::Balance;
54pub type AccountIdOf<Runtime> = <Runtime as frame_system::Config>::AccountId;
55pub type RuntimeCallOf<Runtime> = <Runtime as frame_system::Config>::RuntimeCall;
56pub type RuntimeOriginOf<Runtime> = <Runtime as frame_system::Config>::RuntimeOrigin;
57pub type ValidatorIdOf<Runtime> = <Runtime as pallet_session::Config>::ValidatorId;
58pub type SessionKeysOf<Runtime> = <Runtime as pallet_session::Config>::Keys;
59
60pub struct CollatorSessionKey<
61	Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config,
62> {
63	collator: AccountIdOf<Runtime>,
64	validator: ValidatorIdOf<Runtime>,
65	key: SessionKeysOf<Runtime>,
66}
67
68pub struct CollatorSessionKeys<
69	Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config,
70> {
71	items: Vec<CollatorSessionKey<Runtime>>,
72}
73
74impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config>
75	CollatorSessionKey<Runtime>
76{
77	pub fn new(
78		collator: AccountIdOf<Runtime>,
79		validator: ValidatorIdOf<Runtime>,
80		key: SessionKeysOf<Runtime>,
81	) -> Self {
82		Self { collator, validator, key }
83	}
84}
85
86impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config> Default
87	for CollatorSessionKeys<Runtime>
88{
89	fn default() -> Self {
90		Self { items: vec![] }
91	}
92}
93
94impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config>
95	CollatorSessionKeys<Runtime>
96{
97	pub fn new(
98		collator: AccountIdOf<Runtime>,
99		validator: ValidatorIdOf<Runtime>,
100		key: SessionKeysOf<Runtime>,
101	) -> Self {
102		Self { items: vec![CollatorSessionKey::new(collator, validator, key)] }
103	}
104
105	pub fn add(mut self, item: CollatorSessionKey<Runtime>) -> Self {
106		self.items.push(item);
107		self
108	}
109
110	pub fn collators(&self) -> Vec<AccountIdOf<Runtime>> {
111		self.items.iter().map(|item| item.collator.clone()).collect::<Vec<_>>()
112	}
113
114	pub fn session_keys(
115		&self,
116	) -> Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)> {
117		self.items
118			.iter()
119			.map(|item| (item.collator.clone(), item.validator.clone(), item.key.clone()))
120			.collect::<Vec<_>>()
121	}
122}
123
124pub struct SlotDurations {
125	pub relay: SlotDuration,
126	pub para: SlotDuration,
127}
128
129/// A set of traits for a minimal parachain runtime, that may be used in conjunction with the
130/// `ExtBuilder` and the `RuntimeHelper`.
131pub trait BasicParachainRuntime:
132	frame_system::Config
133	+ pallet_balances::Config
134	+ pallet_session::Config
135	+ pallet_xcm::Config
136	+ parachain_info::Config
137	+ pallet_collator_selection::Config
138	+ cumulus_pallet_parachain_system::Config
139	+ pallet_timestamp::Config
140{
141}
142
143impl<T> BasicParachainRuntime for T
144where
145	T: frame_system::Config
146		+ pallet_balances::Config
147		+ pallet_session::Config
148		+ pallet_xcm::Config
149		+ parachain_info::Config
150		+ pallet_collator_selection::Config
151		+ cumulus_pallet_parachain_system::Config
152		+ pallet_timestamp::Config,
153	ValidatorIdOf<T>: From<AccountIdOf<T>>,
154{
155}
156
157/// Basic builder based on balances, collators and pallet_session.
158pub struct ExtBuilder<Runtime: BasicParachainRuntime> {
159	// endowed accounts with balances
160	balances: Vec<(AccountIdOf<Runtime>, BalanceOf<Runtime>)>,
161	// collators to test block prod
162	collators: Vec<AccountIdOf<Runtime>>,
163	// keys added to pallet session
164	keys: Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)>,
165	// safe XCM version for pallet_xcm
166	safe_xcm_version: Option<XcmVersion>,
167	// para id
168	para_id: Option<ParaId>,
169	_runtime: PhantomData<Runtime>,
170}
171
172impl<Runtime: BasicParachainRuntime> Default for ExtBuilder<Runtime> {
173	fn default() -> ExtBuilder<Runtime> {
174		ExtBuilder {
175			balances: vec![],
176			collators: vec![],
177			keys: vec![],
178			safe_xcm_version: None,
179			para_id: None,
180			_runtime: PhantomData,
181		}
182	}
183}
184
185impl<Runtime: BasicParachainRuntime> ExtBuilder<Runtime> {
186	pub fn with_balances(
187		mut self,
188		balances: Vec<(AccountIdOf<Runtime>, BalanceOf<Runtime>)>,
189	) -> Self {
190		self.balances = balances;
191		self
192	}
193	pub fn with_collators(mut self, collators: Vec<AccountIdOf<Runtime>>) -> Self {
194		self.collators = collators;
195		self
196	}
197
198	pub fn with_session_keys(
199		mut self,
200		keys: Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)>,
201	) -> Self {
202		self.keys = keys;
203		self
204	}
205
206	pub fn with_tracing(self) -> Self {
207		sp_tracing::try_init_simple();
208		self
209	}
210
211	pub fn with_safe_xcm_version(mut self, safe_xcm_version: XcmVersion) -> Self {
212		self.safe_xcm_version = Some(safe_xcm_version);
213		self
214	}
215
216	pub fn with_para_id(mut self, para_id: ParaId) -> Self {
217		self.para_id = Some(para_id);
218		self
219	}
220
221	pub fn build(self) -> sp_io::TestExternalities {
222		let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
223
224		pallet_xcm::GenesisConfig::<Runtime> {
225			safe_xcm_version: self.safe_xcm_version,
226			..Default::default()
227		}
228		.assimilate_storage(&mut t)
229		.unwrap();
230
231		if let Some(para_id) = self.para_id {
232			parachain_info::GenesisConfig::<Runtime> {
233				parachain_id: para_id,
234				..Default::default()
235			}
236			.assimilate_storage(&mut t)
237			.unwrap();
238		}
239
240		pallet_balances::GenesisConfig::<Runtime> { balances: self.balances, ..Default::default() }
241			.assimilate_storage(&mut t)
242			.unwrap();
243
244		pallet_collator_selection::GenesisConfig::<Runtime> {
245			invulnerables: self.collators.clone(),
246			candidacy_bond: Default::default(),
247			desired_candidates: Default::default(),
248		}
249		.assimilate_storage(&mut t)
250		.unwrap();
251
252		pallet_session::GenesisConfig::<Runtime> { keys: self.keys, ..Default::default() }
253			.assimilate_storage(&mut t)
254			.unwrap();
255
256		let mut ext = sp_io::TestExternalities::new(t);
257
258		ext.execute_with(|| {
259			frame_system::Pallet::<Runtime>::set_block_number(1u32.into());
260		});
261
262		ext
263	}
264}
265
266pub struct RuntimeHelper<Runtime, AllPalletsWithoutSystem>(
267	PhantomData<(Runtime, AllPalletsWithoutSystem)>,
268);
269/// Utility function that advances the chain to the desired block number.
270/// If an author is provided, that author information is injected to all the blocks in the meantime.
271impl<
272		Runtime: frame_system::Config + cumulus_pallet_parachain_system::Config + pallet_timestamp::Config,
273		AllPalletsWithoutSystem,
274	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
275where
276	AccountIdOf<Runtime>:
277		Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
278	AllPalletsWithoutSystem:
279		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
280{
281	pub fn run_to_block(n: u32, author: AccountIdOf<Runtime>) -> HeaderFor<Runtime> {
282		let mut last_header = None;
283		loop {
284			let block_number = frame_system::Pallet::<Runtime>::block_number();
285			if block_number >= n.into() {
286				break;
287			}
288			// Set the new block number and author
289
290			// Inherent is not created at every block, don't finalize parachain
291			// system to avoid panicking.
292			let header = frame_system::Pallet::<Runtime>::finalize();
293
294			let pre_digest =
295				Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, author.encode())] };
296			frame_system::Pallet::<Runtime>::reset_events();
297
298			let next_block_number = block_number + 1u32.into();
299			frame_system::Pallet::<Runtime>::initialize(
300				&next_block_number,
301				&header.hash(),
302				&pre_digest,
303			);
304			AllPalletsWithoutSystem::on_initialize(next_block_number);
305			last_header = Some(header);
306		}
307		last_header.expect("run_to_block empty block range")
308	}
309
310	pub fn run_to_block_with_finalize(n: u32) -> HeaderFor<Runtime> {
311		let mut last_header = None;
312		loop {
313			let block_number = frame_system::Pallet::<Runtime>::block_number();
314			if block_number >= n.into() {
315				break;
316			}
317			// Set the new block number and author
318			let header = frame_system::Pallet::<Runtime>::finalize();
319
320			let pre_digest = Digest {
321				logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())],
322			};
323			frame_system::Pallet::<Runtime>::reset_events();
324
325			let next_block_number = block_number + 1u32.into();
326			frame_system::Pallet::<Runtime>::initialize(
327				&next_block_number,
328				&header.hash(),
329				&pre_digest,
330			);
331			AllPalletsWithoutSystem::on_initialize(next_block_number);
332
333			let parent_head = HeadData(header.encode());
334
335			// Get RelayParentOffset from the parachain system pallet config.
336			let relay_parent_offset =
337				<Runtime as cumulus_pallet_parachain_system::Config>::RelayParentOffset::get()
338					.saturated_into::<u64>();
339
340			let sproof_builder = RelayStateSproofBuilder {
341				para_id: <Runtime>::SelfParaId::get(),
342				included_para_head: parent_head.clone().into(),
343				..Default::default()
344			};
345
346			let (relay_parent_storage_root, relay_chain_state, relay_parent_descendants) =
347				sproof_builder.into_state_root_proof_and_descendants(relay_parent_offset);
348			let inherent_data = ParachainInherentData {
349				validation_data: PersistedValidationData {
350					parent_head,
351					relay_parent_number: (block_number.saturated_into::<u32>() * 2 + 1).into(),
352					relay_parent_storage_root,
353					max_pov_size: 100_000_000,
354				},
355				relay_chain_state,
356				downward_messages: Default::default(),
357				horizontal_messages: Default::default(),
358				relay_parent_descendants,
359				collator_peer_id: None,
360			};
361
362			let (inherent_data, downward_messages, horizontal_messages) =
363				deconstruct_parachain_inherent_data(inherent_data);
364
365			let _ = cumulus_pallet_parachain_system::Pallet::<Runtime>::set_validation_data(
366				Runtime::RuntimeOrigin::none(),
367				inherent_data,
368				InboundMessagesData::new(
369					downward_messages.into_abridged(&mut usize::MAX.clone()),
370					horizontal_messages.into_abridged(&mut usize::MAX.clone()),
371				),
372			);
373			let _ = pallet_timestamp::Pallet::<Runtime>::set(
374				Runtime::RuntimeOrigin::none(),
375				300_u32.into(),
376			);
377			AllPalletsWithoutSystem::on_finalize(next_block_number);
378			let header = frame_system::Pallet::<Runtime>::finalize();
379			last_header = Some(header);
380		}
381		last_header.expect("run_to_block empty block range")
382	}
383
384	pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
385		<Runtime as frame_system::Config>::RuntimeOrigin::root()
386	}
387
388	pub fn block_number() -> U256 {
389		frame_system::Pallet::<Runtime>::block_number().into()
390	}
391
392	pub fn origin_of(
393		account_id: AccountIdOf<Runtime>,
394	) -> <Runtime as frame_system::Config>::RuntimeOrigin {
395		<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id.into())
396	}
397}
398
399impl<XcmConfig: xcm_executor::Config, AllPalletsWithoutSystem>
400	RuntimeHelper<XcmConfig, AllPalletsWithoutSystem>
401{
402	pub fn do_transfer(
403		from: Location,
404		to: Location,
405		(asset, amount): (Location, u128),
406	) -> Result<Asset, XcmError> {
407		<XcmConfig::AssetTransactor as TransactAsset>::transfer_asset(
408			&Asset { id: AssetId(asset), fun: Fungible(amount) },
409			&from,
410			&to,
411			// We aren't able to track the XCM that initiated the fee deposit, so we create a
412			// fake message hash here
413			&XcmContext::with_message_id([0; 32]),
414		)
415	}
416}
417
418impl<
419		Runtime: pallet_xcm::Config + cumulus_pallet_parachain_system::Config,
420		AllPalletsWithoutSystem,
421	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
422{
423	pub fn do_teleport_assets<HrmpChannelOpener>(
424		origin: <Runtime as frame_system::Config>::RuntimeOrigin,
425		dest: Location,
426		beneficiary: Location,
427		(asset, amount): (Location, u128),
428		open_hrmp_channel: Option<(u32, u32)>,
429		included_head: HeaderFor<Runtime>,
430		slot_digest: &[u8],
431		slot_durations: &SlotDurations,
432	) -> DispatchResult
433	where
434		HrmpChannelOpener: frame_support::inherent::ProvideInherent<
435			Call = cumulus_pallet_parachain_system::Call<Runtime>,
436		>,
437	{
438		// open hrmp (if needed)
439		if let Some((source_para_id, target_para_id)) = open_hrmp_channel {
440			mock_open_hrmp_channel::<Runtime, HrmpChannelOpener>(
441				source_para_id.into(),
442				target_para_id.into(),
443				included_head,
444				slot_digest,
445				slot_durations,
446			);
447		}
448
449		// do teleport
450		<pallet_xcm::Pallet<Runtime>>::limited_teleport_assets(
451			origin,
452			Box::new(dest.into()),
453			Box::new(beneficiary.into()),
454			Box::new((AssetId(asset), amount).into()),
455			0,
456			Unlimited,
457		)
458	}
459}
460
461impl<
462		Runtime: cumulus_pallet_parachain_system::Config + pallet_xcm::Config,
463		AllPalletsWithoutSystem,
464	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
465{
466	#[deprecated(
467		note = "Will be removed after Aug 2025; It uses hard-coded `Location::parent()`, \
468		use `execute_as_governance_call` instead."
469	)]
470	pub fn execute_as_governance(call: Vec<u8>) -> Outcome {
471		// prepare xcm as governance will do
472		let xcm = Xcm(vec![
473			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
474			Transact {
475				origin_kind: OriginKind::Superuser,
476				call: call.into(),
477				fallback_max_weight: None,
478			},
479			ExpectTransactStatus(MaybeErrorCode::Success),
480		]);
481
482		// execute xcm as parent origin
483		let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
484		<<Runtime as pallet_xcm::Config>::XcmExecutor>::prepare_and_execute(
485			Location::parent(),
486			xcm,
487			&mut hash,
488			Self::xcm_max_weight(XcmReceivedFrom::Parent),
489			Weight::zero(),
490		)
491	}
492
493	pub fn execute_as_governance_call<Call: Dispatchable + Encode>(
494		call: Call,
495		governance_origin: GovernanceOrigin<Call::RuntimeOrigin>,
496	) -> Result<(), Either<DispatchError, InstructionError>> {
497		// execute xcm as governance would send
498		let execute_xcm = |call: Call, governance_location, descend_origin| {
499			// prepare xcm
500			let xcm = if let Some(descend_origin) = descend_origin {
501				Xcm::builder_unsafe().descend_origin(descend_origin)
502			} else {
503				Xcm::builder_unsafe()
504			}
505			.unpaid_execution(Unlimited, None)
506			.transact(OriginKind::Superuser, None, call.encode())
507			.expect_transact_status(MaybeErrorCode::Success)
508			.build();
509
510			let xcm_max_weight = Self::xcm_max_weight_for_location(&governance_location);
511			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
512
513			<<Runtime as pallet_xcm::Config>::XcmExecutor>::prepare_and_execute(
514				governance_location,
515				xcm,
516				&mut hash,
517				xcm_max_weight,
518				Weight::zero(),
519			)
520		};
521
522		match governance_origin {
523			// we are simulating a case of receiving an XCM
524			// and Location::Here() is not a valid destionation for XcmRouter in the fist place
525			GovernanceOrigin::Location(location) if location == Location::here() => {
526				panic!("Location::here() not supported, use GovernanceOrigin::Origin instead")
527			},
528			GovernanceOrigin::Location(location) => {
529				execute_xcm(call, location, None).ensure_complete().map_err(Either::Right)
530			},
531			GovernanceOrigin::LocationAndDescendOrigin(location, descend_origin) => {
532				execute_xcm(call, location, Some(descend_origin))
533					.ensure_complete()
534					.map_err(Either::Right)
535			},
536			GovernanceOrigin::Origin(origin) => {
537				call.dispatch(origin).map(|_| ()).map_err(|e| Either::Left(e.error))
538			},
539		}
540	}
541
542	pub fn execute_as_origin<Call: GetDispatchInfo + Encode>(
543		(origin, origin_kind): (Location, OriginKind),
544		call: Call,
545		maybe_buy_execution_fee: Option<Asset>,
546	) -> Outcome {
547		let mut instructions = if let Some(buy_execution_fee) = maybe_buy_execution_fee {
548			vec![
549				WithdrawAsset(buy_execution_fee.clone().into()),
550				BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
551			]
552		} else {
553			vec![UnpaidExecution { check_origin: None, weight_limit: Unlimited }]
554		};
555
556		// prepare `Transact` xcm
557		instructions.extend(vec![
558			Transact { origin_kind, call: call.encode().into(), fallback_max_weight: None },
559			ExpectTransactStatus(MaybeErrorCode::Success),
560		]);
561		let xcm = Xcm(instructions);
562		let xcm_max_weight = Self::xcm_max_weight_for_location(&origin);
563
564		// execute xcm as parent origin
565		let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
566		<<Runtime as pallet_xcm::Config>::XcmExecutor>::prepare_and_execute(
567			origin,
568			xcm,
569			&mut hash,
570			xcm_max_weight,
571			Weight::zero(),
572		)
573	}
574}
575
576/// Enum representing governance origin/location.
577#[derive(Clone)]
578pub enum GovernanceOrigin<RuntimeOrigin> {
579	Location(Location),
580	LocationAndDescendOrigin(Location, InteriorLocation),
581	Origin(RuntimeOrigin),
582}
583
584pub enum XcmReceivedFrom {
585	Parent,
586	Sibling,
587}
588
589impl<ParachainSystem: cumulus_pallet_parachain_system::Config, AllPalletsWithoutSystem>
590	RuntimeHelper<ParachainSystem, AllPalletsWithoutSystem>
591{
592	pub fn xcm_max_weight(from: XcmReceivedFrom) -> Weight {
593		match from {
594			XcmReceivedFrom::Parent => ParachainSystem::ReservedDmpWeight::get(),
595			XcmReceivedFrom::Sibling => ParachainSystem::ReservedXcmpWeight::get(),
596		}
597	}
598
599	pub fn xcm_max_weight_for_location(location: &Location) -> Weight {
600		Self::xcm_max_weight(if location == &Location::parent() {
601			XcmReceivedFrom::Parent
602		} else {
603			XcmReceivedFrom::Sibling
604		})
605	}
606}
607
608impl<Runtime: frame_system::Config + pallet_xcm::Config, AllPalletsWithoutSystem>
609	RuntimeHelper<Runtime, AllPalletsWithoutSystem>
610{
611	pub fn assert_pallet_xcm_event_outcome(
612		unwrap_pallet_xcm_event: &Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
613		assert_outcome: fn(Outcome),
614	) {
615		assert_outcome(Self::get_pallet_xcm_event_outcome(unwrap_pallet_xcm_event));
616	}
617
618	pub fn get_pallet_xcm_event_outcome(
619		unwrap_pallet_xcm_event: &Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
620	) -> Outcome {
621		<frame_system::Pallet<Runtime>>::events()
622			.into_iter()
623			.filter_map(|e| unwrap_pallet_xcm_event(e.event.encode()))
624			.find_map(|e| match e {
625				pallet_xcm::Event::Attempted { outcome } => Some(outcome),
626				_ => None,
627			})
628			.expect("No `pallet_xcm::Event::Attempted(outcome)` event found!")
629	}
630}
631
632impl<
633		Runtime: frame_system::Config + cumulus_pallet_xcmp_queue::Config,
634		AllPalletsWithoutSystem,
635	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
636{
637	pub fn xcmp_queue_message_sent(
638		unwrap_xcmp_queue_event: Box<
639			dyn Fn(Vec<u8>) -> Option<cumulus_pallet_xcmp_queue::Event<Runtime>>,
640		>,
641	) -> Option<XcmHash> {
642		<frame_system::Pallet<Runtime>>::events()
643			.into_iter()
644			.filter_map(|e| unwrap_xcmp_queue_event(e.event.encode()))
645			.find_map(|e| match e {
646				cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => {
647					Some(message_hash)
648				},
649				_ => None,
650			})
651	}
652}
653
654pub fn assert_metadata<Fungibles, AccountId>(
655	asset_id: impl Into<Fungibles::AssetId> + Clone,
656	expected_name: &str,
657	expected_symbol: &str,
658	expected_decimals: u8,
659) where
660	Fungibles: frame_support::traits::fungibles::metadata::Inspect<AccountId>
661		+ frame_support::traits::fungibles::Inspect<AccountId>,
662{
663	assert_eq!(Fungibles::name(asset_id.clone().into()), Vec::from(expected_name),);
664	assert_eq!(Fungibles::symbol(asset_id.clone().into()), Vec::from(expected_symbol),);
665	assert_eq!(Fungibles::decimals(asset_id.into()), expected_decimals);
666}
667
668pub fn assert_total<Fungibles, AccountId>(
669	asset_id: impl Into<Fungibles::AssetId> + Clone,
670	expected_total_issuance: impl Into<Fungibles::Balance>,
671	expected_active_issuance: impl Into<Fungibles::Balance>,
672) where
673	Fungibles: frame_support::traits::fungibles::metadata::Inspect<AccountId>
674		+ frame_support::traits::fungibles::Inspect<AccountId>,
675{
676	assert_eq!(Fungibles::total_issuance(asset_id.clone().into()), expected_total_issuance.into());
677	assert_eq!(Fungibles::active_issuance(asset_id.into()), expected_active_issuance.into());
678}
679
680/// Helper function which emulates opening HRMP channel which is needed for `XcmpQueue` to pass.
681///
682/// Calls parachain-system's `create_inherent` in case the channel hasn't been opened before, and
683/// thus requires additional parameters for validating it: latest included parachain head and
684/// parachain AuRa-slot.
685///
686/// AuRa consensus hook expects pallets to be initialized, before calling this function make sure to
687/// `run_to_block` at least once.
688pub fn mock_open_hrmp_channel<
689	C: cumulus_pallet_parachain_system::Config,
690	T: ProvideInherent<Call = cumulus_pallet_parachain_system::Call<C>>,
691>(
692	sender: ParaId,
693	recipient: ParaId,
694	included_head: HeaderFor<C>,
695	mut slot_digest: &[u8],
696	slot_durations: &SlotDurations,
697) {
698	let slot = Slot::decode(&mut slot_digest).expect("failed to decode digest");
699	// Convert para slot to relay chain.
700	let timestamp = slot.saturating_mul(slot_durations.para.as_millis());
701	let relay_slot = Slot::from_timestamp(timestamp.into(), slot_durations.relay);
702
703	// Get RelayParentOffset from the parachain system pallet config.
704	let relay_parent_offset = C::RelayParentOffset::get().saturated_into::<u64>();
705
706	let n = 1_u32;
707	let mut sproof_builder = RelayStateSproofBuilder {
708		para_id: sender,
709		included_para_head: Some(HeadData(included_head.encode())),
710		hrmp_egress_channel_index: Some(vec![recipient]),
711		current_slot: relay_slot,
712		..Default::default()
713	};
714	sproof_builder.hrmp_channels.insert(
715		HrmpChannelId { sender, recipient },
716		AbridgedHrmpChannel {
717			max_capacity: 10,
718			max_total_size: 10_000_000_u32,
719			max_message_size: 10_000_000_u32,
720			msg_count: 0,
721			total_size: 0_u32,
722			mqc_head: None,
723		},
724	);
725
726	let (relay_parent_storage_root, relay_chain_state, relay_parent_descendants) =
727		sproof_builder.into_state_root_proof_and_descendants(relay_parent_offset);
728
729	let vfp = PersistedValidationData {
730		relay_parent_number: n as RelayChainBlockNumber,
731		relay_parent_storage_root,
732		..Default::default()
733	};
734	// It is insufficient to push the validation function params
735	// to storage; they must also be included in the inherent data.
736	let inherent_data = {
737		let mut inherent_data = InherentData::default();
738		let system_inherent_data = ParachainInherentData {
739			validation_data: vfp,
740			relay_chain_state,
741			downward_messages: Default::default(),
742			horizontal_messages: Default::default(),
743			relay_parent_descendants,
744			collator_peer_id: None,
745		};
746		inherent_data
747			.put_data(
748				cumulus_primitives_parachain_inherent::INHERENT_IDENTIFIER,
749				&system_inherent_data,
750			)
751			.expect("failed to put VFP inherent");
752		inherent_data
753	};
754
755	// execute the block
756	T::create_inherent(&inherent_data)
757		.expect("got an inherent")
758		.dispatch_bypass_filter(RawOrigin::None.into())
759		.expect("dispatch succeeded");
760}
761
762impl<HrmpChannelSource: cumulus_primitives_core::XcmpMessageSource, AllPalletsWithoutSystem>
763	RuntimeHelper<HrmpChannelSource, AllPalletsWithoutSystem>
764{
765	pub fn take_xcm(sent_to_para_id: ParaId) -> Option<VersionedXcm<()>> {
766		match HrmpChannelSource::take_outbound_messages(10, &[])[..] {
767			[(para_id, ref mut xcm_message_data)] if para_id.eq(&sent_to_para_id.into()) => {
768				let mut xcm_message_data = &xcm_message_data[..];
769				// decode
770				let _ = XcmpMessageFormat::decode(&mut xcm_message_data).expect("valid format");
771				VersionedXcm::<()>::decode_with_depth_limit(
772					MAX_XCM_DECODE_DEPTH,
773					&mut xcm_message_data,
774				)
775				.map(|x| Some(x))
776				.expect("result with xcm")
777			},
778			_ => return None,
779		}
780	}
781}