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 MemTrackingInput,
30};
31use derive_where::derive_where;
32use frame_support::dispatch::GetDispatchInfo;
33use scale_info::TypeInfo;
34
35pub mod v3;
36pub mod v4;
37pub mod v5;
38
39pub mod lts {
40 pub use super::v4::*;
41}
42
43pub mod latest {
44 pub use super::v5::*;
45}
46
47mod double_encoded;
48pub use double_encoded::DoubleEncoded;
49
50mod utils;
51
52#[cfg(test)]
53mod tests;
54
55pub const MAX_XCM_SIZE: usize = 8 * 1024 * 1024;
57pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
62pub const RECURSION_LIMIT: u8 = 10;
66pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
70
71const DECODE_ALL_ERR_MSG: &str = "Input buffer has still data left after decoding!";
72
73pub type Version = u32;
75
76#[derive(Clone, Eq, PartialEq, Debug)]
77pub enum Unsupported {}
78impl Encode for Unsupported {}
79impl Decode for Unsupported {
80 fn decode<I: Input>(_: &mut I) -> Result<Self, CodecError> {
81 Err("Not decodable".into())
82 }
83}
84
85pub trait IntoVersion: Sized {
87 fn into_version(self, version: Version) -> Result<Self, ()>;
89
90 fn into_latest(self) -> Result<Self, ()> {
92 self.into_version(latest::VERSION)
93 }
94}
95
96pub trait TryAs<T> {
97 fn try_as(&self) -> Result<&T, ()>;
98}
99
100macro_rules! versioned_type {
103 ($(#[$attr:meta])* pub enum $n:ident {
104 $(#[$index3:meta])+
105 V3($v3:ty),
106 $(#[$index4:meta])+
107 V4($v4:ty),
108 $(#[$index5:meta])+
109 V5($v5:ty),
110 }) => {
111 #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
112 #[codec(encode_bound())]
113 #[codec(decode_bound())]
114 #[scale_info(replace_segment("staging_xcm", "xcm"))]
115 $(#[$attr])*
116 pub enum $n {
117 $(#[$index3])*
118 V3($v3),
119 $(#[$index4])*
120 V4($v4),
121 $(#[$index5])*
122 V5($v5),
123 }
124 impl $n {
125 pub fn try_as<T>(&self) -> Result<&T, ()> where Self: TryAs<T> {
126 <Self as TryAs<T>>::try_as(&self)
127 }
128 }
129 impl TryAs<$v3> for $n {
130 fn try_as(&self) -> Result<&$v3, ()> {
131 match &self {
132 Self::V3(ref x) => Ok(x),
133 _ => Err(()),
134 }
135 }
136 }
137 impl TryAs<$v4> for $n {
138 fn try_as(&self) -> Result<&$v4, ()> {
139 match &self {
140 Self::V4(ref x) => Ok(x),
141 _ => Err(()),
142 }
143 }
144 }
145 impl TryAs<$v5> for $n {
146 fn try_as(&self) -> Result<&$v5, ()> {
147 match &self {
148 Self::V5(ref x) => Ok(x),
149 _ => Err(()),
150 }
151 }
152 }
153 impl IntoVersion for $n {
154 fn into_version(self, n: Version) -> Result<Self, ()> {
155 let version = self.identify_version();
156 if version == n {
157 Ok(self)
158 } else {
159 Ok(match n {
160 3 => Self::V3(self.try_into()?),
161 4 => Self::V4(self.try_into()?),
162 5 => Self::V5(self.try_into()?),
163 _ => return Err(()),
164 })
165 }
166 }
167 }
168 impl From<$v3> for $n {
169 fn from(x: $v3) -> Self {
170 $n::V3(x.into())
171 }
172 }
173 impl<T: Into<$v5>> From<T> for $n {
174 fn from(x: T) -> Self {
175 $n::V5(x.into())
176 }
177 }
178 impl TryFrom<$n> for $v3 {
179 type Error = ();
180 fn try_from(x: $n) -> Result<Self, ()> {
181 use $n::*;
182 match x {
183 V3(x) => Ok(x),
184 V4(x) => x.try_into().map_err(|_| ()),
185 V5(x) => {
186 let v4: $v4 = x.try_into().map_err(|_| ())?;
187 v4.try_into().map_err(|_| ())
188 }
189 }
190 }
191 }
192 impl TryFrom<$n> for $v4 {
193 type Error = ();
194 fn try_from(x: $n) -> Result<Self, ()> {
195 use $n::*;
196 match x {
197 V3(x) => x.try_into().map_err(|_| ()),
198 V4(x) => Ok(x),
199 V5(x) => x.try_into().map_err(|_| ()),
200 }
201 }
202 }
203 impl TryFrom<$n> for $v5 {
204 type Error = ();
205 fn try_from(x: $n) -> Result<Self, ()> {
206 use $n::*;
207 match x {
208 V3(x) => {
209 let v4: $v4 = x.try_into().map_err(|_| ())?;
210 v4.try_into().map_err(|_| ())
211 },
212 V4(x) => x.try_into().map_err(|_| ()),
213 V5(x) => Ok(x),
214 }
215 }
216 }
217 impl MaxEncodedLen for $n {
218 fn max_encoded_len() -> usize {
219 <$v3>::max_encoded_len()
220 }
221 }
222 impl IdentifyVersion for $n {
223 fn identify_version(&self) -> Version {
224 use $n::*;
225 match self {
226 V3(_) => v3::VERSION,
227 V4(_) => v4::VERSION,
228 V5(_) => v5::VERSION,
229 }
230 }
231 }
232 };
233}
234
235versioned_type! {
236 pub enum VersionedAssetId {
238 #[codec(index = 3)]
239 V3(v3::AssetId),
240 #[codec(index = 4)]
241 V4(v4::AssetId),
242 #[codec(index = 5)]
243 V5(v5::AssetId),
244 }
245}
246
247versioned_type! {
248 pub enum VersionedResponse {
250 #[codec(index = 3)]
251 V3(v3::Response),
252 #[codec(index = 4)]
253 V4(v4::Response),
254 #[codec(index = 5)]
255 V5(v5::Response),
256 }
257}
258
259versioned_type! {
260 pub enum VersionedNetworkId {
262 #[codec(index = 3)]
263 V3(v3::NetworkId),
264 #[codec(index = 4)]
265 V4(v4::NetworkId),
266 #[codec(index = 5)]
267 V5(v5::NetworkId),
268 }
269}
270
271versioned_type! {
272 pub enum VersionedJunction {
274 #[codec(index = 3)]
275 V3(v3::Junction),
276 #[codec(index = 4)]
277 V4(v4::Junction),
278 #[codec(index = 5)]
279 V5(v5::Junction),
280 }
281}
282
283versioned_type! {
284 #[derive(Ord, PartialOrd)]
286 pub enum VersionedLocation {
287 #[codec(index = 3)]
288 V3(v3::MultiLocation),
289 #[codec(index = 4)]
290 V4(v4::Location),
291 #[codec(index = 5)]
292 V5(v5::Location),
293 }
294}
295
296versioned_type! {
297 pub enum VersionedInteriorLocation {
299 #[codec(index = 3)]
300 V3(v3::InteriorMultiLocation),
301 #[codec(index = 4)]
302 V4(v4::InteriorLocation),
303 #[codec(index = 5)]
304 V5(v5::InteriorLocation),
305 }
306}
307
308versioned_type! {
309 pub enum VersionedAsset {
311 #[codec(index = 3)]
312 V3(v3::MultiAsset),
313 #[codec(index = 4)]
314 V4(v4::Asset),
315 #[codec(index = 5)]
316 V5(v5::Asset),
317 }
318}
319
320versioned_type! {
321 pub enum VersionedAssets {
323 #[codec(index = 3)]
324 V3(v3::MultiAssets),
325 #[codec(index = 4)]
326 V4(v4::Assets),
327 #[codec(index = 5)]
328 V5(v5::Assets),
329 }
330}
331
332impl VersionedAssets {
333 pub fn len(&self) -> usize {
335 match self {
336 Self::V3(assets) => assets.len(),
337 Self::V4(assets) => assets.len(),
338 Self::V5(assets) => assets.len(),
339 }
340 }
341
342 pub fn is_empty(&self) -> bool {
344 self.len() == 0
345 }
346}
347
348#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)]
350#[derive_where(Clone, Eq, PartialEq, Debug)]
351#[codec(encode_bound())]
352#[codec(decode_bound(RuntimeCall: Decode))]
353#[codec(decode_with_mem_tracking_bound(RuntimeCall: Decode))]
354#[scale_info(bounds(), skip_type_params(RuntimeCall))]
355#[scale_info(replace_segment("staging_xcm", "xcm"))]
356pub enum VersionedXcm<RuntimeCall> {
357 #[codec(index = 3)]
358 V3(v3::Xcm<RuntimeCall>),
359 #[codec(index = 4)]
360 V4(v4::Xcm<RuntimeCall>),
361 #[codec(index = 5)]
362 V5(v5::Xcm<RuntimeCall>),
363}
364
365impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
366 fn into_version(self, n: Version) -> Result<Self, ()> {
367 Ok(match n {
368 3 => Self::V3(self.try_into()?),
369 4 => Self::V4(self.try_into()?),
370 5 => Self::V5(self.try_into()?),
371 _ => return Err(()),
372 })
373 }
374}
375
376impl<C> IdentifyVersion for VersionedXcm<C> {
377 fn identify_version(&self) -> Version {
378 match self {
379 Self::V3(_) => v3::VERSION,
380 Self::V4(_) => v4::VERSION,
381 Self::V5(_) => v5::VERSION,
382 }
383 }
384}
385
386impl<C: Decode> VersionedXcm<C> {
387 pub fn decode_all_with_mem_and_depth_limit(
393 input: &mut &[u8],
394 ) -> Result<VersionedXcm<C>, CodecError> {
395 let mut mem_tracking_input = MemTrackingInput::new(input, MAX_XCM_SIZE.saturating_add(1));
398 let xcm =
399 VersionedXcm::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut mem_tracking_input)?;
400 if !input.is_empty() {
403 return Err(DECODE_ALL_ERR_MSG.into());
404 }
405
406 Ok(xcm)
407 }
408
409 pub fn check_is_decodable(&self) -> Result<(), ()> {
419 self.using_encoded(|mut enc| {
420 Self::decode_all_with_mem_and_depth_limit(&mut enc).map(|_| ())
421 })
422 .map_err(|e| {
423 tracing::error!(target: "xcm::check_is_decodable", error=?e, xcm=?self, "Decode error!");
424 ()
425 })
426 }
427}
428
429impl<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
430 fn from(x: v3::Xcm<RuntimeCall>) -> Self {
431 VersionedXcm::V3(x)
432 }
433}
434
435impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
436 fn from(x: v4::Xcm<RuntimeCall>) -> Self {
437 VersionedXcm::V4(x)
438 }
439}
440
441impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
442 fn from(x: v5::Xcm<RuntimeCall>) -> Self {
443 VersionedXcm::V5(x)
444 }
445}
446
447impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
448 type Error = ();
449 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
450 use VersionedXcm::*;
451 match x {
452 V3(x) => Ok(x),
453 V4(x) => x.try_into(),
454 V5(x) => {
455 let v4: v4::Xcm<Call> = x.try_into()?;
456 v4.try_into()
457 },
458 }
459 }
460}
461
462impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
463 type Error = ();
464 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
465 use VersionedXcm::*;
466 match x {
467 V3(x) => x.try_into(),
468 V4(x) => Ok(x),
469 V5(x) => x.try_into(),
470 }
471 }
472}
473
474impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
475 type Error = ();
476 fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
477 use VersionedXcm::*;
478 match x {
479 V3(x) => {
480 let v4: v4::Xcm<Call> = x.try_into()?;
481 v4.try_into()
482 },
483 V4(x) => x.try_into(),
484 V5(x) => Ok(x),
485 }
486 }
487}
488
489pub trait WrapVersion {
492 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
493 dest: &latest::Location,
494 xcm: impl Into<VersionedXcm<RuntimeCall>>,
495 ) -> Result<VersionedXcm<RuntimeCall>, ()>;
496}
497
498pub trait IdentifyVersion {
501 fn identify_version(&self) -> Version;
502}
503
504pub trait GetVersion {
507 fn get_version_for(dest: &latest::Location) -> Option<Version>;
508}
509
510impl WrapVersion for () {
513 fn wrap_version<RuntimeCall>(
514 _: &latest::Location,
515 xcm: impl Into<VersionedXcm<RuntimeCall>>,
516 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
517 Ok(xcm.into())
518 }
519}
520
521pub struct AlwaysV3;
524impl WrapVersion for AlwaysV3 {
525 fn wrap_version<Call: Decode + GetDispatchInfo>(
526 _: &latest::Location,
527 xcm: impl Into<VersionedXcm<Call>>,
528 ) -> Result<VersionedXcm<Call>, ()> {
529 Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
530 }
531}
532impl GetVersion for AlwaysV3 {
533 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
534 Some(v3::VERSION)
535 }
536}
537
538pub struct AlwaysV4;
541impl WrapVersion for AlwaysV4 {
542 fn wrap_version<Call: Decode + GetDispatchInfo>(
543 _: &latest::Location,
544 xcm: impl Into<VersionedXcm<Call>>,
545 ) -> Result<VersionedXcm<Call>, ()> {
546 Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
547 }
548}
549impl GetVersion for AlwaysV4 {
550 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
551 Some(v4::VERSION)
552 }
553}
554
555pub struct AlwaysV5;
558impl WrapVersion for AlwaysV5 {
559 fn wrap_version<Call: Decode + GetDispatchInfo>(
560 _: &latest::Location,
561 xcm: impl Into<VersionedXcm<Call>>,
562 ) -> Result<VersionedXcm<Call>, ()> {
563 Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
564 }
565}
566impl GetVersion for AlwaysV5 {
567 fn get_version_for(_dest: &latest::Location) -> Option<Version> {
568 Some(v5::VERSION)
569 }
570}
571
572pub type AlwaysLatest = AlwaysV5;
575
576pub type AlwaysLts = AlwaysV4;
579
580pub mod prelude {
581 pub use super::{
582 latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion,
583 IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset,
584 VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation,
585 VersionedResponse, VersionedXcm, WrapVersion,
586 };
587
588 pub const MIN_XCM_VERSION: XcmVersion = 3;
590}
591
592pub mod opaque {
593 pub mod v3 {
594 pub use crate::v3::*;
596 pub use crate::v3::opaque::{Instruction, Xcm};
598 }
599 pub mod v4 {
600 pub use crate::v4::*;
602 pub use crate::v4::opaque::{Instruction, Xcm};
604 }
605 pub mod v5 {
606 pub use crate::v5::*;
608 pub use crate::v5::opaque::{Instruction, Xcm};
610 }
611
612 pub mod latest {
613 pub use super::v5::*;
614 }
615
616 pub mod lts {
617 pub use super::v4::*;
618 }
619
620 pub type VersionedXcm = super::VersionedXcm<()>;
622}
623
624#[test]
625fn conversion_works() {
626 use latest::prelude::*;
627 let assets: Assets = (Here, 1u128).into();
628 let _: VersionedAssets = assets.into();
629}
630
631#[test]
632fn size_limits() {
633 extern crate std;
634
635 let mut test_failed = false;
636 macro_rules! check_sizes {
637 ($(($kind:ty, $expected:expr),)+) => {
638 $({
639 let s = core::mem::size_of::<$kind>();
640 if s > $expected {
645 test_failed = true;
646 std::eprintln!(
647 "assertion failed: size of '{}' is {} (which is more than the expected {})",
648 stringify!($kind),
649 s,
650 $expected
651 );
652 } else {
653 std::println!(
654 "type '{}' is of size {} which is within the expected {}",
655 stringify!($kind),
656 s,
657 $expected
658 );
659 }
660 })+
661 }
662 }
663
664 check_sizes! {
665 (crate::latest::Instruction<()>, 128),
666 (crate::latest::Asset, 80),
667 (crate::latest::Location, 24),
668 (crate::latest::AssetId, 40),
669 (crate::latest::Junctions, 16),
670 (crate::latest::Junction, 88),
671 (crate::latest::Response, 40),
672 (crate::latest::AssetInstance, 48),
673 (crate::latest::NetworkId, 48),
674 (crate::latest::BodyId, 32),
675 (crate::latest::Assets, 24),
676 (crate::latest::BodyPart, 12),
677 }
678 assert!(!test_failed);
679}
680
681#[test]
682fn check_is_decodable_works() {
683 use crate::{
684 latest::{
685 prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix},
686 Assets, Xcm, MAX_ITEMS_IN_ASSETS,
687 },
688 MAX_INSTRUCTIONS_TO_DECODE,
689 };
690
691 let assets = |count| {
693 let mut assets = Assets::new();
694 for i in 0..count {
695 assets.push((GeneralIndex(i as u128), 100).into());
696 }
697 assets
698 };
699
700 let with_instr = |depth| {
702 let mut xcm = Xcm::<()>(vec![]);
703 for _ in 0..depth - 1 {
704 xcm = Xcm::<()>(vec![SetAppendix(xcm)]);
705 }
706 xcm
707 };
708
709 assert!(VersionedXcm::<()>::from(Xcm(vec![
711 ReserveAssetDeposited(assets(1));
712 (MAX_INSTRUCTIONS_TO_DECODE - 1) as usize
713 ]))
714 .check_is_decodable()
715 .is_ok());
716 assert!(VersionedXcm::<()>::from(Xcm(vec![
717 ReserveAssetDeposited(assets(1));
718 MAX_INSTRUCTIONS_TO_DECODE as usize
719 ]))
720 .check_is_decodable()
721 .is_ok());
722 assert!(VersionedXcm::<()>::from(Xcm(vec![
723 ReserveAssetDeposited(assets(1));
724 (MAX_INSTRUCTIONS_TO_DECODE + 1) as usize
725 ]))
726 .check_is_decodable()
727 .is_err());
728
729 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1))
731 .check_is_decodable()
732 .is_ok());
733 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH))
734 .check_is_decodable()
735 .is_ok());
736 assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1))
737 .check_is_decodable()
738 .is_err());
739
740 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
742 MAX_ITEMS_IN_ASSETS
743 ))]))
744 .check_is_decodable()
745 .is_ok());
746 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
747 MAX_ITEMS_IN_ASSETS - 1
748 ))]))
749 .check_is_decodable()
750 .is_ok());
751 assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
752 MAX_ITEMS_IN_ASSETS + 1
753 ))]))
754 .check_is_decodable()
755 .is_err());
756}