From b38e814cd434eb8a0d1fb0fd63d8626be137f793 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 15:33:24 +0100 Subject: [PATCH 01/11] Start with this --- traits/src/location.rs | 18 ++++++++++-------- xcm-support/src/lib.rs | 9 ++++++--- xtokens/src/lib.rs | 6 ++++-- xtokens/src/mock/para.rs | 3 ++- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/traits/src/location.rs b/traits/src/location.rs index b9a1e0072..c7d7dab96 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -42,12 +42,14 @@ impl Parse for MultiLocation { pub trait Reserve { /// Returns assets reserve location. - fn reserve(&self) -> Option; + fn reserve(asset: MultiAsset) -> Option; } -impl Reserve for MultiAsset { - fn reserve(&self) -> Option { - if let Concrete(location) = &self.id { +pub struct AbsoluteReserveProvider; + +impl Reserve for AbsoluteReserveProvider { + fn reserve(asset: MultiAsset) -> Option { + if let Concrete(location) = &asset.id { location.chain_part() } else { None @@ -79,7 +81,7 @@ mod tests { #[test] fn parent_as_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX))).reserve(), + AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), Some(MultiLocation::parent()) ); } @@ -87,7 +89,7 @@ mod tests { #[test] fn sibling_parachain_as_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX))).reserve(), + AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), Some(MultiLocation::new(1, X1(PARACHAIN))) ); } @@ -95,7 +97,7 @@ mod tests { #[test] fn child_parachain_as_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX))).reserve(), + AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX)))), Some(PARACHAIN.into()) ); } @@ -103,7 +105,7 @@ mod tests { #[test] fn no_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into())))).reserve(), + AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into()))))), None ); } diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index cf71eaa39..6704a43f2 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -44,10 +44,13 @@ where /// A `FilterAssetLocation` implementation. Filters multi native assets whose /// reserve is same with `origin`. -pub struct MultiNativeAsset; -impl FilterAssetLocation for MultiNativeAsset { +pub struct MultiNativeAsset(PhantomData); +impl FilterAssetLocation for MultiNativeAsset +where + ReserveProvider: Reserve, +{ fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - if let Some(ref reserve) = asset.reserve() { + if let Some(ref reserve) = ReserveProvider::reserve(asset.clone()) { if reserve == origin { return true; } diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 5a6fd3810..a0a5d7f85 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -102,6 +102,8 @@ pub mod module { /// The maximum number of distinct assets allowed to be transferred in a /// single helper extrinsic. type MaxAssetsForTransfer: Get; + + type ReserveProvider: Reserve; } #[pallet::event] @@ -476,7 +478,7 @@ pub mod module { Error::::InvalidAsset ); ensure!( - fee.reserve() == asset.reserve(), + T::ReserveProvider::reserve(fee.clone()) == T::ReserveProvider::reserve(asset.clone()), Error::::DistinctReserveForAssetAndFee ); } @@ -654,7 +656,7 @@ pub mod module { let self_location = T::SelfLocation::get(); ensure!(dest != self_location, Error::::NotCrossChainTransfer); - let reserve = asset.reserve().ok_or(Error::::AssetHasNoReserve)?; + let reserve = T::ReserveProvider::reserve(asset.clone()).ok_or(Error::::AssetHasNoReserve)?; let transfer_kind = if reserve == self_location { SelfReserveAsset } else if reserve == dest { diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index ef46b3de3..4181a563d 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -193,7 +193,7 @@ impl Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = MultiNativeAsset; + type IsReserve = MultiNativeAsset; type IsTeleporter = (); type LocationInverter = LocationInverter; type Barrier = Barrier; @@ -284,6 +284,7 @@ impl orml_xtokens::Config for Runtime { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type ReserveProvider = orml_traits::location::AbsoluteReserveProvider; } impl orml_xcm::Config for Runtime { From 465bdc755788624b57530f98701725029df44cc8 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 16:17:31 +0100 Subject: [PATCH 02/11] Add configurable reserve provider in xtokens --- traits/src/location.rs | 13 +++++++------ xcm-support/src/lib.rs | 2 +- xtokens/src/lib.rs | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/traits/src/location.rs b/traits/src/location.rs index c7d7dab96..94e1c7f61 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -42,13 +42,14 @@ impl Parse for MultiLocation { pub trait Reserve { /// Returns assets reserve location. - fn reserve(asset: MultiAsset) -> Option; + fn reserve(asset: &MultiAsset) -> Option; } +// Provide reserve in absolute path view pub struct AbsoluteReserveProvider; impl Reserve for AbsoluteReserveProvider { - fn reserve(asset: MultiAsset) -> Option { + fn reserve(asset: &MultiAsset) -> Option { if let Concrete(location) = &asset.id { location.chain_part() } else { @@ -81,7 +82,7 @@ mod tests { #[test] fn parent_as_reserve_chain() { assert_eq!( - AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), + AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), Some(MultiLocation::parent()) ); } @@ -89,7 +90,7 @@ mod tests { #[test] fn sibling_parachain_as_reserve_chain() { assert_eq!( - AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), + AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), Some(MultiLocation::new(1, X1(PARACHAIN))) ); } @@ -97,7 +98,7 @@ mod tests { #[test] fn child_parachain_as_reserve_chain() { assert_eq!( - AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX)))), + AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX)))), Some(PARACHAIN.into()) ); } @@ -105,7 +106,7 @@ mod tests { #[test] fn no_reserve_chain() { assert_eq!( - AbsoluteReserveProvider::reserve(concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into()))))), + AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into()))))), None ); } diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index 6704a43f2..5e801db17 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -50,7 +50,7 @@ where ReserveProvider: Reserve, { fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - if let Some(ref reserve) = ReserveProvider::reserve(asset.clone()) { + if let Some(ref reserve) = ReserveProvider::reserve(asset) { if reserve == origin { return true; } diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index a0a5d7f85..1492574e3 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -478,7 +478,7 @@ pub mod module { Error::::InvalidAsset ); ensure!( - T::ReserveProvider::reserve(fee.clone()) == T::ReserveProvider::reserve(asset.clone()), + T::ReserveProvider::reserve(&fee) == T::ReserveProvider::reserve(&asset), Error::::DistinctReserveForAssetAndFee ); } @@ -656,7 +656,7 @@ pub mod module { let self_location = T::SelfLocation::get(); ensure!(dest != self_location, Error::::NotCrossChainTransfer); - let reserve = T::ReserveProvider::reserve(asset.clone()).ok_or(Error::::AssetHasNoReserve)?; + let reserve = T::ReserveProvider::reserve(&asset).ok_or(Error::::AssetHasNoReserve)?; let transfer_kind = if reserve == self_location { SelfReserveAsset } else if reserve == dest { From 8eef4552c6cf8f147b50c9150f4a087bc08df575 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 16:26:19 +0100 Subject: [PATCH 03/11] Comment new associated type plus cosmetic changes --- xtokens/src/lib.rs | 2 ++ xtokens/src/mock/para.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 1492574e3..6ae7daedd 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -103,6 +103,8 @@ pub mod module { /// single helper extrinsic. type MaxAssetsForTransfer: Get; + /// The way to retreave the reserve of a MultiAsset. This can be configured + /// to accept absolute or relative paths for self tokens type ReserveProvider: Reserve; } diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 4181a563d..35f48fc1a 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -25,7 +25,7 @@ use xcm_builder::{ }; use xcm_executor::{traits::WeightTrader, Assets, Config, XcmExecutor}; -use orml_traits::parameter_type_with_key; +use orml_traits::{parameter_type_with_key, location::AbsoluteReserveProvider}; use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; pub type AccountId = AccountId32; @@ -193,7 +193,7 @@ impl Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = MultiNativeAsset; + type IsReserve = MultiNativeAsset; type IsTeleporter = (); type LocationInverter = LocationInverter; type Barrier = Barrier; @@ -284,7 +284,7 @@ impl orml_xtokens::Config for Runtime { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; - type ReserveProvider = orml_traits::location::AbsoluteReserveProvider; + type ReserveProvider = AbsoluteReserveProvider; } impl orml_xcm::Config for Runtime { From e83ee5ca2b63fe914b7d37c4df30ead1121d5399 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 16:35:11 +0100 Subject: [PATCH 04/11] FMT --- xtokens/src/lib.rs | 4 ++-- xtokens/src/mock/para.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 6ae7daedd..5432bc934 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -103,8 +103,8 @@ pub mod module { /// single helper extrinsic. type MaxAssetsForTransfer: Get; - /// The way to retreave the reserve of a MultiAsset. This can be configured - /// to accept absolute or relative paths for self tokens + /// The way to retreave the reserve of a MultiAsset. This can be + /// configured to accept absolute or relative paths for self tokens type ReserveProvider: Reserve; } diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 35f48fc1a..73b80076f 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -25,7 +25,7 @@ use xcm_builder::{ }; use xcm_executor::{traits::WeightTrader, Assets, Config, XcmExecutor}; -use orml_traits::{parameter_type_with_key, location::AbsoluteReserveProvider}; +use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; pub type AccountId = AccountId32; From 98bda636ba242cfbea464c0b74cc3c9861dca10f Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 16:47:50 +0100 Subject: [PATCH 05/11] Clippy --- xtokens/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 5432bc934..77b19d4f2 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -480,7 +480,7 @@ pub mod module { Error::::InvalidAsset ); ensure!( - T::ReserveProvider::reserve(&fee) == T::ReserveProvider::reserve(&asset), + T::ReserveProvider::reserve(fee) == T::ReserveProvider::reserve(asset), Error::::DistinctReserveForAssetAndFee ); } @@ -658,7 +658,7 @@ pub mod module { let self_location = T::SelfLocation::get(); ensure!(dest != self_location, Error::::NotCrossChainTransfer); - let reserve = T::ReserveProvider::reserve(&asset).ok_or(Error::::AssetHasNoReserve)?; + let reserve = T::ReserveProvider::reserve(asset).ok_or(Error::::AssetHasNoReserve)?; let transfer_kind = if reserve == self_location { SelfReserveAsset } else if reserve == dest { From d2c0d00bb4d52942f18a94e289bb12521e3662b2 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 17:57:33 +0100 Subject: [PATCH 06/11] fix tests xcm-supports --- xcm-support/src/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xcm-support/src/tests.rs b/xcm-support/src/tests.rs index c47469c41..43ac2fe04 100644 --- a/xcm-support/src/tests.rs +++ b/xcm-support/src/tests.rs @@ -4,7 +4,7 @@ use super::*; -use orml_traits::{location::RelativeLocations, ConcreteFungibleAsset}; +use orml_traits::{location::AbsoluteReserveProvider, location::RelativeLocations, ConcreteFungibleAsset}; #[derive(Debug, PartialEq, Eq)] pub enum TestCurrencyId { @@ -82,18 +82,18 @@ fn is_native_concrete_does_not_matches_non_native_currencies() { #[test] fn multi_native_asset() { - assert!(MultiNativeAsset::filter_asset_location( + assert!(MultiNativeAsset::::filter_asset_location( &MultiAsset { fun: Fungible(10), id: Concrete(MultiLocation::parent()) }, &Parent.into() )); - assert!(MultiNativeAsset::filter_asset_location( + assert!(MultiNativeAsset::::filter_asset_location( &MultiAsset::sibling_parachain_asset(1, "TokenA".into(), 100), &MultiLocation::new(1, X1(Parachain(1))), )); - assert!(!MultiNativeAsset::filter_asset_location( + assert!(!MultiNativeAsset::::filter_asset_location( &MultiAsset::sibling_parachain_asset(1, "TokenA".into(), 100), &MultiLocation::parent(), )); From 911672b6fa4a8763e212fef17d257f714b83b552 Mon Sep 17 00:00:00 2001 From: gorka Date: Tue, 1 Mar 2022 18:02:23 +0100 Subject: [PATCH 07/11] Clippy --- xtokens/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 77b19d4f2..a130f48ca 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -480,7 +480,7 @@ pub mod module { Error::::InvalidAsset ); ensure!( - T::ReserveProvider::reserve(fee) == T::ReserveProvider::reserve(asset), + T::ReserveProvider::reserve(&fee) == T::ReserveProvider::reserve(asset), Error::::DistinctReserveForAssetAndFee ); } From 69847e7f11c785f3c70c6d41fb86d8dec8c3ba2a Mon Sep 17 00:00:00 2001 From: gorka Date: Wed, 2 Mar 2022 11:31:55 +0100 Subject: [PATCH 08/11] Test relative view runtime --- traits/src/location.rs | 42 ++- xtokens/src/mock/mod.rs | 23 ++ xtokens/src/mock/para_relative_view.rs | 381 +++++++++++++++++++++++++ xtokens/src/tests.rs | 113 ++++++++ 4 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 xtokens/src/mock/para_relative_view.rs diff --git a/traits/src/location.rs b/traits/src/location.rs index 94e1c7f61..a60d817e6 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -58,6 +58,30 @@ impl Reserve for AbsoluteReserveProvider { } } +// Provide reserve in relative path view +// Self tokens are represeneted as Here +pub struct RelativeReserveProvider; + +impl Reserve for RelativeReserveProvider { + fn reserve(asset: &MultiAsset) -> Option { + if let Concrete(location) = &asset.id { + match (location.parents, location.first_interior()) { + // sibling parachain + (1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))), + // parent + (1, _) => Some(MultiLocation::parent()), + // children parachain + (0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))), + // All asset that is not a children parachain is a self reserved asset + (0, _) => Some(MultiLocation::here()), + _ => None, + } + } else { + None + } + } +} + pub trait RelativeLocations { fn sibling_parachain_general_key(para_id: u32, general_key: Vec) -> MultiLocation; } @@ -85,6 +109,10 @@ mod tests { AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), Some(MultiLocation::parent()) ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), + Some(MultiLocation::parent()) + ); } #[test] @@ -93,6 +121,10 @@ mod tests { AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), Some(MultiLocation::new(1, X1(PARACHAIN))) ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), + Some(MultiLocation::new(1, X1(PARACHAIN))) + ); } #[test] @@ -101,14 +133,22 @@ mod tests { AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX)))), Some(PARACHAIN.into()) ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X2(PARACHAIN, GENERAL_INDEX)))), + Some(PARACHAIN.into()) + ); } #[test] - fn no_reserve_chain() { + fn no_reserve_chain_for_absolute_self_for_relative() { assert_eq!( AbsoluteReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into()))))), None ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into()))))), + Some(MultiLocation::here()) + ); } #[test] diff --git a/xtokens/src/mock/mod.rs b/xtokens/src/mock/mod.rs index ff6e2f5b9..b3268fe6a 100644 --- a/xtokens/src/mock/mod.rs +++ b/xtokens/src/mock/mod.rs @@ -11,6 +11,7 @@ use sp_runtime::AccountId32; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; pub mod para; +pub mod para_relative_view; pub mod relay; pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); @@ -29,6 +30,8 @@ pub enum CurrencyId { B, /// Parachain B B1 token B1, + /// Parachain D token + D, } pub struct CurrencyIdConvert; @@ -40,6 +43,7 @@ impl Convert> for CurrencyIdConvert { CurrencyId::A1 => Some((Parent, Parachain(1), GeneralKey("A1".into())).into()), CurrencyId::B => Some((Parent, Parachain(2), GeneralKey("B".into())).into()), CurrencyId::B1 => Some((Parent, Parachain(2), GeneralKey("B1".into())).into()), + CurrencyId::D => Some((Parent, Parachain(4), GeneralKey("D".into())).into()), } } } @@ -49,6 +53,8 @@ impl Convert> for CurrencyIdConvert { let a1: Vec = "A1".into(); let b: Vec = "B".into(); let b1: Vec = "B1".into(); + let d: Vec = "D".into(); + if l == MultiLocation::parent() { return Some(CurrencyId::R); } @@ -58,6 +64,7 @@ impl Convert> for CurrencyIdConvert { X2(Parachain(1), GeneralKey(k)) if k == a1 => Some(CurrencyId::A1), X2(Parachain(2), GeneralKey(k)) if k == b => Some(CurrencyId::B), X2(Parachain(2), GeneralKey(k)) if k == b1 => Some(CurrencyId::B1), + X2(Parachain(4), GeneralKey(k)) if k == d => Some(CurrencyId::D), _ => None, }, MultiLocation { parents, interior } if parents == 0 => match interior { @@ -65,6 +72,7 @@ impl Convert> for CurrencyIdConvert { X1(GeneralKey(k)) if k == b => Some(CurrencyId::B), X1(GeneralKey(k)) if k == a1 => Some(CurrencyId::A1), X1(GeneralKey(k)) if k == b1 => Some(CurrencyId::B1), + X1(GeneralKey(k)) if k == d => Some(CurrencyId::D), _ => None, }, _ => None, @@ -115,6 +123,17 @@ decl_test_parachain! { } } +// This parachain is identical to the others but using relative view for self +// tokens +decl_test_parachain! { + pub struct ParaD { + Runtime = para_relative_view::Runtime, + XcmpMessageHandler = para::XcmpQueue, + DmpMessageHandler = para::DmpQueue, + new_ext = para_ext(4), + } +} + decl_test_relay_chain! { pub struct Relay { Runtime = relay::Runtime, @@ -130,6 +149,7 @@ decl_test_network! { (1, ParaA), (2, ParaB), (3, ParaC), + (4, ParaD), ], } } @@ -138,6 +158,9 @@ pub type RelayBalances = pallet_balances::Pallet; pub type ParaTokens = orml_tokens::Pallet; pub type ParaXTokens = orml_xtokens::Pallet; +pub type ParaRelativeTokens = orml_tokens::Pallet; +pub type ParaRelativeXTokens = orml_xtokens::Pallet; + pub fn para_ext(para_id: u32) -> TestExternalities { use para::{Runtime, System}; diff --git a/xtokens/src/mock/para_relative_view.rs b/xtokens/src/mock/para_relative_view.rs new file mode 100644 index 000000000..9775d222e --- /dev/null +++ b/xtokens/src/mock/para_relative_view.rs @@ -0,0 +1,381 @@ +use super::{Amount, Balance, CurrencyId, CurrencyIdConvert, ParachainXcmRouter}; +use crate as orml_xtokens; + +use frame_support::{ + construct_runtime, parameter_types, + traits::{Everything, Nothing}, + weights::{constants::WEIGHT_PER_SECOND, Weight}, +}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{Convert, IdentityLookup, Zero}, + AccountId32, +}; + +use cumulus_primitives_core::{ChannelStatus, GetChannelInfo, ParaId}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, LocationInverter, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, +}; +use xcm_executor::{traits::WeightTrader, Assets, Config, XcmExecutor}; + +use orml_traits::{ + location::{AbsoluteReserveProvider, RelativeReserveProvider}, + parameter_type_with_key, +}; +use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; + +pub type AccountId = AccountId32; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = Everything; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; + pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; +} + +impl parachain_info::Config for Runtime {} + +parameter_types! { + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); + pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); +} + +pub type LocationToAccountId = ( + ParentIsPreset, + SiblingParachainConvertsVia, + AccountId32Aliases, +); + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: Weight = 10; + pub const MaxInstructions: u32 = 100; +} + +pub type LocalAssetTransactor = MultiCurrencyAdapter< + Tokens, + (), + IsNativeConcrete, + AccountId, + LocationToAccountId, + CurrencyId, + CurrencyIdConvert, + (), +>; + +pub type XcmRouter = ParachainXcmRouter; +pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom); + +/// A trader who believes all tokens are created equal to "weight" of any chain, +/// which is not true, but good enough to mock the fee payment of XCM execution. +/// +/// This mock will always trade `n` amount of weight to `n` amount of tokens. +pub struct AllTokensAreCreatedEqualToWeight(MultiLocation); +impl WeightTrader for AllTokensAreCreatedEqualToWeight { + fn new() -> Self { + Self(MultiLocation::parent()) + } + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + let asset_id = payment + .fungible + .iter() + .next() + .expect("Payment must be something; qed") + .0; + let required = MultiAsset { + id: asset_id.clone(), + fun: Fungible(weight as u128), + }; + + if let MultiAsset { + fun: _, + id: Concrete(ref id), + } = &required + { + self.0 = id.clone(); + } + + let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?; + Ok(unused) + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + if weight.is_zero() { + None + } else { + Some((self.0.clone(), weight as u128).into()) + } + } +} + +pub struct XcmConfig; +impl Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = MultiNativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = AllTokensAreCreatedEqualToWeight; + type ResponseHandler = (); + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; +} + +pub struct ChannelInfo; +impl GetChannelInfo for ChannelInfo { + fn get_channel_status(_id: ParaId) -> ChannelStatus { + ChannelStatus::Ready(10, 10) + } + fn get_channel_max(_id: ParaId) -> Option { + Some(usize::max_value()) + } +} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ChannelInfo; + type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToCallOrigin; +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = EnsureRoot; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; +} + +pub struct AccountIdToMultiLocation; +impl Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: account.into(), + }) + .into() + } +} + +pub struct RelativeCurrencyIdConvert; +impl Convert> for RelativeCurrencyIdConvert { + fn convert(id: CurrencyId) -> Option { + match id { + CurrencyId::R => Some(Parent.into()), + CurrencyId::A => Some((Parent, Parachain(1), GeneralKey("A".into())).into()), + CurrencyId::A1 => Some((Parent, Parachain(1), GeneralKey("A1".into())).into()), + CurrencyId::B => Some((Parent, Parachain(2), GeneralKey("B".into())).into()), + CurrencyId::B1 => Some((Parent, Parachain(2), GeneralKey("B1".into())).into()), + CurrencyId::D => Some(GeneralKey("D".into()).into()), + } + } +} +impl Convert> for RelativeCurrencyIdConvert { + fn convert(l: MultiLocation) -> Option { + let a: Vec = "A".into(); + let a1: Vec = "A1".into(); + let b: Vec = "B".into(); + let b1: Vec = "B1".into(); + let d: Vec = "D".into(); + + let self_para_id: u32 = ParachainInfo::parachain_id().into(); + if l == MultiLocation::parent() { + return Some(CurrencyId::R); + } + match l { + MultiLocation { parents, interior } if parents == 1 => match interior { + X2(Parachain(1), GeneralKey(k)) if k == a => Some(CurrencyId::A), + X2(Parachain(1), GeneralKey(k)) if k == a1 => Some(CurrencyId::A1), + X2(Parachain(2), GeneralKey(k)) if k == b => Some(CurrencyId::B), + X2(Parachain(2), GeneralKey(k)) if k == b1 => Some(CurrencyId::B1), + X2(Parachain(para_id), GeneralKey(k)) if k == d && para_id == self_para_id => Some(CurrencyId::D), + _ => None, + }, + MultiLocation { parents, interior } if parents == 0 => match interior { + X1(GeneralKey(k)) if k == a => Some(CurrencyId::A), + X1(GeneralKey(k)) if k == b => Some(CurrencyId::B), + X1(GeneralKey(k)) if k == a1 => Some(CurrencyId::A1), + X1(GeneralKey(k)) if k == b1 => Some(CurrencyId::B1), + X1(GeneralKey(k)) if k == d => Some(CurrencyId::D), + _ => None, + }, + _ => None, + } + } +} +impl Convert> for RelativeCurrencyIdConvert { + fn convert(a: MultiAsset) -> Option { + if let MultiAsset { + fun: Fungible(_), + id: Concrete(id), + } = a + { + Self::convert(id) + } else { + Option::None + } + } +} + +parameter_types! { + pub SelfLocation: MultiLocation = MultiLocation::here(); + pub const BaseXcmWeight: Weight = 100_000_000; + pub const MaxAssetsForTransfer: usize = 2; +} + +impl orml_xtokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type CurrencyId = CurrencyId; + type CurrencyIdConvert = RelativeCurrencyIdConvert; + type AccountIdToMultiLocation = AccountIdToMultiLocation; + type SelfLocation = SelfLocation; + type XcmExecutor = XcmExecutor; + type Weigher = FixedWeightBounds; + type BaseXcmWeight = BaseXcmWeight; + type LocationInverter = LocationInverter; + type MaxAssetsForTransfer = MaxAssetsForTransfer; + type ReserveProvider = RelativeReserveProvider; +} + +impl orml_xcm::Config for Runtime { + type Event = Event; + type SovereignOrigin = EnsureRoot; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + + ParachainInfo: parachain_info::{Pallet, Storage, Config}, + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event}, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event}, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin}, + + Tokens: orml_tokens::{Pallet, Storage, Event, Config}, + XTokens: orml_xtokens::{Pallet, Storage, Call, Event}, + + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + OrmlXcm: orml_xcm::{Pallet, Call, Event}, + } +); diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index df32eefc8..1ee6c032e 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -33,6 +33,11 @@ fn sibling_c_account() -> AccountId32 { Sibling::from(3).into_account() } +fn sibling_d_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(4).into_account() +} + // Not used in any unit tests, but it's super helpful for debugging. Let's // keep it here. #[allow(dead_code)] @@ -1099,3 +1104,111 @@ fn send_with_zero_amount() { // TODO: should have more tests after https://github.com/paritytech/polkadot/issues/4996 } + +#[test] +fn send_self_parachain_asset_to_sibling_relative_parachain() { + TestNet::reset(); + + ParaD::execute_with(|| { + assert_ok!(ParaRelativeTokens::deposit(CurrencyId::D, &ALICE, 1_000)); + + assert_ok!(ParaRelativeXTokens::transfer( + Some(ALICE).into(), + CurrencyId::D, + 500, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + )); + + assert_eq!(ParaRelativeTokens::free_balance(CurrencyId::D, &ALICE), 500); + assert_eq!( + ParaRelativeTokens::free_balance(CurrencyId::D, &sibling_b_account()), + 500 + ); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::D, &BOB), 460); + }); +} + +#[test] +fn send_sibling_asset_to_reserve_sibling_with_relative_view() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::D, &ALICE, 1_000)); + assert_ok!(ParaTokens::deposit(CurrencyId::A, &sibling_d_account(), 1_000)); + }); + + ParaD::execute_with(|| { + assert_ok!(ParaRelativeTokens::deposit(CurrencyId::D, &sibling_a_account(), 1_000)); + assert_ok!(ParaRelativeTokens::deposit(CurrencyId::A, &BOB, 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::D, + 500, + Box::new( + ( + Parent, + Parachain(4), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into() + ), + 40, + )); + + assert_eq!(ParaTokens::free_balance(CurrencyId::D, &ALICE), 500); + }); + + ParaD::execute_with(|| { + assert_eq!( + ParaRelativeTokens::free_balance(CurrencyId::D, &sibling_a_account()), + 500 + ); + assert_eq!(ParaRelativeTokens::free_balance(CurrencyId::D, &BOB), 460); + + assert_ok!(ParaRelativeXTokens::transfer( + Some(BOB).into(), + CurrencyId::A, + 500, + Box::new( + ( + Parent, + Parachain(1), + Junction::AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ) + .into() + ), + 40, + )); + assert_eq!(ParaRelativeTokens::free_balance(CurrencyId::A, &BOB), 500); + }); + + ParaA::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &sibling_d_account()), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &ALICE), 460); + }); +} From 09f1099d9c19b8960b936f282de46d8542d14506 Mon Sep 17 00:00:00 2001 From: gorka Date: Wed, 2 Mar 2022 15:50:48 +0100 Subject: [PATCH 09/11] Modify relative provider as suggested in PR review --- traits/src/location.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/traits/src/location.rs b/traits/src/location.rs index a60d817e6..68676e478 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -65,16 +65,10 @@ pub struct RelativeReserveProvider; impl Reserve for RelativeReserveProvider { fn reserve(asset: &MultiAsset) -> Option { if let Concrete(location) = &asset.id { - match (location.parents, location.first_interior()) { - // sibling parachain - (1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))), - // parent - (1, _) => Some(MultiLocation::parent()), - // children parachain - (0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))), - // All asset that is not a children parachain is a self reserved asset - (0, _) => Some(MultiLocation::here()), - _ => None, + if location.parents == 0 && !is_chain_junction(location.first_interior()) { + Some(MultiLocation::here()) + } else { + location.chain_part() } } else { None From 219425353ef18d5ab897f5ba6ef932c80c75dd8b Mon Sep 17 00:00:00 2001 From: gorka Date: Mon, 7 Mar 2022 06:07:33 -0500 Subject: [PATCH 10/11] Clippy warnings --- xtokens/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 45aad3c38..5d18f751b 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -863,7 +863,7 @@ pub mod module { 0 }; let asset = assets.get(reserve_idx); - asset.and_then(|a| T::ReserveProvider::reserve(a)) + asset.and_then(T::ReserveProvider::reserve) } } From 93740e9f3ba891e4ba2c8039d747c18e01c1d4ea Mon Sep 17 00:00:00 2001 From: gorka Date: Wed, 16 Mar 2022 10:04:42 +0100 Subject: [PATCH 11/11] Add currendy D between non-reserve sibling transfer test and relay from sibling to relative view sibling test --- xtokens/src/tests.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 29ca67a1f..e597d89e5 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -18,6 +18,10 @@ fn para_b_account() -> AccountId32 { ParaId::from(2).into_account() } +fn para_d_account() -> AccountId32 { + ParaId::from(4).into_account() +} + fn sibling_a_account() -> AccountId32 { use sp_runtime::traits::AccountIdConversion; Sibling::from(1).into_account() @@ -1448,3 +1452,96 @@ fn send_sibling_asset_to_reserve_sibling_with_relative_view() { assert_eq!(ParaTokens::free_balance(CurrencyId::A, &ALICE), 460); }); } + +#[test] +fn send_relative_view_sibling_asset_to_non_reserve_sibling() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::D, &ALICE, 1_000)); + }); + + ParaD::execute_with(|| { + assert_ok!(ParaRelativeTokens::deposit(CurrencyId::D, &sibling_a_account(), 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::D, + 500, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40 + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::D, &ALICE), 500); + }); + + // check reserve accounts + ParaD::execute_with(|| { + assert_eq!( + ParaRelativeTokens::free_balance(CurrencyId::D, &sibling_a_account()), + 500 + ); + assert_eq!( + ParaRelativeTokens::free_balance(CurrencyId::D, &sibling_b_account()), + 460 + ); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::D, &BOB), 420); + }); +} + +#[test] +fn send_relay_chain_asset_to_relative_view_sibling() { + TestNet::reset(); + + Relay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 1000); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::R, + 500, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(4), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 500); + }); + + Relay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 500); + assert_eq!(RelayBalances::free_balance(¶_d_account()), 460); + }); + + ParaD::execute_with(|| { + assert_eq!(ParaRelativeTokens::free_balance(CurrencyId::R, &BOB), 420); + }); +}