1#![cfg_attr(not(feature = "std"), no_std)]
24
25extern crate alloc;
26
27use codec::{Decode, DecodeLimit, Encode, Error as CodecError, Input, MaxEncodedLen};
28use derivative::Derivative;
29use frame_support::dispatch::GetDispatchInfo;
30use scale_info::TypeInfo;
31
32pub mod v3;
33pub mod v4;
34pub mod v5;
35
36pub mod lts {
37 pub use super::v4::*;
38}
39
40pub mod latest {
41 pub use super::v5::*;
42}
43
44mod double_encoded;
45pub use double_encoded::DoubleEncoded;
46
47mod utils;
48
49#[cfg(test)]
50mod tests;
51
52pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
54pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
58
59pub type Version = u32;
61
62#[derive(Clone, Eq, PartialEq, Debug)]
63pub enum Unsupported {}
64impl Encode for Unsupported {}
65impl Decode for Unsupported {
66 fn decode<I: Input>(_: &mut I) -> Result<Self, CodecError> {
67 Err("Not decodable".into())
68 }
69}
70
71pub trait IntoVersion: Sized {
73 fn into_version(self, version: Version) -> Result<Self, ()>;
75
76 fn into_latest(self) -> Result<Self, ()> {
78 self.into_version(latest::VERSION)
79 }
80}
81
82pub trait TryAs<T> {
83 fn try_as(&self) -> Result<&T, ()>;
84}
85
86macro_rules! versioned_type {
89 ($(#[$attr:meta])* pub enum $n:ident {
90 $(#[$index3:meta])+
91 V3($v3:ty),
92 $(#[$index4:meta])+
93 V4($v4:ty),
94 $(#[$index5:meta])+
95 V5($v5:ty),
96 }) => {
97 #[derive(Derivative, Encode, Decode, TypeInfo)]
98 #[derivative(
99 Clone(bound = ""),
100 Eq(bound = ""),
101 PartialEq(bound = ""),
102 Debug(bound = "")
103 )]
104 #[codec(encode_bound())]
105 #[codec(decode_bound())]
106 #[scale_info(replace_segment("staging_xcm", "xcm"))]
107 $(#[$attr])*
108 pub enum $n {
109 $(#[$index3])*
110 V3($v3),
111 $(#[$index4])*
112 V4($v4),
113 $(#[$index5])*
114 V5($v5),
115 }
116 impl $n {
117 pub fn try_as<T>(&self) -> Result<&T, ()> where Self: TryAs<T> {
118 <Self as TryAs<T>>::try_as(&self)
119 }
120 }
121 impl TryAs<$v3> for $n {
122 fn try_as(&self) -> Result<&$v3, ()> {
123 match &self {
124 Self::V3(ref x) => Ok(x),
125 _ => Err(()),
126 }
127 }
128 }
129 impl TryAs<$v4> for $n {
130 fn try_as(&self) -> Result<&$v4, ()> {
131 match &self {
132 Self::V4(ref x) => Ok(x),
133 _ => Err(()),
134 }
135 }
136 }
137 impl TryAs<$v5> for $n {
138 fn try_as(&self) -> Result<&$v5, ()> {
139 match &self {
140 Self::V5(ref x) => Ok(x),
141 _ => Err(()),
142 }
143 }
144 }
145 impl IntoVersion for $n {
146 fn into_version(self, n: Version) -> Result<Self, ()> {
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 impl From<$v3> for $n {
156 fn from(x: $v3) -> Self {
157 $n::V3(x.into())
158 }
159 }
160 impl<T: Into<$v5>> From<T> for $n {
161 fn from(x: T) -> Self {
162 $n::V5(x.into())
163 }
164 }
165 impl TryFrom<$n> for $v3 {
166 type Error = ();
167 fn try_from(x: $n) -> Result<Self, ()> {
168 use $n::*;
169 match x {
170 V3(x) => Ok(x),
171 V4(x) => x.try_into().map_err(|_| ()),
172 V5(x) => {
173 let v4: $v4 = x.try_into().map_err(|_| ())?;
174 v4.try_into().map_err(|_| ())
175 }
176 }
177 }
178 }
179 impl TryFrom<$n> for $v4 {
180 type Error = ();
181 fn try_from(x: $n) -> Result<Self, ()> {
182 use $n::*;
183 match x {
184 V3(x) => x.try_into().map_err(|_| ()),
185 V4(x) => Ok(x),
186 V5(x) => x.try_into().map_err(|_| ()),
187 }
188 }
189 }
190 impl TryFrom<$n> for $v5 {
191 type Error = ();
192 fn try_from(x: $n) -> Result<Self, ()> {
193 use $n::*;
194 match x {
195 V3(x) => {
196 let v4: $v4 = x.try_into().map_err(|_| ())?;
197 v4.try_into().map_err(|_| ())
198 },
199 V4(x) => x.try_into().map_err(|_| ()),
200 V5(x) => Ok(x),
201 }
202 }
203 }
204 impl MaxEncodedLen for $n {
205 fn max_encoded_len() -> usize {
206 <$v3>::max_encoded_len()
207 }
208 }
209 impl IdentifyVersion for $n {
210 fn identify_version(&self) -> Version {
211 use $n::*;
212 match self {
213 V3(_) => v3::VERSION,
214 V4(_) => v4::VERSION,
215 V5(_) => v5::VERSION,
216 }
217 }
218 }
219 };
220}
221
222versioned_type! {
223 pub enum VersionedAssetId {
225 #[codec(index = 3)]
226 V3(v3::AssetId),
227 #[codec(index = 4)]
228 V4(v4::AssetId),
229 #[codec(index = 5)]
230 V5(v5::AssetId),
231 }
232}
233
234versioned_type! {
235 pub enum VersionedResponse {
237 #[codec(index = 3)]
238 V3(v3::Response),
239 #[codec(index = 4)]
240 V4(v4::Response),
241 #[codec(index = 5)]
242 V5(v5::Response),
243 }
244}
245
246versioned_type! {
247 pub enum VersionedNetworkId {
249 #[codec(index = 3)]
250 V3(v3::NetworkId),
251 #[codec(index = 4)]
252 V4(v4::NetworkId),
253 #[codec(index = 5)]
254 V5(v5::NetworkId),
255 }
256}
257
258versioned_type! {
259 pub enum VersionedJunction {
261 #[codec(index = 3)]
262 V3(v3::Junction),
263 #[codec(index = 4)]
264 V4(v4::Junction),
265 #[codec(index = 5)]
266 V5(v5::Junction),
267 }
268}
269
270versioned_type! {
271 #[derive(Ord, PartialOrd)]
273 pub enum VersionedLocation {
274 #[codec(index = 3)]
275 V3(v3::MultiLocation),
276 #[codec(index = 4)]
277 V4(v4::Location),
278 #[codec(index = 5)]
279 V5(v5::Location),
280 }
281}
282
283versioned_type! {
284 pub enum VersionedInteriorLocation {
286 #[codec(index = 3)]
287 V3(v3::InteriorMultiLocation),
288 #[codec(index = 4)]
289 V4(v4::InteriorLocation),
290 #[codec(index = 5)]
291 V5(v5::InteriorLocation),
292 }
293}
294
295versioned_type! {
296 pub enum VersionedAsset {
298 #[codec(index = 3)]
299 V3(v3::MultiAsset),
300 #[codec(index = 4)]
301 V4(v4::Asset),
302 #[codec(index = 5)]
303 V5(v5::Asset),
304 }
305}
306
307versioned_type! {
308 pub enum VersionedAssets {
310 #[codec(index = 3)]
311 V3(v3::MultiAssets),
312 #[codec(index = 4)]
313 V4(v4::Assets),
314 #[codec(index = 5)]
315 V5(v5::Assets),
316 }
317}
318
319#[derive(Derivative, Encode, Decode, TypeInfo)]
321#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
322#[codec(encode_bound())]
323#[codec(decode_bound())]
324#[scale_info(bounds(), skip_type_params(RuntimeCall))]
325#[scale_info(replace_segment("staging_xcm", "xcm"))]
326pub enum VersionedXcm<RuntimeCall> {
327 #[codec(index = 3)]
328 V3(v3::Xcm<RuntimeCall>),
329 #[codec(index = 4)]
330 V4(v4::Xcm<RuntimeCall>),
331 #[codec(index = 5)]
332 V5(v5::Xcm<RuntimeCall>),
333}
334
335impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
336 fn into_version(self, n: Version) -> Result<Self, ()> {
337 Ok(match n {
338 3 => Self::V3(self.try_into()?),
339 4 => Self::V4(self.try_into()?),
340 5 => Self::V5(self.try_into()?),
341 _ => return Err(()),
342 })
343 }
344}
345
346impl<C> IdentifyVersion for VersionedXcm<C> {
347 fn identify_version(&self) -> Version {
348 match self {
349 Self::V3(_) => v3::VERSION,
350 Self::V4(_) => v4::VERSION,
351 Self::V5(_) => v5::VERSION,
352 }
353 }
354}
355
356impl<C> VersionedXcm<C> {
357 pub fn validate_xcm_nesting(&self) -> Result<(), ()> {
362 self.using_encoded(|mut enc| {
363 Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ())
364 })
365 .map_err(|e| {
366 log::error!(target: "xcm::validate_xcm_nesting", "Decode error: {e:?} for xcm: {self:?}!");
367 ()
368 })
369 }
370}
371
372impl<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
373 fn from(x: v3::Xcm<RuntimeCall>) -> Self {
374 VersionedXcm::V3(x)
375 }
376}
377
378impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
379 fn from(x: v4::Xcm<RuntimeCall>) -> Self {
380 VersionedXcm::V4(x)
381 }
382}
383
384impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
385 fn from(x: v5::Xcm<RuntimeCall>) -> Self {
386 VersionedXcm::V5(x)
387 }
388}
389
390impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
391 type Error = ();
392 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
393 use VersionedXcm::*;
394 match x {
395 V3(x) => Ok(x),
396 V4(x) => x.try_into(),
397 V5(x) => {
398 let v4: v4::Xcm<Call> = x.try_into()?;
399 v4.try_into()
400 },
401 }
402 }
403}
404
405impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
406 type Error = ();
407 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
408 use VersionedXcm::*;
409 match x {
410 V3(x) => x.try_into(),
411 V4(x) => Ok(x),
412 V5(x) => x.try_into(),
413 }
414 }
415}
416
417impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
418 type Error = ();
419 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
420 use VersionedXcm::*;
421 match x {
422 V3(x) => {
423 let v4: v4::Xcm<Call> = x.try_into()?;
424 v4.try_into()
425 },
426 V4(x) => x.try_into(),
427 V5(x) => Ok(x),
428 }
429 }
430}
431
432pub trait WrapVersion {
435 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
436 dest: &latest::Location,
437 xcm: impl Into<VersionedXcm<RuntimeCall>>,
438 ) -> Result<VersionedXcm<RuntimeCall>, ()>;
439}
440
441pub trait IdentifyVersion {
444 fn identify_version(&self) -> Version;
445}
446
447pub trait GetVersion {
450 fn get_version_for(dest: &latest::Location) -> Option<Version>;
451}
452
453impl WrapVersion for () {
456 fn wrap_version<RuntimeCall>(
457 _: &latest::Location,
458 xcm: impl Into<VersionedXcm<RuntimeCall>>,
459 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
460 Ok(xcm.into())
461 }
462}
463
464pub struct AlwaysV3;
467impl WrapVersion for AlwaysV3 {
468 fn wrap_version<Call: Decode + GetDispatchInfo>(
469 _: &latest::Location,
470 xcm: impl Into<VersionedXcm<Call>>,
471 ) -> Result<VersionedXcm<Call>, ()> {
472 Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
473 }
474}
475impl GetVersion for AlwaysV3 {
476 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
477 Some(v3::VERSION)
478 }
479}
480
481pub struct AlwaysV4;
484impl WrapVersion for AlwaysV4 {
485 fn wrap_version<Call: Decode + GetDispatchInfo>(
486 _: &latest::Location,
487 xcm: impl Into<VersionedXcm<Call>>,
488 ) -> Result<VersionedXcm<Call>, ()> {
489 Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
490 }
491}
492impl GetVersion for AlwaysV4 {
493 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
494 Some(v4::VERSION)
495 }
496}
497
498pub struct AlwaysV5;
501impl WrapVersion for AlwaysV5 {
502 fn wrap_version<Call: Decode + GetDispatchInfo>(
503 _: &latest::Location,
504 xcm: impl Into<VersionedXcm<Call>>,
505 ) -> Result<VersionedXcm<Call>, ()> {
506 Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
507 }
508}
509impl GetVersion for AlwaysV5 {
510 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
511 Some(v5::VERSION)
512 }
513}
514
515pub type AlwaysLatest = AlwaysV5;
518
519pub type AlwaysLts = AlwaysV4;
522
523pub mod prelude {
524 pub use super::{
525 latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion,
526 IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset,
527 VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation,
528 VersionedResponse, VersionedXcm, WrapVersion,
529 };
530
531 pub const MIN_XCM_VERSION: XcmVersion = 3;
533}
534
535pub mod opaque {
536 pub mod v3 {
537 pub use crate::v3::*;
539 pub use crate::v3::opaque::{Instruction, Xcm};
541 }
542 pub mod v4 {
543 pub use crate::v4::*;
545 pub use crate::v4::opaque::{Instruction, Xcm};
547 }
548 pub mod v5 {
549 pub use crate::v5::*;
551 pub use crate::v5::opaque::{Instruction, Xcm};
553 }
554
555 pub mod latest {
556 pub use super::v5::*;
557 }
558
559 pub mod lts {
560 pub use super::v4::*;
561 }
562
563 pub type VersionedXcm = super::VersionedXcm<()>;
565}
566
567#[test]
568fn conversion_works() {
569 use latest::prelude::*;
570 let assets: Assets = (Here, 1u128).into();
571 let _: VersionedAssets = assets.into();
572}
573
574#[test]
575fn size_limits() {
576 extern crate std;
577
578 let mut test_failed = false;
579 macro_rules! check_sizes {
580 ($(($kind:ty, $expected:expr),)+) => {
581 $({
582 let s = core::mem::size_of::<$kind>();
583 if s > $expected {
588 test_failed = true;
589 std::eprintln!(
590 "assertion failed: size of '{}' is {} (which is more than the expected {})",
591 stringify!($kind),
592 s,
593 $expected
594 );
595 } else {
596 std::println!(
597 "type '{}' is of size {} which is within the expected {}",
598 stringify!($kind),
599 s,
600 $expected
601 );
602 }
603 })+
604 }
605 }
606
607 check_sizes! {
608 (crate::latest::Instruction<()>, 128),
609 (crate::latest::Asset, 80),
610 (crate::latest::Location, 24),
611 (crate::latest::AssetId, 40),
612 (crate::latest::Junctions, 16),
613 (crate::latest::Junction, 88),
614 (crate::latest::Response, 40),
615 (crate::latest::AssetInstance, 48),
616 (crate::latest::NetworkId, 48),
617 (crate::latest::BodyId, 32),
618 (crate::latest::Assets, 24),
619 (crate::latest::BodyPart, 12),
620 }
621 assert!(!test_failed);
622}
623
624#[test]
625fn validate_xcm_nesting_works() {
626 use crate::{
627 latest::{
628 prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix},
629 Assets, Xcm, MAX_ITEMS_IN_ASSETS,
630 },
631 MAX_INSTRUCTIONS_TO_DECODE,
632 };
633
634 let assets = |count| {
636 let mut assets = Assets::new();
637 for i in 0..count {
638 assets.push((GeneralIndex(i as u128), 100).into());
639 }
640 assets
641 };
642
643 let with_instr = |depth| {
645 let mut xcm = Xcm::<()>(vec![]);
646 for _ in 0..depth - 1 {
647 xcm = Xcm::<()>(vec![SetAppendix(xcm)]);
648 }
649 xcm
650 };
651
652 assert!(VersionedXcm::<()>::from(Xcm(vec![
654 ReserveAssetDeposited(assets(1));
655 (MAX_INSTRUCTIONS_TO_DECODE - 1) as usize
656 ]))
657 .validate_xcm_nesting()
658 .is_ok());
659 assert!(VersionedXcm::<()>::from(Xcm(vec![
660 ReserveAssetDeposited(assets(1));
661 MAX_INSTRUCTIONS_TO_DECODE as usize
662 ]))
663 .validate_xcm_nesting()
664 .is_ok());
665 assert!(VersionedXcm::<()>::from(Xcm(vec![
666 ReserveAssetDeposited(assets(1));
667 (MAX_INSTRUCTIONS_TO_DECODE + 1) as usize
668 ]))
669 .validate_xcm_nesting()
670 .is_err());
671
672 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1))
674 .validate_xcm_nesting()
675 .is_ok());
676 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH))
677 .validate_xcm_nesting()
678 .is_ok());
679 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1))
680 .validate_xcm_nesting()
681 .is_err());
682
683 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
685 MAX_ITEMS_IN_ASSETS
686 ))]))
687 .validate_xcm_nesting()
688 .is_ok());
689 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
690 MAX_ITEMS_IN_ASSETS - 1
691 ))]))
692 .validate_xcm_nesting()
693 .is_ok());
694 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
695 MAX_ITEMS_IN_ASSETS + 1
696 ))]))
697 .validate_xcm_nesting()
698 .is_err());
699}