1use core::marker::PhantomData;
17
18use codec::{Decode, DecodeLimit};
19use cumulus_primitives_core::{
20 relay_chain::Slot, AbridgedHrmpChannel, ParaId, PersistedValidationData,
21};
22use cumulus_primitives_parachain_inherent::ParachainInherentData;
23use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
24use frame_support::{
25 dispatch::{DispatchResult, GetDispatchInfo, RawOrigin},
26 inherent::{InherentData, ProvideInherent},
27 pallet_prelude::Get,
28 traits::{OnFinalize, OnInitialize, OriginTrait, UnfilteredDispatchable},
29 weights::Weight,
30};
31use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
32use polkadot_parachain_primitives::primitives::{
33 HeadData, HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat,
34};
35use sp_consensus_aura::{SlotDuration, AURA_ENGINE_ID};
36use sp_core::{Encode, U256};
37use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem, SaturatedConversion};
38use xcm::{
39 latest::{Asset, Location, XcmContext, XcmHash},
40 prelude::*,
41 VersionedXcm, MAX_XCM_DECODE_DEPTH,
42};
43use xcm_executor::{traits::TransactAsset, AssetsInHolding};
44
45pub mod test_cases;
46
47pub type BalanceOf<Runtime> = <Runtime as pallet_balances::Config>::Balance;
48pub type AccountIdOf<Runtime> = <Runtime as frame_system::Config>::AccountId;
49pub type RuntimeCallOf<Runtime> = <Runtime as frame_system::Config>::RuntimeCall;
50pub type ValidatorIdOf<Runtime> = <Runtime as pallet_session::Config>::ValidatorId;
51pub type SessionKeysOf<Runtime> = <Runtime as pallet_session::Config>::Keys;
52
53pub struct CollatorSessionKey<
54 Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config,
55> {
56 collator: AccountIdOf<Runtime>,
57 validator: ValidatorIdOf<Runtime>,
58 key: SessionKeysOf<Runtime>,
59}
60
61pub struct CollatorSessionKeys<
62 Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config,
63> {
64 items: Vec<CollatorSessionKey<Runtime>>,
65}
66
67impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config>
68 CollatorSessionKey<Runtime>
69{
70 pub fn new(
71 collator: AccountIdOf<Runtime>,
72 validator: ValidatorIdOf<Runtime>,
73 key: SessionKeysOf<Runtime>,
74 ) -> Self {
75 Self { collator, validator, key }
76 }
77}
78
79impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config> Default
80 for CollatorSessionKeys<Runtime>
81{
82 fn default() -> Self {
83 Self { items: vec![] }
84 }
85}
86
87impl<Runtime: frame_system::Config + pallet_balances::Config + pallet_session::Config>
88 CollatorSessionKeys<Runtime>
89{
90 pub fn new(
91 collator: AccountIdOf<Runtime>,
92 validator: ValidatorIdOf<Runtime>,
93 key: SessionKeysOf<Runtime>,
94 ) -> Self {
95 Self { items: vec![CollatorSessionKey::new(collator, validator, key)] }
96 }
97
98 pub fn add(mut self, item: CollatorSessionKey<Runtime>) -> Self {
99 self.items.push(item);
100 self
101 }
102
103 pub fn collators(&self) -> Vec<AccountIdOf<Runtime>> {
104 self.items.iter().map(|item| item.collator.clone()).collect::<Vec<_>>()
105 }
106
107 pub fn session_keys(
108 &self,
109 ) -> Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)> {
110 self.items
111 .iter()
112 .map(|item| (item.collator.clone(), item.validator.clone(), item.key.clone()))
113 .collect::<Vec<_>>()
114 }
115}
116
117pub struct SlotDurations {
118 pub relay: SlotDuration,
119 pub para: SlotDuration,
120}
121
122pub trait BasicParachainRuntime:
125 frame_system::Config
126 + pallet_balances::Config
127 + pallet_session::Config
128 + pallet_xcm::Config
129 + parachain_info::Config
130 + pallet_collator_selection::Config
131 + cumulus_pallet_parachain_system::Config
132 + pallet_timestamp::Config
133{
134}
135
136impl<T> BasicParachainRuntime for T
137where
138 T: frame_system::Config
139 + pallet_balances::Config
140 + pallet_session::Config
141 + pallet_xcm::Config
142 + parachain_info::Config
143 + pallet_collator_selection::Config
144 + cumulus_pallet_parachain_system::Config
145 + pallet_timestamp::Config,
146 ValidatorIdOf<T>: From<AccountIdOf<T>>,
147{
148}
149
150pub struct ExtBuilder<Runtime: BasicParachainRuntime> {
152 balances: Vec<(AccountIdOf<Runtime>, BalanceOf<Runtime>)>,
154 collators: Vec<AccountIdOf<Runtime>>,
156 keys: Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)>,
158 safe_xcm_version: Option<XcmVersion>,
160 para_id: Option<ParaId>,
162 _runtime: PhantomData<Runtime>,
163}
164
165impl<Runtime: BasicParachainRuntime> Default for ExtBuilder<Runtime> {
166 fn default() -> ExtBuilder<Runtime> {
167 ExtBuilder {
168 balances: vec![],
169 collators: vec![],
170 keys: vec![],
171 safe_xcm_version: None,
172 para_id: None,
173 _runtime: PhantomData,
174 }
175 }
176}
177
178impl<Runtime: BasicParachainRuntime> ExtBuilder<Runtime> {
179 pub fn with_balances(
180 mut self,
181 balances: Vec<(AccountIdOf<Runtime>, BalanceOf<Runtime>)>,
182 ) -> Self {
183 self.balances = balances;
184 self
185 }
186 pub fn with_collators(mut self, collators: Vec<AccountIdOf<Runtime>>) -> Self {
187 self.collators = collators;
188 self
189 }
190
191 pub fn with_session_keys(
192 mut self,
193 keys: Vec<(AccountIdOf<Runtime>, ValidatorIdOf<Runtime>, SessionKeysOf<Runtime>)>,
194 ) -> Self {
195 self.keys = keys;
196 self
197 }
198
199 pub fn with_tracing(self) -> Self {
200 sp_tracing::try_init_simple();
201 self
202 }
203
204 pub fn with_safe_xcm_version(mut self, safe_xcm_version: XcmVersion) -> Self {
205 self.safe_xcm_version = Some(safe_xcm_version);
206 self
207 }
208
209 pub fn with_para_id(mut self, para_id: ParaId) -> Self {
210 self.para_id = Some(para_id);
211 self
212 }
213
214 pub fn build(self) -> sp_io::TestExternalities {
215 let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
216
217 pallet_xcm::GenesisConfig::<Runtime> {
218 safe_xcm_version: self.safe_xcm_version,
219 ..Default::default()
220 }
221 .assimilate_storage(&mut t)
222 .unwrap();
223
224 if let Some(para_id) = self.para_id {
225 parachain_info::GenesisConfig::<Runtime> {
226 parachain_id: para_id,
227 ..Default::default()
228 }
229 .assimilate_storage(&mut t)
230 .unwrap();
231 }
232
233 pallet_balances::GenesisConfig::<Runtime> { balances: self.balances }
234 .assimilate_storage(&mut t)
235 .unwrap();
236
237 pallet_collator_selection::GenesisConfig::<Runtime> {
238 invulnerables: self.collators.clone(),
239 candidacy_bond: Default::default(),
240 desired_candidates: Default::default(),
241 }
242 .assimilate_storage(&mut t)
243 .unwrap();
244
245 pallet_session::GenesisConfig::<Runtime> { keys: self.keys, ..Default::default() }
246 .assimilate_storage(&mut t)
247 .unwrap();
248
249 let mut ext = sp_io::TestExternalities::new(t);
250
251 ext.execute_with(|| {
252 frame_system::Pallet::<Runtime>::set_block_number(1u32.into());
253 });
254
255 ext
256 }
257}
258
259pub struct RuntimeHelper<Runtime, AllPalletsWithoutSystem>(
260 PhantomData<(Runtime, AllPalletsWithoutSystem)>,
261);
262impl<
265 Runtime: frame_system::Config + cumulus_pallet_parachain_system::Config + pallet_timestamp::Config,
266 AllPalletsWithoutSystem,
267 > RuntimeHelper<Runtime, AllPalletsWithoutSystem>
268where
269 AccountIdOf<Runtime>:
270 Into<<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId>,
271 AllPalletsWithoutSystem:
272 OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
273{
274 pub fn run_to_block(n: u32, author: AccountIdOf<Runtime>) -> HeaderFor<Runtime> {
275 let mut last_header = None;
276 loop {
277 let block_number = frame_system::Pallet::<Runtime>::block_number();
278 if block_number >= n.into() {
279 break
280 }
281 let header = frame_system::Pallet::<Runtime>::finalize();
286
287 let pre_digest =
288 Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, author.encode())] };
289 frame_system::Pallet::<Runtime>::reset_events();
290
291 let next_block_number = block_number + 1u32.into();
292 frame_system::Pallet::<Runtime>::initialize(
293 &next_block_number,
294 &header.hash(),
295 &pre_digest,
296 );
297 AllPalletsWithoutSystem::on_initialize(next_block_number);
298 last_header = Some(header);
299 }
300 last_header.expect("run_to_block empty block range")
301 }
302
303 pub fn run_to_block_with_finalize(n: u32) -> HeaderFor<Runtime> {
304 let mut last_header = None;
305 loop {
306 let block_number = frame_system::Pallet::<Runtime>::block_number();
307 if block_number >= n.into() {
308 break
309 }
310 let header = frame_system::Pallet::<Runtime>::finalize();
312
313 let pre_digest = Digest {
314 logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())],
315 };
316 frame_system::Pallet::<Runtime>::reset_events();
317
318 let next_block_number = block_number + 1u32.into();
319 frame_system::Pallet::<Runtime>::initialize(
320 &next_block_number,
321 &header.hash(),
322 &pre_digest,
323 );
324 AllPalletsWithoutSystem::on_initialize(next_block_number);
325
326 let parent_head = HeadData(header.encode());
327 let sproof_builder = RelayStateSproofBuilder {
328 para_id: <Runtime>::SelfParaId::get(),
329 included_para_head: parent_head.clone().into(),
330 ..Default::default()
331 };
332
333 let (relay_parent_storage_root, relay_chain_state) =
334 sproof_builder.into_state_root_and_proof();
335 let inherent_data = ParachainInherentData {
336 validation_data: PersistedValidationData {
337 parent_head,
338 relay_parent_number: (block_number.saturated_into::<u32>() * 2 + 1).into(),
339 relay_parent_storage_root,
340 max_pov_size: 100_000_000,
341 },
342 relay_chain_state,
343 downward_messages: Default::default(),
344 horizontal_messages: Default::default(),
345 };
346
347 let _ = cumulus_pallet_parachain_system::Pallet::<Runtime>::set_validation_data(
348 Runtime::RuntimeOrigin::none(),
349 inherent_data,
350 );
351 let _ = pallet_timestamp::Pallet::<Runtime>::set(
352 Runtime::RuntimeOrigin::none(),
353 300_u32.into(),
354 );
355 AllPalletsWithoutSystem::on_finalize(next_block_number);
356 let header = frame_system::Pallet::<Runtime>::finalize();
357 last_header = Some(header);
358 }
359 last_header.expect("run_to_block empty block range")
360 }
361
362 pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
363 <Runtime as frame_system::Config>::RuntimeOrigin::root()
364 }
365
366 pub fn block_number() -> U256 {
367 frame_system::Pallet::<Runtime>::block_number().into()
368 }
369
370 pub fn origin_of(
371 account_id: AccountIdOf<Runtime>,
372 ) -> <Runtime as frame_system::Config>::RuntimeOrigin {
373 <Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id.into())
374 }
375}
376
377impl<XcmConfig: xcm_executor::Config, AllPalletsWithoutSystem>
378 RuntimeHelper<XcmConfig, AllPalletsWithoutSystem>
379{
380 pub fn do_transfer(
381 from: Location,
382 to: Location,
383 (asset, amount): (Location, u128),
384 ) -> Result<AssetsInHolding, XcmError> {
385 <XcmConfig::AssetTransactor as TransactAsset>::transfer_asset(
386 &Asset { id: AssetId(asset), fun: Fungible(amount) },
387 &from,
388 &to,
389 &XcmContext::with_message_id([0; 32]),
392 )
393 }
394}
395
396impl<
397 Runtime: pallet_xcm::Config + cumulus_pallet_parachain_system::Config,
398 AllPalletsWithoutSystem,
399 > RuntimeHelper<Runtime, AllPalletsWithoutSystem>
400{
401 pub fn do_teleport_assets<HrmpChannelOpener>(
402 origin: <Runtime as frame_system::Config>::RuntimeOrigin,
403 dest: Location,
404 beneficiary: Location,
405 (asset, amount): (Location, u128),
406 open_hrmp_channel: Option<(u32, u32)>,
407 included_head: HeaderFor<Runtime>,
408 slot_digest: &[u8],
409 slot_durations: &SlotDurations,
410 ) -> DispatchResult
411 where
412 HrmpChannelOpener: frame_support::inherent::ProvideInherent<
413 Call = cumulus_pallet_parachain_system::Call<Runtime>,
414 >,
415 {
416 if let Some((source_para_id, target_para_id)) = open_hrmp_channel {
418 mock_open_hrmp_channel::<Runtime, HrmpChannelOpener>(
419 source_para_id.into(),
420 target_para_id.into(),
421 included_head,
422 slot_digest,
423 slot_durations,
424 );
425 }
426
427 <pallet_xcm::Pallet<Runtime>>::limited_teleport_assets(
429 origin,
430 Box::new(dest.into()),
431 Box::new(beneficiary.into()),
432 Box::new((AssetId(asset), amount).into()),
433 0,
434 Unlimited,
435 )
436 }
437}
438
439impl<
440 Runtime: cumulus_pallet_parachain_system::Config + pallet_xcm::Config,
441 AllPalletsWithoutSystem,
442 > RuntimeHelper<Runtime, AllPalletsWithoutSystem>
443{
444 pub fn execute_as_governance(call: Vec<u8>) -> Outcome {
445 let xcm = Xcm(vec![
447 UnpaidExecution { weight_limit: Unlimited, check_origin: None },
448 Transact {
449 origin_kind: OriginKind::Superuser,
450 call: call.into(),
451 fallback_max_weight: None,
452 },
453 ExpectTransactStatus(MaybeErrorCode::Success),
454 ]);
455
456 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
458 <<Runtime as pallet_xcm::Config>::XcmExecutor>::prepare_and_execute(
459 Location::parent(),
460 xcm,
461 &mut hash,
462 Self::xcm_max_weight(XcmReceivedFrom::Parent),
463 Weight::zero(),
464 )
465 }
466
467 pub fn execute_as_origin<Call: GetDispatchInfo + Encode>(
468 (origin, origin_kind): (Location, OriginKind),
469 call: Call,
470 maybe_buy_execution_fee: Option<Asset>,
471 ) -> Outcome {
472 let mut instructions = if let Some(buy_execution_fee) = maybe_buy_execution_fee {
473 vec![
474 WithdrawAsset(buy_execution_fee.clone().into()),
475 BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
476 ]
477 } else {
478 vec![UnpaidExecution { check_origin: None, weight_limit: Unlimited }]
479 };
480
481 instructions.extend(vec![
483 Transact { origin_kind, call: call.encode().into(), fallback_max_weight: None },
484 ExpectTransactStatus(MaybeErrorCode::Success),
485 ]);
486 let xcm = Xcm(instructions);
487
488 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
490 <<Runtime as pallet_xcm::Config>::XcmExecutor>::prepare_and_execute(
491 origin.clone(),
492 xcm,
493 &mut hash,
494 Self::xcm_max_weight(if origin == Location::parent() {
495 XcmReceivedFrom::Parent
496 } else {
497 XcmReceivedFrom::Sibling
498 }),
499 Weight::zero(),
500 )
501 }
502}
503
504pub enum XcmReceivedFrom {
505 Parent,
506 Sibling,
507}
508
509impl<ParachainSystem: cumulus_pallet_parachain_system::Config, AllPalletsWithoutSystem>
510 RuntimeHelper<ParachainSystem, AllPalletsWithoutSystem>
511{
512 pub fn xcm_max_weight(from: XcmReceivedFrom) -> Weight {
513 match from {
514 XcmReceivedFrom::Parent => ParachainSystem::ReservedDmpWeight::get(),
515 XcmReceivedFrom::Sibling => ParachainSystem::ReservedXcmpWeight::get(),
516 }
517 }
518}
519
520impl<Runtime: frame_system::Config + pallet_xcm::Config, AllPalletsWithoutSystem>
521 RuntimeHelper<Runtime, AllPalletsWithoutSystem>
522{
523 pub fn assert_pallet_xcm_event_outcome(
524 unwrap_pallet_xcm_event: &Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
525 assert_outcome: fn(Outcome),
526 ) {
527 assert_outcome(Self::get_pallet_xcm_event_outcome(unwrap_pallet_xcm_event));
528 }
529
530 pub fn get_pallet_xcm_event_outcome(
531 unwrap_pallet_xcm_event: &Box<dyn Fn(Vec<u8>) -> Option<pallet_xcm::Event<Runtime>>>,
532 ) -> Outcome {
533 <frame_system::Pallet<Runtime>>::events()
534 .into_iter()
535 .filter_map(|e| unwrap_pallet_xcm_event(e.event.encode()))
536 .find_map(|e| match e {
537 pallet_xcm::Event::Attempted { outcome } => Some(outcome),
538 _ => None,
539 })
540 .expect("No `pallet_xcm::Event::Attempted(outcome)` event found!")
541 }
542}
543
544impl<
545 Runtime: frame_system::Config + cumulus_pallet_xcmp_queue::Config,
546 AllPalletsWithoutSystem,
547 > RuntimeHelper<Runtime, AllPalletsWithoutSystem>
548{
549 pub fn xcmp_queue_message_sent(
550 unwrap_xcmp_queue_event: Box<
551 dyn Fn(Vec<u8>) -> Option<cumulus_pallet_xcmp_queue::Event<Runtime>>,
552 >,
553 ) -> Option<XcmHash> {
554 <frame_system::Pallet<Runtime>>::events()
555 .into_iter()
556 .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode()))
557 .find_map(|e| match e {
558 cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } =>
559 Some(message_hash),
560 _ => None,
561 })
562 }
563}
564
565pub fn assert_metadata<Fungibles, AccountId>(
566 asset_id: impl Into<Fungibles::AssetId> + Clone,
567 expected_name: &str,
568 expected_symbol: &str,
569 expected_decimals: u8,
570) where
571 Fungibles: frame_support::traits::fungibles::metadata::Inspect<AccountId>
572 + frame_support::traits::fungibles::Inspect<AccountId>,
573{
574 assert_eq!(Fungibles::name(asset_id.clone().into()), Vec::from(expected_name),);
575 assert_eq!(Fungibles::symbol(asset_id.clone().into()), Vec::from(expected_symbol),);
576 assert_eq!(Fungibles::decimals(asset_id.into()), expected_decimals);
577}
578
579pub fn assert_total<Fungibles, AccountId>(
580 asset_id: impl Into<Fungibles::AssetId> + Clone,
581 expected_total_issuance: impl Into<Fungibles::Balance>,
582 expected_active_issuance: impl Into<Fungibles::Balance>,
583) where
584 Fungibles: frame_support::traits::fungibles::metadata::Inspect<AccountId>
585 + frame_support::traits::fungibles::Inspect<AccountId>,
586{
587 assert_eq!(Fungibles::total_issuance(asset_id.clone().into()), expected_total_issuance.into());
588 assert_eq!(Fungibles::active_issuance(asset_id.into()), expected_active_issuance.into());
589}
590
591pub fn mock_open_hrmp_channel<
600 C: cumulus_pallet_parachain_system::Config,
601 T: ProvideInherent<Call = cumulus_pallet_parachain_system::Call<C>>,
602>(
603 sender: ParaId,
604 recipient: ParaId,
605 included_head: HeaderFor<C>,
606 mut slot_digest: &[u8],
607 slot_durations: &SlotDurations,
608) {
609 let slot = Slot::decode(&mut slot_digest).expect("failed to decode digest");
610 let timestamp = slot.saturating_mul(slot_durations.para.as_millis());
612 let relay_slot = Slot::from_timestamp(timestamp.into(), slot_durations.relay);
613
614 let n = 1_u32;
615 let mut sproof_builder = RelayStateSproofBuilder {
616 para_id: sender,
617 included_para_head: Some(HeadData(included_head.encode())),
618 hrmp_egress_channel_index: Some(vec![recipient]),
619 current_slot: relay_slot,
620 ..Default::default()
621 };
622 sproof_builder.hrmp_channels.insert(
623 HrmpChannelId { sender, recipient },
624 AbridgedHrmpChannel {
625 max_capacity: 10,
626 max_total_size: 10_000_000_u32,
627 max_message_size: 10_000_000_u32,
628 msg_count: 0,
629 total_size: 0_u32,
630 mqc_head: None,
631 },
632 );
633
634 let (relay_parent_storage_root, relay_chain_state) = sproof_builder.into_state_root_and_proof();
635 let vfp = PersistedValidationData {
636 relay_parent_number: n as RelayChainBlockNumber,
637 relay_parent_storage_root,
638 ..Default::default()
639 };
640 let inherent_data = {
643 let mut inherent_data = InherentData::default();
644 let system_inherent_data = ParachainInherentData {
645 validation_data: vfp,
646 relay_chain_state,
647 downward_messages: Default::default(),
648 horizontal_messages: Default::default(),
649 };
650 inherent_data
651 .put_data(
652 cumulus_primitives_parachain_inherent::INHERENT_IDENTIFIER,
653 &system_inherent_data,
654 )
655 .expect("failed to put VFP inherent");
656 inherent_data
657 };
658
659 T::create_inherent(&inherent_data)
661 .expect("got an inherent")
662 .dispatch_bypass_filter(RawOrigin::None.into())
663 .expect("dispatch succeeded");
664}
665
666impl<HrmpChannelSource: cumulus_primitives_core::XcmpMessageSource, AllPalletsWithoutSystem>
667 RuntimeHelper<HrmpChannelSource, AllPalletsWithoutSystem>
668{
669 pub fn take_xcm(sent_to_para_id: ParaId) -> Option<VersionedXcm<()>> {
670 match HrmpChannelSource::take_outbound_messages(10)[..] {
671 [(para_id, ref mut xcm_message_data)] if para_id.eq(&sent_to_para_id.into()) => {
672 let mut xcm_message_data = &xcm_message_data[..];
673 let _ = XcmpMessageFormat::decode(&mut xcm_message_data).expect("valid format");
675 VersionedXcm::<()>::decode_with_depth_limit(
676 MAX_XCM_DECODE_DEPTH,
677 &mut xcm_message_data,
678 )
679 .map(|x| Some(x))
680 .expect("result with xcm")
681 },
682 _ => return None,
683 }
684 }
685}