teyrchains_runtimes_test_utils/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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 pezcumulus_pezpallet_teyrchain_system::teyrchain_inherent::{
20	deconstruct_teyrchain_inherent_data, InboundMessagesData,
21};
22use pezcumulus_primitives_core::{
23	relay_chain::Slot, AbridgedHrmpChannel, ParaId, PersistedValidationData,
24};
25use pezcumulus_primitives_teyrchain_inherent::TeyrchainInherentData;
26use pezcumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
27use pezframe_support::{
28	dispatch::{DispatchResult, GetDispatchInfo, RawOrigin},
29	inherent::{InherentData, ProvideInherent},
30	pezpallet_prelude::Get,
31	traits::{OnFinalize, OnInitialize, OriginTrait, UnfilteredDispatchable},
32	weights::Weight,
33};
34use pezframe_system::pezpallet_prelude::{BlockNumberFor, HeaderFor};
35use pezkuwi_teyrchain_primitives::primitives::{
36	HeadData, HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat,
37};
38use pezsp_consensus_aura::{SlotDuration, AURA_ENGINE_ID};
39use pezsp_core::{Encode, U256};
40use pezsp_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, AssetsInHolding};
50
51pub mod test_cases;
52
53pub type BalanceOf<Runtime> = <Runtime as pezpallet_balances::Config>::Balance;
54pub type AccountIdOf<Runtime> = <Runtime as pezframe_system::Config>::AccountId;
55pub type RuntimeCallOf<Runtime> = <Runtime as pezframe_system::Config>::RuntimeCall;
56pub type RuntimeOriginOf<Runtime> = <Runtime as pezframe_system::Config>::RuntimeOrigin;
57pub type ValidatorIdOf<Runtime> = <Runtime as pezpallet_session::Config>::ValidatorId;
58pub type SessionKeysOf<Runtime> = <Runtime as pezpallet_session::Config>::Keys;
59
60pub struct CollatorSessionKey<
61	Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config,
62> {
63	collator: AccountIdOf<Runtime>,
64	validator: ValidatorIdOf<Runtime>,
65	key: SessionKeysOf<Runtime>,
66}
67
68pub struct CollatorSessionKeys<
69	Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config,
70> {
71	items: Vec<CollatorSessionKey<Runtime>>,
72}
73
74impl<Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_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: pezframe_system::Config + pezpallet_balances::Config + pezpallet_session::Config>
87	Default for CollatorSessionKeys<Runtime>
88{
89	fn default() -> Self {
90		Self { items: vec![] }
91	}
92}
93
94impl<Runtime: pezframe_system::Config + pezpallet_balances::Config + pezpallet_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 teyrchain runtime, that may be used in conjunction with the
130/// `ExtBuilder` and the `RuntimeHelper`.
131pub trait BasicTeyrchainRuntime:
132	pezframe_system::Config
133	+ pezpallet_balances::Config
134	+ pezpallet_session::Config
135	+ pezpallet_xcm::Config
136	+ teyrchain_info::Config
137	+ pezpallet_collator_selection::Config
138	+ pezcumulus_pezpallet_teyrchain_system::Config
139	+ pezpallet_timestamp::Config
140{
141}
142
143impl<T> BasicTeyrchainRuntime for T
144where
145	T: pezframe_system::Config
146		+ pezpallet_balances::Config
147		+ pezpallet_session::Config
148		+ pezpallet_xcm::Config
149		+ teyrchain_info::Config
150		+ pezpallet_collator_selection::Config
151		+ pezcumulus_pezpallet_teyrchain_system::Config
152		+ pezpallet_timestamp::Config,
153	ValidatorIdOf<T>: From<AccountIdOf<T>>,
154{
155}
156
157/// Basic builder based on balances, collators and pezpallet_session.
158pub struct ExtBuilder<Runtime: BasicTeyrchainRuntime> {
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 pezpallet session
164	keys: Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)>,
165	// safe XCM version for pezpallet_xcm
166	safe_xcm_version: Option<XcmVersion>,
167	// para id
168	para_id: Option<ParaId>,
169	_runtime: PhantomData<Runtime>,
170}
171
172impl<Runtime: BasicTeyrchainRuntime> 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: BasicTeyrchainRuntime> 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		pezsp_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) -> pezsp_io::TestExternalities {
222		let mut t = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
223
224		pezpallet_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			teyrchain_info::GenesisConfig::<Runtime> {
233				teyrchain_id: para_id,
234				..Default::default()
235			}
236			.assimilate_storage(&mut t)
237			.unwrap();
238		}
239
240		pezpallet_balances::GenesisConfig::<Runtime> {
241			balances: self.balances,
242			..Default::default()
243		}
244		.assimilate_storage(&mut t)
245		.unwrap();
246
247		pezpallet_collator_selection::GenesisConfig::<Runtime> {
248			invulnerables: self.collators.clone(),
249			candidacy_bond: Default::default(),
250			desired_candidates: Default::default(),
251		}
252		.assimilate_storage(&mut t)
253		.unwrap();
254
255		pezpallet_session::GenesisConfig::<Runtime> { keys: self.keys, ..Default::default() }
256			.assimilate_storage(&mut t)
257			.unwrap();
258
259		let mut ext = pezsp_io::TestExternalities::new(t);
260
261		ext.execute_with(|| {
262			pezframe_system::Pezpallet::<Runtime>::set_block_number(1u32.into());
263		});
264
265		ext
266	}
267}
268
269pub struct RuntimeHelper<Runtime, AllPalletsWithoutSystem>(
270	PhantomData<(Runtime, AllPalletsWithoutSystem)>,
271);
272/// Utility function that advances the chain to the desired block number.
273/// If an author is provided, that author information is injected to all the blocks in the meantime.
274impl<
275		Runtime: pezframe_system::Config
276			+ pezcumulus_pezpallet_teyrchain_system::Config
277			+ pezpallet_timestamp::Config,
278		AllPalletsWithoutSystem,
279	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
280where
281	AccountIdOf<Runtime>:
282		Into<<<Runtime as pezframe_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
283	AllPalletsWithoutSystem:
284		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
285{
286	pub fn run_to_block(n: u32, author: AccountIdOf<Runtime>) -> HeaderFor<Runtime> {
287		let mut last_header = None;
288		loop {
289			let block_number = pezframe_system::Pezpallet::<Runtime>::block_number();
290			if block_number >= n.into() {
291				break;
292			}
293			// Set the new block number and author
294
295			// Inherent is not created at every block, don't finalize teyrchain
296			// system to avoid panicking.
297			let header = pezframe_system::Pezpallet::<Runtime>::finalize();
298
299			let pre_digest =
300				Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, author.encode())] };
301			pezframe_system::Pezpallet::<Runtime>::reset_events();
302
303			let next_block_number = block_number + 1u32.into();
304			pezframe_system::Pezpallet::<Runtime>::initialize(
305				&next_block_number,
306				&header.hash(),
307				&pre_digest,
308			);
309			AllPalletsWithoutSystem::on_initialize(next_block_number);
310			last_header = Some(header);
311		}
312		last_header.expect("run_to_block empty block range")
313	}
314
315	pub fn run_to_block_with_finalize(n: u32) -> HeaderFor<Runtime> {
316		let mut last_header = None;
317		loop {
318			let block_number = pezframe_system::Pezpallet::<Runtime>::block_number();
319			if block_number >= n.into() {
320				break;
321			}
322			// Set the new block number and author
323			let header = pezframe_system::Pezpallet::<Runtime>::finalize();
324
325			let pre_digest = Digest {
326				logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())],
327			};
328			pezframe_system::Pezpallet::<Runtime>::reset_events();
329
330			let next_block_number = block_number + 1u32.into();
331			pezframe_system::Pezpallet::<Runtime>::initialize(
332				&next_block_number,
333				&header.hash(),
334				&pre_digest,
335			);
336			AllPalletsWithoutSystem::on_initialize(next_block_number);
337
338			let parent_head = HeadData(header.encode());
339			let sproof_builder = RelayStateSproofBuilder {
340				para_id: <Runtime>::SelfParaId::get(),
341				included_para_head: parent_head.clone().into(),
342				..Default::default()
343			};
344
345			let (relay_parent_storage_root, relay_chain_state) =
346				sproof_builder.into_state_root_and_proof();
347			let inherent_data = TeyrchainInherentData {
348				validation_data: PersistedValidationData {
349					parent_head,
350					relay_parent_number: (block_number.saturated_into::<u32>() * 2 + 1).into(),
351					relay_parent_storage_root,
352					max_pov_size: 100_000_000,
353				},
354				relay_chain_state,
355				downward_messages: Default::default(),
356				horizontal_messages: Default::default(),
357				relay_parent_descendants: Default::default(),
358				collator_peer_id: None,
359			};
360
361			let (inherent_data, downward_messages, horizontal_messages) =
362				deconstruct_teyrchain_inherent_data(inherent_data);
363
364			let _ =
365				pezcumulus_pezpallet_teyrchain_system::Pezpallet::<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 _ = pezpallet_timestamp::Pezpallet::<Runtime>::set(
374				Runtime::RuntimeOrigin::none(),
375				300_u32.into(),
376			);
377			AllPalletsWithoutSystem::on_finalize(next_block_number);
378			let header = pezframe_system::Pezpallet::<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 pezframe_system::Config>::RuntimeOrigin {
385		<Runtime as pezframe_system::Config>::RuntimeOrigin::root()
386	}
387
388	pub fn block_number() -> U256 {
389		pezframe_system::Pezpallet::<Runtime>::block_number().into()
390	}
391
392	pub fn origin_of(
393		account_id: AccountIdOf<Runtime>,
394	) -> <Runtime as pezframe_system::Config>::RuntimeOrigin {
395		<Runtime as pezframe_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<AssetsInHolding, 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: pezpallet_xcm::Config + pezcumulus_pezpallet_teyrchain_system::Config,
420		AllPalletsWithoutSystem,
421	> RuntimeHelper<Runtime, AllPalletsWithoutSystem>
422{
423	pub fn do_teleport_assets<HrmpChannelOpener>(
424		origin: <Runtime as pezframe_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: pezframe_support::inherent::ProvideInherent<
435			Call = pezcumulus_pezpallet_teyrchain_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		<pezpallet_xcm::Pezpallet<Runtime>>::limited_teleport_assets(
451			origin,
452			Box::new(dest.into()),
453			Box::new(beneficiary.into()),
454			Box::new((AssetId(asset.clone()), amount).into()),
455			Box::new(AssetId(asset).into()),
456			Unlimited,
457		)
458	}
459}
460
461impl<
462		Runtime: pezcumulus_pezpallet_teyrchain_system::Config + pezpallet_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(pezsp_io::hashing::blake2_256);
484		<<Runtime as pezpallet_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(pezsp_io::hashing::blake2_256);
512
513			<<Runtime as pezpallet_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(pezsp_io::hashing::blake2_256);
566		<<Runtime as pezpallet_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<TeyrchainSystem: pezcumulus_pezpallet_teyrchain_system::Config, AllPalletsWithoutSystem>
590	RuntimeHelper<TeyrchainSystem, AllPalletsWithoutSystem>
591{
592	pub fn xcm_max_weight(from: XcmReceivedFrom) -> Weight {
593		match from {
594			XcmReceivedFrom::Parent => TeyrchainSystem::ReservedDmpWeight::get(),
595			XcmReceivedFrom::Sibling => TeyrchainSystem::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: pezframe_system::Config + pezpallet_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<pezpallet_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<pezpallet_xcm::Event<Runtime>>>,
620	) -> Outcome {
621		<pezframe_system::Pezpallet<Runtime>>::events()
622			.into_iter()
623			.filter_map(|e| unwrap_pallet_xcm_event(e.event.encode()))
624			.find_map(|e| match e {
625				pezpallet_xcm::Event::Attempted { outcome } => Some(outcome),
626				_ => None,
627			})
628			.expect("No `pezpallet_xcm::Event::Attempted(outcome)` event found!")
629	}
630}
631
632impl<
633		Runtime: pezframe_system::Config + pezcumulus_pezpallet_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<pezcumulus_pezpallet_xcmp_queue::Event<Runtime>>,
640		>,
641	) -> Option<XcmHash> {
642		<pezframe_system::Pezpallet<Runtime>>::events()
643			.into_iter()
644			.filter_map(|e| unwrap_xcmp_queue_event(e.event.encode()))
645			.find_map(|e| match e {
646				pezcumulus_pezpallet_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: pezframe_support::traits::fungibles::metadata::Inspect<AccountId>
661		+ pezframe_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: pezframe_support::traits::fungibles::metadata::Inspect<AccountId>
674		+ pezframe_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 teyrchain-system's `create_inherent` in case the channel hasn't been opened before, and
683/// thus requires additional parameters for validating it: latest included teyrchain head and
684/// teyrchain 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: pezcumulus_pezpallet_teyrchain_system::Config,
690	T: ProvideInherent<Call = pezcumulus_pezpallet_teyrchain_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	let n = 1_u32;
704	let mut sproof_builder = RelayStateSproofBuilder {
705		para_id: sender,
706		included_para_head: Some(HeadData(included_head.encode())),
707		hrmp_egress_channel_index: Some(vec![recipient]),
708		current_slot: relay_slot,
709		..Default::default()
710	};
711	sproof_builder.hrmp_channels.insert(
712		HrmpChannelId { sender, recipient },
713		AbridgedHrmpChannel {
714			max_capacity: 10,
715			max_total_size: 10_000_000_u32,
716			max_message_size: 10_000_000_u32,
717			msg_count: 0,
718			total_size: 0_u32,
719			mqc_head: None,
720		},
721	);
722
723	let (relay_parent_storage_root, relay_chain_state) = sproof_builder.into_state_root_and_proof();
724	let vfp = PersistedValidationData {
725		relay_parent_number: n as RelayChainBlockNumber,
726		relay_parent_storage_root,
727		..Default::default()
728	};
729	// It is insufficient to push the validation function params
730	// to storage; they must also be included in the inherent data.
731	let inherent_data = {
732		let mut inherent_data = InherentData::default();
733		let system_inherent_data = TeyrchainInherentData {
734			validation_data: vfp,
735			relay_chain_state,
736			downward_messages: Default::default(),
737			horizontal_messages: Default::default(),
738			relay_parent_descendants: Default::default(),
739			collator_peer_id: None,
740		};
741		inherent_data
742			.put_data(
743				pezcumulus_primitives_teyrchain_inherent::INHERENT_IDENTIFIER,
744				&system_inherent_data,
745			)
746			.expect("failed to put VFP inherent");
747		inherent_data
748	};
749
750	// execute the block
751	T::create_inherent(&inherent_data)
752		.expect("got an inherent")
753		.dispatch_bypass_filter(RawOrigin::None.into())
754		.expect("dispatch succeeded");
755}
756
757impl<HrmpChannelSource: pezcumulus_primitives_core::XcmpMessageSource, AllPalletsWithoutSystem>
758	RuntimeHelper<HrmpChannelSource, AllPalletsWithoutSystem>
759{
760	pub fn take_xcm(sent_to_para_id: ParaId) -> Option<VersionedXcm<()>> {
761		match HrmpChannelSource::take_outbound_messages(10)[..] {
762			[(para_id, ref mut xcm_message_data)] if para_id.eq(&sent_to_para_id.into()) => {
763				let mut xcm_message_data = &xcm_message_data[..];
764				// decode
765				let _ = XcmpMessageFormat::decode(&mut xcm_message_data).expect("valid format");
766				VersionedXcm::<()>::decode_with_depth_limit(
767					MAX_XCM_DECODE_DEPTH,
768					&mut xcm_message_data,
769				)
770				.map(|x| Some(x))
771				.expect("result with xcm")
772			},
773			_ => return None,
774		}
775	}
776}