pub use super::v3::GetWeight;
use super::{
v3::{
Instruction as OldInstruction, PalletInfo as OldPalletInfo,
QueryResponseInfo as OldQueryResponseInfo, Response as OldResponse, Xcm as OldXcm,
},
v5::{
Instruction as NewInstruction, PalletInfo as NewPalletInfo,
QueryResponseInfo as NewQueryResponseInfo, Response as NewResponse, Xcm as NewXcm,
},
};
use crate::DoubleEncoded;
use alloc::{vec, vec::Vec};
use bounded_collections::{parameter_types, BoundedVec};
use codec::{
self, decode_vec_with_len, Compact, Decode, Encode, Error as CodecError, Input as CodecInput,
MaxEncodedLen,
};
use core::{fmt::Debug, result};
use derivative::Derivative;
use frame_support::dispatch::GetDispatchInfo;
use scale_info::TypeInfo;
mod asset;
mod junction;
pub(crate) mod junctions;
mod location;
mod traits;
pub use asset::{
Asset, AssetFilter, AssetId, AssetInstance, Assets, Fungibility, WildAsset, WildFungibility,
MAX_ITEMS_IN_ASSETS,
};
pub use junction::{BodyId, BodyPart, Junction, NetworkId};
pub use junctions::Junctions;
pub use location::{Ancestor, AncestorThen, InteriorLocation, Location, Parent, ParentThen};
pub use traits::{
send_xcm, validate_send, Error, ExecuteXcm, Outcome, PreparedMessage, Reanchorable, Result,
SendError, SendResult, SendXcm, Weight, XcmHash,
};
pub use super::v3::{MaxDispatchErrorLen, MaybeErrorCode, OriginKind, WeightLimit};
pub const VERSION: super::Version = 4;
pub type QueryId = u64;
#[derive(Derivative, Default, Encode, TypeInfo)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(bounds(), skip_type_params(Call))]
pub struct Xcm<Call>(pub Vec<Instruction<Call>>);
pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
environmental::environmental!(instructions_count: u8);
impl<Call> Decode for Xcm<Call> {
fn decode<I: CodecInput>(input: &mut I) -> core::result::Result<Self, CodecError> {
instructions_count::using_once(&mut 0, || {
let number_of_instructions: u32 = <Compact<u32>>::decode(input)?.into();
instructions_count::with(|count| {
*count = count.saturating_add(number_of_instructions as u8);
if *count > MAX_INSTRUCTIONS_TO_DECODE {
return Err(CodecError::from("Max instructions exceeded"))
}
Ok(())
})
.expect("Called in `using` context and thus can not return `None`; qed")?;
let decoded_instructions = decode_vec_with_len(input, number_of_instructions as usize)?;
Ok(Self(decoded_instructions))
})
}
}
impl<Call> Xcm<Call> {
pub fn new() -> Self {
Self(vec![])
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn inner(&self) -> &[Instruction<Call>] {
&self.0
}
pub fn inner_mut(&mut self) -> &mut Vec<Instruction<Call>> {
&mut self.0
}
pub fn into_inner(self) -> Vec<Instruction<Call>> {
self.0
}
pub fn iter(&self) -> impl Iterator<Item = &Instruction<Call>> {
self.0.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Instruction<Call>> {
self.0.iter_mut()
}
pub fn into_iter(self) -> impl Iterator<Item = Instruction<Call>> {
self.0.into_iter()
}
pub fn or_else(self, f: impl FnOnce() -> Self) -> Self {
if self.0.is_empty() {
f()
} else {
self
}
}
pub fn first(&self) -> Option<&Instruction<Call>> {
self.0.first()
}
pub fn last(&self) -> Option<&Instruction<Call>> {
self.0.last()
}
pub fn only(&self) -> Option<&Instruction<Call>> {
if self.0.len() == 1 {
self.0.first()
} else {
None
}
}
pub fn into_only(mut self) -> core::result::Result<Instruction<Call>, Self> {
if self.0.len() == 1 {
self.0.pop().ok_or(self)
} else {
Err(self)
}
}
}
impl<Call> From<Vec<Instruction<Call>>> for Xcm<Call> {
fn from(c: Vec<Instruction<Call>>) -> Self {
Self(c)
}
}
impl<Call> From<Xcm<Call>> for Vec<Instruction<Call>> {
fn from(c: Xcm<Call>) -> Self {
c.0
}
}
pub mod prelude {
mod contents {
pub use super::super::{
send_xcm, validate_send, Ancestor, AncestorThen, Asset,
AssetFilter::{self, *},
AssetId,
AssetInstance::{self, *},
Assets, BodyId, BodyPart, Error as XcmError, ExecuteXcm,
Fungibility::{self, *},
Instruction::*,
InteriorLocation,
Junction::{self, *},
Junctions::{self, Here},
Location, MaybeErrorCode,
NetworkId::{self, *},
OriginKind, Outcome, PalletInfo, Parent, ParentThen, PreparedMessage, QueryId,
QueryResponseInfo, Reanchorable, Response, Result as XcmResult, SendError, SendResult,
SendXcm, Weight,
WeightLimit::{self, *},
WildAsset::{self, *},
WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible},
XcmContext, XcmHash, XcmWeightInfo, VERSION as XCM_VERSION,
};
}
pub use super::{Instruction, Xcm};
pub use contents::*;
pub mod opaque {
pub use super::{
super::opaque::{Instruction, Xcm},
contents::*,
};
}
}
parameter_types! {
pub MaxPalletNameLen: u32 = 48;
pub MaxPalletsInfo: u32 = 64;
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
pub struct PalletInfo {
#[codec(compact)]
pub index: u32,
pub name: BoundedVec<u8, MaxPalletNameLen>,
pub module_name: BoundedVec<u8, MaxPalletNameLen>,
#[codec(compact)]
pub major: u32,
#[codec(compact)]
pub minor: u32,
#[codec(compact)]
pub patch: u32,
}
impl TryInto<OldPalletInfo> for PalletInfo {
type Error = ();
fn try_into(self) -> result::Result<OldPalletInfo, Self::Error> {
OldPalletInfo::new(
self.index,
self.name.into_inner(),
self.module_name.into_inner(),
self.major,
self.minor,
self.patch,
)
.map_err(|_| ())
}
}
impl TryInto<NewPalletInfo> for PalletInfo {
type Error = ();
fn try_into(self) -> result::Result<NewPalletInfo, Self::Error> {
NewPalletInfo::new(
self.index,
self.name.into_inner(),
self.module_name.into_inner(),
self.major,
self.minor,
self.patch,
)
.map_err(|_| ())
}
}
impl PalletInfo {
pub fn new(
index: u32,
name: Vec<u8>,
module_name: Vec<u8>,
major: u32,
minor: u32,
patch: u32,
) -> result::Result<Self, Error> {
let name = BoundedVec::try_from(name).map_err(|_| Error::Overflow)?;
let module_name = BoundedVec::try_from(module_name).map_err(|_| Error::Overflow)?;
Ok(Self { index, name, module_name, major, minor, patch })
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
pub enum Response {
Null,
Assets(Assets),
ExecutionResult(Option<(u32, Error)>),
Version(super::Version),
PalletsInfo(BoundedVec<PalletInfo, MaxPalletsInfo>),
DispatchResult(MaybeErrorCode),
}
impl Default for Response {
fn default() -> Self {
Self::Null
}
}
impl TryFrom<OldResponse> for Response {
type Error = ();
fn try_from(old: OldResponse) -> result::Result<Self, Self::Error> {
use OldResponse::*;
Ok(match old {
Null => Self::Null,
Assets(assets) => Self::Assets(assets.try_into()?),
ExecutionResult(result) =>
Self::ExecutionResult(result.map(|(num, old_error)| (num, old_error.into()))),
Version(version) => Self::Version(version),
PalletsInfo(pallet_info) => {
let inner = pallet_info
.into_iter()
.map(TryInto::try_into)
.collect::<result::Result<Vec<_>, _>>()?;
Self::PalletsInfo(
BoundedVec::<PalletInfo, MaxPalletsInfo>::try_from(inner).map_err(|_| ())?,
)
},
DispatchResult(maybe_error) => Self::DispatchResult(maybe_error),
})
}
}
impl TryFrom<NewResponse> for Response {
type Error = ();
fn try_from(new: NewResponse) -> result::Result<Self, Self::Error> {
use NewResponse::*;
Ok(match new {
Null => Self::Null,
Assets(assets) => Self::Assets(assets.try_into()?),
ExecutionResult(result) => Self::ExecutionResult(
result
.map(|(num, new_error)| (num, new_error.try_into()))
.map(|(num, result)| result.map(|inner| (num, inner)))
.transpose()?,
),
Version(version) => Self::Version(version),
PalletsInfo(pallet_info) => {
let inner = pallet_info
.into_iter()
.map(TryInto::try_into)
.collect::<result::Result<Vec<_>, _>>()?;
Self::PalletsInfo(
BoundedVec::<PalletInfo, MaxPalletsInfo>::try_from(inner).map_err(|_| ())?,
)
},
DispatchResult(maybe_error) =>
Self::DispatchResult(maybe_error.try_into().map_err(|_| ())?),
})
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
pub struct QueryResponseInfo {
pub destination: Location,
#[codec(compact)]
pub query_id: QueryId,
pub max_weight: Weight,
}
impl TryFrom<NewQueryResponseInfo> for QueryResponseInfo {
type Error = ();
fn try_from(new: NewQueryResponseInfo) -> result::Result<Self, Self::Error> {
Ok(Self {
destination: new.destination.try_into()?,
query_id: new.query_id,
max_weight: new.max_weight,
})
}
}
impl TryFrom<OldQueryResponseInfo> for QueryResponseInfo {
type Error = ();
fn try_from(old: OldQueryResponseInfo) -> result::Result<Self, Self::Error> {
Ok(Self {
destination: old.destination.try_into()?,
query_id: old.query_id,
max_weight: old.max_weight,
})
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)]
pub struct XcmContext {
pub origin: Option<Location>,
pub message_id: XcmHash,
pub topic: Option<[u8; 32]>,
}
impl XcmContext {
pub fn with_message_id(message_id: XcmHash) -> XcmContext {
XcmContext { origin: None, message_id, topic: None }
}
}
#[derive(
Derivative,
Encode,
Decode,
TypeInfo,
xcm_procedural::XcmWeightInfoTrait,
xcm_procedural::Builder,
)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(bounds(), skip_type_params(Call))]
pub enum Instruction<Call> {
#[builder(loads_holding)]
WithdrawAsset(Assets),
#[builder(loads_holding)]
ReserveAssetDeposited(Assets),
#[builder(loads_holding)]
ReceiveTeleportedAsset(Assets),
QueryResponse {
#[codec(compact)]
query_id: QueryId,
response: Response,
max_weight: Weight,
querier: Option<Location>,
},
TransferAsset { assets: Assets, beneficiary: Location },
TransferReserveAsset { assets: Assets, dest: Location, xcm: Xcm<()> },
Transact { origin_kind: OriginKind, require_weight_at_most: Weight, call: DoubleEncoded<Call> },
HrmpNewChannelOpenRequest {
#[codec(compact)]
sender: u32,
#[codec(compact)]
max_message_size: u32,
#[codec(compact)]
max_capacity: u32,
},
HrmpChannelAccepted {
#[codec(compact)]
recipient: u32,
},
HrmpChannelClosing {
#[codec(compact)]
initiator: u32,
#[codec(compact)]
sender: u32,
#[codec(compact)]
recipient: u32,
},
ClearOrigin,
DescendOrigin(InteriorLocation),
ReportError(QueryResponseInfo),
DepositAsset { assets: AssetFilter, beneficiary: Location },
DepositReserveAsset { assets: AssetFilter, dest: Location, xcm: Xcm<()> },
ExchangeAsset { give: AssetFilter, want: Assets, maximal: bool },
InitiateReserveWithdraw { assets: AssetFilter, reserve: Location, xcm: Xcm<()> },
InitiateTeleport { assets: AssetFilter, dest: Location, xcm: Xcm<()> },
ReportHolding { response_info: QueryResponseInfo, assets: AssetFilter },
#[builder(pays_fees)]
BuyExecution { fees: Asset, weight_limit: WeightLimit },
RefundSurplus,
SetErrorHandler(Xcm<Call>),
SetAppendix(Xcm<Call>),
ClearError,
#[builder(loads_holding)]
ClaimAsset { assets: Assets, ticket: Location },
Trap(#[codec(compact)] u64),
SubscribeVersion {
#[codec(compact)]
query_id: QueryId,
max_response_weight: Weight,
},
UnsubscribeVersion,
BurnAsset(Assets),
ExpectAsset(Assets),
ExpectOrigin(Option<Location>),
ExpectError(Option<(u32, Error)>),
ExpectTransactStatus(MaybeErrorCode),
QueryPallet { module_name: Vec<u8>, response_info: QueryResponseInfo },
ExpectPallet {
#[codec(compact)]
index: u32,
name: Vec<u8>,
module_name: Vec<u8>,
#[codec(compact)]
crate_major: u32,
#[codec(compact)]
min_crate_minor: u32,
},
ReportTransactStatus(QueryResponseInfo),
ClearTransactStatus,
UniversalOrigin(Junction),
ExportMessage { network: NetworkId, destination: InteriorLocation, xcm: Xcm<()> },
LockAsset { asset: Asset, unlocker: Location },
UnlockAsset { asset: Asset, target: Location },
NoteUnlockable { asset: Asset, owner: Location },
RequestUnlock { asset: Asset, locker: Location },
SetFeesMode { jit_withdraw: bool },
SetTopic([u8; 32]),
ClearTopic,
AliasOrigin(Location),
UnpaidExecution { weight_limit: WeightLimit, check_origin: Option<Location> },
}
impl<Call> Xcm<Call> {
pub fn into<C>(self) -> Xcm<C> {
Xcm::from(self)
}
pub fn from<C>(xcm: Xcm<C>) -> Self {
Self(xcm.0.into_iter().map(Instruction::<Call>::from).collect())
}
}
impl<Call> Instruction<Call> {
pub fn into<C>(self) -> Instruction<C> {
Instruction::from(self)
}
pub fn from<C>(xcm: Instruction<C>) -> Self {
use Instruction::*;
match xcm {
WithdrawAsset(assets) => WithdrawAsset(assets),
ReserveAssetDeposited(assets) => ReserveAssetDeposited(assets),
ReceiveTeleportedAsset(assets) => ReceiveTeleportedAsset(assets),
QueryResponse { query_id, response, max_weight, querier } =>
QueryResponse { query_id, response, max_weight, querier },
TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary },
TransferReserveAsset { assets, dest, xcm } =>
TransferReserveAsset { assets, dest, xcm },
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient },
HrmpChannelClosing { initiator, sender, recipient } =>
HrmpChannelClosing { initiator, sender, recipient },
Transact { origin_kind, require_weight_at_most, call } =>
Transact { origin_kind, require_weight_at_most, call: call.into() },
ReportError(response_info) => ReportError(response_info),
DepositAsset { assets, beneficiary } => DepositAsset { assets, beneficiary },
DepositReserveAsset { assets, dest, xcm } => DepositReserveAsset { assets, dest, xcm },
ExchangeAsset { give, want, maximal } => ExchangeAsset { give, want, maximal },
InitiateReserveWithdraw { assets, reserve, xcm } =>
InitiateReserveWithdraw { assets, reserve, xcm },
InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, xcm },
ReportHolding { response_info, assets } => ReportHolding { response_info, assets },
BuyExecution { fees, weight_limit } => BuyExecution { fees, weight_limit },
ClearOrigin => ClearOrigin,
DescendOrigin(who) => DescendOrigin(who),
RefundSurplus => RefundSurplus,
SetErrorHandler(xcm) => SetErrorHandler(xcm.into()),
SetAppendix(xcm) => SetAppendix(xcm.into()),
ClearError => ClearError,
ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket },
Trap(code) => Trap(code),
SubscribeVersion { query_id, max_response_weight } =>
SubscribeVersion { query_id, max_response_weight },
UnsubscribeVersion => UnsubscribeVersion,
BurnAsset(assets) => BurnAsset(assets),
ExpectAsset(assets) => ExpectAsset(assets),
ExpectOrigin(origin) => ExpectOrigin(origin),
ExpectError(error) => ExpectError(error),
ExpectTransactStatus(transact_status) => ExpectTransactStatus(transact_status),
QueryPallet { module_name, response_info } =>
QueryPallet { module_name, response_info },
ExpectPallet { index, name, module_name, crate_major, min_crate_minor } =>
ExpectPallet { index, name, module_name, crate_major, min_crate_minor },
ReportTransactStatus(response_info) => ReportTransactStatus(response_info),
ClearTransactStatus => ClearTransactStatus,
UniversalOrigin(j) => UniversalOrigin(j),
ExportMessage { network, destination, xcm } =>
ExportMessage { network, destination, xcm },
LockAsset { asset, unlocker } => LockAsset { asset, unlocker },
UnlockAsset { asset, target } => UnlockAsset { asset, target },
NoteUnlockable { asset, owner } => NoteUnlockable { asset, owner },
RequestUnlock { asset, locker } => RequestUnlock { asset, locker },
SetFeesMode { jit_withdraw } => SetFeesMode { jit_withdraw },
SetTopic(topic) => SetTopic(topic),
ClearTopic => ClearTopic,
AliasOrigin(location) => AliasOrigin(location),
UnpaidExecution { weight_limit, check_origin } =>
UnpaidExecution { weight_limit, check_origin },
}
}
}
impl<Call, W: XcmWeightInfo<Call>> GetWeight<W> for Instruction<Call> {
fn weight(&self) -> Weight {
use Instruction::*;
match self {
WithdrawAsset(assets) => W::withdraw_asset(assets),
ReserveAssetDeposited(assets) => W::reserve_asset_deposited(assets),
ReceiveTeleportedAsset(assets) => W::receive_teleported_asset(assets),
QueryResponse { query_id, response, max_weight, querier } =>
W::query_response(query_id, response, max_weight, querier),
TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary),
TransferReserveAsset { assets, dest, xcm } =>
W::transfer_reserve_asset(&assets, dest, xcm),
Transact { origin_kind, require_weight_at_most, call } =>
W::transact(origin_kind, require_weight_at_most, call),
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity),
HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient),
HrmpChannelClosing { initiator, sender, recipient } =>
W::hrmp_channel_closing(initiator, sender, recipient),
ClearOrigin => W::clear_origin(),
DescendOrigin(who) => W::descend_origin(who),
ReportError(response_info) => W::report_error(&response_info),
DepositAsset { assets, beneficiary } => W::deposit_asset(assets, beneficiary),
DepositReserveAsset { assets, dest, xcm } =>
W::deposit_reserve_asset(assets, dest, xcm),
ExchangeAsset { give, want, maximal } => W::exchange_asset(give, want, maximal),
InitiateReserveWithdraw { assets, reserve, xcm } =>
W::initiate_reserve_withdraw(assets, reserve, xcm),
InitiateTeleport { assets, dest, xcm } => W::initiate_teleport(assets, dest, xcm),
ReportHolding { response_info, assets } => W::report_holding(&response_info, &assets),
BuyExecution { fees, weight_limit } => W::buy_execution(fees, weight_limit),
RefundSurplus => W::refund_surplus(),
SetErrorHandler(xcm) => W::set_error_handler(xcm),
SetAppendix(xcm) => W::set_appendix(xcm),
ClearError => W::clear_error(),
ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket),
Trap(code) => W::trap(code),
SubscribeVersion { query_id, max_response_weight } =>
W::subscribe_version(query_id, max_response_weight),
UnsubscribeVersion => W::unsubscribe_version(),
BurnAsset(assets) => W::burn_asset(assets),
ExpectAsset(assets) => W::expect_asset(assets),
ExpectOrigin(origin) => W::expect_origin(origin),
ExpectError(error) => W::expect_error(error),
ExpectTransactStatus(transact_status) => W::expect_transact_status(transact_status),
QueryPallet { module_name, response_info } =>
W::query_pallet(module_name, response_info),
ExpectPallet { index, name, module_name, crate_major, min_crate_minor } =>
W::expect_pallet(index, name, module_name, crate_major, min_crate_minor),
ReportTransactStatus(response_info) => W::report_transact_status(response_info),
ClearTransactStatus => W::clear_transact_status(),
UniversalOrigin(j) => W::universal_origin(j),
ExportMessage { network, destination, xcm } =>
W::export_message(network, destination, xcm),
LockAsset { asset, unlocker } => W::lock_asset(asset, unlocker),
UnlockAsset { asset, target } => W::unlock_asset(asset, target),
NoteUnlockable { asset, owner } => W::note_unlockable(asset, owner),
RequestUnlock { asset, locker } => W::request_unlock(asset, locker),
SetFeesMode { jit_withdraw } => W::set_fees_mode(jit_withdraw),
SetTopic(topic) => W::set_topic(topic),
ClearTopic => W::clear_topic(),
AliasOrigin(location) => W::alias_origin(location),
UnpaidExecution { weight_limit, check_origin } =>
W::unpaid_execution(weight_limit, check_origin),
}
}
}
pub mod opaque {
pub type Xcm = super::Xcm<()>;
pub type Instruction = super::Instruction<()>;
}
impl<Call> TryFrom<OldXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(old_xcm: OldXcm<Call>) -> result::Result<Self, Self::Error> {
Ok(Xcm(old_xcm.0.into_iter().map(TryInto::try_into).collect::<result::Result<_, _>>()?))
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<NewXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(new_xcm: NewXcm<Call>) -> result::Result<Self, Self::Error> {
Ok(Xcm(new_xcm.0.into_iter().map(TryInto::try_into).collect::<result::Result<_, _>>()?))
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<NewInstruction<Call>> for Instruction<Call> {
type Error = ();
fn try_from(new_instruction: NewInstruction<Call>) -> result::Result<Self, Self::Error> {
use NewInstruction::*;
Ok(match new_instruction {
WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?),
ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?),
ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?),
QueryResponse { query_id, response, max_weight, querier: Some(querier) } =>
Self::QueryResponse {
query_id,
querier: querier.try_into()?,
response: response.try_into()?,
max_weight,
},
QueryResponse { query_id, response, max_weight, querier: None } =>
Self::QueryResponse {
query_id,
querier: None,
response: response.try_into()?,
max_weight,
},
TransferAsset { assets, beneficiary } => Self::TransferAsset {
assets: assets.try_into()?,
beneficiary: beneficiary.try_into()?,
},
TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset {
assets: assets.try_into()?,
dest: dest.try_into()?,
xcm: xcm.try_into()?,
},
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient },
HrmpChannelClosing { initiator, sender, recipient } =>
Self::HrmpChannelClosing { initiator, sender, recipient },
Transact { origin_kind, mut call, fallback_max_weight } => {
let require_weight_at_most = match call.take_decoded() {
Ok(decoded) => decoded.get_dispatch_info().call_weight,
Err(error) => {
let fallback_weight = fallback_max_weight.unwrap_or(Weight::MAX);
log::debug!(
target: "xcm::versions::v5Tov4",
"Couldn't decode call in Transact: {:?}, using fallback weight: {:?}",
error,
fallback_weight,
);
fallback_weight
},
};
Self::Transact { origin_kind, require_weight_at_most, call: call.into() }
},
ReportError(response_info) => Self::ReportError(QueryResponseInfo {
query_id: response_info.query_id,
destination: response_info.destination.try_into().map_err(|_| ())?,
max_weight: response_info.max_weight,
}),
DepositAsset { assets, beneficiary } => {
let beneficiary = beneficiary.try_into()?;
let assets = assets.try_into()?;
Self::DepositAsset { assets, beneficiary }
},
DepositReserveAsset { assets, dest, xcm } => {
let dest = dest.try_into()?;
let xcm = xcm.try_into()?;
let assets = assets.try_into()?;
Self::DepositReserveAsset { assets, dest, xcm }
},
ExchangeAsset { give, want, maximal } => {
let give = give.try_into()?;
let want = want.try_into()?;
Self::ExchangeAsset { give, want, maximal }
},
InitiateReserveWithdraw { assets, reserve, xcm } => {
let assets = assets.try_into()?;
let reserve = reserve.try_into()?;
let xcm = xcm.try_into()?;
Self::InitiateReserveWithdraw { assets, reserve, xcm }
},
InitiateTeleport { assets, dest, xcm } => {
let assets = assets.try_into()?;
let dest = dest.try_into()?;
let xcm = xcm.try_into()?;
Self::InitiateTeleport { assets, dest, xcm }
},
ReportHolding { response_info, assets } => {
let response_info = QueryResponseInfo {
destination: response_info.destination.try_into().map_err(|_| ())?,
query_id: response_info.query_id,
max_weight: response_info.max_weight,
};
Self::ReportHolding { response_info, assets: assets.try_into()? }
},
BuyExecution { fees, weight_limit } => {
let fees = fees.try_into()?;
let weight_limit = weight_limit.into();
Self::BuyExecution { fees, weight_limit }
},
ClearOrigin => Self::ClearOrigin,
DescendOrigin(who) => Self::DescendOrigin(who.try_into()?),
RefundSurplus => Self::RefundSurplus,
SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?),
SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?),
ClearError => Self::ClearError,
ClaimAsset { assets, ticket } => {
let assets = assets.try_into()?;
let ticket = ticket.try_into()?;
Self::ClaimAsset { assets, ticket }
},
Trap(code) => Self::Trap(code),
SubscribeVersion { query_id, max_response_weight } =>
Self::SubscribeVersion { query_id, max_response_weight },
UnsubscribeVersion => Self::UnsubscribeVersion,
BurnAsset(assets) => Self::BurnAsset(assets.try_into()?),
ExpectAsset(assets) => Self::ExpectAsset(assets.try_into()?),
ExpectOrigin(maybe_origin) =>
Self::ExpectOrigin(maybe_origin.map(|origin| origin.try_into()).transpose()?),
ExpectError(maybe_error) => Self::ExpectError(
maybe_error
.map(|(num, new_error)| (num, new_error.try_into()))
.map(|(num, result)| result.map(|inner| (num, inner)))
.transpose()?,
),
ExpectTransactStatus(maybe_error_code) => Self::ExpectTransactStatus(maybe_error_code),
QueryPallet { module_name, response_info } =>
Self::QueryPallet { module_name, response_info: response_info.try_into()? },
ExpectPallet { index, name, module_name, crate_major, min_crate_minor } =>
Self::ExpectPallet { index, name, module_name, crate_major, min_crate_minor },
ReportTransactStatus(response_info) =>
Self::ReportTransactStatus(response_info.try_into()?),
ClearTransactStatus => Self::ClearTransactStatus,
UniversalOrigin(junction) => Self::UniversalOrigin(junction.try_into()?),
ExportMessage { network, destination, xcm } => Self::ExportMessage {
network: network.into(),
destination: destination.try_into()?,
xcm: xcm.try_into()?,
},
LockAsset { asset, unlocker } =>
Self::LockAsset { asset: asset.try_into()?, unlocker: unlocker.try_into()? },
UnlockAsset { asset, target } =>
Self::UnlockAsset { asset: asset.try_into()?, target: target.try_into()? },
NoteUnlockable { asset, owner } =>
Self::NoteUnlockable { asset: asset.try_into()?, owner: owner.try_into()? },
RequestUnlock { asset, locker } =>
Self::RequestUnlock { asset: asset.try_into()?, locker: locker.try_into()? },
SetFeesMode { jit_withdraw } => Self::SetFeesMode { jit_withdraw },
SetTopic(topic) => Self::SetTopic(topic),
ClearTopic => Self::ClearTopic,
AliasOrigin(location) => Self::AliasOrigin(location.try_into()?),
UnpaidExecution { weight_limit, check_origin } => Self::UnpaidExecution {
weight_limit,
check_origin: check_origin.map(|origin| origin.try_into()).transpose()?,
},
InitiateTransfer { .. } |
PayFees { .. } |
SetHints { .. } |
ExecuteWithOrigin { .. } => {
log::debug!(target: "xcm::versions::v5tov4", "`{new_instruction:?}` not supported by v4");
return Err(());
},
})
}
}
impl<Call> TryFrom<OldInstruction<Call>> for Instruction<Call> {
type Error = ();
fn try_from(old_instruction: OldInstruction<Call>) -> result::Result<Self, Self::Error> {
use OldInstruction::*;
Ok(match old_instruction {
WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?),
ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?),
ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?),
QueryResponse { query_id, response, max_weight, querier: Some(querier) } =>
Self::QueryResponse {
query_id,
querier: querier.try_into()?,
response: response.try_into()?,
max_weight,
},
QueryResponse { query_id, response, max_weight, querier: None } =>
Self::QueryResponse {
query_id,
querier: None,
response: response.try_into()?,
max_weight,
},
TransferAsset { assets, beneficiary } => Self::TransferAsset {
assets: assets.try_into()?,
beneficiary: beneficiary.try_into()?,
},
TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset {
assets: assets.try_into()?,
dest: dest.try_into()?,
xcm: xcm.try_into()?,
},
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity },
HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient },
HrmpChannelClosing { initiator, sender, recipient } =>
Self::HrmpChannelClosing { initiator, sender, recipient },
Transact { origin_kind, require_weight_at_most, call } =>
Self::Transact { origin_kind, require_weight_at_most, call: call.into() },
ReportError(response_info) => Self::ReportError(QueryResponseInfo {
query_id: response_info.query_id,
destination: response_info.destination.try_into().map_err(|_| ())?,
max_weight: response_info.max_weight,
}),
DepositAsset { assets, beneficiary } => {
let beneficiary = beneficiary.try_into()?;
let assets = assets.try_into()?;
Self::DepositAsset { assets, beneficiary }
},
DepositReserveAsset { assets, dest, xcm } => {
let dest = dest.try_into()?;
let xcm = xcm.try_into()?;
let assets = assets.try_into()?;
Self::DepositReserveAsset { assets, dest, xcm }
},
ExchangeAsset { give, want, maximal } => {
let give = give.try_into()?;
let want = want.try_into()?;
Self::ExchangeAsset { give, want, maximal }
},
InitiateReserveWithdraw { assets, reserve, xcm } => {
let assets = assets.try_into()?;
let reserve = reserve.try_into()?;
let xcm = xcm.try_into()?;
Self::InitiateReserveWithdraw { assets, reserve, xcm }
},
InitiateTeleport { assets, dest, xcm } => {
let assets = assets.try_into()?;
let dest = dest.try_into()?;
let xcm = xcm.try_into()?;
Self::InitiateTeleport { assets, dest, xcm }
},
ReportHolding { response_info, assets } => {
let response_info = QueryResponseInfo {
destination: response_info.destination.try_into().map_err(|_| ())?,
query_id: response_info.query_id,
max_weight: response_info.max_weight,
};
Self::ReportHolding { response_info, assets: assets.try_into()? }
},
BuyExecution { fees, weight_limit } => {
let fees = fees.try_into()?;
let weight_limit = weight_limit.into();
Self::BuyExecution { fees, weight_limit }
},
ClearOrigin => Self::ClearOrigin,
DescendOrigin(who) => Self::DescendOrigin(who.try_into()?),
RefundSurplus => Self::RefundSurplus,
SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?),
SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?),
ClearError => Self::ClearError,
ClaimAsset { assets, ticket } => {
let assets = assets.try_into()?;
let ticket = ticket.try_into()?;
Self::ClaimAsset { assets, ticket }
},
Trap(code) => Self::Trap(code),
SubscribeVersion { query_id, max_response_weight } =>
Self::SubscribeVersion { query_id, max_response_weight },
UnsubscribeVersion => Self::UnsubscribeVersion,
BurnAsset(assets) => Self::BurnAsset(assets.try_into()?),
ExpectAsset(assets) => Self::ExpectAsset(assets.try_into()?),
ExpectOrigin(maybe_location) => Self::ExpectOrigin(
maybe_location.map(|location| location.try_into()).transpose().map_err(|_| ())?,
),
ExpectError(maybe_error) => Self::ExpectError(
maybe_error.map(|error| error.try_into()).transpose().map_err(|_| ())?,
),
ExpectTransactStatus(maybe_error_code) => Self::ExpectTransactStatus(maybe_error_code),
QueryPallet { module_name, response_info } => Self::QueryPallet {
module_name,
response_info: response_info.try_into().map_err(|_| ())?,
},
ExpectPallet { index, name, module_name, crate_major, min_crate_minor } =>
Self::ExpectPallet { index, name, module_name, crate_major, min_crate_minor },
ReportTransactStatus(response_info) =>
Self::ReportTransactStatus(response_info.try_into().map_err(|_| ())?),
ClearTransactStatus => Self::ClearTransactStatus,
UniversalOrigin(junction) =>
Self::UniversalOrigin(junction.try_into().map_err(|_| ())?),
ExportMessage { network, destination, xcm } => Self::ExportMessage {
network: network.into(),
destination: destination.try_into().map_err(|_| ())?,
xcm: xcm.try_into().map_err(|_| ())?,
},
LockAsset { asset, unlocker } => Self::LockAsset {
asset: asset.try_into().map_err(|_| ())?,
unlocker: unlocker.try_into().map_err(|_| ())?,
},
UnlockAsset { asset, target } => Self::UnlockAsset {
asset: asset.try_into().map_err(|_| ())?,
target: target.try_into().map_err(|_| ())?,
},
NoteUnlockable { asset, owner } => Self::NoteUnlockable {
asset: asset.try_into().map_err(|_| ())?,
owner: owner.try_into().map_err(|_| ())?,
},
RequestUnlock { asset, locker } => Self::RequestUnlock {
asset: asset.try_into().map_err(|_| ())?,
locker: locker.try_into().map_err(|_| ())?,
},
SetFeesMode { jit_withdraw } => Self::SetFeesMode { jit_withdraw },
SetTopic(topic) => Self::SetTopic(topic),
ClearTopic => Self::ClearTopic,
AliasOrigin(location) => Self::AliasOrigin(location.try_into().map_err(|_| ())?),
UnpaidExecution { weight_limit, check_origin } => Self::UnpaidExecution {
weight_limit,
check_origin: check_origin
.map(|location| location.try_into())
.transpose()
.map_err(|_| ())?,
},
})
}
}
#[cfg(test)]
mod tests {
use super::{prelude::*, *};
use crate::v3::{
Junctions::Here as OldHere, MultiAssetFilter as OldMultiAssetFilter,
WildMultiAsset as OldWildMultiAsset,
};
#[test]
fn basic_roundtrip_works() {
let xcm = Xcm::<()>(vec![TransferAsset {
assets: (Here, 1u128).into(),
beneficiary: Here.into(),
}]);
let old_xcm = OldXcm::<()>(vec![OldInstruction::TransferAsset {
assets: (OldHere, 1u128).into(),
beneficiary: OldHere.into(),
}]);
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn teleport_roundtrip_works() {
let xcm = Xcm::<()>(vec![
ReceiveTeleportedAsset((Here, 1u128).into()),
ClearOrigin,
DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() },
]);
let old_xcm: OldXcm<()> = OldXcm::<()>(vec![
OldInstruction::ReceiveTeleportedAsset((OldHere, 1u128).into()),
OldInstruction::ClearOrigin,
OldInstruction::DepositAsset {
assets: crate::v3::MultiAssetFilter::Wild(crate::v3::WildMultiAsset::AllCounted(1)),
beneficiary: OldHere.into(),
},
]);
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn reserve_deposit_roundtrip_works() {
let xcm = Xcm::<()>(vec![
ReserveAssetDeposited((Here, 1u128).into()),
ClearOrigin,
BuyExecution {
fees: (Here, 1u128).into(),
weight_limit: Some(Weight::from_parts(1, 1)).into(),
},
DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() },
]);
let old_xcm = OldXcm::<()>(vec![
OldInstruction::ReserveAssetDeposited((OldHere, 1u128).into()),
OldInstruction::ClearOrigin,
OldInstruction::BuyExecution {
fees: (OldHere, 1u128).into(),
weight_limit: WeightLimit::Limited(Weight::from_parts(1, 1)),
},
OldInstruction::DepositAsset {
assets: crate::v3::MultiAssetFilter::Wild(crate::v3::WildMultiAsset::AllCounted(1)),
beneficiary: OldHere.into(),
},
]);
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn deposit_asset_roundtrip_works() {
let xcm = Xcm::<()>(vec![
WithdrawAsset((Here, 1u128).into()),
DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() },
]);
let old_xcm = OldXcm::<()>(vec![
OldInstruction::WithdrawAsset((OldHere, 1u128).into()),
OldInstruction::DepositAsset {
assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::AllCounted(1)),
beneficiary: OldHere.into(),
},
]);
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn deposit_reserve_asset_roundtrip_works() {
let xcm = Xcm::<()>(vec![
WithdrawAsset((Here, 1u128).into()),
DepositReserveAsset {
assets: Wild(AllCounted(1)),
dest: Here.into(),
xcm: Xcm::<()>(vec![]),
},
]);
let old_xcm = OldXcm::<()>(vec![
OldInstruction::WithdrawAsset((OldHere, 1u128).into()),
OldInstruction::DepositReserveAsset {
assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::AllCounted(1)),
dest: OldHere.into(),
xcm: OldXcm::<()>(vec![]),
},
]);
assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap());
let new_xcm: Xcm<()> = old_xcm.try_into().unwrap();
assert_eq!(new_xcm, xcm);
}
#[test]
fn decoding_respects_limit() {
let max_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize]);
let encoded = max_xcm.encode();
assert!(Xcm::<()>::decode(&mut &encoded[..]).is_ok());
let big_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize + 1]);
let encoded = big_xcm.encode();
assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err());
let nested_xcm = Xcm::<()>(vec![
DepositReserveAsset {
assets: All.into(),
dest: Here.into(),
xcm: max_xcm,
};
(MAX_INSTRUCTIONS_TO_DECODE / 2) as usize
]);
let encoded = nested_xcm.encode();
assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err());
let even_more_nested_xcm = Xcm::<()>(vec![SetAppendix(nested_xcm); 64]);
let encoded = even_more_nested_xcm.encode();
assert_eq!(encoded.len(), 342530);
assert_eq!(MAX_INSTRUCTIONS_TO_DECODE, 100, "precondition");
assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err());
}
}