1#![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
54pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
56pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
60
61pub 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
73pub trait IntoVersion: Sized {
75 fn into_version(self, version: Version) -> Result<Self, ()>;
77
78 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
88macro_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 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 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 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 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 #[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 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 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 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 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 pub fn is_empty(&self) -> bool {
332 self.len() == 0
333 }
334}
335
336#[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 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
449pub 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
458pub trait IdentifyVersion {
461 fn identify_version(&self) -> Version;
462}
463
464pub trait GetVersion {
467 fn get_version_for(dest: &latest::Location) -> Option<Version>;
468}
469
470impl 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
481pub 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
498pub 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
515pub 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
532pub type AlwaysLatest = AlwaysV5;
535
536pub 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 pub const MIN_XCM_VERSION: XcmVersion = 3;
550}
551
552pub mod opaque {
553 pub mod v3 {
554 pub use crate::v3::*;
556 pub use crate::v3::opaque::{Instruction, Xcm};
558 }
559 pub mod v4 {
560 pub use crate::v4::*;
562 pub use crate::v4::opaque::{Instruction, Xcm};
564 }
565 pub mod v5 {
566 pub use crate::v5::*;
568 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 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 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 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 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 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 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 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}