xcm_simulator/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Test kit to simulate cross-chain message passing and XCM execution.
18
19/// Implementation of a simple message queue.
20/// Used for sending messages.
21pub 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	/// Initialize the test environment.
51	fn new_ext() -> sp_io::TestExternalities;
52	/// Resets the state of the test environment.
53	fn reset_ext();
54	/// Execute code in the context of the test externalities, without automatic
55	/// message processing. All messages in the message buses can be processed
56	/// by calling `Self::dispatch_xcm_buses()`.
57	fn execute_without_dispatch<R>(execute: impl FnOnce() -> R) -> R;
58	/// Process all messages in the message buses
59	fn dispatch_xcm_buses();
60	/// Execute some code in the context of the test externalities, with
61	/// automatic message processing.
62	/// Messages are dispatched once the passed closure completes.
63	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
76/// Encodes the provided XCM message based on the `message_kind`.
77pub 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/// The macro is implementing upward message passing(UMP) for the provided relay
96/// chain struct. The struct has to provide the XCM configuration for the relay
97/// chain.
98///
99/// ```ignore
100/// decl_test_relay_chain! {
101///	    pub struct Relay {
102///	        Runtime = relay_chain::Runtime,
103///	        XcmConfig = relay_chain::XcmConfig,
104///	        new_ext = relay_ext(),
105///	    }
106///	}
107/// ```
108#[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/// The macro is implementing the `XcmMessageHandlerT` and `DmpMessageHandlerT`
165/// traits for the provided parachain struct. Expects the provided parachain
166/// struct to define the XcmpMessageHandler and DmpMessageHandler pallets that
167/// contain the message handling logic.
168///
169/// ```ignore
170/// decl_test_parachain! {
171/// 	    pub struct ParaA {
172/// 	        Runtime = parachain::Runtime,
173/// 	        XcmpMessageHandler = parachain::MsgQueue,
174/// 	        DmpMessageHandler = parachain::MsgQueue,
175/// 	        new_ext = para_ext(),
176/// 	    }
177/// }
178/// ```
179#[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/// Implements the `TestExt` trait for a specified struct.
225#[macro_export]
226macro_rules! __impl_ext {
227	// entry point: generate ext name
228	($name:ident, $new_ext:expr) => {
229		$crate::paste::paste! {
230			$crate::__impl_ext!(@impl $name, $new_ext, [<EXT_ $name:upper>]);
231		}
232	};
233	// impl
234	(@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/// Declares a test network that consists of a relay chain and multiple
275/// parachains. Expects a network struct as an argument and implements testing
276/// functionality, `ParachainXcmRouter` and the `RelayChainXcmRouter`. The
277/// struct needs to contain the relay chain struct and an indexed list of
278/// parachains that are going to be in the network.
279///
280/// ```ignore
281/// decl_test_network! {
282/// 	    pub struct ExampleNet {
283/// 	        relay_chain = Relay,
284/// 	        parachains = vec![
285/// 	            (1, ParaA),
286/// 	            (2, ParaB),
287/// 	        ],
288/// 	    }
289/// }
290/// ```
291#[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				// Reset relay chain message bus.
306				$crate::RELAY_MESSAGE_BUS.with(|b| b.replace(VecDeque::new()));
307				// Reset parachain message bus.
308				$crate::PARA_MESSAGE_BUS.with(|b| b.replace(VecDeque::new()));
309				<$relay_chain>::reset_ext();
310				$( <$parachain>::reset_ext(); )*
311			}
312		}
313
314		/// Check if any messages exist in either message bus.
315		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		/// Process all messages originating from parachains.
323		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							// Not really the correct error, but there is no "undecodable".
341							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		/// Process all messages originating from the relay chain.
365		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							// NOTE: RelayChainBlockNumber is hard-coded to 1
375							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		/// XCM router for parachain.
389		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		/// XCM router for relay chain.
423		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	/// Derives a topic ID for an XCM in tests.
462	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	/// A test utility for tracking XCM topic IDs
471	#[derive(Clone)]
472	pub struct TopicIdTracker {
473		ids: HashMap<String, H256>,
474	}
475	impl TopicIdTracker {
476		/// Initialises a new, empty topic ID tracker.
477		pub fn new() -> Self {
478			TopicIdTracker { ids: HashMap::new() }
479		}
480
481		/// Asserts that exactly one unique topic ID is present across all captured entries.
482		pub fn assert_unique(&self) {
483			let unique_ids: HashSet<_> = self.ids.values().collect();
484			assert_eq!(
485				unique_ids.len(),
486				1,
487				"Expected exactly one topic ID, found {}: {:?}",
488				unique_ids.len(),
489				unique_ids
490			);
491		}
492
493		/// Inserts a topic ID with the given chain name in the captor.
494		pub fn insert(&mut self, chain: &str, id: H256) {
495			self.ids.insert(chain.to_string(), id);
496		}
497
498		/// Inserts a topic ID for a given chain and then asserts global uniqueness.
499		pub fn insert_and_assert_unique(&mut self, chain: &str, id: H256) {
500			if let Some(existing_id) = self.ids.get(chain) {
501				assert_eq!(
502					id, *existing_id,
503					"Topic ID mismatch for chain '{}': expected {:?}, got {:?}",
504					id, existing_id, chain
505				);
506			} else {
507				self.insert(chain, id);
508			}
509			self.assert_unique();
510		}
511	}
512}