Skip to main content

staging_xcm/
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//! Cross-Consensus Message format data structures.
18
19// NOTE, this crate is meant to be used in many different environments, notably wasm, but not
20// necessarily related to FRAME or even Substrate.
21//
22// Hence, `no_std` rather than sp-runtime.
23#![cfg_attr(not(feature = "std"), no_std)]
24
25extern crate alloc;
26
27use codec::{
28	Decode, DecodeLimit, DecodeWithMemTracking, Encode, Error as CodecError, Input, MaxEncodedLen,
29};
30use derive_where::derive_where;
31use frame_support::dispatch::GetDispatchInfo;
32use scale_info::TypeInfo;
33
34pub mod v3;
35pub mod v4;
36pub mod v5;
37
38pub mod lts {
39	pub use super::v4::*;
40}
41
42pub mod latest {
43	pub use super::v5::*;
44}
45
46mod double_encoded;
47pub use double_encoded::DoubleEncoded;
48
49mod utils;
50
51#[cfg(test)]
52mod tests;
53
54/// Maximum nesting level for XCM decoding.
55pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
56/// The maximal number of instructions in an XCM before decoding fails.
57///
58/// This is a deliberate limit - not a technical one.
59pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
60
61/// A version of XCM.
62pub type Version = u32;
63
64#[derive(Clone, Eq, PartialEq, Debug)]
65pub enum Unsupported {}
66impl Encode for Unsupported {}
67impl Decode for Unsupported {
68	fn decode<I: Input>(_: &mut I) -> Result<Self, CodecError> {
69		Err("Not decodable".into())
70	}
71}
72
73/// Attempt to convert `self` into a particular version of itself.
74pub trait IntoVersion: Sized {
75	/// Consume `self` and return same value expressed in some particular `version` of XCM.
76	fn into_version(self, version: Version) -> Result<Self, ()>;
77
78	/// Consume `self` and return same value expressed the latest version of XCM.
79	fn into_latest(self) -> Result<Self, ()> {
80		self.into_version(latest::VERSION)
81	}
82}
83
84pub trait TryAs<T> {
85	fn try_as(&self) -> Result<&T, ()>;
86}
87
88// Macro that generated versioned wrapper types.
89// NOTE: converting a v4 type into a versioned type will make it v5.
90macro_rules! versioned_type {
91	($(#[$attr:meta])* pub enum $n:ident {
92		$(#[$index3:meta])+
93		V3($v3:ty),
94		$(#[$index4:meta])+
95		V4($v4:ty),
96		$(#[$index5:meta])+
97		V5($v5:ty),
98	}) => {
99		#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
100		#[codec(encode_bound())]
101		#[codec(decode_bound())]
102		#[scale_info(replace_segment("staging_xcm", "xcm"))]
103		$(#[$attr])*
104		pub enum $n {
105			$(#[$index3])*
106			V3($v3),
107			$(#[$index4])*
108			V4($v4),
109			$(#[$index5])*
110			V5($v5),
111		}
112		impl $n {
113			pub fn try_as<T>(&self) -> Result<&T, ()> where Self: TryAs<T> {
114				<Self as TryAs<T>>::try_as(&self)
115			}
116		}
117		impl TryAs<$v3> for $n {
118			fn try_as(&self) -> Result<&$v3, ()> {
119				match &self {
120					Self::V3(ref x) => Ok(x),
121					_ => Err(()),
122				}
123			}
124		}
125		impl TryAs<$v4> for $n {
126			fn try_as(&self) -> Result<&$v4, ()> {
127				match &self {
128					Self::V4(ref x) => Ok(x),
129					_ => Err(()),
130				}
131			}
132		}
133		impl TryAs<$v5> for $n {
134			fn try_as(&self) -> Result<&$v5, ()> {
135				match &self {
136					Self::V5(ref x) => Ok(x),
137					_ => Err(()),
138				}
139			}
140		}
141		impl IntoVersion for $n {
142			fn into_version(self, n: Version) -> Result<Self, ()> {
143				let version = self.identify_version();
144				if version == n {
145					Ok(self)
146				} else {
147					Ok(match n {
148						3 => Self::V3(self.try_into()?),
149						4 => Self::V4(self.try_into()?),
150						5 => Self::V5(self.try_into()?),
151						_ => return Err(()),
152					})
153				}
154			}
155		}
156		impl From<$v3> for $n {
157			fn from(x: $v3) -> Self {
158				$n::V3(x.into())
159			}
160		}
161		impl<T: Into<$v5>> From<T> for $n {
162			fn from(x: T) -> Self {
163				$n::V5(x.into())
164			}
165		}
166		impl TryFrom<$n> for $v3 {
167			type Error = ();
168			fn try_from(x: $n) -> Result<Self, ()> {
169				use $n::*;
170				match x {
171					V3(x) => Ok(x),
172					V4(x) => x.try_into().map_err(|_| ()),
173					V5(x) => {
174						let v4: $v4 = x.try_into().map_err(|_| ())?;
175						v4.try_into().map_err(|_| ())
176					}
177				}
178			}
179		}
180		impl TryFrom<$n> for $v4 {
181			type Error = ();
182			fn try_from(x: $n) -> Result<Self, ()> {
183				use $n::*;
184				match x {
185					V3(x) => x.try_into().map_err(|_| ()),
186					V4(x) => Ok(x),
187					V5(x) => x.try_into().map_err(|_| ()),
188				}
189			}
190		}
191		impl TryFrom<$n> for $v5 {
192			type Error = ();
193			fn try_from(x: $n) -> Result<Self, ()> {
194				use $n::*;
195				match x {
196					V3(x) => {
197						let v4: $v4 = x.try_into().map_err(|_| ())?;
198						v4.try_into().map_err(|_| ())
199					},
200					V4(x) => x.try_into().map_err(|_| ()),
201					V5(x) => Ok(x),
202				}
203			}
204		}
205		impl MaxEncodedLen for $n {
206			fn max_encoded_len() -> usize {
207				<$v3>::max_encoded_len()
208			}
209		}
210		impl IdentifyVersion for $n {
211			fn identify_version(&self) -> Version {
212				use $n::*;
213				match self {
214					V3(_) => v3::VERSION,
215					V4(_) => v4::VERSION,
216					V5(_) => v5::VERSION,
217				}
218			}
219		}
220	};
221}
222
223versioned_type! {
224	/// A single version's `AssetId` value, together with its version code.
225	pub enum VersionedAssetId {
226		#[codec(index = 3)]
227		V3(v3::AssetId),
228		#[codec(index = 4)]
229		V4(v4::AssetId),
230		#[codec(index = 5)]
231		V5(v5::AssetId),
232	}
233}
234
235versioned_type! {
236	/// A single version's `Response` value, together with its version code.
237	pub enum VersionedResponse {
238		#[codec(index = 3)]
239		V3(v3::Response),
240		#[codec(index = 4)]
241		V4(v4::Response),
242		#[codec(index = 5)]
243		V5(v5::Response),
244	}
245}
246
247versioned_type! {
248	/// A single `NetworkId` value, together with its version code.
249	pub enum VersionedNetworkId {
250		#[codec(index = 3)]
251		V3(v3::NetworkId),
252		#[codec(index = 4)]
253		V4(v4::NetworkId),
254		#[codec(index = 5)]
255		V5(v5::NetworkId),
256	}
257}
258
259versioned_type! {
260	/// A single `Junction` value, together with its version code.
261	pub enum VersionedJunction {
262		#[codec(index = 3)]
263		V3(v3::Junction),
264		#[codec(index = 4)]
265		V4(v4::Junction),
266		#[codec(index = 5)]
267		V5(v5::Junction),
268	}
269}
270
271versioned_type! {
272	/// A single `Location` value, together with its version code.
273	#[derive(Ord, PartialOrd)]
274	pub enum VersionedLocation {
275		#[codec(index = 3)]
276		V3(v3::MultiLocation),
277		#[codec(index = 4)]
278		V4(v4::Location),
279		#[codec(index = 5)]
280		V5(v5::Location),
281	}
282}
283
284versioned_type! {
285	/// A single `InteriorLocation` value, together with its version code.
286	pub enum VersionedInteriorLocation {
287		#[codec(index = 3)]
288		V3(v3::InteriorMultiLocation),
289		#[codec(index = 4)]
290		V4(v4::InteriorLocation),
291		#[codec(index = 5)]
292		V5(v5::InteriorLocation),
293	}
294}
295
296versioned_type! {
297	/// A single `Asset` value, together with its version code.
298	pub enum VersionedAsset {
299		#[codec(index = 3)]
300		V3(v3::MultiAsset),
301		#[codec(index = 4)]
302		V4(v4::Asset),
303		#[codec(index = 5)]
304		V5(v5::Asset),
305	}
306}
307
308versioned_type! {
309	/// A single `MultiAssets` value, together with its version code.
310	pub enum VersionedAssets {
311		#[codec(index = 3)]
312		V3(v3::MultiAssets),
313		#[codec(index = 4)]
314		V4(v4::Assets),
315		#[codec(index = 5)]
316		V5(v5::Assets),
317	}
318}
319
320impl VersionedAssets {
321	/// The number of assets in the collection, regardless of XCM version.
322	pub fn len(&self) -> usize {
323		match self {
324			Self::V3(assets) => assets.len(),
325			Self::V4(assets) => assets.len(),
326			Self::V5(assets) => assets.len(),
327		}
328	}
329
330	/// Whether the collection contains no assets.
331	pub fn is_empty(&self) -> bool {
332		self.len() == 0
333	}
334}
335
336/// A single XCM message, together with its version code.
337#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)]
338#[derive_where(Clone, Eq, PartialEq, Debug)]
339#[codec(encode_bound())]
340#[codec(decode_bound())]
341#[scale_info(bounds(), skip_type_params(RuntimeCall))]
342#[scale_info(replace_segment("staging_xcm", "xcm"))]
343pub enum VersionedXcm<RuntimeCall> {
344	#[codec(index = 3)]
345	V3(v3::Xcm<RuntimeCall>),
346	#[codec(index = 4)]
347	V4(v4::Xcm<RuntimeCall>),
348	#[codec(index = 5)]
349	V5(v5::Xcm<RuntimeCall>),
350}
351
352impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
353	fn into_version(self, n: Version) -> Result<Self, ()> {
354		Ok(match n {
355			3 => Self::V3(self.try_into()?),
356			4 => Self::V4(self.try_into()?),
357			5 => Self::V5(self.try_into()?),
358			_ => return Err(()),
359		})
360	}
361}
362
363impl<C> IdentifyVersion for VersionedXcm<C> {
364	fn identify_version(&self) -> Version {
365		match self {
366			Self::V3(_) => v3::VERSION,
367			Self::V4(_) => v4::VERSION,
368			Self::V5(_) => v5::VERSION,
369		}
370	}
371}
372
373impl<C> VersionedXcm<C> {
374	/// Checks if the XCM is decodable. Consequently, it checks all decoding constraints,
375	/// such as `MAX_XCM_DECODE_DEPTH`, `MAX_ITEMS_IN_ASSETS` or `MAX_INSTRUCTIONS_TO_DECODE`.
376	///
377	/// Note that this uses the limit of the sender - not the receiver. It is a best effort.
378	pub fn check_is_decodable(&self) -> Result<(), ()> {
379		self.using_encoded(|mut enc| {
380			Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ())
381		})
382		.map_err(|e| {
383			tracing::error!(target: "xcm::check_is_decodable", error=?e, xcm=?self, "Decode error!");
384			()
385		})
386	}
387}
388
389impl<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
390	fn from(x: v3::Xcm<RuntimeCall>) -> Self {
391		VersionedXcm::V3(x)
392	}
393}
394
395impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
396	fn from(x: v4::Xcm<RuntimeCall>) -> Self {
397		VersionedXcm::V4(x)
398	}
399}
400
401impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
402	fn from(x: v5::Xcm<RuntimeCall>) -> Self {
403		VersionedXcm::V5(x)
404	}
405}
406
407impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
408	type Error = ();
409	fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
410		use VersionedXcm::*;
411		match x {
412			V3(x) => Ok(x),
413			V4(x) => x.try_into(),
414			V5(x) => {
415				let v4: v4::Xcm<Call> = x.try_into()?;
416				v4.try_into()
417			},
418		}
419	}
420}
421
422impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
423	type Error = ();
424	fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
425		use VersionedXcm::*;
426		match x {
427			V3(x) => x.try_into(),
428			V4(x) => Ok(x),
429			V5(x) => x.try_into(),
430		}
431	}
432}
433
434impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
435	type Error = ();
436	fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
437		use VersionedXcm::*;
438		match x {
439			V3(x) => {
440				let v4: v4::Xcm<Call> = x.try_into()?;
441				v4.try_into()
442			},
443			V4(x) => x.try_into(),
444			V5(x) => Ok(x),
445		}
446	}
447}
448
449/// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `Location` which will
450/// interpret it.
451pub trait WrapVersion {
452	fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
453		dest: &latest::Location,
454		xcm: impl Into<VersionedXcm<RuntimeCall>>,
455	) -> Result<VersionedXcm<RuntimeCall>, ()>;
456}
457
458/// Used to get the version out of a versioned type.
459// TODO(XCMv5): This could be `GetVersion` and we change the current one to `GetVersionFor`.
460pub trait IdentifyVersion {
461	fn identify_version(&self) -> Version;
462}
463
464/// Check and return the `Version` that should be used for the `Xcm` datum for the destination
465/// `Location`, which will interpret it.
466pub trait GetVersion {
467	fn get_version_for(dest: &latest::Location) -> Option<Version>;
468}
469
470/// `()` implementation does nothing with the XCM, just sending with whatever version it was
471/// authored as.
472impl WrapVersion for () {
473	fn wrap_version<RuntimeCall>(
474		_: &latest::Location,
475		xcm: impl Into<VersionedXcm<RuntimeCall>>,
476	) -> Result<VersionedXcm<RuntimeCall>, ()> {
477		Ok(xcm.into())
478	}
479}
480
481/// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before
482/// wrapping it.
483pub struct AlwaysV3;
484impl WrapVersion for AlwaysV3 {
485	fn wrap_version<Call: Decode + GetDispatchInfo>(
486		_: &latest::Location,
487		xcm: impl Into<VersionedXcm<Call>>,
488	) -> Result<VersionedXcm<Call>, ()> {
489		Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
490	}
491}
492impl GetVersion for AlwaysV3 {
493	fn get_version_for(_dest: &latest::Location) -> Option<Version> {
494		Some(v3::VERSION)
495	}
496}
497
498/// `WrapVersion` implementation which attempts to always convert the XCM to version 4 before
499/// wrapping it.
500pub struct AlwaysV4;
501impl WrapVersion for AlwaysV4 {
502	fn wrap_version<Call: Decode + GetDispatchInfo>(
503		_: &latest::Location,
504		xcm: impl Into<VersionedXcm<Call>>,
505	) -> Result<VersionedXcm<Call>, ()> {
506		Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
507	}
508}
509impl GetVersion for AlwaysV4 {
510	fn get_version_for(_dest: &latest::Location) -> Option<Version> {
511		Some(v4::VERSION)
512	}
513}
514
515/// `WrapVersion` implementation which attempts to always convert the XCM to version 5 before
516/// wrapping it.
517pub struct AlwaysV5;
518impl WrapVersion for AlwaysV5 {
519	fn wrap_version<Call: Decode + GetDispatchInfo>(
520		_: &latest::Location,
521		xcm: impl Into<VersionedXcm<Call>>,
522	) -> Result<VersionedXcm<Call>, ()> {
523		Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
524	}
525}
526impl GetVersion for AlwaysV5 {
527	fn get_version_for(_dest: &latest::Location) -> Option<Version> {
528		Some(v5::VERSION)
529	}
530}
531
532/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version
533/// before wrapping it.
534pub type AlwaysLatest = AlwaysV5;
535
536/// `WrapVersion` implementation which attempts to always convert the XCM to the most recent Long-
537/// Term-Support version before wrapping it.
538pub type AlwaysLts = AlwaysV4;
539
540pub mod prelude {
541	pub use super::{
542		latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion,
543		IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset,
544		VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation,
545		VersionedResponse, VersionedXcm, WrapVersion,
546	};
547
548	/// The minimal supported XCM version
549	pub const MIN_XCM_VERSION: XcmVersion = 3;
550}
551
552pub mod opaque {
553	pub mod v3 {
554		// Everything from v3
555		pub use crate::v3::*;
556		// Then override with the opaque types in v3
557		pub use crate::v3::opaque::{Instruction, Xcm};
558	}
559	pub mod v4 {
560		// Everything from v4
561		pub use crate::v4::*;
562		// Then override with the opaque types in v4
563		pub use crate::v4::opaque::{Instruction, Xcm};
564	}
565	pub mod v5 {
566		// Everything from v4
567		pub use crate::v5::*;
568		// Then override with the opaque types in v5
569		pub use crate::v5::opaque::{Instruction, Xcm};
570	}
571
572	pub mod latest {
573		pub use super::v5::*;
574	}
575
576	pub mod lts {
577		pub use super::v4::*;
578	}
579
580	/// The basic `VersionedXcm` type which just uses the `Vec<u8>` as an encoded call.
581	pub type VersionedXcm = super::VersionedXcm<()>;
582}
583
584#[test]
585fn conversion_works() {
586	use latest::prelude::*;
587	let assets: Assets = (Here, 1u128).into();
588	let _: VersionedAssets = assets.into();
589}
590
591#[test]
592fn size_limits() {
593	extern crate std;
594
595	let mut test_failed = false;
596	macro_rules! check_sizes {
597        ($(($kind:ty, $expected:expr),)+) => {
598            $({
599                let s = core::mem::size_of::<$kind>();
600                // Since the types often affect the size of other types in which they're included
601                // it is more convenient to check multiple types at the same time and only fail
602                // the test at the end. For debugging it's also useful to print out all of the sizes,
603                // even if they're within the expected range.
604                if s > $expected {
605                    test_failed = true;
606                    std::eprintln!(
607                        "assertion failed: size of '{}' is {} (which is more than the expected {})",
608                        stringify!($kind),
609                        s,
610                        $expected
611                    );
612                } else {
613                    std::println!(
614                        "type '{}' is of size {} which is within the expected {}",
615                        stringify!($kind),
616                        s,
617                        $expected
618                    );
619                }
620            })+
621        }
622    }
623
624	check_sizes! {
625		(crate::latest::Instruction<()>, 128),
626		(crate::latest::Asset, 80),
627		(crate::latest::Location, 24),
628		(crate::latest::AssetId, 40),
629		(crate::latest::Junctions, 16),
630		(crate::latest::Junction, 88),
631		(crate::latest::Response, 40),
632		(crate::latest::AssetInstance, 48),
633		(crate::latest::NetworkId, 48),
634		(crate::latest::BodyId, 32),
635		(crate::latest::Assets, 24),
636		(crate::latest::BodyPart, 12),
637	}
638	assert!(!test_failed);
639}
640
641#[test]
642fn check_is_decodable_works() {
643	use crate::{
644		latest::{
645			prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix},
646			Assets, Xcm, MAX_ITEMS_IN_ASSETS,
647		},
648		MAX_INSTRUCTIONS_TO_DECODE,
649	};
650
651	// closure generates assets of `count`
652	let assets = |count| {
653		let mut assets = Assets::new();
654		for i in 0..count {
655			assets.push((GeneralIndex(i as u128), 100).into());
656		}
657		assets
658	};
659
660	// closer generates `Xcm` with nested instructions of `depth`
661	let with_instr = |depth| {
662		let mut xcm = Xcm::<()>(vec![]);
663		for _ in 0..depth - 1 {
664			xcm = Xcm::<()>(vec![SetAppendix(xcm)]);
665		}
666		xcm
667	};
668
669	// `MAX_INSTRUCTIONS_TO_DECODE` check
670	assert!(VersionedXcm::<()>::from(Xcm(vec![
671		ReserveAssetDeposited(assets(1));
672		(MAX_INSTRUCTIONS_TO_DECODE - 1) as usize
673	]))
674	.check_is_decodable()
675	.is_ok());
676	assert!(VersionedXcm::<()>::from(Xcm(vec![
677		ReserveAssetDeposited(assets(1));
678		MAX_INSTRUCTIONS_TO_DECODE as usize
679	]))
680	.check_is_decodable()
681	.is_ok());
682	assert!(VersionedXcm::<()>::from(Xcm(vec![
683		ReserveAssetDeposited(assets(1));
684		(MAX_INSTRUCTIONS_TO_DECODE + 1) as usize
685	]))
686	.check_is_decodable()
687	.is_err());
688
689	// `MAX_XCM_DECODE_DEPTH` check
690	assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1))
691		.check_is_decodable()
692		.is_ok());
693	assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH))
694		.check_is_decodable()
695		.is_ok());
696	assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1))
697		.check_is_decodable()
698		.is_err());
699
700	// `MAX_ITEMS_IN_ASSETS` check
701	assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
702		MAX_ITEMS_IN_ASSETS
703	))]))
704	.check_is_decodable()
705	.is_ok());
706	assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
707		MAX_ITEMS_IN_ASSETS - 1
708	))]))
709	.check_is_decodable()
710	.is_ok());
711	assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
712		MAX_ITEMS_IN_ASSETS + 1
713	))]))
714	.check_is_decodable()
715	.is_err());
716}