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
320#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)]
322#[derive_where(Clone, Eq, PartialEq, Debug)]
323#[codec(encode_bound())]
324#[codec(decode_bound())]
325#[scale_info(bounds(), skip_type_params(RuntimeCall))]
326#[scale_info(replace_segment("staging_xcm", "xcm"))]
327pub enum VersionedXcm<RuntimeCall> {
328 #[codec(index = 3)]
329 V3(v3::Xcm<RuntimeCall>),
330 #[codec(index = 4)]
331 V4(v4::Xcm<RuntimeCall>),
332 #[codec(index = 5)]
333 V5(v5::Xcm<RuntimeCall>),
334}
335
336impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
337 fn into_version(self, n: Version) -> Result<Self, ()> {
338 Ok(match n {
339 3 => Self::V3(self.try_into()?),
340 4 => Self::V4(self.try_into()?),
341 5 => Self::V5(self.try_into()?),
342 _ => return Err(()),
343 })
344 }
345}
346
347impl<C> IdentifyVersion for VersionedXcm<C> {
348 fn identify_version(&self) -> Version {
349 match self {
350 Self::V3(_) => v3::VERSION,
351 Self::V4(_) => v4::VERSION,
352 Self::V5(_) => v5::VERSION,
353 }
354 }
355}
356
357impl<C> VersionedXcm<C> {
358 pub fn check_is_decodable(&self) -> Result<(), ()> {
363 self.using_encoded(|mut enc| {
364 Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ())
365 })
366 .map_err(|e| {
367 tracing::error!(target: "xcm::check_is_decodable", error=?e, xcm=?self, "Decode error!");
368 ()
369 })
370 }
371}
372
373impl<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
374 fn from(x: v3::Xcm<RuntimeCall>) -> Self {
375 VersionedXcm::V3(x)
376 }
377}
378
379impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
380 fn from(x: v4::Xcm<RuntimeCall>) -> Self {
381 VersionedXcm::V4(x)
382 }
383}
384
385impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
386 fn from(x: v5::Xcm<RuntimeCall>) -> Self {
387 VersionedXcm::V5(x)
388 }
389}
390
391impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
392 type Error = ();
393 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
394 use VersionedXcm::*;
395 match x {
396 V3(x) => Ok(x),
397 V4(x) => x.try_into(),
398 V5(x) => {
399 let v4: v4::Xcm<Call> = x.try_into()?;
400 v4.try_into()
401 },
402 }
403 }
404}
405
406impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
407 type Error = ();
408 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
409 use VersionedXcm::*;
410 match x {
411 V3(x) => x.try_into(),
412 V4(x) => Ok(x),
413 V5(x) => x.try_into(),
414 }
415 }
416}
417
418impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
419 type Error = ();
420 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
421 use VersionedXcm::*;
422 match x {
423 V3(x) => {
424 let v4: v4::Xcm<Call> = x.try_into()?;
425 v4.try_into()
426 },
427 V4(x) => x.try_into(),
428 V5(x) => Ok(x),
429 }
430 }
431}
432
433pub trait WrapVersion {
436 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
437 dest: &latest::Location,
438 xcm: impl Into<VersionedXcm<RuntimeCall>>,
439 ) -> Result<VersionedXcm<RuntimeCall>, ()>;
440}
441
442pub trait IdentifyVersion {
445 fn identify_version(&self) -> Version;
446}
447
448pub trait GetVersion {
451 fn get_version_for(dest: &latest::Location) -> Option<Version>;
452}
453
454impl WrapVersion for () {
457 fn wrap_version<RuntimeCall>(
458 _: &latest::Location,
459 xcm: impl Into<VersionedXcm<RuntimeCall>>,
460 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
461 Ok(xcm.into())
462 }
463}
464
465pub struct AlwaysV3;
468impl WrapVersion for AlwaysV3 {
469 fn wrap_version<Call: Decode + GetDispatchInfo>(
470 _: &latest::Location,
471 xcm: impl Into<VersionedXcm<Call>>,
472 ) -> Result<VersionedXcm<Call>, ()> {
473 Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
474 }
475}
476impl GetVersion for AlwaysV3 {
477 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
478 Some(v3::VERSION)
479 }
480}
481
482pub struct AlwaysV4;
485impl WrapVersion for AlwaysV4 {
486 fn wrap_version<Call: Decode + GetDispatchInfo>(
487 _: &latest::Location,
488 xcm: impl Into<VersionedXcm<Call>>,
489 ) -> Result<VersionedXcm<Call>, ()> {
490 Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
491 }
492}
493impl GetVersion for AlwaysV4 {
494 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
495 Some(v4::VERSION)
496 }
497}
498
499pub struct AlwaysV5;
502impl WrapVersion for AlwaysV5 {
503 fn wrap_version<Call: Decode + GetDispatchInfo>(
504 _: &latest::Location,
505 xcm: impl Into<VersionedXcm<Call>>,
506 ) -> Result<VersionedXcm<Call>, ()> {
507 Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
508 }
509}
510impl GetVersion for AlwaysV5 {
511 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
512 Some(v5::VERSION)
513 }
514}
515
516pub type AlwaysLatest = AlwaysV5;
519
520pub type AlwaysLts = AlwaysV4;
523
524pub mod prelude {
525 pub use super::{
526 latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion,
527 IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset,
528 VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation,
529 VersionedResponse, VersionedXcm, WrapVersion,
530 };
531
532 pub const MIN_XCM_VERSION: XcmVersion = 3;
534}
535
536pub mod opaque {
537 pub mod v3 {
538 pub use crate::v3::*;
540 pub use crate::v3::opaque::{Instruction, Xcm};
542 }
543 pub mod v4 {
544 pub use crate::v4::*;
546 pub use crate::v4::opaque::{Instruction, Xcm};
548 }
549 pub mod v5 {
550 pub use crate::v5::*;
552 pub use crate::v5::opaque::{Instruction, Xcm};
554 }
555
556 pub mod latest {
557 pub use super::v5::*;
558 }
559
560 pub mod lts {
561 pub use super::v4::*;
562 }
563
564 pub type VersionedXcm = super::VersionedXcm<()>;
566}
567
568#[test]
569fn conversion_works() {
570 use latest::prelude::*;
571 let assets: Assets = (Here, 1u128).into();
572 let _: VersionedAssets = assets.into();
573}
574
575#[test]
576fn size_limits() {
577 extern crate std;
578
579 let mut test_failed = false;
580 macro_rules! check_sizes {
581 ($(($kind:ty, $expected:expr),)+) => {
582 $({
583 let s = core::mem::size_of::<$kind>();
584 if s > $expected {
589 test_failed = true;
590 std::eprintln!(
591 "assertion failed: size of '{}' is {} (which is more than the expected {})",
592 stringify!($kind),
593 s,
594 $expected
595 );
596 } else {
597 std::println!(
598 "type '{}' is of size {} which is within the expected {}",
599 stringify!($kind),
600 s,
601 $expected
602 );
603 }
604 })+
605 }
606 }
607
608 check_sizes! {
609 (crate::latest::Instruction<()>, 128),
610 (crate::latest::Asset, 80),
611 (crate::latest::Location, 24),
612 (crate::latest::AssetId, 40),
613 (crate::latest::Junctions, 16),
614 (crate::latest::Junction, 88),
615 (crate::latest::Response, 40),
616 (crate::latest::AssetInstance, 48),
617 (crate::latest::NetworkId, 48),
618 (crate::latest::BodyId, 32),
619 (crate::latest::Assets, 24),
620 (crate::latest::BodyPart, 12),
621 }
622 assert!(!test_failed);
623}
624
625#[test]
626fn check_is_decodable_works() {
627 use crate::{
628 latest::{
629 prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix},
630 Assets, Xcm, MAX_ITEMS_IN_ASSETS,
631 },
632 MAX_INSTRUCTIONS_TO_DECODE,
633 };
634
635 let assets = |count| {
637 let mut assets = Assets::new();
638 for i in 0..count {
639 assets.push((GeneralIndex(i as u128), 100).into());
640 }
641 assets
642 };
643
644 let with_instr = |depth| {
646 let mut xcm = Xcm::<()>(vec![]);
647 for _ in 0..depth - 1 {
648 xcm = Xcm::<()>(vec![SetAppendix(xcm)]);
649 }
650 xcm
651 };
652
653 assert!(VersionedXcm::<()>::from(Xcm(vec![
655 ReserveAssetDeposited(assets(1));
656 (MAX_INSTRUCTIONS_TO_DECODE - 1) as usize
657 ]))
658 .check_is_decodable()
659 .is_ok());
660 assert!(VersionedXcm::<()>::from(Xcm(vec![
661 ReserveAssetDeposited(assets(1));
662 MAX_INSTRUCTIONS_TO_DECODE as usize
663 ]))
664 .check_is_decodable()
665 .is_ok());
666 assert!(VersionedXcm::<()>::from(Xcm(vec![
667 ReserveAssetDeposited(assets(1));
668 (MAX_INSTRUCTIONS_TO_DECODE + 1) as usize
669 ]))
670 .check_is_decodable()
671 .is_err());
672
673 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1))
675 .check_is_decodable()
676 .is_ok());
677 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH))
678 .check_is_decodable()
679 .is_ok());
680 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1))
681 .check_is_decodable()
682 .is_err());
683
684 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
686 MAX_ITEMS_IN_ASSETS
687 ))]))
688 .check_is_decodable()
689 .is_ok());
690 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
691 MAX_ITEMS_IN_ASSETS - 1
692 ))]))
693 .check_is_decodable()
694 .is_ok());
695 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
696 MAX_ITEMS_IN_ASSETS + 1
697 ))]))
698 .check_is_decodable()
699 .is_err());
700}