1pub mod mock_message_queue;
22
23extern crate alloc;
24
25pub use codec::Encode;
26pub use paste;
27
28pub use alloc::collections::vec_deque::VecDeque;
29pub use core::{cell::RefCell, marker::PhantomData};
30pub use frame_support::{
31 traits::{EnqueueMessage, Get, ProcessMessage, ProcessMessageError, ServiceQueues},
32 weights::{Weight, WeightMeter},
33};
34pub use sp_io::{hashing::blake2_256, TestExternalities};
35
36pub use polkadot_core_primitives::BlockNumber as RelayBlockNumber;
37pub use polkadot_parachain_primitives::primitives::{
38 DmpMessageHandler as DmpMessageHandlerT, Id as ParaId, XcmpMessageFormat,
39 XcmpMessageHandler as XcmpMessageHandlerT,
40};
41pub use polkadot_runtime_parachains::{
42 dmp,
43 inclusion::{AggregateMessageOrigin, UmpQueueId},
44};
45pub use xcm::{latest::prelude::*, VersionedXcm};
46pub use xcm_builder::ProcessXcmMessage;
47pub use xcm_executor::XcmExecutor;
48
49pub trait TestExt {
50 fn new_ext() -> sp_io::TestExternalities;
52 fn reset_ext();
54 fn execute_without_dispatch<R>(execute: impl FnOnce() -> R) -> R;
58 fn dispatch_xcm_buses();
60 fn execute_with<R>(execute: impl FnOnce() -> R) -> R {
64 let result = Self::execute_without_dispatch(execute);
65 Self::dispatch_xcm_buses();
66 result
67 }
68}
69
70pub enum MessageKind {
71 Ump,
72 Dmp,
73 Xcmp,
74}
75
76pub fn encode_xcm(message: Xcm<()>, message_kind: MessageKind) -> Vec<u8> {
78 match message_kind {
79 MessageKind::Ump | MessageKind::Dmp => VersionedXcm::<()>::from(message).encode(),
80 MessageKind::Xcmp => {
81 let fmt = XcmpMessageFormat::ConcatenatedVersionedXcm;
82 let mut outbound = fmt.encode();
83
84 let encoded = VersionedXcm::<()>::from(message).encode();
85 outbound.extend_from_slice(&encoded[..]);
86 outbound
87 },
88 }
89}
90
91pub fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash {
92 message.using_encoded(blake2_256)
93}
94
95#[macro_export]
109#[rustfmt::skip]
110macro_rules! decl_test_relay_chain {
111 (
112 pub struct $name:ident {
113 Runtime = $runtime:path,
114 RuntimeCall = $runtime_call:path,
115 RuntimeEvent = $runtime_event:path,
116 XcmConfig = $xcm_config:path,
117 MessageQueue = $mq:path,
118 System = $system:path,
119 new_ext = $new_ext:expr,
120 }
121 ) => {
122 pub struct $name;
123
124 $crate::__impl_ext!($name, $new_ext);
125
126 impl $crate::ProcessMessage for $name {
127 type Origin = $crate::ParaId;
128
129 fn process_message(
130 msg: &[u8],
131 para: Self::Origin,
132 meter: &mut $crate::WeightMeter,
133 id: &mut [u8; 32],
134 ) -> Result<bool, $crate::ProcessMessageError> {
135 use $crate::{Weight, AggregateMessageOrigin, UmpQueueId, ServiceQueues, EnqueueMessage};
136 use $mq as message_queue;
137 use $runtime_event as runtime_event;
138
139 Self::execute_with(|| {
140 <$mq as EnqueueMessage<AggregateMessageOrigin>>::enqueue_message(
141 msg.try_into().expect("Message too long"),
142 AggregateMessageOrigin::Ump(UmpQueueId::Para(para.clone()))
143 );
144
145 <$system>::reset_events();
146 <$mq as ServiceQueues>::service_queues(Weight::MAX);
147 let events = <$system>::events();
148 let event = events.last().expect("There must be at least one event");
149
150 match &event.event {
151 runtime_event::MessageQueue(
152 pallet_message_queue::Event::Processed {origin, ..}) => {
153 assert_eq!(origin, &AggregateMessageOrigin::Ump(UmpQueueId::Para(para)));
154 },
155 event => panic!("Unexpected event: {:#?}", event),
156 }
157 Ok(true)
158 })
159 }
160 }
161 };
162}
163
164#[macro_export]
180macro_rules! decl_test_parachain {
181 (
182 pub struct $name:ident {
183 Runtime = $runtime:path,
184 XcmpMessageHandler = $xcmp_message_handler:path,
185 DmpMessageHandler = $dmp_message_handler:path,
186 new_ext = $new_ext:expr,
187 }
188 ) => {
189 pub struct $name;
190
191 $crate::__impl_ext!($name, $new_ext);
192
193 impl $crate::XcmpMessageHandlerT for $name {
194 fn handle_xcmp_messages<
195 'a,
196 I: Iterator<Item = ($crate::ParaId, $crate::RelayBlockNumber, &'a [u8])>,
197 >(
198 iter: I,
199 max_weight: $crate::Weight,
200 ) -> $crate::Weight {
201 use $crate::{TestExt, XcmpMessageHandlerT};
202
203 $name::execute_with(|| {
204 <$xcmp_message_handler>::handle_xcmp_messages(iter, max_weight)
205 })
206 }
207 }
208
209 impl $crate::DmpMessageHandlerT for $name {
210 fn handle_dmp_messages(
211 iter: impl Iterator<Item = ($crate::RelayBlockNumber, Vec<u8>)>,
212 max_weight: $crate::Weight,
213 ) -> $crate::Weight {
214 use $crate::{DmpMessageHandlerT, TestExt};
215
216 $name::execute_with(|| {
217 <$dmp_message_handler>::handle_dmp_messages(iter, max_weight)
218 })
219 }
220 }
221 };
222}
223
224#[macro_export]
226macro_rules! __impl_ext {
227 ($name:ident, $new_ext:expr) => {
229 $crate::paste::paste! {
230 $crate::__impl_ext!(@impl $name, $new_ext, [<EXT_ $name:upper>]);
231 }
232 };
233 (@impl $name:ident, $new_ext:expr, $ext_name:ident) => {
235 thread_local! {
236 pub static $ext_name: $crate::RefCell<$crate::TestExternalities>
237 = $crate::RefCell::new($new_ext);
238 }
239
240 impl $crate::TestExt for $name {
241 fn new_ext() -> $crate::TestExternalities {
242 $new_ext
243 }
244
245 fn reset_ext() {
246 $ext_name.with(|v| *v.borrow_mut() = $new_ext);
247 }
248
249 fn execute_without_dispatch<R>(execute: impl FnOnce() -> R) -> R {
250 $ext_name.with(|v| v.borrow_mut().execute_with(execute))
251 }
252
253 fn dispatch_xcm_buses() {
254 while exists_messages_in_any_bus() {
255 if let Err(xcm_error) = process_relay_messages() {
256 panic!("Relay chain XCM execution failure: {:?}", xcm_error);
257 }
258 if let Err(xcm_error) = process_para_messages() {
259 panic!("Parachain XCM execution failure: {:?}", xcm_error);
260 }
261 }
262 }
263 }
264 };
265}
266
267thread_local! {
268 pub static PARA_MESSAGE_BUS: RefCell<VecDeque<(ParaId, Location, Xcm<()>)>>
269 = RefCell::new(VecDeque::new());
270 pub static RELAY_MESSAGE_BUS: RefCell<VecDeque<(Location, Xcm<()>)>>
271 = RefCell::new(VecDeque::new());
272}
273
274#[macro_export]
292macro_rules! decl_test_network {
293 (
294 pub struct $name:ident {
295 relay_chain = $relay_chain:ty,
296 parachains = vec![ $( ($para_id:expr, $parachain:ty), )* ],
297 }
298 ) => {
299 use $crate::Encode;
300 pub struct $name;
301
302 impl $name {
303 pub fn reset() {
304 use $crate::{TestExt, VecDeque};
305 $crate::RELAY_MESSAGE_BUS.with(|b| b.replace(VecDeque::new()));
307 $crate::PARA_MESSAGE_BUS.with(|b| b.replace(VecDeque::new()));
309 <$relay_chain>::reset_ext();
310 $( <$parachain>::reset_ext(); )*
311 }
312 }
313
314 fn exists_messages_in_any_bus() -> bool {
316 use $crate::{RELAY_MESSAGE_BUS, PARA_MESSAGE_BUS};
317 let no_relay_messages_left = RELAY_MESSAGE_BUS.with(|b| b.borrow().is_empty());
318 let no_parachain_messages_left = PARA_MESSAGE_BUS.with(|b| b.borrow().is_empty());
319 !(no_relay_messages_left && no_parachain_messages_left)
320 }
321
322 fn process_para_messages() -> $crate::XcmResult {
324 use $crate::{ProcessMessage, XcmpMessageHandlerT};
325
326 while let Some((para_id, destination, message)) = $crate::PARA_MESSAGE_BUS.with(
327 |b| b.borrow_mut().pop_front()) {
328 match destination.unpack() {
329 (1, []) => {
330 let encoded = $crate::encode_xcm(message, $crate::MessageKind::Ump);
331 let mut _id = [0; 32];
332 let r = <$relay_chain>::process_message(
333 encoded.as_slice(), para_id,
334 &mut $crate::WeightMeter::new(),
335 &mut _id,
336 );
337 match r {
338 Err($crate::ProcessMessageError::Overweight(required)) =>
339 return Err($crate::XcmError::WeightLimitReached(required)),
340 Err(_) => return Err($crate::XcmError::Unimplemented),
342 Ok(_) => (),
343 }
344 },
345 $(
346 (1, [$crate::Parachain(id)]) if *id == $para_id => {
347 let encoded = $crate::encode_xcm(message, $crate::MessageKind::Xcmp);
348 let messages = vec![(para_id, 1, &encoded[..])];
349 let _weight = <$parachain>::handle_xcmp_messages(
350 messages.into_iter(),
351 $crate::Weight::MAX,
352 );
353 },
354 )*
355 _ => {
356 return Err($crate::XcmError::Unroutable);
357 }
358 }
359 }
360
361 Ok(())
362 }
363
364 fn process_relay_messages() -> $crate::XcmResult {
366 use $crate::DmpMessageHandlerT;
367
368 while let Some((destination, message)) = $crate::RELAY_MESSAGE_BUS.with(
369 |b| b.borrow_mut().pop_front()) {
370 match destination.unpack() {
371 $(
372 (0, [$crate::Parachain(id)]) if *id == $para_id => {
373 let encoded = $crate::encode_xcm(message, $crate::MessageKind::Dmp);
374 let messages = vec![(1, encoded)];
376 let _weight = <$parachain>::handle_dmp_messages(
377 messages.into_iter(), $crate::Weight::MAX,
378 );
379 },
380 )*
381 _ => return Err($crate::XcmError::Transport("Only sends to children parachain.")),
382 }
383 }
384
385 Ok(())
386 }
387
388 pub struct ParachainXcmRouter<T>($crate::PhantomData<T>);
390
391 impl<T: $crate::Get<$crate::ParaId>> $crate::SendXcm for ParachainXcmRouter<T> {
392 type Ticket = ($crate::ParaId, $crate::Location, $crate::Xcm<()>);
393 fn validate(
394 destination: &mut Option<$crate::Location>,
395 message: &mut Option<$crate::Xcm<()>>,
396 ) -> $crate::SendResult<($crate::ParaId, $crate::Location, $crate::Xcm<()>)> {
397 use $crate::XcmpMessageHandlerT;
398
399 let d = destination.take().ok_or($crate::SendError::MissingArgument)?;
400 match d.unpack() {
401 (1, []) => {},
402 $(
403 (1, [$crate::Parachain(id)]) if id == &$para_id => {}
404 )*
405 _ => {
406 *destination = Some(d);
407 return Err($crate::SendError::NotApplicable)
408 },
409 }
410 let m = message.take().ok_or($crate::SendError::MissingArgument)?;
411 Ok(((T::get(), d, m), $crate::Assets::new()))
412 }
413 fn deliver(
414 triple: ($crate::ParaId, $crate::Location, $crate::Xcm<()>),
415 ) -> Result<$crate::XcmHash, $crate::SendError> {
416 let hash = $crate::helpers::derive_topic_id(&triple.2);
417 $crate::PARA_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(triple));
418 Ok(hash)
419 }
420 }
421
422 pub struct RelayChainXcmRouter;
424 impl $crate::SendXcm for RelayChainXcmRouter {
425 type Ticket = ($crate::Location, $crate::Xcm<()>);
426 fn validate(
427 destination: &mut Option<$crate::Location>,
428 message: &mut Option<$crate::Xcm<()>>,
429 ) -> $crate::SendResult<($crate::Location, $crate::Xcm<()>)> {
430 use $crate::DmpMessageHandlerT;
431
432 let d = destination.take().ok_or($crate::SendError::MissingArgument)?;
433 match d.unpack() {
434 $(
435 (0, [$crate::Parachain(id)]) if id == &$para_id => {},
436 )*
437 _ => {
438 *destination = Some(d);
439 return Err($crate::SendError::NotApplicable)
440 },
441 }
442 let m = message.take().ok_or($crate::SendError::MissingArgument)?;
443 Ok(((d, m), $crate::Assets::new()))
444 }
445 fn deliver(
446 pair: ($crate::Location, $crate::Xcm<()>),
447 ) -> Result<$crate::XcmHash, $crate::SendError> {
448 let hash = $crate::helpers::derive_topic_id(&pair.1);
449 $crate::RELAY_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(pair));
450 Ok(hash)
451 }
452 }
453 };
454}
455
456pub mod helpers {
457 use super::*;
458 use sp_runtime::testing::H256;
459 use std::collections::{HashMap, HashSet};
460
461 pub fn derive_topic_id<T>(message: &Xcm<T>) -> XcmHash {
463 if let Some(SetTopic(topic_id)) = message.last() {
464 *topic_id
465 } else {
466 fake_message_hash(message)
467 }
468 }
469
470 #[derive(Clone, Debug)]
513 pub struct TopicIdTracker {
514 ids: HashMap<String, HashSet<H256>>,
515 }
516 impl TopicIdTracker {
517 pub fn new() -> Self {
519 TopicIdTracker { ids: HashMap::new() }
520 }
521
522 pub fn assert_contains(&self, chain: &str, id: &H256) {
524 let ids = self
525 .ids
526 .get(chain)
527 .expect(&format!("No topic IDs recorded for chain '{}'", chain));
528
529 assert!(
530 ids.contains(id),
531 "Expected topic ID {:?} not found for chain '{}'. Found topic IDs: {:?}",
532 id,
533 chain,
534 ids
535 );
536 }
537
538 pub fn assert_id_seen_on_all_chains(&self, id: &H256) {
540 self.ids.keys().for_each(|chain| {
541 self.assert_contains(chain, id);
542 });
543 }
544
545 pub fn assert_only_id_seen_on_all_chains(&self, chain: &str) {
548 let ids = self
549 .ids
550 .get(chain)
551 .expect(&format!("No topic IDs recorded for chain '{}'", chain));
552
553 assert_eq!(
554 ids.len(),
555 1,
556 "Expected exactly one topic ID for chain '{}', but found {}: {:?}",
557 chain,
558 ids.len(),
559 ids
560 );
561
562 let id = *ids.iter().next().unwrap();
563 self.assert_id_seen_on_all_chains(&id);
564 }
565
566 pub fn assert_unique(&self) {
568 let unique_ids: HashSet<_> = self.ids.values().flatten().collect();
569 assert_eq!(
570 unique_ids.len(),
571 1,
572 "Expected exactly one topic ID, found {}: {:?}",
573 unique_ids.len(),
574 unique_ids
575 );
576 }
577
578 pub fn insert(&mut self, chain: &str, id: H256) {
580 self.ids.entry(chain.to_string()).or_default().insert(id);
581 }
582
583 pub fn insert_all(&mut self, chain: &str, ids: &[H256]) {
585 ids.iter().for_each(|&id| self.insert(chain, id));
586 }
587
588 pub fn insert_and_assert_unique(&mut self, chain: &str, id: H256) {
590 if let Some(existing_ids) = self.ids.get(chain) {
591 assert_eq!(
592 existing_ids.len(),
593 1,
594 "Expected exactly one topic ID for chain '{}', but found: {:?}",
595 chain,
596 existing_ids
597 );
598 let existing_id =
599 *existing_ids.iter().next().expect(&format!("Topic ID for chain '{}'", chain));
600 assert_eq!(
601 id, existing_id,
602 "Topic ID mismatch for chain '{}': expected {:?}, got {:?}",
603 id, existing_id, chain
604 );
605 } else {
606 self.insert(chain, id);
607 }
608 self.assert_unique();
609 }
610 }
611
612 #[cfg(test)]
613 mod tests {
614 use super::*;
615 use sp_runtime::testing::H256;
616
617 #[test]
618 #[should_panic(expected = "Expected exactly one topic ID")]
619 fn test_assert_unique_fails_with_multiple_ids() {
620 let mut tracker = TopicIdTracker::new();
621 let id1 = H256::repeat_byte(0x42);
622 let id2 = H256::repeat_byte(0x43);
623
624 tracker.insert("ChainA", id1);
625 tracker.insert("ChainB", id2);
626 tracker.assert_unique();
627 }
628
629 #[test]
630 #[should_panic(expected = "Topic ID mismatch")]
631 fn test_insert_and_assert_unique_mismatch() {
632 let mut tracker = TopicIdTracker::new();
633 let id1 = H256::repeat_byte(0x42);
634 let id2 = H256::repeat_byte(0x43);
635
636 tracker.insert_and_assert_unique("ChainA", id1);
637 tracker.insert_and_assert_unique("ChainA", id2);
638 }
639 }
640}