diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index 2a68197e5..1d58784fe 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -10,15 +10,18 @@ #![allow(clippy::unused_unit)] use frame_support::dispatch::{DispatchError, DispatchResult}; +use frame_support::{ensure, weights::Weight}; + use sp_runtime::traits::{CheckedConversion, Convert}; use sp_std::{convert::TryFrom, marker::PhantomData, prelude::*}; use xcm::latest::prelude::*; -use xcm_executor::traits::{FilterAssetLocation, MatchesFungible}; +use xcm_executor::traits::{FilterAssetLocation, MatchesFungible, ShouldExecute}; use orml_traits::location::Reserve; pub use currency_adapter::MultiCurrencyAdapter; +use frame_support::pallet_prelude::Get; mod currency_adapter; @@ -75,3 +78,82 @@ impl UnknownAsset for () { Err(DispatchError::Other(NO_UNKNOWN_ASSET_IMPL)) } } + +/// Extracts the `AccountId32` from the passed `location` if the network +/// matches or is `NetworkId::Any`. +pub struct RelayChainAccountId32Aliases(PhantomData<(Network, AccountId)>); +impl, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> + xcm_executor::traits::Convert for RelayChainAccountId32Aliases +{ + fn convert(location: MultiLocation) -> Result { + if let MultiLocation { + parents: 1, + interior: X1(AccountId32 { id, network }), + } = location.clone() + { + if network == NetworkId::Any || network == Network::get() { + return Ok(id.into()); + } + }; + Err(location) + } + + fn reverse(who: AccountId) -> Result { + Ok(( + 1, + AccountId32 { + id: who.into(), + network: Network::get(), + }, + ) + .into()) + } +} + +/// Allows execution from `origin` if it is `Parent`. +pub struct AllowRelayedPaidExecutionFromParent(PhantomData); +impl> ShouldExecute for AllowRelayedPaidExecutionFromParent { + fn should_execute( + origin: &MultiLocation, + message: &mut Xcm, + max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + ensure!(origin.contains_parents_only(1), ()); + let mut iter = message.0.iter_mut(); + let i = iter.next().ok_or(())?; + match i { + DescendOrigin(X1(Junction::AccountId32 { network, .. })) + if network == &NetworkId::Any || network == &Network::get() => + { + () + } + _ => return Err(()), + } + let i = iter.next().ok_or(())?; + match i { + WithdrawAsset(..) => (), + _ => return Err(()), + } + let i = iter.next().ok_or(())?; + match i { + BuyExecution { + weight_limit: Limited(ref mut weight), + .. + } if *weight >= max_weight => { + *weight = max_weight; + () + } + _ => return Err(()), + } + let i = iter.next().ok_or(())?; + match i { + Transact { + origin_type: OriginKind::SovereignAccount, + .. + } + | DepositAsset { .. } => Ok(()), + _ => Err(()), + } + } +} diff --git a/xcm-support/src/tests.rs b/xcm-support/src/tests.rs index c47469c41..98efc9544 100644 --- a/xcm-support/src/tests.rs +++ b/xcm-support/src/tests.rs @@ -4,7 +4,9 @@ use super::*; +use frame_support::{assert_ok, pallet_prelude::Encode, parameter_types}; use orml_traits::{location::RelativeLocations, ConcreteFungibleAsset}; +use sp_runtime::AccountId32; #[derive(Debug, PartialEq, Eq)] pub enum TestCurrencyId { @@ -98,3 +100,81 @@ fn multi_native_asset() { &MultiLocation::parent(), )); } + +#[test] +fn relay_account_convert() { + use xcm_executor::traits::Convert; + + parameter_types! { + const RelayNetwork: NetworkId = NetworkId::Any; + } + let destination: MultiLocation = ( + Parent, + Junction::AccountId32 { + network: NetworkId::Any, + id: [0; 32], + }, + ) + .into(); + let account: Result = + RelayChainAccountId32Aliases::::convert(destination); + assert_eq!(account, Ok(AccountId32::new([0; 32]))); +} + +#[test] +fn allow_relayed_paid_execution_transact_works() { + parameter_types! { + const RelayNetwork: NetworkId = NetworkId::Any; + } + let assets: MultiAsset = (Parent, 1000).into(); + let mut xcm = Xcm::<()>(vec![ + DescendOrigin(X1(Junction::AccountId32 { + network: NetworkId::Any, + id: [0; 32], + })), + WithdrawAsset(assets.clone().into()), + BuyExecution { + fees: assets, + weight_limit: Limited(1000), + }, + Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: 1000 as u64, + call: Encode::encode(&100).into(), + }, + ]); + let r = + AllowRelayedPaidExecutionFromParent::::should_execute(&(Parent.into()), &mut xcm, 100, &mut 100); + assert_ok!(r); +} + +#[test] +fn allow_relayed_paid_execution_weight_not_works() { + parameter_types! { + const RelayNetwork: NetworkId = NetworkId::Any; + } + let bob = X1(Junction::AccountId32 { + network: NetworkId::Kusama, + id: [1; 32], + }); + let assets: MultiAsset = (Parent, 1000).into(); + let mut xcm = Xcm::<()>(vec![ + DescendOrigin(X1(Junction::AccountId32 { + network: NetworkId::Any, + id: [0; 32], + })), + WithdrawAsset(assets.clone().into()), + BuyExecution { + fees: assets, + weight_limit: Limited(1000), + }, + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: (0, bob).into(), + }, + ]); + let r = + AllowRelayedPaidExecutionFromParent::::should_execute(&(Parent.into()), &mut xcm, 2000, &mut 100); + assert_eq!(r, Err(())); +} diff --git a/xtokens/src/mock/mod.rs b/xtokens/src/mock/mod.rs index 35291882f..81fcb56f2 100644 --- a/xtokens/src/mock/mod.rs +++ b/xtokens/src/mock/mod.rs @@ -8,13 +8,39 @@ use serde::{Deserialize, Serialize}; use sp_io::TestExternalities; use sp_runtime::AccountId32; +use cumulus_primitives_core::ParaId; +use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; pub mod para; pub mod relay; -pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); -pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); +pub const INITIAL_BALANCE: u128 = 1_000; + +pub fn para_a_account() -> AccountId32 { + ParaId::from(1).into_account() +} + +pub fn para_b_account() -> AccountId32 { + ParaId::from(2).into_account() +} + +pub fn sibling_a_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(1).into_account() +} + +pub fn sibling_b_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(2).into_account() +} + +pub fn sibling_c_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(3).into_account() +} #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -135,7 +161,7 @@ pub fn para_ext(para_id: u32) -> TestExternalities { .unwrap(); orml_tokens::GenesisConfig:: { - balances: vec![(ALICE, CurrencyId::R, 1_000)], + balances: vec![(ALICE, CurrencyId::R, INITIAL_BALANCE)], } .assimilate_storage(&mut t) .unwrap(); @@ -153,7 +179,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { .unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 1_000)], + balances: vec![(ALICE, INITIAL_BALANCE)], } .assimilate_storage(&mut t) .unwrap(); diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 0c67e974a..686a58fd8 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -26,7 +26,10 @@ use xcm_builder::{ use xcm_executor::{traits::WeightTrader, Assets, Config, XcmExecutor}; use orml_traits::parameter_type_with_key; -use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; +use orml_xcm_support::{ + AllowRelayedPaidExecutionFromParent, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset, + RelayChainAccountId32Aliases, +}; pub type AccountId = AccountId32; @@ -114,6 +117,7 @@ pub type LocationToAccountId = ( ParentIsDefault, SiblingParachainConvertsVia, AccountId32Aliases, + RelayChainAccountId32Aliases, ); pub type XcmOriginToCallOrigin = ( @@ -140,7 +144,11 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter< >; pub type XcmRouter = ParachainXcmRouter; -pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom); +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowRelayedPaidExecutionFromParent, +); /// 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. diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index ba2540dfd..be2c8ca78 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -1,38 +1,11 @@ #![cfg(test)] use super::*; -use codec::Encode; -use cumulus_primitives_core::ParaId; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use mock::*; use orml_traits::{ConcreteFungibleAsset, MultiCurrency}; -use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; -use sp_runtime::AccountId32; use xcm_simulator::TestExt; -fn para_a_account() -> AccountId32 { - ParaId::from(1).into_account() -} - -fn para_b_account() -> AccountId32 { - ParaId::from(2).into_account() -} - -fn sibling_a_account() -> AccountId32 { - use sp_runtime::traits::AccountIdConversion; - Sibling::from(1).into_account() -} - -fn sibling_b_account() -> AccountId32 { - use sp_runtime::traits::AccountIdConversion; - Sibling::from(2).into_account() -} - -fn sibling_c_account() -> AccountId32 { - use sp_runtime::traits::AccountIdConversion; - Sibling::from(3).into_account() -} - // Not used in any unit tests, but it's super helpful for debugging. Let's // keep it here. #[allow(dead_code)]