Files
pezkuwi-sdk/pezkuwi/xcm/src/lib.rs
T
pezkuwichain 4666047395 chore: add Dijital Kurdistan Tech Institute to copyright headers
Updated 4763 files with dual copyright:
- Parity Technologies (UK) Ltd.
- Dijital Kurdistan Tech Institute
2025-12-27 21:28:36 +03:00

701 lines
18 KiB
Rust

// 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 <http://www.gnu.org/licenses/>.
//! 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<I: Input>(_: &mut I) -> Result<Self, CodecError> {
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<Self, ()>;
/// Consume `self` and return same value expressed the latest version of XCM.
fn into_latest(self) -> Result<Self, ()> {
self.into_version(latest::VERSION)
}
}
pub trait TryAs<T> {
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<T>(&self) -> Result<&T, ()> where Self: TryAs<T> {
<Self as TryAs<T>>::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<Self, ()> {
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<T: Into<$v5>> From<T> for $n {
fn from(x: T) -> Self {
$n::V5(x.into())
}
}
impl TryFrom<$n> for $v3 {
type Error = ();
fn try_from(x: $n) -> Result<Self, ()> {
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<Self, ()> {
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<Self, ()> {
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<RuntimeCall> {
#[codec(index = 3)]
V3(v3::Xcm<RuntimeCall>),
#[codec(index = 4)]
V4(v4::Xcm<RuntimeCall>),
#[codec(index = 5)]
V5(v5::Xcm<RuntimeCall>),
}
impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
fn into_version(self, n: Version) -> Result<Self, ()> {
Ok(match n {
3 => Self::V3(self.try_into()?),
4 => Self::V4(self.try_into()?),
5 => Self::V5(self.try_into()?),
_ => return Err(()),
})
}
}
impl<C> IdentifyVersion for VersionedXcm<C> {
fn identify_version(&self) -> Version {
match self {
Self::V3(_) => v3::VERSION,
Self::V4(_) => v4::VERSION,
Self::V5(_) => v5::VERSION,
}
}
}
impl<C> VersionedXcm<C> {
/// 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<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v3::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V3(x)
}
}
impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v4::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V4(x)
}
}
impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v5::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V5(x)
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => Ok(x),
V4(x) => x.try_into(),
V5(x) => {
let v4: v4::Xcm<Call> = x.try_into()?;
v4.try_into()
},
}
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => x.try_into(),
V4(x) => Ok(x),
V5(x) => x.try_into(),
}
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => {
let v4: v4::Xcm<Call> = 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<RuntimeCall: Decode + GetDispatchInfo>(
dest: &latest::Location,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()>;
}
/// 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<Version>;
}
/// `()` implementation does nothing with the XCM, just sending with whatever version it was
/// authored as.
impl WrapVersion for () {
fn wrap_version<RuntimeCall>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()> {
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<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV3 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
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<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV4 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
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<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV5 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
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<u8>` 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());
}