diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index be39c8c85..6165264f4 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -22,7 +22,13 @@ #![allow(clippy::unused_unit)] #![allow(clippy::large_enum_variant)] -use frame_support::{log, pallet_prelude::*, require_transactional, traits::Get, transactional, Parameter}; +use frame_support::{ + log, + pallet_prelude::*, + require_transactional, + traits::{Contains, Get}, + transactional, Parameter, +}; use frame_system::{ensure_signed, pallet_prelude::*}; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member, Zero}, @@ -88,6 +94,9 @@ pub mod module { /// XCM executor. type XcmExecutor: ExecuteXcm; + /// MultiLocation filter + type MultiLocationsFilter: Contains; + /// Means of measuring the weight consumed by an XCM message locally. type Weigher: WeightBounds; @@ -158,6 +167,8 @@ pub mod module { AssetIndexNonExistent, /// Fee is not enough. FeeNotEnough, + /// Not supported MultiLocation + NotSupportedMultiLocation, } #[pallet::hooks] @@ -372,6 +383,10 @@ pub mod module { T::CurrencyIdConvert::convert(currency_id).ok_or(Error::::NotCrossChainTransferableCurrency)?; ensure!(!amount.is_zero(), Error::::ZeroAmount); + ensure!( + T::MultiLocationsFilter::contains(&dest), + Error::::NotSupportedMultiLocation + ); let asset: MultiAsset = (location, amount.into()).into(); Self::do_transfer_multiassets(who, vec![asset.clone()].into(), asset, dest, dest_weight) @@ -390,6 +405,10 @@ pub mod module { ensure!(!amount.is_zero(), Error::::ZeroAmount); ensure!(!fee.is_zero(), Error::::ZeroFee); + ensure!( + T::MultiLocationsFilter::contains(&dest), + Error::::NotSupportedMultiLocation + ); let asset = (location.clone(), amount.into()).into(); let fee_asset: MultiAsset = (location, fee.into()).into(); @@ -439,6 +458,10 @@ pub mod module { currencies.len() <= T::MaxAssetsForTransfer::get(), Error::::TooManyAssetsBeingSent ); + ensure!( + T::MultiLocationsFilter::contains(&dest), + Error::::NotSupportedMultiLocation + ); let mut assets = MultiAssets::new(); @@ -451,6 +474,7 @@ pub mod module { let location: MultiLocation = T::CurrencyIdConvert::convert(currency_id.clone()) .ok_or(Error::::NotCrossChainTransferableCurrency)?; ensure!(!amount.is_zero(), Error::::ZeroAmount); + // Push contains saturated addition, so we should be able to use it safely assets.push((location, (*amount).into()).into()) } @@ -476,6 +500,10 @@ pub mod module { assets.len() <= T::MaxAssetsForTransfer::get(), Error::::TooManyAssetsBeingSent ); + ensure!( + T::MultiLocationsFilter::contains(&dest), + Error::::NotSupportedMultiLocation + ); let origin_location = T::AccountIdToMultiLocation::convert(who.clone()); let mut non_fee_reserve: Option = None; diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 689fa3940..609dd7b8c 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -2,7 +2,7 @@ use super::{Amount, Balance, CurrencyId, CurrencyIdConvert, ParachainXcmRouter}; use crate as orml_xtokens; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, match_type, parameter_types, traits::{Everything, Get, Nothing}, weights::{constants::WEIGHT_PER_SECOND, Weight}, }; @@ -272,6 +272,17 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 3; } +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(100), Junction::AccountId32 { .. }) } + }; +} + parameter_type_with_key! { pub ParachainMinFee: |location: MultiLocation| -> u128 { #[allow(clippy::match_ref_pats)] // false positive @@ -289,6 +300,7 @@ impl orml_xtokens::Config for Runtime { type CurrencyIdConvert = CurrencyIdConvert; type AccountIdToMultiLocation = AccountIdToMultiLocation; type SelfLocation = SelfLocation; + type MultiLocationsFilter = ParentOrParachains; type MinXcmFee = ParachainMinFee; type XcmExecutor = XcmExecutor; type Weigher = FixedWeightBounds; diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index bb4ce5f60..2b68d35e8 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -1335,3 +1335,54 @@ fn send_with_zero_amount() { // TODO: should have more tests after https://github.com/paritytech/polkadot/issues/4996 } + +#[test] +fn unsupported_multilocation_should_be_filtered() { + TestNet::reset(); + + ParaB::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 1_000)); + assert_ok!(ParaTokens::deposit(CurrencyId::B1, &ALICE, 1_000)); + assert_noop!( + ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::B, + 500, + Box::new( + ( + Parent, + Parachain(4), // parachain 4 is not supported list. + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into() + ), + 40, + ), + Error::::NotSupportedMultiLocation + ); + + assert_noop!( + ParaXTokens::transfer_multicurrencies( + Some(ALICE).into(), + vec![(CurrencyId::B1, 50), (CurrencyId::B, 450)], + 0, + Box::new( + ( + Parent, + Parachain(4), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into() + ), + 40, + ), + Error::::NotSupportedMultiLocation + ); + }); +}