Skip to content
84 changes: 83 additions & 1 deletion xcm-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Network, AccountId>(PhantomData<(Network, AccountId)>);
impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
xcm_executor::traits::Convert<MultiLocation, AccountId> for RelayChainAccountId32Aliases<Network, AccountId>
{
fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
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<MultiLocation, AccountId> {
Ok((
1,
AccountId32 {
id: who.into(),
network: Network::get(),
},
)
.into())
}
}

/// Allows execution from `origin` if it is `Parent`.
pub struct AllowRelayedPaidExecutionFromParent<Network>(PhantomData<Network>);
impl<Network: Get<NetworkId>> ShouldExecute for AllowRelayedPaidExecutionFromParent<Network> {
fn should_execute<Call>(
origin: &MultiLocation,
message: &mut Xcm<Call>,
max_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
ensure!(origin.contains_parents_only(1), ());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to make this a configurable generic that implements Contains<MultiLocation> ?
We have a use-case for allowing sibling chains to send Transact instructions to us.

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(()),
}
}
}
80 changes: 80 additions & 0 deletions xcm-support/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<AccountId32, MultiLocation> =
RelayChainAccountId32Aliases::<RelayNetwork, AccountId32>::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::<RelayNetwork>::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::<RelayNetwork>::should_execute(&(Parent.into()), &mut xcm, 2000, &mut 100);
assert_eq!(r, Err(()));
}
34 changes: 30 additions & 4 deletions xtokens/src/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -135,7 +161,7 @@ pub fn para_ext(para_id: u32) -> TestExternalities {
.unwrap();

orml_tokens::GenesisConfig::<Runtime> {
balances: vec![(ALICE, CurrencyId::R, 1_000)],
balances: vec![(ALICE, CurrencyId::R, INITIAL_BALANCE)],
}
.assimilate_storage(&mut t)
.unwrap();
Expand All @@ -153,7 +179,7 @@ pub fn relay_ext() -> sp_io::TestExternalities {
.unwrap();

pallet_balances::GenesisConfig::<Runtime> {
balances: vec![(ALICE, 1_000)],
balances: vec![(ALICE, INITIAL_BALANCE)],
}
.assimilate_storage(&mut t)
.unwrap();
Expand Down
12 changes: 10 additions & 2 deletions xtokens/src/mock/para.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -114,6 +117,7 @@ pub type LocationToAccountId = (
ParentIsDefault<AccountId>,
SiblingParachainConvertsVia<Sibling, AccountId>,
AccountId32Aliases<RelayNetwork, AccountId>,
RelayChainAccountId32Aliases<RelayNetwork, AccountId>,
);

pub type XcmOriginToCallOrigin = (
Expand All @@ -140,7 +144,11 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter<
>;

pub type XcmRouter = ParachainXcmRouter<ParachainInfo>;
pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom<Everything>);
pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowRelayedPaidExecutionFromParent<RelayNetwork>,
);

/// 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.
Expand Down
27 changes: 0 additions & 27 deletions xtokens/src/tests.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down