// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // This file is part of Pezkuwi. // Pezkuwi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Pezkuwi is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Pezkuwi. If not, see . //! Cross-Consensus Message format data structures. // NOTE, this crate is meant to be used in many different environments, notably wasm, but not // necessarily related to FRAME or even Bizinikiwi. // // Hence, `no_std` rather than sp-runtime. #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; use codec::{ Decode, DecodeLimit, DecodeWithMemTracking, Encode, Error as CodecError, Input, MaxEncodedLen, }; use derive_where::derive_where; use pezframe_support::dispatch::GetDispatchInfo; use scale_info::TypeInfo; pub mod v3; pub mod v4; pub mod v5; pub mod lts { pub use super::v4::*; } pub mod latest { pub use super::v5::*; } mod double_encoded; pub use double_encoded::DoubleEncoded; mod utils; #[cfg(test)] mod tests; /// Maximum nesting level for XCM decoding. pub const MAX_XCM_DECODE_DEPTH: u32 = 8; /// The maximal number of instructions in an XCM before decoding fails. /// /// This is a deliberate limit - not a technical one. pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100; /// A version of XCM. pub type Version = u32; #[derive(Clone, Eq, PartialEq, Debug)] pub enum Unsupported {} impl Encode for Unsupported {} impl Decode for Unsupported { fn decode(_: &mut I) -> Result { Err("Not decodable".into()) } } /// Attempt to convert `self` into a particular version of itself. pub trait IntoVersion: Sized { /// Consume `self` and return same value expressed in some particular `version` of XCM. fn into_version(self, version: Version) -> Result; /// Consume `self` and return same value expressed the latest version of XCM. fn into_latest(self) -> Result { self.into_version(latest::VERSION) } } pub trait TryAs { fn try_as(&self) -> Result<&T, ()>; } // Macro that generated versioned wrapper types. // NOTE: converting a v4 type into a versioned type will make it v5. macro_rules! versioned_type { ($(#[$attr:meta])* pub enum $n:ident { $(#[$index3:meta])+ V3($v3:ty), $(#[$index4:meta])+ V4($v4:ty), $(#[$index5:meta])+ V5($v5:ty), }) => { #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(replace_segment("pezstaging_xcm", "xcm"))] $(#[$attr])* pub enum $n { $(#[$index3])* V3($v3), $(#[$index4])* V4($v4), $(#[$index5])* V5($v5), } impl $n { pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { >::try_as(&self) } } impl TryAs<$v3> for $n { fn try_as(&self) -> Result<&$v3, ()> { match &self { Self::V3(ref x) => Ok(x), _ => Err(()), } } } impl TryAs<$v4> for $n { fn try_as(&self) -> Result<&$v4, ()> { match &self { Self::V4(ref x) => Ok(x), _ => Err(()), } } } impl TryAs<$v5> for $n { fn try_as(&self) -> Result<&$v5, ()> { match &self { Self::V5(ref x) => Ok(x), _ => Err(()), } } } impl IntoVersion for $n { fn into_version(self, n: Version) -> Result { let version = self.identify_version(); if version == n { Ok(self) } else { Ok(match n { 3 => Self::V3(self.try_into()?), 4 => Self::V4(self.try_into()?), 5 => Self::V5(self.try_into()?), _ => return Err(()), }) } } } impl From<$v3> for $n { fn from(x: $v3) -> Self { $n::V3(x.into()) } } impl> From for $n { fn from(x: T) -> Self { $n::V5(x.into()) } } impl TryFrom<$n> for $v3 { type Error = (); fn try_from(x: $n) -> Result { use $n::*; match x { V3(x) => Ok(x), V4(x) => x.try_into().map_err(|_| ()), V5(x) => { let v4: $v4 = x.try_into().map_err(|_| ())?; v4.try_into().map_err(|_| ()) } } } } impl TryFrom<$n> for $v4 { type Error = (); fn try_from(x: $n) -> Result { use $n::*; match x { V3(x) => x.try_into().map_err(|_| ()), V4(x) => Ok(x), V5(x) => x.try_into().map_err(|_| ()), } } } impl TryFrom<$n> for $v5 { type Error = (); fn try_from(x: $n) -> Result { use $n::*; match x { V3(x) => { let v4: $v4 = x.try_into().map_err(|_| ())?; v4.try_into().map_err(|_| ()) }, V4(x) => x.try_into().map_err(|_| ()), V5(x) => Ok(x), } } } impl MaxEncodedLen for $n { fn max_encoded_len() -> usize { <$v3>::max_encoded_len() } } impl IdentifyVersion for $n { fn identify_version(&self) -> Version { use $n::*; match self { V3(_) => v3::VERSION, V4(_) => v4::VERSION, V5(_) => v5::VERSION, } } } }; } versioned_type! { /// A single version's `AssetId` value, together with its version code. pub enum VersionedAssetId { #[codec(index = 3)] V3(v3::AssetId), #[codec(index = 4)] V4(v4::AssetId), #[codec(index = 5)] V5(v5::AssetId), } } versioned_type! { /// A single version's `Response` value, together with its version code. pub enum VersionedResponse { #[codec(index = 3)] V3(v3::Response), #[codec(index = 4)] V4(v4::Response), #[codec(index = 5)] V5(v5::Response), } } versioned_type! { /// A single `NetworkId` value, together with its version code. pub enum VersionedNetworkId { #[codec(index = 3)] V3(v3::NetworkId), #[codec(index = 4)] V4(v4::NetworkId), #[codec(index = 5)] V5(v5::NetworkId), } } versioned_type! { /// A single `Junction` value, together with its version code. pub enum VersionedJunction { #[codec(index = 3)] V3(v3::Junction), #[codec(index = 4)] V4(v4::Junction), #[codec(index = 5)] V5(v5::Junction), } } versioned_type! { /// A single `Location` value, together with its version code. #[derive(Ord, PartialOrd)] pub enum VersionedLocation { #[codec(index = 3)] V3(v3::MultiLocation), #[codec(index = 4)] V4(v4::Location), #[codec(index = 5)] V5(v5::Location), } } versioned_type! { /// A single `InteriorLocation` value, together with its version code. pub enum VersionedInteriorLocation { #[codec(index = 3)] V3(v3::InteriorMultiLocation), #[codec(index = 4)] V4(v4::InteriorLocation), #[codec(index = 5)] V5(v5::InteriorLocation), } } versioned_type! { /// A single `Asset` value, together with its version code. pub enum VersionedAsset { #[codec(index = 3)] V3(v3::MultiAsset), #[codec(index = 4)] V4(v4::Asset), #[codec(index = 5)] V5(v5::Asset), } } versioned_type! { /// A single `MultiAssets` value, together with its version code. pub enum VersionedAssets { #[codec(index = 3)] V3(v3::MultiAssets), #[codec(index = 4)] V4(v4::Assets), #[codec(index = 5)] V5(v5::Assets), } } /// A single XCM message, together with its version code. #[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)] #[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(RuntimeCall))] #[scale_info(replace_segment("pezstaging_xcm", "xcm"))] pub enum VersionedXcm { #[codec(index = 3)] V3(v3::Xcm), #[codec(index = 4)] V4(v4::Xcm), #[codec(index = 5)] V5(v5::Xcm), } impl IntoVersion for VersionedXcm { fn into_version(self, n: Version) -> Result { Ok(match n { 3 => Self::V3(self.try_into()?), 4 => Self::V4(self.try_into()?), 5 => Self::V5(self.try_into()?), _ => return Err(()), }) } } impl IdentifyVersion for VersionedXcm { fn identify_version(&self) -> Version { match self { Self::V3(_) => v3::VERSION, Self::V4(_) => v4::VERSION, Self::V5(_) => v5::VERSION, } } } impl VersionedXcm { /// Checks if the XCM is decodable. Consequently, it checks all decoding constraints, /// such as `MAX_XCM_DECODE_DEPTH`, `MAX_ITEMS_IN_ASSETS` or `MAX_INSTRUCTIONS_TO_DECODE`. /// /// Note that this uses the limit of the sender - not the receiver. It is a best effort. pub fn check_is_decodable(&self) -> Result<(), ()> { self.using_encoded(|mut enc| { Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ()) }) .map_err(|e| { tracing::error!(target: "xcm::check_is_decodable", error=?e, xcm=?self, "Decode error!"); () }) } } impl From> for VersionedXcm { fn from(x: v3::Xcm) -> Self { VersionedXcm::V3(x) } } impl From> for VersionedXcm { fn from(x: v4::Xcm) -> Self { VersionedXcm::V4(x) } } impl From> for VersionedXcm { fn from(x: v5::Xcm) -> Self { VersionedXcm::V5(x) } } impl TryFrom> for v3::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { use VersionedXcm::*; match x { V3(x) => Ok(x), V4(x) => x.try_into(), V5(x) => { let v4: v4::Xcm = x.try_into()?; v4.try_into() }, } } } impl TryFrom> for v4::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { use VersionedXcm::*; match x { V3(x) => x.try_into(), V4(x) => Ok(x), V5(x) => x.try_into(), } } } impl TryFrom> for v5::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { use VersionedXcm::*; match x { V3(x) => { let v4: v4::Xcm = x.try_into()?; v4.try_into() }, V4(x) => x.try_into(), V5(x) => Ok(x), } } } /// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `Location` which will /// interpret it. pub trait WrapVersion { fn wrap_version( dest: &latest::Location, xcm: impl Into>, ) -> Result, ()>; } /// Used to get the version out of a versioned type. // TODO(XCMv5): This could be `GetVersion` and we change the current one to `GetVersionFor`. pub trait IdentifyVersion { fn identify_version(&self) -> Version; } /// Check and return the `Version` that should be used for the `Xcm` datum for the destination /// `Location`, which will interpret it. pub trait GetVersion { fn get_version_for(dest: &latest::Location) -> Option; } /// `()` implementation does nothing with the XCM, just sending with whatever version it was /// authored as. impl WrapVersion for () { fn wrap_version( _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(xcm.into()) } } /// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before /// wrapping it. pub struct AlwaysV3; impl WrapVersion for AlwaysV3 { fn wrap_version( _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(VersionedXcm::::V3(xcm.into().try_into()?)) } } impl GetVersion for AlwaysV3 { fn get_version_for(_dest: &latest::Location) -> Option { Some(v3::VERSION) } } /// `WrapVersion` implementation which attempts to always convert the XCM to version 4 before /// wrapping it. pub struct AlwaysV4; impl WrapVersion for AlwaysV4 { fn wrap_version( _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(VersionedXcm::::V4(xcm.into().try_into()?)) } } impl GetVersion for AlwaysV4 { fn get_version_for(_dest: &latest::Location) -> Option { Some(v4::VERSION) } } /// `WrapVersion` implementation which attempts to always convert the XCM to version 5 before /// wrapping it. pub struct AlwaysV5; impl WrapVersion for AlwaysV5 { fn wrap_version( _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(VersionedXcm::::V5(xcm.into().try_into()?)) } } impl GetVersion for AlwaysV5 { fn get_version_for(_dest: &latest::Location) -> Option { Some(v5::VERSION) } } /// `WrapVersion` implementation which attempts to always convert the XCM to the latest version /// before wrapping it. pub type AlwaysLatest = AlwaysV5; /// `WrapVersion` implementation which attempts to always convert the XCM to the most recent Long- /// Term-Support version before wrapping it. pub type AlwaysLts = AlwaysV4; pub mod prelude { pub use super::{ latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion, IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset, VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation, VersionedResponse, VersionedXcm, WrapVersion, }; /// The minimal supported XCM version pub const MIN_XCM_VERSION: XcmVersion = 3; } pub mod opaque { pub mod v3 { // Everything from v3 pub use crate::v3::*; // Then override with the opaque types in v3 pub use crate::v3::opaque::{Instruction, Xcm}; } pub mod v4 { // Everything from v4 pub use crate::v4::*; // Then override with the opaque types in v4 pub use crate::v4::opaque::{Instruction, Xcm}; } pub mod v5 { // Everything from v4 pub use crate::v5::*; // Then override with the opaque types in v5 pub use crate::v5::opaque::{Instruction, Xcm}; } pub mod latest { pub use super::v5::*; } pub mod lts { pub use super::v4::*; } /// The basic `VersionedXcm` type which just uses the `Vec` as an encoded call. pub type VersionedXcm = super::VersionedXcm<()>; } #[test] fn conversion_works() { use latest::prelude::*; let assets: Assets = (Here, 1u128).into(); let _: VersionedAssets = assets.into(); } #[test] fn size_limits() { extern crate std; let mut test_failed = false; macro_rules! check_sizes { ($(($kind:ty, $expected:expr),)+) => { $({ let s = core::mem::size_of::<$kind>(); // Since the types often affect the size of other types in which they're included // it is more convenient to check multiple types at the same time and only fail // the test at the end. For debugging it's also useful to print out all of the sizes, // even if they're within the expected range. if s > $expected { test_failed = true; std::eprintln!( "assertion failed: size of '{}' is {} (which is more than the expected {})", stringify!($kind), s, $expected ); } else { std::println!( "type '{}' is of size {} which is within the expected {}", stringify!($kind), s, $expected ); } })+ } } check_sizes! { (crate::latest::Instruction<()>, 128), (crate::latest::Asset, 80), (crate::latest::Location, 24), (crate::latest::AssetId, 40), (crate::latest::Junctions, 16), (crate::latest::Junction, 88), (crate::latest::Response, 40), (crate::latest::AssetInstance, 48), (crate::latest::NetworkId, 48), (crate::latest::BodyId, 32), (crate::latest::Assets, 24), (crate::latest::BodyPart, 12), } assert!(!test_failed); } #[test] fn check_is_decodable_works() { use crate::{ latest::{ prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix}, Assets, Xcm, MAX_ITEMS_IN_ASSETS, }, MAX_INSTRUCTIONS_TO_DECODE, }; // closure generates assets of `count` let assets = |count| { let mut assets = Assets::new(); for i in 0..count { assets.push((GeneralIndex(i as u128), 100).into()); } assets }; // closer generates `Xcm` with nested instructions of `depth` let with_instr = |depth| { let mut xcm = Xcm::<()>(vec![]); for _ in 0..depth - 1 { xcm = Xcm::<()>(vec![SetAppendix(xcm)]); } xcm }; // `MAX_INSTRUCTIONS_TO_DECODE` check assert!(VersionedXcm::<()>::from(Xcm(vec![ ReserveAssetDeposited(assets(1)); (MAX_INSTRUCTIONS_TO_DECODE - 1) as usize ])) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(Xcm(vec![ ReserveAssetDeposited(assets(1)); MAX_INSTRUCTIONS_TO_DECODE as usize ])) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(Xcm(vec![ ReserveAssetDeposited(assets(1)); (MAX_INSTRUCTIONS_TO_DECODE + 1) as usize ])) .check_is_decodable() .is_err()); // `MAX_XCM_DECODE_DEPTH` check assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1)) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH)) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1)) .check_is_decodable() .is_err()); // `MAX_ITEMS_IN_ASSETS` check assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( MAX_ITEMS_IN_ASSETS ))])) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( MAX_ITEMS_IN_ASSETS - 1 ))])) .check_is_decodable() .is_ok()); assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( MAX_ITEMS_IN_ASSETS + 1 ))])) .check_is_decodable() .is_err()); }