diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 6771313a5..4d9a1475f 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -208,7 +208,7 @@ pub mod module { dest: MultiLocation, dest_weight: Weight, ) -> XcmExecutionResult { - let (transfer_kind, reserve, dest, recipient) = Self::transfer_kind(&asset, &dest)?; + let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(&asset, &dest)?; let buy_order = BuyExecution { fees: All, // Zero weight for additional XCM (since there are none to execute) diff --git a/xtokens/src/mock/mod.rs b/xtokens/src/mock/mod.rs index ef3229d67..9257aaa12 100644 --- a/xtokens/src/mock/mod.rs +++ b/xtokens/src/mock/mod.rs @@ -90,6 +90,13 @@ decl_test_parachain! { } } +decl_test_parachain! { + pub struct ParaC { + Runtime = para::Runtime, + new_ext = para_ext(3), + } +} + decl_test_relay_chain! { pub struct Relay { Runtime = relay::Runtime, @@ -104,6 +111,7 @@ decl_test_network! { parachains = vec![ (1, ParaA), (2, ParaB), + (3, ParaC), ], } } diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index a3045c718..ab6b63dd3 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -17,23 +17,22 @@ use sp_runtime::{ use cumulus_primitives_core::{ChannelStatus, GetChannelInfo, ParaId}; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; -pub use xcm::v0::{ +use xcm::v0::{ + Error as XcmError, Junction::{self, Parachain, Parent}, MultiAsset, - MultiLocation::{self, X1, X2, X3}, + MultiLocation::{self, X1, X2}, NetworkId, Xcm, }; -pub use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, - CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfConcreteFungible, FixedWeightBounds, IsConcrete, - LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, LocationInverter, + ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::WeightTrader, Assets, Config, XcmExecutor}; use orml_traits::parameter_type_with_key; -use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter}; +use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; pub type AccountId = AccountId32; @@ -132,7 +131,6 @@ pub type XcmOriginToCallOrigin = ( parameter_types! { pub const UnitWeightCost: Weight = 10; - pub KsmPerSecond: (MultiLocation, u128) = (X1(Parent), 1_000); } pub type LocalAssetTransactor = MultiCurrencyAdapter< @@ -146,7 +144,42 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter< >; pub type XcmRouter = ParachainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom>; +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::Null) + } + + 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 = asset_id.clone().into_fungible_multiasset(weight as u128); + + if let MultiAsset::ConcreteFungible { ref id, amount: _ } = required { + self.0 = id.clone(); + } + + let (unused, _) = payment.less(required).map_err(|_| XcmError::TooExpensive)?; + Ok(unused) + } + + fn refund_weight(&mut self, weight: Weight) -> MultiAsset { + MultiAsset::ConcreteFungible { + id: self.0.clone(), + amount: weight as u128, + } + } +} pub struct XcmConfig; impl Config for XcmConfig { @@ -154,12 +187,12 @@ impl Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = NativeAsset; + type IsReserve = MultiNativeAsset; type IsTeleporter = (); type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfConcreteFungible; + type Trader = AllTokensAreCreatedEqualToWeight; type ResponseHandler = (); } diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 193a50e90..c8272c3e0 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -6,34 +6,33 @@ use cumulus_primitives_core::ParaId; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use mock::*; use orml_traits::MultiCurrency; -// use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; -use polkadot_parachain::primitives::AccountIdConversion; +use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; use sp_runtime::AccountId32; -use xcm::v0::{Junction, NetworkId, Order}; +use xcm::v0::{Error as XcmError, Junction, NetworkId, Order}; 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 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_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_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() -// } +fn sibling_c_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(3).into_account() +} #[test] fn send_relay_chain_asset_to_relay_chain() { @@ -72,13 +71,14 @@ fn cannot_lost_fund_on_send_failed() { TestNet::reset(); ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 1_000)); assert_ok!(ParaXTokens::transfer( Some(ALICE).into(), - CurrencyId::R, + CurrencyId::A, 500, ( Parent, - Parachain(3), + Parachain(100), Junction::AccountId32 { network: NetworkId::Kusama, id: BOB.into(), @@ -89,160 +89,167 @@ fn cannot_lost_fund_on_send_failed() { )); assert!(para::System::events().iter().any(|r| matches!( r.event, - para::Event::XTokens(Event::::TransferFailed(_, _, _, _, _)) + para::Event::XTokens(Event::::TransferFailed( + _, + _, + _, + _, + XcmError::CannotReachDestination(_, _) + )) ))); assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 1_000); }); } -// #[test] -// fn send_relay_chain_asset_to_sibling() { -// TestNet::reset(); - -// Relay::execute_with(|| { -// let _ = RelayBalances::deposit_creating(¶_a_account(), 100); -// }); - -// ParaA::execute_with(|| { -// assert_ok!(ParaXTokens::transfer( -// Some(ALICE).into(), -// CurrencyId::R, -// 30, -// ( -// Parent, -// Parachain { id: 2 }, -// Junction::AccountId32 { -// network: NetworkId::Any, -// id: BOB.into(), -// }, -// ) -// .into(), -// )); -// assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 70); -// }); - -// Relay::execute_with(|| { -// assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); -// assert_eq!(RelayBalances::free_balance(¶_b_account()), 30); -// }); - -// ParaB::execute_with(|| { -// assert_eq!(ParaTokens::free_balance(CurrencyId::R, &BOB), 30); -// }); -// } - -// #[test] -// fn send_sibling_asset_to_reserve_sibling() { -// TestNet::reset(); - -// ParaA::execute_with(|| { -// assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 100)); -// }); - -// ParaB::execute_with(|| { -// assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 100)); -// }); - -// ParaA::execute_with(|| { -// assert_ok!(ParaXTokens::transfer( -// Some(ALICE).into(), -// CurrencyId::B, -// 30, -// ( -// Parent, -// Parachain { id: 2 }, -// Junction::AccountId32 { -// network: NetworkId::Any, -// id: BOB.into(), -// }, -// ) -// .into(), -// )); - -// assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 70); -// }); - -// ParaB::execute_with(|| { -// assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), -// 70); assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 30); -// }); -// } - -// #[test] -// fn send_sibling_asset_to_non_reserve_sibling() { -// TestNet::reset(); - -// ParaA::execute_with(|| { -// assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 100)); -// }); - -// ParaB::execute_with(|| { -// assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 100)); -// }); - -// ParaA::execute_with(|| { -// assert_ok!(ParaXTokens::transfer( -// Some(ALICE).into(), -// CurrencyId::B, -// 30, -// ( -// Parent, -// Parachain { id: 3 }, -// Junction::AccountId32 { -// network: NetworkId::Any, -// id: BOB.into(), -// }, -// ) -// .into(), -// )); -// assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 70); -// }); - -// // check reserve accounts -// ParaB::execute_with(|| { -// assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), -// 70); assert_eq!(ParaTokens::free_balance(CurrencyId::B, -// &sibling_c_account()), 30); }); - -// ParaC::execute_with(|| { -// assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 30); -// }); -// } - -// #[test] -// fn send_self_parachain_asset_to_sibling() { -// TestNet::reset(); - -// ParaA::execute_with(|| { -// assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 100)); - -// assert_ok!(ParaXTokens::transfer( -// Some(ALICE).into(), -// CurrencyId::A, -// 30, -// ( -// Parent, -// Parachain { id: 2 }, -// Junction::AccountId32 { -// network: NetworkId::Any, -// id: BOB.into(), -// }, -// ) -// .into(), -// )); - -// assert_eq!(ParaTokens::free_balance(CurrencyId::A, &ALICE), 70); -// assert_eq!(ParaTokens::free_balance(CurrencyId::A, &sibling_b_account()), -// 30); }); - -// ParaB::execute_with(|| { -// para_b::System::events().iter().for_each(|r| { -// println!(">>> {:?}", r.event); -// }); -// assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 30); -// }); -// } +#[test] +fn send_relay_chain_asset_to_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, + ( + Parent, + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + 30, + )); + 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(¶_b_account()), 470); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &BOB), 440); + }); +} + +#[test] +fn send_sibling_asset_to_reserve_sibling() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 1_000)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::B, + 500, + ( + Parent, + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + 30, + )); + + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 500); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 470); + }); +} + +#[test] +fn send_sibling_asset_to_non_reserve_sibling() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 1_000)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::B, + 500, + ( + Parent, + Parachain(3), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + 30 + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 500); + }); + + // check reserve accounts + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_c_account()), 470); + }); + + ParaC::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 440); + }); +} + +#[test] +fn send_self_parachain_asset_to_sibling() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 1_000)); + + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::A, + 500, + ( + Parent, + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + 30, + )); + + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &ALICE), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &sibling_b_account()), 500); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 470); + }); +} #[test] fn transfer_no_reserve_assets_fails() {