mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 16:21:02 +00:00
Add tests for PayOverXcm (#7319)
* PayOverXcm: Add test skeleton * PayOverXcm: Add tests checking PayOverXcm generates correct message * Add PayOverXcm and salary pallet integration test * Fix XCM execution issue * Fix documentation issues * Add missing period at the end of comment * Fix mock problems * Add missing licenses * Fix lints * Fix more lints * Add docs to mock * Add transfer assertions * Remove magic numbers --------- Co-authored-by: parity-processbot <>
This commit is contained in:
committed by
GitHub
parent
5174b9d2d7
commit
85115402ac
Generated
+21
@@ -6584,6 +6584,24 @@ dependencies = [
|
|||||||
"sp-std",
|
"sp-std",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pallet-salary"
|
||||||
|
version = "4.0.0-dev"
|
||||||
|
source = "git+https://github.com/paritytech/substrate?branch=master#96f8c97dff7f419a349ee01d02613445ba4a41f8"
|
||||||
|
dependencies = [
|
||||||
|
"frame-benchmarking",
|
||||||
|
"frame-support",
|
||||||
|
"frame-system",
|
||||||
|
"log",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"sp-arithmetic",
|
||||||
|
"sp-core",
|
||||||
|
"sp-io",
|
||||||
|
"sp-runtime",
|
||||||
|
"sp-std",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pallet-scheduler"
|
name = "pallet-scheduler"
|
||||||
version = "4.0.0-dev"
|
version = "4.0.0-dev"
|
||||||
@@ -15130,11 +15148,14 @@ dependencies = [
|
|||||||
"frame-system",
|
"frame-system",
|
||||||
"impl-trait-for-tuples",
|
"impl-trait-for-tuples",
|
||||||
"log",
|
"log",
|
||||||
|
"pallet-assets",
|
||||||
"pallet-balances",
|
"pallet-balances",
|
||||||
|
"pallet-salary",
|
||||||
"pallet-transaction-payment",
|
"pallet-transaction-payment",
|
||||||
"pallet-xcm",
|
"pallet-xcm",
|
||||||
"parity-scale-codec 3.6.4",
|
"parity-scale-codec 3.6.4",
|
||||||
"polkadot-parachain",
|
"polkadot-parachain",
|
||||||
|
"polkadot-primitives",
|
||||||
"polkadot-runtime-parachains",
|
"polkadot-runtime-parachains",
|
||||||
"polkadot-test-runtime",
|
"polkadot-test-runtime",
|
||||||
"primitive-types",
|
"primitive-types",
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ polkadot-parachain = { path = "../../parachain", default-features = false }
|
|||||||
primitive-types = "0.12.1"
|
primitive-types = "0.12.1"
|
||||||
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
pallet-xcm = { path = "../pallet-xcm" }
|
pallet-xcm = { path = "../pallet-xcm" }
|
||||||
|
pallet-salary = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
primitives = { package = "polkadot-primitives", path = "../../primitives" }
|
||||||
polkadot-runtime-parachains = { path = "../../runtime/parachains" }
|
polkadot-runtime-parachains = { path = "../../runtime/parachains" }
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
polkadot-test-runtime = { path = "../../runtime/test-runtime" }
|
polkadot-test-runtime = { path = "../../runtime/test-runtime" }
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Mock implementations to test XCM builder configuration types.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
barriers::{AllowSubscriptionsFrom, RespectSuspension, TrailingSetTopicAsId},
|
barriers::{AllowSubscriptionsFrom, RespectSuspension, TrailingSetTopicAsId},
|
||||||
test_utils::*,
|
test_utils::*,
|
||||||
@@ -42,7 +44,7 @@ pub use sp_std::{
|
|||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
pub use xcm::latest::{prelude::*, Weight};
|
pub use xcm::latest::{prelude::*, Weight};
|
||||||
use xcm_executor::traits::Properties;
|
use xcm_executor::traits::{Properties, QueryHandler, QueryResponseStatus};
|
||||||
pub use xcm_executor::{
|
pub use xcm_executor::{
|
||||||
traits::{
|
traits::{
|
||||||
AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager,
|
AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager,
|
||||||
@@ -410,6 +412,63 @@ pub fn response(query_id: u64) -> Option<Response> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mock implementation of the [`QueryHandler`] trait for creating XCM success queries and expecting
|
||||||
|
/// responses.
|
||||||
|
pub struct TestQueryHandler<T, BlockNumber>(core::marker::PhantomData<(T, BlockNumber)>);
|
||||||
|
impl<T: Config, BlockNumber: sp_runtime::traits::Zero> QueryHandler
|
||||||
|
for TestQueryHandler<T, BlockNumber>
|
||||||
|
{
|
||||||
|
type QueryId = u64;
|
||||||
|
type BlockNumber = BlockNumber;
|
||||||
|
type Error = XcmError;
|
||||||
|
type UniversalLocation = T::UniversalLocation;
|
||||||
|
|
||||||
|
fn new_query(
|
||||||
|
responder: impl Into<MultiLocation>,
|
||||||
|
_timeout: Self::BlockNumber,
|
||||||
|
_match_querier: impl Into<MultiLocation>,
|
||||||
|
) -> Self::QueryId {
|
||||||
|
let query_id = 1;
|
||||||
|
expect_response(query_id, responder.into());
|
||||||
|
query_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_outcome(
|
||||||
|
message: &mut Xcm<()>,
|
||||||
|
responder: impl Into<MultiLocation>,
|
||||||
|
timeout: Self::BlockNumber,
|
||||||
|
) -> Result<Self::QueryId, Self::Error> {
|
||||||
|
let responder = responder.into();
|
||||||
|
let destination = Self::UniversalLocation::get()
|
||||||
|
.invert_target(&responder)
|
||||||
|
.map_err(|()| XcmError::LocationNotInvertible)?;
|
||||||
|
let query_id = Self::new_query(responder, timeout, Here);
|
||||||
|
let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
|
||||||
|
let report_error = Xcm(vec![ReportError(response_info)]);
|
||||||
|
message.0.insert(0, SetAppendix(report_error));
|
||||||
|
Ok(query_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_response(query_id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber> {
|
||||||
|
QUERIES
|
||||||
|
.with(|q| {
|
||||||
|
q.borrow().get(&query_id).and_then(|v| match v {
|
||||||
|
ResponseSlot::Received(r) => Some(QueryResponseStatus::Ready {
|
||||||
|
response: r.clone(),
|
||||||
|
at: Self::BlockNumber::zero(),
|
||||||
|
}),
|
||||||
|
_ => Some(QueryResponseStatus::NotFound),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(QueryResponseStatus::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
fn expect_response(_id: Self::QueryId, _response: xcm::latest::Response) {
|
||||||
|
// Unnecessary since it's only a test implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static ExecutorUniversalLocation: InteriorMultiLocation
|
pub static ExecutorUniversalLocation: InteriorMultiLocation
|
||||||
= (ByGenesis([0; 32]), Parachain(42)).into();
|
= (ByGenesis([0; 32]), Parachain(42)).into();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ mod bridging;
|
|||||||
mod expecting;
|
mod expecting;
|
||||||
mod locking;
|
mod locking;
|
||||||
mod origins;
|
mod origins;
|
||||||
|
mod pay;
|
||||||
mod querying;
|
mod querying;
|
||||||
mod transacting;
|
mod transacting;
|
||||||
mod version_subscriptions;
|
mod version_subscriptions;
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use frame_support::traits::{AsEnsureOriginWithArg, Nothing};
|
||||||
|
|
||||||
|
use frame_support::derive_impl;
|
||||||
|
|
||||||
|
use frame_support::{
|
||||||
|
construct_runtime, parameter_types,
|
||||||
|
traits::{ConstU32, Everything},
|
||||||
|
};
|
||||||
|
use frame_system::{EnsureRoot, EnsureSigned};
|
||||||
|
use polkadot_test_runtime::SignedExtra;
|
||||||
|
use primitives::{AccountIndex, BlakeTwo256, Signature};
|
||||||
|
use sp_runtime::{generic, traits::MaybeEquivalence, AccountId32, BuildStorage};
|
||||||
|
use xcm_executor::{traits::ConvertLocation, XcmExecutor};
|
||||||
|
|
||||||
|
pub type Address = sp_runtime::MultiAddress<AccountId, AccountIndex>;
|
||||||
|
pub type UncheckedExtrinsic =
|
||||||
|
generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
|
||||||
|
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||||
|
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||||
|
|
||||||
|
pub type BlockNumber = u32;
|
||||||
|
pub type AccountId = AccountId32;
|
||||||
|
|
||||||
|
construct_runtime!(
|
||||||
|
pub struct Test {
|
||||||
|
System: frame_system,
|
||||||
|
Balances: pallet_balances,
|
||||||
|
Assets: pallet_assets,
|
||||||
|
Salary: pallet_salary,
|
||||||
|
XcmPallet: pallet_xcm,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const BlockHashCount: BlockNumber = 250;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
|
||||||
|
impl frame_system::Config for Test {
|
||||||
|
type Block = Block;
|
||||||
|
type BlockHashCount = BlockHashCount;
|
||||||
|
type BaseCallFilter = frame_support::traits::Everything;
|
||||||
|
type RuntimeOrigin = RuntimeOrigin;
|
||||||
|
type RuntimeCall = RuntimeCall;
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type PalletInfo = PalletInfo;
|
||||||
|
type OnSetCode = ();
|
||||||
|
type AccountData = pallet_balances::AccountData<Balance>;
|
||||||
|
type AccountId = AccountId;
|
||||||
|
type Lookup = sp_runtime::traits::IdentityLookup<AccountId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Balance = u128;
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const ExistentialDeposit: Balance = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl pallet_balances::Config for Test {
|
||||||
|
type MaxLocks = ConstU32<0>;
|
||||||
|
type Balance = Balance;
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type DustRemoval = ();
|
||||||
|
type ExistentialDeposit = ExistentialDeposit;
|
||||||
|
type AccountStore = System;
|
||||||
|
type WeightInfo = ();
|
||||||
|
type MaxReserves = ();
|
||||||
|
type ReserveIdentifier = [u8; 8];
|
||||||
|
type RuntimeHoldReason = RuntimeHoldReason;
|
||||||
|
type FreezeIdentifier = ();
|
||||||
|
type MaxHolds = ConstU32<0>;
|
||||||
|
type MaxFreezes = ConstU32<0>;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const AssetDeposit: u128 = 1_000_000;
|
||||||
|
pub const MetadataDepositBase: u128 = 1_000_000;
|
||||||
|
pub const MetadataDepositPerByte: u128 = 100_000;
|
||||||
|
pub const AssetAccountDeposit: u128 = 1_000_000;
|
||||||
|
pub const ApprovalDeposit: u128 = 1_000_000;
|
||||||
|
pub const AssetsStringLimit: u32 = 50;
|
||||||
|
pub const RemoveItemsLimit: u32 = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl pallet_assets::Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type Balance = Balance;
|
||||||
|
type AssetId = AssetIdForAssets;
|
||||||
|
type Currency = Balances;
|
||||||
|
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<AccountId>>;
|
||||||
|
type ForceOrigin = EnsureRoot<AccountId>;
|
||||||
|
type AssetDeposit = AssetDeposit;
|
||||||
|
type MetadataDepositBase = MetadataDepositBase;
|
||||||
|
type MetadataDepositPerByte = MetadataDepositPerByte;
|
||||||
|
type AssetAccountDeposit = AssetAccountDeposit;
|
||||||
|
type ApprovalDeposit = ApprovalDeposit;
|
||||||
|
type StringLimit = AssetsStringLimit;
|
||||||
|
type Freezer = ();
|
||||||
|
type Extra = ();
|
||||||
|
type WeightInfo = ();
|
||||||
|
type RemoveItemsLimit = RemoveItemsLimit;
|
||||||
|
type AssetIdParameter = AssetIdForAssets;
|
||||||
|
type CallbackHandle = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const RelayLocation: MultiLocation = Here.into_location();
|
||||||
|
pub const AnyNetwork: Option<NetworkId> = None;
|
||||||
|
pub UniversalLocation: InteriorMultiLocation = (ByGenesis([0; 32]), Parachain(42)).into();
|
||||||
|
pub UnitWeightCost: u64 = 1_000;
|
||||||
|
pub static AdvertisedXcmVersion: u32 = 3;
|
||||||
|
pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000);
|
||||||
|
pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (Concrete(RelayLocation::get()), 1, 1);
|
||||||
|
pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into());
|
||||||
|
pub const MaxInstructions: u32 = 100;
|
||||||
|
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||||
|
pub CheckingAccount: AccountId = XcmPallet::check_account();
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetIdForAssets = u128;
|
||||||
|
|
||||||
|
pub struct FromMultiLocationToAsset<MultiLocation, AssetId>(
|
||||||
|
core::marker::PhantomData<(MultiLocation, AssetId)>,
|
||||||
|
);
|
||||||
|
impl MaybeEquivalence<MultiLocation, AssetIdForAssets>
|
||||||
|
for FromMultiLocationToAsset<MultiLocation, AssetIdForAssets>
|
||||||
|
{
|
||||||
|
fn convert(value: &MultiLocation) -> Option<AssetIdForAssets> {
|
||||||
|
match value {
|
||||||
|
MultiLocation { parents: 0, interior: Here } => Some(0 as AssetIdForAssets),
|
||||||
|
MultiLocation { parents: 1, interior: Here } => Some(1 as AssetIdForAssets),
|
||||||
|
MultiLocation { parents: 0, interior: X2(PalletInstance(1), GeneralIndex(index)) }
|
||||||
|
if ![0, 1].contains(index) =>
|
||||||
|
Some(*index as AssetIdForAssets),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_back(value: &AssetIdForAssets) -> Option<MultiLocation> {
|
||||||
|
match value {
|
||||||
|
0u128 => Some(MultiLocation { parents: 1, interior: Here }),
|
||||||
|
para_id @ 1..=1000 =>
|
||||||
|
Some(MultiLocation { parents: 1, interior: X1(Parachain(*para_id as u32)) }),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, AnyNetwork>;
|
||||||
|
pub type LocalAssetsTransactor = FungiblesAdapter<
|
||||||
|
Assets,
|
||||||
|
ConvertedConcreteId<
|
||||||
|
AssetIdForAssets,
|
||||||
|
Balance,
|
||||||
|
FromMultiLocationToAsset<MultiLocation, AssetIdForAssets>,
|
||||||
|
JustTry,
|
||||||
|
>,
|
||||||
|
SovereignAccountOf,
|
||||||
|
AccountId,
|
||||||
|
NoChecking,
|
||||||
|
CheckingAccount,
|
||||||
|
>;
|
||||||
|
|
||||||
|
type OriginConverter = (
|
||||||
|
pallet_xcm::XcmPassthrough<RuntimeOrigin>,
|
||||||
|
SignedAccountId32AsNative<AnyNetwork, RuntimeOrigin>,
|
||||||
|
);
|
||||||
|
type Barrier = AllowUnpaidExecutionFrom<Everything>;
|
||||||
|
|
||||||
|
pub struct DummyWeightTrader;
|
||||||
|
impl WeightTrader for DummyWeightTrader {
|
||||||
|
fn new() -> Self {
|
||||||
|
DummyWeightTrader
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buy_weight(
|
||||||
|
&mut self,
|
||||||
|
_weight: Weight,
|
||||||
|
_payment: xcm_executor::Assets,
|
||||||
|
_context: &XcmContext,
|
||||||
|
) -> Result<xcm_executor::Assets, XcmError> {
|
||||||
|
Ok(xcm_executor::Assets::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct XcmConfig;
|
||||||
|
impl xcm_executor::Config for XcmConfig {
|
||||||
|
type RuntimeCall = RuntimeCall;
|
||||||
|
type XcmSender = TestMessageSender;
|
||||||
|
type AssetTransactor = LocalAssetsTransactor;
|
||||||
|
type OriginConverter = OriginConverter;
|
||||||
|
type IsReserve = ();
|
||||||
|
type IsTeleporter = ();
|
||||||
|
type UniversalLocation = UniversalLocation;
|
||||||
|
type Barrier = Barrier;
|
||||||
|
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
|
||||||
|
type Trader = DummyWeightTrader;
|
||||||
|
type ResponseHandler = XcmPallet;
|
||||||
|
type AssetTrap = XcmPallet;
|
||||||
|
type AssetLocker = ();
|
||||||
|
type AssetExchanger = ();
|
||||||
|
type AssetClaims = XcmPallet;
|
||||||
|
type SubscriptionService = XcmPallet;
|
||||||
|
type PalletInstancesInfo = ();
|
||||||
|
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||||
|
type FeeManager = ();
|
||||||
|
type MessageExporter = ();
|
||||||
|
type UniversalAliases = Nothing;
|
||||||
|
type CallDispatcher = RuntimeCall;
|
||||||
|
type SafeCallFilter = Everything;
|
||||||
|
type Aliasers = Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub TreasuryAccountId: AccountId = AccountId::new([42u8; 32]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TreasuryToAccount;
|
||||||
|
impl ConvertLocation<AccountId> for TreasuryToAccount {
|
||||||
|
fn convert_location(location: &MultiLocation) -> Option<AccountId> {
|
||||||
|
match location {
|
||||||
|
MultiLocation {
|
||||||
|
parents: 1,
|
||||||
|
interior:
|
||||||
|
X2(Parachain(42), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }),
|
||||||
|
} => Some(TreasuryAccountId::get()), // Hardcoded test treasury account id
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SovereignAccountOf = (
|
||||||
|
AccountId32Aliases<AnyNetwork, AccountId>,
|
||||||
|
TreasuryToAccount,
|
||||||
|
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
parameter_types! {
|
||||||
|
pub ReachableDest: Option<MultiLocation> = Some(Parachain(1000).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
impl pallet_xcm::Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||||
|
type XcmRouter = TestMessageSender;
|
||||||
|
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||||
|
type XcmExecuteFilter = Everything;
|
||||||
|
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||||
|
type XcmTeleportFilter = Everything;
|
||||||
|
type XcmReserveTransferFilter = Everything;
|
||||||
|
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
|
||||||
|
type UniversalLocation = UniversalLocation;
|
||||||
|
type RuntimeOrigin = RuntimeOrigin;
|
||||||
|
type RuntimeCall = RuntimeCall;
|
||||||
|
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||||
|
type AdvertisedXcmVersion = AdvertisedXcmVersion;
|
||||||
|
type TrustedLockers = ();
|
||||||
|
type SovereignAccountOf = SovereignAccountOf;
|
||||||
|
type Currency = Balances;
|
||||||
|
type CurrencyMatcher = IsConcrete<RelayLocation>;
|
||||||
|
type MaxLockers = frame_support::traits::ConstU32<8>;
|
||||||
|
type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>;
|
||||||
|
type RemoteLockConsumerIdentifier = ();
|
||||||
|
type WeightInfo = pallet_xcm::TestWeightInfo;
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
type ReachableDest = ReachableDest;
|
||||||
|
type AdminOrigin = EnsureRoot<AccountId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UNITS: Balance = 1_000_000_000_000;
|
||||||
|
pub const INITIAL_BALANCE: Balance = 100 * UNITS;
|
||||||
|
pub const MINIMUM_BALANCE: Balance = 1 * UNITS;
|
||||||
|
|
||||||
|
pub fn sibling_chain_account_id(para_id: u32, account: [u8; 32]) -> AccountId {
|
||||||
|
let location: MultiLocation =
|
||||||
|
(Parent, Parachain(para_id), Junction::AccountId32 { id: account, network: None }).into();
|
||||||
|
SovereignAccountOf::convert_location(&location).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||||
|
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||||
|
let admin_account: AccountId = AccountId::new([0u8; 32]);
|
||||||
|
pallet_assets::GenesisConfig::<Test> {
|
||||||
|
assets: vec![
|
||||||
|
(0, admin_account.clone(), true, MINIMUM_BALANCE),
|
||||||
|
(1, admin_account.clone(), true, MINIMUM_BALANCE),
|
||||||
|
(100, admin_account.clone(), true, MINIMUM_BALANCE),
|
||||||
|
],
|
||||||
|
metadata: vec![
|
||||||
|
(0, "Native token".encode(), "NTV".encode(), 12),
|
||||||
|
(1, "Relay token".encode(), "RLY".encode(), 12),
|
||||||
|
(100, "Test token".encode(), "TST".encode(), 12),
|
||||||
|
],
|
||||||
|
accounts: vec![
|
||||||
|
(0, sibling_chain_account_id(42, [3u8; 32]), INITIAL_BALANCE),
|
||||||
|
(1, TreasuryAccountId::get(), INITIAL_BALANCE),
|
||||||
|
(100, TreasuryAccountId::get(), INITIAL_BALANCE),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.assimilate_storage(&mut t)
|
||||||
|
.unwrap();
|
||||||
|
let mut ext = sp_io::TestExternalities::new(t);
|
||||||
|
ext.execute_with(|| System::set_block_number(1));
|
||||||
|
ext
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_block() {
|
||||||
|
System::set_block_number(System::block_number() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_to(block_number: BlockNumber) {
|
||||||
|
while System::block_number() < block_number {
|
||||||
|
next_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod mock;
|
||||||
|
mod pay;
|
||||||
|
mod salary;
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Tests for making sure `PayOverXcm::pay` generates the correct message and sends it to the
|
||||||
|
//! correct destination
|
||||||
|
|
||||||
|
use super::{mock::*, *};
|
||||||
|
use frame_support::{assert_ok, traits::tokens::Pay};
|
||||||
|
|
||||||
|
/// Type representing both a location and an asset that is held at that location.
|
||||||
|
/// The id of the held asset is relative to the location where it is being held.
|
||||||
|
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct AssetKind {
|
||||||
|
destination: MultiLocation,
|
||||||
|
asset_id: AssetId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocatableAssetKindConverter;
|
||||||
|
impl sp_runtime::traits::Convert<AssetKind, LocatableAssetId> for LocatableAssetKindConverter {
|
||||||
|
fn convert(value: AssetKind) -> LocatableAssetId {
|
||||||
|
LocatableAssetId { asset_id: value.asset_id, location: value.destination }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub SenderAccount: AccountId = AccountId::new([3u8; 32]);
|
||||||
|
pub InteriorAccount: InteriorMultiLocation = AccountId32 { id: SenderAccount::get().into(), network: None }.into();
|
||||||
|
pub InteriorBody: InteriorMultiLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into();
|
||||||
|
pub Timeout: BlockNumber = 5; // 5 blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scenario:
|
||||||
|
/// Account #3 on the local chain, parachain 42, controls an amount of funds on parachain 2.
|
||||||
|
/// [`PayOverXcm::pay`] creates the correct message for account #3 to pay another account, account
|
||||||
|
/// #5, on parachain 2, remotely, in its native token.
|
||||||
|
#[test]
|
||||||
|
fn pay_over_xcm_works() {
|
||||||
|
let recipient = AccountId::new([5u8; 32]);
|
||||||
|
let asset_kind =
|
||||||
|
AssetKind { destination: (Parent, Parachain(2)).into(), asset_id: Here.into() };
|
||||||
|
let amount = 10 * UNITS;
|
||||||
|
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// Check starting balance
|
||||||
|
assert_eq!(mock::Assets::balance(0, &recipient), 0);
|
||||||
|
|
||||||
|
assert_ok!(PayOverXcm::<
|
||||||
|
InteriorAccount,
|
||||||
|
TestMessageSender,
|
||||||
|
TestQueryHandler<TestConfig, BlockNumber>,
|
||||||
|
Timeout,
|
||||||
|
AccountId,
|
||||||
|
AssetKind,
|
||||||
|
LocatableAssetKindConverter,
|
||||||
|
AliasesIntoAccountId32<AnyNetwork, AccountId>,
|
||||||
|
>::pay(&recipient, asset_kind, amount));
|
||||||
|
|
||||||
|
let expected_message = Xcm(vec![
|
||||||
|
DescendOrigin(AccountId32 { id: SenderAccount::get().into(), network: None }.into()),
|
||||||
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||||
|
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
|
||||||
|
destination: (Parent, Parachain(42)).into(),
|
||||||
|
query_id: 1,
|
||||||
|
max_weight: Weight::zero(),
|
||||||
|
})])),
|
||||||
|
TransferAsset {
|
||||||
|
assets: (Here, amount).into(),
|
||||||
|
beneficiary: AccountId32 { id: recipient.clone().into(), network: None }.into(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
let expected_hash = fake_message_hash(&expected_message);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
sent_xcm(),
|
||||||
|
vec![((Parent, Parachain(2)).into(), expected_message, expected_hash)]
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, message, hash) = sent_xcm()[0].clone();
|
||||||
|
let message =
|
||||||
|
Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::from(message.clone());
|
||||||
|
|
||||||
|
// Execute message in parachain 2 with parachain 42's origin
|
||||||
|
let origin = (Parent, Parachain(42));
|
||||||
|
XcmExecutor::<XcmConfig>::execute_xcm(origin, message, hash, Weight::MAX);
|
||||||
|
assert_eq!(mock::Assets::balance(0, &recipient), amount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scenario:
|
||||||
|
/// A pluralistic body, a Treasury, on the local chain, parachain 42, controls an amount of funds
|
||||||
|
/// on parachain 2. [`PayOverXcm::pay`] creates the correct message for the treasury to pay
|
||||||
|
/// another account, account #7, on parachain 2, remotely, in the relay's token.
|
||||||
|
#[test]
|
||||||
|
fn pay_over_xcm_governance_body() {
|
||||||
|
let recipient = AccountId::new([7u8; 32]);
|
||||||
|
let asset_kind =
|
||||||
|
AssetKind { destination: (Parent, Parachain(2)).into(), asset_id: Parent.into() };
|
||||||
|
let amount = 10 * UNITS;
|
||||||
|
|
||||||
|
let relay_asset_index = 1;
|
||||||
|
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// Check starting balance
|
||||||
|
assert_eq!(mock::Assets::balance(relay_asset_index, &recipient), 0);
|
||||||
|
|
||||||
|
assert_ok!(PayOverXcm::<
|
||||||
|
InteriorBody,
|
||||||
|
TestMessageSender,
|
||||||
|
TestQueryHandler<TestConfig, BlockNumber>,
|
||||||
|
Timeout,
|
||||||
|
AccountId,
|
||||||
|
AssetKind,
|
||||||
|
LocatableAssetKindConverter,
|
||||||
|
AliasesIntoAccountId32<AnyNetwork, AccountId>,
|
||||||
|
>::pay(&recipient, asset_kind, amount));
|
||||||
|
|
||||||
|
let expected_message = Xcm(vec![
|
||||||
|
DescendOrigin(Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into()),
|
||||||
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||||
|
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
|
||||||
|
destination: (Parent, Parachain(42)).into(),
|
||||||
|
query_id: 1,
|
||||||
|
max_weight: Weight::zero(),
|
||||||
|
})])),
|
||||||
|
TransferAsset {
|
||||||
|
assets: (Parent, amount).into(),
|
||||||
|
beneficiary: AccountId32 { id: recipient.clone().into(), network: None }.into(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
let expected_hash = fake_message_hash(&expected_message);
|
||||||
|
assert_eq!(
|
||||||
|
sent_xcm(),
|
||||||
|
vec![((Parent, Parachain(2)).into(), expected_message, expected_hash)]
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, message, hash) = sent_xcm()[0].clone();
|
||||||
|
let message =
|
||||||
|
Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::from(message.clone());
|
||||||
|
|
||||||
|
// Execute message in parachain 2 with parachain 42's origin
|
||||||
|
let origin = (Parent, Parachain(42));
|
||||||
|
XcmExecutor::<XcmConfig>::execute_xcm(origin, message, hash, Weight::MAX);
|
||||||
|
assert_eq!(mock::Assets::balance(relay_asset_index, &recipient), amount);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Tests for the integration between `PayOverXcm` and the salary pallet
|
||||||
|
|
||||||
|
use super::{mock::*, *};
|
||||||
|
|
||||||
|
use frame_support::{
|
||||||
|
assert_ok,
|
||||||
|
traits::{tokens::GetSalary, RankedMembers},
|
||||||
|
};
|
||||||
|
use sp_runtime::{traits::ConvertToValue, DispatchResult};
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub Interior: InteriorMultiLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into();
|
||||||
|
pub Timeout: BlockNumber = 5;
|
||||||
|
pub AssetHub: MultiLocation = (Parent, Parachain(1)).into();
|
||||||
|
pub AssetIdGeneralIndex: u128 = 100;
|
||||||
|
pub AssetHubAssetId: AssetId = (PalletInstance(1), GeneralIndex(AssetIdGeneralIndex::get())).into();
|
||||||
|
pub LocatableAsset: LocatableAssetId = LocatableAssetId { asset_id: AssetHubAssetId::get(), location: AssetHub::get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
type SalaryPayOverXcm = PayOverXcm<
|
||||||
|
Interior,
|
||||||
|
TestMessageSender,
|
||||||
|
TestQueryHandler<TestConfig, BlockNumber>,
|
||||||
|
Timeout,
|
||||||
|
AccountId,
|
||||||
|
(),
|
||||||
|
ConvertToValue<LocatableAsset>,
|
||||||
|
AliasesIntoAccountId32<AnyNetwork, AccountId>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Rank = u128;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static CLUB: RefCell<BTreeMap<AccountId, Rank>> = RefCell::new(BTreeMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestClub;
|
||||||
|
impl RankedMembers for TestClub {
|
||||||
|
type AccountId = AccountId;
|
||||||
|
type Rank = Rank;
|
||||||
|
|
||||||
|
fn min_rank() -> Self::Rank {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn rank_of(who: &Self::AccountId) -> Option<Self::Rank> {
|
||||||
|
CLUB.with(|club| club.borrow().get(who).cloned())
|
||||||
|
}
|
||||||
|
fn induct(who: &Self::AccountId) -> DispatchResult {
|
||||||
|
CLUB.with(|club| club.borrow_mut().insert(who.clone(), 0));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn promote(who: &Self::AccountId) -> DispatchResult {
|
||||||
|
CLUB.with(|club| {
|
||||||
|
club.borrow_mut().entry(who.clone()).and_modify(|rank| *rank += 1);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn demote(who: &Self::AccountId) -> DispatchResult {
|
||||||
|
CLUB.with(|club| match club.borrow().get(who) {
|
||||||
|
None => Err(sp_runtime::DispatchError::Unavailable),
|
||||||
|
Some(&0) => {
|
||||||
|
club.borrow_mut().remove(&who);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Some(_) => {
|
||||||
|
club.borrow_mut().entry(who.clone()).and_modify(|rank| *rank += 1);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_rank(who: AccountId, rank: u128) {
|
||||||
|
CLUB.with(|club| club.borrow_mut().insert(who, rank));
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const RegistrationPeriod: BlockNumber = 2;
|
||||||
|
pub const PayoutPeriod: BlockNumber = 2;
|
||||||
|
pub const FixedSalaryAmount: Balance = 10 * UNITS;
|
||||||
|
pub static Budget: Balance = FixedSalaryAmount::get();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FixedSalary;
|
||||||
|
impl GetSalary<Rank, AccountId, Balance> for FixedSalary {
|
||||||
|
fn get_salary(_rank: Rank, _who: &AccountId) -> Balance {
|
||||||
|
FixedSalaryAmount::get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl pallet_salary::Config for Test {
|
||||||
|
type WeightInfo = ();
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type Paymaster = SalaryPayOverXcm;
|
||||||
|
type Members = TestClub;
|
||||||
|
type Salary = FixedSalary;
|
||||||
|
type RegistrationPeriod = RegistrationPeriod;
|
||||||
|
type PayoutPeriod = PayoutPeriod;
|
||||||
|
type Budget = Budget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scenario:
|
||||||
|
/// The salary pallet is used to pay a member over XCM.
|
||||||
|
/// The correct XCM message is generated and when executed in the remote chain,
|
||||||
|
/// the member receives the salary.
|
||||||
|
#[test]
|
||||||
|
fn salary_pay_over_xcm_works() {
|
||||||
|
let recipient = AccountId::new([1u8; 32]);
|
||||||
|
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// Set the recipient as a member of a ranked collective
|
||||||
|
set_rank(recipient.clone(), 1);
|
||||||
|
|
||||||
|
// Check starting balance
|
||||||
|
assert_eq!(mock::Assets::balance(AssetIdGeneralIndex::get(), &recipient.clone()), 0);
|
||||||
|
|
||||||
|
// Use salary pallet to call `PayOverXcm::pay`
|
||||||
|
assert_ok!(Salary::init(RuntimeOrigin::signed(recipient.clone())));
|
||||||
|
run_to(5);
|
||||||
|
assert_ok!(Salary::induct(RuntimeOrigin::signed(recipient.clone())));
|
||||||
|
assert_ok!(Salary::bump(RuntimeOrigin::signed(recipient.clone())));
|
||||||
|
assert_ok!(Salary::register(RuntimeOrigin::signed(recipient.clone())));
|
||||||
|
run_to(7);
|
||||||
|
assert_ok!(Salary::payout(RuntimeOrigin::signed(recipient.clone())));
|
||||||
|
|
||||||
|
// Get message from mock transport layer
|
||||||
|
let (_, message, hash) = sent_xcm()[0].clone();
|
||||||
|
// Change type from `Xcm<()>` to `Xcm<RuntimeCall>` to be able to execute later
|
||||||
|
let message =
|
||||||
|
Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::from(message.clone());
|
||||||
|
|
||||||
|
let expected_message: Xcm<RuntimeCall> = Xcm::<RuntimeCall>(vec![
|
||||||
|
DescendOrigin(Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into()),
|
||||||
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||||
|
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
|
||||||
|
destination: (Parent, Parachain(42)).into(),
|
||||||
|
query_id: 1,
|
||||||
|
max_weight: Weight::zero(),
|
||||||
|
})])),
|
||||||
|
TransferAsset {
|
||||||
|
assets: (AssetHubAssetId::get(), FixedSalaryAmount::get()).into(),
|
||||||
|
beneficiary: AccountId32 { id: recipient.clone().into(), network: None }.into(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert_eq!(message, expected_message);
|
||||||
|
|
||||||
|
// Execute message as the asset hub
|
||||||
|
XcmExecutor::<XcmConfig>::execute_xcm((Parent, Parachain(42)), message, hash, Weight::MAX);
|
||||||
|
|
||||||
|
// Recipient receives the payment
|
||||||
|
assert_eq!(
|
||||||
|
mock::Assets::balance(AssetIdGeneralIndex::get(), &recipient),
|
||||||
|
FixedSalaryAmount::get()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user