From 8f0a57ab0303dab775bb132f77c4ebf25a271e73 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Fri, 6 Aug 2021 18:25:01 +0200 Subject: [PATCH] XCM v1 (#2815) * MultiAsset TWO * Draft next MultiAsset API. * XCM core builds * XCM Executor builds * XCM Builder builds * API changes making their way throughout * Some TODOs * Further build fixes * Basic compile builds * First test fixed * All executor tests fixed * Typo * Optimize subsume_assets and add test * Optimize checked_sub * XCM Builder first test fixed * Fix builder tests * Fix doc test * fix some doc tests * spelling * named fields for AllOf * Update xcm/src/v0/multiasset.rs Co-authored-by: Alexander Popiak * Update xcm/src/v0/multiasset.rs Co-authored-by: Alexander Popiak * Update xcm/src/v0/multiasset.rs Co-authored-by: Alexander Popiak * Update xcm/src/v0/multiasset.rs Co-authored-by: Alexander Popiak * Reformat * Move to XCM version 1 * Spelling * warnings * Replace some more v0->v1s * warnings * format * Add max_assets param * building * test fixes * tests * another test * final test * tests * Rename Null -> Here * Introduce * More ergonomics * More ergonomics * test fix * test fixes * docs * BuyExecution includes * Fix XCM extrinsics * fmt * Make Vec/MultiAssets conversions safe * More MultiAssets conversion safety * spelling * fix doc test * Apply suggestions from code review Co-authored-by: Amar Singh * Apply suggestions from code review Co-authored-by: Amar Singh * fmt * Add v0, remove VersionedMultiAsset * Remove VersionedMultiLocation * Update xcm/src/v1/order.rs Co-authored-by: Amar Singh * Update xcm/src/v1/mod.rs Co-authored-by: Amar Singh * XCM v0 backwards compatibility * Full compatibility * fmt * Update xcm/pallet-xcm/src/lib.rs * Update xcm/src/v0/order.rs Co-authored-by: Shawn Tabrizi * Tweaks to versioning system * Fixes * fmt * Update xcm/xcm-executor/src/assets.rs Co-authored-by: Shawn Tabrizi * Update xcm/xcm-executor/src/assets.rs Co-authored-by: Shawn Tabrizi * Grumbles * Update xcm/src/v1/multiasset.rs Co-authored-by: Shawn Tabrizi * fmt * Update xcm/src/v1/multiasset.rs Co-authored-by: Shawn Tabrizi * Update xcm/src/v1/multiasset.rs Co-authored-by: Shawn Tabrizi * Fixes * Formatting Co-authored-by: Alexander Popiak Co-authored-by: Amar Singh Co-authored-by: Shawn Tabrizi --- runtime/common/src/xcm_sender.rs | 21 +- runtime/kusama/src/lib.rs | 75 +- runtime/parachains/src/dmp.rs | 2 +- runtime/parachains/src/hrmp.rs | 6 +- runtime/parachains/src/ump.rs | 6 +- runtime/rococo/src/lib.rs | 82 +- runtime/westend/src/constants.rs | 2 +- runtime/westend/src/lib.rs | 72 +- scripts/gitlab/lingua.dic | 1 + xcm/pallet-xcm/src/lib.rs | 63 +- xcm/pallet-xcm/src/mock.rs | 33 +- xcm/pallet-xcm/src/tests.rs | 58 +- xcm/src/lib.rs | 121 ++- xcm/src/v0/mod.rs | 116 ++- xcm/src/v0/multi_asset.rs | 127 ++- xcm/src/v0/multi_location.rs | 30 +- xcm/src/v0/order.rs | 54 +- xcm/src/{v0 => v1}/junction.rs | 6 +- xcm/src/v1/mod.rs | 392 ++++++++ xcm/src/v1/multiasset.rs | 575 +++++++++++ xcm/src/v1/multilocation.rs | 821 +++++++++++++++ xcm/src/v1/order.rs | 243 +++++ xcm/src/v1/traits.rs | 263 +++++ xcm/xcm-builder/src/barriers.rs | 6 +- xcm/xcm-builder/src/currency_adapter.rs | 4 +- xcm/xcm-builder/src/filter_asset_location.rs | 6 +- xcm/xcm-builder/src/fungibles_adapter.rs | 15 +- xcm/xcm-builder/src/lib.rs | 4 +- xcm/xcm-builder/src/location_conversion.rs | 6 +- xcm/xcm-builder/src/matches_fungible.rs | 25 +- xcm/xcm-builder/src/mock.rs | 35 +- xcm/xcm-builder/src/origin_conversion.rs | 4 +- xcm/xcm-builder/src/tests.rs | 139 +-- xcm/xcm-builder/src/weight.rs | 166 +++- xcm/xcm-executor/src/assets.rs | 940 ++++++++---------- xcm/xcm-executor/src/config.rs | 2 +- xcm/xcm-executor/src/lib.rs | 97 +- xcm/xcm-executor/src/traits/conversion.rs | 4 +- .../src/traits/filter_asset_location.rs | 2 +- .../src/traits/matches_fungible.rs | 2 +- .../src/traits/matches_fungibles.rs | 2 +- xcm/xcm-executor/src/traits/on_response.rs | 2 +- xcm/xcm-executor/src/traits/should_execute.rs | 2 +- xcm/xcm-executor/src/traits/transact_asset.rs | 21 +- xcm/xcm-executor/src/traits/weight.rs | 6 +- xcm/xcm-simulator/example/src/lib.rs | 24 +- xcm/xcm-simulator/example/src/parachain.rs | 17 +- xcm/xcm-simulator/example/src/relay_chain.rs | 15 +- xcm/xcm-simulator/src/lib.rs | 2 +- 49 files changed, 3475 insertions(+), 1242 deletions(-) rename xcm/src/{v0 => v1}/junction.rs (98%) create mode 100644 xcm/src/v1/mod.rs create mode 100644 xcm/src/v1/multiasset.rs create mode 100644 xcm/src/v1/multilocation.rs create mode 100644 xcm/src/v1/order.rs create mode 100644 xcm/src/v1/traits.rs diff --git a/runtime/common/src/xcm_sender.rs b/runtime/common/src/xcm_sender.rs index 151c649a8c4c..c386fc5252d4 100644 --- a/runtime/common/src/xcm_sender.rs +++ b/runtime/common/src/xcm_sender.rs @@ -19,29 +19,30 @@ use parity_scale_codec::Encode; use runtime_parachains::{configuration, dmp}; use sp_std::marker::PhantomData; -use xcm::opaque::{ - v0::{Error, Junction, MultiLocation, Result, SendXcm, Xcm}, - VersionedXcm, -}; +use xcm::opaque::v1::{Error, Junction, MultiLocation, Result, SendXcm, Xcm}; /// XCM sender for relay chain. It only sends downward message. -pub struct ChildParachainRouter(PhantomData); +pub struct ChildParachainRouter(PhantomData<(T, W)>); -impl SendXcm for ChildParachainRouter { +impl SendXcm + for ChildParachainRouter +{ fn send_xcm(dest: MultiLocation, msg: Xcm) -> Result { - match dest { + match &dest { MultiLocation::X1(Junction::Parachain(id)) => { // Downward message passing. + let versioned_xcm = + W::wrap_version(&dest, msg).map_err(|()| Error::DestinationUnsupported)?; let config = >::config(); >::queue_downward_message( &config, - id.into(), - VersionedXcm::from(msg).encode(), + (*id).into(), + versioned_xcm.encode(), ) .map_err(Into::::into)?; Ok(()) }, - d => Err(Error::CannotReachDestination(d, msg)), + _ => Err(Error::CannotReachDestination(dest, msg)), } } } diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index c4f0da4cfafb..2a83120b41ed 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -75,13 +75,7 @@ use sp_staking::SessionIndex; use sp_version::NativeVersion; use sp_version::RuntimeVersion; use static_assertions::const_assert; -use xcm::v0::{ - BodyId, - Junction::Parachain, - MultiAsset::{self, AllConcreteFungible}, - MultiLocation::{self, Null, X1}, - NetworkId, Xcm, -}; +use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, @@ -1205,14 +1199,14 @@ impl auctions::Config for Runtime { parameter_types! { /// The location of the KSM token, from the context of this chain. Since this token is native to this - /// chain, we make it synonymous with it and thus it is the `Null` location, which means "equivalent to + /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to /// the context". - pub const KsmLocation: MultiLocation = MultiLocation::Null; + pub const KsmLocation: MultiLocation = MultiLocation::Here; /// The Kusama network ID. This is named. pub const KusamaNetwork: NetworkId = NetworkId::Kusama; /// Our XCM location ancestry - i.e. what, if anything, `Parent` means evaluated in our context. Since /// Kusama is a top-level relay-chain, there is no ancestry. - pub const Ancestry: MultiLocation = MultiLocation::Null; + pub const Ancestry: MultiLocation = MultiLocation::Here; /// The check account, which holds any native assets that have been teleported out and not back in (yet). pub CheckAccount: AccountId = XcmPallet::check_account(); } @@ -1264,12 +1258,12 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const KusamaForStatemint: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(1000))); + pub const Kusama: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(KsmLocation::get()) }); + pub const KusamaForStatemint: (MultiAssetFilter, MultiLocation) = (Kusama::get(), X1(Parachain(1000))); } pub type TrustedTeleporters = (xcm_builder::Case,); @@ -1317,65 +1311,14 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -pub struct OnlyWithdrawTeleportForAccounts; -impl frame_support::traits::Contains<(MultiLocation, Xcm)> - for OnlyWithdrawTeleportForAccounts -{ - fn contains((ref origin, ref msg): &(MultiLocation, Xcm)) -> bool { - use xcm::v0::{ - Junction::{AccountId32, Plurality}, - MultiAsset::{All, ConcreteFungible}, - Order::{BuyExecution, DepositAsset, InitiateTeleport}, - Xcm::WithdrawAsset, - }; - match origin { - // Root and council are are allowed to execute anything. - Null | X1(Plurality { .. }) => true, - X1(AccountId32 { .. }) => { - // An account ID trying to send a message. We ensure that it's sensible. - // This checks that it's of the form: - // WithdrawAsset { - // assets: [ ConcreteFungible { id: Null } ], - // effects: [ BuyExecution, InitiateTeleport { - // assets: All, - // dest: Parachain, - // effects: [ BuyExecution, DepositAssets { - // assets: All, - // dest: AccountId32, - // } ] - // } ] - // } - matches!(msg, WithdrawAsset { ref assets, ref effects } - if assets.len() == 1 - && matches!(assets[0], ConcreteFungible { id: Null, .. }) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], InitiateTeleport { ref assets, dest: X1(Parachain(..)), ref effects } - if assets.len() == 1 - && matches!(assets[0], All) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], DepositAsset { ref assets, dest: X1(AccountId32{..}) } - if assets.len() == 1 - && matches!(assets[0], All) - ) - ) - ) - }, - // Nobody else is allowed to execute anything. - _ => false, - } - } -} - impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally... type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - // ...but they must match our filter, which requires them to be a simple withdraw + teleport. - type XcmExecuteFilter = OnlyWithdrawTeleportForAccounts; + // ...but they must match our filter, which rejects all. + type XcmExecuteFilter = (); type XcmExecutor = XcmExecutor; type XcmTeleportFilter = All<(MultiLocation, Vec)>; type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; diff --git a/runtime/parachains/src/dmp.rs b/runtime/parachains/src/dmp.rs index 7982424800e5..6ca7f09fc773 100644 --- a/runtime/parachains/src/dmp.rs +++ b/runtime/parachains/src/dmp.rs @@ -22,7 +22,7 @@ use frame_support::pallet_prelude::*; use primitives::v1::{DownwardMessage, Hash, Id as ParaId, InboundDownwardMessage}; use sp_runtime::traits::{BlakeTwo256, Hash as HashT, SaturatedConversion}; use sp_std::{fmt, prelude::*}; -use xcm::v0::Error as XcmError; +use xcm::latest::Error as XcmError; pub use pallet::*; diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 320992b39827..1a85cbe9451e 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -1007,7 +1007,7 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v1::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpNewChannelOpenRequest { sender: u32::from(origin), @@ -1066,7 +1066,7 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v1::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelAccepted { recipient: u32::from(origin) }).encode() }; @@ -1106,7 +1106,7 @@ impl Pallet { let config = >::config(); let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v1::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelClosing { initiator: u32::from(origin), diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs index c64123537b02..31a72875407a 100644 --- a/runtime/parachains/src/ump.rs +++ b/runtime/parachains/src/ump.rs @@ -27,7 +27,7 @@ use sp_std::{ marker::PhantomData, prelude::*, }; -use xcm::v0::Outcome; +use xcm::latest::Outcome; pub use pallet::*; @@ -78,14 +78,14 @@ pub type MessageId = [u8; 32]; /// and will be forwarded to the XCM Executor. pub struct XcmSink(PhantomData<(XcmExecutor, Config)>); -impl, C: Config> UmpSink for XcmSink { +impl, C: Config> UmpSink for XcmSink { fn process_upward_message( origin: ParaId, data: &[u8], max_weight: Weight, ) -> Result { use xcm::{ - v0::{Error as XcmError, Junction, MultiLocation, Xcm}, + latest::{Error as XcmError, Junction, MultiLocation, Xcm}, VersionedXcm, }; diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index aa6be8361030..e4b5f737cd22 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -80,7 +80,7 @@ use polkadot_parachain::primitives::Id as ParaId; use constants::{currency::*, fee::*, time::*}; use frame_support::traits::InstanceFilter; -use xcm::v0::{BodyId, MultiLocation, NetworkId, Xcm}; +use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, @@ -583,9 +583,9 @@ impl parachains_paras::Config for Runtime { } parameter_types! { - pub const RocLocation: MultiLocation = MultiLocation::Null; + pub const RocLocation: MultiLocation = MultiLocation::Here; pub const RococoNetwork: NetworkId = NetworkId::Polkadot; - pub const Ancestry: MultiLocation = MultiLocation::Null; + pub const Ancestry: MultiLocation = MultiLocation::Here; pub CheckAccount: AccountId = XcmPallet::check_account(); } @@ -620,24 +620,15 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); -use xcm::v0::{ - Junction::Parachain, - MultiAsset, - MultiAsset::AllConcreteFungible, - MultiLocation::{Null, X1}, -}; parameter_types! { - pub const RococoForTick: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(100))); - pub const RococoForTrick: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(110))); - pub const RococoForTrack: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(120))); - pub const RococoForStatemint: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(1001))); + pub const Rococo: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(RocLocation::get()) }); + pub const RococoForTick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), X1(Parachain(100))); + pub const RococoForTrick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), X1(Parachain(110))); + pub const RococoForTrack: (MultiAssetFilter, MultiLocation) = (Rococo::get(), X1(Parachain(120))); + pub const RococoForStatemint: (MultiAssetFilter, MultiLocation) = (Rococo::get(), X1(Parachain(1001))); } pub type TrustedTeleporters = ( xcm_builder::Case, @@ -692,65 +683,14 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -pub struct OnlyWithdrawTeleportForAccounts; -impl frame_support::traits::Contains<(MultiLocation, Xcm)> - for OnlyWithdrawTeleportForAccounts -{ - fn contains((ref origin, ref msg): &(MultiLocation, Xcm)) -> bool { - use xcm::v0::{ - Junction::{AccountId32, Plurality}, - MultiAsset::{All, ConcreteFungible}, - Order::{BuyExecution, DepositAsset, InitiateTeleport}, - Xcm::WithdrawAsset, - }; - match origin { - // Root and collective are allowed to execute anything. - Null | X1(Plurality { .. }) => true, - X1(AccountId32 { .. }) => { - // An account ID trying to send a message. We ensure that it's sensible. - // This checks that it's of the form: - // WithdrawAsset { - // assets: [ ConcreteFungible { id: Null } ], - // effects: [ BuyExecution, InitiateTeleport { - // assets: All, - // dest: Parachain, - // effects: [ BuyExecution, DepositAssets { - // assets: All, - // dest: AccountId32, - // } ] - // } ] - // } - matches!(msg, WithdrawAsset { ref assets, ref effects } - if assets.len() == 1 - && matches!(assets[0], ConcreteFungible { id: Null, .. }) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], InitiateTeleport { ref assets, dest: X1(Parachain(..)), ref effects } - if assets.len() == 1 - && matches!(assets[0], All) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], DepositAsset { ref assets, dest: X1(AccountId32{..}) } - if assets.len() == 1 - && matches!(assets[0], All) - ) - ) - ) - }, - // Nobody else is allowed to execute anything. - _ => false, - } - } -} - impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally... type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - // ...but they must match our filter, which requires them to be a simple withdraw + teleport. - type XcmExecuteFilter = OnlyWithdrawTeleportForAccounts; + // ...but they must match our filter, which right now rejects everything. + type XcmExecuteFilter = (); type XcmExecutor = XcmExecutor; type XcmTeleportFilter = All<(MultiLocation, Vec)>; type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; diff --git a/runtime/westend/src/constants.rs b/runtime/westend/src/constants.rs index 77bdd954c03b..a6eaba9e2063 100644 --- a/runtime/westend/src/constants.rs +++ b/runtime/westend/src/constants.rs @@ -49,7 +49,7 @@ pub mod fee { use frame_support::weights::{ WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }; - use primitives::v0::Balance; + use primitives::v1::Balance; use runtime_common::ExtrinsicBaseWeight; use smallvec::smallvec; pub use sp_runtime::Perbill; diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index cdfc47da27fd..4d4efab21f2c 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -44,12 +44,7 @@ use runtime_parachains::{ session_info as parachains_session_info, shared as parachains_shared, ump as parachains_ump, }; -use xcm::v0::{ - Junction::Parachain, - MultiAsset::{self, AllConcreteFungible}, - MultiLocation::{self, Null, X1}, - NetworkId, Xcm, -}; +use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, @@ -871,8 +866,8 @@ impl auctions::Config for Runtime { } parameter_types! { - pub const WndLocation: MultiLocation = MultiLocation::Null; - pub const Ancestry: MultiLocation = MultiLocation::Null; + pub const WndLocation: MultiLocation = MultiLocation::Here; + pub const Ancestry: MultiLocation = MultiLocation::Here; pub WestendNetwork: NetworkId = NetworkId::Named(b"Westend".to_vec()); pub CheckAccount: AccountId = XcmPallet::check_account(); } @@ -908,12 +903,12 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const WestendForWestmint: (MultiAsset, MultiLocation) = - (AllConcreteFungible { id: Null }, X1(Parachain(1000))); + pub const WestendForWestmint: (MultiAssetFilter, MultiLocation) = + (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), X1(Parachain(1000))); } pub type TrustedTeleporters = (xcm_builder::Case,); @@ -949,65 +944,14 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -pub struct OnlyWithdrawTeleportForAccounts; -impl frame_support::traits::Contains<(MultiLocation, Xcm)> - for OnlyWithdrawTeleportForAccounts -{ - fn contains((ref origin, ref msg): &(MultiLocation, Xcm)) -> bool { - use xcm::v0::{ - Junction::AccountId32, - MultiAsset::{All, ConcreteFungible}, - Order::{BuyExecution, DepositAsset, InitiateTeleport}, - Xcm::WithdrawAsset, - }; - match origin { - // Root is allowed to execute anything. - Null => true, - X1(AccountId32 { .. }) => { - // An account ID trying to send a message. We ensure that it's sensible. - // This checks that it's of the form: - // WithdrawAsset { - // assets: [ ConcreteFungible { id: Null } ], - // effects: [ BuyExecution, InitiateTeleport { - // assets: All, - // dest: Parachain, - // effects: [ BuyExecution, DepositAssets { - // assets: All, - // dest: AccountId32, - // } ] - // } ] - // } - matches!(msg, WithdrawAsset { ref assets, ref effects } - if assets.len() == 1 - && matches!(assets[0], ConcreteFungible { id: Null, .. }) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], InitiateTeleport { ref assets, dest: X1(Parachain(..)), ref effects } - if assets.len() == 1 - && matches!(assets[0], All) - && effects.len() == 2 - && matches!(effects[0], BuyExecution { .. }) - && matches!(effects[1], DepositAsset { ref assets, dest: X1(AccountId32{..}) } - if assets.len() == 1 - && matches!(assets[0], All) - ) - ) - ) - }, - // Nobody else is allowed to execute anything. - _ => false, - } - } -} - impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally... type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - // ...but they must match our filter, which requires them to be a simple withdraw + teleport. - type XcmExecuteFilter = OnlyWithdrawTeleportForAccounts; + // ...but they must match our filter, which rejects everything. + type XcmExecuteFilter = (); type XcmExecutor = XcmExecutor; type XcmTeleportFilter = All<(MultiLocation, Vec)>; type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index 05a3f48207d6..948ef7e1000f 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -74,6 +74,7 @@ fedora/M finalize/B FRAME/MS FSMs +fungibility gameable getter/MS GiB/S diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 8187171bf86e..7ac74be7f089 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -23,10 +23,11 @@ mod mock; #[cfg(test)] mod tests; +use codec::{Decode, Encode}; use frame_support::traits::{Contains, EnsureOrigin, Filter, Get, OriginTrait}; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; use sp_std::{boxed::Box, convert::TryInto, marker::PhantomData, prelude::*, vec}; -use xcm::v0::prelude::*; +use xcm::latest::prelude::*; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -87,7 +88,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - Attempted(xcm::v0::Outcome), + Attempted(xcm::latest::Outcome), Sent(MultiLocation, MultiLocation, Xcm<()>), } @@ -134,15 +135,15 @@ pub mod pallet { /// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be /// an `AccountId32` value. - /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the - /// `dest` side. + /// - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the + /// `dest` side. May not be empty. /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message /// `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ let mut message = Xcm::WithdrawAsset { assets: assets.clone(), effects: sp_std::vec![ InitiateTeleport { - assets: sp_std::vec![ All ], + assets: Wild(All), dest: dest.clone(), effects: sp_std::vec![], } ] @@ -153,21 +154,28 @@ pub mod pallet { origin: OriginFor, dest: MultiLocation, beneficiary: MultiLocation, - assets: Vec, + assets: MultiAssets, + fee_asset_item: u32, dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let value = (origin_location, assets); + let value = (origin_location, assets.drain()); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (origin_location, assets) = value; let inv_dest = T::LocationInverter::invert_location(&dest); - let mut fees = assets.first().ok_or(Error::::Empty)?.clone(); - fees.reanchor(&inv_dest).map_err(|_| Error::::CannotReanchor)?; + let fees = assets + .get(fee_asset_item as usize) + .ok_or(Error::::Empty)? + .clone() + .reanchored(&inv_dest) + .map_err(|_| Error::::CannotReanchor)?; + let max_assets = assets.len() as u32; + let assets = assets.into(); let mut message = Xcm::WithdrawAsset { assets, effects: vec![InitiateTeleport { - assets: vec![All], + assets: Wild(All), dest, effects: vec![ BuyExecution { @@ -176,9 +184,10 @@ pub mod pallet { weight: 0, debt: dest_weight, halt_on_error: false, - xcm: vec![], + orders: vec![], + instructions: vec![], }, - DepositAsset { assets: vec![All], dest: beneficiary }, + DepositAsset { assets: Wild(All), max_assets, beneficiary }, ], }], }; @@ -203,7 +212,7 @@ pub mod pallet { /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the /// `dest` side. /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message - /// `ReserveAssetDeposit { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. + /// `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ let mut message = Xcm::TransferReserveAsset { assets: assets.clone(), @@ -216,30 +225,38 @@ pub mod pallet { origin: OriginFor, dest: MultiLocation, beneficiary: MultiLocation, - assets: Vec, + assets: MultiAssets, + fee_asset_item: u32, dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let value = (origin_location, assets); + let value = (origin_location, assets.drain()); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (origin_location, assets) = value; let inv_dest = T::LocationInverter::invert_location(&dest); - let mut fees = assets.first().ok_or(Error::::Empty)?.clone(); - fees.reanchor(&inv_dest).map_err(|_| Error::::CannotReanchor)?; + let fees = assets + .get(fee_asset_item as usize) + .ok_or(Error::::Empty)? + .clone() + .reanchored(&inv_dest) + .map_err(|_| Error::::CannotReanchor)?; + let max_assets = assets.len() as u32; + let assets = assets.into(); let mut message = Xcm::TransferReserveAsset { assets, dest, effects: vec![ BuyExecution { fees, - // Zero weight for additional XCM (since there are none to execute) + // Zero weight for additional instructions/orders (since there are none to execute) weight: 0, - debt: dest_weight, + debt: dest_weight, // covers this, `TransferReserveAsset` xcm, and `DepositAsset` order. halt_on_error: false, - xcm: vec![], + orders: vec![], + instructions: vec![], }, - DepositAsset { assets: vec![All], dest: beneficiary }, + DepositAsset { assets: Wild(All), max_assets, beneficiary }, ], }; let weight = @@ -286,7 +303,7 @@ pub mod pallet { message: Xcm<()>, ) -> Result<(), XcmError> { let message = match interior { - MultiLocation::Null => message, + MultiLocation::Here => message, who => Xcm::<()>::RelayedFrom { who, message: Box::new(message) }, }; log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); @@ -363,7 +380,7 @@ where #[cfg(feature = "runtime-benchmarks")] fn successful_origin() -> O { - O::from(Origin::Xcm(MultiLocation::Null)) + O::from(Origin::Xcm(MultiLocation::Here)) } } diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 4689b256bc07..7929534126b2 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -25,15 +25,15 @@ use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData}; use xcm::{ - opaque::v0::{Error as XcmError, MultiAsset, Result as XcmResult, SendXcm, Xcm}, - v0::{MultiLocation, NetworkId, Order}, + latest::prelude::*, + opaque::latest::{Error as XcmError, MultiAsset, Result as XcmResult, SendXcm, Xcm}, }; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfConcreteFungible, FixedWeightBounds, - IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, }; use xcm_executor::XcmExecutor; @@ -133,9 +133,9 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::Null; + pub const RelayLocation: MultiLocation = MultiLocation::Here; pub const AnyNetwork: NetworkId = NetworkId::Any; - pub Ancestry: MultiLocation = MultiLocation::Null; + pub Ancestry: MultiLocation = MultiLocation::Here; pub UnitWeightCost: Weight = 1_000; } @@ -154,7 +154,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = 1_000; - pub CurrencyPerSecond: (MultiLocation, u128) = (RelayLocation::get(), 1); + pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1); } pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom>); @@ -170,7 +170,7 @@ impl xcm_executor::Config for XcmConfig { type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfConcreteFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); } @@ -181,7 +181,7 @@ impl pallet_xcm::Config for Test { type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = (TestSendXcmErrX8, TestSendXcm); type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - type XcmExecuteFilter = All<(MultiLocation, xcm::v0::Xcm)>; + type XcmExecuteFilter = All<(MultiLocation, xcm::latest::Xcm)>; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = All<(MultiLocation, Vec)>; type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; @@ -195,9 +195,16 @@ pub(crate) fn last_event() -> Event { System::events().pop().expect("Event expected").event } -pub(crate) fn buy_execution(debt: Weight, fees: MultiAsset) -> Order { - use xcm::opaque::v0::prelude::*; - Order::BuyExecution { fees, weight: 0, debt, halt_on_error: false, xcm: vec![] } +pub(crate) fn buy_execution(fees: impl Into, debt: Weight) -> Order { + use xcm::opaque::latest::prelude::*; + Order::BuyExecution { + fees: fees.into(), + weight: 0, + debt, + halt_on_error: false, + orders: vec![], + instructions: vec![], + } } pub(crate) fn new_test_ext_with_balances( diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index a7e6b89430bf..f10e0490484d 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -18,8 +18,8 @@ use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use xcm::{ - opaque::v0::prelude::*, - v0::{Junction, Xcm}, + opaque::v1::prelude::*, + v1::{Junction, Xcm}, }; const ALICE: AccountId = AccountId::new([0u8; 32]); @@ -38,22 +38,19 @@ fn send_works() { new_test_ext_with_balances(balances).execute_with(|| { let weight = 2 * BaseXcmWeight::get(); let sender: MultiLocation = - Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let message = Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }], + AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let message = Xcm::ReserveAssetDeposited { + assets: (X1(Parent), SEND_AMOUNT).into(), effects: vec![ - buy_execution( - weight, - ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }, - ), - DepositAsset { assets: vec![All], dest: sender.clone() }, + buy_execution((Parent, SEND_AMOUNT), weight), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ], }; assert_ok!(XcmPallet::send(Origin::signed(ALICE), RelayLocation::get(), message.clone())); assert_eq!( sent_xcm(), vec![( - MultiLocation::Null, + MultiLocation::Here, RelayedFrom { who: sender.clone(), message: Box::new(message.clone()) } )] ); @@ -76,14 +73,11 @@ fn send_fails_when_xcm_router_blocks() { let weight = 2 * BaseXcmWeight::get(); let sender: MultiLocation = Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let message = Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }], + let message = Xcm::ReserveAssetDeposited { + assets: (Parent, SEND_AMOUNT).into(), effects: vec![ - buy_execution( - weight, - ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }, - ), - DepositAsset { assets: vec![All], dest: sender.clone() }, + buy_execution((Parent, SEND_AMOUNT), weight), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ], }; assert_noop!( @@ -120,8 +114,9 @@ fn teleport_assets_works() { assert_ok!(XcmPallet::teleport_assets( Origin::signed(ALICE), RelayLocation::get(), - MultiLocation::X1(Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }), - vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }], + X1(AccountId32 { network: Any, id: BOB.into() }), + (Here, SEND_AMOUNT).into(), + 0, weight, )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); @@ -149,7 +144,8 @@ fn reserve_transfer_assets_works() { Origin::signed(ALICE), Parachain(PARA_ID).into(), dest.clone(), - vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }], + (Here, SEND_AMOUNT).into(), + 0, weight )); // Alice spent amount @@ -161,14 +157,11 @@ fn reserve_transfer_assets_works() { sent_xcm(), vec![( Parachain(PARA_ID).into(), - Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }], + Xcm::ReserveAssetDeposited { + assets: (X1(Parent), SEND_AMOUNT).into(), effects: vec![ - buy_execution( - weight, - ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT } - ), - DepositAsset { assets: vec![All], dest }, + buy_execution((Parent, SEND_AMOUNT), weight), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, ] } )] @@ -196,13 +189,10 @@ fn execute_withdraw_to_deposit_works() { assert_ok!(XcmPallet::execute( Origin::signed(ALICE), Box::new(Xcm::WithdrawAsset { - assets: vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }], + assets: (Here, SEND_AMOUNT).into(), effects: vec![ - buy_execution( - weight, - ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT } - ), - DepositAsset { assets: vec![All], dest }, + buy_execution((Here, SEND_AMOUNT), weight), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest } ], }), weight diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index b51b1b985148..05443fc76b1c 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -23,14 +23,32 @@ #![no_std] extern crate alloc; +use core::{ + convert::{TryFrom, TryInto}, + result::Result, +}; use derivative::Derivative; -use parity_scale_codec::{Decode, Encode}; +use parity_scale_codec::{Decode, Encode, Error as CodecError, Input}; pub mod v0; +pub mod v1; + +pub mod latest { + pub use super::v1::*; +} mod double_encoded; pub use double_encoded::DoubleEncoded; +#[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()) + } +} + /// A single XCM message, together with its version code. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] @@ -38,8 +56,87 @@ pub use double_encoded::DoubleEncoded; #[codec(decode_bound())] pub enum VersionedXcm { V0(v0::Xcm), + V1(v1::Xcm), +} + +impl From> for VersionedXcm { + fn from(x: v0::Xcm) -> Self { + VersionedXcm::V0(x) + } +} + +impl From> for VersionedXcm { + fn from(x: v1::Xcm) -> Self { + VersionedXcm::V1(x) + } +} + +impl TryFrom> for v0::Xcm { + type Error = (); + fn try_from(x: VersionedXcm) -> Result { + match x { + VersionedXcm::V0(x) => Ok(x), + VersionedXcm::V1(x) => x.try_into(), + } + } +} + +impl TryFrom> for v1::Xcm { + type Error = (); + fn try_from(x: VersionedXcm) -> Result { + match x { + VersionedXcm::V0(x) => x.try_into(), + VersionedXcm::V1(x) => Ok(x), + } + } +} + +/// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `MultiLocation` which will interpret it. +pub trait WrapVersion { + fn wrap_version( + dest: &latest::MultiLocation, + xcm: impl Into>, + ) -> Result, ()>; } +/// `()` implementation does nothing with the XCM, just sending with whatever version it was authored as. +impl WrapVersion for () { + fn wrap_version( + _: &latest::MultiLocation, + xcm: impl Into>, + ) -> Result, ()> { + Ok(xcm.into()) + } +} + +/// `WrapVersion` implementation which attempts to always convert the XCM to version 0 before wrapping it. +pub struct AlwaysV0; +impl WrapVersion for AlwaysV0 { + fn wrap_version( + _: &latest::MultiLocation, + xcm: impl Into>, + ) -> Result, ()> { + Ok(VersionedXcm::::V0(xcm.into().try_into()?)) + } +} + +/// `WrapVersion` implementation which attempts to always convert the XCM to version 1 before wrapping it. +pub struct AlwaysV1; +impl WrapVersion for AlwaysV1 { + fn wrap_version( + _: &latest::MultiLocation, + xcm: impl Into>, + ) -> Result, ()> { + Ok(VersionedXcm::::V1(xcm.into().try_into()?)) + } +} + +/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version before wrapping it. +pub type AlwaysLatest = AlwaysV1; + +/// `WrapVersion` implementation which attempts to always convert the XCM to the release version before wrapping it. +pub type AlwaysRelease = AlwaysV0; + pub mod opaque { pub mod v0 { // Everything from v0 @@ -47,19 +144,17 @@ pub mod opaque { // Then override with the opaque types in v0 pub use crate::v0::opaque::{Order, Xcm}; } + pub mod v1 { + // Everything from v1 + pub use crate::v1::*; + // Then override with the opaque types in v1 + pub use crate::v1::opaque::{Order, Xcm}; + } + + pub mod latest { + pub use super::v1::*; + } /// The basic `VersionedXcm` type which just uses the `Vec` as an encoded call. pub type VersionedXcm = super::VersionedXcm<()>; } - -/// A versioned multi-location, a relative location of a cross-consensus system identifier. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] -pub enum VersionedMultiLocation { - V0(v0::MultiLocation), -} - -/// A versioned multi-asset, an identifier for an asset within a consensus system. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] -pub enum VersionedMultiAsset { - V0(v0::MultiAsset), -} diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index d775f9acfb0b..ad2f70e31cf5 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -16,18 +16,20 @@ //! Version 0 of the Cross-Consensus Message format data structures. -use crate::{DoubleEncoded, VersionedMultiAsset, VersionedXcm}; +use crate::DoubleEncoded; use alloc::vec::Vec; -use core::{convert::TryFrom, fmt::Debug, result}; +use core::{ + convert::{TryFrom, TryInto}, + result, +}; use derivative::Derivative; use parity_scale_codec::{self, Decode, Encode}; -mod junction; mod multi_asset; mod multi_location; mod order; mod traits; -pub use junction::{BodyId, BodyPart, Junction, NetworkId}; +use super::v1::Xcm as Xcm1; pub use multi_asset::{AssetInstance, MultiAsset}; pub use multi_location::MultiLocation; pub use order::Order; @@ -36,7 +38,6 @@ pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { pub use super::{ - junction::{BodyId, BodyPart, Junction::*, NetworkId}, multi_asset::{ AssetInstance::{self, *}, MultiAsset::{self, *}, @@ -44,43 +45,14 @@ pub mod prelude { multi_location::MultiLocation::{self, *}, order::Order::{self, *}, traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, - OriginKind, + BodyId, BodyPart, + Junction::*, + NetworkId, OriginKind, Xcm::{self, *}, }; } -// TODO: #2841 #XCMENCODE Efficient encodings for Vec, Vec, using initial byte values 128+ to encode -// the number of items in the vector. - -/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug)] -pub enum OriginKind { - /// Origin should just be the native dispatch origin representation for the sender in the - /// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin - /// if coming from a chain, though there may be others if the `MultiLocation` XCM origin has a - /// primary/native dispatch origin form. - Native, - - /// Origin should just be the standard account-based origin with the sovereign account of - /// the sender. For Cumulus/Frame chains, this is the `Signed` origin. - SovereignAccount, - - /// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin. - /// This will not usually be an available option. - Superuser, - - /// Origin should be interpreted as an XCM native origin and the `MultiLocation` should be - /// encoded directly in the dispatch origin unchanged. For Cumulus/Frame chains, this will be - /// the `pallet_xcm::Origin::Xcm` type. - Xcm, -} - -/// Response data to a query. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] -pub enum Response { - /// Some assets. - Assets(Vec), -} +pub use super::v1::{BodyId, BodyPart, Junction, NetworkId, OriginKind, Response}; /// Cross-Consensus Message: A message from one consensus system to another. /// @@ -275,21 +247,6 @@ pub enum Xcm { RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, } -impl From> for VersionedXcm { - fn from(x: Xcm) -> Self { - VersionedXcm::V0(x) - } -} - -impl TryFrom> for Xcm { - type Error = (); - fn try_from(x: VersionedXcm) -> result::Result { - match x { - VersionedXcm::V0(x) => Ok(x), - } - } -} - impl Xcm { pub fn into(self) -> Xcm { Xcm::from(self) @@ -329,3 +286,56 @@ pub mod opaque { pub use super::order::opaque::*; } + +impl TryFrom> for Xcm { + type Error = (); + fn try_from(x: Xcm1) -> result::Result, ()> { + use Xcm::*; + Ok(match x { + Xcm1::WithdrawAsset { assets, effects } => WithdrawAsset { + assets: assets.into(), + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm1::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposit { + assets: assets.into(), + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm1::ReceiveTeleportedAsset { assets, effects } => TeleportAsset { + assets: assets.into(), + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm1::QueryResponse { query_id: u64, response } => + QueryResponse { query_id: u64, response }, + Xcm1::TransferAsset { assets, beneficiary } => + TransferAsset { assets: assets.into(), dest: beneficiary.into() }, + Xcm1::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + assets: assets.into(), + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm1::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + Xcm1::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + Xcm1::HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + Xcm1::Transact { origin_type, require_weight_at_most, call } => + Transact { origin_type, require_weight_at_most, call: call.into() }, + Xcm1::RelayedFrom { who, message } => RelayedFrom { + who: who.into(), + message: alloc::boxed::Box::new((*message).try_into()?), + }, + }) + } +} diff --git a/xcm/src/v0/multi_asset.rs b/xcm/src/v0/multi_asset.rs index c29c4d87e447..3225e38a213a 100644 --- a/xcm/src/v0/multi_asset.rs +++ b/xcm/src/v0/multi_asset.rs @@ -16,40 +16,12 @@ //! Cross-Consensus Message format data structures. -use alloc::vec::Vec; -use core::{convert::TryFrom, result}; - -use super::{MultiLocation, VersionedMultiAsset}; +use super::MultiLocation; +use crate::v1::{MultiAsset as MultiAsset1, MultiAssetFilter, MultiAssets, WildMultiAsset}; +use alloc::{vec, vec::Vec}; use parity_scale_codec::{self, Decode, Encode}; -/// A general identifier for an instance of a non-fungible asset class. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] -pub enum AssetInstance { - /// Undefined - used if the NFA class has only one instance. - Undefined, - - /// A compact index. Technically this could be greater than `u128`, but this implementation supports only - /// values up to `2**128 - 1`. - Index { - #[codec(compact)] - id: u128, - }, - - /// A 4-byte fixed-length datum. - Array4([u8; 4]), - - /// An 8-byte fixed-length datum. - Array8([u8; 8]), - - /// A 16-byte fixed-length datum. - Array16([u8; 16]), - - /// A 32-byte fixed-length datum. - Array32([u8; 32]), - - /// An arbitrary piece of data. Use only when necessary. - Blob(Vec), -} +pub use crate::v1::AssetInstance; /// A single general identifier for an asset. /// @@ -318,17 +290,53 @@ impl MultiAsset { } } -impl From for VersionedMultiAsset { - fn from(x: MultiAsset) -> Self { - VersionedMultiAsset::V0(x) +impl From for MultiAsset { + fn from(a: MultiAsset1) -> MultiAsset { + use crate::v1::{AssetId::*, Fungibility::*}; + use MultiAsset::*; + match (a.id, a.fun) { + (Concrete(id), Fungible(amount)) => ConcreteFungible { id: id.into(), amount }, + (Concrete(class), NonFungible(instance)) => + ConcreteNonFungible { class: class.into(), instance }, + (Abstract(id), Fungible(amount)) => AbstractFungible { id, amount }, + (Abstract(class), NonFungible(instance)) => AbstractNonFungible { class, instance }, + } } } -impl TryFrom for MultiAsset { - type Error = (); - fn try_from(x: VersionedMultiAsset) -> result::Result { - match x { - VersionedMultiAsset::V0(x) => Ok(x), +impl From for Vec { + fn from(a: MultiAssets) -> Vec { + a.drain().into_iter().map(MultiAsset::from).collect() + } +} + +impl From for MultiAsset { + fn from(a: WildMultiAsset) -> MultiAsset { + use crate::v1::{AssetId::*, WildFungibility::*}; + use MultiAsset::*; + match a { + WildMultiAsset::All => All, + WildMultiAsset::AllOf { id, fun } => match (id, fun) { + (Concrete(id), Fungible) => AllConcreteFungible { id: id.into() }, + (Concrete(class), NonFungible) => AllConcreteNonFungible { class: class.into() }, + (Abstract(id), Fungible) => AllAbstractFungible { id }, + (Abstract(class), NonFungible) => AllAbstractNonFungible { class }, + }, + } + } +} + +impl From for Vec { + fn from(a: WildMultiAsset) -> Vec { + vec![a.into()] + } +} + +impl From for Vec { + fn from(a: MultiAssetFilter) -> Vec { + match a { + MultiAssetFilter::Definite(assets) => assets.into(), + MultiAssetFilter::Wild(wildcard) => wildcard.into(), } } } @@ -367,29 +375,20 @@ mod tests { .contains(&AbstractFungible { id: vec![99u8], amount: 100 })); // For non-fungibles, containing is equality. - assert!(!AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index { id: 9 } - } - .contains(&AbstractNonFungible { - class: vec![98u8], - instance: AssetInstance::Index { id: 9 } - })); - assert!(!AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index { id: 8 } - } - .contains(&AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index { id: 9 } - })); - assert!(AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index { id: 9 } - } - .contains(&AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index { id: 9 } - })); + assert!(!AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(9) } + .contains(&AbstractNonFungible { + class: vec![98u8], + instance: AssetInstance::Index(9) + })); + assert!(!AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(8) } + .contains(&AbstractNonFungible { + class: vec![99u8], + instance: AssetInstance::Index(9) + })); + assert!(AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(9) } + .contains(&AbstractNonFungible { + class: vec![99u8], + instance: AssetInstance::Index(9) + })); } } diff --git a/xcm/src/v0/multi_location.rs b/xcm/src/v0/multi_location.rs index e45df25a0ff6..1426529f2fdb 100644 --- a/xcm/src/v0/multi_location.rs +++ b/xcm/src/v0/multi_location.rs @@ -16,10 +16,9 @@ //! Cross-Consensus Message format data structures. -use core::{convert::TryFrom, mem, result}; +use core::{mem, result}; -use super::Junction; -use crate::VersionedMultiLocation; +use super::{super::v1::MultiLocation as MultiLocation1, Junction}; use parity_scale_codec::{self, Decode, Encode}; /// A relative path between state-bearing consensus systems. @@ -697,17 +696,20 @@ impl MultiLocation { } } -impl From for VersionedMultiLocation { - fn from(x: MultiLocation) -> Self { - VersionedMultiLocation::V0(x) - } -} - -impl TryFrom for MultiLocation { - type Error = (); - fn try_from(x: VersionedMultiLocation) -> result::Result { - match x { - VersionedMultiLocation::V0(x) => Ok(x), +impl From for MultiLocation { + fn from(old: MultiLocation1) -> Self { + use MultiLocation::*; + match old { + MultiLocation1::Here => Null, + MultiLocation1::X1(j0) => X1(j0), + MultiLocation1::X2(j0, j1) => X2(j0, j1), + MultiLocation1::X3(j0, j1, j2) => X3(j0, j1, j2), + MultiLocation1::X4(j0, j1, j2, j3) => X4(j0, j1, j2, j3), + MultiLocation1::X5(j0, j1, j2, j3, j4) => X5(j0, j1, j2, j3, j4), + MultiLocation1::X6(j0, j1, j2, j3, j4, j5) => X6(j0, j1, j2, j3, j4, j5), + MultiLocation1::X7(j0, j1, j2, j3, j4, j5, j6) => X7(j0, j1, j2, j3, j4, j5, j6), + MultiLocation1::X8(j0, j1, j2, j3, j4, j5, j6, j7) => + X8(j0, j1, j2, j3, j4, j5, j6, j7), } } } diff --git a/xcm/src/v0/order.rs b/xcm/src/v0/order.rs index c63df711758f..4674046e2628 100644 --- a/xcm/src/v0/order.rs +++ b/xcm/src/v0/order.rs @@ -16,8 +16,9 @@ //! Version 0 of the Cross-Consensus Message format data structures. -use super::{MultiAsset, MultiLocation, Xcm}; +use super::{super::v1::Order as Order1, MultiAsset, MultiLocation, Xcm}; use alloc::vec::Vec; +use core::{convert::TryFrom, result}; use derivative::Derivative; use parity_scale_codec::{self, Decode, Encode}; @@ -152,3 +153,54 @@ impl Order { } } } + +impl TryFrom> for Order { + type Error = (); + fn try_from(old: Order1) -> result::Result, ()> { + use Order::*; + Ok(match old { + Order1::Noop => Null, + Order1::DepositAsset { assets, beneficiary, .. } => + DepositAsset { assets: assets.into(), dest: beneficiary.into() }, + Order1::DepositReserveAsset { assets, dest, effects, .. } => DepositReserveAsset { + assets: assets.into(), + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order1::ExchangeAsset { give, receive } => + ExchangeAsset { give: give.into(), receive: receive.into() }, + Order1::InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { + assets: assets.into(), + reserve: reserve.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order1::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + assets: assets.into(), + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order1::QueryHolding { query_id, dest, assets } => + QueryHolding { query_id, dest: dest.into(), assets: assets.into() }, + Order1::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { + if !orders.is_empty() { + return Err(()) + } + let xcm = instructions + .into_iter() + .map(Xcm::::try_from) + .collect::>()?; + BuyExecution { fees: fees.into(), weight, debt, halt_on_error, xcm } + }, + }) + } +} diff --git a/xcm/src/v0/junction.rs b/xcm/src/v1/junction.rs similarity index 98% rename from xcm/src/v0/junction.rs rename to xcm/src/v1/junction.rs index cf474f62851a..569930506664 100644 --- a/xcm/src/v0/junction.rs +++ b/xcm/src/v1/junction.rs @@ -40,11 +40,7 @@ pub enum BodyId { /// A named body. Named(Vec), /// An indexed body. - // TODO: parity-scale-codec#262: Change to be a tuple. - Index { - #[codec(compact)] - id: u32, - }, + Index(#[codec(compact)] u32), /// The unambiguous executive body (for Polkadot, this would be the Polkadot council). Executive, /// The unambiguous technical body (for Polkadot, this would be the Technical Committee). diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs new file mode 100644 index 000000000000..6b7b4d4f2aea --- /dev/null +++ b/xcm/src/v1/mod.rs @@ -0,0 +1,392 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Version 1 of the Cross-Consensus Message format data structures. + +use super::v0::Xcm as Xcm0; +use crate::DoubleEncoded; +use alloc::vec::Vec; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + result, +}; +use derivative::Derivative; +use parity_scale_codec::{self, Decode, Encode}; + +mod junction; +pub mod multiasset; +mod multilocation; +mod order; +mod traits; // the new multiasset. + +pub use junction::{BodyId, BodyPart, Junction, NetworkId}; +pub use multiasset::{ + AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, + WildFungibility, WildMultiAsset, +}; +pub use multilocation::MultiLocation; +pub use order::Order; +pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; + +/// A prelude for importing all types typically used when interacting with XCM messages. +pub mod prelude { + pub use super::{ + junction::{ + BodyId, BodyPart, + Junction::*, + NetworkId::{self, *}, + }, + multiasset::{ + AssetId::{self, *}, + AssetInstance::{self, *}, + Fungibility::{self, *}, + MultiAsset, + MultiAssetFilter::{self, *}, + MultiAssets, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{self, *}, + }, + multilocation::MultiLocation::{self, *}, + opaque, + order::Order::{self, *}, + traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, + OriginKind, Response, + Xcm::{self, *}, + }; +} + +// TODO: #2841 #XCMENCODE Efficient encodings for MultiAssets, Vec, using initial byte values 128+ to encode +// the number of items in the vector. + +/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum OriginKind { + /// Origin should just be the native dispatch origin representation for the sender in the + /// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin + /// if coming from a chain, though there may be others if the `MultiLocation` XCM origin has a + /// primary/native dispatch origin form. + Native, + + /// Origin should just be the standard account-based origin with the sovereign account of + /// the sender. For Cumulus/Frame chains, this is the `Signed` origin. + SovereignAccount, + + /// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin. + /// This will not usually be an available option. + Superuser, + + /// Origin should be interpreted as an XCM native origin and the `MultiLocation` should be + /// encoded directly in the dispatch origin unchanged. For Cumulus/Frame chains, this will be + /// the `pallet_xcm::Origin::Xcm` type. + Xcm, +} + +/// Response data to a query. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum Response { + /// Some assets. + Assets(MultiAssets), +} + +/// Cross-Consensus Message: A message from one consensus system to another. +/// +/// Consensus systems that may send and receive messages include blockchains and smart contracts. +/// +/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. +/// +/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer +/// XCM format, known as `VersionedXcm`. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Xcm { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the + /// orders (`effects`). + /// + /// - `assets`: The asset(s) to be withdrawn into holding. + /// - `effects`: The order(s) to execute on the holding register. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 0)] + WithdrawAsset { assets: MultiAssets, effects: Vec> }, + + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into `holding`. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// - `effects`: The order(s) to execute on the holding register. + /// + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be + /// withdrawn should this system send a corresponding message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[codec(index = 1)] + ReserveAssetDeposited { assets: MultiAssets, effects: Vec> }, + + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be + /// created on this system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into the Holding Register. + /// - `effects`: The order(s) to execute on the Holding Register. + /// + /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` prior as a consequence + /// of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[codec(index = 2)] + ReceiveTeleportedAsset { assets: MultiAssets, effects: Vec> }, + + /// Indication of the contents of the holding register corresponding to the `QueryHolding` order of `query_id`. + /// + /// - `query_id`: The identifier of the query that resulted in this message being sent. + /// - `assets`: The message content. + /// + /// Safety: No concerns. + /// + /// Kind: *Information*. + /// + /// Errors: + #[codec(index = 3)] + QueryResponse { + #[codec(compact)] + query_id: u64, + response: Response, + }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the + /// ownership of `beneficiary`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `beneficiary`: The new owner for the assets. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 4)] + TransferAsset { assets: MultiAssets, beneficiary: MultiLocation }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the + /// ownership of `dest` within this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the + /// assets and the notification target for the reserve asset deposit message. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to + /// `dest`. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 5)] + TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, effects: Vec> }, + + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind + /// of origin `origin_type`. + /// + /// - `origin_type`: The means of expressing the message origin as a dispatch origin. + /// - `max_weight`: The weight of `call`; this should be at least the chain's calculated weight and will + /// be used in the weight determination arithmetic. + /// - `call`: The encoded transaction to be applied. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 6)] + Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded }, + + /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the + /// relay-chain to a para. + /// + /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening. + /// - `max_message_size`: The maximum size of a message proposed by the sender. + /// - `max_capacity`: The maximum number of messages that can be queued in the channel. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + #[codec(index = 7)] + HrmpNewChannelOpenRequest { + #[codec(compact)] + sender: u32, + #[codec(compact)] + max_message_size: u32, + #[codec(compact)] + max_capacity: u32, + }, + + /// A message to notify about that a previously sent open channel request has been accepted by + /// the recipient. That means that the channel will be opened during the next relay-chain session + /// change. This message is meant to be sent by the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + #[codec(index = 8)] + HrmpChannelAccepted { + #[codec(compact)] + recipient: u32, + }, + + /// A message to notify that the other party in an open channel decided to close it. In particular, + /// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close + /// will be enacted at the next relay-chain session change. This message is meant to be sent by + /// the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + #[codec(index = 9)] + HrmpChannelClosing { + #[codec(compact)] + initiator: u32, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, + }, + + /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus + /// location within the origin. + /// + /// Safety: `who` must be an interior location of the context. This basically means that no `Parent` + /// junctions are allowed in it. This should be verified at the time of XCM execution. + /// + /// Kind: *Instruction* + /// + /// Errors: + #[codec(index = 10)] + RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, +} + +impl Xcm { + pub fn into(self) -> Xcm { + Xcm::from(self) + } + pub fn from(xcm: Xcm) -> Self { + use Xcm::*; + match xcm { + WithdrawAsset { assets, effects } => + WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, + ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + assets, + effects: effects.into_iter().map(Order::into).collect(), + }, + ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { + assets, + effects: effects.into_iter().map(Order::into).collect(), + }, + QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response }, + TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, + TransferReserveAsset { assets, dest, effects } => + TransferReserveAsset { assets, dest, effects }, + 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_type, require_weight_at_most, call } => + Transact { origin_type, require_weight_at_most, call: call.into() }, + RelayedFrom { who, message } => + RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, + } + } +} + +pub mod opaque { + /// The basic concrete type of `generic::Xcm`, which doesn't make any assumptions about the format of a + /// call other than it is pre-encoded. + pub type Xcm = super::Xcm<()>; + + pub use super::order::opaque::*; +} + +impl TryFrom> for Xcm { + type Error = (); + fn try_from(old: Xcm0) -> result::Result, ()> { + use Xcm::*; + Ok(match old { + Xcm0::WithdrawAsset { assets, effects } => WithdrawAsset { + assets: assets.try_into()?, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm0::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited { + assets: assets.try_into()?, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm0::TeleportAsset { assets, effects } => ReceiveTeleportedAsset { + assets: assets.try_into()?, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm0::QueryResponse { query_id: u64, response } => + QueryResponse { query_id: u64, response }, + Xcm0::TransferAsset { assets, dest } => + TransferAsset { assets: assets.try_into()?, beneficiary: dest.into() }, + Xcm0::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + assets: assets.try_into()?, + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + Xcm0::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + Xcm0::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + Xcm0::HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + Xcm0::Transact { origin_type, require_weight_at_most, call } => + Transact { origin_type, require_weight_at_most, call: call.into() }, + Xcm0::RelayedFrom { who, message } => RelayedFrom { + who: who.into(), + message: alloc::boxed::Box::new((*message).try_into()?), + }, + }) + } +} diff --git a/xcm/src/v1/multiasset.rs b/xcm/src/v1/multiasset.rs new file mode 100644 index 000000000000..70614863f837 --- /dev/null +++ b/xcm/src/v1/multiasset.rs @@ -0,0 +1,575 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 Polkadot. If not, see . + +//! Cross-Consensus Message format asset data structures. +//! +//! This encompasses four types for representing assets: +//! - `MultiAsset`: A description of a single asset, either an instance of a non-fungible or some amount of a fungible. +//! - `MultiAssets`: A collection of `MultiAsset`s. These are stored in a `Vec` and sorted with fungibles first. +//! - `Wild`: A single asset wildcard, this can either be "all" assets, or all assets of a specific kind. +//! - `MultiAssetFilter`: A combination of `Wild` and `MultiAssets` designed for efficiently filtering an XCM holding +//! account. + +use super::{ + Junction, + MultiLocation::{self, X1}, +}; +use alloc::{vec, vec::Vec}; +use core::{ + cmp::Ordering, + convert::{TryFrom, TryInto}, + result, +}; +use parity_scale_codec::{self as codec, Decode, Encode}; + +/// A general identifier for an instance of a non-fungible asset class. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum AssetInstance { + /// Undefined - used if the non-fungible asset class has only one instance. + Undefined, + + /// A compact index. Technically this could be greater than `u128`, but this implementation supports only + /// values up to `2**128 - 1`. + Index(#[codec(compact)] u128), + + /// A 4-byte fixed-length datum. + Array4([u8; 4]), + + /// An 8-byte fixed-length datum. + Array8([u8; 8]), + + /// A 16-byte fixed-length datum. + Array16([u8; 16]), + + /// A 32-byte fixed-length datum. + Array32([u8; 32]), + + /// An arbitrary piece of data. Use only when necessary. + Blob(Vec), +} + +impl From<()> for AssetInstance { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +impl From<[u8; 4]> for AssetInstance { + fn from(x: [u8; 4]) -> Self { + Self::Array4(x) + } +} + +impl From<[u8; 8]> for AssetInstance { + fn from(x: [u8; 8]) -> Self { + Self::Array8(x) + } +} + +impl From<[u8; 16]> for AssetInstance { + fn from(x: [u8; 16]) -> Self { + Self::Array16(x) + } +} + +impl From<[u8; 32]> for AssetInstance { + fn from(x: [u8; 32]) -> Self { + Self::Array32(x) + } +} + +impl From> for AssetInstance { + fn from(x: Vec) -> Self { + Self::Blob(x) + } +} + +/// Classification of an asset being concrete or abstract. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode)] +pub enum AssetId { + Concrete(MultiLocation), + Abstract(Vec), +} + +impl From for AssetId { + fn from(x: MultiLocation) -> Self { + Self::Concrete(x) + } +} + +impl From for AssetId { + fn from(x: Junction) -> Self { + Self::Concrete(X1(x)) + } +} + +impl From> for AssetId { + fn from(x: Vec) -> Self { + Self::Abstract(x) + } +} + +impl AssetId { + /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + if let AssetId::Concrete(ref mut l) = self { + l.prepend_with(prepend.clone()).map_err(|_| ())?; + } + Ok(()) + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding `MultiAsset` value. + pub fn into_multiasset(self, fun: Fungibility) -> MultiAsset { + MultiAsset { fun, id: self } + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding `WildMultiAsset` + /// wildcard (`AllOf`) value. + pub fn into_wild(self, fun: WildFungibility) -> WildMultiAsset { + WildMultiAsset::AllOf { fun, id: self } + } +} + +/// Classification of whether an asset is fungible or not, along with a mandatory amount or instance. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode)] +pub enum Fungibility { + Fungible(u128), + NonFungible(AssetInstance), +} + +impl Fungibility { + pub fn is_kind(&self, w: WildFungibility) -> bool { + use Fungibility::*; + use WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}; + matches!((self, w), (Fungible(_), WildFungible) | (NonFungible(_), WildNonFungible)) + } +} + +impl From for Fungibility { + fn from(amount: u128) -> Fungibility { + debug_assert_ne!(amount, 0); + Fungibility::Fungible(amount) + } +} + +impl> From for Fungibility { + fn from(instance: T) -> Fungibility { + Fungibility::NonFungible(instance.into()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode)] +pub struct MultiAsset { + pub id: AssetId, + pub fun: Fungibility, +} + +impl PartialOrd for MultiAsset { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MultiAsset { + fn cmp(&self, other: &Self) -> Ordering { + match (&self.fun, &other.fun) { + (Fungibility::Fungible(..), Fungibility::NonFungible(..)) => Ordering::Less, + (Fungibility::NonFungible(..), Fungibility::Fungible(..)) => Ordering::Greater, + _ => (&self.id, &self.fun).cmp(&(&other.id, &other.fun)), + } + } +} + +impl, B: Into> From<(A, B)> for MultiAsset { + fn from((id, fun): (A, B)) -> MultiAsset { + MultiAsset { fun: fun.into(), id: id.into() } + } +} + +impl MultiAsset { + pub fn is_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, Fungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + pub fn is_non_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, NonFungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + self.id.reanchor(prepend) + } + + /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. + pub fn reanchored(mut self, prepend: &MultiLocation) -> Result { + self.reanchor(prepend)?; + Ok(self) + } + + /// Returns true if `self` is a super-set of the given `inner`. + pub fn contains(&self, inner: &MultiAsset) -> bool { + use Fungibility::*; + if self.id == inner.id { + match (&self.fun, &inner.fun) { + (Fungible(a), Fungible(i)) if a >= i => return true, + (NonFungible(a), NonFungible(i)) if a == i => return true, + _ => (), + } + } + false + } +} + +impl TryFrom for MultiAsset { + type Error = (); + fn try_from(old: super::super::v0::MultiAsset) -> result::Result { + use super::super::v0::MultiAsset as V0; + use AssetId::*; + use Fungibility::*; + let (id, fun) = match old { + V0::ConcreteFungible { id, amount } => (Concrete(id.into()), Fungible(amount)), + V0::ConcreteNonFungible { class, instance } => + (Concrete(class.into()), NonFungible(instance)), + V0::AbstractFungible { id, amount } => (Abstract(id), Fungible(amount)), + V0::AbstractNonFungible { class, instance } => (Abstract(class), NonFungible(instance)), + _ => return Err(()), + }; + Ok(MultiAsset { id, fun }) + } +} + +impl TryFrom for Option { + type Error = (); + fn try_from(old: super::super::v0::MultiAsset) -> result::Result, ()> { + match old { + super::super::v0::MultiAsset::None => return Ok(None), + x => return Ok(Some(x.try_into()?)), + } + } +} + +impl TryFrom> for MultiAsset { + type Error = (); + fn try_from(mut old: Vec) -> result::Result { + if old.len() == 1 { + old.remove(0).try_into() + } else { + Err(()) + } + } +} + +/// A `Vec` of `MultiAsset`s. There may be no duplicate fungible items in here and when decoding, they must be sorted. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode)] +pub struct MultiAssets(Vec); + +impl Decode for MultiAssets { + fn decode(input: &mut I) -> Result { + Self::from_sorted_and_deduplicated(Vec::::decode(input)?) + .map_err(|()| "Out of order".into()) + } +} + +impl TryFrom> for MultiAssets { + type Error = (); + fn try_from(old: Vec) -> result::Result { + let v = old + .into_iter() + .map(Option::::try_from) + .filter_map(|x| x.transpose()) + .collect::, ()>>()?; + Ok(v.into()) + } +} + +impl From> for MultiAssets { + fn from(mut assets: Vec) -> Self { + let mut res = Vec::with_capacity(assets.len()); + if !assets.is_empty() { + assets.sort(); + let mut iter = assets.into_iter(); + if let Some(first) = iter.next() { + let last = iter.fold(first, |a, b| -> MultiAsset { + match (a, b) { + ( + MultiAsset { fun: Fungibility::Fungible(a_amount), id: a_id }, + MultiAsset { fun: Fungibility::Fungible(b_amount), id: b_id }, + ) if a_id == b_id => + MultiAsset { id: a_id, fun: Fungibility::Fungible(a_amount + b_amount) }, + ( + MultiAsset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + MultiAsset { fun: Fungibility::NonFungible(b_instance), id: b_id }, + ) if a_id == b_id && a_instance == b_instance => + MultiAsset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + (to_push, to_remember) => { + res.push(to_push); + to_remember + }, + } + }); + res.push(last); + } + } + Self(res) + } +} + +impl> From for MultiAssets { + fn from(x: T) -> Self { + Self(vec![x.into()]) + } +} + +impl MultiAssets { + /// A new (empty) value. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// Returns `Ok` if the operation succeeds and `Err` if `r` is out of order or had duplicates. If you can't + /// guarantee that `r` is sorted and deduplicated, then use `From::>::from` which is infallible. + pub fn from_sorted_and_deduplicated(r: Vec) -> Result { + if r.is_empty() { + return Ok(Self(Vec::new())) + } + r.iter().skip(1).try_fold(&r[0], |a, b| -> Result<&MultiAsset, ()> { + if a.id < b.id || a < b && (a.is_non_fungible(None) || b.is_non_fungible(None)) { + Ok(b) + } else { + Err(()) + } + })?; + Ok(Self(r)) + } + + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a negligible-cost operation. + /// Generally though you should avoid using it unless you have a strict proof that `r` is valid. + #[cfg(test)] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self::from_sorted_and_deduplicated(r).expect("Invalid input r is not sorted/deduped") + } + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a negligible-cost operation. + /// Generally though you should avoid using it unless you have a strict proof that `r` is valid. + /// + /// In test mode, this checks anyway and panics on fail. + #[cfg(not(test))] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self(r) + } + + /// Add some asset onto the list, saturating. This is quite a laborious operation since it maintains the ordering. + pub fn push(&mut self, a: MultiAsset) { + if let Fungibility::Fungible(ref amount) = a.fun { + for asset in self.0.iter_mut().filter(|x| x.id == a.id) { + if let Fungibility::Fungible(ref mut balance) = asset.fun { + *balance = balance.saturating_add(*amount); + return + } + } + } + self.0.push(a); + self.0.sort(); + } + + /// Returns `true` if this definitely represents no asset. + pub fn is_none(&self) -> bool { + self.0.is_empty() + } + + /// Returns true if `self` is a super-set of the given `inner`. + pub fn contains(&self, inner: &MultiAsset) -> bool { + self.0.iter().any(|i| i.contains(inner)) + } + + /// Consume `self` and return the inner vec. + pub fn drain(self) -> Vec { + self.0 + } + + /// Return a reference to the inner vec. + pub fn inner(&self) -> &Vec { + &self.0 + } + + /// Return the number of distinct asset instances contained. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location. + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.reanchor(prepend)) + } + + /// Return a reference to an item at a specific index or `None` if it doesn't exist. + pub fn get(&self, index: usize) -> Option<&MultiAsset> { + self.0.get(index) + } +} +/// Classification of whether an asset is fungible or not. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode)] +pub enum WildFungibility { + Fungible, + NonFungible, +} + +/// A wildcard representing a set of assets. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode)] +pub enum WildMultiAsset { + /// All assets in the holding register, up to `usize` individual assets (different instances of non-fungibles could + /// be separate assets). + All, + /// All assets in the holding register of a given fungibility and ID. If operating on non-fungibles, then a limit + /// is provided for the maximum amount of matching instances. + AllOf { id: AssetId, fun: WildFungibility }, +} + +impl TryFrom for WildMultiAsset { + type Error = (); + fn try_from(old: super::super::v0::MultiAsset) -> result::Result { + use super::super::v0::MultiAsset as V0; + use AssetId::*; + use WildFungibility::*; + let (id, fun) = match old { + V0::All => return Ok(WildMultiAsset::All), + V0::AllConcreteFungible { id } => (Concrete(id.into()), Fungible), + V0::AllConcreteNonFungible { class } => (Concrete(class.into()), NonFungible), + V0::AllAbstractFungible { id } => (Abstract(id), Fungible), + V0::AllAbstractNonFungible { class } => (Abstract(class), NonFungible), + _ => return Err(()), + }; + Ok(WildMultiAsset::AllOf { id, fun }) + } +} + +impl TryFrom> for WildMultiAsset { + type Error = (); + fn try_from(mut old: Vec) -> result::Result { + if old.len() == 1 { + old.remove(0).try_into() + } else { + Err(()) + } + } +} + +impl WildMultiAsset { + /// Returns true if `self` is a super-set of the given `inner`. + /// + /// Typically, any wildcard is never contained in anything else, and a wildcard can contain any other non-wildcard. + /// For more details, see the implementation and tests. + pub fn contains(&self, inner: &MultiAsset) -> bool { + use WildMultiAsset::*; + match self { + AllOf { fun, id } => inner.fun.is_kind(*fun) && &inner.id == id, + All => true, + } + } + + /// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location. + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + use WildMultiAsset::*; + match self { + AllOf { ref mut id, .. } => id.reanchor(prepend).map_err(|_| ()), + All => Ok(()), + } + } +} + +impl, B: Into> From<(A, B)> for WildMultiAsset { + fn from((id, fun): (A, B)) -> WildMultiAsset { + WildMultiAsset::AllOf { fun: fun.into(), id: id.into() } + } +} + +/// `MultiAsset` collection, either `MultiAssets` or a single wildcard. +/// +/// Note: Vectors of wildcards whose encoding is supported in XCM v0 are unsupported +/// in this implementation and will result in a decode error. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode)] +pub enum MultiAssetFilter { + Definite(MultiAssets), + Wild(WildMultiAsset), +} + +impl> From for MultiAssetFilter { + fn from(x: T) -> Self { + Self::Wild(x.into()) + } +} + +impl From for MultiAssetFilter { + fn from(x: MultiAsset) -> Self { + Self::Definite(vec![x].into()) + } +} + +impl From> for MultiAssetFilter { + fn from(x: Vec) -> Self { + Self::Definite(x.into()) + } +} + +impl From for MultiAssetFilter { + fn from(x: MultiAssets) -> Self { + Self::Definite(x) + } +} + +impl MultiAssetFilter { + /// Returns true if `self` is a super-set of the given `inner`. + /// + /// Typically, any wildcard is never contained in anything else, and a wildcard can contain any other non-wildcard. + /// For more details, see the implementation and tests. + pub fn contains(&self, inner: &MultiAsset) -> bool { + match self { + MultiAssetFilter::Definite(ref assets) => assets.contains(inner), + MultiAssetFilter::Wild(ref wild) => wild.contains(inner), + } + } + + /// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location. + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + match self { + MultiAssetFilter::Definite(ref mut assets) => assets.reanchor(prepend), + MultiAssetFilter::Wild(ref mut wild) => wild.reanchor(prepend), + } + } +} + +impl TryFrom> for MultiAssetFilter { + type Error = (); + fn try_from( + mut old: Vec, + ) -> result::Result { + if old.len() == 1 && old[0].is_wildcard() { + Ok(MultiAssetFilter::Wild(old.remove(0).try_into()?)) + } else { + Ok(MultiAssetFilter::Definite(old.try_into()?)) + } + } +} diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v1/multilocation.rs new file mode 100644 index 000000000000..57cdda4209d4 --- /dev/null +++ b/xcm/src/v1/multilocation.rs @@ -0,0 +1,821 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Cross-Consensus Message format data structures. + +use super::{super::v0::MultiLocation as MultiLocation0, Junction}; +use core::{mem, result}; +use parity_scale_codec::{self, Decode, Encode}; + +/// A relative path between state-bearing consensus systems. +/// +/// A location in a consensus system is defined as an *isolatable state machine* held within global consensus. The +/// location in question need not have a sophisticated consensus algorithm of its own; a single account within +/// Ethereum, for example, could be considered a location. +/// +/// A very-much non-exhaustive list of types of location include: +/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. +/// - A layer-0 super-chain, e.g. the Polkadot Relay chain. +/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. +/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based Substrate chain. +/// - An account. +/// +/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the relative path +/// between two locations, and cannot generally be used to refer to a location universally. It is comprised of a +/// number of *junctions*, each morphing the previous location, either diving down into one of its internal locations, +/// called a *sub-consensus*, or going up into its parent location. Correct `MultiLocation` values must have all +/// `Parent` junctions as a prefix to all *sub-consensus* junctions. +/// +/// This specific `MultiLocation` implementation uses a Rust `enum` in order to make pattern matching easier. +/// +/// The `MultiLocation` value of `Here` simply refers to the interpreting consensus system. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum MultiLocation { + /// The interpreting consensus system. + Here, + /// A relative path comprising 1 junction. + X1(Junction), + /// A relative path comprising 2 junctions. + X2(Junction, Junction), + /// A relative path comprising 3 junctions. + X3(Junction, Junction, Junction), + /// A relative path comprising 4 junctions. + X4(Junction, Junction, Junction, Junction), + /// A relative path comprising 5 junctions. + X5(Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 6 junctions. + X6(Junction, Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 7 junctions. + X7(Junction, Junction, Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 8 junctions. + X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), +} + +/// Maximum number of junctions a `MultiLocation` can contain. +pub const MAX_MULTILOCATION_LENGTH: usize = 8; + +impl From for MultiLocation { + fn from(x: Junction) -> Self { + MultiLocation::X1(x) + } +} + +impl From<()> for MultiLocation { + fn from(_: ()) -> Self { + MultiLocation::Here + } +} +impl From<(Junction,)> for MultiLocation { + fn from(x: (Junction,)) -> Self { + MultiLocation::X1(x.0) + } +} +impl From<(Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction)) -> Self { + MultiLocation::X2(x.0, x.1) + } +} +impl From<(Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction)) -> Self { + MultiLocation::X3(x.0, x.1, x.2) + } +} +impl From<(Junction, Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction, Junction)) -> Self { + MultiLocation::X4(x.0, x.1, x.2, x.3) + } +} +impl From<(Junction, Junction, Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction, Junction, Junction)) -> Self { + MultiLocation::X5(x.0, x.1, x.2, x.3, x.4) + } +} +impl From<(Junction, Junction, Junction, Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction, Junction, Junction, Junction)) -> Self { + MultiLocation::X6(x.0, x.1, x.2, x.3, x.4, x.5) + } +} +impl From<(Junction, Junction, Junction, Junction, Junction, Junction, Junction)> + for MultiLocation +{ + fn from(x: (Junction, Junction, Junction, Junction, Junction, Junction, Junction)) -> Self { + MultiLocation::X7(x.0, x.1, x.2, x.3, x.4, x.5, x.6) + } +} +impl From<(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction)> + for MultiLocation +{ + fn from( + x: (Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), + ) -> Self { + MultiLocation::X8(x.0, x.1, x.2, x.3, x.4, x.5, x.6, x.7) + } +} + +impl From<[Junction; 0]> for MultiLocation { + fn from(_: [Junction; 0]) -> Self { + MultiLocation::Here + } +} +impl From<[Junction; 1]> for MultiLocation { + fn from(x: [Junction; 1]) -> Self { + let [x0] = x; + MultiLocation::X1(x0) + } +} +impl From<[Junction; 2]> for MultiLocation { + fn from(x: [Junction; 2]) -> Self { + let [x0, x1] = x; + MultiLocation::X2(x0, x1) + } +} +impl From<[Junction; 3]> for MultiLocation { + fn from(x: [Junction; 3]) -> Self { + let [x0, x1, x2] = x; + MultiLocation::X3(x0, x1, x2) + } +} +impl From<[Junction; 4]> for MultiLocation { + fn from(x: [Junction; 4]) -> Self { + let [x0, x1, x2, x3] = x; + MultiLocation::X4(x0, x1, x2, x3) + } +} +impl From<[Junction; 5]> for MultiLocation { + fn from(x: [Junction; 5]) -> Self { + let [x0, x1, x2, x3, x4] = x; + MultiLocation::X5(x0, x1, x2, x3, x4) + } +} +impl From<[Junction; 6]> for MultiLocation { + fn from(x: [Junction; 6]) -> Self { + let [x0, x1, x2, x3, x4, x5] = x; + MultiLocation::X6(x0, x1, x2, x3, x4, x5) + } +} +impl From<[Junction; 7]> for MultiLocation { + fn from(x: [Junction; 7]) -> Self { + let [x0, x1, x2, x3, x4, x5, x6] = x; + MultiLocation::X7(x0, x1, x2, x3, x4, x5, x6) + } +} +impl From<[Junction; 8]> for MultiLocation { + fn from(x: [Junction; 8]) -> Self { + let [x0, x1, x2, x3, x4, x5, x6, x7] = x; + MultiLocation::X8(x0, x1, x2, x3, x4, x5, x6, x7) + } +} + +pub struct MultiLocationIterator(MultiLocation); +impl Iterator for MultiLocationIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.0.take_first() + } +} + +pub struct MultiLocationReverseIterator(MultiLocation); +impl Iterator for MultiLocationReverseIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.0.take_last() + } +} + +pub struct MultiLocationRefIterator<'a>(&'a MultiLocation, usize); +impl<'a> Iterator for MultiLocationRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + let result = self.0.at(self.1); + self.1 += 1; + result + } +} + +pub struct MultiLocationReverseRefIterator<'a>(&'a MultiLocation, usize); +impl<'a> Iterator for MultiLocationReverseRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + self.1 += 1; + self.0.at(self.0.len().checked_sub(self.1)?) + } +} + +impl MultiLocation { + /// Returns first junction, or `None` if the location is empty. + pub fn first(&self) -> Option<&Junction> { + match &self { + MultiLocation::Here => None, + MultiLocation::X1(ref a) => Some(a), + MultiLocation::X2(ref a, ..) => Some(a), + MultiLocation::X3(ref a, ..) => Some(a), + MultiLocation::X4(ref a, ..) => Some(a), + MultiLocation::X5(ref a, ..) => Some(a), + MultiLocation::X6(ref a, ..) => Some(a), + MultiLocation::X7(ref a, ..) => Some(a), + MultiLocation::X8(ref a, ..) => Some(a), + } + } + + /// Returns last junction, or `None` if the location is empty. + pub fn last(&self) -> Option<&Junction> { + match &self { + MultiLocation::Here => None, + MultiLocation::X1(ref a) => Some(a), + MultiLocation::X2(.., ref a) => Some(a), + MultiLocation::X3(.., ref a) => Some(a), + MultiLocation::X4(.., ref a) => Some(a), + MultiLocation::X5(.., ref a) => Some(a), + MultiLocation::X6(.., ref a) => Some(a), + MultiLocation::X7(.., ref a) => Some(a), + MultiLocation::X8(.., ref a) => Some(a), + } + } + + /// Splits off the first junction, returning the remaining suffix (first item in tuple) and the first element + /// (second item in tuple) or `None` if it was empty. + pub fn split_first(self) -> (MultiLocation, Option) { + match self { + MultiLocation::Here => (MultiLocation::Here, None), + MultiLocation::X1(a) => (MultiLocation::Here, Some(a)), + MultiLocation::X2(a, b) => (MultiLocation::X1(b), Some(a)), + MultiLocation::X3(a, b, c) => (MultiLocation::X2(b, c), Some(a)), + MultiLocation::X4(a, b, c, d) => (MultiLocation::X3(b, c, d), Some(a)), + MultiLocation::X5(a, b, c, d, e) => (MultiLocation::X4(b, c, d, e), Some(a)), + MultiLocation::X6(a, b, c, d, e, f) => (MultiLocation::X5(b, c, d, e, f), Some(a)), + MultiLocation::X7(a, b, c, d, e, f, g) => + (MultiLocation::X6(b, c, d, e, f, g), Some(a)), + MultiLocation::X8(a, b, c, d, e, f, g, h) => + (MultiLocation::X7(b, c, d, e, f, g, h), Some(a)), + } + } + + /// Splits off the last junction, returning the remaining prefix (first item in tuple) and the last element + /// (second item in tuple) or `None` if it was empty. + pub fn split_last(self) -> (MultiLocation, Option) { + match self { + MultiLocation::Here => (MultiLocation::Here, None), + MultiLocation::X1(a) => (MultiLocation::Here, Some(a)), + MultiLocation::X2(a, b) => (MultiLocation::X1(a), Some(b)), + MultiLocation::X3(a, b, c) => (MultiLocation::X2(a, b), Some(c)), + MultiLocation::X4(a, b, c, d) => (MultiLocation::X3(a, b, c), Some(d)), + MultiLocation::X5(a, b, c, d, e) => (MultiLocation::X4(a, b, c, d), Some(e)), + MultiLocation::X6(a, b, c, d, e, f) => (MultiLocation::X5(a, b, c, d, e), Some(f)), + MultiLocation::X7(a, b, c, d, e, f, g) => + (MultiLocation::X6(a, b, c, d, e, f), Some(g)), + MultiLocation::X8(a, b, c, d, e, f, g, h) => + (MultiLocation::X7(a, b, c, d, e, f, g), Some(h)), + } + } + + /// Removes the first element from `self`, returning it (or `None` if it was empty). + pub fn take_first(&mut self) -> Option { + let mut d = MultiLocation::Here; + mem::swap(&mut *self, &mut d); + let (tail, head) = d.split_first(); + *self = tail; + head + } + + /// Removes the last element from `self`, returning it (or `None` if it was empty). + pub fn take_last(&mut self) -> Option { + let mut d = MultiLocation::Here; + mem::swap(&mut *self, &mut d); + let (head, tail) = d.split_last(); + *self = head; + tail + } + + /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with the original value of + /// `self` in case of overflow. + pub fn pushed_with(self, new: Junction) -> result::Result { + Ok(match self { + MultiLocation::Here => MultiLocation::X1(new), + MultiLocation::X1(a) => MultiLocation::X2(a, new), + MultiLocation::X2(a, b) => MultiLocation::X3(a, b, new), + MultiLocation::X3(a, b, c) => MultiLocation::X4(a, b, c, new), + MultiLocation::X4(a, b, c, d) => MultiLocation::X5(a, b, c, d, new), + MultiLocation::X5(a, b, c, d, e) => MultiLocation::X6(a, b, c, d, e, new), + MultiLocation::X6(a, b, c, d, e, f) => MultiLocation::X7(a, b, c, d, e, f, new), + MultiLocation::X7(a, b, c, d, e, f, g) => MultiLocation::X8(a, b, c, d, e, f, g, new), + s => Err(s)?, + }) + } + + /// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the original value of + /// `self` in case of overflow. + pub fn pushed_front_with(self, new: Junction) -> result::Result { + Ok(match self { + MultiLocation::Here => MultiLocation::X1(new), + MultiLocation::X1(a) => MultiLocation::X2(new, a), + MultiLocation::X2(a, b) => MultiLocation::X3(new, a, b), + MultiLocation::X3(a, b, c) => MultiLocation::X4(new, a, b, c), + MultiLocation::X4(a, b, c, d) => MultiLocation::X5(new, a, b, c, d), + MultiLocation::X5(a, b, c, d, e) => MultiLocation::X6(new, a, b, c, d, e), + MultiLocation::X6(a, b, c, d, e, f) => MultiLocation::X7(new, a, b, c, d, e, f), + MultiLocation::X7(a, b, c, d, e, f, g) => MultiLocation::X8(new, a, b, c, d, e, f, g), + s => Err(s)?, + }) + } + + /// Returns the number of junctions in `self`. + pub fn len(&self) -> usize { + match &self { + MultiLocation::Here => 0, + MultiLocation::X1(..) => 1, + MultiLocation::X2(..) => 2, + MultiLocation::X3(..) => 3, + MultiLocation::X4(..) => 4, + MultiLocation::X5(..) => 5, + MultiLocation::X6(..) => 6, + MultiLocation::X7(..) => 7, + MultiLocation::X8(..) => 8, + } + } + + /// Returns the junction at index `i`, or `None` if the location doesn't contain that many elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + Some(match (i, &self) { + (0, MultiLocation::X1(ref a)) => a, + (0, MultiLocation::X2(ref a, ..)) => a, + (0, MultiLocation::X3(ref a, ..)) => a, + (0, MultiLocation::X4(ref a, ..)) => a, + (0, MultiLocation::X5(ref a, ..)) => a, + (0, MultiLocation::X6(ref a, ..)) => a, + (0, MultiLocation::X7(ref a, ..)) => a, + (0, MultiLocation::X8(ref a, ..)) => a, + (1, MultiLocation::X2(_, ref a)) => a, + (1, MultiLocation::X3(_, ref a, ..)) => a, + (1, MultiLocation::X4(_, ref a, ..)) => a, + (1, MultiLocation::X5(_, ref a, ..)) => a, + (1, MultiLocation::X6(_, ref a, ..)) => a, + (1, MultiLocation::X7(_, ref a, ..)) => a, + (1, MultiLocation::X8(_, ref a, ..)) => a, + (2, MultiLocation::X3(_, _, ref a)) => a, + (2, MultiLocation::X4(_, _, ref a, ..)) => a, + (2, MultiLocation::X5(_, _, ref a, ..)) => a, + (2, MultiLocation::X6(_, _, ref a, ..)) => a, + (2, MultiLocation::X7(_, _, ref a, ..)) => a, + (2, MultiLocation::X8(_, _, ref a, ..)) => a, + (3, MultiLocation::X4(_, _, _, ref a)) => a, + (3, MultiLocation::X5(_, _, _, ref a, ..)) => a, + (3, MultiLocation::X6(_, _, _, ref a, ..)) => a, + (3, MultiLocation::X7(_, _, _, ref a, ..)) => a, + (3, MultiLocation::X8(_, _, _, ref a, ..)) => a, + (4, MultiLocation::X5(_, _, _, _, ref a)) => a, + (4, MultiLocation::X6(_, _, _, _, ref a, ..)) => a, + (4, MultiLocation::X7(_, _, _, _, ref a, ..)) => a, + (4, MultiLocation::X8(_, _, _, _, ref a, ..)) => a, + (5, MultiLocation::X6(_, _, _, _, _, ref a)) => a, + (5, MultiLocation::X7(_, _, _, _, _, ref a, ..)) => a, + (5, MultiLocation::X8(_, _, _, _, _, ref a, ..)) => a, + (6, MultiLocation::X7(_, _, _, _, _, _, ref a)) => a, + (6, MultiLocation::X8(_, _, _, _, _, _, ref a, ..)) => a, + (7, MultiLocation::X8(_, _, _, _, _, _, _, ref a)) => a, + _ => return None, + }) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't contain that many + /// elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + Some(match (i, self) { + (0, MultiLocation::X1(ref mut a)) => a, + (0, MultiLocation::X2(ref mut a, ..)) => a, + (0, MultiLocation::X3(ref mut a, ..)) => a, + (0, MultiLocation::X4(ref mut a, ..)) => a, + (0, MultiLocation::X5(ref mut a, ..)) => a, + (0, MultiLocation::X6(ref mut a, ..)) => a, + (0, MultiLocation::X7(ref mut a, ..)) => a, + (0, MultiLocation::X8(ref mut a, ..)) => a, + (1, MultiLocation::X2(_, ref mut a)) => a, + (1, MultiLocation::X3(_, ref mut a, ..)) => a, + (1, MultiLocation::X4(_, ref mut a, ..)) => a, + (1, MultiLocation::X5(_, ref mut a, ..)) => a, + (1, MultiLocation::X6(_, ref mut a, ..)) => a, + (1, MultiLocation::X7(_, ref mut a, ..)) => a, + (1, MultiLocation::X8(_, ref mut a, ..)) => a, + (2, MultiLocation::X3(_, _, ref mut a)) => a, + (2, MultiLocation::X4(_, _, ref mut a, ..)) => a, + (2, MultiLocation::X5(_, _, ref mut a, ..)) => a, + (2, MultiLocation::X6(_, _, ref mut a, ..)) => a, + (2, MultiLocation::X7(_, _, ref mut a, ..)) => a, + (2, MultiLocation::X8(_, _, ref mut a, ..)) => a, + (3, MultiLocation::X4(_, _, _, ref mut a)) => a, + (3, MultiLocation::X5(_, _, _, ref mut a, ..)) => a, + (3, MultiLocation::X6(_, _, _, ref mut a, ..)) => a, + (3, MultiLocation::X7(_, _, _, ref mut a, ..)) => a, + (3, MultiLocation::X8(_, _, _, ref mut a, ..)) => a, + (4, MultiLocation::X5(_, _, _, _, ref mut a)) => a, + (4, MultiLocation::X6(_, _, _, _, ref mut a, ..)) => a, + (4, MultiLocation::X7(_, _, _, _, ref mut a, ..)) => a, + (4, MultiLocation::X8(_, _, _, _, ref mut a, ..)) => a, + (5, MultiLocation::X6(_, _, _, _, _, ref mut a)) => a, + (5, MultiLocation::X7(_, _, _, _, _, ref mut a, ..)) => a, + (5, MultiLocation::X8(_, _, _, _, _, ref mut a, ..)) => a, + (6, MultiLocation::X7(_, _, _, _, _, _, ref mut a)) => a, + (6, MultiLocation::X8(_, _, _, _, _, _, ref mut a, ..)) => a, + (7, MultiLocation::X8(_, _, _, _, _, _, _, ref mut a)) => a, + _ => return None, + }) + } + + /// Returns a reference iterator over the junctions. + pub fn iter(&self) -> MultiLocationRefIterator { + MultiLocationRefIterator(&self, 0) + } + + /// Returns a reference iterator over the junctions in reverse. + pub fn iter_rev(&self) -> MultiLocationReverseRefIterator { + MultiLocationReverseRefIterator(&self, 0) + } + + /// Consumes `self` and returns an iterator over the junctions. + pub fn into_iter(self) -> MultiLocationIterator { + MultiLocationIterator(self) + } + + /// Consumes `self` and returns an iterator over the junctions in reverse. + pub fn into_iter_rev(self) -> MultiLocationReverseIterator { + MultiLocationReverseIterator(self) + } + + /// Ensures that self begins with `prefix` and that it has a single `Junction` item following. + /// If so, returns a reference to this `Junction` item. + /// + /// # Example + /// ```rust + /// # use xcm::latest::{MultiLocation::*, Junction::*}; + /// # fn main() { + /// let mut m = X3(Parent, PalletInstance(3), OnlyChild); + /// assert_eq!(m.match_and_split(&X2(Parent, PalletInstance(3))), Some(&OnlyChild)); + /// assert_eq!(m.match_and_split(&X1(Parent)), None); + /// # } + /// ``` + pub fn match_and_split(&self, prefix: &MultiLocation) -> Option<&Junction> { + if prefix.len() + 1 != self.len() { + return None + } + for i in 0..prefix.len() { + if prefix.at(i) != self.at(i) { + return None + } + } + return self.at(prefix.len()) + } + + /// Mutates `self`, suffixing it with `new`. Returns `Err` in case of overflow. + pub fn push(&mut self, new: Junction) -> result::Result<(), ()> { + let mut n = MultiLocation::Here; + mem::swap(&mut *self, &mut n); + match n.pushed_with(new) { + Ok(result) => { + *self = result; + Ok(()) + }, + Err(old) => { + *self = old; + Err(()) + }, + } + } + + /// Mutates `self`, prefixing it with `new`. Returns `Err` in case of overflow. + pub fn push_front(&mut self, new: Junction) -> result::Result<(), ()> { + let mut n = MultiLocation::Here; + mem::swap(&mut *self, &mut n); + match n.pushed_front_with(new) { + Ok(result) => { + *self = result; + Ok(()) + }, + Err(old) => { + *self = old; + Err(()) + }, + } + } + + /// Returns the number of `Parent` junctions at the beginning of `self`. + pub fn leading_parent_count(&self) -> usize { + use Junction::Parent; + match self { + MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent) => 8, + + MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, ..) => 7, + MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent) => 7, + + MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, ..) => 6, + MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, Parent, ..) => 6, + MultiLocation::X6(Parent, Parent, Parent, Parent, Parent, Parent) => 6, + + MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, ..) => 5, + MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, ..) => 5, + MultiLocation::X6(Parent, Parent, Parent, Parent, Parent, ..) => 5, + MultiLocation::X5(Parent, Parent, Parent, Parent, Parent) => 5, + + MultiLocation::X8(Parent, Parent, Parent, Parent, ..) => 4, + MultiLocation::X7(Parent, Parent, Parent, Parent, ..) => 4, + MultiLocation::X6(Parent, Parent, Parent, Parent, ..) => 4, + MultiLocation::X5(Parent, Parent, Parent, Parent, ..) => 4, + MultiLocation::X4(Parent, Parent, Parent, Parent) => 4, + + MultiLocation::X8(Parent, Parent, Parent, ..) => 3, + MultiLocation::X7(Parent, Parent, Parent, ..) => 3, + MultiLocation::X6(Parent, Parent, Parent, ..) => 3, + MultiLocation::X5(Parent, Parent, Parent, ..) => 3, + MultiLocation::X4(Parent, Parent, Parent, ..) => 3, + MultiLocation::X3(Parent, Parent, Parent) => 3, + + MultiLocation::X8(Parent, Parent, ..) => 2, + MultiLocation::X7(Parent, Parent, ..) => 2, + MultiLocation::X6(Parent, Parent, ..) => 2, + MultiLocation::X5(Parent, Parent, ..) => 2, + MultiLocation::X4(Parent, Parent, ..) => 2, + MultiLocation::X3(Parent, Parent, ..) => 2, + MultiLocation::X2(Parent, Parent) => 2, + + MultiLocation::X8(Parent, ..) => 1, + MultiLocation::X7(Parent, ..) => 1, + MultiLocation::X6(Parent, ..) => 1, + MultiLocation::X5(Parent, ..) => 1, + MultiLocation::X4(Parent, ..) => 1, + MultiLocation::X3(Parent, ..) => 1, + MultiLocation::X2(Parent, ..) => 1, + MultiLocation::X1(Parent) => 1, + _ => 0, + } + } + + /// This function ensures a multi-junction is in its canonicalized/normalized form, removing + /// any internal `[Non-Parent, Parent]` combinations. + pub fn canonicalize(&mut self) { + let mut normalized = MultiLocation::Here; + let mut iter = self.iter(); + // We build up the the new normalized path by taking items from the original multi-location. + // When the next item we would add is `Parent`, we instead remove the last item assuming + // it is non-parent. + const EXPECT_MESSAGE: &'static str = + "`self` is a well formed multi-location with N junctions; \ + this loop iterates over the junctions of `self`; \ + the loop can push to the new multi-location at most one time; \ + thus the size of the new multi-location is at most N junctions; \ + qed"; + while let Some(j) = iter.next() { + if j == &Junction::Parent { + match normalized.last() { + None | Some(Junction::Parent) => {}, + Some(_) => { + normalized.take_last(); + continue + }, + } + } + + normalized.push(j.clone()).expect(EXPECT_MESSAGE); + } + + core::mem::swap(self, &mut normalized); + } + + /// Mutate `self` so that it is suffixed with `suffix`. The correct normalized form is returned, + /// removing any internal `[Non-Parent, Parent]` combinations. + /// + /// In the case of overflow, `self` is unmodified and we return `Err` with `suffix`. + /// + /// # Example + /// ```rust + /// # use xcm::latest::{MultiLocation::*, Junction::*}; + /// # fn main() { + /// let mut m = X3(Parent, Parachain(21), OnlyChild); + /// assert_eq!(m.append_with(X2(Parent, PalletInstance(3))), Ok(())); + /// assert_eq!(m, X3(Parent, Parachain(21), PalletInstance(3))); + /// # } + /// ``` + pub fn append_with(&mut self, suffix: MultiLocation) -> Result<(), MultiLocation> { + let mut prefix = suffix; + core::mem::swap(self, &mut prefix); + match self.prepend_with(prefix) { + Ok(()) => Ok(()), + Err(prefix) => { + let mut suffix = prefix; + core::mem::swap(self, &mut suffix); + Err(suffix) + }, + } + } + + /// Mutate `self` so that it is prefixed with `prefix`. The correct normalized form is returned, + /// removing any internal [Non-Parent, `Parent`] combinations. + /// + /// In the case of overflow, `self` is unmodified and we return `Err` with `prefix`. + /// + /// # Example + /// ```rust + /// # use xcm::latest::{MultiLocation::*, Junction::*, NetworkId::Any}; + /// # fn main() { + /// let mut m = X3(Parent, Parent, PalletInstance(3)); + /// assert_eq!(m.prepend_with(X3(Parent, Parachain(21), OnlyChild)), Ok(())); + /// assert_eq!(m, X2(Parent, PalletInstance(3))); + /// # } + /// ``` + pub fn prepend_with(&mut self, prefix: MultiLocation) -> Result<(), MultiLocation> { + let mut prefix = prefix; + + // This will guarantee that all `Parent` junctions in the prefix are leading, which is + // important for calculating the `skipped` items below. + prefix.canonicalize(); + + let self_leading_parents = self.leading_parent_count(); + // These are the number of `non-parent` items in the prefix that we can + // potentially remove if the original location leads with parents. + let prefix_rest = prefix.len() - prefix.leading_parent_count(); + // 2 * skipped items will be removed when performing the normalization below. + let skipped = self_leading_parents.min(prefix_rest); + + // Pre-pending this prefix would create a multi-location with too many junctions. + if self.len() + prefix.len() - 2 * skipped > MAX_MULTILOCATION_LENGTH { + return Err(prefix) + } + + // Here we cancel out `[Non-Parent, Parent]` items (normalization), where + // the non-parent item comes from the end of the prefix, and the parent item + // comes from the front of the original location. + // + // We calculated already how many of these there should be above. + for _ in 0..skipped { + let _non_parent = prefix.take_last(); + let _parent = self.take_first(); + debug_assert!( + _non_parent.is_some() && _non_parent != Some(Junction::Parent), + "prepend_with should always remove a non-parent from the end of the prefix", + ); + debug_assert!( + _parent == Some(Junction::Parent), + "prepend_with should always remove a parent from the front of the location", + ); + } + + for j in prefix.into_iter_rev() { + self.push_front(j) + .expect("len + prefix minus 2*skipped is less than max length; qed"); + } + Ok(()) + } + + /// Returns true iff `self` is an interior location. For this it may not contain any `Junction`s + /// for which `Junction::is_interior` returns `false`. This is generally true, except for the + /// `Parent` item. + /// + /// # Example + /// ```rust + /// # use xcm::latest::{MultiLocation::*, Junction::*, NetworkId::Any}; + /// # fn main() { + /// let parent = X1(Parent); + /// assert_eq!(parent.is_interior(), false); + /// let m = X2(PalletInstance(12), AccountIndex64 { network: Any, index: 23 }); + /// assert_eq!(m.is_interior(), true); + /// # } + /// ``` + pub fn is_interior(&self) -> bool { + self.iter().all(Junction::is_interior) + } +} + +impl From for MultiLocation { + fn from(old: MultiLocation0) -> Self { + use MultiLocation::*; + match old { + MultiLocation0::Null => Here, + MultiLocation0::X1(j0) => X1(j0), + MultiLocation0::X2(j0, j1) => X2(j0, j1), + MultiLocation0::X3(j0, j1, j2) => X3(j0, j1, j2), + MultiLocation0::X4(j0, j1, j2, j3) => X4(j0, j1, j2, j3), + MultiLocation0::X5(j0, j1, j2, j3, j4) => X5(j0, j1, j2, j3, j4), + MultiLocation0::X6(j0, j1, j2, j3, j4, j5) => X6(j0, j1, j2, j3, j4, j5), + MultiLocation0::X7(j0, j1, j2, j3, j4, j5, j6) => X7(j0, j1, j2, j3, j4, j5, j6), + MultiLocation0::X8(j0, j1, j2, j3, j4, j5, j6, j7) => + X8(j0, j1, j2, j3, j4, j5, j6, j7), + } + } +} + +#[cfg(test)] +mod tests { + use super::MultiLocation::*; + use crate::opaque::v1::{Junction::*, NetworkId::Any}; + + #[test] + fn match_and_split_works() { + let m = X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 }); + assert_eq!(m.match_and_split(&X1(Parent)), None); + assert_eq!( + m.match_and_split(&X2(Parent, Parachain(42))), + Some(&AccountIndex64 { network: Any, index: 23 }) + ); + assert_eq!(m.match_and_split(&m), None); + } + + #[test] + fn append_with_works() { + let acc = AccountIndex64 { network: Any, index: 23 }; + let mut m = X2(Parent, Parachain(42)); + assert_eq!(m.append_with(X2(PalletInstance(3), acc.clone())), Ok(())); + assert_eq!(m, X4(Parent, Parachain(42), PalletInstance(3), acc.clone())); + + // cannot append to create overly long multilocation + let acc = AccountIndex64 { network: Any, index: 23 }; + let mut m = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parachain(42)); + let suffix = X2(PalletInstance(3), acc.clone()); + assert_eq!(m.append_with(suffix.clone()), Err(suffix)); + } + + #[test] + fn prepend_with_works() { + let mut m = X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 }); + assert_eq!(m.prepend_with(X2(Parent, OnlyChild)), Ok(())); + assert_eq!(m, X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 })); + + // cannot prepend to create overly long multilocation + let mut m = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parachain(42)); + let prefix = X2(Parent, Parent); + assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); + + // Can handle shared prefix and resizing correctly. + let mut m = X1(Parent); + let prefix = X8( + Parachain(100), + OnlyChild, + OnlyChild, + OnlyChild, + OnlyChild, + OnlyChild, + OnlyChild, + Parent, + ); + assert_eq!(m.prepend_with(prefix.clone()), Ok(())); + assert_eq!(m, X5(Parachain(100), OnlyChild, OnlyChild, OnlyChild, OnlyChild)); + + let mut m = X1(Parent); + let prefix = X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent); + assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); + + let mut m = X1(Parent); + let prefix = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent); + assert_eq!(m.prepend_with(prefix.clone()), Ok(())); + assert_eq!(m, X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent)); + + let mut m = X1(Parent); + let prefix = X8(Parent, Parent, Parent, Parent, OnlyChild, Parent, Parent, Parent); + assert_eq!(m.prepend_with(prefix.clone()), Ok(())); + assert_eq!(m, X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent)); + } + + #[test] + fn canonicalize_works() { + let mut m = X1(Parent); + m.canonicalize(); + assert_eq!(m, X1(Parent)); + + let mut m = X1(Parachain(1)); + m.canonicalize(); + assert_eq!(m, X1(Parachain(1))); + + let mut m = X6(Parent, Parachain(1), Parent, Parachain(2), Parent, Parachain(3)); + m.canonicalize(); + assert_eq!(m, X2(Parent, Parachain(3))); + + let mut m = X5(Parachain(1), Parent, Parachain(2), Parent, Parachain(3)); + m.canonicalize(); + assert_eq!(m, X1(Parachain(3))); + + let mut m = X6(Parachain(1), Parent, Parachain(2), Parent, Parachain(3), Parent); + m.canonicalize(); + assert_eq!(m, Here); + + let mut m = X5(Parachain(1), Parent, Parent, Parent, Parachain(3)); + m.canonicalize(); + assert_eq!(m, X3(Parent, Parent, Parachain(3))); + + let mut m = X4(Parachain(1), Parachain(2), Parent, Parent); + m.canonicalize(); + assert_eq!(m, Here); + + let mut m = X4(Parent, Parent, Parachain(1), Parachain(2)); + m.canonicalize(); + assert_eq!(m, X4(Parent, Parent, Parachain(1), Parachain(2))); + } +} diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs new file mode 100644 index 000000000000..06363c39ce83 --- /dev/null +++ b/xcm/src/v1/order.rs @@ -0,0 +1,243 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Version 1 of the Cross-Consensus Message format data structures. + +use super::{ + super::v0::Order as Order0, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, +}; +use alloc::{vec, vec::Vec}; +use core::{ + convert::{TryFrom, TryInto}, + result, +}; +use derivative::Derivative; +use parity_scale_codec::{self, Decode, Encode}; + +/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Order { + /// Do nothing. Not generally used. + #[codec(index = 0)] + Noop, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `beneficiary` + /// within this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first + /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset + /// ordering. Any others will remain in holding. + /// - `beneficiary`: The new owner for the assets. + /// + /// Errors: + #[codec(index = 1)] + DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation }, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within + /// this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first + /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset + /// ordering. Any others will remain in holding. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the + /// assets and the notification target for the reserve asset deposit message. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to + /// `dest`. + /// + /// Errors: + #[codec(index = 2)] + DepositReserveAsset { + assets: MultiAssetFilter, + max_assets: u32, + dest: MultiLocation, + effects: Vec>, + }, + + /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. + /// + /// The minimum amount of assets to be received into holding for the order not to fail may be stated. + /// + /// - `give`: The asset(s) to remove from holding. + /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. + /// + /// Errors: + #[codec(index = 3)] + ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account + /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will + /// be executed on them. There will typically be only one valid location on any given asset/chain combination. + /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. + /// + /// Errors: + #[codec(index = 4)] + InitiateReserveWithdraw { + assets: MultiAssetFilter, + reserve: MultiLocation, + effects: Vec>, + }, + + /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message to a `destination` + /// location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. + /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. + /// + /// NOTE: The `destination` location *MUST* respect this origin as a valid teleportation origin for all `assets`. + /// If it does not, then the assets may be lost. + /// + /// Errors: + #[codec(index = 5)] + InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, effects: Vec> }, + + /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- + /// wise, *the lesser of this value and the holding register*. No wildcards will be used when reporting assets + /// back. + /// + /// Errors: + #[codec(index = 6)] + QueryHolding { + #[codec(compact)] + query_id: u64, + dest: MultiLocation, + assets: MultiAssetFilter, + }, + + /// Pay for the execution of some XCM `instructions` and `orders` with up to `weight` picoseconds of execution time, + /// paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from holding to pay for fees. + /// - `weight`: The amount of weight to purchase; this should be at least the shallow weight of `effects` and `xcm`. + /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal to the unpaid weight of + /// any surrounding operations/orders. + /// - `halt_on_error`: If `true`, the execution of the `orders` and `operations` will halt on the first failure. If + /// `false`, then execution will continue regardless. + /// - `orders`: Orders to be executed with the existing Holding Register; execution of these orders happens PRIOR to + /// execution of the `operations`. The (shallow) weight for these must be paid for with the `weight` purchased. + /// - `instructions`: XCM instructions to be executed outside of the context of the current Holding Register; + /// execution of these instructions happens AFTER the execution of the `orders`. The (shallow) weight for these + /// must be paid for with the `weight` purchased. + /// Errors: + #[codec(index = 7)] + BuyExecution { + fees: MultiAsset, + weight: u64, + debt: u64, + halt_on_error: bool, + orders: Vec>, + instructions: Vec>, + }, +} + +pub mod opaque { + pub type Order = super::Order<()>; +} + +impl Order { + pub fn into(self) -> Order { + Order::from(self) + } + pub fn from(order: Order) -> Self { + use Order::*; + match order { + Noop => Noop, + DepositAsset { assets, max_assets, beneficiary } => + DepositAsset { assets, max_assets, beneficiary }, + DepositReserveAsset { assets, max_assets, dest, effects } => + DepositReserveAsset { assets, max_assets, dest, effects }, + ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { assets, reserve, effects }, + InitiateTeleport { assets, dest, effects } => + InitiateTeleport { assets, dest, effects }, + QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest, assets }, + BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { + let orders = orders.into_iter().map(Order::from).collect(); + let instructions = instructions.into_iter().map(Xcm::from).collect(); + BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } + }, + } + } +} + +impl TryFrom> for Order { + type Error = (); + fn try_from(old: Order0) -> result::Result, ()> { + use Order::*; + Ok(match old { + Order0::Null => Noop, + Order0::DepositAsset { assets, dest } => + DepositAsset { assets: assets.try_into()?, max_assets: 1, beneficiary: dest.into() }, + Order0::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset { + assets: assets.try_into()?, + max_assets: 1, + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order0::ExchangeAsset { give, receive } => + ExchangeAsset { give: give.try_into()?, receive: receive.try_into()? }, + Order0::InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { + assets: assets.try_into()?, + reserve: reserve.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order0::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + assets: assets.try_into()?, + dest: dest.into(), + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + Order0::QueryHolding { query_id, dest, assets } => + QueryHolding { query_id, dest: dest.into(), assets: assets.try_into()? }, + Order0::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { + let instructions = + xcm.into_iter().map(Xcm::::try_from).collect::>()?; + BuyExecution { + fees: fees.try_into()?, + weight, + debt, + halt_on_error, + orders: vec![], + instructions, + } + }, + }) + } +} diff --git a/xcm/src/v1/traits.rs b/xcm/src/v1/traits.rs new file mode 100644 index 000000000000..94fd4449f21d --- /dev/null +++ b/xcm/src/v1/traits.rs @@ -0,0 +1,263 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Cross-Consensus Message format data structures. + +use core::result; +use parity_scale_codec::{Decode, Encode}; + +use super::{MultiLocation, Xcm}; + +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +pub enum Error { + Undefined, + /// An arithmetic overflow happened. + Overflow, + /// The operation is intentionally unsupported. + Unimplemented, + UnhandledXcmVersion, + /// The implementation does not handle a given XCM. + UnhandledXcmMessage, + /// The implementation does not handle an effect present in an XCM. + UnhandledEffect, + EscalationOfPrivilege, + UntrustedReserveLocation, + UntrustedTeleportLocation, + DestinationBufferOverflow, + /// The message and destination was recognized as being reachable but the operation could not be completed. + /// A human-readable explanation of the specific issue is provided. + SendFailed(#[codec(skip)] &'static str), + /// The message and destination combination was not recognized as being reachable. + CannotReachDestination(MultiLocation, Xcm<()>), + MultiLocationFull, + FailedToDecode, + BadOrigin, + ExceedsMaxMessageSize, + /// An asset transaction (like withdraw or deposit) failed. + /// See implementers of the `TransactAsset` trait for sources. + /// Causes can include type conversion failures between id or balance types. + FailedToTransactAsset(#[codec(skip)] &'static str), + /// Execution of the XCM would potentially result in a greater weight used than the pre-specified + /// weight limit. The amount that is potentially required is the parameter. + WeightLimitReached(Weight), + /// An asset wildcard was passed where it was not expected (e.g. as the asset to withdraw in a + /// `WithdrawAsset` XCM). + Wildcard, + /// The case where an XCM message has specified a optional weight limit and the weight required for + /// processing is too great. + /// + /// Used by: + /// - `Transact` + TooMuchWeightRequired, + /// The fees specified by the XCM message were not found in the holding register. + /// + /// Used by: + /// - `BuyExecution` + NotHoldingFees, + /// The weight of an XCM message is not computable ahead of execution. This generally means at least part + /// of the message is invalid, which could be due to it containing overly nested structures or an invalid + /// nested data segment (e.g. for the call in `Transact`). + WeightNotComputable, + /// The XCM did not pass the barrier condition for execution. The barrier condition differs on different + /// chains and in different circumstances, but generally it means that the conditions surrounding the message + /// were not such that the chain considers the message worth spending time executing. Since most chains + /// lift the barrier to execution on appropriate payment, presentation of an NFT voucher, or based on the + /// message origin, it means that none of those were the case. + Barrier, + /// Indicates that it is not possible for a location to have an asset be withdrawn or transferred from its + /// ownership. This probably means it doesn't own (enough of) it, but may also indicate that it is under a + /// lock, hold, freeze or is otherwise unavailable. + NotWithdrawable, + /// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location. + LocationCannotHold, + /// The assets given to purchase weight is are insufficient for the weight desired. + TooExpensive, + /// The given asset is not handled. + AssetNotFound, + /// The given message cannot be translated into a format that the destination can be expected to interpret. + DestinationUnsupported, +} + +impl From<()> for Error { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +pub type Result = result::Result<(), Error>; + +/// Local weight type; execution time in picoseconds. +pub type Weight = u64; + +/// Outcome of an XCM execution. +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +pub enum Outcome { + /// Execution completed successfully; given weight was used. + Complete(Weight), + /// Execution started, but did not complete successfully due to the given error; given weight was used. + Incomplete(Weight, Error), + /// Execution did not start due to the given error. + Error(Error), +} + +impl Outcome { + pub fn ensure_complete(self) -> Result { + match self { + Outcome::Complete(_) => Ok(()), + Outcome::Incomplete(_, e) => Err(e), + Outcome::Error(e) => Err(e), + } + } + pub fn ensure_execution(self) -> result::Result { + match self { + Outcome::Complete(w) => Ok(w), + Outcome::Incomplete(w, _) => Ok(w), + Outcome::Error(e) => Err(e), + } + } + /// How much weight was used by the XCM execution attempt. + pub fn weight_used(&self) -> Weight { + match self { + Outcome::Complete(w) => *w, + Outcome::Incomplete(w, _) => *w, + Outcome::Error(_) => 0, + } + } +} + +/// Type of XCM message executor. +pub trait ExecuteXcm { + /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. The weight limit is + /// a basic hard-limit and the implementation may place further restrictions or requirements on weight and + /// other aspects. + fn execute_xcm(origin: MultiLocation, message: Xcm, weight_limit: Weight) -> Outcome { + log::debug!( + target: "xcm::execute_xcm", + "origin: {:?}, message: {:?}, weight_limit: {:?}", + origin, + message, + weight_limit, + ); + Self::execute_xcm_in_credit(origin, message, weight_limit, 0) + } + + /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. + /// + /// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow + /// execution without associated payment. + fn execute_xcm_in_credit( + origin: MultiLocation, + message: Xcm, + weight_limit: Weight, + weight_credit: Weight, + ) -> Outcome; +} + +impl ExecuteXcm for () { + fn execute_xcm_in_credit( + _origin: MultiLocation, + _message: Xcm, + _weight_limit: Weight, + _weight_credit: Weight, + ) -> Outcome { + Outcome::Error(Error::Unimplemented) + } +} + +/// Utility for sending an XCM message. +/// +/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return +/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination` +/// might alter the destination and the XCM message for to the next router. +/// +/// +/// # Example +/// ```rust +/// # use xcm::v1::{MultiLocation, Xcm, Junction, Error, OriginKind, SendXcm, Result}; +/// # use parity_scale_codec::Encode; +/// +/// /// A sender that only passes the message through and does nothing. +/// struct Sender1; +/// impl SendXcm for Sender1 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// return Err(Error::CannotReachDestination(destination, message)) +/// } +/// } +/// +/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. +/// struct Sender2; +/// impl SendXcm for Sender2 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// if let MultiLocation::X2(j1, j2) = destination { +/// Ok(()) +/// } else { +/// Err(Error::Undefined) +/// } +/// } +/// } +/// +/// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. +/// struct Sender3; +/// impl SendXcm for Sender3 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// match destination { +/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), +/// _ => Err(Error::CannotReachDestination(destination, message)), +/// } +/// } +/// } +/// +/// // A call to send via XCM. We don't really care about this. +/// # fn main() { +/// let call: Vec = ().encode(); +/// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; +/// let destination = MultiLocation::X1(Junction::Parent); +/// +/// assert!( +/// // Sender2 will block this. +/// <(Sender1, Sender2, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) +/// .is_err() +/// ); +/// +/// assert!( +/// // Sender3 will catch this. +/// <(Sender1, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) +/// .is_ok() +/// ); +/// # } +/// ``` +pub trait SendXcm { + /// Send an XCM `message` to a given `destination`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* + /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without + /// trying other type fields. + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl SendXcm for Tuple { + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { + for_tuples!( #( + // we shadow `destination` and `message` in each expansion for the next one. + let (destination, message) = match Tuple::send_xcm(destination, message) { + Err(Error::CannotReachDestination(d, m)) => (d, m), + o @ _ => return o, + }; + )* ); + Err(Error::CannotReachDestination(destination, message)) + } +} diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index 17d5b721b3cb..3e412af629da 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -19,7 +19,7 @@ use frame_support::{ensure, traits::Contains, weights::Weight}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; -use xcm::v0::{Junction, MultiLocation, Order, Xcm}; +use xcm::latest::{Junction, MultiLocation, Order, Xcm}; use xcm_executor::traits::{OnResponse, ShouldExecute}; /// Execution barrier that just takes `shallow_weight` from `weight_credit`. @@ -51,9 +51,9 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro ensure!(T::contains(origin), ()); ensure!(top_level, ()); match message { - Xcm::TeleportAsset { effects, .. } | + Xcm::ReceiveTeleportedAsset { effects, .. } | Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposit { effects, .. } + Xcm::ReserveAssetDeposited { effects, .. } if matches!( effects.first(), Some(Order::BuyExecution { debt, ..}) if *debt >= shallow_weight diff --git a/xcm/xcm-builder/src/currency_adapter.rs b/xcm/xcm-builder/src/currency_adapter.rs index fcbaa93c5998..9742d035c78a 100644 --- a/xcm/xcm-builder/src/currency_adapter.rs +++ b/xcm/xcm-builder/src/currency_adapter.rs @@ -19,7 +19,7 @@ use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons}; use sp_runtime::traits::{CheckedSub, SaturatedConversion}; use sp_std::{convert::TryInto, marker::PhantomData, result}; -use xcm::v0::{Error as XcmError, MultiAsset, MultiLocation, Result}; +use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result}; use xcm_executor::{ traits::{Convert, MatchesFungible, TransactAsset}, Assets, @@ -53,7 +53,7 @@ impl From for XcmError { /// # Example /// ``` /// use frame_support::parameter_types; -/// use xcm::v0::{MultiLocation, Junction}; +/// use xcm::latest::{MultiLocation, Junction}; /// use xcm_builder::{ParentIsDefault, CurrencyAdapter, IsConcrete}; /// /// /// Our chain's account id. diff --git a/xcm/xcm-builder/src/filter_asset_location.rs b/xcm/xcm-builder/src/filter_asset_location.rs index cd27065173fb..9794c2f44f5b 100644 --- a/xcm/xcm-builder/src/filter_asset_location.rs +++ b/xcm/xcm-builder/src/filter_asset_location.rs @@ -18,20 +18,20 @@ use frame_support::traits::Get; use sp_std::marker::PhantomData; -use xcm::v0::{MultiAsset, MultiLocation}; +use xcm::latest::{AssetId::Concrete, MultiAsset, MultiAssetFilter, MultiLocation}; use xcm_executor::traits::FilterAssetLocation; /// Accepts an asset iff it is a native asset. pub struct NativeAsset; impl FilterAssetLocation for NativeAsset { fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - matches!(asset, MultiAsset::ConcreteFungible { ref id, .. } if id == origin) + matches!(asset.id, Concrete(ref id) if id == origin) } } /// Accepts an asset if it is contained in the given `T`'s `Get` implementation. pub struct Case(PhantomData); -impl> FilterAssetLocation for Case { +impl> FilterAssetLocation for Case { fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { let (a, o) = T::get(); a.contains(asset) && &o == origin diff --git a/xcm/xcm-builder/src/fungibles_adapter.rs b/xcm/xcm-builder/src/fungibles_adapter.rs index f3c98deb4b04..d9f1e026ee0b 100644 --- a/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/xcm/xcm-builder/src/fungibles_adapter.rs @@ -18,7 +18,12 @@ use frame_support::traits::{tokens::fungibles, Contains, Get}; use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result}; -use xcm::v0::{Error as XcmError, Junction, MultiAsset, MultiLocation, Result}; +use xcm::latest::{ + AssetId::{Abstract, Concrete}, + Error as XcmError, + Fungibility::Fungible, + Junction, MultiAsset, MultiLocation, Result, +}; use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, TransactAsset}; /// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto`) into @@ -61,8 +66,8 @@ impl< for ConvertedConcreteAssetId { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { - let (id, amount) = match a { - MultiAsset::ConcreteFungible { id, amount } => (id, amount), + let (amount, id) = match (&a.fun, &a.id) { + (Fungible(ref amount), Concrete(ref id)) => (amount, id), _ => return Err(MatchError::AssetNotFound), }; let what = @@ -85,8 +90,8 @@ impl< for ConvertedAbstractAssetId { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { - let (id, amount) = match a { - MultiAsset::AbstractFungible { id, amount } => (id, amount), + let (amount, id) = match (&a.fun, &a.id) { + (Fungible(ref amount), Abstract(ref id)) => (amount, id), _ => return Err(MatchError::AssetNotFound), }; let what = diff --git a/xcm/xcm-builder/src/lib.rs b/xcm/xcm-builder/src/lib.rs index a6cccc2d288a..d2e2d2e23f38 100644 --- a/xcm/xcm-builder/src/lib.rs +++ b/xcm/xcm-builder/src/lib.rs @@ -55,7 +55,9 @@ pub use fungibles_adapter::{ }; mod weight; -pub use weight::{FixedRateOfConcreteFungible, FixedWeightBounds, TakeRevenue, UsingComponents}; +#[allow(deprecated)] +pub use weight::FixedRateOfConcreteFungible; +pub use weight::{FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents}; mod matches_fungible; pub use matches_fungible::{IsAbstract, IsConcrete}; diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index 23612629946e..5daaf11d4b81 100644 --- a/xcm/xcm-builder/src/location_conversion.rs +++ b/xcm/xcm-builder/src/location_conversion.rs @@ -19,7 +19,7 @@ use parity_scale_codec::Encode; use sp_io::hashing::blake2_256; use sp_runtime::traits::AccountIdConversion; use sp_std::{borrow::Borrow, marker::PhantomData}; -use xcm::v0::{Junction, MultiLocation, NetworkId}; +use xcm::latest::{Junction, MultiLocation, NetworkId}; use xcm_executor::traits::{Convert, InvertLocation}; pub struct Account32Hash(PhantomData<(Network, AccountId)>); @@ -155,7 +155,7 @@ impl, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone /// ``` /// ```rust /// # use frame_support::parameter_types; -/// # use xcm::v0::{MultiLocation::{self, *}, Junction::*, NetworkId::Any}; +/// # use xcm::latest::{MultiLocation::{self, *}, Junction::*, NetworkId::Any}; /// # use xcm_builder::LocationInverter; /// # use xcm_executor::traits::InvertLocation; /// # fn main() { @@ -200,7 +200,7 @@ mod tests { use super::*; use frame_support::parameter_types; - use xcm::v0::{Junction::*, MultiLocation::*, NetworkId::Any}; + use xcm::latest::{Junction::*, MultiLocation::*, NetworkId::Any}; fn account20() -> Junction { AccountKey20 { network: Any, key: Default::default() } diff --git a/xcm/xcm-builder/src/matches_fungible.rs b/xcm/xcm-builder/src/matches_fungible.rs index 34871e5eba26..f1e619c97068 100644 --- a/xcm/xcm-builder/src/matches_fungible.rs +++ b/xcm/xcm-builder/src/matches_fungible.rs @@ -19,7 +19,11 @@ use frame_support::traits::Get; use sp_runtime::traits::CheckedConversion; use sp_std::{convert::TryFrom, marker::PhantomData}; -use xcm::v0::{MultiAsset, MultiLocation}; +use xcm::latest::{ + AssetId::{Abstract, Concrete}, + Fungibility::Fungible, + MultiAsset, MultiLocation, +}; use xcm_executor::traits::MatchesFungible; /// Converts a `MultiAsset` into balance `B` if it is a concrete fungible with an id equal to that @@ -28,17 +32,16 @@ use xcm_executor::traits::MatchesFungible; /// # Example /// /// ``` -/// use xcm::v0::{MultiAsset, MultiLocation, Junction}; +/// use xcm::latest::prelude::*; /// use xcm_builder::IsConcrete; /// use xcm_executor::traits::MatchesFungible; /// /// frame_support::parameter_types! { -/// pub TargetLocation: MultiLocation = MultiLocation::X1(Junction::Parent); +/// pub TargetLocation: MultiLocation = X1(Parent); /// } /// /// # fn main() { -/// let id = MultiLocation::X1(Junction::Parent); -/// let asset = MultiAsset::ConcreteFungible { id, amount: 999u128 }; +/// let asset = (X1(Parent), 999).into(); /// // match `asset` if it is a concrete asset in `TargetLocation`. /// assert_eq!( as MatchesFungible>::matches_fungible(&asset), Some(999)); /// # } @@ -46,8 +49,8 @@ use xcm_executor::traits::MatchesFungible; pub struct IsConcrete(PhantomData); impl, B: TryFrom> MatchesFungible for IsConcrete { fn matches_fungible(a: &MultiAsset) -> Option { - match a { - MultiAsset::ConcreteFungible { id, amount } if id == &T::get() => + match (&a.id, &a.fun) { + (Concrete(ref id), Fungible(ref amount)) if id == &T::get() => CheckedConversion::checked_from(*amount), _ => None, } @@ -59,7 +62,7 @@ impl, B: TryFrom> MatchesFungible for IsConcrete< /// # Example /// /// ``` -/// use xcm::v0::{MultiAsset}; +/// use xcm::latest::prelude::*; /// use xcm_builder::IsAbstract; /// use xcm_executor::traits::MatchesFungible; /// @@ -68,7 +71,7 @@ impl, B: TryFrom> MatchesFungible for IsConcrete< /// } /// /// # fn main() { -/// let asset = MultiAsset::AbstractFungible { id: vec![7u8], amount: 999u128 }; +/// let asset = (vec![7u8], 999).into(); /// // match `asset` if it is a concrete asset in `TargetLocation`. /// assert_eq!( as MatchesFungible>::matches_fungible(&asset), Some(999)); /// # } @@ -76,8 +79,8 @@ impl, B: TryFrom> MatchesFungible for IsConcrete< pub struct IsAbstract(PhantomData); impl, B: TryFrom> MatchesFungible for IsAbstract { fn matches_fungible(a: &MultiAsset) -> Option { - match a { - MultiAsset::AbstractFungible { id, amount } if &id[..] == T::get() => + match (&a.id, &a.fun) { + (Abstract(ref id), Fungible(ref amount)) if id == &T::get() => CheckedConversion::checked_from(*amount), _ => None, } diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 86d54fa0fe91..21835dd39b1e 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -16,7 +16,7 @@ pub use crate::{ AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, - FixedRateOfConcreteFungible, FixedWeightBounds, LocationInverter, TakeWeightCredit, + FixedRateOfFungible, FixedWeightBounds, LocationInverter, TakeWeightCredit, }; pub use frame_support::{ dispatch::{ @@ -34,10 +34,7 @@ pub use sp_std::{ fmt::Debug, marker::PhantomData, }; -pub use xcm::v0::{ - opaque, Error as XcmError, Junction, Junction::*, MultiAsset, MultiLocation, MultiLocation::*, - Order, OriginKind, Result as XcmResult, SendXcm, Xcm, -}; +pub use xcm::latest::prelude::*; pub use xcm_executor::{ traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset}, Assets, Config, @@ -123,7 +120,7 @@ pub fn assets(who: u64) -> Vec { ASSETS.with(|a| a.borrow().get(&who).map_or(vec![], |a| a.clone().into())) } pub fn add_asset(who: u64, what: MultiAsset) { - ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).saturating_subsume(what)); + ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).subsume(what)); } pub struct TestAssetTransactor; @@ -140,8 +137,8 @@ impl TransactAsset for TestAssetTransactor { a.borrow_mut() .get_mut(&who) .ok_or(XcmError::NotWithdrawable)? - .try_take(what.clone()) - .map_err(|()| XcmError::NotWithdrawable) + .try_take(what.clone().into()) + .map_err(|_| XcmError::NotWithdrawable) }) } } @@ -155,7 +152,7 @@ pub fn to_account(l: MultiLocation) -> Result { // Children at 1000+id X1(Parachain(id)) => 1000 + id as u64, // Self at 3000 - Null => 3000, + Here => 3000, // Parent at 3001 X1(Parent) => 3001, l => return Err(l), @@ -181,14 +178,14 @@ impl ConvertOrigin for TestOriginConverter { } thread_local! { - pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); - pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); + pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); + pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); } -pub fn add_reserve(from: MultiLocation, asset: MultiAsset) { +pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) { IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } #[allow(dead_code)] -pub fn add_teleporter(from: MultiLocation, asset: MultiAsset) { +pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) { IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } pub struct TestIsReserve; @@ -206,7 +203,7 @@ impl FilterAssetLocation for TestIsTeleporter { } } -use xcm::v0::Response; +use xcm::latest::Response; pub enum ResponseSlot { Expecting(MultiLocation), Received(Response), @@ -222,7 +219,11 @@ impl OnResponse for TestResponseHandler { _ => false, }) } - fn on_response(_origin: MultiLocation, query_id: u64, response: xcm::v0::Response) -> Weight { + fn on_response( + _origin: MultiLocation, + query_id: u64, + response: xcm::latest::Response, + ) -> Weight { QUERIES.with(|q| { q.borrow_mut().entry(query_id).and_modify(|v| { if matches!(*v, ResponseSlot::Expecting(..)) { @@ -254,7 +255,7 @@ parameter_types! { pub static AllowUnpaidFrom: Vec = vec![]; pub static AllowPaidFrom: Vec = vec![]; // 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight. - pub static WeightPrice: (MultiLocation, u128) = (Null, 1_000_000_000_000); + pub static WeightPrice: (AssetId, u128) = (Here.into(), 1_000_000_000_000); } pub type TestBarrier = ( @@ -275,6 +276,6 @@ impl Config for TestConfig { type LocationInverter = LocationInverter; type Barrier = TestBarrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfConcreteFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = TestResponseHandler; } diff --git a/xcm/xcm-builder/src/origin_conversion.rs b/xcm/xcm-builder/src/origin_conversion.rs index 2a1956ca79de..08a6de91c648 100644 --- a/xcm/xcm-builder/src/origin_conversion.rs +++ b/xcm/xcm-builder/src/origin_conversion.rs @@ -20,7 +20,7 @@ use frame_support::traits::{EnsureOrigin, Get, GetBacking, OriginTrait}; use frame_system::RawOrigin as SystemRawOrigin; use polkadot_parachain::primitives::IsSystem; use sp_std::{convert::TryInto, marker::PhantomData}; -use xcm::v0::{BodyId, BodyPart, Junction, MultiLocation, NetworkId, OriginKind}; +use xcm::latest::{BodyId, BodyPart, Junction, MultiLocation, NetworkId, OriginKind}; use xcm_executor::traits::{Convert, ConvertOrigin}; /// Sovereign accounts use the system's `Signed` origin with an account ID derived from the `LocationConverter`. @@ -170,7 +170,7 @@ where // We institute a root fallback so root can always represent the context. This // guarantees that `successful_origin` will work. if o.caller() == Origin::root().caller() { - Ok(MultiLocation::Null) + Ok(MultiLocation::Here) } else { Err(o) } diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 4b5ace87bd76..31f141ab68be 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -15,16 +15,14 @@ // along with Polkadot. If not, see . use super::{mock::*, *}; -use xcm::v0::{ExecuteXcm, NetworkId::Any, Order, Outcome, Response}; +use xcm::latest::prelude::*; use xcm_executor::{traits::*, Config, XcmExecutor}; -use MultiAsset::*; -use Option::None; #[test] fn basic_setup_works() { - add_reserve(X1(Parent), AllConcreteFungible { id: X1(Parent) }); + add_reserve(X1(Parent), Wild((X1(Parent), WildFungible).into())); assert!(::IsReserve::filter_asset_location( - &ConcreteFungible { id: X1(Parent), amount: 100 }, + &(X1(Parent), 100).into(), &X1(Parent), )); @@ -34,22 +32,23 @@ fn basic_setup_works() { assert_eq!(to_account(X2(Parent, Parachain(50))), Ok(2050)); assert_eq!(to_account(X1(AccountIndex64 { index: 1, network: Any })), Ok(1)); assert_eq!(to_account(X1(AccountIndex64 { index: 42, network: Any })), Ok(42)); - assert_eq!(to_account(Null), Ok(3000)); + assert_eq!(to_account(Here), Ok(3000)); } #[test] fn weigher_should_work() { - let mut message = opaque::Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + let mut message = opaque::Xcm::ReserveAssetDeposited { + assets: (X1(Parent), 100).into(), effects: vec![ Order::BuyExecution { - fees: All, + fees: (X1(Parent), 1).into(), weight: 0, debt: 30, halt_on_error: true, - xcm: vec![], + orders: vec![], + instructions: vec![], }, - Order::DepositAsset { assets: vec![All], dest: Null }, + Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here }, ], } .into(); @@ -58,10 +57,8 @@ fn weigher_should_work() { #[test] fn take_weight_credit_barrier_should_work() { - let mut message = opaque::Xcm::TransferAsset { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], - dest: Null, - }; + let mut message = + opaque::Xcm::TransferAsset { assets: (X1(Parent), 100).into(), beneficiary: Here }; let mut weight_credit = 10; let r = @@ -77,10 +74,8 @@ fn take_weight_credit_barrier_should_work() { #[test] fn allow_unpaid_should_work() { - let mut message = opaque::Xcm::TransferAsset { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], - dest: Null, - }; + let mut message = + opaque::Xcm::TransferAsset { assets: (X1(Parent), 100).into(), beneficiary: Here }; AllowUnpaidFrom::set(vec![X1(Parent)]); @@ -107,10 +102,8 @@ fn allow_unpaid_should_work() { fn allow_paid_should_work() { AllowPaidFrom::set(vec![X1(Parent)]); - let mut message = opaque::Xcm::TransferAsset { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], - dest: Null, - }; + let mut message = + opaque::Xcm::TransferAsset { assets: (X1(Parent), 100).into(), beneficiary: Here }; let r = AllowTopLevelPaidExecutionFrom::>::should_execute( &X1(Parachain(1)), @@ -121,17 +114,19 @@ fn allow_paid_should_work() { ); assert_eq!(r, Err(())); - let mut underpaying_message = opaque::Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + let fees = (X1(Parent), 1).into(); + let mut underpaying_message = opaque::Xcm::ReserveAssetDeposited { + assets: (X1(Parent), 100).into(), effects: vec![ Order::BuyExecution { - fees: All, + fees, weight: 0, debt: 20, halt_on_error: true, - xcm: vec![], + orders: vec![], + instructions: vec![], }, - Order::DepositAsset { assets: vec![All], dest: Null }, + Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here }, ], }; @@ -144,17 +139,19 @@ fn allow_paid_should_work() { ); assert_eq!(r, Err(())); - let mut paying_message = opaque::Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + let fees = (X1(Parent), 1).into(); + let mut paying_message = opaque::Xcm::ReserveAssetDeposited { + assets: (X1(Parent), 100).into(), effects: vec![ Order::BuyExecution { - fees: All, + fees, weight: 0, debt: 30, halt_on_error: true, - xcm: vec![], + orders: vec![], + instructions: vec![], }, - Order::DepositAsset { assets: vec![All], dest: Null }, + Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here }, ], }; @@ -180,27 +177,33 @@ fn allow_paid_should_work() { #[test] fn paying_reserve_deposit_should_work() { AllowPaidFrom::set(vec![X1(Parent)]); - add_reserve(X1(Parent), AllConcreteFungible { id: X1(Parent) }); - WeightPrice::set((X1(Parent), 1_000_000_000_000)); + add_reserve(X1(Parent), (Parent, WildFungible).into()); + WeightPrice::set((Parent.into(), 1_000_000_000_000)); let origin = X1(Parent); - let message = Xcm::::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + let fees = (X1(Parent), 30).into(); + let message = Xcm::::ReserveAssetDeposited { + assets: (X1(Parent), 100).into(), effects: vec![ Order::::BuyExecution { - fees: All, + fees, weight: 0, debt: 30, halt_on_error: true, - xcm: vec![], + orders: vec![], + instructions: vec![], + }, + Order::::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Here, }, - Order::::DepositAsset { assets: vec![All], dest: Null }, ], }; let weight_limit = 50; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); assert_eq!(r, Outcome::Complete(30)); - assert_eq!(assets(3000), vec![ConcreteFungible { id: X1(Parent), amount: 70 }]); + assert_eq!(assets(3000), vec![(X1(Parent), 70).into()]); } #[test] @@ -208,19 +211,19 @@ fn transfer_should_work() { // we'll let them have message execution for free. AllowUnpaidFrom::set(vec![X1(Parachain(1))]); // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, ConcreteFungible { id: Null, amount: 1000 }); + add_asset(1001, (Here, 1000).into()); // They want to transfer 100 of them to their sibling parachain #2 let r = XcmExecutor::::execute_xcm( X1(Parachain(1)), Xcm::TransferAsset { - assets: vec![ConcreteFungible { id: Null, amount: 100 }], - dest: X1(AccountIndex64 { index: 3, network: Any }), + assets: (Here, 100).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }), }, 50, ); assert_eq!(r, Outcome::Complete(10)); - assert_eq!(assets(3), vec![ConcreteFungible { id: Null, amount: 100 }]); - assert_eq!(assets(1001), vec![ConcreteFungible { id: Null, amount: 900 }]); + assert_eq!(assets(3), vec![(Here, 100).into()]); + assert_eq!(assets(1001), vec![(Here, 900).into()]); assert_eq!(sent_xcm(), vec![]); } @@ -228,7 +231,7 @@ fn transfer_should_work() { fn reserve_transfer_should_work() { AllowUnpaidFrom::set(vec![X1(Parachain(1))]); // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, ConcreteFungible { id: Null, amount: 1000 }); + add_asset(1001, (Here, 1000).into()); // The remote account owned by gav. let three = X1(AccountIndex64 { index: 3, network: Any }); @@ -237,22 +240,30 @@ fn reserve_transfer_should_work() { let r = XcmExecutor::::execute_xcm( X1(Parachain(1)), Xcm::TransferReserveAsset { - assets: vec![ConcreteFungible { id: Null, amount: 100 }], + assets: (Here, 100).into(), dest: X1(Parachain(2)), - effects: vec![Order::DepositAsset { assets: vec![All], dest: three.clone() }], + effects: vec![Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: three.clone(), + }], }, 50, ); assert_eq!(r, Outcome::Complete(10)); - assert_eq!(assets(1002), vec![ConcreteFungible { id: Null, amount: 100 }]); + assert_eq!(assets(1002), vec![(Here, 100).into()]); assert_eq!( sent_xcm(), vec![( X1(Parachain(2)), - Xcm::ReserveAssetDeposit { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], - effects: vec![Order::DepositAsset { assets: vec![All], dest: three }], + Xcm::ReserveAssetDeposited { + assets: (X1(Parent), 100).into(), + effects: vec![Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: three + }], } )] ); @@ -307,32 +318,38 @@ fn transacting_should_refund_weight() { fn paid_transacting_should_refund_payment_for_unused_weight() { let one = X1(AccountIndex64 { index: 1, network: Any }); AllowPaidFrom::set(vec![one.clone()]); - add_asset(1, ConcreteFungible { id: X1(Parent), amount: 100 }); - WeightPrice::set((X1(Parent), 1_000_000_000_000)); + add_asset(1, (Parent, 100).into()); + WeightPrice::set((Parent.into(), 1_000_000_000_000)); let origin = one.clone(); + let fees = (X1(Parent), 100).into(); let message = Xcm::::WithdrawAsset { - assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], // enough for 100 units of weight. + assets: (X1(Parent), 100).into(), // enough for 100 units of weight. effects: vec![ Order::::BuyExecution { - fees: All, + fees, weight: 70, debt: 30, halt_on_error: true, - xcm: vec![Xcm::::Transact { + orders: vec![], + instructions: vec![Xcm::::Transact { origin_type: OriginKind::Native, require_weight_at_most: 60, // call estimated at 70 but only takes 10. call: TestCall::Any(60, Some(10)).encode().into(), }], }, - Order::::DepositAsset { assets: vec![All], dest: one.clone() }, + Order::::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: one.clone(), + }, ], }; let weight_limit = 100; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); assert_eq!(r, Outcome::Complete(50)); - assert_eq!(assets(1), vec![ConcreteFungible { id: X1(Parent), amount: 50 }]); + assert_eq!(assets(1), vec![(X1(Parent), 50).into()]); } #[test] @@ -342,7 +359,7 @@ fn prepaid_result_of_query_should_get_free_execution() { // We put this in manually here, but normally this would be done at the point of crafting the message. expect_response(query_id, origin.clone()); - let the_response = Response::Assets(vec![ConcreteFungible { id: X1(Parent), amount: 100 }]); + let the_response = Response::Assets((X1(Parent), 100).into()); let message = Xcm::::QueryResponse { query_id, response: the_response.clone() }; let weight_limit = 10; diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index f86030b902b3..12ec9ff173d8 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -21,7 +21,7 @@ use frame_support::{ use parity_scale_codec::Decode; use sp_runtime::traits::{SaturatedConversion, Saturating, Zero}; use sp_std::{convert::TryInto, marker::PhantomData, result::Result}; -use xcm::v0::{Error, MultiAsset, MultiLocation, Order, Xcm}; +use xcm::latest::{AssetId, AssetId::Concrete, Error, MultiAsset, MultiLocation, Order, Xcm}; use xcm_executor::{ traits::{WeightBounds, WeightTrader}, Assets, @@ -36,23 +36,13 @@ impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeigh Xcm::RelayedFrom { ref mut message, .. } => T::get().saturating_add(Self::shallow(message.as_mut())?), Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposit { effects, .. } | - Xcm::TeleportAsset { effects, .. } => { - let inner: Weight = effects - .iter_mut() - .map(|effect| match effect { - Order::BuyExecution { .. } => { - // On success, execution of this will result in more weight being consumed but - // we don't count it here since this is only the *shallow*, non-negotiable weight - // spend and doesn't count weight placed behind a `BuyExecution` since it will not - // be definitely consumed from any existing weight credit if execution of the message - // is attempted. - T::get() - }, - _ => T::get(), - }) - .sum(); - T::get().saturating_add(inner) + Xcm::ReserveAssetDeposited { effects, .. } | + Xcm::ReceiveTeleportedAsset { effects, .. } => { + let mut extra = T::get(); + for order in effects.iter_mut() { + extra.saturating_accrue(Self::shallow_order(order)?); + } + extra }, _ => T::get(), }) @@ -61,19 +51,46 @@ impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeigh Ok(match message { Xcm::RelayedFrom { ref mut message, .. } => Self::deep(message.as_mut())?, Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposit { effects, .. } | - Xcm::TeleportAsset { effects, .. } => { + Xcm::ReserveAssetDeposited { effects, .. } | + Xcm::ReceiveTeleportedAsset { effects, .. } => { + let mut extra = 0; + for order in effects.iter_mut() { + extra.saturating_accrue(Self::deep_order(order)?); + } + extra + }, + _ => 0, + }) + } +} + +impl, C: Decode + GetDispatchInfo> FixedWeightBounds { + fn shallow_order(order: &mut Order) -> Result { + Ok(match order { + Order::BuyExecution { .. } => { + // On success, execution of this will result in more weight being consumed but + // we don't count it here since this is only the *shallow*, non-negotiable weight + // spend and doesn't count weight placed behind a `BuyExecution` since it will not + // be definitely consumed from any existing weight credit if execution of the message + // is attempted. + T::get() + }, + _ => T::get(), + }) + } + fn deep_order(order: &mut Order) -> Result { + Ok(match order { + Order::BuyExecution { orders, instructions, .. } => { let mut extra = 0; - for effect in effects.iter_mut() { - match effect { - Order::BuyExecution { xcm, .. } => - for message in xcm.iter_mut() { - extra.saturating_accrue( - Self::shallow(message)?.saturating_add(Self::deep(message)?), - ); - }, - _ => {}, - } + for instruction in instructions.iter_mut() { + extra.saturating_accrue( + Self::shallow(instruction)?.saturating_add(Self::deep(instruction)?), + ); + } + for order in orders.iter_mut() { + extra.saturating_accrue( + Self::shallow_order(order)?.saturating_add(Self::deep_order(order)?), + ); } extra }, @@ -98,11 +115,13 @@ impl TakeRevenue for () { /// /// The constant `Get` type parameter should be the concrete fungible ID and the amount of it required for /// one second of weight. +#[deprecated = "Use `FixedRateOfFungible` instead"] pub struct FixedRateOfConcreteFungible, R: TakeRevenue>( Weight, u128, PhantomData<(T, R)>, ); +#[allow(deprecated)] impl, R: TakeRevenue> WeightTrader for FixedRateOfConcreteFungible { @@ -114,28 +133,80 @@ impl, R: TakeRevenue> WeightTrader let (id, units_per_second) = T::get(); use frame_support::weights::constants::WEIGHT_PER_SECOND; let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); - let required = MultiAsset::ConcreteFungible { amount, id }; - let (unused, _) = payment.less(required).map_err(|_| Error::TooExpensive)?; + let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) } - fn refund_weight(&mut self, weight: Weight) -> MultiAsset { + fn refund_weight(&mut self, weight: Weight) -> Option { let (id, units_per_second) = T::get(); let weight = weight.min(self.0); let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128; self.0 -= weight; self.1 = self.1.saturating_sub(amount); - let result = MultiAsset::ConcreteFungible { amount, id }; - result + if amount > 0 { + Some((Concrete(id), amount).into()) + } else { + None + } } } - +#[allow(deprecated)] impl, R: TakeRevenue> Drop for FixedRateOfConcreteFungible { fn drop(&mut self) { - let revenue = MultiAsset::ConcreteFungible { amount: self.1, id: T::get().0 }; - R::take_revenue(revenue); + if self.1 > 0 { + R::take_revenue((Concrete(T::get().0), self.1).into()); + } + } +} + +/// Simple fee calculator that requires payment in a single fungible at a fixed rate. +/// +/// The constant `Get` type parameter should be the fungible ID and the amount of it required for +/// one second of weight. +pub struct FixedRateOfFungible, R: TakeRevenue>( + Weight, + u128, + PhantomData<(T, R)>, +); +impl, R: TakeRevenue> WeightTrader for FixedRateOfFungible { + fn new() -> Self { + Self(0, 0, PhantomData) + } + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + let (id, units_per_second) = T::get(); + use frame_support::weights::constants::WEIGHT_PER_SECOND; + let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); + if amount == 0 { + return Ok(payment) + } + let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?; + self.0 = self.0.saturating_add(weight); + self.1 = self.1.saturating_add(amount); + Ok(unused) + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + let (id, units_per_second) = T::get(); + let weight = weight.min(self.0); + let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128; + self.0 -= weight; + self.1 = self.1.saturating_sub(amount); + if amount > 0 { + Some((id, amount).into()) + } else { + None + } + } +} + +impl, R: TakeRevenue> Drop for FixedRateOfFungible { + fn drop(&mut self) { + if self.1 > 0 { + R::take_revenue((T::get().0, self.1).into()); + } } } @@ -166,24 +237,25 @@ impl< fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let amount = WeightToFee::calc(&weight); - let required = MultiAsset::ConcreteFungible { - amount: amount.try_into().map_err(|_| Error::Overflow)?, - id: AssetId::get(), - }; - let (unused, _) = payment.less(required).map_err(|_| Error::TooExpensive)?; + let u128_amount: u128 = amount.try_into().map_err(|_| Error::Overflow)?; + let required = (Concrete(AssetId::get()), u128_amount).into(); + let unused = payment.checked_sub(required).map_err(|_| Error::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) } - fn refund_weight(&mut self, weight: Weight) -> MultiAsset { + fn refund_weight(&mut self, weight: Weight) -> Option { let weight = weight.min(self.0); let amount = WeightToFee::calc(&weight); self.0 -= weight; self.1 = self.1.saturating_sub(amount); - let result = - MultiAsset::ConcreteFungible { amount: amount.saturated_into(), id: AssetId::get() }; - result + let amount: u128 = amount.saturated_into(); + if amount > 0 { + Some((AssetId::get(), amount).into()) + } else { + None + } } } impl< diff --git a/xcm/xcm-executor/src/assets.rs b/xcm/xcm-executor/src/assets.rs index a98560d2357b..4c241471a793 100644 --- a/xcm/xcm-executor/src/assets.rs +++ b/xcm/xcm-executor/src/assets.rs @@ -14,48 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use sp_runtime::RuntimeDebug; +use sp_runtime::{traits::Saturating, RuntimeDebug}; use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, mem, prelude::*, }; -use xcm::v0::{AssetInstance, MultiAsset, MultiLocation}; - -/// Classification of an asset being concrete or abstract. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] -pub enum AssetId { - Concrete(MultiLocation), - Abstract(Vec), -} - -impl AssetId { - /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. - pub fn prepend_location(&mut self, prepend: &MultiLocation) -> Result<(), ()> { - if let AssetId::Concrete(ref mut l) = self { - l.prepend_with(prepend.clone()).map_err(|_| ())?; - } - Ok(()) - } - - /// Use the value of `self` along with an `amount to create the corresponding `MultiAsset` value for a - /// fungible asset. - pub fn into_fungible_multiasset(self, amount: u128) -> MultiAsset { - match self { - AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, - AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, - } - } - - /// Use the value of `self` along with an `instance to create the corresponding `MultiAsset` value for a - /// non-fungible asset. - pub fn into_non_fungible_multiasset(self, instance: AssetInstance) -> MultiAsset { - match self { - AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance }, - AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance }, - } - } -} +use xcm::latest::{ + AssetId, AssetInstance, + Fungibility::{Fungible, NonFungible}, + MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, + WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{All, AllOf}, +}; /// List of non-wildcard fungible and non-fungible assets. #[derive(Default, Clone, RuntimeDebug, Eq, PartialEq)] @@ -64,232 +35,148 @@ pub struct Assets { pub fungible: BTreeMap, /// The non-fungible assets. - // OPTIMIZE: Consider BTreeMap> + // TODO: Consider BTreeMap> // or even BTreeMap> pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, } +impl From for Assets { + fn from(asset: MultiAsset) -> Assets { + let mut result = Self::default(); + result.subsume(asset); + result + } +} + impl From> for Assets { fn from(assets: Vec) -> Assets { let mut result = Self::default(); for asset in assets.into_iter() { - result.saturating_subsume(asset) + result.subsume(asset) } result } } +impl From for Assets { + fn from(assets: MultiAssets) -> Assets { + assets.drain().into() + } +} + impl From for Vec { fn from(a: Assets) -> Self { a.into_assets_iter().collect() } } -impl From for Assets { - fn from(asset: MultiAsset) -> Assets { - let mut result = Self::default(); - result.saturating_subsume(asset); - result +impl From for MultiAssets { + fn from(a: Assets) -> Self { + a.into_assets_iter().collect::>().into() } } +/// An error emitted by `take` operations. +#[derive(Debug)] +pub enum TakeError { + /// There was an attempt to take an asset without saturating (enough of) which did not exist. + AssetUnderflow(MultiAsset), +} + impl Assets { /// New value, containing no assets. pub fn new() -> Self { Self::default() } - /// An iterator over the fungible assets. + /// Total number of distinct assets. + pub fn len(&self) -> usize { + self.fungible.len() + self.non_fungible.len() + } + + /// A borrowing iterator over the fungible assets. pub fn fungible_assets_iter<'a>(&'a self) -> impl Iterator + 'a { - self.fungible.iter().map(|(id, &amount)| match id.clone() { - AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, - AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, - }) + self.fungible + .iter() + .map(|(id, &amount)| MultiAsset { fun: Fungible(amount), id: id.clone() }) } - /// An iterator over the non-fungible assets. + /// A borrowing iterator over the non-fungible assets. pub fn non_fungible_assets_iter<'a>(&'a self) -> impl Iterator + 'a { - self.non_fungible.iter().map(|&(ref class, ref instance)| match class.clone() { - AssetId::Concrete(class) => - MultiAsset::ConcreteNonFungible { class, instance: instance.clone() }, - AssetId::Abstract(class) => - MultiAsset::AbstractNonFungible { class, instance: instance.clone() }, - }) + self.non_fungible + .iter() + .map(|(id, instance)| MultiAsset { fun: NonFungible(instance.clone()), id: id.clone() }) } - /// An iterator over all assets. + /// A consuming iterator over all assets. pub fn into_assets_iter(self) -> impl Iterator { - let fungible = self.fungible.into_iter().map(|(id, amount)| match id { - AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, - AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, - }); - let non_fungible = self.non_fungible.into_iter().map(|(id, instance)| match id { - AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance }, - AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance }, - }); - fungible.chain(non_fungible) - } - - /// An iterator over all assets. + self.fungible + .into_iter() + .map(|(id, amount)| MultiAsset { fun: Fungible(amount), id }) + .chain( + self.non_fungible + .into_iter() + .map(|(id, instance)| MultiAsset { fun: NonFungible(instance), id }), + ) + } + + /// A borrowing iterator over all assets. pub fn assets_iter<'a>(&'a self) -> impl Iterator + 'a { - let fungible = self.fungible_assets_iter(); - let non_fungible = self.non_fungible_assets_iter(); - fungible.chain(non_fungible) + self.fungible_assets_iter().chain(self.non_fungible_assets_iter()) } /// Mutate `self` to contain all given `assets`, saturating if necessary. /// - /// Wildcards in `assets` are ignored. - pub fn saturating_subsume_all(&mut self, assets: Assets) { - // OPTIMIZE: Could be done with a much faster btree entry merge and only sum the entries with the - // same key. - for asset in assets.into_assets_iter() { - self.saturating_subsume(asset) + /// NOTE: [`Assets`] are always sorted, allowing us to optimize this function from `O(n^2)` to `O(n)`. + pub fn subsume_assets(&mut self, mut assets: Assets) { + let mut f_iter = assets.fungible.iter_mut(); + let mut g_iter = self.fungible.iter_mut(); + if let (Some(mut f), Some(mut g)) = (f_iter.next(), g_iter.next()) { + loop { + if f.0 == g.0 { + // keys are equal. in this case, we add `self`'s balance for the asset onto `assets`, balance, knowing + // that the `append` operation which follows will clobber `self`'s value and only use `assets`'s. + (*f.1).saturating_accrue(*g.1); + } + if f.0 <= g.0 { + f = match f_iter.next() { + Some(x) => x, + None => break, + }; + } + if f.0 >= g.0 { + g = match g_iter.next() { + Some(x) => x, + None => break, + }; + } + } } + self.fungible.append(&mut assets.fungible); + self.non_fungible.append(&mut assets.non_fungible); } /// Mutate `self` to contain the given `asset`, saturating if necessary. /// /// Wildcard values of `asset` do nothing. - pub fn saturating_subsume(&mut self, asset: MultiAsset) { - match asset { - MultiAsset::ConcreteFungible { id, amount } => { - self.saturating_subsume_fungible(AssetId::Concrete(id), amount); + pub fn subsume(&mut self, asset: MultiAsset) { + match asset.fun { + Fungible(amount) => { + self.fungible + .entry(asset.id) + .and_modify(|e| *e = e.saturating_add(amount)) + .or_insert(amount); }, - MultiAsset::AbstractFungible { id, amount } => { - self.saturating_subsume_fungible(AssetId::Abstract(id), amount); + NonFungible(instance) => { + self.non_fungible.insert((asset.id, instance)); }, - MultiAsset::ConcreteNonFungible { class, instance } => { - self.saturating_subsume_non_fungible(AssetId::Concrete(class), instance); - }, - MultiAsset::AbstractNonFungible { class, instance } => { - self.saturating_subsume_non_fungible(AssetId::Abstract(class), instance); - }, - _ => (), } } - /// Consumes `self` and returns its original value excluding `asset` iff it contains at least `asset`. - /// - /// Wildcard assets in `self` will result in an error. - /// - /// `asset` may be a wildcard and are evaluated in the context of `self`. - /// - /// Returns `Ok` with the `self` minus `asset` and the non-wildcard equivalence of `asset` taken if `self` - /// contains `asset`, and `Err` with `self` otherwise. - pub fn less(mut self, asset: MultiAsset) -> Result<(Self, Assets), Self> { - match self.try_take(asset) { - Ok(taken) => Ok((self, taken)), - Err(()) => Err(self), - } - } - - /// Mutates `self` to its original value less `asset` and returns `true` iff it contains at least `asset`. - /// - /// Wildcard assets in `self` will result in an error. - /// - /// `asset` may be a wildcard and are evaluated in the context of `self`. - /// - /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its value minus - /// `asset` if `self` contains `asset`, and return `Err` otherwise. - pub fn try_take(&mut self, asset: MultiAsset) -> Result { - match asset { - MultiAsset::None => Ok(Assets::new()), - MultiAsset::ConcreteFungible { id, amount } => - self.try_take_fungible(AssetId::Concrete(id), amount), - MultiAsset::AbstractFungible { id, amount } => - self.try_take_fungible(AssetId::Abstract(id), amount), - MultiAsset::ConcreteNonFungible { class, instance } => - self.try_take_non_fungible(AssetId::Concrete(class), instance), - MultiAsset::AbstractNonFungible { class, instance } => - self.try_take_non_fungible(AssetId::Abstract(class), instance), - MultiAsset::AllAbstractFungible { id } => - Ok(self.take_fungible(&AssetId::Abstract(id))), - MultiAsset::AllConcreteFungible { id } => - Ok(self.take_fungible(&AssetId::Concrete(id))), - MultiAsset::AllAbstractNonFungible { class } => - Ok(self.take_non_fungible(&AssetId::Abstract(class))), - MultiAsset::AllConcreteNonFungible { class } => - Ok(self.take_non_fungible(&AssetId::Concrete(class))), - MultiAsset::AllFungible => { - let mut taken = Assets::new(); - mem::swap(&mut self.fungible, &mut taken.fungible); - Ok(taken) - }, - MultiAsset::AllNonFungible => { - let mut taken = Assets::new(); - mem::swap(&mut self.non_fungible, &mut taken.non_fungible); - Ok(taken) - }, - MultiAsset::All => Ok(self.swapped(Assets::new())), - } - } - - pub fn try_take_fungible(&mut self, id: AssetId, amount: u128) -> Result { - self.try_remove_fungible(&id, amount)?; - Ok(id.into_fungible_multiasset(amount).into()) - } - - pub fn try_take_non_fungible( - &mut self, - id: AssetId, - instance: AssetInstance, - ) -> Result { - let asset_id_instance = (id, instance); - self.try_remove_non_fungible(&asset_id_instance)?; - let (asset_id, instance) = asset_id_instance; - Ok(asset_id.into_non_fungible_multiasset(instance).into()) - } - - pub fn take_fungible(&mut self, id: &AssetId) -> Assets { - let mut taken = Assets::new(); - if let Some((id, amount)) = self.fungible.remove_entry(&id) { - taken.fungible.insert(id, amount); - } - taken - } - - pub fn take_non_fungible(&mut self, id: &AssetId) -> Assets { - let mut taken = Assets::new(); - let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); - non_fungible.into_iter().for_each(|(c, instance)| { - if &c == id { - taken.non_fungible.insert((c, instance)); - } else { - self.non_fungible.insert((c, instance)); - } - }); - taken - } - - pub fn try_remove_fungible(&mut self, id: &AssetId, amount: u128) -> Result<(), ()> { - let self_amount = self.fungible.get_mut(&id).ok_or(())?; - *self_amount = self_amount.checked_sub(amount).ok_or(())?; - Ok(()) - } - - pub fn try_remove_non_fungible( - &mut self, - class_instance: &(AssetId, AssetInstance), - ) -> Result<(), ()> { - match self.non_fungible.remove(class_instance) { - true => Ok(()), - false => Err(()), - } - } - - /// Modify `self` to include a new fungible asset by `id` and `amount`, - /// saturating if necessary. - pub fn saturating_subsume_fungible(&mut self, id: AssetId, amount: u128) { - self.fungible - .entry(id) - .and_modify(|e| *e = e.saturating_add(amount)) - .or_insert(amount); - } - - /// Modify `self` to include a new non-fungible asset by `class` and `instance`. - pub fn saturating_subsume_non_fungible(&mut self, class: AssetId, instance: AssetInstance) { - self.non_fungible.insert((class, instance)); + /// Swaps two mutable Assets, without deinitializing either one. + pub fn swapped(&mut self, mut with: Assets) -> Self { + mem::swap(&mut *self, &mut with); + with } /// Alter any concretely identified assets by prepending the given `MultiLocation`. @@ -302,7 +189,7 @@ impl Assets { self.fungible = fungible .into_iter() .map(|(mut id, amount)| { - let _ = id.prepend_location(prepend); + let _ = id.reanchor(prepend); (id, amount) }) .collect(); @@ -311,308 +198,304 @@ impl Assets { self.non_fungible = non_fungible .into_iter() .map(|(mut class, inst)| { - let _ = class.prepend_location(prepend); + let _ = class.reanchor(prepend); (class, inst) }) .collect(); } - /// Return the assets in `self`, but (asset-wise) of no greater value than `assets`. - /// - /// Result is undefined if `assets` includes elements which match to the same asset more than once. - /// - /// Example: - /// - /// ``` - /// use xcm_executor::Assets; - /// use xcm::v0::{MultiAsset, MultiLocation}; - /// let assets_i_have: Assets = vec![ - /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, - /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, - /// ].into(); - /// let assets_they_want: Assets = vec![ - /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 200 }, - /// MultiAsset::AbstractFungible { id: vec![0], amount: 50 }, - /// ].into(); - /// - /// let assets_we_can_trade: Assets = assets_i_have.min(assets_they_want.assets_iter()); - /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ - /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, - /// MultiAsset::AbstractFungible { id: vec![0], amount: 50 }, - /// ]); - /// ``` - pub fn min<'a, M, I>(&self, assets: I) -> Self - where - M: 'a + sp_std::borrow::Borrow, - I: IntoIterator, - { - let mut result = Assets::default(); - for asset in assets.into_iter() { - match asset.borrow() { - MultiAsset::None => (), - MultiAsset::All => return self.clone(), - MultiAsset::AllFungible => { - // Replace `result.fungible` with all fungible assets, - // keeping `result.non_fungible` the same. - result = Assets { - fungible: self.fungible.clone(), - non_fungible: result.non_fungible, + /// Returns an error unless all `assets` are contained in `self`. In the case of an error, the first asset in + /// `assets` which is not wholly in `self` is returned. + fn ensure_contains(&self, assets: &MultiAssets) -> Result<(), TakeError> { + for asset in assets.inner().iter() { + match asset { + MultiAsset { fun: Fungible(ref amount), ref id } => { + if self.fungible.get(id).map_or(true, |a| a < amount) { + return Err(TakeError::AssetUnderflow((id.clone(), *amount).into())) } }, - MultiAsset::AllNonFungible => { - // Replace `result.non_fungible` with all non-fungible assets, - // keeping `result.fungible` the same. - result = Assets { - fungible: result.fungible, - non_fungible: self.non_fungible.clone(), + MultiAsset { fun: NonFungible(ref instance), ref id } => { + let id_instance = (id.clone(), instance.clone()); + if !self.non_fungible.contains(&id_instance) { + return Err(TakeError::AssetUnderflow(id_instance.into())) } }, - MultiAsset::AllAbstractFungible { id } => - for asset in self.fungible_assets_iter() { - match &asset { - MultiAsset::AbstractFungible { id: identifier, .. } => { - if id == identifier { - result.saturating_subsume(asset) - } - }, - _ => (), + } + } + return Ok(()) + } + + /// Mutates `self` to its original value less `mask` and returns assets that were removed. + /// + /// If `saturate` is `true`, then `self` is considered to be masked by `mask`, thereby avoiding any attempt at + /// reducing it by assets it does not contain. In this case, the function is infallible. If `saturate` is `false` + /// and `mask` references a definite asset which `self` does not contain then an error is returned. + /// + /// The number of unique assets which are removed will never be any greater than `limit`. + /// + /// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value minus + /// `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough of) a definite asset to + /// be removed. + fn general_take( + &mut self, + mask: MultiAssetFilter, + saturate: bool, + limit: usize, + ) -> Result { + let mut taken = Assets::new(); + match mask { + MultiAssetFilter::Wild(All) => + if self.fungible.len() + self.non_fungible.len() <= limit { + return Ok(self.swapped(Assets::new())) + } else { + let fungible = mem::replace(&mut self.fungible, Default::default()); + fungible.into_iter().for_each(|(c, amount)| { + if taken.len() < limit { + taken.fungible.insert(c, amount); + } else { + self.fungible.insert(c, amount); } - }, - MultiAsset::AllAbstractNonFungible { class } => { - for asset in self.non_fungible_assets_iter() { - match &asset { - MultiAsset::AbstractNonFungible { class: c, .. } => - if class == c { - result.saturating_subsume(asset) - }, - _ => (), + }); + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if taken.len() < limit { + taken.non_fungible.insert((c, instance)); + } else { + self.non_fungible.insert((c, instance)); } - } + }); }, - MultiAsset::AllConcreteFungible { id } => - for asset in self.fungible_assets_iter() { - match &asset { - MultiAsset::ConcreteFungible { id: identifier, .. } => { - if id == identifier { - result.saturating_subsume(asset) - } - }, - _ => (), - } - }, - MultiAsset::AllConcreteNonFungible { class } => { - for asset in self.non_fungible_assets_iter() { - match &asset { - MultiAsset::ConcreteNonFungible { class: c, .. } => - if class == c { - result.saturating_subsume(asset) + MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => { + if let Some((id, amount)) = self.fungible.remove_entry(&id) { + taken.fungible.insert(id, amount); + } + }, + MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if c == id && taken.len() < limit { + taken.non_fungible.insert((c, instance)); + } else { + self.non_fungible.insert((c, instance)); + } + }); + }, + MultiAssetFilter::Definite(assets) => { + if !saturate { + self.ensure_contains(&assets)?; + } + for asset in assets.drain().into_iter() { + match asset { + MultiAsset { fun: Fungible(amount), id } => { + let (remove, amount) = match self.fungible.get_mut(&id) { + Some(self_amount) => { + let amount = amount.min(*self_amount); + *self_amount -= amount; + (*self_amount == 0, amount) }, - _ => (), - } + None => (false, 0), + }; + if remove { + self.fungible.remove(&id); + } + if amount > 0 { + taken.subsume(MultiAsset::from((id, amount)).into()); + } + }, + MultiAsset { fun: NonFungible(instance), id } => { + let id_instance = (id, instance); + if self.non_fungible.remove(&id_instance) { + taken.subsume(id_instance.into()) + } + }, } - }, - x @ MultiAsset::ConcreteFungible { .. } | - x @ MultiAsset::AbstractFungible { .. } => { - let (id, amount) = match x { - MultiAsset::ConcreteFungible { id, amount } => - (AssetId::Concrete(id.clone()), *amount), - MultiAsset::AbstractFungible { id, amount } => - (AssetId::Abstract(id.clone()), *amount), - _ => unreachable!(), - }; - if let Some(v) = self.fungible.get(&id) { - result.saturating_subsume_fungible(id, amount.min(*v)); + if taken.len() == limit { + break } - }, - x @ MultiAsset::ConcreteNonFungible { .. } | - x @ MultiAsset::AbstractNonFungible { .. } => { - let (class, instance) = match x { - MultiAsset::ConcreteNonFungible { class, instance } => - (AssetId::Concrete(class.clone()), instance.clone()), - MultiAsset::AbstractNonFungible { class, instance } => - (AssetId::Abstract(class.clone()), instance.clone()), - _ => unreachable!(), - }; - let item = (class, instance); - if self.non_fungible.contains(&item) { - result.non_fungible.insert(item); + } + }, + } + Ok(taken) + } + + /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least `mask`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its value minus + /// `mask` if `self` contains `asset`, and return `Err` otherwise. + pub fn saturating_take(&mut self, asset: MultiAssetFilter) -> Assets { + self.general_take(asset, true, usize::max_value()) + .expect("general_take never results in error when saturating") + } + + /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least `mask`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its value minus + /// `mask` if `self` contains `asset`, and return `Err` otherwise. + pub fn limited_saturating_take(&mut self, asset: MultiAssetFilter, limit: usize) -> Assets { + self.general_take(asset, true, limit) + .expect("general_take never results in error when saturating") + } + + /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least `mask`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its value minus + /// `asset` if `self` contains `asset`, and return `Err` otherwise. + pub fn try_take(&mut self, mask: MultiAssetFilter) -> Result { + self.general_take(mask, false, usize::max_value()) + } + + /// Consumes `self` and returns its original value excluding `asset` iff it contains at least `asset`. + pub fn checked_sub(mut self, asset: MultiAsset) -> Result { + match asset.fun { + Fungible(amount) => { + let remove = if let Some(balance) = self.fungible.get_mut(&asset.id) { + if *balance >= amount { + *balance -= amount; + *balance == 0 + } else { + return Err(self) } + } else { + return Err(self) + }; + if remove { + self.fungible.remove(&asset.id); + } + Ok(self) + }, + NonFungible(instance) => + if self.non_fungible.remove(&(asset.id, instance)) { + Ok(self) + } else { + Err(self) }, - } } - result } - /// Take all possible assets up to `assets` from `self`, mutating `self` and returning the - /// assets taken. + /// Return the assets in `self`, but (asset-wise) of no greater value than `mask`. /// - /// Wildcards work. + /// Result is undefined if `mask` includes elements which match to the same asset more than once. /// /// Example: /// /// ``` /// use xcm_executor::Assets; - /// use xcm::v0::{MultiAsset, MultiLocation}; - /// let mut assets_i_have: Assets = vec![ - /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, - /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, - /// ].into(); - /// let assets_they_want = vec![ - /// MultiAsset::AllAbstractFungible { id: vec![0] }, - /// ]; + /// use xcm::latest::prelude::*; + /// let assets_i_have: Assets = vec![ (Here, 100).into(), (vec![0], 100).into() ].into(); + /// let assets_they_want: MultiAssetFilter = vec![ (Here, 200).into(), (vec![0], 50).into() ].into(); /// - /// let assets_they_took: Assets = assets_i_have.saturating_take(assets_they_want); - /// assert_eq!(assets_they_took.into_assets_iter().collect::>(), vec![ - /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, - /// ]); - /// assert_eq!(assets_i_have.into_assets_iter().collect::>(), vec![ - /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, + /// let assets_we_can_trade: Assets = assets_i_have.min(&assets_they_want); + /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ + /// (Here, 100).into(), (vec![0], 50).into(), /// ]); /// ``` - pub fn saturating_take(&mut self, assets: I) -> Assets - where - I: IntoIterator, - { - let mut result = Assets::default(); - for asset in assets.into_iter() { - match asset { - MultiAsset::None => (), - MultiAsset::All => return self.swapped(Assets::default()), - MultiAsset::AllFungible => { - // Remove all fungible assets, and copy them into `result`. - let fungible = mem::replace(&mut self.fungible, Default::default()); - fungible.into_iter().for_each(|(id, amount)| { - result.saturating_subsume_fungible(id, amount); - }) - }, - MultiAsset::AllNonFungible => { - // Remove all non-fungible assets, and copy them into `result`. - let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); - non_fungible.into_iter().for_each(|(class, instance)| { - result.saturating_subsume_non_fungible(class, instance); - }); - }, - x @ MultiAsset::AllAbstractFungible { .. } | - x @ MultiAsset::AllConcreteFungible { .. } => { - let id = match x { - MultiAsset::AllConcreteFungible { id } => AssetId::Concrete(id), - MultiAsset::AllAbstractFungible { id } => AssetId::Abstract(id), - _ => unreachable!(), - }; - // At the end of this block, we will be left with only the non-matching fungibles. - let mut non_matching_fungibles = BTreeMap::::new(); - let fungible = mem::replace(&mut self.fungible, Default::default()); - fungible.into_iter().for_each(|(iden, amount)| { - if iden == id { - result.saturating_subsume_fungible(iden, amount); - } else { - non_matching_fungibles.insert(iden, amount); - } - }); - self.fungible = non_matching_fungibles; - }, - x @ MultiAsset::AllAbstractNonFungible { .. } | - x @ MultiAsset::AllConcreteNonFungible { .. } => { - let class = match x { - MultiAsset::AllConcreteNonFungible { class } => AssetId::Concrete(class), - MultiAsset::AllAbstractNonFungible { class } => AssetId::Abstract(class), - _ => unreachable!(), - }; - // At the end of this block, we will be left with only the non-matching non-fungibles. - let mut non_matching_non_fungibles = - BTreeSet::<(AssetId, AssetInstance)>::new(); - let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); - non_fungible.into_iter().for_each(|(c, instance)| { - if class == c { - result.saturating_subsume_non_fungible(c, instance); - } else { - non_matching_non_fungibles.insert((c, instance)); - } - }); - self.non_fungible = non_matching_non_fungibles; - }, - x @ MultiAsset::ConcreteFungible { .. } | - x @ MultiAsset::AbstractFungible { .. } => { - let (id, amount) = match x { - MultiAsset::ConcreteFungible { id, amount } => - (AssetId::Concrete(id), amount), - MultiAsset::AbstractFungible { id, amount } => - (AssetId::Abstract(id), amount), - _ => unreachable!(), - }; - // remove the maxmimum possible up to id/amount from self, add the removed onto - // result - let maybe_value = self.fungible.get(&id); - if let Some(&e) = maybe_value { - if e > amount { - self.fungible.insert(id.clone(), e - amount); - result.saturating_subsume_fungible(id, amount); - } else { - self.fungible.remove(&id); - result.saturating_subsume_fungible(id, e.clone()); - } + pub fn min(&self, mask: &MultiAssetFilter) -> Assets { + let mut masked = Assets::new(); + match mask { + MultiAssetFilter::Wild(All) => return self.clone(), + MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => { + if let Some(&amount) = self.fungible.get(&id) { + masked.fungible.insert(id.clone(), amount); + } + }, + MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { + self.non_fungible.iter().for_each(|(ref c, ref instance)| { + if c == id { + masked.non_fungible.insert((c.clone(), instance.clone())); } - }, - x @ MultiAsset::ConcreteNonFungible { .. } | - x @ MultiAsset::AbstractNonFungible { .. } => { - let (class, instance) = match x { - MultiAsset::ConcreteNonFungible { class, instance } => - (AssetId::Concrete(class), instance), - MultiAsset::AbstractNonFungible { class, instance } => - (AssetId::Abstract(class), instance), - _ => unreachable!(), - }; - // remove the maxmimum possible up to id/amount from self, add the removed onto - // result - if let Some(entry) = self.non_fungible.take(&(class, instance)) { - result.non_fungible.insert(entry); + }); + }, + MultiAssetFilter::Definite(assets) => + for asset in assets.inner().iter() { + match asset { + MultiAsset { fun: Fungible(ref amount), ref id } => { + if let Some(m) = self.fungible.get(id) { + masked.subsume((id.clone(), Fungible(*amount.min(m))).into()); + } + }, + MultiAsset { fun: NonFungible(ref instance), ref id } => { + let id_instance = (id.clone(), instance.clone()); + if self.non_fungible.contains(&id_instance) { + masked.subsume(id_instance.into()); + } + }, } }, - } } - result - } - - /// Swaps two mutable Assets, without deinitializing either one. - pub fn swapped(&mut self, mut with: Assets) -> Self { - mem::swap(&mut *self, &mut with); - with + masked } } #[cfg(test)] mod tests { use super::*; + use xcm::latest::prelude::*; + use MultiLocation::Here; #[allow(non_snake_case)] + /// Abstract fungible constructor fn AF(id: u8, amount: u128) -> MultiAsset { - MultiAsset::AbstractFungible { id: vec![id], amount } + (vec![id], amount).into() } #[allow(non_snake_case)] - fn ANF(class: u8, instance_id: u128) -> MultiAsset { - MultiAsset::AbstractNonFungible { - class: vec![class], - instance: AssetInstance::Index { id: instance_id }, - } + /// Abstract non-fungible constructor + fn ANF(class: u8, instance_id: u8) -> MultiAsset { + (vec![class], vec![instance_id]).into() } #[allow(non_snake_case)] + /// Concrete fungible constructor fn CF(amount: u128) -> MultiAsset { - MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount } + (Here, amount).into() } #[allow(non_snake_case)] - fn CNF(instance_id: u128) -> MultiAsset { - MultiAsset::ConcreteNonFungible { - class: MultiLocation::Null, - instance: AssetInstance::Index { id: instance_id }, - } + /// Concrete non-fungible constructor + fn CNF(instance_id: u8) -> MultiAsset { + (Here, [instance_id; 4]).into() } fn test_assets() -> Assets { - let mut assets_vec: Vec = Vec::new(); - assets_vec.push(AF(1, 100)); - assets_vec.push(ANF(2, 200)); - assets_vec.push(CF(300)); - assets_vec.push(CNF(400)); - assets_vec.into() + let mut assets = Assets::new(); + assets.subsume(AF(1, 100)); + assets.subsume(ANF(2, 20)); + assets.subsume(CF(300)); + assets.subsume(CNF(40)); + assets + } + + #[test] + fn subsume_assets_works() { + let t1 = test_assets(); + let mut t2 = Assets::new(); + t2.subsume(AF(1, 50)); + t2.subsume(ANF(2, 10)); + t2.subsume(CF(300)); + t2.subsume(CNF(50)); + let mut r1 = t1.clone(); + r1.subsume_assets(t2.clone()); + let mut r2 = t1.clone(); + for a in t2.assets_iter() { + r2.subsume(a) + } + assert_eq!(r1, r2); + } + + #[test] + fn checked_sub_works() { + let t = test_assets(); + let t = t.checked_sub(AF(1, 50)).unwrap(); + let t = t.checked_sub(AF(1, 51)).unwrap_err(); + let t = t.checked_sub(AF(1, 50)).unwrap(); + let t = t.checked_sub(AF(1, 1)).unwrap_err(); + let t = t.checked_sub(CF(150)).unwrap(); + let t = t.checked_sub(CF(151)).unwrap_err(); + let t = t.checked_sub(CF(150)).unwrap(); + let t = t.checked_sub(CF(1)).unwrap_err(); + let t = t.checked_sub(ANF(2, 21)).unwrap_err(); + let t = t.checked_sub(ANF(2, 20)).unwrap(); + let t = t.checked_sub(ANF(2, 20)).unwrap_err(); + let t = t.checked_sub(CNF(41)).unwrap_err(); + let t = t.checked_sub(CNF(40)).unwrap(); + let t = t.checked_sub(CNF(40)).unwrap_err(); + assert_eq!(t, Assets::new()); } #[test] @@ -622,8 +505,8 @@ mod tests { // Order defined by implementation: CF, AF, CNF, ANF assert_eq!(Some(CF(300)), iter.next()); assert_eq!(Some(AF(1, 100)), iter.next()); - assert_eq!(Some(CNF(400)), iter.next()); - assert_eq!(Some(ANF(2, 200)), iter.next()); + assert_eq!(Some(CNF(40)), iter.next()); + assert_eq!(Some(ANF(2, 20)), iter.next()); assert_eq!(None, iter.next()); } @@ -631,14 +514,14 @@ mod tests { fn assets_into_works() { let mut assets_vec: Vec = Vec::new(); assets_vec.push(AF(1, 100)); - assets_vec.push(ANF(2, 200)); + assets_vec.push(ANF(2, 20)); assets_vec.push(CF(300)); - assets_vec.push(CNF(400)); + assets_vec.push(CNF(40)); // Push same group of tokens again assets_vec.push(AF(1, 100)); - assets_vec.push(ANF(2, 200)); + assets_vec.push(ANF(2, 20)); assets_vec.push(CF(300)); - assets_vec.push(CNF(400)); + assets_vec.push(CNF(40)); let assets: Assets = assets_vec.into(); let mut iter = assets.into_assets_iter(); @@ -646,169 +529,138 @@ mod tests { assert_eq!(Some(CF(600)), iter.next()); assert_eq!(Some(AF(1, 200)), iter.next()); // Non-fungibles collapse - assert_eq!(Some(CNF(400)), iter.next()); - assert_eq!(Some(ANF(2, 200)), iter.next()); + assert_eq!(Some(CNF(40)), iter.next()); + assert_eq!(Some(ANF(2, 20)), iter.next()); assert_eq!(None, iter.next()); } #[test] fn min_all_and_none_works() { let assets = test_assets(); - let none = vec![MultiAsset::None]; - let all = vec![MultiAsset::All]; + let none = MultiAssets::new().into(); + let all = All.into(); - let none_min = assets.min(none.iter()); + let none_min = assets.min(&none); assert_eq!(None, none_min.assets_iter().next()); - let all_min = assets.min(all.iter()); + let all_min = assets.min(&all); assert!(all_min.assets_iter().eq(assets.assets_iter())); } - #[test] - fn min_all_fungible_and_all_non_fungible_works() { - let assets = test_assets(); - let fungible = vec![MultiAsset::AllFungible]; - let non_fungible = vec![MultiAsset::AllNonFungible]; - - let fungible = assets.min(fungible.iter()); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![CF(300), AF(1, 100)]); - let non_fungible = assets.min(non_fungible.iter()); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![CNF(400), ANF(2, 200)]); - } - #[test] fn min_all_abstract_works() { let assets = test_assets(); - let fungible = vec![MultiAsset::AllAbstractFungible { id: vec![1] }]; - let non_fungible = vec![MultiAsset::AllAbstractNonFungible { class: vec![2] }]; + let fungible = Wild((vec![1], WildFungible).into()); + let non_fungible = Wild((vec![2], WildNonFungible).into()); - let fungible = assets.min(fungible.iter()); + let fungible = assets.min(&fungible); let fungible = fungible.assets_iter().collect::>(); assert_eq!(fungible, vec![AF(1, 100)]); - let non_fungible = assets.min(non_fungible.iter()); + let non_fungible = assets.min(&non_fungible); let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![ANF(2, 200)]); + assert_eq!(non_fungible, vec![ANF(2, 20)]); } #[test] fn min_all_concrete_works() { let assets = test_assets(); - let fungible = vec![MultiAsset::AllConcreteFungible { id: MultiLocation::Null }]; - let non_fungible = vec![MultiAsset::AllConcreteNonFungible { class: MultiLocation::Null }]; + let fungible = Wild((Here, WildFungible).into()); + let non_fungible = Wild((Here, WildNonFungible).into()); - let fungible = assets.min(fungible.iter()); + let fungible = assets.min(&fungible); let fungible = fungible.assets_iter().collect::>(); assert_eq!(fungible, vec![CF(300)]); - let non_fungible = assets.min(non_fungible.iter()); + let non_fungible = assets.min(&non_fungible); let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![CNF(400)]); + assert_eq!(non_fungible, vec![CNF(40)]); } #[test] fn min_basic_works() { let assets1 = test_assets(); - let mut assets2_vec: Vec = Vec::new(); + let mut assets2 = Assets::new(); // This is less than 100, so it will decrease to 50 - assets2_vec.push(AF(1, 50)); + assets2.subsume(AF(1, 50)); // This asset does not exist, so not included - assets2_vec.push(ANF(2, 400)); + assets2.subsume(ANF(2, 40)); // This is more then 300, so it should stay at 300 - assets2_vec.push(CF(600)); + assets2.subsume(CF(600)); // This asset should be included - assets2_vec.push(CNF(400)); - let assets2: Assets = assets2_vec.into(); + assets2.subsume(CNF(40)); + let assets2: MultiAssets = assets2.into(); - let assets_min = assets1.min(assets2.assets_iter()); + let assets_min = assets1.min(&assets2.into()); let assets_min = assets_min.into_assets_iter().collect::>(); - assert_eq!(assets_min, vec![CF(300), AF(1, 50), CNF(400)]); + assert_eq!(assets_min, vec![CF(300), AF(1, 50), CNF(40)]); } #[test] fn saturating_take_all_and_none_works() { let mut assets = test_assets(); - let none = vec![MultiAsset::None]; - let all = vec![MultiAsset::All]; - let taken_none = assets.saturating_take(none); + let taken_none = assets.saturating_take(vec![].into()); assert_eq!(None, taken_none.assets_iter().next()); - let taken_all = assets.saturating_take(all); + let taken_all = assets.saturating_take(All.into()); // Everything taken assert_eq!(None, assets.assets_iter().next()); let all_iter = taken_all.assets_iter(); assert!(all_iter.eq(test_assets().assets_iter())); } - #[test] - fn saturating_take_all_fungible_and_all_non_fungible_works() { - let mut assets = test_assets(); - let fungible = vec![MultiAsset::AllFungible]; - let non_fungible = vec![MultiAsset::AllNonFungible]; - - let fungible = assets.saturating_take(fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![CF(300), AF(1, 100)]); - let non_fungible = assets.saturating_take(non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, [CNF(400), ANF(2, 200)]); - // Assets completely drained - assert_eq!(None, assets.assets_iter().next()); - } - #[test] fn saturating_take_all_abstract_works() { let mut assets = test_assets(); - let fungible = vec![MultiAsset::AllAbstractFungible { id: vec![1] }]; - let non_fungible = vec![MultiAsset::AllAbstractNonFungible { class: vec![2] }]; + let fungible = Wild((vec![1], WildFungible).into()); + let non_fungible = Wild((vec![2], WildNonFungible).into()); let fungible = assets.saturating_take(fungible); let fungible = fungible.assets_iter().collect::>(); assert_eq!(fungible, vec![AF(1, 100)]); let non_fungible = assets.saturating_take(non_fungible); let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![ANF(2, 200)]); + assert_eq!(non_fungible, vec![ANF(2, 20)]); // Assets drained of abstract let final_assets = assets.assets_iter().collect::>(); - assert_eq!(final_assets, vec![CF(300), CNF(400)]); + assert_eq!(final_assets, vec![CF(300), CNF(40)]); } #[test] fn saturating_take_all_concrete_works() { let mut assets = test_assets(); - let fungible = vec![MultiAsset::AllConcreteFungible { id: MultiLocation::Null }]; - let non_fungible = vec![MultiAsset::AllConcreteNonFungible { class: MultiLocation::Null }]; + let fungible = Wild((Here, WildFungible).into()); + let non_fungible = Wild((Here, WildNonFungible).into()); let fungible = assets.saturating_take(fungible); let fungible = fungible.assets_iter().collect::>(); assert_eq!(fungible, vec![CF(300)]); let non_fungible = assets.saturating_take(non_fungible); let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![CNF(400)]); + assert_eq!(non_fungible, vec![CNF(40)]); // Assets drained of concrete let assets = assets.assets_iter().collect::>(); - assert_eq!(assets, vec![AF(1, 100), ANF(2, 200)]); + assert_eq!(assets, vec![AF(1, 100), ANF(2, 20)]); } #[test] fn saturating_take_basic_works() { let mut assets1 = test_assets(); - let mut assets2_vec: Vec = Vec::new(); + let mut assets2 = Assets::new(); // We should take 50 - assets2_vec.push(AF(1, 50)); + assets2.subsume(AF(1, 50)); // This asset should not be taken - assets2_vec.push(ANF(2, 400)); + assets2.subsume(ANF(2, 40)); // This is more then 300, so it takes everything - assets2_vec.push(CF(600)); + assets2.subsume(CF(600)); // This asset should be taken - assets2_vec.push(CNF(400)); + assets2.subsume(CNF(40)); + let assets2: MultiAssets = assets2.into(); - let taken = assets1.saturating_take(assets2_vec); + let taken = assets1.saturating_take(assets2.into()); let taken = taken.into_assets_iter().collect::>(); - assert_eq!(taken, vec![CF(300), AF(1, 50), CNF(400)]); + assert_eq!(taken, vec![CF(300), AF(1, 50), CNF(40)]); let assets = assets1.into_assets_iter().collect::>(); - assert_eq!(assets, vec![AF(1, 50), ANF(2, 200)]); + assert_eq!(assets, vec![AF(1, 50), ANF(2, 20)]); } } diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index ff39be56ef5e..7f4bf08f5fa0 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -22,7 +22,7 @@ use frame_support::{ dispatch::{Dispatchable, Parameter}, weights::{GetDispatchInfo, PostDispatchInfo}, }; -use xcm::v0::SendXcm; +use xcm::latest::SendXcm; /// The trait to parameterize the `XcmExecutor`. pub trait Config { diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index bb0b8eb604a8..102f241fbc4e 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -22,8 +22,8 @@ use frame_support::{ weights::GetDispatchInfo, }; use sp_std::{marker::PhantomData, prelude::*}; -use xcm::v0::{ - Error as XcmError, ExecuteXcm, MultiAsset, MultiLocation, Order, Outcome, Response, SendXcm, +use xcm::latest::{ + Error as XcmError, ExecuteXcm, MultiAssets, MultiLocation, Order, Outcome, Response, SendXcm, Xcm, }; @@ -34,7 +34,7 @@ use traits::{ }; mod assets; -pub use assets::{AssetId, Assets}; +pub use assets::Assets; mod config; pub use config::Config; @@ -94,10 +94,10 @@ impl ExecuteXcm for XcmExecutor { } impl XcmExecutor { - fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec { + fn reanchored(mut assets: Assets, dest: &MultiLocation) -> MultiAssets { let inv_dest = Config::LocationInverter::invert_location(&dest); assets.prepend_location(&inv_dest); - assets.into_assets_iter().collect::>() + assets.into_assets_iter().collect::>().into() } /// Execute the XCM and return the portion of weight of `shallow_weight + deep_weight` that `message` did not use. @@ -144,17 +144,15 @@ impl XcmExecutor { (origin, Xcm::WithdrawAsset { assets, effects }) => { // Take `assets` from the origin account (on-chain) and place in holding. let mut holding = Assets::default(); - for asset in assets { - ensure!(!asset.is_wildcard(), XcmError::Wildcard); + for asset in assets.inner() { let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?; - holding.saturating_subsume_all(withdrawn); + holding.subsume_assets(withdrawn); } Some((holding, effects)) }, - (origin, Xcm::ReserveAssetDeposit { assets, effects }) => { + (origin, Xcm::ReserveAssetDeposited { assets, effects }) => { // check whether we trust origin to be our reserve location for this asset. - for asset in assets.iter() { - ensure!(!asset.is_wildcard(), XcmError::Wildcard); + for asset in assets.inner() { // We only trust the origin to send us assets that they identify as their // sovereign assets. ensure!( @@ -162,31 +160,28 @@ impl XcmExecutor { XcmError::UntrustedReserveLocation ); } - Some((Assets::from(assets), effects)) + Some((assets.into(), effects)) }, - (origin, Xcm::TransferAsset { assets, dest }) => { + (origin, Xcm::TransferAsset { assets, beneficiary }) => { // Take `assets` from the origin account (on-chain) and place into dest account. - for asset in assets { - ensure!(!asset.is_wildcard(), XcmError::Wildcard); - Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?; + for asset in assets.inner() { + Config::AssetTransactor::beam_asset(&asset, &origin, &beneficiary)?; } None }, (origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => { // Take `assets` from the origin account (on-chain) and place into dest account. let inv_dest = Config::LocationInverter::invert_location(&dest); - for asset in assets.iter_mut() { - ensure!(!asset.is_wildcard(), XcmError::Wildcard); - Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?; - asset.reanchor(&inv_dest)?; + for asset in assets.inner() { + Config::AssetTransactor::beam_asset(asset, &origin, &dest)?; } - Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?; + assets.reanchor(&inv_dest)?; + Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?; None }, - (origin, Xcm::TeleportAsset { assets, effects }) => { + (origin, Xcm::ReceiveTeleportedAsset { assets, effects }) => { // check whether we trust origin to teleport this asset to us via config trait. - for asset in assets.iter() { - ensure!(!asset.is_wildcard(), XcmError::Wildcard); + for asset in assets.inner() { // We only trust the origin to send us assets that they identify as their // sovereign assets. ensure!( @@ -198,7 +193,7 @@ impl XcmExecutor { // don't want to punish a possibly innocent chain/user). Config::AssetTransactor::can_check_in(&origin, asset)?; } - for asset in assets.iter() { + for asset in assets.inner() { Config::AssetTransactor::check_in(&origin, asset); } Some((Assets::from(assets), effects)) @@ -252,41 +247,41 @@ impl XcmExecutor { if let Some((mut holding, effects)) = maybe_holding_effects { for effect in effects.into_iter() { - total_surplus += Self::execute_effects(&origin, &mut holding, effect, trader)?; + total_surplus += Self::execute_orders(&origin, &mut holding, effect, trader)?; } } Ok(total_surplus) } - fn execute_effects( + fn execute_orders( origin: &MultiLocation, holding: &mut Assets, - effect: Order, + order: Order, trader: &mut Config::Trader, ) -> Result { log::trace!( - target: "xcm::execute_effects", + target: "xcm::execute_orders", "origin: {:?}, holding: {:?}, effect: {:?}", origin, holding, - effect, + order, ); let mut total_surplus = 0; - match effect { - Order::DepositAsset { assets, dest } => { - let deposited = holding.saturating_take(assets); + match order { + Order::DepositAsset { assets, max_assets, beneficiary } => { + let deposited = holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.into_assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &dest)?; + Config::AssetTransactor::deposit_asset(&asset, &beneficiary)?; } }, - Order::DepositReserveAsset { assets, dest, effects } => { - let deposited = holding.saturating_take(assets); + Order::DepositReserveAsset { assets, max_assets, dest, effects } => { + let deposited = holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &dest)?; } let assets = Self::reanchored(deposited, &dest); - Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?; + Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?; }, Order::InitiateReserveWithdraw { assets, reserve, effects } => { let assets = Self::reanchored(holding.saturating_take(assets), &reserve); @@ -299,29 +294,37 @@ impl XcmExecutor { Config::AssetTransactor::check_out(&origin, &asset); } let assets = Self::reanchored(assets, &dest); - Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects })?; + Config::XcmSender::send_xcm(dest, Xcm::ReceiveTeleportedAsset { assets, effects })?; }, Order::QueryHolding { query_id, dest, assets } => { - let assets = Self::reanchored(holding.min(assets.iter()), &dest); + let assets = Self::reanchored(holding.min(&assets), &dest); Config::XcmSender::send_xcm( dest, Xcm::QueryResponse { query_id, response: Response::Assets(assets) }, )?; }, - Order::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { - // pay for `weight` using up to `fees` of the holding account. + Order::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { + // pay for `weight` using up to `fees` of the holding register. let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?); - let max_fee = holding.try_take(fees).map_err(|()| XcmError::NotHoldingFees)?; + let max_fee = + holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; let unspent = trader.buy_weight(purchasing_weight, max_fee)?; - holding.saturating_subsume_all(unspent); + holding.subsume_assets(unspent); let mut remaining_weight = weight; - for message in xcm.into_iter() { + for order in orders.into_iter() { + match Self::execute_orders(origin, holding, order, trader) { + Err(e) if halt_on_error => return Err(e), + Err(_) => {}, + Ok(surplus) => total_surplus += surplus, + } + } + for instruction in instructions.into_iter() { match Self::do_execute_xcm( origin.clone(), false, - message, + instruction, &mut remaining_weight, None, trader, @@ -331,7 +334,9 @@ impl XcmExecutor { Ok(surplus) => total_surplus += surplus, } } - holding.saturating_subsume(trader.refund_weight(remaining_weight)); + if let Some(w) = trader.refund_weight(remaining_weight) { + holding.subsume(w); + } }, _ => return Err(XcmError::UnhandledEffect)?, } diff --git a/xcm/xcm-executor/src/traits/conversion.rs b/xcm/xcm-executor/src/traits/conversion.rs index 49f5db698982..4342fc5002e5 100644 --- a/xcm/xcm-executor/src/traits/conversion.rs +++ b/xcm/xcm-executor/src/traits/conversion.rs @@ -16,7 +16,7 @@ use parity_scale_codec::{Decode, Encode}; use sp_std::{borrow::Borrow, convert::TryFrom, prelude::*, result::Result}; -use xcm::v0::{MultiLocation, OriginKind}; +use xcm::latest::{MultiLocation, OriginKind}; /// Generic third-party conversion trait. Use this when you don't want to force the user to use default /// implementations of `From` and `Into` for the types you wish to convert between. @@ -139,7 +139,7 @@ impl Convert, T> for Decoded { /// which is passed to the next convert item. /// /// ```rust -/// # use xcm::v0::{MultiLocation, Junction, OriginKind}; +/// # use xcm::latest::{MultiLocation, Junction, OriginKind}; /// # use xcm_executor::traits::ConvertOrigin; /// // A convertor that will bump the para id and pass it to the next one. /// struct BumpParaId; diff --git a/xcm/xcm-executor/src/traits/filter_asset_location.rs b/xcm/xcm-executor/src/traits/filter_asset_location.rs index 8b1a7bd1d1dc..31b9c47a828c 100644 --- a/xcm/xcm-executor/src/traits/filter_asset_location.rs +++ b/xcm/xcm-executor/src/traits/filter_asset_location.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use xcm::v0::{MultiAsset, MultiLocation}; +use xcm::latest::{MultiAsset, MultiLocation}; /// Filters assets/location pairs. /// diff --git a/xcm/xcm-executor/src/traits/matches_fungible.rs b/xcm/xcm-executor/src/traits/matches_fungible.rs index 6634d16d0243..4989f263a63d 100644 --- a/xcm/xcm-executor/src/traits/matches_fungible.rs +++ b/xcm/xcm-executor/src/traits/matches_fungible.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use xcm::v0::MultiAsset; +use xcm::latest::MultiAsset; pub trait MatchesFungible { fn matches_fungible(a: &MultiAsset) -> Option; diff --git a/xcm/xcm-executor/src/traits/matches_fungibles.rs b/xcm/xcm-executor/src/traits/matches_fungibles.rs index c1722d000f02..f5baafdcd97a 100644 --- a/xcm/xcm-executor/src/traits/matches_fungibles.rs +++ b/xcm/xcm-executor/src/traits/matches_fungibles.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use sp_std::result; -use xcm::v0::{Error as XcmError, MultiAsset}; +use xcm::latest::{Error as XcmError, MultiAsset}; /// Errors associated with [`MatchesFungibles`] operation. pub enum Error { diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index 134891b4b704..8af238e7cef2 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use frame_support::weights::Weight; -use xcm::v0::{MultiLocation, Response}; +use xcm::latest::{MultiLocation, Response}; /// Define what needs to be done upon receiving a query response. pub trait OnResponse { diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs index ed92866146c1..a6f7c0207b1a 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -16,7 +16,7 @@ use frame_support::weights::Weight; use sp_std::result::Result; -use xcm::v0::{MultiLocation, Xcm}; +use xcm::latest::{MultiLocation, Xcm}; /// Trait to determine whether the execution engine should actually execute a given XCM. /// diff --git a/xcm/xcm-executor/src/traits/transact_asset.rs b/xcm/xcm-executor/src/traits/transact_asset.rs index e228e957e649..e261d9c9c963 100644 --- a/xcm/xcm-executor/src/traits/transact_asset.rs +++ b/xcm/xcm-executor/src/traits/transact_asset.rs @@ -16,7 +16,7 @@ use crate::Assets; use sp_std::result::Result; -use xcm::v0::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmResult}; +use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmResult}; /// Facility for asset transacting. /// @@ -89,7 +89,7 @@ pub trait TransactAsset { /// Move an `asset` `from` one location in `to` another location. /// /// Attempts to use `transfer_asset` and if not available then falls back to using a two-part withdraw/deposit. - fn teleport_asset( + fn beam_asset( asset: &MultiAsset, from: &MultiLocation, to: &MultiLocation, @@ -111,7 +111,7 @@ impl TransactAsset for Tuple { fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> XcmResult { for_tuples!( #( match Tuple::can_check_in(origin, what) { - Err(XcmError::AssetNotFound | XcmError::Unimplemented) => (), + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); @@ -139,7 +139,7 @@ impl TransactAsset for Tuple { fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult { for_tuples!( #( match Tuple::deposit_asset(what, who) { - Err(XcmError::AssetNotFound | XcmError::Unimplemented) => (), + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); @@ -155,7 +155,7 @@ impl TransactAsset for Tuple { fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result { for_tuples!( #( match Tuple::withdraw_asset(what, who) { - Err(XcmError::AssetNotFound | XcmError::Unimplemented) => (), + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); @@ -175,7 +175,7 @@ impl TransactAsset for Tuple { ) -> Result { for_tuples!( #( match Tuple::transfer_asset(what, from, to) { - Err(XcmError::AssetNotFound | XcmError::Unimplemented) => (), + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); @@ -193,6 +193,7 @@ impl TransactAsset for Tuple { #[cfg(test)] mod tests { use super::*; + use MultiLocation::Here; pub struct UnimplementedTransactor; impl TransactAsset for UnimplementedTransactor {} @@ -272,7 +273,7 @@ mod tests { (UnimplementedTransactor, NotFoundTransactor, UnimplementedTransactor); assert_eq!( - MultiTransactor::deposit_asset(&MultiAsset::All, &MultiLocation::Null), + MultiTransactor::deposit_asset(&(Here, 1).into(), &Here), Err(XcmError::AssetNotFound) ); } @@ -281,7 +282,7 @@ mod tests { fn unimplemented_and_not_found_continue_iteration() { type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, SuccessfulTransactor); - assert_eq!(MultiTransactor::deposit_asset(&MultiAsset::All, &MultiLocation::Null), Ok(())); + assert_eq!(MultiTransactor::deposit_asset(&(Here, 1).into(), &Here), Ok(())); } #[test] @@ -289,7 +290,7 @@ mod tests { type MultiTransactor = (OverflowTransactor, SuccessfulTransactor); assert_eq!( - MultiTransactor::deposit_asset(&MultiAsset::All, &MultiLocation::Null), + MultiTransactor::deposit_asset(&(Here, 1).into(), &Here), Err(XcmError::Overflow) ); } @@ -298,6 +299,6 @@ mod tests { fn success_stops_iteration() { type MultiTransactor = (SuccessfulTransactor, OverflowTransactor); - assert_eq!(MultiTransactor::deposit_asset(&MultiAsset::All, &MultiLocation::Null), Ok(())); + assert_eq!(MultiTransactor::deposit_asset(&(Here, 1).into(), &Here), Ok(())); } } diff --git a/xcm/xcm-executor/src/traits/weight.rs b/xcm/xcm-executor/src/traits/weight.rs index 824383da508e..8c9e6ec6366d 100644 --- a/xcm/xcm-executor/src/traits/weight.rs +++ b/xcm/xcm-executor/src/traits/weight.rs @@ -17,7 +17,7 @@ use crate::Assets; use frame_support::weights::Weight; use sp_std::result::Result; -use xcm::v0::{Error, MultiAsset, MultiLocation, Xcm}; +use xcm::latest::{Error, MultiAsset, MultiLocation, Xcm}; /// Determine the weight of an XCM message. pub trait WeightBounds { @@ -71,8 +71,8 @@ pub trait WeightTrader: Sized { /// purchased using `buy_weight`. /// /// Default implementation refunds nothing. - fn refund_weight(&mut self, _weight: Weight) -> MultiAsset { - MultiAsset::None + fn refund_weight(&mut self, _weight: Weight) -> Option { + None } } diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index f318409bf187..2426b83819bb 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -17,10 +17,9 @@ mod parachain; mod relay_chain; -use sp_runtime::AccountId32; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; -pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); +pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); decl_test_parachain! { pub struct ParaA { @@ -100,13 +99,7 @@ mod tests { use codec::Encode; use frame_support::assert_ok; - use xcm::v0::{ - Junction::{self, Parachain, Parent}, - MultiAsset::*, - MultiLocation::*, - NetworkId, OriginKind, - Xcm::*, - }; + use xcm::latest::prelude::*; use xcm_simulator::TestExt; #[test] @@ -118,7 +111,7 @@ mod tests { ); Relay::execute_with(|| { assert_ok!(RelayChainPalletXcm::send_xcm( - Null, + Here, X1(Parachain(1)), Transact { origin_type: OriginKind::SovereignAccount, @@ -145,7 +138,7 @@ mod tests { ); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm( - Null, + Here, X1(Parent), Transact { origin_type: OriginKind::SovereignAccount, @@ -172,7 +165,7 @@ mod tests { ); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm( - Null, + Here, X2(Parent, Parachain(2)), Transact { origin_type: OriginKind::SovereignAccount, @@ -198,9 +191,10 @@ mod tests { assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( relay_chain::Origin::signed(ALICE), X1(Parachain(1)), - X1(Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }), - vec![ConcreteFungible { id: Null, amount: 123 }], - 123, + X1(AccountId32 { network: Any, id: ALICE.into() }), + (Here, 123).into(), + 0, + 3, )); }); diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index 30aa5ba21e2e..bf2eff15c27c 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -35,19 +35,10 @@ use polkadot_core_primitives::BlockNumber as RelayBlockNumber; use polkadot_parachain::primitives::{ DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, }; -use xcm::{ - v0::{ - Error as XcmError, ExecuteXcm, - Junction::{Parachain, Parent}, - MultiAsset, - MultiLocation::{self, X1}, - NetworkId, Outcome, Xcm, - }, - VersionedXcm, -}; +use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfConcreteFungible, FixedWeightBounds, IsConcrete, LocationInverter, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsDefault, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; @@ -129,7 +120,7 @@ pub type XcmOriginToCallOrigin = ( parameter_types! { pub const UnitWeightCost: Weight = 1; - pub KsmPerSecond: (MultiLocation, u128) = (X1(Parent), 1); + pub KsmPerSecond: (AssetId, u128) = (Concrete(X1(Parent)), 1); } pub type LocalAssetTransactor = @@ -149,7 +140,7 @@ impl Config for XcmConfig { type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfConcreteFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); } diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index 3b7b2f2d4815..b89bc2abca01 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -26,13 +26,12 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared, ump}; -use xcm::v0::{MultiAsset, MultiLocation, NetworkId}; +use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfConcreteFungible, FixedWeightBounds, - IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -92,10 +91,10 @@ impl shared::Config for Runtime {} impl configuration::Config for Runtime {} parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::Null; + pub const KsmLocation: MultiLocation = MultiLocation::Here; pub const KusamaNetwork: NetworkId = NetworkId::Kusama; pub const AnyNetwork: NetworkId = NetworkId::Any; - pub Ancestry: MultiLocation = MultiLocation::Null; + pub Ancestry: MultiLocation = MultiLocation::Here; pub UnitWeightCost: Weight = 1_000; } @@ -114,7 +113,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = 1_000; - pub KsmPerSecond: (MultiLocation, u128) = (KsmLocation::get(), 1); + pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); } pub type XcmRouter = super::RelayChainXcmRouter; @@ -131,7 +130,7 @@ impl Config for XcmConfig { type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfConcreteFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); } diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs index d68072858e80..7a3ad25d1555 100644 --- a/xcm/xcm-simulator/src/lib.rs +++ b/xcm/xcm-simulator/src/lib.rs @@ -32,7 +32,7 @@ pub use polkadot_runtime_parachains::{ dmp, ump::{self, MessageId, UmpSink, XcmSink}, }; -pub use xcm::{v0::prelude::*, VersionedXcm}; +pub use xcm::{latest::prelude::*, VersionedXcm}; pub use xcm_executor::XcmExecutor; pub trait TestExt {