diff --git a/traits/src/location.rs b/traits/src/location.rs index b9a1e0072..68676e478 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -42,12 +42,15 @@ 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 { +// Provide reserve in absolute path view +pub struct AbsoluteReserveProvider; + +impl Reserve for AbsoluteReserveProvider { + fn reserve(asset: &MultiAsset) -> Option { + if let Concrete(location) = &asset.id { location.chain_part() } else { None @@ -55,6 +58,24 @@ impl Reserve for MultiAsset { } } +// 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 { + if location.parents == 0 && !is_chain_junction(location.first_interior()) { + Some(MultiLocation::here()) + } else { + location.chain_part() + } + } else { + None + } + } +} + pub trait RelativeLocations { fn sibling_parachain_general_key(para_id: u32, general_key: Vec) -> MultiLocation; } @@ -79,7 +100,11 @@ 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()) + ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X1(GENERAL_INDEX)))), Some(MultiLocation::parent()) ); } @@ -87,7 +112,11 @@ 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))) + ); + assert_eq!( + RelativeReserveProvider::reserve(&concrete_fungible(MultiLocation::new(1, X2(PARACHAIN, GENERAL_INDEX)))), Some(MultiLocation::new(1, X1(PARACHAIN))) ); } @@ -95,17 +124,25 @@ 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()) + ); + 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!( - concrete_fungible(MultiLocation::new(0, X1(GeneralKey("DOT".into())))).reserve(), + 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/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index cf71eaa39..5e801db17 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) { if reserve == origin { return true; } 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(), )); diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 6165264f4..1d52a63c6 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -113,6 +113,10 @@ pub mod module { /// The maximum number of distinct assets allowed to be transferred in a /// 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; } #[pallet::event] @@ -516,18 +520,18 @@ pub mod module { ); // `assets` includes fee, the reserve location is decided by non fee asset if (fee != *asset && non_fee_reserve.is_none()) || asset_len == 1 { - non_fee_reserve = asset.reserve(); + non_fee_reserve = T::ReserveProvider::reserve(asset); } // make sure all non fee assets share the same reserve if non_fee_reserve.is_some() { ensure!( - non_fee_reserve == asset.reserve(), + non_fee_reserve == T::ReserveProvider::reserve(asset), Error::::DistinctReserveForAssetAndFee ); } } - let fee_reserve = fee.reserve(); + let fee_reserve = T::ReserveProvider::reserve(&fee); if fee_reserve != non_fee_reserve { // Current only support `ToReserve` with relay-chain asset as fee. other case // like `NonReserve` or `SelfReserve` with relay-chain fee is not support. @@ -762,7 +766,6 @@ pub mod module { let self_location = T::SelfLocation::get(); ensure!(dest != self_location, Error::::NotCrossChainTransfer); - let reserve = reserve.ok_or(Error::::AssetHasNoReserve)?; let transfer_kind = if reserve == self_location { SelfReserveAsset @@ -782,7 +785,9 @@ pub mod module { let asset: Result = asset.clone().try_into(); let dest = dest.clone().try_into(); if let (Ok(asset), Ok(dest)) = (asset, dest) { - if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(asset.reserve(), &dest) { + if let Ok((transfer_kind, dest, _, reserve)) = + Self::transfer_kind(T::ReserveProvider::reserve(&asset), &dest) + { let mut msg = match transfer_kind { SelfReserveAsset => Xcm(vec![ WithdrawAsset(MultiAssets::from(asset)), @@ -891,7 +896,7 @@ pub mod module { 0 }; let asset = assets.get(reserve_idx); - asset.and_then(|a| a.reserve()) + asset.and_then(T::ReserveProvider::reserve) } } diff --git a/xtokens/src/mock/mod.rs b/xtokens/src/mock/mod.rs index 4f6fa3065..928a94023 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]); @@ -31,6 +32,8 @@ pub enum CurrencyId { B1, /// Parachain B B2 token B2, + /// Parachain D token + D, } pub struct CurrencyIdConvert; @@ -43,6 +46,7 @@ impl Convert> for CurrencyIdConvert { CurrencyId::B => Some((Parent, Parachain(2), GeneralKey("B".into())).into()), CurrencyId::B1 => Some((Parent, Parachain(2), GeneralKey("B1".into())).into()), CurrencyId::B2 => Some((Parent, Parachain(2), GeneralKey("B2".into())).into()), + CurrencyId::D => Some((Parent, Parachain(4), GeneralKey("D".into())).into()), } } } @@ -53,6 +57,7 @@ impl Convert> for CurrencyIdConvert { let b: Vec = "B".into(); let b1: Vec = "B1".into(); let b2: Vec = "B2".into(); + let d: Vec = "D".into(); if l == MultiLocation::parent() { return Some(CurrencyId::R); } @@ -62,7 +67,8 @@ 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(2), GeneralKey(k)) if k == b1 => Some(CurrencyId::B2), + X2(Parachain(2), GeneralKey(k)) if k == b2 => Some(CurrencyId::B2), + X2(Parachain(4), GeneralKey(k)) if k == d => Some(CurrencyId::D), _ => None, }, MultiLocation { parents, interior } if parents == 0 => match interior { @@ -71,6 +77,7 @@ impl Convert> for CurrencyIdConvert { X1(GeneralKey(k)) if k == a1 => Some(CurrencyId::A1), X1(GeneralKey(k)) if k == b1 => Some(CurrencyId::B1), X1(GeneralKey(k)) if k == b2 => Some(CurrencyId::B2), + X1(GeneralKey(k)) if k == d => Some(CurrencyId::D), _ => None, }, _ => None, @@ -121,6 +128,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, @@ -136,6 +154,7 @@ decl_test_network! { (1, ParaA), (2, ParaB), (3, ParaC), + (4, ParaD), ], } } @@ -144,6 +163,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.rs b/xtokens/src/mock/para.rs index 609dd7b8c..d80f5ebbc 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::{location::AbsoluteReserveProvider, parameter_type_with_key}; 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; @@ -279,6 +279,7 @@ match_type! { MultiLocation { parents: 1, interior: X2(Parachain(1), Junction::AccountId32 { .. }) } | MultiLocation { parents: 1, interior: X2(Parachain(2), Junction::AccountId32 { .. }) } | MultiLocation { parents: 1, interior: X2(Parachain(3), Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(4), Junction::AccountId32 { .. }) } | MultiLocation { parents: 1, interior: X2(Parachain(100), Junction::AccountId32 { .. }) } }; } @@ -307,6 +308,7 @@ impl orml_xtokens::Config for Runtime { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type ReserveProvider = AbsoluteReserveProvider; } impl orml_xcm::Config for Runtime { diff --git a/xtokens/src/mock/para_relative_view.rs b/xtokens/src/mock/para_relative_view.rs new file mode 100644 index 000000000..fe0155d41 --- /dev/null +++ b/xtokens/src/mock/para_relative_view.rs @@ -0,0 +1,409 @@ +use super::{Amount, Balance, CurrencyId, CurrencyIdConvert, ParachainXcmRouter}; +use crate as orml_xtokens; + +use frame_support::{ + construct_runtime, match_type, 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::B2 => Some((Parent, Parachain(2), GeneralKey("B2".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 b2: Vec = "B2".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(2), GeneralKey(k)) if k == b2 => Some(CurrencyId::B2), + 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 == b2 => Some(CurrencyId::B2), + 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; +} + +match_type! { + pub type ParentOrParachains: impl Contains = { + MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X1(Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(1), Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(2), Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(3), Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(4), Junction::AccountId32 { .. }) } | + MultiLocation { parents: 1, interior: X2(Parachain(100), Junction::AccountId32 { .. }) } + }; +} + +parameter_type_with_key! { + pub ParachainMinFee: |location: MultiLocation| -> u128 { + #[allow(clippy::match_ref_pats)] // false positive + match (location.parents, location.first_interior()) { + (1, Some(Parachain(2))) => 40, + _ => u128::MAX, + } + }; +} + +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 MultiLocationsFilter = ParentOrParachains; + type MinXcmFee = ParachainMinFee; + 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 2b68d35e8..4a056f877 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() @@ -33,6 +37,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)] @@ -1336,6 +1345,207 @@ 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); + }); +} + +#[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); + }); +} + #[test] fn unsupported_multilocation_should_be_filtered() { TestNet::reset(); @@ -1351,7 +1561,7 @@ fn unsupported_multilocation_should_be_filtered() { Box::new( ( Parent, - Parachain(4), // parachain 4 is not supported list. + Parachain(5), // parachain 4 is not supported list. Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(), @@ -1372,7 +1582,7 @@ fn unsupported_multilocation_should_be_filtered() { Box::new( ( Parent, - Parachain(4), + Parachain(5), Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(),